-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgamebehaviors.pas
More file actions
156 lines (127 loc) · 5.03 KB
/
gamebehaviors.pas
File metadata and controls
156 lines (127 loc) · 5.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
{
Copyright 2022-2022 Michalis Kamburelis.
This file is part of "Lynch".
"Lynch" is free software; see the file COPYING.txt,
included in this distribution, for details about the copyright.
"Lynch" 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.
----------------------------------------------------------------------------
}
{ Behaviors (TCastleBehaviors) attached to game objects. }
unit GameBehaviors;
interface
uses CastleBehaviors, CastleTransform, CastleCameras, CastleTimeUtils, CastleScene;
type
{ Play footsteps sound, when we're walking.
This simply observes Navigation.IsWalkingOnTheGround,
and controls the playback of FootstepsSoundSource
(which is TCastleSoundSource found on parent). }
TFootstepsBehavior = class(TCastleBehavior)
strict private
FootstepsSoundSource: TCastleSoundSource;
{ To avoid starting/stopping sound often when Navigation.IsWalkingOnTheGround
changes too abruptly, we only schedule sound stop, instead of doing
it immediately. (But we still start it immediately.)
StopAfterTime is non-zero if some change is scheduled now,
and determines how many seconds need to pass for the change to happen. }
StopAfterTime: TFloatTime;
public
{ Navigation to observe.
Set it right after creation, cannot remain @nil for Update. }
Navigation: TCastleWalkNavigation;
procedure ParentAfterAttach; override;
procedure Update(const SecondsPassed: Single; var RemoveMe: TRemoveType); override;
end;
{ Rotate the statue (and make a sound) when it is *not seen* by player. }
TStatueBehavior = class(TCastleBehavior)
strict private
Scene: TCastleScene;
SoundSource: TCastleSoundSource;
public
procedure ParentAfterAttach; override;
procedure Update(const SecondsPassed: Single; var RemoveMe: TRemoveType); override;
end;
implementation
uses Math, SysUtils,
CastleVectors;
{ TFootstepsBehavior --------------------------------------------------------- }
procedure TFootstepsBehavior.ParentAfterAttach;
begin
inherited;
FootstepsSoundSource := Parent.FindBehavior(TCastleSoundSource) as TCastleSoundSource;
end;
procedure TFootstepsBehavior.Update(const SecondsPassed: Single; var RemoveMe: TRemoveType);
const
DefaultStopAfterTime = 0.25;
begin
inherited;
{ This simple implementation would just change FootstepsSoundSource.SoundPlaying
immediately. }
// FootstepsSoundSource.SoundPlaying := Navigation.IsWalkingOnTheGround;
{ Change FootstepsSoundSource.SoundPlaying.
But do not change it to false immediately, only schedule such change. }
if FootstepsSoundSource.SoundPlaying and not Navigation.IsWalkingOnTheGround then
begin
{ schedule stop, unless it is already scheduled }
if StopAfterTime = 0 then
StopAfterTime := DefaultStopAfterTime;
end else
begin
FootstepsSoundSource.SoundPlaying := Navigation.IsWalkingOnTheGround;
StopAfterTime := 0; // no stop scheduled
end;
{ Apply StopAfterTime, maybe stopping sound now. }
if StopAfterTime <> 0 then
begin
StopAfterTime := StopAfterTime - SecondsPassed;
if StopAfterTime <= 0 then
begin
StopAfterTime := 0;
FootstepsSoundSource.SoundPlaying := false;
end;
end;
end;
{ TStatueBehavior ------------------------------------------------------------ }
procedure TStatueBehavior.ParentAfterAttach;
begin
inherited;
Scene := Parent as TCastleScene;
SoundSource := Parent.FindBehavior(TCastleSoundSource) as TCastleSoundSource;
end;
procedure TStatueBehavior.Update(const SecondsPassed: Single; var RemoveMe: TRemoveType);
const
RotationSpeed = 0.5; // radians / second
var
Camera: TCastleCamera;
DesiredDirection, NewDirection, Cross: TVector3;
Angle: Single;
begin
if not Scene.WasVisible then
begin
Camera := Parent.World.MainCamera;
DesiredDirection := Camera.WorldTranslation - Scene.LocalToWorld(TVector3.Zero);
MakeVectorsOrthoOnTheirPlane(DesiredDirection, Camera.GravityUp);
DesiredDirection := DesiredDirection.Normalize;
// Cross is either gravity up, or negation of it, or zero
Cross := TVector3.CrossProduct(DesiredDirection, Parent.Direction);
if not Cross.IsZero then
begin
Angle := RotationAngleRadBetweenVectors(DesiredDirection, Parent.Direction, Cross);
{ Angle values after correcting below are very small, so we judge whether
to play SoundSource before correcting.
Also, this makes it independent of SecondsPassed,
so independent of system FPS. }
SoundSource.SoundPlaying := Abs(Angle) > 0.01;
if Angle > 0 then
Angle := Min(Angle, RotationSpeed * SecondsPassed)
else
Angle := Max(Angle, -RotationSpeed * SecondsPassed);
NewDirection := RotatePointAroundAxisRad(Angle, Parent.Direction, -Cross);
Parent.Direction := NewDirection;
end;
//Parent.Direction := DesiredDirection; // this would make immediate rotation
end else
SoundSource.SoundPlaying := false;
end;
end.