Home - Forums - Documentation - Gallery - Bugs
(tutorial work in progress)
Current revision (16:49, 8 July 2008) (edit) (undo)
m (We need Python)
 
(29 intermediate revisions not shown.)
Line 1: Line 1:
 +
[[Category:Tutorial]]
= Introduction =
= Introduction =
-
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.
+
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 tutorials in the Crystal Space Manual, if not, please, do it first. I assume too that 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.
+
'''Cally in the maze'''(Cim) will be a small, but real game. It will contain anything that full games have: GUI, background music, sound effects, story line, debug console, cheat mode, simple rules, load/save system, hall of fame, and installer (on Windows) or install script (on Linux, tested on Suse 10.0).
-
My main system is WindowsXP with MSVC 8 Express.
+
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.2).
-
I use the latest stable version of Crystal Space (currently 1.0RC2).
+
= Step One: Creating a Python console =
 +
== Very first step ==
 +
The simpmap tutorial is a good starting point to begin Cally In The Maze. It has 3d graphics, collision detection, map loading, so it's ideal for us. You can find the details here: [http://www.crystalspace3d.org/docs/online/manual-1.2/Tutorial-Simple-Map.php#0 tutorial]
-
= 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):
+
Nothing new. We load and initialize the CS environment, load the necessary plugins, load a map, and create a collider actor and a camera. The user can move in the world and can jump. A wise programmer always makes a debug console for his app. Just look at walktest. You can watch, test, debug etc. your maps, or just play around, using console commands. We will use the console heavily later, let’s go to create it.
-
<pre>
+
== Console basics ==
-
//cim.h
+
The console in CS has three parts, a console input ( it ensures that you can type into the console), a console output (it ensures that you can show the console’s content), and a so called execution callback (when you press enter, this callback will handle your commands).
-
ifndef __CIM_H__
+
The CS SDK has one console input plugin, and two console output plugins (standard and fancy, we will use the standard, but you can use the fancy version, if you want). This execution callback struct is only a pure interface (no standard implementation of course, almost every application needs a different execution callback), so we need to implement this iConsoleExecCallback SCF interface. (There is not a big deal.)
-
#define __CIM_H__
+
-
#include <crystalspace.h>
+
We encapsulate the three classes in one class, called cimConsole. The console class provides all neccessary services, to toggle, show and hide, intialize, etc, the console. The console has an Initialize function, this function loads the needed plugins. The console class is easy to use ( I hope):
-
class cimGame : public csApplicationFramework, public csBaseEventHandler
+
<pre>
 +
class cimConsole:public csRefCount
{
{
private:
private:
-
+
iObjectRegistry* object_reg;
-
bool Setup ();
+
bool visible;
-
/// A pointer to the 3D engine.
+
public:
-
csRef<iEngine> engine;
+
csRef<iConsoleInput> conin;
 +
csRef<iConsoleOutput> conout;
-
/// A pointer to the map loader plugin.
+
~cimConsole();
-
csRef<iLoader> loader;
+
bool Initialize(iObjectRegistry* obj_reg);
 +
void Show();
 +
void Hide();
 +
void Toggle();
 +
bool HandleEvent (iEvent& ev);
 +
bool IsVisible();
 +
};
 +
</pre>
-
///A pointer to the 2d renderer plugin.
+
== About csRefCount ==
-
csRef<iGraphics2D> g2d;
+
-
/// A pointer to the 3D renderer plugin.
+
Some note about ''CsRefCount''. This class will ensure, that ''cimConsole'' can be used with ''csRef'' template. Every class, that implement ''DecRef()'' and ''IncRef'' can be used with ''csRef''. ''csRefCount'' implements this functions - so we use ''csRefCount'' as ancestor and continue our work happily.
-
csRef<iGraphics3D> g3d;
+
-
/// A pointer to the keyboard driver.
+
== We need Python ==
-
csRef<iKeyboardDriver> kbd;
+
-
/// A pointer to the virtual clock.
+
We need a script interpreter, the console is useless without this. We don’t write one (like in walktest), we will use Python. When Python is enough for the guys by '''NASA''' or '''Google''', it will be enough for Cally in the maze too. You can download it from here: [http://www.python.org Python homepage]
-
csRef<iVirtualClock> vc;
+
-
/// A pointer to the collision detection system.
+
I use Python 2.5, but you can use another Python version, if you want. Please build the cspython at least (and optionally _cspace.pyd) when this modules are not compiled yet.
-
csRef<iCollideSystem> cdsys;
+
You can download Python 2.5 from [http://www.python.org/download/releases/2.5/ here.]
-
/// A pointer to the view which contains the camera.
+
At the end of tutorial, you find a solution ''without'' Python.
-
csRef<iView> view;
+
-
/// A pointer to the sector the camera will be in.
+
=== Note for MSVC users ===
-
iSector* room;
+
-
/// Our collider used for gravity and CD.
+
When you use MSVC, like MSVC8 Express, you can see, the official wkstypical solution don’t have project for this. Don’t worry, just follow my MSVC and Python [http://www.crystalspace3d.org/main/Compiling_Python_binding_MSVC buliding tutorial]. Other option: you can download the dll here: [http://crystaldoc.atw.hu/cspython.zip cspython.zip]
-
csColliderActor collider_actor;
+
-
/**
+
This dll requires CS 1.2, Python 2.5 and works with MSVC8 Express only (and maybe with other MSVC8 editions). Ok, Python is ready to work, let’s go to create an execution callback!
-
* 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&);
+
-
/**
+
== Creating an execution callback ==
-
* 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 ();
+
-
public:
+
<pre>
 +
struct ExecCallback : public scfImplementation1<ExecCallback,iConsoleExecCallback>
 +
{
 +
ExecCallback () : scfImplementationType (this) { }
 +
virtual ~ExecCallback () { }
 +
virtual void Execute (const char* cmd);
 +
csRef<iScript> python;
 +
bool InitPython(iObjectRegistry *obj_reg);
 +
};
 +
</pre>
-
/// Construct our game. This will just set the application ID for now.
 
-
cimGame ();
 
-
/// Destructor.
+
We implement the ''iConsoleExecCallback'' struct. This struct has one member function only, the ''Execute'' function. We add an ''InitPython'' function, and a reference for the python module.
-
~cimGame ();
+
-
/// Final cleanup.
+
We hide this struct inside the main Console class, (the app’s other elements don’t need to reach this). The console class will be something like this:
-
void OnExit ();
+
-
/**
+
<pre>
-
* Main initialization routine. This routine will set up some basic stuff
+
class cimConsole: public csRefCount
-
* (like load all needed plugins, setup the event handler, ...).
+
{
-
* In case of failure this routine will return false. You can assume
+
private:
-
* that the error message has been reported to the user.
+
iObjectRegistry* object_reg;
-
*/
+
-
bool OnInitialize (int argc, char* argv[]);
+
-
/**
+
struct ExecCallback : public scfImplementation1<ExecCallback,iConsoleExecCallback>
-
* Run the application.
+
{
-
* First, there are some more initialization (everything that is needed
+
ExecCallback () : scfImplementationType (this) { }
-
* by cimGame1 to use Crystal Space), then this routine fires up the main
+
virtual ~ExecCallback () { }
-
* event loop. This is where everything starts. This loop will basically
+
virtual void Execute (const char* cmd);
-
* start firing events which actually causes Crystal Space to function.
+
csRef<iScript> python;
-
* Only when the program exits this function will return.
+
bool InitPython(iObjectRegistry *obj_reg);
-
*/
+
};
-
bool Application ();
+
-
CS_EVENTHANDLER_NAMES ("crystalspace.cim")
+
bool visible;
-
CS_EVENTHANDLER_NIL_CONSTRAINTS
+
-
};
+
-
#endif
+
public:
 +
csRef<iConsoleInput> conin;
 +
csRef<iConsoleOutput> conout;
 +
 +
~cimConsole();
-
And here is the .cpp file:
+
bool Initialize(iObjectRegistry* obj_reg);
 +
void Show();
 +
void Hide();
 +
void Toggle();
 +
bool HandleEvent (iEvent& ev);
 +
bool IsVisible();
 +
};
 +
</pre>
 +
Ok, we go to write the implementation.
-
 
+
<pre>
-
/*
+
void cimConsole::ExecCallback::Execute(const char *cmd)
-
cim.cpp
+
-
*/
+
-
 
+
-
#include "cim.h"
+
-
 
+
-
CS_IMPLEMENT_APPLICATION
+
-
 
+
-
//-----------------------------------------------------------------------------
+
-
 
+
-
cimGame::cimGame ()
+
{
{
-
SetApplicationName ("CrystalSpace.cim");
+
//we call the python interpreter:
 +
python->RunText(cmd);
}
}
 +
</pre>
 +
 +
We call the python interpreter(the RunText function send our text to the Python interpreter) ,and the execution is done (let’s work python!). We initialize the python binding with this code:
-
cimGame::~cimGame ()
+
<pre>
 +
bool cimConsole::ExecCallback::InitPython(iObjectRegistry *obj_reg)
{
{
 +
python = csQueryRegistryOrLoad<iScript> (obj_reg,
 +
"crystalspace.script.python");
 +
if (!python)
 +
{
 +
csReport (obj_reg,
 +
CS_REPORTER_SEVERITY_ERROR, "cim.console",
 +
"Can't load the python script module!");
 +
return false;
 +
}
 +
python->RunText("from cspace import *");
 +
if (!python->LoadModule("cshelper") )
 +
return false;
 +
return true;
}
}
 +
</pre>
-
bool cimGame::Setup ()
+
We try to load the python plugin first. When done, we import two python module. The cspace module is essential, when we want to use CS functions from python. (Officialy the cspython plugin loads cspace at startup, but I have to load it manually for some reason .).
-
{
+
-
// 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);
+
These scipts are in the ''CS/scripts/python'' folder.
-
if (!g3d) return ReportError ("Failed to locate 2D renderer!");
+
-
engine = CS_QUERY_REGISTRY (GetObjectRegistry(), iEngine);
+
The cshelper module is a very useful, it redirects the python stdout and stderr streams to the reporter plugin. The reporter uses our console to show the messages, so we can see the Python interpreter’s any anwser.
-
if (!engine) return ReportError ("Failed to locate 3D engine!");
+
-
vc = CS_QUERY_REGISTRY (GetObjectRegistry(), iVirtualClock);
+
Note, when you run the app with –python-enable-reporter comandline option, the cshelper module will be loaded internally, you don’t need to load this from code.
-
if (!vc) return ReportError ("Failed to locate Virtual Clock!");
+
-
kbd = CS_QUERY_REGISTRY (GetObjectRegistry(), iKeyboardDriver);
+
== Finishing our console class ==
-
if (!kbd) return ReportError ("Failed to locate Keyboard Driver!");
+
<pre>
-
 
+
bool cimConsole::Initialize(iObjectRegistry* obj_reg)
-
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 ();
+
-
}
+
-
else
+
-
{
+
-
// 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.
+
object_reg = obj_reg;
-
csTicks elapsed_time = vc->GetElapsedTicks ();
+
-
csVector3 obj_move (0);
+
conout = csQueryRegistryOrLoad<iConsoleOutput> (object_reg,
-
csVector3 obj_rotate (0);
+
"crystalspace.console.output.standard");
 +
if (!conout)
 +
{
 +
csReport (object_reg,
 +
CS_REPORTER_SEVERITY_ERROR, "cim.console",
 +
"Can't load the output console!");
 +
return false;
 +
}
 +
conin = csQueryRegistryOrLoad<iConsoleInput> (object_reg,
 +
"crystalspace.console.input.standard");
 +
if (!conin)
 +
{
 +
csReport (object_reg,
 +
CS_REPORTER_SEVERITY_ERROR, "cim.console",
 +
"Can't load the input console!");
 +
return false;
 +
}
 +
</pre>
-
if (kbd->GetKeyState (CSKEY_SHIFT))
+
We load or query the csconin and csconout plugins, the csQueryRegistryOrLoad template is very useful to do this easily. When everything is fine, we bind our console input to the console output, set the prompt, create a new execution callback object, and bind to the console input.
-
{
+
-
// 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;
+
-
}
+
-
else
+
-
{
+
-
// 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,
+
<pre>
-
obj_move, obj_rotate);
+
conin->Bind (conout);
 +
conin->SetPrompt ("cim:");
 +
ExecCallback* cb = new ExecCallback ();
 +
conin->SetExecuteCallback (cb);
 +
if(!cb->InitPython(object_reg)) return false;
-
// Tell 3D driver we're going to display 3D things.
+
cb->DecRef ();
-
if (!g3d->BeginDraw (engine->GetBeginDrawFlags () | CSDRAW_3DGRAPHICS))
+
conout->SetVisible (false);
-
return;
+
-
// Tell the camera to render into the frame buffer.
+
return true;
-
view->Draw ();
+
}
}
 +
</pre>
-
void cimGame::FinishFrame ()
+
When the console gets an event, we delegate it to the console output plugin:
-
{
+
-
// Just tell the 3D renderer that everything has been rendered.
+
-
+
-
g3d->FinishDraw ();
+
-
g3d->Print (0);
+
-
}
+
<pre>
-
 
+
bool cimConsole::HandleEvent(iEvent &ev)
-
bool cimGame::OnKeyboard(iEvent& ev)
+
{
{
-
+
return conin->HandleEvent(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;
+
}
}
 +
</pre>
-
bool cimGame::OnInitialize(int /*argc*/, char* /*argv*/ [])
+
This functions control the console’s visiblity:
-
{
+
-
// 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(),
+
-
CS_REQUEST_VFS,
+
-
CS_REQUEST_OPENGL3D,
+
-
CS_REQUEST_ENGINE,
+
-
CS_REQUEST_FONTSERVER,
+
-
CS_REQUEST_IMAGELOADER,
+
-
CS_REQUEST_LEVELLOADER,
+
-
CS_REQUEST_REPORTER,
+
-
CS_REQUEST_REPORTERLISTENER,
+
-
CS_REQUEST_PLUGIN("crystalspace.collisiondetection.opcode",
+
-
iCollideSystem),
+
-
CS_REQUEST_END))
+
-
return ReportError("Failed to initialize plugins!");
+
-
csBaseEventHandler::Initialize(GetObjectRegistry());
+
<pre>
-
 
+
void cimConsole::Hide()
-
// 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()
+
{
{
 +
visible = false;
 +
conout->SetVisible(false);
}
}
-
bool cimGame::Application()
+
void cimConsole::Show()
{
{
-
// Open the main system. This will open all the previously loaded plug-ins.
+
visible = true;
-
// i.e. all windows will be opened.
+
conout->SetVisible(true);
-
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 ()
+
void cimConsole::Toggle()
{
{
-
// Set VFS current directory to the level we want to load.
+
visible = !visible;
-
csRef<iVFS> VFS (CS_QUERY_REGISTRY (GetObjectRegistry (), iVFS));
+
conout->SetVisible(visible);
-
VFS->ChDir ("/lev/flarge");
+
-
// Load the level file which is called 'world'.
+
-
if (!loader->LoadMapFile ("world"))
+
-
ReportError("Error couldn't load level!");
+
-
 
+
-
return true;
+
}
}
-
/*---------------------------------------------------------------------*
+
bool cimConsole::IsVisible()
-
* Main function
+
-
*---------------------------------------------------------------------*/
+
-
int main (int argc, char* argv[])
+
{
{
-
/* Runs the application.
+
return visible;
-
*
+
-
* 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);
+
}
}
</pre>
</pre>
-
Nothing new. We load and initialize the CS environment, load the necessary plugins, load a map, and create a collider actor and a camera. The user can move in the world and can jump.
+
The console is ready. We create one instance in the main app. Add the following private member to the cimGame class:
-
A wise programmer always makes a debug console for his app. Just look at walktest , you can watch, test, debug etc. your maps , or just play around, using console commands. We will use heavily the console later, let’s go to create it.
+
== Final steps ==
-
The console in CS has three parts, a console input ( it ensures that you can type into the console) , a console output (it ensures that you can show the console’s content), and a so called execution callback (when you press enter, this callback will handle your commands).
+
This comes into the OnInitialize function (''cim.cpp''):
 +
<pre>
 +
csRef<cimConsole> console;
 +
console.AttachNew(new cimConsole);
 +
if ( !console->Initialize(GetObjectRegistry() ))
 +
return ReportError("Failed to initialize console!");
 +
console->Show();
 +
</pre>
-
The CS SDK has one console input plugin , and two console output plugin (standard and fancy, we will use the standard, but you can use the fancy version , if you want ).
 
-
This execution callback struct is only a pure interface (no standard implementation of course, almost every application needs different execution callback), so we need implement this iConsoleExecCallback SCF interface. (There is not a big deal.)
 
- 
-
We encapsulate the three class in one class, called cimConsole. The console class provides
 
-
all neccessary services, to toggle, show and hide, intialize, etc, the console.. The console has an Initialize function, this function loads the needed plugins. The console class is easy to use ( I hope):
 
 +
This comes to the OnKeyboard function:
<pre>
<pre>
-
class cimConsole
+
//Old code:
-
{
+
csKeyEventType eventtype = csKeyEventHelper::GetEventType(&ev);
-
private:
+
if (eventtype == csKeyEventTypeDown)
-
+
{
 +
// The user pressed a key (as opposed to releasing it).
 +
utf32_char code = csKeyEventHelper::GetCookedCode(&ev);
-
iObjectRegistry* object_reg;
+
//End old code
 +
//The user pressed tab to toggle console
 +
if (code == CSKEY_TAB)
 +
{
 +
console->Toggle();
 +
return false;
 +
}
 +
</pre>
-
bool visible;
+
When the user presses the TAB key, we change the console’s visiblity. When the console is active, we have to delegate all keyboard event to the console class:
-
public:
+
<pre>
-
csRef<iConsoleInput> conin;
+
if (console->IsVisible() )
 +
return console->HandleEvent(ev);
 +
</pre>
-
csRef<iConsoleOutput> conout;
+
Finally, we have to check, when the user presses the arrays, that console is visible or not. When visible, we don’t make any move in the 3d world:
-
~cimConsole();
+
<pre>
 +
void cimGame::ProcessFrame ()
 +
{
 +
// First get elapsed time from the virtual clock.
 +
csTicks elapsed_time = vc->GetElapsedTicks ();
-
bool Initialize(iObjectRegistry* obj_reg);
+
csVector3 obj_move (0);
 +
csVector3 obj_rotate (0);
-
void Show();
+
if (!console->IsVisible())
 +
{
 +
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))
 +
// etc.
 +
</pre>
-
void Hide();
+
On the ''FinishFrame'' function, we render the console output onto the screen:
-
void Toggle();
+
<pre>
-
 
+
void cimGame::FinishFrame ()
-
bool HandleEvent (iEvent& ev);
+
{
-
 
+
// the console rendering part
-
bool IsVisible();
+
if ( console->IsVisible() )
 +
{
 +
g3d->BeginDraw (CSDRAW_2DGRAPHICS);
 +
console->conout->Draw2D (0);
 +
g3d->BeginDraw (CSDRAW_3DGRAPHICS);
 +
console->conout->Draw3D (0);
 +
}
 +
// Just tell the 3D renderer that everything has been rendered.
 +
g3d->FinishDraw ();
 +
g3d->Print (0);
}
}
</pre>
</pre>
-
We need a script interpreter now, the console is useless without this. We don’t write one (like in walktest), We will use Python. When Python is enough for the guys by NASA or Google, it will be enough for Cally in the maze too. You can download it from here:
+
== And now... ==
 +
And now compile the app and test it. With the ''TAB'' key you can toggle the console, and you can add regular Python commands, Python answers on the console. We’re done. We have a console, we can control his visiblity, we call python to interpret our commands.
-
[http://www.python.org Python homepage]
+
Get the sources from [http://www.crystaldoc.atw.hu/src/cim/step1.zip here]
 +
== I don't want to use Python! ==
-
I use Python 2.3.5., but you can use newer Python versions too. Please build the cspython at least (and optionally _cspace.pyd) when this modules are not compiled yet..
+
When you want use other scripting langeuge, just change this function:
-
Note for MSVC users:
+
<pre>
 +
bool cimConsole::ExecCallback::InitPython(iObjectRegistry *obj_reg)
 +
{
 +
python = csQueryRegistryOrLoad<iScript> (obj_reg,
 +
"crystalspace.script.python");
 +
.
 +
.
 +
.
 +
//more code
 +
</pre>
-
When you use MSVC, like MSVC8 Express, you can see, the official wkstypical solution don’t have project for this. Don’t worry, just follow my MSVC and Python tutorial, (it’s not done yet). Other option: you can download the dll here:
+
You can load here another scripting plugin. Second option, you implement your own command handling code (like walktest).
-
[http://w3.cablenet.hu/maxjoco/cspython. cspython.dll]
+
I show you a very simple and limited version to do this, without explanation. You can type a few command without attributes. You can download [http://www.crystaldoc.atw.hu/src/cim/step1-no_python.zip here]
 +
:
-
This dll requires CS1.0RC1, Python2.3.5 and works with MSVC8 Express only.
 
-
Ok, Python is ready to work, let’s go to create an execution callback!
 
 +
[[User:Nagyjoco|Nagyjoco]] 11:46, 16 May 2008 (CEST)
-
<pre>
 
-
struct ExecCallback : public scfImplementation1<ExecCallback,iConsoleExecCallback>
+
[[Tutorials|Tutorial Home]]
-
{
+
-
ExecCallback () : scfImplementationType (this) { }
+
-
virtual ~ExecCallback () { }
+
-
virtual void Execute (const char* cmd);
+
-
csRef<iScript> python;
+
-
bool InitPython(iObjectRegistry *obj_reg);
+
-
};
+
-
</pre>
+
[[Cimstep2|Next: Step2 - Basic configuration handling]]
-
 
+
-
 
+
-
We implement the iConsoleExecCallback struct. This struct has one member function only, the Execute function. We add an InitPython function, and a reference for the python module.
+
-
 
+
-
We hide this struct inside the main Console class, (the app’s other elements don’t need to reach this). The console class will be something like this:
+

Current revision

Contents

Introduction

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 tutorials in the Crystal Space Manual, if not, please, do it first. I assume too that 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 contain anything that full games have: GUI, background music, sound effects, story line, debug console, cheat mode, simple 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.2).

Step One: Creating a Python console

Very first step

The simpmap tutorial is a good starting point to begin Cally In The Maze. It has 3d graphics, collision detection, map loading, so it's ideal for us. You can find the details here: tutorial


Nothing new. We load and initialize the CS environment, load the necessary plugins, load a map, and create a collider actor and a camera. The user can move in the world and can jump. A wise programmer always makes a debug console for his app. Just look at walktest. You can watch, test, debug etc. your maps, or just play around, using console commands. We will use the console heavily later, let’s go to create it.

Console basics

The console in CS has three parts, a console input ( it ensures that you can type into the console), a console output (it ensures that you can show the console’s content), and a so called execution callback (when you press enter, this callback will handle your commands).

The CS SDK has one console input plugin, and two console output plugins (standard and fancy, we will use the standard, but you can use the fancy version, if you want). This execution callback struct is only a pure interface (no standard implementation of course, almost every application needs a different execution callback), so we need to implement this iConsoleExecCallback SCF interface. (There is not a big deal.)

We encapsulate the three classes in one class, called cimConsole. The console class provides all neccessary services, to toggle, show and hide, intialize, etc, the console. The console has an Initialize function, this function loads the needed plugins. The console class is easy to use ( I hope):

class cimConsole:public csRefCount
{
private:
    iObjectRegistry* object_reg; 
    bool visible;

public:
    csRef<iConsoleInput> conin;
    csRef<iConsoleOutput> conout;

    ~cimConsole();
    bool Initialize(iObjectRegistry* obj_reg);
    void Show();
    void Hide();
    void Toggle();
    bool HandleEvent (iEvent& ev);
    bool IsVisible();
};

About csRefCount

Some note about CsRefCount. This class will ensure, that cimConsole can be used with csRef template. Every class, that implement DecRef() and IncRef can be used with csRef. csRefCount implements this functions - so we use csRefCount as ancestor and continue our work happily.

We need Python

We need a script interpreter, the console is useless without this. We don’t write one (like in walktest), we will use Python. When Python is enough for the guys by NASA or Google, it will be enough for Cally in the maze too. You can download it from here: Python homepage

I use Python 2.5, but you can use another Python version, if you want. Please build the cspython at least (and optionally _cspace.pyd) when this modules are not compiled yet. You can download Python 2.5 from here.

At the end of tutorial, you find a solution without Python.

Note for MSVC users

When you use MSVC, like MSVC8 Express, you can see, the official wkstypical solution don’t have project for this. Don’t worry, just follow my MSVC and Python buliding tutorial. Other option: you can download the dll here: cspython.zip

This dll requires CS 1.2, Python 2.5 and works with MSVC8 Express only (and maybe with other MSVC8 editions). Ok, Python is ready to work, let’s go to create an execution callback!

Creating an execution callback

struct ExecCallback : public scfImplementation1<ExecCallback,iConsoleExecCallback>
{
    ExecCallback () : scfImplementationType (this) { }
    virtual ~ExecCallback () { }
    virtual void Execute (const char* cmd);
    csRef<iScript> python;
    bool InitPython(iObjectRegistry *obj_reg);
};


We implement the iConsoleExecCallback struct. This struct has one member function only, the Execute function. We add an InitPython function, and a reference for the python module.

We hide this struct inside the main Console class, (the app’s other elements don’t need to reach this). The console class will be something like this:

class cimConsole: public csRefCount
{
private:
    iObjectRegistry* object_reg;

    struct ExecCallback : public scfImplementation1<ExecCallback,iConsoleExecCallback>
    {
        ExecCallback () : scfImplementationType (this) { }
        virtual ~ExecCallback () { }
        virtual void Execute (const char* cmd);
        csRef<iScript> python;
        bool InitPython(iObjectRegistry *obj_reg);
    };

    bool visible;

public:
    csRef<iConsoleInput> conin;
    csRef<iConsoleOutput> conout;
	
    ~cimConsole();

    bool Initialize(iObjectRegistry* obj_reg);
    void Show();
    void Hide();
    void Toggle();
    bool HandleEvent (iEvent& ev);
    bool IsVisible();
};

Ok, we go to write the implementation.

void cimConsole::ExecCallback::Execute(const char *cmd)
{
    //we call the python interpreter:
    python->RunText(cmd);
}

We call the python interpreter(the RunText function send our text to the Python interpreter) ,and the execution is done (let’s work python!). We initialize the python binding with this code:

bool cimConsole::ExecCallback::InitPython(iObjectRegistry *obj_reg)
{
    python = csQueryRegistryOrLoad<iScript> (obj_reg,
	  "crystalspace.script.python");
    if (!python)
    {
        csReport (obj_reg,
	    	CS_REPORTER_SEVERITY_ERROR, "cim.console",
		"Can't load the python script module!");
        return false;
    }
    python->RunText("from cspace import *");
    if (!python->LoadModule("cshelper") )
        return false;
    return true;
}

We try to load the python plugin first. When done, we import two python module. The cspace module is essential, when we want to use CS functions from python. (Officialy the cspython plugin loads cspace at startup, but I have to load it manually for some reason .).

These scipts are in the CS/scripts/python folder.

The cshelper module is a very useful, it redirects the python stdout and stderr streams to the reporter plugin. The reporter uses our console to show the messages, so we can see the Python interpreter’s any anwser.

Note, when you run the app with –python-enable-reporter comandline option, the cshelper module will be loaded internally, you don’t need to load this from code.

Finishing our console class

bool cimConsole::Initialize(iObjectRegistry* obj_reg)
{
    object_reg = obj_reg;

    conout = csQueryRegistryOrLoad<iConsoleOutput> (object_reg,
        "crystalspace.console.output.standard");
    if (!conout)
    {
        csReport (object_reg,
	    	CS_REPORTER_SEVERITY_ERROR, "cim.console",
		"Can't load the output console!");
        return false;
    }
    conin = csQueryRegistryOrLoad<iConsoleInput> (object_reg,
  	"crystalspace.console.input.standard");
    if (!conin)
    {
        csReport (object_reg,
	    	CS_REPORTER_SEVERITY_ERROR, "cim.console",
		"Can't load the input console!");
        return false;
    }

We load or query the csconin and csconout plugins, the csQueryRegistryOrLoad template is very useful to do this easily. When everything is fine, we bind our console input to the console output, set the prompt, create a new execution callback object, and bind to the console input.

    conin->Bind (conout);
    conin->SetPrompt ("cim:");
    ExecCallback* cb = new ExecCallback ();
    conin->SetExecuteCallback (cb);
    if(!cb->InitPython(object_reg)) return false;

    cb->DecRef ();
    conout->SetVisible (false);

    return true;
}

When the console gets an event, we delegate it to the console output plugin:

bool cimConsole::HandleEvent(iEvent &ev)
{
    return conin->HandleEvent(ev);
}

This functions control the console’s visiblity:

void cimConsole::Hide()
{
    visible = false;
    conout->SetVisible(false);
}

void cimConsole::Show()
{
    visible = true;
    conout->SetVisible(true);
}

void cimConsole::Toggle()
{
    visible = !visible;
    conout->SetVisible(visible);
}

bool cimConsole::IsVisible()
{
    return visible;
}

The console is ready. We create one instance in the main app. Add the following private member to the cimGame class:

Final steps

This comes into the OnInitialize function (cim.cpp):

    csRef<cimConsole> console;
    console.AttachNew(new cimConsole);
    if ( !console->Initialize(GetObjectRegistry() ))
        return ReportError("Failed to initialize console!");
    console->Show();


This comes to the OnKeyboard function:

    //Old code:
    csKeyEventType eventtype = csKeyEventHelper::GetEventType(&ev);
    if (eventtype == csKeyEventTypeDown)
    {
        // The user pressed a key (as opposed to releasing it).
        utf32_char code = csKeyEventHelper::GetCookedCode(&ev);
	
    //End old code
    //The user pressed tab to toggle console
    if (code == CSKEY_TAB)
    {
        console->Toggle();
        return false;
    }

When the user presses the TAB key, we change the console’s visiblity. When the console is active, we have to delegate all keyboard event to the console class:

    if (console->IsVisible() )
        return console->HandleEvent(ev);

Finally, we have to check, when the user presses the arrays, that console is visible or not. When visible, we don’t make any move in the 3d world:

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 (!console->IsVisible())
    {
        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))
            // etc.

On the FinishFrame function, we render the console output onto the screen:

void cimGame::FinishFrame ()
{
    // the console rendering part
    if ( console->IsVisible() )
    {
        g3d->BeginDraw (CSDRAW_2DGRAPHICS);
        console->conout->Draw2D (0);
        g3d->BeginDraw (CSDRAW_3DGRAPHICS);
        console->conout->Draw3D (0);
    }	
    // Just tell the 3D renderer that everything has been rendered.
    g3d->FinishDraw ();
    g3d->Print (0);
}

And now...

And now compile the app and test it. With the TAB key you can toggle the console, and you can add regular Python commands, Python answers on the console. We’re done. We have a console, we can control his visiblity, we call python to interpret our commands.

Get the sources from here

I don't want to use Python!

When you want use other scripting langeuge, just change this function:

bool cimConsole::ExecCallback::InitPython(iObjectRegistry *obj_reg)
{
    python = csQueryRegistryOrLoad<iScript> (obj_reg,
	  "crystalspace.script.python");
.
.
.
//more code
    

You can load here another scripting plugin. Second option, you implement your own command handling code (like walktest).

I show you a very simple and limited version to do this, without explanation. You can type a few command without attributes. You can download here


Nagyjoco 11:46, 16 May 2008 (CEST)


Tutorial Home

Next: Step2 - Basic configuration handling

| Article | Discussion | View source | History |