Aren’t there any extra brackets?
No, I was specific about having the second pair nested in the first pair, because otherwise it wouldn’t register it properly as a Vector3 or a group of three numbers/coordinates; I know because I tried that already myself.
Now I don’t have time to delve deeper but will the Vector3 keyword be missing?
I know usually for using variables in function parameters, you don’t actually use the type explicitly (IE: you don’t type “string” or “char” or anything at all for the calls to the animations above; only the actual value you want to use for the string.)
function Move (const TranslationChange: TVector3; const BecauseOfGravity: boolean; const EnableWallSliding: boolean = true): boolean;
Isn’t there a parameter missing? Maybe you need to define BecauseOfGravity.
From what I understand EnableWallSliding is set to True by default, but the parameter preceding it appears to be missing.
I tried it that way earlier but was getting more weird errors, but now that I have a Vector3 defined explicitly it might work correctly. I will try again.
Okay, now it works perfectly. My character moves correctly in the way I want when I start the program, with no errors.
Hopefully now everything will be smooth sailing from here on out.
Okay, unfortunately, the code looks like this 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;
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;
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 := Vector3(0, 1.23, 0);
MoveAmount := Vector3(0, -5, 0);
MaxDistanceToGround := 2.36;
GroundRayCast := AvatarRigidBody.PhysicsRayCast(
RayOrigin,
Vector3(0, -1, 0),
MaxDistanceToGround
);
StandOnGround := GroundRayCast.Hit and (GroundRayCast.Distance <= 1.23 + 0.01);
if StandOnGround then
begin
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);
end;
// UpdateAimAvatar;
LabelFps.Caption := 'FPS: ' + Container.Fps.ToString
end;
function TViewPlay.Press(const Event: TInputPressRelease): Boolean;
function AvatarRayCast: TCastleTransform;
var
RayCastResult: TPhysicsRayCastResult;
begin
RayCastResult := AvatarRigidBody.PhysicsRayCast(
SandyTorso.Middle,
SandyTorso.Direction
);
Result := RayCastResult.Transform;
{ 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;
*)
end;
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'));
{ We clicked on enemy if
- HitByAvatar indicates we hit something
- It has a behavior of TEnemy. }
HitByAvatar := AvatarRayCast;
if (HitByAvatar <> nil) and
(HitByAvatar.FindBehavior(TEnemy) <> nil) then
begin
HitEnemy := HitByAvatar.FindBehavior(TEnemy) as TEnemy;
HitEnemy.Hurt;
end;
Exit(true);
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.
But my Sandy Cheeks player character doesn’t change to idle properly when she is actually touching the ground, even though I am pretty sure the code is typed correctly (IE: it says specifically only to move the transform and play the airborne pose only if it’s NOT touching the ground, otherwise it should correctly play the idle animation).
Can someone please help explain why the code isn’t working the way it is supposed to? I know how if else conditionals are supposed to work in theory, but unfortunately it doesn’t seem to be working with the TouchingGround conditional for whatever reason.
You should start by debugging the code.
First of all: do IDLE animations run correctly when you run the game?
Is StandOnGround really as true as you expect?
Try inserting a ShowMessage (or similar) or a label that changes the text if StandOnGround is True. E.g.
StandOnGround := GroundRayCast.Hit and (GroundRayCast.Distance <= 1.23 + 0.01);
if StandOnGround then
begin
// do something here <---
SandyLegs.PlayAnimation('LEGS_IDLE', true);
SandyTorso.PlayAnimation('TORSO_IDLE', true);
end
If the code doesn’t run you focus on RayCast, otherwise on PlayAnimation.
I can try that, but because I included “StandOnGround” under Update, it should be checking every frame period and not just one time or a couple of times, therefore I expected it should work logically without a hitch, in reply to “Is StandOnGround really as true as you expect?”
And yes, idle animations run correctly by default.
I will try your suggestion however, of doing something like a label change to verify that in fact it is StandOnGround not working, because I don’t have enough proof right now as you pointed out; it could be caused by something else.
Correct, ShowMessage in Update is a bad solution, but a label is not. When you start the application the label will have the value you have predefined, when the character jumps or flies it will have another one, when it touches the ground it should return to the default value. If it doesn’t happen, you know what to work on.
Just assign a different value to the label when StandOnGround is true and false.
Okay, I have the code like this 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;
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;
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 := Vector3(0, 1.23, 0);
MoveAmount := Vector3(0, -5, 0);
MaxDistanceToGround := 2.36;
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;
function AvatarRayCast: TCastleTransform;
var
RayCastResult: TPhysicsRayCastResult;
begin
RayCastResult := AvatarRigidBody.PhysicsRayCast(
SandyTorso.Middle,
SandyTorso.Direction
);
Result := RayCastResult.Transform;
{ 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;
*)
end;
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'));
{ We clicked on enemy if
- HitByAvatar indicates we hit something
- It has a behavior of TEnemy. }
HitByAvatar := AvatarRayCast;
if (HitByAvatar <> nil) and
(HitByAvatar.FindBehavior(TEnemy) <> nil) then
begin
HitEnemy := HitByAvatar.FindBehavior(TEnemy) as TEnemy;
HitEnemy.Hurt;
end;
Exit(true);
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.
And I can see that the FPS never actually displays the “PLACEHOLDER” word when you are touching the ground, so now i know more definitely that the TouchGround condition is never met is most likely the source of the problem, whereas there were more possibilites earlier.
I get that’s your point.
But again, even if it is the problem I still have zero idea how to correct it, which was my original point; based off how I was told the conditional should work it should work smoothly and error free, and it’s not logical that it doesn’t.
In theory the code is correct: if the character doesn’t touch the ground do something, otherwise do something else.
But if the StandOnGround = True condition is never executed, it means there is something wrong with its assignment.
Right now I’m very busy developing code under Unity and I can’t go into more detail but it’s normal to wonder: if StandOnGround never becomes True, could there be something wrong with RayCast? Are all variables defined correctly?
As far as I was doing it according to the code and what others told me explicitly, yes; however, it looks silly that the Raycast origin is at a very specific and fixed point and doesn’t adapt to the player’s actual coordinates, but again I was simply following the directions I was given earlier.
Perhaps trying to make the origin something in the AvatarRigidBody would work?
Mine was a rhetorical question as the answer is no. The RayCast is not done correctly, otherwise StandOnGround would have different values
I know obviously it isn’t working, therefore why I posted for help, but my point has been and always will be that it should work, given what I was told and what everything I can find on the matter tells me, like the API, manual and tutorials michaelis gave me, all of that stuff.
And now when I use the AvatarTransform’s Translation/position coordinates as a Vector3 for the ray’s origin, it’s giving me errors about duplicate AvatarTransform on the declaration of “function AvatarTransform” down below, even though it should be smart enough to know that a declaration of a function with the same name as an object on the scene list are two totally unrelated things.
For reference here is the code now; what should I rename the function called “AvatarTransform” so that everything still works and I can use the AvatarTransform object from the scene correctly in order to set the raycast’s origin at its coordinates?
{
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;
RayOrigin: TVector3;
MoveAmount: TVector3;
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);
MaxDistanceToGround := 2.36;
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;
function AvatarTransform: TCastleTransform;
var
RayCastResult: TPhysicsRayCastResult;
begin
RayCastResult := AvatarRigidBody.PhysicsRayCast(
SandyTorso.Middle,
SandyTorso.Direction
);
Result := RayCastResult.Transform;
{ 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;
*)
end;
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'));
{ We clicked on enemy if
- HitByAvatar indicates we hit something
- It has a behavior of TEnemy. }
HitByAvatar := AvatarRayCast;
if (HitByAvatar <> nil) and
(HitByAvatar.FindBehavior(TEnemy) <> nil) then
begin
HitEnemy := HitByAvatar.FindBehavior(TEnemy) as TEnemy;
HitEnemy.Hurt;
end;
Exit(true);
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.
On top of this, given that I already used “AvatarTransform.Move” below my new code earlier and it didn’t give me an error, as you can tell when you go back and read earlier versions, why is it objecting about duplicate identifiers now?
The second parameter of PhysicsRayCast would appear to be a direction and not a specific position in space.
The basic formula is:
Vector3 direction = target. position-transform. position.
I think RayOrigin is AvatarTransform.Translation.
Take a look at the API, it explains well.
I already did do the research; the parameter is indeed a direction, but I think I have that already; I only replaced the RayOrigin and not the RayDirection.