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

4.2.4.7 Adversary Class

The Adversary class represents an enemy. For every enemy in the game we will have an adversary.

 
class Adversary : public scfImplementationExt1<Adversary,
	csObject, scfFakeInterface<Adversary> >
{
private:
  csWeakRef<iMeshWrapper> mesh;
  RoomCoordinate current_location;
  AppMazing* app;

  bool moving;
  csVector3 start, end;
  float remaining_seconds;

public:
  SCF_INTERFACE(Adversary, 1, 0, 0);

  Adversary (AppMazing* app, iMeshWrapper* mesh, const RoomCoordinate& rc);
  virtual ~Adversary () { }

  void ThinkAndMove (float elapsed_seconds);

  iMeshWrapper* GetMesh () const { return mesh; }
};

This class declaration may look a bit strange. The reason for this is that we need to be able to find the correct Adversary instance starting from a Crystal Space mesh object. To do that Crystal Space provides the csObject system. With this system one can relate objects to each other (parent/child relationships). Note that this relation is purely organizational. It is not a visual hierarchical object. (see section Managing Game Specific Data). What we want to do here is to attach the adversary instance to the mesh so that when we have a mesh we can go back to the adversary instance without having to scan all adversaries one by one. To do this we basically have to implement the iObject interface. Objects that implement that interface can be attached to other objects that also implement that interface. A mesh (iMeshWrapper) also implements iObject so that is very convenient here. To let our Adversary class implement iObject the easiest way is to inherit from csObject. And this is what the class inheritence above actually does. The scfImplementationExt1 template makes it possible to let Adversary be an SCF class (which is needed to implement iObject) and extend from csObject as well. Finally we add a scfFakeInterface<Adversary> so that we can actually find out if an iObject instance is an adversary. How to do this will be shown later.

In the class declaration itself we also need to specify a version of this class using SCF_INTERFACE. For our simple case this is actually not very useful but in general these versions can allow someone to be sure it gets the right version of the interface.

The main function in this class is the ThinkAndMove() function. This function takes the elapsed seconds (since last frame) as a parameter and based on that it will calculate how and where to move the adversary.

 
Adversary::Adversary (AppMazing* app,
	iMeshWrapper* mesh, const RoomCoordinate& rc) :
	scfImplementationType (this)
{
  Adversary::app = app;
  Adversary::mesh = mesh;
  current_location = rc;
  moving = false;
}

void Adversary::ThinkAndMove (float elapsed_seconds)
{
  if (!moving)
  {
    const csArray<RoomCoordinate>& connections = app->GetMaze ()
    	->GetConnections (current_location);
    size_t moveto = (rand () >> 3) % connections.Length ();
    if (app->GetMaze ()->IsSpaceFree (connections[moveto]))
    {
      start.x = float (current_location.x) * ROOM_DIMENSION;
      start.y = float (current_location.y) * ROOM_DIMENSION;
      start.z = float (current_location.z) * ROOM_DIMENSION;
      app->GetMaze ()->FreeSpace (current_location);
      current_location = connections[moveto];
      app->GetMaze ()->OccupySpace (current_location);
      end.x = float (current_location.x) * ROOM_DIMENSION;
      end.y = float (current_location.y) * ROOM_DIMENSION;
      end.z = float (current_location.z) * ROOM_DIMENSION;
      remaining_seconds = ADVERSARY_MOVETIME;
      moving = true;
    }
  }
  else
  {
    remaining_seconds -= elapsed_seconds;
    csVector3 new_pos;
    if (remaining_seconds <= 0)
    {
      moving = false;
      new_pos = end;
    }
    else
    {
      csMath3::Between (end, start, new_pos,
      	100.0 * remaining_seconds / ADVERSARY_MOVETIME, 0);
    }
    iMovable* movable = mesh->GetMovable ();
    iSector* old_sector = movable->GetSectors ()->Get (0);
    bool mirror;
    iSector* new_sector = old_sector->FollowSegment (
    	movable->GetTransform (), new_pos, mirror, true);
    movable->SetSector (new_sector);
    movable->GetTransform ().SetOrigin (new_pos);
    movable->UpdateMove ();
  }
}

An adversary can be in two states: either it is moving or else it is not moving. This state is set in the ‘moving’ boolean variable. If the adversary is not moving the ThinkAndMove() will attempt a random connection from the current node it is occupying. If the destination node of that connection is free then it will initiate a move to that location and set ‘moving’ to true. Otherwise it will do nothing (in which case next frame the test happens again).

If the adversary is moving then ThinkAndMove() will calculate (based on how much time has elapsed since previous frame) how far the adversary should move between the original node and the new destination node. If it arrives at the destination the moving flag is set to false again.


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

This document was generated using texi2html 1.76.