Crystal Space: 4.2.4.7 Adversary Class

[ < ] | [ > ] |  | [ << ] | [ 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 ] | [ >> ]

This document was generated using texi2html 1.76.