Change the scale in 2D

Hi, I’m making a breakout game. The paddle must be able to work with three lengths: narrow, normal and long. This should be able to be changed later with the grabbing of a powerup. However, I have a problem. When I change the X in Vector3 in the code, I get an access violation error. It looks like I’m not allowed to change the X of the scale.

Example: PaddleScene.Scale := Vector3(PaddleArr[PaddleSize].Size, 0.75, 1);

Of which PaddleSize is the element of the array, so that the correct size can be taken.

Now I have read the documentation and understand that it its better to use the scale in uniform, for example (3, 3, 3) and avoid to use (3, 1, 1). But why?

Thanks and greetings.

You can change the X of the scale. Looking at this line,

PaddleScene.Scale := Vector3(PaddleArr[PaddleSize].Size, 0.75, 1);

if this causes Access Violation then likely PaddleScene is nil (or invalid pointer) or PaddleArr[PaddleSize] is nil (or invalid pointer). Add a test like

Assert(PaddleScene <> nil);
Assert(PaddleArr[PaddleSize] <> nil);

or

WritelnLog('PaddleScene? %s', [BoolToStr(PaddleScene <> nil, true)]);
WritelnLog('PaddleArr[PaddleSize]? %s', [BoolToStr(PaddleArr[PaddleSize] <> nil, true));

to see what’s going on.

Physics colliders may not work exactly when scale is non-uniform. A collider with Mode2D=true may approximate scale by taking an avarage of Scale.X and Scale.Y. A collider with Mode2D=false may approximate scale by taking an avarage of Scale.X, Scale.Y, Scale.Z.

See TCastleTransform.Scale for detail docs: Castle Game Engine: CastleTransform: Class TCastleTransform

In any case, this is unrelated to the access violation. Non-uniform scale will not cause access violation.

I have tried what you say, but the scene PaddleScene and the array PaddleArr[] are not nil. Without the scale award, the PaddleScene works.

Here, I have a code cut from Lazarus.

I have tried first the method Setup2D but that’s not the problem. I cannot change the Scale in the code.

Isn’t it weird when you have to keep all three parameters of Vector3 the same? Why can’t I change one of the parameters of Vector3? This is possible in Unity.

I hope you can see the png. If not, I don’t know how I can implement png files in a reply.

Just to be on the safe side, I’ve put the code below.

constructor TViewMain.Create(AOwner: TComponent);
begin
inherited;
DesignUrl := ‘castle-data:/gameviewmain.castle-user-interface’;
InitPaddle;
PaddleSize := Normal;

//PaddleScene.Scale := Vector3(PaddleArr[PaddleSize].Size, 0.75, 1);
//PaddleScene.Setup2D; // is not necessary
PaddleScene.Scale := Vector3(0.50, 0.75, 1.00);
end;

The InitPaddle is the array with the record.

Thanks.

Thanks for the answer! Your PNG is OK, unfortunately that’s not enough for me to debug the problem.

The assignment SomeScene.Scale := Vector3(0.5, 0.75, 1.0); is generally valid, and looking only at your code from the screenshot – I cannot tell what’s wrong, it looks good. To be sure, I just tested using

SceneMain.Scale := Vector3(0.5, 0.75, 1.0);

inside TViewMain.Start in the new project generated from our “3D Model Viewer” template and it works OK. The object looks correctly (non-uniformly) scaled. So something else must be wrong there.

Please submit a bigger code piece to debug the problem, preferably please submit a complete testcase that compiles and allows to reproduce the problem.

See my previous answer – they do not have to be the same in general. See the documentation about Scale on Castle Game Engine: CastleTransform: Class TCastleTransform – in general we try to support all scales, including non-uniform and even negative and zero.

But for perfect physics collision, non-uniform scale is a problem, due to the way internally collisions are calculated, which in turn boils down to collision code (and physics engines) wanting to be efficient. CGE is not alone in this problem, even Unity has this, albeit maybe they are less often visible. But see e.g.

We plan to integrate with another physics engine in the future , see Physics | Manual | Castle Game Engine . PhysX or Bullet. Possibly this will allow to deal with non-uniform scale better, though from what my experience (and looking also at Unity and Godot) non-uniform scale is never perfect when combined with collisions.

In any case, this is really almost for sure unrelated to your “Access Violation”. Setting non-uniform scale should be (and is, in my tests) allowed, it should never crash.

Hello,

I watched a tutorial. There I saw that a TCastleScene object is created with DesignedComponent. Should I do the same? But without it, the paddle works well. Maybe TCastleScene doesn’t have everything from PaddleScene, as it was explained in another forum.

In any case, I’ll show you what I’ve done below.


PaddleScene properties

{ Main view, where most of the application logic takes place.

Feel free to use this code as a starting point for your own projects.
This template code is in public domain, unlike most other CGE code which
is covered by BSD or LGPL (see License | Manual | Castle Game Engine). }
unit GameViewMain;

interface

uses Classes, CastleScene, CastleTransform,
CastleVectors, CastleComponentSerialize,
CastleUIControls, CastleControls, CastleKeysMouse;

type
{ Main view, where most of the application logic takes place. }
TViewMain = class(TCastleView)
published
{ Components designed using CGE editor.
These fields will be automatically initialized at Start. }
LabelFps: TCastleLabel;
PaddleScene: TCastleScene;
public
constructor Create(AOwner: TComponent); override;
procedure Start; override;
procedure Update(const SecondsPassed: Single; var HandleInput: Boolean); override;
function Press(const Event: TInputPressRelease): Boolean; override;
end;

TPaddleRec = record
Size: Single;
Left: Real;
Right: Real;
end;
TPaddleSize = (Small, Normal, Large);
var
ViewMain: TViewMain;
PaddleArr: array[TPaddleSize] of TPaddleRec;
PaddleSize: TPaddleSize;
const
PaddleY = -448;
implementation

uses SysUtils;

procedure InitPaddle;
begin
with PaddleArr[Small] do
begin
Size := 0.25;
Left := -833;
Right := 833;
end;
with PaddleArr[Normal] do
begin
Size := 0.5;
Left := -798;
Right := 798;
end;
with PaddleArr[Large] do
begin
Size := 0.75;
Left := -765;
Right := 765;
end;
end;

{ TViewMain ----------------------------------------------------------------- }

constructor TViewMain.Create(AOwner: TComponent);
begin
inherited;
DesignUrl := ‘castle-data:/gameviewmain.castle-user-interface’;
//Container.MousePosition := Vector2(0, PaddleY);
InitPaddle;
PaddleSize := Normal;

PaddleScene.Scale := Vector3(PaddleArr[PaddleSize].Size, 0.75, 1);
//PaddleScene.Scale := Vector3(0.50, 0.75, 1.00);
//PaddleScene.Setup2D;
//PaddleScene.Scale := Vector3(0.50, 0.75, 1.00);

end;

procedure TViewMain.Start;
begin
inherited;
end;

procedure TViewMain.Update(const SecondsPassed: Single; var HandleInput: Boolean);
const
MoveSpeed = 500;
var
PlayerPos: TVector3;

begin
inherited;
{ This virtual method is executed every frame (many times per second). }
Assert(LabelFps <> nil, ‘If you remove LabelFps from the design, remember to remove also the assignment “LabelFps.Caption := …” from code’);
LabelFps.Caption := 'FPS: ’ + Container.Fps.ToString + ’ X: ’ + FloatToStr(PaddleScene.Translation.X);
//PlayerPos := PaddleScene.Translation;
PlayerPos := Vector3(Container.MousePosition.X - PaddleArr[PaddleSize].Right, PaddleY, 0);
// NEW CODE WE ADD:
{if Container.Pressed[keyArrowLeft] then
PlayerPos := PlayerPos + Vector3(-MoveSpeed * SecondsPassed, 0, 0);
if Container.Pressed[keyArrowRight] then
PlayerPos := PlayerPos + Vector3(MoveSpeed * SecondsPassed, 0, 0);}
{if Container.Pressed[keyArrowDown] then
PlayerPos := PlayerPosition + Vector2(0, -MoveSpeed * SecondsPassed);
if Container.Pressed[keyArrowUp] then
PlayerPos := PlayerPosition + Vector2(0, MoveSpeed * SecondsPassed);}
if PlayerPos.X <= PaddleArr[PaddleSize].Left then
PlayerPos := Vector3(PaddleArr[PaddleSize].Left, PaddleY, 0);
if PlayerPos.X >= PaddleArr[PaddleSize].Right then
PlayerPos := Vector3(PaddleArr[PaddleSize].Right, PaddleY, 0);

PaddleScene.Translation := PlayerPos;
end;

function TViewMain.Press(const Event: TInputPressRelease): Boolean;
begin
Result := inherited;
if Result then Exit; // allow the ancestor to handle keys

{ This virtual method is executed when user presses
a key, a mouse button, or touches a touch-screen.

Note that each UI control has also events like OnPress and OnClick.
These events can be used to handle the "press", if it should do something
specific when used in that UI control.
The TViewMain.Press method should be used to handle keys
not handled in children controls.

}

// Use this to handle keys:
{
if Event.IsKey(keyXxx) then
begin
// DoSomething;
Exit(true); // key was handled
end;
}
end;

end.

After some experimentation, I found the error. A small mistake, because it turns out that I should not have put the ‘PaddleScene.Scale’ in the Create event. Now that I have placed the Scale rule in the Start event, it now works. I can now use any enum I want in the code. The Scale changes automatically.

Thanks.

Indeed that is the cause of the problem. I’m happy it is resolved :slight_smile:

To add some context:

  • During constructor (TViewMain.Create) the design is not yet loaded. This means that the TCastleScene instance that should go to PaddleScene has not been created yet.

    Setting DesignUrl only instructs the view to load the design later, when the view will be started.

  • So PaddleScene should definitely be nil at this point (during constructor, TViewMain.Create). That is why I recommended checking

    Assert(PaddleScene <> nil);
    

    or

    WritelnLog('PaddleScene? %s', [BoolToStr(PaddleScene <> nil, true)]);
    

    If you place them anywhere in constructor, they should show that PaddleScene is indeed nil at this point.

  • The design becomes loaded right before the Start method is called. At this point, the published fields of the view, like PaddleScene, are automatically initialized to the components with matching names in the design.

    So only between Start and Stop you can reliably refer to PaddleScene.

  • There is no need to use DesignedComponent anymore. The tutorial on https://www.youtube.com/watch?v=rPU-IFltcuM was done in July 2022, and since October 2022 there’s no need to use DesignedComponent (when the corresponding field is published and has a name matching in design), see news Published state fields are now automatically initialized, no need in most cases for DesignedComponent calls – Castle Game Engine . I have in TODO to record a new tutorial :slight_smile:

    In the meantime of course nothing bad will happen if you will use DesignedComponent like PaddleScene := DesignedComponent('PaddleScene') ...; You will just initialize something that was already initialized.

  • See more info about using views in manual, Managing Views | Manual | Castle Game Engine .