Home - Forums - Documentation - Gallery - Bugs


This tutorial is the first part of a long (hopefully interesting) serial. In this serial we create a game step by step with Crystal Space. Every step will have a detailed description, with source code of course. I assume, you’ve read (at least) the first three tutorial in the Crystal Space Manual, when not, please, do it first. I assume it too, you can build a new (working) CS application in your system, and you don’t have issues when running CS apps in general.

Cally in the maze(Cim) will be a small, but real game. It will contents anything, that full games have: GUI, background music, sound effects, story line, debug console, cheat mode, simply rules, load/save system, hall of fame, and installer (on Windows) or install script (on Linux, tested on Suse 10.0). Cally in the maze falls under the LGPL license.

My main system is WindowsXP with MSVC 8 Express.

I use the latest stable version of Crystal Space (currently 1.0RC2).

Step One: Creating a Python console

The simpmap tutorial is a good starting point to begin Cally In The Maze. It has 3d graphics, collision detection, map loading,so ideal for us. Here is the code (I changed the class name only):


ifndef __CIM_H__
#define __CIM_H__

#include <crystalspace.h>

class cimGame : public csApplicationFramework, public csBaseEventHandler
  bool Setup ();

  /// A pointer to the 3D engine.
  csRef<iEngine> engine;

  /// A pointer to the map loader plugin.
  csRef<iLoader> loader;

  ///A pointer to the 2d renderer plugin.
  csRef<iGraphics2D> g2d;

  /// A pointer to the 3D renderer plugin.
  csRef<iGraphics3D> g3d;

  /// A pointer to the keyboard driver.
  csRef<iKeyboardDriver> kbd;

  /// A pointer to the virtual clock.
  csRef<iVirtualClock> vc;

  /// A pointer to the collision detection system.
  csRef<iCollideSystem> cdsys;

  /// A pointer to the view which contains the camera.
  csRef<iView> view;

  /// A pointer to the sector the camera will be in.
  iSector* room;

  /// Our collider used for gravity and CD.
  csColliderActor collider_actor;

   * Handle keyboard events - ie key presses and releases.
   * This routine is called from the event handler in response to a 
   * csevKeyboard event.
  bool OnKeyboard (iEvent&);

   * Setup everything that needs to be rendered on screen. This routine
   * is called from the event handler in response to a csevProcess
   * broadcast message.
  void ProcessFrame ();
   * Finally render the screen. This routine is called from the event
   * handler in response to a csevFinalProcess broadcast message.
  void FinishFrame ();
  /// Here we will load our world from a map file.
  bool LoadMap ();


  /// Construct our game. This will just set the application ID for now.
  cimGame ();

  /// Destructor.
  ~cimGame ();

  /// Final cleanup.
  void OnExit ();

   * Main initialization routine. This routine will set up some basic stuff
   * (like load all needed plugins, setup the event handler, ...).
   * In case of failure this routine will return false. You can assume
   * that the error message has been reported to the user.
  bool OnInitialize (int argc, char* argv[]);

   * Run the application.
   * First, there are some more initialization (everything that is needed 
   * by cimGame1 to use Crystal Space), then this routine fires up the main
   * event loop. This is where everything starts. This loop will  basically
   * start firing events which actually causes Crystal Space to function.
   * Only when the program exits this function will return.
  bool Application ();

  CS_EVENTHANDLER_NAMES ("crystalspace.cim")


And here is the .cpp file:


#include "cim.h"



cimGame::cimGame ()
  SetApplicationName ("CrystalSpace.cim");

cimGame::~cimGame ()

bool cimGame::Setup ()
  // Now get the pointer to various modules we need. We fetch them
  // from the object registry. The RequestPlugins() call we did earlier
  // registered all loaded plugins with the object registry.
  // The virtual clock.
  g3d = CS_QUERY_REGISTRY (GetObjectRegistry(), iGraphics3D);
  if (!g3d) return ReportError ("Failed to locate 3D renderer!");

  g2d = CS_QUERY_REGISTRY (GetObjectRegistry(), iGraphics2D);
  if (!g3d) return ReportError ("Failed to locate 2D renderer!");

  engine = CS_QUERY_REGISTRY (GetObjectRegistry(), iEngine);
  if (!engine) return ReportError ("Failed to locate 3D engine!");

  vc = CS_QUERY_REGISTRY (GetObjectRegistry(), iVirtualClock);
  if (!vc) return ReportError ("Failed to locate Virtual Clock!");

  kbd = CS_QUERY_REGISTRY (GetObjectRegistry(), iKeyboardDriver);
  if (!kbd) return ReportError ("Failed to locate Keyboard Driver!");

  loader = CS_QUERY_REGISTRY (GetObjectRegistry(), iLoader);
  if (!loader) return ReportError ("Failed to locate Loader!");

  cdsys = CS_QUERY_REGISTRY (GetObjectRegistry(), iCollideSystem);
  if (!cdsys) return ReportError ("Failed to locate CD system!");

  // We need a View to the virtual world.
  view.AttachNew(new csView (engine, g3d));
  iGraphics2D* g2d = g3d->GetDriver2D ();
  // We use the full window to draw the world.
  view->SetRectangle (0, 0, g2d->GetWidth (), g2d->GetHeight ());

  // Here we load our world from a map file.
  if (!LoadMap ()) return false;

  // Initialize collision objects for all loaded objects.
  csColliderHelper::InitializeCollisionWrappers (cdsys, engine);

  // Let the engine prepare all lightmaps for use and also free all images 
  // that were loaded for the texture manager.
  engine->Prepare ();

  // Find the starting position in this level.
  csVector3 pos (0);
  if (engine->GetCameraPositions ()->GetCount () > 0)
    // There is a valid starting position defined in the level file.
    iCameraPosition* campos = engine->GetCameraPositions ()->Get (0);
    room = engine->GetSectors ()->FindByName (campos->GetSector ());
    pos = campos->GetPosition ();
    // We didn't find a valid starting position. So we default
    // to going to room called 'room' at position (0,0,0).
    room = engine->GetSectors ()->FindByName ("room");
    pos = csVector3 (0, 0, 0);
  if (!room)
    ReportError("Can't find a valid starting position!");

  // Now we need to position the camera in our world.
  view->GetCamera ()->SetSector (room);
  view->GetCamera ()->GetTransform ().SetOrigin (pos);

  // Initialize our collider actor.
  collider_actor.SetCollideSystem (cdsys);
  collider_actor.SetEngine (engine);
  csVector3 legs (.2f, .3f, .2f);
  csVector3 body (.2f, 1.2f, .2f);
  csVector3 shift (0, -1, 0);
  collider_actor.InitializeColliders (view->GetCamera (),
  	legs, body, shift);

  return true;
void cimGame::ProcessFrame ()
  // First get elapsed time from the virtual clock.
  csTicks elapsed_time = vc->GetElapsedTicks ();

  csVector3 obj_move (0);
  csVector3 obj_rotate (0);

  if (kbd->GetKeyState (CSKEY_SHIFT))
    // If the user is holding down shift, the arrow keys will cause
    // the camera to strafe up, down, left or right from it's
    // current position.
    if (kbd->GetKeyState (CSKEY_RIGHT))
      obj_move = CS_VEC_RIGHT * 3.0f;
    if (kbd->GetKeyState (CSKEY_LEFT))
      obj_move = CS_VEC_LEFT * 3.0f;
    if (kbd->GetKeyState (CSKEY_UP))
      obj_move = CS_VEC_UP * 3.0f;
    if (kbd->GetKeyState (CSKEY_DOWN))
      obj_move = CS_VEC_DOWN * 3.0f;
    // left and right cause the camera to rotate on the global Y
    // axis; page up and page down cause the camera to rotate on the
    // _camera's_ X axis (more on this in a second) and up and down
    // arrows cause the camera to go forwards and backwards.
    if (kbd->GetKeyState (CSKEY_RIGHT))
      obj_rotate.Set (0, 1, 0);
    if (kbd->GetKeyState (CSKEY_LEFT))
      obj_rotate.Set (0, -1, 0);
    if (kbd->GetKeyState (CSKEY_PGUP))
      obj_rotate.Set (1, 0, 0);
    if (kbd->GetKeyState (CSKEY_PGDN))
      obj_rotate.Set (-1, 0, 0);
    if (kbd->GetKeyState (CSKEY_UP))
      obj_move = CS_VEC_FORWARD * 3.0f;
    if (kbd->GetKeyState (CSKEY_DOWN))
      obj_move = CS_VEC_BACKWARD * 3.0f;

  collider_actor.Move (float (elapsed_time) / 1000.0f, 1.0f,
    	obj_move, obj_rotate);

  // Tell 3D driver we're going to display 3D things.
  if (!g3d->BeginDraw (engine->GetBeginDrawFlags () | CSDRAW_3DGRAPHICS))

  // Tell the camera to render into the frame buffer.
  view->Draw ();

void cimGame::FinishFrame ()
  // Just tell the 3D renderer that everything has been rendered.
	g3d->FinishDraw ();
	g3d->Print (0);


bool cimGame::OnKeyboard(iEvent& ev)
  // We got a keyboard event.
  csKeyEventType eventtype = csKeyEventHelper::GetEventType(&ev);
  if (eventtype == csKeyEventTypeDown)
    // The user pressed a key (as opposed to releasing it).
    utf32_char code = csKeyEventHelper::GetCookedCode(&ev);

    if (code == CSKEY_ESC)
      // The user pressed escape to exit the application.
      // The proper way to quit a Crystal Space application
      // is by broadcasting a csevQuit event. That will cause the
      // main runloop to stop. To do that we get the event queue from
      // the object registry and then post the event.
      csRef<iEventQueue> q = 
        CS_QUERY_REGISTRY(GetObjectRegistry(), iEventQueue);
      if (q.IsValid()) q->GetEventOutlet()->Broadcast(csevQuit(GetObjectRegistry()));
  return false;

bool cimGame::OnInitialize(int /*argc*/, char* /*argv*/ [])
  // RequestPlugins() will load all plugins we specify. In addition
  // it will also check if there are plugins that need to be loaded
  // from the config system (both the application config and CS or
  // global configs). In addition it also supports specifying plugins
  // on the commandline.
  iObjectRegistry *object_reg;
  if (!csInitializer::RequestPlugins(object_reg = GetObjectRegistry(),
    return ReportError("Failed to initialize plugins!");


  // Now we need to setup an event handler for our application.
  // Crystal Space is fully event-driven. Everything (except for this
  // initialization) happens in an event.
  if (!RegisterQueue(GetObjectRegistry(), csevAllEvents(GetObjectRegistry())))
    return ReportError("Failed to set up event handler!");
  return true;

void cimGame::OnExit()

bool cimGame::Application()
  // Open the main system. This will open all the previously loaded plug-ins.
  // i.e. all windows will be opened.
  if (!OpenApplication (GetObjectRegistry()))
    return ReportError ("Error opening system!");

  if (!Setup())
    return false;

  // This calls the default runloop. This will basically just keep
  // broadcasting process events to keep the game going.
  Run ();

  return true;

bool cimGame::LoadMap ()
  // Set VFS current directory to the level we want to load.
  csRef<iVFS> VFS (CS_QUERY_REGISTRY (GetObjectRegistry (), iVFS));
  VFS->ChDir ("/lev/flarge");
  // Load the level file which is called 'world'.
  if (!loader->LoadMapFile ("world"))
    ReportError("Error couldn't load level!");

  return true;

 * Main function
int main (int argc, char* argv[])
  /* Runs the application. 
   * csApplicationRunner<> is a small wrapper to support "restartable" 
   * applications (ie where CS needs to be completely shut down and loaded 
   * again). cimGame does not use that functionality itself, however, it
   * allows you to later use "cimGame.Restart();" and it'll just work.
  return csApplicationRunner<cimGame>::Run (argc, argv);
| Article | Discussion | View source | History |