MyBehavior := TMyBehavior.Create(...);
MyTransform.AddBehavior(MyBehavior);
Behaviors allow to customize the parent TCastleTransform
behavior.
A behavior is a class descending from TCastleBehavior
that you add to parent using TCastleTransform.AddBehavior
.
MyBehavior := TMyBehavior.Create(...);
MyTransform.AddBehavior(MyBehavior);
Using behaviors allows to:
Encapsulate (separate) the logic of some action as a behavior class,
The behavior can be freely added and removed at runtime.
First example behavior is in the "New Project → 3D FPS Game" template. See how the simple enemy AI is implemented there, in GameEnemy unit.
You can query for behavior existence. In the simplest case just use TCastleTransform.FindBehavior
method to check whether a given TCastleTransform
contains a behavior. To iterate over all behaviors in a given tree use TCastleTransform.FindAllBehaviors
.
Note
|
In gamedev terminology, our "behaviors" are somewhat similar to what Unity3D or Entity Component Systems call "components". That is, you can add and remove behaviors at runtime, and various game objects (like player, enemy, vehicle…) can be composed from multiple behaviors, and behaviors define both data and functionality. We use the term "behavior" throughout our engine, not "component", because in the Pascal context the word "component" is already associated with the |
Note
|
There are alternative ways to operate on
But behaviors remain our recommended approach, because it nicely scales when you need multiple behaviors. |
The engine provides a number of ready behavior classes, e.g.
TCastleSoundSource
(see sound),
TCastleRigidBody
, all colliders derived from TCastleCollider
, physics joints (see physics).
Let’s modify our cars demo from Writing code to modify scenes and transformations example to use behaviors to move each car.
Define and implement class TCarBehavior
that descends from TCastleBehavior
, and moves a car. Place this code in your project (you can create a new unit, or add it to the implementation of GameViewMain
unit):
type
TCarBehavior = class(TCastleBehavior)
public
procedure Update(const SecondsPassed: Single; var RemoveMe: TRemoveType); override;
end;
procedure TCarBehavior.Update(const SecondsPassed: Single; var RemoveMe: TRemoveType);
var
T: TVector3;
begin
T := Parent.Translation;
{ Thanks to multiplying by SecondsPassed, it is a time-based operation,
and will always move 40 units / per second along the +Z axis. }
T := T + Vector3(0, 0, 40) * SecondsPassed;
{ Wrap the Z position, to move in a loop }
if T.Z > 70 then
T.Z := -50;
Parent.Translation := T;
end;
Create an instance of TCarBehavior
(owned by FreeAtStop
) and insert it into each CarTransforms[I]
instance. To do this, extend the loop in TViewMain.Start
that creates CarTransforms[I]
instances from previous section. After this line:
CarTransforms[I].Add(CarScene);
add a new line:
CarTransforms[I].AddBehavior(TCarBehavior.Create(FreeAtStop));
Note
|
The order doesn’t really matter much, and you can rearrange most of the code we describe. In general, you can add children and behaviors in any order. |
Remove previous movement logic from TViewMain.Update
, if you added it by following the Writing code to modify scenes and transformations chapter. We don’t need it anymore, as new behavior class will move the car. So make TViewMain.Update
as simple as it was at the beginning:
procedure TViewMain.Update(const SecondsPassed: Single; var HandleInput: Boolean);
begin
inherited;
{ This virtual method is executed every frame.}
LabelFps.Caption := 'FPS: ' + Container.Fps.ToString;
end;
Test and run. Play around with adding behaviors to only some of the cars (i.e. only cars with I < 10
) to feel the flexibility of this.
A behavior can be used to connect some other components with a custom functionality. For example this simple TFootstepsBehavior
can be attached to the camera, and
It assumes that the parent defines another behavior, TCastleSoundSource
, that can play footsteps sound,
It requires you to set Navigation
property to a TCastleWalkNavigation
instance, so that it can observe TCastleWalkNavigation.IsWalkingOnTheGround
to know when to make a footsteps sound.
The sample implementation is as follows:
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;
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;
procedure TFootstepsBehavior.ParentAfterAttach;
begin
inherited;
FootstepsSoundSource := Parent.FindBehavior(TCastleSoundSource) as TCastleSoundSource;
end;
procedure TFootstepsBehavior.Update(const SecondsPassed: Single; var RemoveMe: TRemoveType);
begin
inherited;
FootstepsSoundSource.SoundPlaying := Navigation.IsWalkingOnTheGround;
end;
You can initialize it like this from code:
procedure TViewPlay.Start;
var
Footsteps: TFootstepsBehavior;
begin
inherited;
Footsteps := TFootstepsBehavior.Create(FreeAtStop);
Footsteps.Navigation := WalkNavigation;
PlayerCamera.AddBehavior(Footsteps);
end;
This simple implementation works, but you will notice an important problem: the footsteps sound may stop and start too often, if you walk on an uneven terrain. You can take a look at the example of TFootstepsBehavior from the "Lynch" demo that avoids this problem, by not stopping the footsteps playback immediately. Thanks to the power of behaviors, this complicated mechanism is nicely encapsulated inside a TFootstepsBehavior
class.
As any other component, behaviors can be registered as custom component for the CGE editor. This way you can add and configure behaviors at design-time.
Simply call RegisterSerializableComponent
to register the behavior, like
unit MyBehavior;
interface
uses CastleTransform;
type
TMyBehavior = class(TCastleBehavior)
end;
implementation
uses CastleComponentSerialize;
initialization
RegisterSerializableComponent(TMyBehavior, 'My Behavior');
end.
Inside your CastleEngineManifest.xml file, set the attribute editor_units
to list all the units that call the RegisterSerializableComponent
, like editor_units="MyBehavior"
.
To improve this documentation just edit this page and create a pull request to cge-www repository.