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

4.2.4.10 Game Class

Game is the main class controlling the game logic.

 
class Game
{
private:
  AppMazing* app;

  /**
   * The factory for our adversary.
   */
  csRef<iMeshFactoryWrapper> adversary_factory;

  /**
   * The factory for our laser beam.
   */
  csRef<iMeshFactoryWrapper> laserbeam_factory;

  /**
   * The factory for our explosion.
   */
  csRef<iMeshFactoryWrapper> explosion_factory;

  //--- Game Data ------------------------------------------------------

  Player player;
  Maze maze;
  Laser laser;
  /// A list of all adversaries.
  csRefArray<Adversary> adversaries;

  /// A list of all explosions in progress.
  csArray<Explosion> explosions;

  /// Start an explosion.
  void StartExplosion (iSector* sector, const csVector3& pos);
  /// Handle all explosions.
  void HandleExplosions (csTicks elapsed_ticks);

  //--- Setup of Game --------------------------------------------------
  bool CreateFactories ();
  bool CreateAdversary (int x, int y, int z);

  bool InitCollisionDetection ();

public:
  /**
   * Constructor.
   */
  Game(AppMazing* app);

  /**
   * Setup the game.
   */
  bool SetupGame ();

  Maze* GetMaze () { return &maze; }

  /**
   * Explode an adversary.
   */
  void ExplodeAdversary (Adversary* adv);

  /**
   * Handle a frame in the game.
   */
  void Handle (csTicks elapsed_ticks);

  /**
   * Handle game keyboard event.
   */
  bool OnKeyboard (iEvent& ev);
};

The important data structures used here are:

The important functions used in this class are:

 
Game::Game() :
  	app (app),
	player (app),
	maze (app),
	laser (app)
{
}

Just the constructor.

 
bool Game::CreateFactories ()
{
  csRef<iGeneralFactoryState> fstate;
  iEngine* engine = app->GetEngine ();
  iLoader* loader = app->GetLoader ();

  //---------------------------------------------------------------------
  // Adversary factory.
  adversary_factory = engine->CreateMeshFactory (
  	"crystalspace.mesh.object.genmesh", "adversary");
  if (!adversary_factory) return false;

  fstate = scfQueryInterface<iGeneralFactoryState> (
  	adversary_factory->GetMeshObjectFactory ());
  csEllipsoid ellips (
  	csVector3 (0, 0, 0),
	csVector3 (ADVERSARY_DIMENSION, ADVERSARY_DIMENSION,
		ADVERSARY_DIMENSION));
  fstate->GenerateSphere (ellips, 10);

  if (!loader->LoadTexture ("adversary_texture", "/lib/stdtex/misty.jpg"))
    return app->ReportError ("Error loading 'misty' texture!");
  iMaterialWrapper* adversary_material = engine->GetMaterialList ()
  	->FindByName ("adversary_texture");
  adversary_factory->GetMeshObjectFactory ()
    ->SetMaterialWrapper (adversary_material);

  //---------------------------------------------------------------------
  // Beam factory.
  laserbeam_factory = engine->CreateMeshFactory (
  	"crystalspace.mesh.object.genmesh", "laserbeam");
  if (!laserbeam_factory) return false;

  fstate = scfQueryInterface<iGeneralFactoryState> (
  	laserbeam_factory->GetMeshObjectFactory ());
  csBox3 laser_box (
  	csVector3 (-LASER_WIDTH, -LASER_WIDTH, 0),
  	csVector3 (LASER_WIDTH, LASER_WIDTH, LASER_LENGTH));
  fstate->GenerateBox (laser_box);
  fstate->SetLighting (false);
  fstate->SetColor (csColor (1.0, 1.0, 1.0));
  // We don't want to hit the player against the laserbeam when it is
  // visible so we disable the collision detection mesh here.
  laserbeam_factory->GetMeshObjectFactory ()->GetObjectModel ()
  	->SetPolygonMeshColldet (0);

  if (!loader->LoadTexture ("laserbeam_texture", "/lib/stdtex/blobby.jpg"))
    return app->ReportError ("Error loading 'blobby' texture!");
  iMaterialWrapper* laserbeam_material = engine->GetMaterialList ()
  	->FindByName ("laserbeam_texture");
  laserbeam_factory->GetMeshObjectFactory ()
    ->SetMaterialWrapper (laserbeam_material);

  //---------------------------------------------------------------------
  // Beam object.
  csRef<iMeshWrapper> laserbeam = engine->CreateMeshWrapper (
  	laserbeam_factory, "laserbeam");
  if (!laserbeam)
    return app->ReportError ("Error creating laserbeam mesh!");
  // Set our laser beam to NOHITBEAM so that we can use HitBeam() methods
  // to find out what our laser hits without HitBeam() returning the
  // laser itself.
  laserbeam->GetFlags ().Set (CS_ENTITY_NOHITBEAM);
  laser.SetMeshWrapper (laserbeam);

  //---------------------------------------------------------------------
  // Explosion factory.
  explosion_factory = engine->CreateMeshFactory (
  	"crystalspace.mesh.object.particles", "explosion");
  if (!explosion_factory) return false;

  csRef<iParticleSystemFactory> pbase = scfQueryInterface<
  	iParticleSystemFactory> (explosion_factory->GetMeshObjectFactory ());

  if (!loader->LoadTexture ("explosion_texture", "/lib/std/spark.png"))
    return app->ReportError ("Error loading 'spark' texture!");
  iMaterialWrapper* explosion_material = engine->GetMaterialList ()
  	->FindByName ("explosion_texture");
  
  explosion_factory->GetMeshObjectFactory ()->SetMaterialWrapper (explosion_material);
  explosion_factory->GetMeshObjectFactory ()->SetMixMode (CS_FX_ADD);

  pbase->SetParticleSize (csVector2 (0.2f, 0.2f));
  pbase->SetParticleRenderOrientation (CS_PARTICLE_CAMERAFACE_APPROX);
//  pbase->SetDeepCreation (true);

  csRef<iParticleBuiltinEmitterFactory> emitter_factory = 
    csLoadPluginCheck<iParticleBuiltinEmitterFactory> (app->GetObjectRegistry (),
    "crystalspace.mesh.object.particles.emitter");
  
  csRef<iParticleBuiltinEmitterSphere> emitter = emitter_factory->CreateSphere ();
  emitter->SetRadius (0);
  emitter->SetParticlePlacement (CS_PARTICLE_BUILTIN_CENTER);
  emitter->SetDuration (EXPLOSION_EMITTIME / 1000.0f);
  emitter->SetInitialTTL (EXPLOSION_PARTTIMELOW / 1000.0f, EXPLOSION_PARTTIMEHIGH / 1000.0f);
  emitter->SetUniformVelocity (false);
  emitter->SetInitialVelocity (csVector3 (3.0f,0,0), csVector3 (0.0f));
  emitter->SetEmissionRate (50);

  pbase->AddEmitter (emitter);

  return true;
}

We use a genmesh for both the laserbeam and the adversary sphere (see section Genmesh Mesh Object).The laserbeam is created in such a way that we can orient it easily along the camera transformation. That means that it is a long beam oriented in the Z direction.

For the explosion we use a particles mesh (see section Particle Systems in General). With a bit of effort we're confident that you can make a better explosion by tweaking the parameters and using a better texture.

A few special remarks about the geometry above. On the laserbeam factory we use iObjectModel::SetPolygonMeshColldet() to clear the collision detection mesh. The laserbeam mesh is generated at roughly the same spot as the camera. We don't want the collision detection system to detect hits with it (we use other methods to detect hits of the laserbeam with adversary).

The laserbeam mesh is also special. In the Laser::Check() function we used iSector::HitBeamPortals() to detect if the laser hits anything. We don't want that function to detect the laserbeam mesh itself (which happens to be at that same spot) so that's why we set the `CS_ENTITY_NOHITBEAM' flag on the mesh.

 
bool Game::CreateAdversary (int x, int y, int z)
{
  float sx = float (x) * ROOM_DIMENSION;
  float sy = float (y) * ROOM_DIMENSION;
  float sz = float (z) * ROOM_DIMENSION;
  csRef<iMeshWrapper> adversary = app->GetEngine ()->CreateMeshWrapper (
  	adversary_factory, "adversary",
	maze.GetSector (x, y, z), csVector3 (sx, sy, sz));
  if (!adversary)
    return app->ReportError ("Couldn't create adversary mesh!");

  RoomCoordinate rc (x, y, z);
  Adversary* adv = new Adversary (app, adversary, rc);
  adversaries.Push (adv);
  adversary->QueryObject ()->ObjAdd ((iObject*)adv);
  adv->DecRef ();
  
  return true;
}

Here we create an adversary. Note how we first make a new instance of Adversary, then we push it on the `adversaries' table and finally we call DecRef(). This is very important. The `adversaries' is a ref counting array (csRefArray). That means that it will increase the reference count of all objects that you push on it. Also note that new objects automatically start with a reference count of 1. So that means that after pushing the object on `adversaries' the reference count will be 2. We must therefor release our own reference at the end by using DecRef().

In this function we also add our adversary instance as a child of the adversary mesh. This is possible because both the mesh and the adversary implement iObject. Note that adding our adversary to the mesh as a child will also increase the reference count. So finally there will be two references to every adversary.

 
bool Game::InitCollisionDetection ()
{
  csColliderHelper::InitializeCollisionWrappers (
      app->GetCollisionDetectionSystem (), app->GetEngine (), 0);
  return player.InitCollisionDetection ();
}

bool Game::SetupGame ()
{
  if (!maze.CreateGeometry ())
    return app->ReportError("Error creating the geometry!");

  if (!CreateFactories ())
    return app->ReportError ("Error creating mesh factories!");

  iEngine* engine = app->GetEngine ();
  engine->Prepare ();

  if (!InitCollisionDetection ())
    return false;

  if (!CreateAdversary (0, 0, 2)) return false;
  if (!CreateAdversary (1, 1, 1)) return false;
  if (!CreateAdversary (1, 0, 2)) return false;
  if (!CreateAdversary (2, 2, 2)) return false;
  return true;
}

Here we setup the game by creating the maze (Maze::CreateGeometry()) and then creating all factories (CreateFactories()). After that we create iEngine::Prepare(). In CreateGeometry() we used iEngine::SetLightingCacheMode() to disable caching of lightmaps. This will be used in Prepare() so that the lightmaps are calculated right away.

After that setup collision detection.

And at the end we create four adversaries that will roam the game.

 
void Game::ExplodeAdversary (Adversary* adv)
{
  iMeshWrapper* mesh = adv->GetMesh ();
  StartExplosion (mesh->GetMovable ()->GetSectors ()->Get (0),
      	mesh->GetMovable ()->GetTransform ().GetOrigin ());
  app->GetEngine ()->RemoveObject (mesh);
  adversaries.Delete (adv);
}

void Game::StartExplosion (iSector* sector, const csVector3& pos)
{
  csRef<iMeshWrapper> explo = app->GetEngine ()->CreateMeshWrapper (
  	explosion_factory, "explosion", sector, pos);
  if (!explo)
  {
    app->ReportError ("Error creating explosion mesh!");
    return;
  }
  explo->SetZBufMode (CS_ZBUF_TEST);
  Explosion exp (explo, EXPLOSION_TIME);
  explosions.Push (exp);
}

void Game::HandleExplosions (csTicks elapsed_ticks)
{
  size_t i = 0;
  while (i < explosions.Length ())
  {
    if (explosions[i].Handle (elapsed_ticks)) i++;
    else
    {
      app->GetEngine ()->RemoveObject (explosions[i].GetMesh ());
      explosions.DeleteIndex (i);
    }
  }
}

The ExplodeAdversary() function is called from the Laser::Check() function whenever an adversary needs to be exploded. To start the actual explosion mesh the StartExplosion() function is used. This function will just remove the adversary mesh from the engine (using iEngine::RemoveObject()) and then it will delete the adversary from the `adversaries' array. Note that these two operations will cause the reference count of the adversary to become 0. i.e. deleting the adversary from the `adversaries' array will decrease one reference and then removing the mesh from the engine will remove the other since when the mesh is deleted it will also release the reference to its children.

The StartExplosion() function will create an explosion mesh out of the explosion factory at the given sector and position. The explosion mesh factory is created using `CS_FX_ADD' mixmode. That means that every particle will be added to the background. This is a kind of transparency. The advantage of this transparency is that you don't have to sort the particles from back to front (`add' mixmode can be done in any order). We use `CS_ZBUF_TEST' z-buffer mode for the explosion mesh because we don't want the particles to update the z-buffer but just test against it (so particles will appear correctly clipped against walls that are near the camera).

HandleExplosions() is called every frame and will go over all active explosions to check if they should be deleted.

 
void Game::Handle (csTicks elapsed_ticks)
{
  float elapsed_seconds = float (elapsed_ticks) / 1000.0;

  // Handle the laser.
  laser.Handle (elapsed_ticks);

  // Handle explosions.
  HandleExplosions (elapsed_ticks);

  // Move the camera.
  player.MoveAndRotateCamera (elapsed_seconds);

  // Let all the adversaries think about what to do.
  size_t i;
  for (i = 0 ; i < adversaries.Length () ; i++)
    adversaries[i]->ThinkAndMove (elapsed_seconds);
}

Handle() will be automatically called every frame. In this function we will handle all game logic. Handling the game logic basically means we just call code in Laser, Explosion, Adversary, and Player to calculate the logic based on the number of seconds that have ellapsed since the previous frame.

 
bool Game::OnKeyboard(iEvent& ev)
{
  // We got a keyboard event.
  if (csKeyEventHelper::GetEventType(&ev) == csKeyEventTypeDown)
  {
    // The user pressed a key (as opposed to releasing it).
    utf32_char code = csKeyEventHelper::GetCookedCode(&ev);
    switch (code)
    {
      case 'e':
	player.StartMovement (csVector3 (0, 1, 0));
	return true;
      case 'q':
	player.StartMovement (csVector3 (0, -1, 0));
	return true;
      case 'a':
	player.StartMovement (csVector3 (-1, 0, 0));
	return true;
      case 'd':
	player.StartMovement (csVector3 (1, 0, 0));
	return true;
      case 'w':
	player.StartMovement (csVector3 (0, 0, 1));
	return true;
      case 's':
	player.StartMovement (csVector3 (0, 0, -1));
	return true;
      case CSKEY_UP:
	player.StartRotation (csVector3 (-1, 0, 0));
	return true;
      case CSKEY_DOWN:
	player.StartRotation (csVector3 (1, 0, 0));
	return true;
      case CSKEY_LEFT:
	player.StartRotation (csVector3 (0, -1, 0));
	return true;
      case CSKEY_RIGHT:
	player.StartRotation (csVector3 (0, 1, 0));
	return true;
      case ' ':
	laser.Start ();
	return true;
    }
  }
  return false;
}

In this function we handle all keyboard events for the game.


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

This document was generated using texi2html 1.76.