I have an issue where my player plays an animation from standing to lying down, but when I use PhysicsRayCast to detect collisions, it behaves as if the player is still standing. The raycast seems to ignore the animation state and detects collisions as if the player hasn’t changed position.
Has anyone faced this before? Do I need to manually update the bounding box or physics after playing an animation?
PhysicsRayCast (whether you mean the one at Viewport.Items.RayCast or the TCastleRigidBody.PhysicsRayCast ) takes a position from which to shoot the ray. ( The documentation linked above clarify the coordinate system of this position – either world or parent coordinate system. ) In neither case does the animation or bounding box of the scene matter.
IOW, playing animation doesn’t change the player position, i.e. TCastleScene.Translation stays the same.
When the player who was shot is in a standing position. Here everything is good. But if he crawls on the ground here the function returns the results as if the player was in a standing position.
The player who was shot does not change his place, he only changes his animation, for example, standing. croshing. Crawling on the ground.
I found the solution :
You must execute TCastleMeshCollider.MeshChanged after play new animation
especially from standing animation to croshing or Crawling on the ground.
{ Update the collider triangles to reflect current @link(Mesh) shape.
This is necessary for physics engine to reflect the @link(Mesh).
We do it automatically in some circumstances (like when you change
collider properties, like @link(Mesh) or @link(DoubleSided),
or when you change @link(TCastleSceneCore.Url)) but we don't try to keep
the physics mesh follow the triangles every frame (as you really shouldn't
change physics mesh so often, it's supposed to be a static collider). }
procedure MeshChanged;
now i play animation like this :
procedure TPlayer.PlayAnimation(forward, Loop: Boolean; TransitionDuration: Single; Name: string);
var
CastleMeshCollider: TCastleMeshCollider;
Params: TPlayAnimationParameters;
begin
if FScene <> nil then
begin
Params := TPlayAnimationParameters.Create;
try
Params.Name := Name;
Params.forward := forward;
Params.Loop := Loop;
Params.TransitionDuration := TransitionDuration;
Params.StopNotification := OnAnimationStopped;
FScene.PlayAnimation(Params);
TTask.Run(procedure
begin
// Wait for the animation to reach the new position before updating the collider
TTask.CurrentTask.Wait(Round(TransitionDuration * 1000) + UpdateMeshAfterAnimationStartDelay {100}); //-> Wait for transition + 100ms
TThread.Queue(nil, procedure
begin
// Update Mesh after the animation transition completes
CastleMeshCollider := (Scene.FindBehavior(TCastleMeshCollider) as TCastleMeshCollider);
if CastleMeshCollider <> nil then
begin
CastleMeshCollider.InternalAutoSize;
CastleMeshCollider.MeshChanged;
end;
end);
end);
FIsPlayingAnimation := True;
FAnimationPlayedName := Name;
finally
FreeAndNil(Params);
end;
end;
end;
Ah, got it! OK, I understand now what you do better – you want TCastleMeshCollider to reflect the new animation state. Indeed, this does not happen automatically, because the MeshChanged call takes some time (it’s not something you want to do every frame) and it “forgets” all collisions with previous mesh.
But in your case, I guess this is acceptable. Calling it just once, after changing the animation, may be acceptable. And you don’t care that it “forgets” current collisions, you want the new calls to PhysicsRayCast to account for it OK.
So, overall, your solution is OK… except you really don’t need TTask.Run, TTask.CurrentTask.Wait, TThread.Queue That’s a bit complicated. See PlayAnimation docs, in particular:
Calling this method does not change the scene immediately.
…
If you really need to change the scene immediately (for example, because you don’t want to show user the initial scene state), simply call ForceInitialAnimationPose right after PlayAnimation.
So, right after FScene.PlayAnimation(Params);, it should be enough to call FScene.ForceInitialAnimationPose and then do your calls
// Update Mesh after the animation transition completes
CastleMeshCollider := (Scene.FindBehavior(TCastleMeshCollider) as TCastleMeshCollider);
if CastleMeshCollider <> nil then
begin
CastleMeshCollider.InternalAutoSize;
CastleMeshCollider.MeshChanged;
end;
No need for threads, no need to wait
Hopefully this helps, in the end you found the solution yourself, but I hope my suggestion above helps to make it even simpler
Your explanation was really helpful. I didn’t realize that ForceInitialAnimationPose could simplify things that much! Now the collider updates perfectly without the need for threads, and everything works smoothly.
I really appreciate your support and the time you take to help us. Castle Game Engine keeps getting more fun to work with thanks to your guidance!
** Optimal Collision Detection Method for a Shooter Game
After extensive testing and analysis, I have found an efficient and optimized way to handle collision detection in a shooter game.
Final Conclusion: The Best Collision Detection Approach
Use TCastleScene for Each Player Bone
Every bone should have its own scene without a collider (MeshCollider) or RigidBody.
This ensures that every part of the player’s body can be targeted accurately.
Use RayCast Instead of Traditional Physics Colliders
RayCast can detect collisions accurately without updating the mesh collider.
When a bullet is fired, a RayCast is sent from the bullet to check for collisions with the player’s bones.
This method ensures real-time, accurate hit detection, even when the player is crouching, prone, or moving dynamically.
Avoid Using MeshCollider or RigidBody for the Main Player
MeshCollider is very CPU-intensive because it requires continuous updates during animations.
RigidBody and Physics Colliders should only be used for static objects like buildings, walls, and roads.
Use Physics RayCast Only for Static Objects
When detecting collisions with the environment, buildings, terrain, Physics RayCast is useful.
For moving players, use traditional RayCast instead to ensure real-time accuracy.
Key Advantages of This Method:
Reduces CPU Load → No need to constantly update Mesh Colliders. Increases Collision Accuracy → Every body part can be hit precisely. Avoids Issues with Player Animation (Crouching, Prone, etc.). Easier to Implement Bullet Penetration & Damage Calculation.
Final Takeaway:
Use RayCast with bone-based scenes (without physical colliders) for player hit detection, and use Physics RayCast only for static objects like buildings and terrain.
This method provides the best performance and accuracy for modern shooter games!
IMO, that’s not exactly the right conclusion. Here’s my approach:
Indeed, you are right that “mesh collider” is supposed to be used for static objects only. Rebuilding mesh collider (by CastleMeshCollider.MeshChanged) is a time-consuming operation, not something you should do often.
But it doesn’t mean that non-physical RayCast is the way to go. It doesn’t actually give you more efficiency. In fact, eventually the non-physical RayCast will disappear and we will rely on PhysicsRayCast for all collision detection (see " 13. Old system for collisions and gravity" section on Physics | Manual | Castle Game Engine ).
The efficient way to have precise collisions in FPS game (and this is true for both Castle Game Engine and other engines, e.g. I used this with Unity games too long time ago) is to attach TCastleRigodBody and colliders (but not static mesh colliders!) to every bone. So you add e.g. TCastleSphereCollider for the player’s head, then 2 TCastleBoxCollider for player’s 2 arms etc. Use our ExposeTransforms mechanism to attach things, like colliders, to bones of your model.
And then you can use PhysicsRayCast with everything.
Additional benefit: if you define joints on your rigid bodies, then you should be able to do “rag-doll” on your models, e.g. let the enemies’ bodies fly into walls with realistic physics when they are dead.
TODO: I’m aware I should make a demo of everything here :), to show how it should be done. It should come!
Actually, I thought about this approach,
but then I wondered—if there are many players, say 30, and each bone has its own collider and rigid body, maybe this would be too taxing on the CPU.
So, I asked ChatGPT, and it told me, “You’re right!”
So I abandoned the idea and instead used a scene for each bone with WorldRay.
The result, I think, was quite good:
No bullets skipped
All collisions detected
But I’ll still try PhysicsRayCast with colliders + rigid bodies to see how it compares.
ChatGPT is guessing. It will tell you anything to please you. It hallucinates. It will agree to anything you ask, when you ask in the right way, it will agree that 2+2=5. It knows Castle Game Engine (and everything else) only on a very superficial level from scanning the Internet and it’s guessing a lot when the answer didn’t exactly appear on the Internet already.
To be clear, using AI for development is useful, but you need to “be in charge” and be able to verify everything it tells you. Answers to high-level questions, like “how do I do something”, may be completely wrong, unless the exact same question have been already asked before and the answer was on the Internet. Answers to details (like “why does this not compile”) are sometimes better, when you provide it enough context.
To be clear, I use Copilot for coding, and I value it. It’s useful. AI is useful. But you need to be ready to reject a lot of the things it says, because it’s very often just wrong.
It is taxing indeed, but the other solution you outlined – no colliders and just using non-physics ray casting – is also taxing See 13. Old system for collisions and gravity – our old system just assumes a box for each scene. So it also has a lot of “colliders”, they are just not visible as explicit components.
That said, having a hierarchy of colliders (with proper parameters for colliders – you will want TCastleRigidBody.Animated = true and TCastleRigidBody.Dynamic = false when animating the skeleton; change them only when the creature is dead and you want to use ragdoll) → should be fast enough. That’s in general what FPS games do.
Of course, testing this in practice is definitely wise, please report if it doesn’t work like I describe.
And I know – I should prepare a demo using it too. I have added this to my TODO.