[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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.