Okay, awesome! Thank you so much!
I will share the source model with you as it is in the .glb format, so here it is:
I don’t believe it’s necessary to share the original .blend file, because the Castle Game Engine will never accept .blend files so it has to be converted to another format like .glb first out of necessity, and on top of that I deleted it to save space on my hard drive.
JoshuaPFingerle-SandyDome.glb (2.5 MB)
On an unrelated note, I have been working hard on implementing features like jumping, foot steps working in sync with your movements, and so on, but ran into an error about “Single mod Single not being overriden” when I tried to mod the SecondsPassed in the Update function by 2 to make the boot step sound play every 2 seconds while you’re running,
So I am assuming it is because the mod operator that comes with the engine by default only mods integers and not singles, in which case I will need to override it because the Update function’s SecondsPassed is a single by default when you start a blank project, and I assume it’s intended to be that way?
{ Main view, where most of the application logic takes place.
Feel free to use this code as a starting point for your own projects.
This template code is in public domain, unlike most other CGE code which
is covered by BSD or LGPL (see https://castle-engine.io/license). }
unit GameViewPlay;
interface
uses Classes,
CastleComponentSerialize, CastleUIControls, CastleControls,
CastleKeysMouse, CastleViewport, CastleScene, CastleSoundEngine, CastleVectors,
CastleCameras, CastleTransform, CastleInputs, CastleThirdPersonNavigation,
CastleDebugTransform, CastleSceneCore,
GameEnemy;
type
{ Main view, where most of the application logic takes place. }
TViewMain = class(TCastleView)
published
{ Components designed using CGE editor.
These fields will be automatically initialized at Start. }
MainViewport: TCastleViewport;
ThirdPersonNavigation: TCastleThirdPersonNavigation;
SceneLevel: TCastleScene;
AvatarTransform: TCastleTransform;
SceneLegs: TCastleScene;
SandyJump: TCastleSound;
SandyBootstep: TCastleSound;
private
Enemies: TEnemyList;
procedure NavigationSetAnimation(const Sender: TCastleThirdPersonNavigation;
const AnimationNames: array of String);
public
constructor Create(AOwner: TComponent); override;
procedure Start; override;
procedure Update(const SecondsPassed: Single; var HandleInput: Boolean); override;
function Press(const Event: TInputPressRelease): Boolean; override;
end;
var
ViewMain: TViewMain;
JumpAnimation: array[0..1] of String;
RunAnimation: array[0..1] of String;
SandyRunning: Boolean;
implementation
uses SysUtils;
{ TViewMain ----------------------------------------------------------------- }
constructor TViewMain.Create(AOwner: TComponent);
begin
inherited;
DesignUrl := 'castle-data:/gameviewmain.castle-user-interface';
end;
procedure TViewMain.Start;
begin
inherited;
{ Critical to make camera orbiting around and movement of avatar
to follow proper direction and up.
In the AvatarTransform local coordinate system,
the avatar is moving in +X, and has up (head) in +Z. }
AvatarTransform.Orientation := otUpZDirectionX;
ThirdPersonNavigation.MouseLook := true;
ThirdPersonNavigation.OnAnimation := {$ifdef FPC}@{$endif} NavigationSetAnimation;
{ Configure parameters to move nicely using old simple physics,
see examples/third_person_navigation for comments.
Use these if you decide to move using "direct" method
(when AvatarTransform.ChangeTransform = ctDirect,
or when AvatarTransform.ChangeTransform = ctAuto and
AvatarTransform has no rigid body and collider). }
AvatarTransform.MiddleHeight := 0.9;
AvatarTransform.GrowSpeed := 10.0;
AvatarTransform.FallSpeed := 10.0;
// a bit large, but it is scaled by AvatarTransform scale = 0.1, making it 0.3 effectively
AvatarTransform.CollisionSphereRadius := 3;
ThirdPersonNavigation.Init;
end;
procedure TViewMain.NavigationSetAnimation(const Sender: TCastleThirdPersonNavigation;
const AnimationNames: array of String);
begin
{ Example implementation that merely sets animation on SceneLegs,
to either TORSO_IDLE or TORSO_RUN.
Use castle-model-viewer (formerly view3dscene),
https://castle-engine.io/castle-model-viewer,
just double-click on MD3 file from CGE editor, to see available animations
(in "Animations" panel).
}
if AnimationNames[0] = 'idle' then
SceneLegs.AutoAnimation := 'TORSO_IDLE'
else
if AnimationNames[0] = 'jump' then
SceneLegs.AutoAnimation := 'TORSO_JUMP'
else
if AnimationNames[0] = 'run' then
SceneLegs.AutoAnimation := 'TORSO_RUN'
end;
procedure TViewMain.Update(const SecondsPassed: Single; var HandleInput: Boolean);
begin
inherited;
while (SandyRunning = true and (SecondsPassed mod 2.0) = 0) do
SoundEngine.Play(SandyBootstep);
{ This virtual method is executed every frame (many times per second). }
end;
function TViewMain.Press(const Event: TInputPressRelease): Boolean;
begin
{ Alternative version, using Items.PhysicsRayCast
(everything in world space coordinates).
This works equally well, showing it here just for reference.
RayCastResult := MainViewport.Items.PhysicsRayCast(
SceneAvatar.Parent.LocalToWorld(SceneAvatar.Middle),
SceneAvatar.Parent.LocalToWorldDirection(SceneAvatar.Direction),
MaxSingle,
AvatarRigidBody
);
Result := RayCastResult.Transform;
}
(* Alternative versions, using old physics,
see https://castle-engine.io/physics#_old_system_for_collisions_and_gravity .
They still work (even when you also use new physics).
if not AvatarRigidBody.Exists then
begin
{ SceneAvatar.RayCast tests a ray collision,
ignoring the collisions with SceneAvatar itself (so we don't detect our own
geometry as colliding). }
Result := SceneAvatar.RayCast(SceneAvatar.Middle, SceneAvatar.Direction);
end else
begin
{ When physics engine is working, we should not toggle Exists multiple
times in a single frame, which makes the curent TCastleTransform.RayCast not good.
So use Items.WorldRayCast, and secure from "hitting yourself" by just moving
the initial ray point by 0.5 units. }
Result := MainViewport.Items.WorldRayCast(
SceneAvatar.Middle + SceneAvatar.Direction * 0.5, SceneAvatar.Direction);
end;
*)
{ This virtual method is executed when user presses
a key, a mouse button, or touches a touch-screen.
Note that each UI control has also events like OnPress and OnClick.
These events can be used to handle the "press", if it should do something
specific when used in that UI control.
The TViewMain.Press method should be used to handle keys
not handled in children controls.
}
// Use this to handle keys:
{
if Event.IsKey(keyXxx) then
begin
// DoSomething;
Exit(true); // key was handled
end;
}
if Event.IsKey(keySpace) then
begin
JumpAnimation[0] := 'jump';
SoundEngine.Play(SandyJump);
AvatarTransform.Move(Vector3(0, 3, 0), false, true);
NavigationSetAnimation(ThirdPersonNavigation, JumpAnimation);
Exit(true);
end;
if Event.IsKey(keyArrowUp) then
begin
RunAnimation[0] := 'run';
SandyRunning := true;
NavigationSetAnimation(ThirdPersonNavigation, RunAnimation);
Exit(true);
end;
if Event.IsKey(keyArrowDown) then
begin
RunAnimation[0] := 'run';
SandyRunning := true;
NavigationSetAnimation(ThirdPersonNavigation, RunAnimation);
Exit(true);
end;
if Event.IsKey(keyArrowLeft) then
begin
RunAnimation[0] := 'run';
SandyRunning := true;
NavigationSetAnimation(ThirdPersonNavigation, RunAnimation);
Exit(true);
end;
if Event.IsKey(keyArrowRight) then
begin
RunAnimation[0] := 'run';
SandyRunning := true;
NavigationSetAnimation(ThirdPersonNavigation, RunAnimation);
Exit(true);
end;
end;
end.