Need Help Making Collision with Ground Work Correctly

Hi everyone,

I needed help in a previous thread with tweaking the fine details of a character colliding with the ground, ensuring that it collides with the ground at exactly the right height so it doesn’t register as touching the ground while it’s actually still in the air.

My code looks like this right now:

{
  Copyright 2020-2023 Michalis Kamburelis.

  This file is part of "Castle Game Engine".

  "Castle Game Engine" is free software; see the file COPYING.txt,
  included in this distribution, for details about the copyright.

  "Castle Game Engine" is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

  ----------------------------------------------------------------------------
}

{ Main "playing game" view, where most of the game logic takes place. }
unit GameViewPlay;

{.$define CASTLE_UNFINISHED_CHANGE_TRANSFORMATION_BY_FORCE}

interface

uses Classes,
  CastleComponentSerialize, CastleUIControls, CastleControls,
  CastleKeysMouse, CastleViewport, CastleScene, CastleVectors, CastleCameras,
  CastleTransform, CastleInputs, CastleThirdPersonNavigation, CastleDebugTransform,
  CastleSceneCore,
  GameEnemy;

type
  { Main "playing game" view, where most of the game logic takes place. }
  TViewPlay = class(TCastleView)
  published
    { Components designed using CGE editor.
      These fields will be automatically initialized at Start. }
    LabelFps: TCastleLabel;
    MainViewport: TCastleViewport;
    AvatarTransform: TCastleTransform;
    CapsuleCollider: TCastleCollider;
    SandyLegs, SandyHead, SandyTorso, SceneLevel: TCastleScene;
    AvatarRigidBody: TCastleRigidBody;
    CheckboxCameraFollows: TCastleCheckbox;
    CheckboxAimAvatar: TCastleCheckbox;
    CheckboxDebugAvatarColliders: TCastleCheckbox;
    CheckboxImmediatelyFixBlockedCamera: TCastleCheckbox;
    SliderAirRotationControl: TCastleFloatSlider;
    SliderAirMovementControl: TCastleFloatSlider;
    ButtonChangeTransformationAuto,
      ButtonChangeTransformationDirect,
      ButtonChangeTransformationVelocity,
      ButtonChangeTransformationForce: TCastleButton;
  private
    { Enemies behaviors }
    Enemies: TEnemyList;

    DebugAvatar: TDebugTransform;
    { Change things after ThirdPersonNavigation.ChangeTransformation changed. }
    procedure UpdateAfterChangeTransformation;
    procedure ChangeCheckboxCameraFollows(Sender: TObject);
    procedure ChangeCheckboxAimAvatar(Sender: TObject);
    procedure ChangeCheckboxDebugAvatarColliders(Sender: TObject);
    procedure ChangeCheckboxImmediatelyFixBlockedCamera(Sender: TObject);
    procedure ChangeAirRotationControl(Sender: TObject);
    procedure ChangeAirMovementControl(Sender: TObject);
    procedure ClickChangeTransformationAuto(Sender: TObject);
    procedure ClickChangeTransformationDirect(Sender: TObject);
    procedure ClickChangeTransformationVelocity(Sender: TObject);
    procedure ClickChangeTransformationForce(Sender: TObject);
  public
    constructor Create(AOwner: TComponent); override;
    procedure Start; override;
    procedure Stop; override;
    procedure Update(const SecondsPassed: Single; var HandleInput: Boolean); override;
    function Press(const Event: TInputPressRelease): Boolean; override;
  end;

  TMyThirdPersonNavigation = class(TCastleThirdPersonNavigation)
  protected
    procedure SetAnimation(const AnimationNames: array of String); override;
  end;

var
  ViewPlay: TViewPlay;
  MyThirdPersonNavigation: TMyThirdPersonNavigation;
  GroundRayCast: TPhysicsRayCastResult;
  RayOrigin: TVector3;
  MoveAmount: TVector3;
  AboveHeight: Single;
  MaxDistanceToGround: Single;
  StandOnGround: Boolean;

implementation

uses SysUtils, Math, StrUtils,
  CastleSoundEngine, CastleLog, CastleStringUtils, CastleFilesUtils, CastleUtils,
  GameViewMenu;

{ TViewPlay ----------------------------------------------------------------- }

constructor TViewPlay.Create(AOwner: TComponent);
begin
  inherited;
  DesignUrl := 'castle-data:/gameviewplay.castle-user-interface';
end;

procedure TMyThirdPersonNavigation.SetAnimation(const AnimationNames: array of String);
begin

end;

procedure TViewPlay.Start;
var
  SoldierScene: TCastleScene;
  Enemy: TEnemy;
  I: Integer;
begin
  inherited;
  MyThirdPersonNavigation := TMyThirdPersonNavigation.Create(FreeAtStop);
  { Create TEnemy instances, add them to Enemies list }
  Enemies := TEnemyList.Create(true);
  for I := 1 to 4 do
  begin
    SoldierScene := DesignedComponent('SceneSoldier' + IntToStr(I)) as TCastleScene;
    { Below using nil as Owner of TEnemy, as the Enemies list already "owns"
      instances of this class, i.e. it will free them. }
    Enemy := TEnemy.Create(nil);
    SoldierScene.AddBehavior(Enemy);
    Enemies.Add(Enemy);
  end;

  { synchronize state -> UI }
  SliderAirRotationControl.Value := MyThirdPersonNavigation.AirRotationControl;
  SliderAirMovementControl.Value := MyThirdPersonNavigation.AirMovementControl;
  UpdateAfterChangeTransformation;

  CheckboxCameraFollows.OnChange := {$ifdef FPC}@{$endif} ChangeCheckboxCameraFollows;
  CheckboxAimAvatar.OnChange := {$ifdef FPC}@{$endif} ChangeCheckboxAimAvatar;
  CheckboxDebugAvatarColliders.OnChange := {$ifdef FPC}@{$endif} ChangeCheckboxDebugAvatarColliders;
  CheckboxImmediatelyFixBlockedCamera.OnChange := {$ifdef FPC}@{$endif} ChangeCheckboxImmediatelyFixBlockedCamera;
  SliderAirRotationControl.OnChange := {$ifdef FPC}@{$endif} ChangeAirRotationControl;
  SliderAirMovementControl.OnChange := {$ifdef FPC}@{$endif} ChangeAirMovementControl;
  ButtonChangeTransformationAuto.OnClick := {$ifdef FPC}@{$endif} ClickChangeTransformationAuto;
  ButtonChangeTransformationDirect.OnClick := {$ifdef FPC}@{$endif} ClickChangeTransformationDirect;
  ButtonChangeTransformationVelocity.OnClick := {$ifdef FPC}@{$endif} ClickChangeTransformationVelocity;
  ButtonChangeTransformationForce.OnClick := {$ifdef FPC}@{$endif} ClickChangeTransformationForce;

  {$ifndef CASTLE_UNFINISHED_CHANGE_TRANSFORMATION_BY_FORCE}
  { Hide UI to test ChangeTransformation = ctForce, it is not finished now,
    not really useful for normal usage. }
  ButtonChangeTransformationForce.Exists := false;
  {$endif}

  { This configures SceneAvatar.Middle point, used for shooting.
    In case of old physics (ChangeTransformation = ctDirect) this is also the center
    of SceneAvatar.CollisionSphereRadius. }
  // SceneAvatar.MiddleHeight := 0.9;

  { Configure some parameters of old simple physics,
    these only matter when SceneAvatar.Gravity = true.
    Don't use these deprecated things if you don't plan to use ChangeTransformation = ctDirect! }
  // SceneAvatar.GrowSpeed := 10.0;
  // SceneAvatar.FallSpeed := 10.0;
  { When avatar collides as sphere it can climb stairs,
    because legs can temporarily collide with objects. }
  // SceneAvatar.CollisionSphereRadius := 0.5;

  { Visualize SceneAvatar bounding box, sphere, middle point, direction etc. }
  DebugAvatar := TDebugTransform.Create(FreeAtStop);
  // DebugAvatar.Parent := SceneAvatar;

  { Configure MyThirdPersonNavigation keys (for now, we don't expose doing this in CGE editor). }
  MyThirdPersonNavigation.Input_LeftStrafe.Assign(keyQ);
  MyThirdPersonNavigation.Input_RightStrafe.Assign(keyE);
  MyThirdPersonNavigation.MouseLook := true; // TODO: assigning it from editor doesn't make mouse hidden in mouse look
  MyThirdPersonNavigation.Init;
  MyThirdPersonNavigation.AvatarHierarchy := AvatarTransform;
  SandyLegs.AutoAnimation := 'LEGS_IDLE';
  SandyTorso.AutoAnimation := 'TORSO_IDLE';
end;

procedure TViewPlay.Stop;
begin
  FreeAndNil(Enemies);
  inherited;
end;

procedure TViewPlay.Update(const SecondsPassed: Single; var HandleInput: Boolean);

  // Test: use this to make AimAvatar only when *holding* right mouse button.
  (*
  procedure UpdateAimAvatar;
  begin
    if buttonRight in Container.MousePressed then
      ThirdPersonNavigation.AimAvatar := aaHorizontal
    else
      ThirdPersonNavigation.AimAvatar := aaNone;

    { In this case CheckboxAimAvatar only serves to visualize whether
      the right mouse button is pressed now. }
    CheckboxAimAvatar.Checked := ThirdPersonNavigation.AimAvatar <> aaNone;
  end;
  *)

var
  SandyAnims: array of String;

begin
  inherited;
  { This virtual method is executed every frame (many times per second). }
  RayOrigin := AvatarTransform.Translation;
  MoveAmount := Vector3(0, -5, 0);
  AboveHeight := 1.0;
  MaxDistanceToGround := CapsuleCollider.Height;
  GroundRayCast := AvatarRigidBody.PhysicsRayCast(
    RayOrigin,
    Vector3(0, -1, 0),
    MaxDistanceToGround
  );
  StandOnGround := GroundRayCast.Hit;
  //and (GroundRayCast.Distance <= 1.23 + 0.01)
  if StandOnGround then
  begin
  LabelFps.Caption := 'FPS: PLACEHOLDER';
  SandyLegs.PlayAnimation('LEGS_IDLE', true);
  SandyTorso.PlayAnimation('TORSO_IDLE', true);
  end
  else
  begin
  SandyTorso.PlayAnimation('TORSO_AIRBORNE', true);
  SandyLegs.PlayAnimation('LEGS_AIRBORNE', true);
  AvatarTransform.Move(MoveAmount, false, true);
  LabelFps.Caption := 'FPS: ' + Container.Fps.ToString
  end;
  // UpdateAimAvatar;
end;

function TViewPlay.Press(const Event: TInputPressRelease): Boolean;
var
  HitByAvatar: TCastleTransform;
  HitEnemy: TEnemy;
  SandyInRunning: Boolean;
begin
  SandyInRunning := false;
  Result := inherited;
  if Result then Exit; // allow the ancestor to handle keys

  { 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 TViewPlay.Press method should be used to handle keys
    not handled in children controls.
  }

  if Event.IsMouseButton(buttonLeft) then
  begin
    SoundEngine.Play(SoundEngine.SoundFromName('shoot_sound'));
  end;

  if Event.IsMouseButton(buttonRight) then
  begin
    MyThirdPersonNavigation.MouseLook := not MyThirdPersonNavigation.MouseLook;
    Exit(true);
  end;

  if Event.IsKey(keyF5) then
  begin
    Container.SaveScreenToDefaultFile;
    Exit(true);
  end;

  if Event.IsKey(keyEscape) then
  begin
    Container.View := ViewMenu;
    Exit(true);
  end;

  if (Event.IsKey(keyArrowUp) and SandyInRunning = false) then
  begin
    SandyInRunning := true;
    SandyLegs.PlayAnimation('LEGS_RUN', true);
    SandyTorso.PlayAnimation('TORSO_RUN', true);
  end;
end;

procedure TViewPlay.ChangeCheckboxCameraFollows(Sender: TObject);
begin
  MyThirdPersonNavigation.CameraFollows := CheckboxCameraFollows.Checked;
end;

procedure TViewPlay.ChangeCheckboxAimAvatar(Sender: TObject);
begin
  if CheckboxAimAvatar.Checked then
    MyThirdPersonNavigation.AimAvatar := aaHorizontal
  else
    MyThirdPersonNavigation.AimAvatar := aaNone;

  { The 3rd option, aaFlying, doesn't make sense for this case,
    when avatar walks on the ground and has Gravity = true. }
end;

procedure TViewPlay.ChangeCheckboxDebugAvatarColliders(Sender: TObject);
begin
  DebugAvatar.Exists := CheckboxDebugAvatarColliders.Checked;
end;

procedure TViewPlay.ChangeCheckboxImmediatelyFixBlockedCamera(Sender: TObject);
begin
  MyThirdPersonNavigation.ImmediatelyFixBlockedCamera := CheckboxImmediatelyFixBlockedCamera.Checked;
end;

procedure TViewPlay.ChangeAirRotationControl(Sender: TObject);
begin
  MyThirdPersonNavigation.AirRotationControl := SliderAirRotationControl.Value;
end;

procedure TViewPlay.ChangeAirMovementControl(Sender: TObject);
begin
  MyThirdPersonNavigation.AirMovementControl := SliderAirMovementControl.Value;
end;

procedure TViewPlay.UpdateAfterChangeTransformation;
begin
  ButtonChangeTransformationAuto.Pressed := MyThirdPersonNavigation.ChangeTransformation = ctAuto;
  ButtonChangeTransformationDirect.Pressed := MyThirdPersonNavigation.ChangeTransformation =  ctDirect;
  ButtonChangeTransformationVelocity.Pressed := MyThirdPersonNavigation.ChangeTransformation =  ctVelocity;
  {$ifdef CASTLE_UNFINISHED_CHANGE_TRANSFORMATION_BY_FORCE}
  ButtonChangeTransformationForce.Pressed := ThirdPersonNavigation.ChangeTransformation = ctForce;
  {$endif}

  { ctDirect requires to set up gravity without physics engine,
    using deprecated TCastleTransform.Gravity.
    See https://castle-engine.io/physics#_old_system_for_collisions_and_gravity }
  AvatarRigidBody.Exists := MyThirdPersonNavigation.ChangeTransformation <> ctDirect;

  { Gravity means that object tries to maintain a constant height
    (SceneAvatar.PreferredHeight) above the ground.
    GrowSpeed means that object raises properly (makes walking up the stairs work).
    FallSpeed means that object falls properly (makes walking down the stairs,
    falling down pit etc. work). }
  SandyTorso.Gravity := not AvatarRigidBody.Exists;
  SandyLegs.Gravity := not AvatarRigidBody.Exists;
  SandyHead.Gravity := not AvatarRigidBody.Exists;
end;

procedure TViewPlay.ClickChangeTransformationAuto(Sender: TObject);
begin
  MyThirdPersonNavigation.ChangeTransformation := ctAuto;
  UpdateAfterChangeTransformation;
end;

procedure TViewPlay.ClickChangeTransformationDirect(Sender: TObject);
begin
  MyThirdPersonNavigation.ChangeTransformation := ctDirect;
  UpdateAfterChangeTransformation;
end;

procedure TViewPlay.ClickChangeTransformationVelocity(Sender: TObject);
begin
  MyThirdPersonNavigation.ChangeTransformation := ctVelocity;
  UpdateAfterChangeTransformation;
end;

procedure TViewPlay.ClickChangeTransformationForce(Sender: TObject);
begin
  {$ifdef CASTLE_UNFINISHED_CHANGE_TRANSFORMATION_BY_FORCE}
  ThirdPersonNavigation.ChangeTransformation := ctForce;
  {$endif}
  UpdateAfterChangeTransformation;
end;

end.

However, I am told that CapsuleCollider idents no member Height on trying to compile, even though I know from the API pages, in particular this one:

Proof that Capsule Collider object has a height property

That it is listed word for word, that TCapsuleCollider has a height field to it.

What is going on to explain this strangeness now?

Okay, I re-read it carefully and I think it’s cause I was using the wrong name for the class declaration, it’s actually supposed to be TCapsuleCollider, but now even though it compiles and runs correctly the test still doesn’t work; it has the same problem as earlier as never actually registering as touching the ground when it should.

Does anyone know how I should address the problem now?

Cause I can’t find any height measurements listed directly besides the MiddleHeight thing which I know from experience doesn’t work the way it should, and the other Height thing listed doesn’t match the type the vector wants for input as discussed earlier - it’s a function that returns a Boolean value while the height input for PhysicsRayCast is specifically a Single - it doesn’t make sense for a vector length to be only true or false.

The CapsuleCollider height was mentioned as a suggestion as the very last answer as of now when I asked for help, but as evidenced just now it doesn’t actually work the way it’s supposed to either - what else should I try?

This is wrong, delete it.
I’ll tell you again, even if you’ve already tried it, you did it wrong.
Use the code I suggested and change the value of MaxDistanceToGround each time.
With 0.01 what does the label display? :question:
With 0.02? Get to 0.1 by always adding 0.01, then tell me.

Also you should be able to view the ray, under Unity it is possible, maybe Michalis implemented it. You would see where the character starts from, its direction and its length. This would help you a lot. Try searching the API.

Unfortunately I cannot see the ray, I think it’s because I created it via code rather than actually adding it as an object in the scene, again just because I was told to do it that way in particular explicitly.

And if the capsule height was wrong, why the heck suggest it in the first place?! I already tried the trial and error method you are suggesting now in the previous thread and pointing out that it wasn’t working properly, so I am not sure what else I should try.

I already did do the small increments, but as I pointed out if I go from 4.331 to 4.332 for example, it jumps straight between being in the air standing, and on the ground in the falling pose, when all I actually wanted was to be standing once I am on the ground.

And I know the ray test the way it is doesn’t work the way it should, because the result doesn’t change depending on the actual length of the ray like you would logically expect - it literally only goes between two very fixed distances.

The position of the character once you start the game that is, only goes between two very fixed positions/distances and nothing else, whereas it should change depending on exactly how long the raycast is, if I understand how it is supposed to work in theory.

To elborate further in case what I said directly above wasn’t clear:

If you cast a ray that starts specifically at the player’s location/translation, and then ends in a specific pre-defined length as I am currently doing,

And then cast this same ray every frame and move the object casting the ray every frame,

It should register as touching the ground and stop moving at different positions, as the ray is a different length each time but always starts at the object’s origin, rather than just always stopping in one of two very specific positions.

Maybe this is getting too different from the topic at hand, but after I get help fixing this implementation, I am thinking for the other animations, I can code them as:

  • For waiting with her hands on her hips, she should wait a random number of seconds, and it should cancel when you start moving or doing anything else like attacking.

  • For the laser attack, she should wait for you to press the Z key.

  • For the spin attack, she should wait for you to press the H key.

  • For the running without attacking, it should play when you are not doing the laser or spin attack.

  • For the jumping without attacking, it should play when you are in the air and not attacking.

  • For the pain animation, it should only play when you get hurt.

  • For the death animation, it should only play when you run out of health/die.

  • For the zipline animation, it should only play when you are on a zipline.

  • For the stomp animation, it should only play when you are in the air and hit H to stomp.

  • For the climbing animation, it should only play when you are climbing a rope or something similar.

  • For the teetering animation, it should only play when you are on the edge of a platform or similar, or when you are using the rocket boots.

  • For the hovering animation, it should only play when you are riding the hover boots.

  • For the grappling animation, it should only play when you are riding the grapple hook.

  • For the sliding animation, it should only play when you are sliding down a steep surface.

  • For the falling animation, it should only play after you are airborne/falling for too long.

Does anyone have any ideas of how to fix the issue I mentioned, about how it does stop at only two specific positions rather than behaving properly according to the actual length of the ray? The code should be 100% accurate/correct, so I don’t know why it’s not working the way it should - it is told very specifically to calculate a ray of a particular length, and then stop when the ray hits the ground, so it should stop at different distances rather than only two specific ones.

Hi, does anyone have any comments or suggestions about how to fix the issue mentioned and re-stated more clearly in the post above? I really can’t make the game without it being fixed in some way first, cause it would look unprofessional to have Sandy in the airborne pose while being on the ground.

Still no replies/help? Again, look at the post right before this one to understand what I actually need help with.

Some general notes, scanning this thread:

  • Do not use TCapsuleCollider – it is deprecated, as shown in the documentation Castle Game Engine: CastleTransform: Class TCapsuleCollider , during compilation the compiler will also warn you about this.

    Do use TCastleCapsuleCollider – see Castle Game Engine: CastleTransform: Class TCastleCapsuleCollider . It has Height property. You did something wrong if the compiler complained.

    Your code attached to the 1st post didn’t show how you declared CapsuleCollider. So it is not possible to directly help with that.

  • The PhysicsRayCast routine is used in a few examples:

    • examples\viewport_and_scenes\collisions\
    • examples\third_person_navigation\
    • examples\platformer\

    Check them out, to make sure you understand how to use PhysicsRayCast correctly.

  • There’s also a good suggestion in this thread to add some debug display to your application, to visualize the ray you’re testing. You can e.g. do this using TCastleRenderUnlitMesh, see

    example\research_special_rendering_methods\test_rendering_opengl_capabilities\

    and in particular

    examples\research_special_rendering_methods\test_rendering_opengl_capabilities\code\gamemymesh.pas

    The TMyMesh.LocalRender can each frame render something – like a visualization of your ray origin and direction.

    Or see examples\physics\physics_forces\ how to visualize the lines using TLineSet.

  • If you have a specific bug with a specific routine, please submit a testcase (a complete and short example showing only this bug, a complete project with all data and code that can be executed).

Okay, thank you so much for the help!

Okay, as it turns out I never actually did use the Capsule Collider directly in the code, only as an object in the scene; I referenced the Rigid Body in the code instead.

However, I will make sure to be mindful of that difference if I ever need to reference it in the code, particularly when I change the Physics Raycast procedure given that it doesn’t work with my current code.

Hi everyone,

Unfortunately, I got an error that doesn’t seem to actually make logical sense, based off how the code is supposed to work (namely, that PhysicsRaycast doesn’t actually accept Boolean values because they wouldn’t make logical sense in that context, last time I checked and understood it, as it doesn’t make sense for any of the three components of a ray/vector to have only two rigid/specific possible values, as it would be more easier for everyone if you could set it to any value you want for flexibility).

The code for reference is:

{
  Copyright 2020-2023 Michalis Kamburelis.

  This file is part of "Castle Game Engine".

  "Castle Game Engine" is free software; see the file COPYING.txt,
  included in this distribution, for details about the copyright.

  "Castle Game Engine" is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

  ----------------------------------------------------------------------------
}

{ Main "playing game" view, where most of the game logic takes place. }
unit GameViewPlay;

{.$define CASTLE_UNFINISHED_CHANGE_TRANSFORMATION_BY_FORCE}

interface

uses Classes,
  CastleComponentSerialize, CastleUIControls, CastleControls,
  CastleKeysMouse, CastleViewport, CastleScene, CastleVectors, CastleCameras,
  CastleTransform, CastleInputs, CastleThirdPersonNavigation, CastleDebugTransform,
  CastleSceneCore,
  GameEnemy;

type
  { Main "playing game" view, where most of the game logic takes place. }
  TViewPlay = class(TCastleView)
  published
    { Components designed using CGE editor.
      These fields will be automatically initialized at Start. }
    LabelFps: TCastleLabel;
    MainViewport: TCastleViewport;
    AvatarTransform: TCastleTransform;
    SandyLegs, SandyHead, SandyTorso, SceneLevel: TCastleScene;
    AvatarRigidBody: TCastleRigidBody;
    CheckboxCameraFollows: TCastleCheckbox;
    CheckboxAimAvatar: TCastleCheckbox;
    CheckboxDebugAvatarColliders: TCastleCheckbox;
    CheckboxImmediatelyFixBlockedCamera: TCastleCheckbox;
    SliderAirRotationControl: TCastleFloatSlider;
    SliderAirMovementControl: TCastleFloatSlider;
    ButtonChangeTransformationAuto,
      ButtonChangeTransformationDirect,
      ButtonChangeTransformationVelocity,
      ButtonChangeTransformationForce: TCastleButton;
  private
    { Enemies behaviors }
    Enemies: TEnemyList;

    DebugAvatar: TDebugTransform;
    { Change things after ThirdPersonNavigation.ChangeTransformation changed. }
    procedure UpdateAfterChangeTransformation;
    procedure ChangeCheckboxCameraFollows(Sender: TObject);
    procedure ChangeCheckboxAimAvatar(Sender: TObject);
    procedure ChangeCheckboxDebugAvatarColliders(Sender: TObject);
    procedure ChangeCheckboxImmediatelyFixBlockedCamera(Sender: TObject);
    procedure ChangeAirRotationControl(Sender: TObject);
    procedure ChangeAirMovementControl(Sender: TObject);
    procedure ClickChangeTransformationAuto(Sender: TObject);
    procedure ClickChangeTransformationDirect(Sender: TObject);
    procedure ClickChangeTransformationVelocity(Sender: TObject);
    procedure ClickChangeTransformationForce(Sender: TObject);
  public
    constructor Create(AOwner: TComponent); override;
    procedure Start; override;
    procedure Stop; override;
    procedure Update(const SecondsPassed: Single; var HandleInput: Boolean); override;
    function Press(const Event: TInputPressRelease): Boolean; override;
  end;

  TMyThirdPersonNavigation = class(TCastleThirdPersonNavigation)
  protected
    procedure SetAnimation(const AnimationNames: array of String); override;
  end;

var
  ViewPlay: TViewPlay;
  MyThirdPersonNavigation: TMyThirdPersonNavigation;
  GroundRayCast: TPhysicsRayCastResult;
  StandOnGround: Boolean;

implementation

uses SysUtils, Math, StrUtils,
  CastleSoundEngine, CastleLog, CastleStringUtils, CastleFilesUtils, CastleUtils,
  GameViewMenu;

{ TViewPlay ----------------------------------------------------------------- }

constructor TViewPlay.Create(AOwner: TComponent);
begin
  inherited;
  DesignUrl := 'castle-data:/gameviewplay.castle-user-interface';
end;

procedure TMyThirdPersonNavigation.SetAnimation(const AnimationNames: array of String);
begin

end;

procedure TViewPlay.Start;
var
  SoldierScene: TCastleScene;
  Enemy: TEnemy;
  I: Integer;
begin
  inherited;
  MyThirdPersonNavigation := TMyThirdPersonNavigation.Create(FreeAtStop);
  { Create TEnemy instances, add them to Enemies list }
  Enemies := TEnemyList.Create(true);
  for I := 1 to 4 do
  begin
    SoldierScene := DesignedComponent('SceneSoldier' + IntToStr(I)) as TCastleScene;
    { Below using nil as Owner of TEnemy, as the Enemies list already "owns"
      instances of this class, i.e. it will free them. }
    Enemy := TEnemy.Create(nil);
    SoldierScene.AddBehavior(Enemy);
    Enemies.Add(Enemy);
  end;

  { synchronize state -> UI }
  SliderAirRotationControl.Value := MyThirdPersonNavigation.AirRotationControl;
  SliderAirMovementControl.Value := MyThirdPersonNavigation.AirMovementControl;
  UpdateAfterChangeTransformation;

  CheckboxCameraFollows.OnChange := {$ifdef FPC}@{$endif} ChangeCheckboxCameraFollows;
  CheckboxAimAvatar.OnChange := {$ifdef FPC}@{$endif} ChangeCheckboxAimAvatar;
  CheckboxDebugAvatarColliders.OnChange := {$ifdef FPC}@{$endif} ChangeCheckboxDebugAvatarColliders;
  CheckboxImmediatelyFixBlockedCamera.OnChange := {$ifdef FPC}@{$endif} ChangeCheckboxImmediatelyFixBlockedCamera;
  SliderAirRotationControl.OnChange := {$ifdef FPC}@{$endif} ChangeAirRotationControl;
  SliderAirMovementControl.OnChange := {$ifdef FPC}@{$endif} ChangeAirMovementControl;
  ButtonChangeTransformationAuto.OnClick := {$ifdef FPC}@{$endif} ClickChangeTransformationAuto;
  ButtonChangeTransformationDirect.OnClick := {$ifdef FPC}@{$endif} ClickChangeTransformationDirect;
  ButtonChangeTransformationVelocity.OnClick := {$ifdef FPC}@{$endif} ClickChangeTransformationVelocity;
  ButtonChangeTransformationForce.OnClick := {$ifdef FPC}@{$endif} ClickChangeTransformationForce;

  {$ifndef CASTLE_UNFINISHED_CHANGE_TRANSFORMATION_BY_FORCE}
  { Hide UI to test ChangeTransformation = ctForce, it is not finished now,
    not really useful for normal usage. }
  ButtonChangeTransformationForce.Exists := false;
  {$endif}

  { This configures SceneAvatar.Middle point, used for shooting.
    In case of old physics (ChangeTransformation = ctDirect) this is also the center
    of SceneAvatar.CollisionSphereRadius. }
  // SceneAvatar.MiddleHeight := 0.9;

  { Configure some parameters of old simple physics,
    these only matter when SceneAvatar.Gravity = true.
    Don't use these deprecated things if you don't plan to use ChangeTransformation = ctDirect! }
  // SceneAvatar.GrowSpeed := 10.0;
  // SceneAvatar.FallSpeed := 10.0;
  { When avatar collides as sphere it can climb stairs,
    because legs can temporarily collide with objects. }
  // SceneAvatar.CollisionSphereRadius := 0.5;

  { Visualize SceneAvatar bounding box, sphere, middle point, direction etc. }
  DebugAvatar := TDebugTransform.Create(FreeAtStop);
  // DebugAvatar.Parent := SceneAvatar;

  { Configure MyThirdPersonNavigation keys (for now, we don't expose doing this in CGE editor). }
  MyThirdPersonNavigation.Input_LeftStrafe.Assign(keyQ);
  MyThirdPersonNavigation.Input_RightStrafe.Assign(keyE);
  MyThirdPersonNavigation.MouseLook := true; // TODO: assigning it from editor doesn't make mouse hidden in mouse look
  MyThirdPersonNavigation.Init;
  MyThirdPersonNavigation.AvatarHierarchy := AvatarTransform;
  SandyLegs.AutoAnimation := 'LEGS_IDLE';
  SandyTorso.AutoAnimation := 'TORSO_IDLE';
end;

procedure TViewPlay.Stop;
begin
  FreeAndNil(Enemies);
  inherited;
end;

procedure TViewPlay.Update(const SecondsPassed: Single; var HandleInput: Boolean);

  // Test: use this to make AimAvatar only when *holding* right mouse button.
  (*
  procedure UpdateAimAvatar;
  begin
    if buttonRight in Container.MousePressed then
      ThirdPersonNavigation.AimAvatar := aaHorizontal
    else
      ThirdPersonNavigation.AimAvatar := aaNone;

    { In this case CheckboxAimAvatar only serves to visualize whether
      the right mouse button is pressed now. }
    CheckboxAimAvatar.Checked := ThirdPersonNavigation.AimAvatar <> aaNone;
  end;
  *)

var
  SandyAnims: array of String;
  MoveAmount: Integer;

begin
  inherited;
  { This virtual method is executed every frame (many times per second). }
  MoveAmount := 5;
  StandOnGround := AvatarRigidBody.PhysicsRayCast(AvatarTransform.Translation,
    Vector3(0, -1, 0), AvatarTransform.BoundingBox.SizeY / 2 + 5).Transform;
  //and (GroundRayCast.Distance <= 1.23 + 0.01)
  if StandOnGround then
  begin
  LabelFps.Caption := 'FPS: PLACEHOLDER';
  SandyLegs.PlayAnimation('LEGS_IDLE', true);
  SandyTorso.PlayAnimation('TORSO_IDLE', true);
  end
  else
  begin
  SandyTorso.PlayAnimation('TORSO_AIRBORNE', true);
  SandyLegs.PlayAnimation('LEGS_AIRBORNE', true);
  AvatarTransform.Move(MoveAmount, false, true);
  LabelFps.Caption := 'FPS: ' + Container.Fps.ToString
  end;
  // UpdateAimAvatar;
end;

function TViewPlay.Press(const Event: TInputPressRelease): Boolean;
var
  HitByAvatar: TCastleTransform;
  HitEnemy: TEnemy;
  SandyInRunning: Boolean;
begin
  SandyInRunning := false;
  Result := inherited;
  if Result then Exit; // allow the ancestor to handle keys

  { 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 TViewPlay.Press method should be used to handle keys
    not handled in children controls.
  }

  if Event.IsMouseButton(buttonLeft) then
  begin
    SoundEngine.Play(SoundEngine.SoundFromName('shoot_sound'));
  end;

  if Event.IsMouseButton(buttonRight) then
  begin
    MyThirdPersonNavigation.MouseLook := not MyThirdPersonNavigation.MouseLook;
    Exit(true);
  end;

  if Event.IsKey(keyF5) then
  begin
    Container.SaveScreenToDefaultFile;
    Exit(true);
  end;

  if Event.IsKey(keyEscape) then
  begin
    Container.View := ViewMenu;
    Exit(true);
  end;

  if (Event.IsKey(keyArrowUp) and SandyInRunning = false) then
  begin
    SandyInRunning := true;
    SandyLegs.PlayAnimation('LEGS_RUN', true);
    SandyTorso.PlayAnimation('TORSO_RUN', true);
  end;
end;

procedure TViewPlay.ChangeCheckboxCameraFollows(Sender: TObject);
begin
  MyThirdPersonNavigation.CameraFollows := CheckboxCameraFollows.Checked;
end;

procedure TViewPlay.ChangeCheckboxAimAvatar(Sender: TObject);
begin
  if CheckboxAimAvatar.Checked then
    MyThirdPersonNavigation.AimAvatar := aaHorizontal
  else
    MyThirdPersonNavigation.AimAvatar := aaNone;

  { The 3rd option, aaFlying, doesn't make sense for this case,
    when avatar walks on the ground and has Gravity = true. }
end;

procedure TViewPlay.ChangeCheckboxDebugAvatarColliders(Sender: TObject);
begin
  DebugAvatar.Exists := CheckboxDebugAvatarColliders.Checked;
end;

procedure TViewPlay.ChangeCheckboxImmediatelyFixBlockedCamera(Sender: TObject);
begin
  MyThirdPersonNavigation.ImmediatelyFixBlockedCamera := CheckboxImmediatelyFixBlockedCamera.Checked;
end;

procedure TViewPlay.ChangeAirRotationControl(Sender: TObject);
begin
  MyThirdPersonNavigation.AirRotationControl := SliderAirRotationControl.Value;
end;

procedure TViewPlay.ChangeAirMovementControl(Sender: TObject);
begin
  MyThirdPersonNavigation.AirMovementControl := SliderAirMovementControl.Value;
end;

procedure TViewPlay.UpdateAfterChangeTransformation;
begin
  ButtonChangeTransformationAuto.Pressed := MyThirdPersonNavigation.ChangeTransformation = ctAuto;
  ButtonChangeTransformationDirect.Pressed := MyThirdPersonNavigation.ChangeTransformation =  ctDirect;
  ButtonChangeTransformationVelocity.Pressed := MyThirdPersonNavigation.ChangeTransformation =  ctVelocity;
  {$ifdef CASTLE_UNFINISHED_CHANGE_TRANSFORMATION_BY_FORCE}
  ButtonChangeTransformationForce.Pressed := ThirdPersonNavigation.ChangeTransformation = ctForce;
  {$endif}

  { ctDirect requires to set up gravity without physics engine,
    using deprecated TCastleTransform.Gravity.
    See https://castle-engine.io/physics#_old_system_for_collisions_and_gravity }
  AvatarRigidBody.Exists := MyThirdPersonNavigation.ChangeTransformation <> ctDirect;

  { Gravity means that object tries to maintain a constant height
    (SceneAvatar.PreferredHeight) above the ground.
    GrowSpeed means that object raises properly (makes walking up the stairs work).
    FallSpeed means that object falls properly (makes walking down the stairs,
    falling down pit etc. work). }
  SandyTorso.Gravity := not AvatarRigidBody.Exists;
  SandyLegs.Gravity := not AvatarRigidBody.Exists;
  SandyHead.Gravity := not AvatarRigidBody.Exists;
end;

procedure TViewPlay.ClickChangeTransformationAuto(Sender: TObject);
begin
  MyThirdPersonNavigation.ChangeTransformation := ctAuto;
  UpdateAfterChangeTransformation;
end;

procedure TViewPlay.ClickChangeTransformationDirect(Sender: TObject);
begin
  MyThirdPersonNavigation.ChangeTransformation := ctDirect;
  UpdateAfterChangeTransformation;
end;

procedure TViewPlay.ClickChangeTransformationVelocity(Sender: TObject);
begin
  MyThirdPersonNavigation.ChangeTransformation := ctVelocity;
  UpdateAfterChangeTransformation;
end;

procedure TViewPlay.ClickChangeTransformationForce(Sender: TObject);
begin
  {$ifdef CASTLE_UNFINISHED_CHANGE_TRANSFORMATION_BY_FORCE}
  ThirdPersonNavigation.ChangeTransformation := ctForce;
  {$endif}
  UpdateAfterChangeTransformation;
end;

end.

The error snippet, again because I know it’s easier to debug if we are explicit about actual details, is:

The other error message I looked at and know to fix, because the Move argument wants a Vector explicitly and not a single dimension value/Integer, but I will correct that later; the first error that I know doesn’t make logical sense we will focus on first.

Why don’t you look at the examples? Your way of working is curious :thinking:

From GameViewPlay unit of “examples\third_person_navigation” demo:

  var
    RayCastResult: TPhysicsRayCastResult;
begin
    RayCastResult := AvatarRigidBody.PhysicsRayCast(
      SceneAvatar.Middle,
      SceneAvatar.Direction
    );
    [...]
end;

Cast a ray using physics engine, see what is hit.
Returns the TPhysicsRayCastResult record. Read TPhysicsRayCastResult.Hit (boolean),

Does it seem like StandOnGround is of type TPhysicsRayCastResult to you?
You are of course free to work as you like, but if you don’t take a moment to understand what you do and why you do it, I fear it will take you years just to move the character.

RayCastResult returns you some fields Castle Game Engine: CastleTransform: Record TPhysicsRayCastResult and you need to understand what they are to move forward.

That makes a lot more sense for being readable and organized too; I only did it that way because I was just following what the platformer example’s code read at one piece without understanding the context, but this way is obviously a lot better and neater. Thank you!

{
  Copyright 2020-2023 Michalis Kamburelis.

  This file is part of "Castle Game Engine".

  "Castle Game Engine" is free software; see the file COPYING.txt,
  included in this distribution, for details about the copyright.

  "Castle Game Engine" is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

  ----------------------------------------------------------------------------
}

{ Main "playing game" view, where most of the game logic takes place. }
unit GameViewPlay;

{.$define CASTLE_UNFINISHED_CHANGE_TRANSFORMATION_BY_FORCE}

interface

uses Classes,
  CastleComponentSerialize, CastleUIControls, CastleControls,
  CastleKeysMouse, CastleViewport, CastleScene, CastleVectors, CastleCameras,
  CastleTransform, CastleInputs, CastleThirdPersonNavigation, CastleDebugTransform,
  CastleSceneCore,
  GameEnemy;

type
  { Main "playing game" view, where most of the game logic takes place. }
  TViewPlay = class(TCastleView)
  published
    { Components designed using CGE editor.
      These fields will be automatically initialized at Start. }
    LabelFps: TCastleLabel;
    MainViewport: TCastleViewport;
    AvatarTransform: TCastleTransform;
    SandyLegs, SandyHead, SandyTorso, SceneLevel: TCastleScene;
    AvatarRigidBody: TCastleRigidBody;
    CheckboxCameraFollows: TCastleCheckbox;
    CheckboxAimAvatar: TCastleCheckbox;
    CheckboxDebugAvatarColliders: TCastleCheckbox;
    CheckboxImmediatelyFixBlockedCamera: TCastleCheckbox;
    SliderAirRotationControl: TCastleFloatSlider;
    SliderAirMovementControl: TCastleFloatSlider;
    ButtonChangeTransformationAuto,
      ButtonChangeTransformationDirect,
      ButtonChangeTransformationVelocity,
      ButtonChangeTransformationForce: TCastleButton;
  private
    { Enemies behaviors }
    Enemies: TEnemyList;

    DebugAvatar: TDebugTransform;
    { Change things after ThirdPersonNavigation.ChangeTransformation changed. }
    procedure UpdateAfterChangeTransformation;
    procedure ChangeCheckboxCameraFollows(Sender: TObject);
    procedure ChangeCheckboxAimAvatar(Sender: TObject);
    procedure ChangeCheckboxDebugAvatarColliders(Sender: TObject);
    procedure ChangeCheckboxImmediatelyFixBlockedCamera(Sender: TObject);
    procedure ChangeAirRotationControl(Sender: TObject);
    procedure ChangeAirMovementControl(Sender: TObject);
    procedure ClickChangeTransformationAuto(Sender: TObject);
    procedure ClickChangeTransformationDirect(Sender: TObject);
    procedure ClickChangeTransformationVelocity(Sender: TObject);
    procedure ClickChangeTransformationForce(Sender: TObject);
  public
    constructor Create(AOwner: TComponent); override;
    procedure Start; override;
    procedure Stop; override;
    procedure Update(const SecondsPassed: Single; var HandleInput: Boolean); override;
    function Press(const Event: TInputPressRelease): Boolean; override;
  end;

  TMyThirdPersonNavigation = class(TCastleThirdPersonNavigation)
  protected
    procedure SetAnimation(const AnimationNames: array of String); override;
  end;

var
  ViewPlay: TViewPlay;
  MyThirdPersonNavigation: TMyThirdPersonNavigation;
  GroundRayCast: TPhysicsRayCastResult;
  StandOnGround: Boolean;
  SandyInRunning: Boolean;

implementation

uses SysUtils, Math, StrUtils,
  CastleSoundEngine, CastleLog, CastleStringUtils, CastleFilesUtils, CastleUtils,
  GameViewMenu;

{ TViewPlay ----------------------------------------------------------------- }

constructor TViewPlay.Create(AOwner: TComponent);
begin
  inherited;
  DesignUrl := 'castle-data:/gameviewplay.castle-user-interface';
end;

procedure TMyThirdPersonNavigation.SetAnimation(const AnimationNames: array of String);
begin

end;

procedure TViewPlay.Start;
var
  SoldierScene: TCastleScene;
  Enemy: TEnemy;
  I: Integer;
begin
  inherited;
  MyThirdPersonNavigation := TMyThirdPersonNavigation.Create(FreeAtStop);
  { Create TEnemy instances, add them to Enemies list }
  Enemies := TEnemyList.Create(true);
  for I := 1 to 4 do
  begin
    SoldierScene := DesignedComponent('SceneSoldier' + IntToStr(I)) as TCastleScene;
    { Below using nil as Owner of TEnemy, as the Enemies list already "owns"
      instances of this class, i.e. it will free them. }
    Enemy := TEnemy.Create(nil);
    SoldierScene.AddBehavior(Enemy);
    Enemies.Add(Enemy);
  end;

  { synchronize state -> UI }
  SliderAirRotationControl.Value := MyThirdPersonNavigation.AirRotationControl;
  SliderAirMovementControl.Value := MyThirdPersonNavigation.AirMovementControl;
  UpdateAfterChangeTransformation;

  CheckboxCameraFollows.OnChange := {$ifdef FPC}@{$endif} ChangeCheckboxCameraFollows;
  CheckboxAimAvatar.OnChange := {$ifdef FPC}@{$endif} ChangeCheckboxAimAvatar;
  CheckboxDebugAvatarColliders.OnChange := {$ifdef FPC}@{$endif} ChangeCheckboxDebugAvatarColliders;
  CheckboxImmediatelyFixBlockedCamera.OnChange := {$ifdef FPC}@{$endif} ChangeCheckboxImmediatelyFixBlockedCamera;
  SliderAirRotationControl.OnChange := {$ifdef FPC}@{$endif} ChangeAirRotationControl;
  SliderAirMovementControl.OnChange := {$ifdef FPC}@{$endif} ChangeAirMovementControl;
  ButtonChangeTransformationAuto.OnClick := {$ifdef FPC}@{$endif} ClickChangeTransformationAuto;
  ButtonChangeTransformationDirect.OnClick := {$ifdef FPC}@{$endif} ClickChangeTransformationDirect;
  ButtonChangeTransformationVelocity.OnClick := {$ifdef FPC}@{$endif} ClickChangeTransformationVelocity;
  ButtonChangeTransformationForce.OnClick := {$ifdef FPC}@{$endif} ClickChangeTransformationForce;

  {$ifndef CASTLE_UNFINISHED_CHANGE_TRANSFORMATION_BY_FORCE}
  { Hide UI to test ChangeTransformation = ctForce, it is not finished now,
    not really useful for normal usage. }
  ButtonChangeTransformationForce.Exists := false;
  {$endif}

  { This configures SceneAvatar.Middle point, used for shooting.
    In case of old physics (ChangeTransformation = ctDirect) this is also the center
    of SceneAvatar.CollisionSphereRadius. }
  // SceneAvatar.MiddleHeight := 0.9;

  { Configure some parameters of old simple physics,
    these only matter when SceneAvatar.Gravity = true.
    Don't use these deprecated things if you don't plan to use ChangeTransformation = ctDirect! }
  // SceneAvatar.GrowSpeed := 10.0;
  // SceneAvatar.FallSpeed := 10.0;
  { When avatar collides as sphere it can climb stairs,
    because legs can temporarily collide with objects. }
  // SceneAvatar.CollisionSphereRadius := 0.5;

  { Visualize SceneAvatar bounding box, sphere, middle point, direction etc. }
  DebugAvatar := TDebugTransform.Create(FreeAtStop);
  // DebugAvatar.Parent := SceneAvatar;

  { Configure MyThirdPersonNavigation keys (for now, we don't expose doing this in CGE editor). }
  MyThirdPersonNavigation.Input_LeftStrafe.Assign(keyQ);
  MyThirdPersonNavigation.Input_RightStrafe.Assign(keyE);
  MyThirdPersonNavigation.MouseLook := true; // TODO: assigning it from editor doesn't make mouse hidden in mouse look
  MyThirdPersonNavigation.Init;
  MyThirdPersonNavigation.AvatarHierarchy := AvatarTransform;
  SandyLegs.AutoAnimation := 'LEGS_IDLE';
  SandyTorso.AutoAnimation := 'TORSO_IDLE';
end;

procedure TViewPlay.Stop;
begin
  FreeAndNil(Enemies);
  inherited;
end;

procedure TViewPlay.Update(const SecondsPassed: Single; var HandleInput: Boolean);

  // Test: use this to make AimAvatar only when *holding* right mouse button.
  (*
  procedure UpdateAimAvatar;
  begin
    if buttonRight in Container.MousePressed then
      ThirdPersonNavigation.AimAvatar := aaHorizontal
    else
      ThirdPersonNavigation.AimAvatar := aaNone;

    { In this case CheckboxAimAvatar only serves to visualize whether
      the right mouse button is pressed now. }
    CheckboxAimAvatar.Checked := ThirdPersonNavigation.AimAvatar <> aaNone;
  end;
  *)

var
  SandyAnims: array of String;
  RayCastResult: TPhysicsRayCastResult;
  MoveAmount: TVector3;
  SandyAirborne: Integer;
  SandyIdling: Integer;
begin
  inherited;
  { This virtual method is executed every frame (many times per second). }
  SandyIdling := 0;
  SandyAirborne := 0;
  MoveAmount := Vector3(0, -5, 0);
  RayCastResult := AvatarRigidBody.PhysicsRayCast(
  AvatarTransform.Middle,
  AvatarTransform.Direction
  );
  if RayCastResult.Hit then
  SandyAirborne := 0;
  begin
  LabelFps.Caption := 'FPS: PLACEHOLDER';
  if SandyIdling = 0 then
  begin
  SandyIdling := 1;
  SandyLegs.PlayAnimation('LEGS_IDLE', true);
  SandyTorso.PlayAnimation('TORSO_IDLE', true);
  end
  end;
  else
  SandyIdling := 0;
  begin
  if SandyAirborne = 0 then
  begin
  SandyAirborne := 1;
  SandyTorso.PlayAnimation('TORSO_AIRBORNE', true);
  SandyLegs.PlayAnimation('LEGS_AIRBORNE', true);
  end
  AvatarTransform.Move(MoveAmount, false, true);
  LabelFps.Caption := 'FPS: ' + Container.Fps.ToString
  end;
  // UpdateAimAvatar;
end;

function TViewPlay.Press(const Event: TInputPressRelease): Boolean;
var
  HitByAvatar: TCastleTransform;
  HitEnemy: TEnemy;
begin
  Result := inherited;
  if Result then Exit; // allow the ancestor to handle keys

  { 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 TViewPlay.Press method should be used to handle keys
    not handled in children controls.
  }

  if Event.IsMouseButton(buttonLeft) then
  begin
    SoundEngine.Play(SoundEngine.SoundFromName('shoot_sound'));
  end;

  if Event.IsMouseButton(buttonRight) then
  begin
    MyThirdPersonNavigation.MouseLook := not MyThirdPersonNavigation.MouseLook;
    Exit(true);
  end;

  if Event.IsKey(keyF5) then
  begin
    Container.SaveScreenToDefaultFile;
    Exit(true);
  end;

  if Event.IsKey(keyEscape) then
  begin
    Container.View := ViewMenu;
    Exit(true);
  end;

  if (Event.IsKey(keyArrowUp) and SandyInRunning = false) then
  begin
    SandyInRunning := true;
    SandyLegs.PlayAnimation('LEGS_RUN', true);
    SandyTorso.PlayAnimation('TORSO_RUN', true);
  end;
end;

function TViewPlay.Release(const Event: TInputPressRelease): Boolean;
var
begin
  Result := inherited;
  if Result then Exit; // allow the ancestor to handle keys

  { 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 TViewPlay.Press method should be used to handle keys
    not handled in children controls.
  }

  if (Event.IsKey(keyArrowUp) and SandyInRunning = true) then
  begin
    SandyInRunning := false;
    SandyLegs.PlayAnimation('LEGS_IDLE', true);
    SandyTorso.PlayAnimation('TORSO_IDLE', true);
  end;
end;

procedure TViewPlay.ChangeCheckboxCameraFollows(Sender: TObject);
begin
  MyThirdPersonNavigation.CameraFollows := CheckboxCameraFollows.Checked;
end;

procedure TViewPlay.ChangeCheckboxAimAvatar(Sender: TObject);
begin
  if CheckboxAimAvatar.Checked then
    MyThirdPersonNavigation.AimAvatar := aaHorizontal
  else
    MyThirdPersonNavigation.AimAvatar := aaNone;

  { The 3rd option, aaFlying, doesn't make sense for this case,
    when avatar walks on the ground and has Gravity = true. }
end;

procedure TViewPlay.ChangeCheckboxDebugAvatarColliders(Sender: TObject);
begin
  DebugAvatar.Exists := CheckboxDebugAvatarColliders.Checked;
end;

procedure TViewPlay.ChangeCheckboxImmediatelyFixBlockedCamera(Sender: TObject);
begin
  MyThirdPersonNavigation.ImmediatelyFixBlockedCamera := CheckboxImmediatelyFixBlockedCamera.Checked;
end;

procedure TViewPlay.ChangeAirRotationControl(Sender: TObject);
begin
  MyThirdPersonNavigation.AirRotationControl := SliderAirRotationControl.Value;
end;

procedure TViewPlay.ChangeAirMovementControl(Sender: TObject);
begin
  MyThirdPersonNavigation.AirMovementControl := SliderAirMovementControl.Value;
end;

procedure TViewPlay.UpdateAfterChangeTransformation;
begin
  ButtonChangeTransformationAuto.Pressed := MyThirdPersonNavigation.ChangeTransformation = ctAuto;
  ButtonChangeTransformationDirect.Pressed := MyThirdPersonNavigation.ChangeTransformation =  ctDirect;
  ButtonChangeTransformationVelocity.Pressed := MyThirdPersonNavigation.ChangeTransformation =  ctVelocity;
  {$ifdef CASTLE_UNFINISHED_CHANGE_TRANSFORMATION_BY_FORCE}
  ButtonChangeTransformationForce.Pressed := ThirdPersonNavigation.ChangeTransformation = ctForce;
  {$endif}

  { ctDirect requires to set up gravity without physics engine,
    using deprecated TCastleTransform.Gravity.
    See https://castle-engine.io/physics#_old_system_for_collisions_and_gravity }
  AvatarRigidBody.Exists := MyThirdPersonNavigation.ChangeTransformation <> ctDirect;

  { Gravity means that object tries to maintain a constant height
    (SceneAvatar.PreferredHeight) above the ground.
    GrowSpeed means that object raises properly (makes walking up the stairs work).
    FallSpeed means that object falls properly (makes walking down the stairs,
    falling down pit etc. work). }
  SandyTorso.Gravity := not AvatarRigidBody.Exists;
  SandyLegs.Gravity := not AvatarRigidBody.Exists;
  SandyHead.Gravity := not AvatarRigidBody.Exists;
end;

procedure TViewPlay.ClickChangeTransformationAuto(Sender: TObject);
begin
  MyThirdPersonNavigation.ChangeTransformation := ctAuto;
  UpdateAfterChangeTransformation;
end;

procedure TViewPlay.ClickChangeTransformationDirect(Sender: TObject);
begin
  MyThirdPersonNavigation.ChangeTransformation := ctDirect;
  UpdateAfterChangeTransformation;
end;

procedure TViewPlay.ClickChangeTransformationVelocity(Sender: TObject);
begin
  MyThirdPersonNavigation.ChangeTransformation := ctVelocity;
  UpdateAfterChangeTransformation;
end;

procedure TViewPlay.ClickChangeTransformationForce(Sender: TObject);
begin
  {$ifdef CASTLE_UNFINISHED_CHANGE_TRANSFORMATION_BY_FORCE}
  ThirdPersonNavigation.ChangeTransformation := ctForce;
  {$endif}
  UpdateAfterChangeTransformation;
end;

end.

Hi everyone,

Now I got an error about a semicolon being expected, even though I did the correct formatting, namely that I know you aren’t supposed to put the semicolon stating the end of a block at the beginning of the line, but rather the end of the previous line, even though the line given in the error message seems to want to suggest otherwise.

What the heck is going on?

You are making mistakes as a true beginner and if these are the conditions it is really impossible to help you.
I think you should go back and take a look at the basics of programming in Pascal.
Get into the habit of formatting your code, it helps you understand how the begin and end blocks are distributed.
In any case, I’m only answering because my previous post confused you, and this happened because, once again, you don’t want to understand what you do and why you do it.
You copied and pasted the example code, completely replacing your code.
This:

    my_wrong_type := AvatarRigidBody.PhysicsRayCast(
    AvatarTransform.Translation,
    Vector3(0, -1, 0), 
    AvatarTransform.BoundingBox.SizeY / 2 + 5).Transform;

it became this

    RayCastResult := AvatarRigidBody.PhysicsRayCast(
      SceneAvatar.Middle,
      SceneAvatar.Direction
    );

Is this really what you need? Are you even trying to ask yourself that?
Where do you want the ray to start from? From the character’s feet? From the center of it? from your head? (first parameter)
In what direction should the ray go from its starting point? Forward? Backwards? Downward? Upward? In another direction? (second parameter)
Must the ray have infinite length or limited to a fixed length? (third parameter)

AvatarTransform.Translation is the same as SceneAvatar.Middle?
Vector3(0, -1, 0) which represents Vector3.down is the same as SceneAvatar.Direction?
Not setting MaxDistance, as you do in the second piece of code, is that equivalent to having an infinite ray? Or limited with default length? In your initial code it would appear that you need a very specific length.
So ask yourself: in my 3d world, where is my ray going?
If you don’t answer this very simple question, I’m sure that your character flies while its feet are on the ground, and even worse.
As was explained to you by michalis you have the great opportunity to visualize the ray. This would be of great help to you since you are already working without knowing what you are doing.

Do as you think best, but if you don’t start demonstrating that you really want to commit to what you do, you risk having fewer and fewer people reply to you here.