Using Designed Components in code

Hi!

I have used the editor to create and load mouse as TCastleScene; it is under the viewport and named ‘Mouse’.
Now I get an error when I want to acces it when I try to run the code.
How can I use the designed mouse in the code?

type
  TMouse = class;
 
  var
    Mouse: TMouse;
   
type
TMouse = class(TCastleScene)
public
  procedure CollisionEnter(const CollisionDetails: TPhysicsCollisionDetails);
  constructor Create(AOwner: TComponent); override;
 end;

constructor TMouse.Create(AOwner: TComponent);
var
  RBody: TRigidBody;
  Collider: TBoxCollider;
begin
  inherited Create(AOwner);
 
  RBody := TRigidBody.Create(Self);
  RBody.Dynamic:= true;
  RBody.Setup2D;
  RBody.OnCollisionEnter:= @CollisionEnter;

  Collider := TBoxCollider.Create(RBody);
  Collider.Size := LocalBoundingBox.Size;
  RigidBody := RBody;
end; 

constructor TStatePlay.Create(AOwner: TComponent);
begin
  inherited;
   Mouse := TMouse.Create(Application); 

MainViewport := DesignedComponent ('Viewport') as TCastleViewport;
  Mouse := DesignedComponent ('Mouse') as TCastleScene;

It’s a bit complicated here. You use an ancestor of TCastleScene i.e. it’s a TCastleScene+Something. When you load a designed Mouse - it’s only a TCastleScene and Something is missing - that’s what the compiler complains about.

In case you’d do it the other way around (assigning TCastleScene+Something to TCastleScene) the compiler would make it look like “Something” doesn’t exist. But when you need to create “a larger class” there’s simply nowhere to get that “Something” from.

What can you do here?

You need “a larger class”.

One way to do this is to make TMouse a custom component (see Editor | Manual | Castle Game Engine) - I’m not sure how it works with TCastleScene derivatives though.

Second way (I’d rather go this path, however, I’m not sure how well it’ll work with physics) - is to “wrap” the designed mouse as a simple TCastleScene inside a TMouse. I.e. TMouse would have a field MouseScene: TCastleScene and you would Mouse.MouseScene := DesignedComponent ('Mouse') as TCastleScene;.

Third way (again, I’m not sure how it’d work with physics) - simply add the designed mouse as a child for this TMouse i.e. Mouse.Add(DesignedComponent ('Mouse') as TCastleScene);.

And the fourth solution I can come up with is cloning the mouse (no pun intended) as in Mouse.Load((DesignedComponent ('Mouse') as TCastleScene).RootNode, false);. This path should work with physics and will waste RAM a bit. Also you might need to hide the original mouse too. But it looks like the simplest solution. Note that there may be some memory-management-related problems here, e.g. you might want/need to Mouse.Load((DesignedComponent ('Mouse') as TCastleScene).RootNode.DeepCopy, true); to avoid possible memory leaks or “scene added twice” error.

Adding to what Eugene said, I would point 2 solutions:

  1. Do not create a TMouse class that descends from TCastleScene. It is not really necessary. Instead have simple declaration Mouse: TCastleScene; and then just do Mouse := DesignedComponent ('Mouse') as TCastleScene;.

    You can attach the rigid body (TRigidBody.Create...) to such Mouse scene from code anyway, and thus you can attach there an event like OnCollisionEnter. You can define the handler method CollisionEnter at any other class in your application that suits (e.g. usually it could be TUIState descendant, if you follow the standard CGE application layout – the TUIState descendant then serves as a simple “default” place where you can declare methods).

  2. Another way is to keep your existing layout, and make the TMouse class known in the editor. See Editor | Manual | Castle Game Engine . It is rather straightforward:

    • Add RegisterSerializableComponent(TMouse, 'My Mouse'); to the initialization section of the unit (usually, the unit that defines TMouse). You will most likely want to have a dedicated unit for “controls known to the editor”, like MyGameControls, with TMouse.

    • In CastleEngineManifest.xml, specify editor_units, like editor_units="MyGameControls" .

    • Now you can use "“Project → Restart Editor (With Custom Components)” in the CGE editor, and in effect your editor will contain a new class TMouse. You can now design your UI using TMouse, and load it from code like Mouse := DesignedComponent ('Mouse') as TMouse; . That is, TMouse will behave like a normal CGE class, CGE editor will be aware of it.

1 Like

That is where I got stuck with “translating” the CGE examples with rigid body, because they are all connected to a class.
So attaching the rigid body directly to the Mouse Scene would be perfect.
So would this be okay:

Mouse.RigidBody := TRigidBody.Create(Self);
Mouse.RigidBody.Dynamic:= true;
Mouse.RigidBody.Setup2D;
Mouse.RigidBody.OnCollisionEnter:= @CollisionEnter;

But @CollisionEnter has to point at a class, so how can I set this up?

One way is to have a dummy event handler:

TEventHandler = class
  procedure CollisionEnter(const CollisionDetails: TPhysicsCollisionDetails);
end;
...
RBody.OnCollisionEnter:= @TEventHandler(nil).CollisionEnter;

The other - just add CollisionEnter to your TUiState.

Thanks.
Because I am learning the editor now which sets up TUiState in code I think I would prefer that option, so how can I set this up for the collisionEnter?

btw I tried the dummy event handler as you suggested and now I get an error in the rigid body code:

You need to set a proper owner for the TRigidBody. Owner is the class that will free the TRigidBody when it itself is destroyed. In your case it can be TRigidBody.Create(Mouse);.

Thanks.

Now I am confused in setting up the final Rigidbody.
What is not correct here?

I’m not sure what you are trying to achieve with this line? You have Mouse.RigidBody - what more do you need? I’m looking at physics_2d_collisions example and it seems like there is no need to do anything more with RigidBody, maybe tweak its parameters, but not assign it to some external variable.

Hm, the last line in the examples is always RigidBody := RBody;
Can I skip this as i am not inside a class in my code?
Now I have removed the line and I have:

Mouse := DesignedComponent (‘Mouse’) as TCastleScene;
MakeMousePhysics;

Now I get a SIGSEGV error in the castletransform.physics unit?

Unfortunately now it’s getting more complicated. SIGSEGV is a generic “something went wrong”, without specifying what exactly is wrong. Usually this is caused by something not properly initialized.

Sometimes it simply helps to press F8 - and the next error message might explain more verbose what exactly happened (e.g. some pointer is nil).

Try setting a breakpoint (F5) somewhere before the place where the SIGSEGV is triggered and then step-over (F8) until it hits. Find the line, then if it’s possible, try to go inside that procedure and do the same in there. Try to understand what uninitialized reference can be called in there and why.

Maybe you’re trying to use physics before the Window was shown? Or CollisionEnter refers to some variables of its class (and you pass TEventHandler(nil) - thus the class is nil i.e. uninitialized)? There are a lot of variants. If that doesn’t help - try to make a minimal code that can be compiled and post it somewhere.

Found it. Forgot to add
Mouse := TCastleScene.Create(Mouse);

Now I get

Probably because I created MouseScene 2 times; in the TUIstateplay and as a variable outside of it.
So something is wrong in the code. When I remove either of the mousescenes I get a SIGSEGV or the message above. Maybe I should put all code from the MakeMousePhysics in the Stateplay or?





type
    TStatePlay = class(TUIState)
    private
     Mouse: 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;    

var
     Mouse: TCastleScene;
     MainViewport: TCastleViewport;

procedure TStatePlay.Start;
begin
  inherited;
   DesignUrl := 'castle-data:/westbeach-gamestateplay.castle-user-interface';
   MainViewport := DesignedComponent ('Viewport') as TCastleViewport;
   Mouse := DesignedComponent ('Mouse') as TCastleScene;
 
   MakeMousePhysics;
end;

procedure TStatePlay.Update(const SecondsPassed: Single; var HandleInput: Boolean);
begin
  Mouse.Translation := Vector3(Mainviewport.PositionTo2DWorld(Container.MousePosition, true), 9);
end;                                                                                  

procedure MakeMousePhysics;
var
  Size: TVector3;
  Collider: TBoxCollider;
begin
  Mouse := TCastleScene.Create(Mouse);
  Mouse.RigidBody := TRigidBody.Create(Mouse);
  Mouse.RigidBody.Dynamic:= true;
  Mouse.RigidBody.Setup2D;
  Mouse.RigidBody.OnCollisionEnter:= @TEventHandler(nil).CollisionEnter;

  Collider := TBoxCollider.Create(Mouse.RigidBody);

  Size := Mouse.LocalBoundingBox.Size;
  Size.Z := Max(0.1, Size.Z);
  Collider.Size := Size;

end;

Sorry, that’s a bit nonsense. You create an instance of Mouse and use previous value of Mouse as an owner.

You should not need to create it at all. After all, you designed the Mouse scene, and loaded it above Mouse := DesignedComponent ('Mouse') as TCastleScene; .

However, you have 2 variables called Mouse in your code: one is global, one is a field inside TStatePlay. This is most likely a mistake, you wanted to have only 1 such variable.

So:

  1. Remove the global Mouse: TCastleScene variable. You most likely didn’t want it.

  2. Change MakeMousePhysics from a global routine to a private method inside TStatePlay. This way MakeMousePhysics will access the Mouse field, the same field you initialize by Mouse := DesignedComponent ('Mouse') as TCastleScene;.

1 Like

I tried but I don’t know how to do this in TStatePlay. Do you have an example for putting the code from MakeMousePhysics procedure into TStatePlay? Thanks.

Just add it into a private area:

type
    TStatePlay = class(TUIState)
    private
       Mouse: TCastleScene;
       procedure MakeMousePhysics;
    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;
...
procedure TStatePlay.MakeMousePhysics;
begin
  ...(do something with Mouse)...
end;

Thanks!
I was almost there but forgot putting the TStatePlay. before the MakeMousePhysics procedure.

Now the code is running without runtime errors and I can move the mouse around.
So for today I’m happy. :slight_smile:
Next thing will be adding a second CastleScene so I can try if collision actually works.
To be continued…

1 Like

Finally I tried to add collision, also using the StatePlay but got 2 errors.
What is wrong in the code here?

 type
    TStatePlay = class(TUIState)
    private
      Mouse: TCastleScene;
      StandIdleAnim: TCastleScene;
      procedure MakeMousePhysics;
      procedure MouseCollision(const CollisionDetails: TPhysicsCollisionDetails);
      procedure MakeStandIdlePhysics;

    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;     
    
    var
  MainViewport: TCastleViewport;

Procedure TStatePlay.MouseCollision(const CollisionDetails: TPhysicsCollisionDetails);
begin
  if CollisionDetails.OtherTransform <> nil then
   begin
     if CollisionDetails.OtherTransform is StatePlay.StandIdleAnim then Application.Terminate;
   end;
end;

Procedure TStatePlay.MakeMousePhysics;
 var
  Size: TVector3;
  Collider: TBoxCollider;
begin
  Mouse.RigidBody := TRigidBody.Create(Mouse);
  Mouse.RigidBody.Dynamic:= true;
  Mouse.RigidBody.Setup2D;
  Mouse.RigidBody.OnCollisionEnter:= @TStatePlay.MouseCollision;

  Collider := TBoxCollider.Create(Mouse.RigidBody);

  Size := Mouse.LocalBoundingBox.Size;
  Size.Z := Max(0.1, Size.Z);
  Collider.Size := Size;
end;                       

Class or interface type expected, but got “TCastleScene”

Means that A is B checks if “A” is of type “B”, so it expects “B” to be a type (like a class, e.g. TCastleScene, not a class instance (as in variable of class type)). I’m not exactly sure what you are trying to achieve with the code above, but if you want to check if OtherTransform equals to StandIdleAnim then you should simply use “=” symbol. I.e.

if CollisionDetails.OtherTransform = StatePlay.StandIdleAnim then Application.Terminate;

Error: Incompatible type for arg no. 1

You are referencing the (non-class) procedure by class type without class instance. I.e. the compiler can’t call MouseCollision without class instance - it should at least be nil (as in TEventHandler(nil).SomeProcedure). However, in this case you are refering to the procedure within this class and so you should use this class instance called Self - as in Mouse.RigidBody.OnCollisionEnter:= @Self.MouseCollision; or in most situations you may drop the “Self” keyword and just use Mouse.RigidBody.OnCollisionEnter:= @MouseCollision; as MouseCollision is available in the current namespace.

Thanks!

Okay, now the code is running again but when I move the mouse scene over the “standidle” scene still nothing happens (it should end Application as a test if collision actually works). So what could still be wrong?
This is the setup in the editor:

image