{
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;
{ Unit for GameMesh renderer features. }
{.$define CASTLE_UNFINISHED_CHANGE_TRANSFORMATION_BY_FORCE}
interface
uses Classes,
CastleComponentSerialize, CastleUIControls, CastleControls,
CastleKeysMouse, CastleViewport, CastleScene, CastleVectors, CastleCameras,
CastleTransform, CastleInputs, CastleThirdPersonNavigation, CastleDebugTransform,
CastleSceneCore, SysUtils, CastleRenderPrimitives, CastleBoxes,
GameEnemy, CastleGLImages, CastleGLUtils;
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, LabelGlInformation: TCastleLabel;
MainViewport: TCastleViewport;
WalkNavigation: TCastleWalkNavigation;
AvatarTransform: TCastleTransform;
SandyLegs, SandyHead, SandyTorso, SceneLevel, ScenePhong: TCastleScene;
AvatarRigidBody: TCastleRigidBody;
CheckboxCameraFollows: TCastleCheckbox;
CheckboxAimAvatar: TCastleCheckbox;
CheckboxDebugAvatarColliders: TCastleCheckbox;
CheckboxImmediatelyFixBlockedCamera: TCastleCheckbox;
SliderAirRotationControl: TCastleFloatSlider;
SliderAirMovementControl: TCastleFloatSlider;
ButtonChangeTransformationAuto,
ButtonChangeTransformationDirect,
ButtonChangeTransformationVelocity,
ButtonChangeTransformationForce: TCastleButton;
private
{ Drawable Image setup }
DrawableImage: TDrawableImage;
{ 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;
procedure RenderOverChildren; override;
function Press(const Event: TInputPressRelease): Boolean; override;
function Release(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;
MaxDistance: Integer;
implementation
uses Math, StrUtils, CastleRenderContext, CastleColors,
CastleSoundEngine, CastleLog, CastleStringUtils, CastleFilesUtils,
GameViewMenu, GameMyMesh, CastleLoadGltf, CastleRectangles,
CastleImages, CastleUtils, X3DLoad;
{ 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;
MyMesh: TMyMesh;
begin
inherited;
AvatarTransform := AvatarTransform.Create(FreeAtStop);
LabelGlInformation.Caption :=
'OpenGL capabilities requested: ' + CapabilitiesStr[TGLFeatures.RequestCapabilities] + NL +
'(see log for the details about context)';
{ Load ScenePhong.Url with temporary GltfForcePhongMaterials set to true.
We want to test Phong shading works in ForceFixedFunction too. }
GltfForcePhongMaterials := true;
ScenePhong.Url := 'castle-data:/sample_3d.gltf';
GltfForcePhongMaterials := false;
DrawableImage := TDrawableImage.Create('castle-data:/texture_alpha.png');
MyMesh := TMyMesh.Create(FreeAtStop);
MyMesh.AvatarTransform := AvatarTransform;
AvatarTransform.Add(MyMesh);
MainViewport.AddScreenEffect(LoadNode('castle-data:/screen_effect_frame.x3dv'));
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);
FreeAndNil(DrawableImage);
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;
RayCastDirection: TVector3;
MaxDistance: Single;
SandyAirborne: Integer;
SandyIdling: Integer;
begin
inherited;
{ This virtual method is executed every frame (many times per second). }
SandyIdling := 0;
SandyAirborne := 0;
MaxDistance := 0.3;
MoveAmount := Vector3(0, -5, 0);
RayCastDirection := Vector3(0, -1, 0);
RayCastResult := AvatarRigidBody.PhysicsRayCast(
(AvatarTransform.Translation),
RayCastDirection,
0.3
);
if RayCastResult.Hit then
begin
SandyAirborne := 0;
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;
WalkNavigation.MouseLook := buttonRight in Container.MousePressed;
// UpdateAimAvatar;
end;
procedure TViewPlay.RenderOverChildren;
begin
inherited;
{ Do some direct drawing, to test this API works with ForceFixedFunction too. }
DrawRectangle(FloatRectangle(10, 10, 50, 50), Green);
DrawableImage.Draw(100, 10);
{ Render DrawableImage again, forcing alpha testing }
DrawableImage.Alpha := acTest;
DrawableImage.Draw(400, 10);
DrawableImage.Alpha := acAuto;
FallbackFont.Print(700, 10, Red, 'Another sample text');
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;
begin
Result := inherited;
if Result then Exit; // allow the ancestor to handle keys
{ This virtual method is executed when user releases
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.Release 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('BOTH_IDLE', true);
SandyTorso.PlayAnimation('BOTH_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.