Behaviors

1. Introduction

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:

  1. Encapsulate (separate) the logic of some action as a behavior class,

  2. 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 TComponent class. And TComponent is an important standard Pascal class that is a basis (used also by our engine) for everything that can be visually edited and serialized. It’s something completely independent from the concept of "behavior" that we introduce here.

Note

There are alternative ways to operate on TCastleTransform:

But behaviors remain our recommended approach, because it nicely scales when you need multiple behaviors.

2. Built-in behaviors

The engine provides a number of ready behavior classes, e.g.

3. Example: behavior of a car

Let’s modify our cars demo from Writing code to modify scenes and transformations example to use behaviors to move each car.

  1. 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;
  2. 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.
  3. 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.

4. Example: controlling the footsteps sound

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

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.

5. Registering behaviors to use at design-time (in CGE editor)

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.