Need Help Understanding how to Assign a Transform as an Avatar of a Third Person Navigation Camera, After Following Instructions

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.