Crystal Entity Layer: 4.2.5 behave.cpp

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

### 4.2.5 behave.cpp

Here is the source for our behaviour layer. The `CreateBehaviour()` function in `BehaviourLayer` will compare the given name parameter with the possible names for behaviours and then create the appropriate subclass of `BehaviourCommon`.

    #include <crystalspace.h>

    #include <physicallayer/entity.h>
    #include <physicallayer/propclas.h>
    #include <propclass/linmove.h>
    #include <propclass/prop.h>

    #include "behave.h"

    SCF_IMPLEMENT_IBASE (BehaviourLayer)
      SCF_IMPLEMENTS_INTERFACE (iCelBlLayer)
    SCF_IMPLEMENT_IBASE_END

    BehaviourLayer::BehaviourLayer (iCelPlLayer* pl)
    {
      SCF_CONSTRUCT_IBASE (0);
      BehaviourLayer::pl = pl;
    }

    BehaviourLayer::~BehaviourLayer ()
    {
      SCF_DESTRUCT_IBASE ();
    }

    iCelBehaviour* BehaviourLayer::CreateBehaviour (iCelEntity* entity,
    	const char* name)
    {
      iCelBehaviour* behave = 0;
      if (!strcmp (name, "level_behave"))
        behave = new BehaviourLevel (entity, this, pl);
      else if (!strcmp (name, "player_behave"))
        behave = new BehaviourPlayer (entity, this, pl);
      else if (!strcmp (name, "box_behave"))
        behave = new BehaviourBox (entity, this, pl);
      else if (!strcmp (name, "badone_behave"))
        behave = new BehaviourBadOne (entity, this, pl);

      if (behave)
      {
        entity->SetBehaviour (behave);
        // There is now a reference in the entity. We destroy
        // our own reference here.
        behave->DecRef ();
      }
      return behave;
    }

In `SendMessage()` of `BehaviourCommon` we use the `FetchStringID()` function to ``intern'` the message string to an ID. In the actual implementations of `SendMessage()` in the various behaviours we can then use integer comparisons instead of string compares.

    SCF_IMPLEMENT_IBASE (BehaviourCommon)
      SCF_IMPLEMENTS_INTERFACE (iCelBehaviour)
    SCF_IMPLEMENT_IBASE_END

    BehaviourCommon::BehaviourCommon (iCelEntity* entity, BehaviourLayer* bl, iCelPlLayer* pl)
    {
      SCF_CONSTRUCT_IBASE (0);
      BehaviourCommon::entity = entity;
      BehaviourCommon::bl = bl;
      BehaviourCommon::pl = pl;
    }

    BehaviourCommon::~BehaviourCommon ()
    {
      SCF_DESTRUCT_IBASE ();
    }

    bool BehaviourCommon::SendMessage (const char* msg_id,
    	iCelPropertyClass* pc,
      	celData& ret, iCelParameterBlock* params, ...)
    {
      va_list arg;
      va_start (arg, params);
      bool rc = SendMessageV (msg_id, pc, ret, params, arg);
      va_end (arg);
      return rc;
    }

    bool BehaviourCommon::SendMessageV (const char* msg_id,
    	iCelPropertyClass* pc,
      	celData& ret, iCelParameterBlock* params, va_list arg)
    {
      csStringID id = pl->FetchStringID (msg_id);
      return SendMessage (id, pc, ret, params, arg);
    }

    bool BehaviourCommon::SendMessage (csStringID,
    	iCelPropertyClass*,
      	celData&, iCelParameterBlock*, va_list)
    {
      return false;
    }

The level behaviour does not do anything in this tutorial.

    bool BehaviourLevel::SendMessage (csStringID msg_id,
    	iCelPropertyClass* pc,
      	celData& ret, iCelParameterBlock* params, va_list arg)
    {
      return BehaviourCommon::SendMessage (msg_id, pc, ret, params, arg);
    }

The player behaviour is responsible for managing input (from ``pccommandinput'`) and moving the actor in response to that input (with ``pcactormove'`). First we request ID's from the physical layer for the messages from the input property class that we want to respond too. For every command we defined in a `Bind()` in the `CreatePlayer()` function we get both a ``1'` (when key is pressed) and a ``0'` message (when key is released).

We also respond to messages from the inventory system so that we can print out a message when an object is added or removed from the player inventory. The `ShowInventory()` function will show the inventory on standard output and the `Drop()` function will drop one item from the inventory.

In `SendMessage()` we test which message we got. If it is one of the input messages we call the appropriate API method in ``pcactormove'` to move, rotate, or jump the player. If it is one of the inventory messages then we show the inventory contents.

    BehaviourPlayer::BehaviourPlayer (iCelEntity* entity, BehaviourLayer* bl, iCelPlLayer* pl)
      : BehaviourCommon (entity, bl, pl)
    {
      id_pccommandinput_forward1 = pl->FetchStringID ("pccommandinput_forward1");
      id_pccommandinput_forward0 = pl->FetchStringID ("pccommandinput_forward0");
      id_pccommandinput_backward1 = pl->FetchStringID ("pccommandinput_backward1");
      id_pccommandinput_backward0 = pl->FetchStringID ("pccommandinput_backward0");
      id_pccommandinput_rotateleft1 = pl->FetchStringID ("pccommandinput_rotateleft1");
      id_pccommandinput_rotateleft0 = pl->FetchStringID ("pccommandinput_rotateleft0");
      id_pccommandinput_rotateright1 = pl->FetchStringID ("pccommandinput_rotateright1");
      id_pccommandinput_rotateright0 = pl->FetchStringID ("pccommandinput_rotateright0");
      id_pccommandinput_cammode1 = pl->FetchStringID ("pccommandinput_cammode1");
      id_pccommandinput_drop1 = pl->FetchStringID ("pccommandinput_drop1");

      id_pcinventory_addchild = pl->FetchStringID ("pcinventory_addchild");
      id_pcinventory_removechild = pl->FetchStringID ("pcinventory_removechild");
    }

    void BehaviourPlayer::GetActorMove ()
    {
      if (!pcactormove)
      {
        pcactormove = CEL_QUERY_PROPCLASS_ENT (entity, iPcActorMove);
      }
    }

    void BehaviourPlayer::GetInventory ()
    {
      if (!pcinventory)
      {
        pcinventory = CEL_QUERY_PROPCLASS_ENT (entity, iPcInventory);
      }
    }

    void BehaviourPlayer::GetMesh ()
    {
      if (!pcmesh)
      {
        pcmesh = CEL_QUERY_PROPCLASS_ENT (entity, iPcMesh);
      }
    }

    void BehaviourPlayer::ShowInventory ()
    {
      size_t count = pcinventory->GetEntityCount ();
      size_t i;
      for (i = 0 ; i < count ; i++)
      {
        iCelEntity* child = pcinventory->GetEntity (i);
        printf ("  child %d is '%s'\n", i, child->GetName ());
      }
    }

    void BehaviourPlayer::Drop ()
    {
      GetInventory ();
      size_t count = pcinventory->GetEntityCount ();
      if (count <= 0)
      {
        printf ("Inventory is empty!\n");
        return;
      }
      iCelEntity* child = pcinventory->GetEntity (0);
      pcinventory->RemoveEntity (child);
      csRef<iPcLinearMovement> pclinmove = CEL_QUERY_PROPCLASS_ENT (
      	child, iPcLinearMovement);
      if (pclinmove)
      {
        GetMesh ();
        // Now we get current position and orientation from player
        // mesh and from that we calculate a spot in front of the
        // player where we will drop down the item.
        csVector3 pos = pcmesh->GetMesh ()->GetMovable ()->GetTransform ()
        	.This2Other (csVector3 (0, 2, -2));
        iSector* sector = pcmesh->GetMesh ()->GetMovable ()->GetSectors ()->Get (0);
        pclinmove->SetPosition (pos, 0, sector);
        pclinmove->SetVelocity (csVector3 (0, .1f, 0));
        csRef<iPcMesh> pcmesh_child = CEL_QUERY_PROPCLASS_ENT (child, iPcMesh);
        if (pcmesh_child) pcmesh_child->Show ();
      }
    }

    bool BehaviourPlayer::SendMessage (csStringID msg_id,
    	iCelPropertyClass* pc,
      	celData& ret, iCelParameterBlock* params, va_list arg)
    {
      GetActorMove ();

      if (msg_id == id_pccommandinput_forward1)
        pcactormove->Forward (true);
      else if (msg_id == id_pccommandinput_forward0)
        pcactormove->Forward (false);
      else if (msg_id == id_pccommandinput_backward1)
        pcactormove->Backward (true);
      else if (msg_id == id_pccommandinput_backward0)
        pcactormove->Backward (false);
      else if (msg_id == id_pccommandinput_rotateleft1)
        pcactormove->RotateLeft (true);
      else if (msg_id == id_pccommandinput_rotateleft0)
        pcactormove->RotateLeft (false);
      else if (msg_id == id_pccommandinput_rotateright1)
        pcactormove->RotateRight (true);
      else if (msg_id == id_pccommandinput_rotateright0)
        pcactormove->RotateRight (false);
      else if (msg_id == id_pccommandinput_cammode1)
        pcactormove->ToggleCameraMode ();
      else if (msg_id == id_pccommandinput_drop1)
        Drop ();
      else if (msg_id == id_pcinventory_addchild)
      {
        GetInventory ();
        printf ("Got a new object! Objects in inventory:\n");
        ShowInventory ();
      }
      else if (msg_id == id_pcinventory_removechild)
      {
        GetInventory ();
        printf ("Object removed from inventory! Objects in inventory:\n");
        ShowInventory ();
      }
      else
        return BehaviourCommon::SendMessage (msg_id, pc, ret, params, arg);;

      return true;
    }

The box behaviour supports picking up and putting itself in the player inventory. To do that entities that use this behaviour need to use the ``pcmeshselect'` property class so that they get a message whenever the entity is selected. In this behaviour we wait for that message and when we get it we will add ourselves to the inventory of the player.

    BehaviourBox::BehaviourBox (iCelEntity* entity, BehaviourLayer* bl, iCelPlLayer* pl)
      : BehaviourCommon (entity, bl, pl)
    {
      id_pcmeshsel_down = pl->FetchStringID ("pcmeshsel_down");
      GetPlayer ();
    }

    void BehaviourBox::PickUp ()
    {
      if (!player) return;
      csRef<iPcInventory> pcinv = CEL_QUERY_PROPCLASS_ENT (player, iPcInventory);
      if (pcinv)
      {
        pcinv->AddEntity (entity);
        csRef<iPcMesh> pcmesh = CEL_QUERY_PROPCLASS_ENT (entity, iPcMesh);
        if (pcmesh) pcmesh->Hide ();
      }
    }

    void BehaviourBox::GetPlayer ()
    {
      if (!pcmeshsel || !player)
      {
        pcmeshsel = CEL_QUERY_PROPCLASS_ENT (entity, iPcMeshSelect);
        player = pl->FindEntity ("player");
        if (!player) return;
        csRef<iPcCamera> pccamera = CEL_QUERY_PROPCLASS_ENT (player, iPcCamera);
        if (pccamera)
          pcmeshsel->SetCamera (pccamera);
      }
    }

    bool BehaviourBox::SendMessage (csStringID msg_id,
    	iCelPropertyClass* pc,
      	celData& ret, iCelParameterBlock* params, va_list arg)
    {
      if (msg_id == id_pcmeshsel_down)
      {
        PickUp ();
        return true;
      }

      return BehaviourCommon::SendMessage (msg_id, pc, ret, params, arg);
    }

This behaviour represents an entity that moves around on a path. We use catmull rom splines to calculate a smooth path. The `csPath` class from Crystal Space helps with this. In the `ReadPath()` function we read the path from properties that are assigned to the entity (using the general ``pcproperties'` property class).

Entities that use this behaviour also need to have a ``pctimer'` property class. This is used to be able to restart the movement path after a certain time has expired.

    BehaviourBadOne::BehaviourBadOne (iCelEntity* entity, BehaviourLayer* bl, iCelPlLayer* pl)
      : BehaviourCommon (entity, bl, pl)
    {
      id_pctimer_wakeup = pl->FetchStringID ("pctimer_wakeup");
      id_par_elapsedticks = pl->FetchStringID ("cel.parameter.elapsedticks");

      ReadPath ();
    }

    static bool GetPropVector (iPcProperties* pcprop, const char* prefix, int i, csVector3& v)
    {
      csString propname = prefix;
      propname += i;
      size_t idx = pcprop->GetPropertyIndex (propname);
      if (idx == csArrayItemNotFound) return false;
      if (!pcprop->GetPropertyVector (idx, v)) return false;
      return true;
    }

    static bool GetPropLong (iPcProperties* pcprop, const char* prefix, int i, long& l)
    {
      csString propname = prefix;
      propname += i;
      size_t idx = pcprop->GetPropertyIndex (propname);
      if (idx == csArrayItemNotFound) return false;
      l = pcprop->GetPropertyLong (idx);
      return true;
    }

    void BehaviourBadOne::ReadPath ()
    {
      csRef<iPcProperties> pcprop = CEL_QUERY_PROPCLASS_ENT (entity, iPcProperties);

      // Count the number of points we have.
      int count = 0;
      for (;;)
      {
        csVector3 v;
        if (!GetPropVector (pcprop, "pos", count+1, v)) break;
        count++;
      }

      path.AttachNew (new csPath(count));

      int i = 0;
      long totaltime = 0;
      for (i = 0 ; i < count ; i++)
      {
        csVector3 pos, forward, up;
        GetPropVector (pcprop, "pos", i+1, pos);
        if (!GetPropVector (pcprop, "forward", i+1, forward)) forward.Set (1, 0, 0);
        if (!GetPropVector (pcprop, "up", i+1, up)) up.Set (0, 1, 0);
        long time;
        if (!GetPropLong (pcprop, "time", i+1, time)) time = 1000;
        path->SetPositionVector (i, pos);
        path->SetUpVector (i, up);
        path->SetForwardVector (i, forward);
        path->SetTime (i, float (totaltime) / 1000.0);
        totaltime += time;
      }

      csRef<iPcLinearMovement> pclinmove = CEL_QUERY_PROPCLASS_ENT (
      	entity, iPcLinearMovement);
      if (pclinmove)
      {
        for (i = 0 ; i < count ; i++)
        {
          pclinmove->SetPathAction (i, "default");
        }
      }
    }

    void BehaviourBadOne::Restart ()
    {
      csRef<iPcLinearMovement> pclinmove = CEL_QUERY_PROPCLASS_ENT (
      	entity, iPcLinearMovement);
      if (pclinmove)
      {
        pclinmove->SetPath (path);
        pclinmove->SetPathTime (0);
      }
    }

    bool BehaviourBadOne::SendMessage (csStringID msg_id,
    	iCelPropertyClass* pc,
      	celData& ret, iCelParameterBlock* params, va_list arg)
    {
      if (msg_id == id_pctimer_wakeup)
      {
        Restart ();
        return true;
      }

      return BehaviourCommon::SendMessage (msg_id, pc, ret, params, arg);
    }

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ]

This document was generated using texi2html 1.76.