Hello,
I’m coming back to finally read and answer some old threads on our forum I’m not sure if this thread was fully resolved, I see in the last post you ask how to determine what enemy is hit using the ray-cast.
Your code seems OK (except missing FindBehavior, see below) and in general following the “3D FPS Game” template code is the simplest approach. To be clear what happens there:
-
We have a TEnemy
class, a descendant of TCastleBehavior ( Behaviors | Manual | Castle Game Engine ) . We add an instance of TEnemy
to anything that can be hit by a ray.
-
Each time you press the key / mouse button to shoot, we make a “ray cast” to see whether the ray hits anything that has TEnemy
attached.
In the example code, we check MainViewport.TransformUnderMouse
. This is actually a simple way of doing a “ray cast” – CGE makes a ray cast to determine the MainViewport.TransformUnderMouse
value.
I see in your code you do a “ray cast” using MainViewport.Items.WorldRay(...)
which is also perfectly OK, this is a way to check arbitrary ray (from any position, along any direction).
Once you have a RayCollision
, you can look at the first object hit. It will be a TCastleTransform instance. To be safe, you can actually chose first TCastleTransform instance on RayCollision that is not csTransient
. Though for TCastleScene, choosing just the first TCastleTransform instance on RayCollision by RayCollision[0]
will be equally good.
Once you have this, you should check whether the given transform has TEnemy behavior, using ExampleTransform.FindBehavior(TEnemy)
. This bit seems missing from the screenshot of your code in Doom RPG-like game and questions about it - #48 by sthox .
In total, to account for everything I wrote, I can define a function like this:
{ Extract enemy hit, given RayCollision instance as input.
RayCollision instance may be obtained by using
@link(TCastleViewport.MouseRayHit)
or calling SomeViewport.Items.WorldRay(...).
Returns nil if we didn't hit any enemy. }
function GetEnemyFromRayCollision(const RayCollision: TRayCollision): TEnemy;
var
I: Integer;
T, FirstTransform: TCastleTransform;
begin
FirstTransform := nil;
if RayCollision <> nil then
for I := 0 to MouseRayHit.Count - 1 do
begin
T := MouseRayHit[I].Item;
if not (csTransient in T.ComponentStyle) then
begin
FirstTransform := T;
break;
end;
end;
if FirstTransform <> nil then
Result := FirstTransform.FindBehavior(TEnemy)
else
Result := nil;
end;
And then in your code, you can do
if Event.IsMouseButton(...) then
begin
RayCollision := MainViewport.Items.WorldRay(...);
try
Enemy := GetEnemyFromRayCollision(RayCollision);
if Enemy <> nil then
begin
// act on Enemy being hit
end;
finally FreeAndNil(RayCollision) end;
end;
I am thinking how to make writing things like GetEnemyFromRayCollision
unnecessary / simpler in future games
Let me know if this helps, and / or if there are any remaining pending questions here.