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:
-
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).
-
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:
-
Remove the global Mouse: TCastleScene
variable. You most likely didn’t want it.
-
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.
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: