Hi, do you know how I can revamp the check the collision with the ground from the third person navigation example, to work with the Transform object with multiple Scenes in it, and not a single model Scene like in that example? I would assume it’s the same process, just with Transforms rather than Scenes, reading the API and seeing how AbstractTransform class supports “raycast”, “direction” and “middle”.
LATEST EDIT: I tried using the commented in code provided above about using MainViewport’s “Items” Abstract Root Transform for the physics raycast and adding a Rigid body to the Avatar Transform, and it compiles fine now, but it lags incredibly awkwardly and annoyingly, to the point where it basically freezes.
Am I doing something wrong or is this something that will be addressed later?
{ 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 https://castle-engine.io/license). }
unit GameViewPlay;
interface
uses Classes,
CastleComponentSerialize, CastleUIControls, CastleControls,
CastleKeysMouse, CastleViewport, CastleScene, CastleSoundEngine, CastleVectors,
CastleCameras, CastleTransform, CastleInputs, CastleThirdPersonNavigation,
CastleDebugTransform, CastleSceneCore, CastleTimeUtils, CastleGLUtils,
CastleGLImages, GameEnemy;
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. }
MainViewport: TCastleViewport;
RunningTimer: TCastleTimer;
ThirdPersonNavigation: TCastleThirdPersonNavigation;
SceneLevel: TCastleScene;
AvatarTransform: TCastleTransform;
AvatarRigidBody: TCastleRigidBody;
SceneLegs: TCastleScene;
SandyJump: TCastleSound;
SandyBootstep: TCastleSound;
Silence: TCastleSound;
private
Enemies: TEnemyList;
procedure NavigationSetAnimation(const Sender: TCastleThirdPersonNavigation;
const AnimationNames: array of String);
procedure EventSandyFootstep(Sender: TObject);
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;
function Release(const Event: TInputPressRelease): Boolean; override;
end;
var
ViewMain: TViewMain;
JumpAnimation: array[0..1] of String;
RunAnimation: array[0..1] of String;
IdleAnimation: array[0..1] of String;
SandyRunning: Boolean;
MaxSingle: Single;
implementation
uses SysUtils,
CastleLoadGltf, CastleRectangles, CastleImages,
CastleBoxes, CastleColors, CastleRenderContext, CastleUtils, X3DLoad,
GameMyMesh;
{ TViewMain ----------------------------------------------------------------- }
constructor TViewMain.Create(AOwner: TComponent);
begin
inherited;
DesignUrl := 'castle-data:/gameviewmain.castle-user-interface';
end;
procedure TViewMain.Start;
begin
inherited;
RunningTimer := TCastleTimer.Create(FreeAtStop);
RunningTimer.IntervalSeconds := 1;
RunningTimer.OnTimer := {$ifdef FPC}@{$endif} EventSandyFootstep;
InsertFront(RunningTimer);
{ Critical to make camera orbiting around and movement of avatar
to follow proper direction and up.
In the AvatarTransform local coordinate system,
the avatar is moving in +X, and has up (head) in +Z. }
AvatarTransform.Orientation := otUpZDirectionX;
ThirdPersonNavigation.MouseLook := true;
ThirdPersonNavigation.OnAnimation := {$ifdef FPC}@{$endif} NavigationSetAnimation;
{ Configure parameters to move nicely using old simple physics,
see examples/third_person_navigation for comments.
Use these if you decide to move using "direct" method
(when AvatarTransform.ChangeTransform = ctDirect,
or when AvatarTransform.ChangeTransform = ctAuto and
AvatarTransform has no rigid body and collider). }
AvatarTransform.MiddleHeight := 0.9;
AvatarTransform.GrowSpeed := 10.0;
AvatarTransform.FallSpeed := 10.0;
// a bit large, but it is scaled by AvatarTransform scale = 0.1, making it 0.3 effectively
AvatarTransform.CollisionSphereRadius := 0.8;
ThirdPersonNavigation.Init;
end;
procedure TViewMain.EventSandyFootstep(Sender: TObject);
begin
if SandyRunning = true then
SoundEngine.Play(SandyBootstep)
else
SoundEngine.Play(Silence)
end;
procedure TViewMain.NavigationSetAnimation(const Sender: TCastleThirdPersonNavigation;
const AnimationNames: array of String);
begin
{ Example implementation that merely sets animation on SceneLegs,
to either TORSO_IDLE or TORSO_RUN.
Use castle-model-viewer (formerly view3dscene),
https://castle-engine.io/castle-model-viewer,
just double-click on MD3 file from CGE editor, to see available animations
(in "Animations" panel).
}
if AnimationNames[0] = 'idle' then
SceneLegs.AutoAnimation := 'TORSO_IDLE'
else
if AnimationNames[0] = 'jump' then
SceneLegs.AutoAnimation := 'TORSO_JUMP'
else
if AnimationNames[0] = 'run' then
SceneLegs.AutoAnimation := 'TORSO_RUN'
end;
procedure TViewMain.Update(const SecondsPassed: Single; var HandleInput: Boolean);
begin
inherited;
end;
function TViewMain.Press(const Event: TInputPressRelease): Boolean;
function AvatarRayCast: TCastleTransform;
var
RayCastResult: TPhysicsRayCastResult;
begin
RayCastResult := MainViewport.Items.PhysicsRayCast(
SceneLegs.Parent.LocalToWorld(SceneLegs.Middle),
SceneLegs.Parent.LocalToWorldDirection(SceneLegs.Direction),
MaxSingle,
AvatarRigidBody
);
Result := RayCastResult.Transform;
end;
begin
// Use this to handle keys:
{
if Event.IsKey(keyXxx) then
begin
// DoSomething;
Exit(true); // key was handled
end;
}
if Event.IsKey(keyX) then
begin
JumpAnimation[0] := 'jump';
SoundEngine.Play(SandyJump);
AvatarTransform.Move(Vector3(0, 3, 0), false, true);
NavigationSetAnimation(ThirdPersonNavigation, JumpAnimation);
Exit(true);
end;
if Event.IsKey(keyArrowUp) then
begin
RunAnimation[0] := 'run';
SandyRunning := true;
NavigationSetAnimation(ThirdPersonNavigation, RunAnimation);
Exit(true);
end;
if Event.IsKey(keyArrowDown) then
begin
RunAnimation[0] := 'run';
SandyRunning := true;
NavigationSetAnimation(ThirdPersonNavigation, RunAnimation);
Exit(true);
end;
if Event.IsKey(keyArrowLeft) then
begin
RunAnimation[0] := 'run';
SandyRunning := true;
NavigationSetAnimation(ThirdPersonNavigation, RunAnimation);
Exit(true);
end;
if Event.IsKey(keyArrowRight) then
begin
RunAnimation[0] := 'run';
SandyRunning := true;
NavigationSetAnimation(ThirdPersonNavigation, RunAnimation);
Exit(true);
end;
end;
function TViewMain.Release(const Event: TInputPressRelease): Boolean;
begin
{ Alternative version, using Items.PhysicsRayCast
(everything in world space coordinates).
This works equally well, showing it here just for reference.
RayCastResult := MainViewport.Items.PhysicsRayCast(
SceneAvatar.Parent.LocalToWorld(SceneAvatar.Middle),
SceneAvatar.Parent.LocalToWorldDirection(SceneAvatar.Direction),
MaxSingle,
AvatarRigidBody
);
Result := RayCastResult.Transform;
}
(* Alternative versions, using old physics,
see https://castle-engine.io/physics#_old_system_for_collisions_and_gravity .
They still work (even when you also use new physics).
if not AvatarRigidBody.Exists then
begin
{ SceneAvatar.RayCast tests a ray collision,
ignoring the collisions with SceneAvatar itself (so we don't detect our own
geometry as colliding). }
Result := SceneAvatar.RayCast(SceneAvatar.Middle, SceneAvatar.Direction);
end else
begin
{ When physics engine is working, we should not toggle Exists multiple
times in a single frame, which makes the curent TCastleTransform.RayCast not good.
So use Items.WorldRayCast, and secure from "hitting yourself" by just moving
the initial ray point by 0.5 units. }
Result := MainViewport.Items.WorldRayCast(
SceneAvatar.Middle + SceneAvatar.Direction * 0.5, SceneAvatar.Direction);
end;
*)
{ 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;
}
if Event.IsKey(keyArrowUp) then
begin
IdleAnimation[0] := 'idle';
SandyRunning := false;
NavigationSetAnimation(ThirdPersonNavigation, IdleAnimation);
Exit(true);
end;
if Event.IsKey(keyArrowDown) then
begin
IdleAnimation[0] := 'idle';
SandyRunning := false;
NavigationSetAnimation(ThirdPersonNavigation, IdleAnimation);
Exit(true);
end;
if Event.IsKey(keyArrowLeft) then
begin
IdleAnimation[0] := 'idle';
SandyRunning := false;
NavigationSetAnimation(ThirdPersonNavigation, IdleAnimation);
Exit(true);
end;
if Event.IsKey(keyArrowRight) then
begin
IdleAnimation[0] := 'idle';
SandyRunning := false;
NavigationSetAnimation(ThirdPersonNavigation, IdleAnimation);
Exit(true);
end;
end;
end.
This is what the code looks like in full, with the RigidBody added.