- Full game title: Tibor: Tale of a Kind Vampire
- Developer: Cateia Games
- Year of release: 2009
- Type: Commercial
- Target Platforms: Windows, Linux, Mac, iPhone, Nintendo DS
For questions / suggestions about this page use this forum thread.
Table of contents[Show/Hide]
- Software and tools
- CPU Optimizations
- GPU Optimizations
- iPhone port
Tibor: Tale of a Kind Vampire is our first platform game, inspired by good ol' games like Prehistorik and Super Mario.
The game is based on a Croatian fairy tale written by author Damir ÄŒivrak.
The game targets the casual gamers audience.
- Programming Language: -Python 2.5
- Main library: Python-Ogre (v1.6)
- Other libraries: OIS, -CEGUI, OpenAL
- Ogre plugins: OctreeSceneManager
Here are the reasons we chose Ogre3D for this game as well as the pros and cons related to this genre:
- Both OpenGL and DirectX support
- ParticleSystem support
- Octree support
- Various optimisation support
- No game console support
- Some exporter trouble
The Video game industry evolves rapidly and developing/maintaining your own in-house engine just isn't profitable.
Ogre is a modern, well documented and extensible 3D engine, and most importantly - open source!
Since the engine itself is free, we are morally obliged to give something back to the community. So we occasionally write patches for Ogre or develop/maintain code. Currently we're working on improving the Theora Video Plugin and writing wiki articles like these ;)
Cegui is a mature gui library with a relatively good set of tools and well documented API.
Our only objection would be that it is very big and difficult to set up.
We chose to use the -Python programming language for the following reasons:
- Python-Ogre works very well, stable, fast, supports the latest ogre and all the plugins and libraries commonly used with Ogre.
- Productivity - Having many years of experience as a C++ developer I have to say that it takes us much much less time and lines of code to do the same thing in -Python then in C++. And since time is money, this choice makes the project cheaper. Less lines of code means statistically less bugs and a codebase that is easier to maintain.
- Ease of debugging - -Python supports the 'eval & exec' functions which gives the programmer the ability to execute code given in a string. hold here for a minute, think about what that means... Yes, it means you can have a console in the game and call your functions as well as Ogre's, create and manipulate objects etc. Basically you can write and test code on the fly.
- No long compiling - Nuf' said.
The downsides of using -Python over C++:
- python is slower, but since most of the cpu intensive work is done within Ogre, there is virtually no performance impact.
- Filesize: Another downside of using -Python and PythonOgre is that there is more data in the final product. Each wrapper (C interface) library takes space. python25.dll and other python dependencies give weight to the final version of the game which is somewhat important for a casual game whose primary distribution channel is the internet. However, the size impact is not big enough to be a problem (the entire game packaged takes about 80 MB)
- Memory usage: -Python being a high-level scripting language takes more RAM for similar datatypes then C++, plus the wrappers themselves take a few megabytes. We estimate that python and python-ogre take up to 50-60 Megabytes more RAM then the C++ equivalent.
Conclusion: Productivity and Cost are important aspects of any commercial project. The downsides listed above are all negligible within the scope of our project making -Python and Python-Ogre a logical choice.
We initially chose to use OgreAL, but it caused us a lot of unexplained problems: slowdowns and crashes so we decided to make our own simple audio library.
The library had to be used in python so we had to wrap it somehow. We used the same tools Python-Ogre uses to achieve that goal: Py++. Wrapping C++ code with Py++ is quite easy (once you figure out all that Boost.Python stuff).
Subversion of course ;)
Tibor uses five different tilesets throughout the game, all of which have an identical layout: 4x4 tiles as shown in the sample tileset on the left.
Four tiles in the upper left corner are ground tiles, and they are randomized on each level load, to avoid having repetitive patterns.
The game has several planes, the 'main' plane which the character walks on, usually a background plane and one or two planes coplanar with the main plane, such as cavern tiles.
Once the program starts, it creates 16 entities (quad meshes), one for each tile. and it does so by first creating Manual Objects and then converting them to meshes from which the entities are created.
These entities are then used to generate Static Geometry for each Tile Plane. Static Geometry is a cool little object that makes sure non-movable geometry (like the tile planes) are rendered as efficiently as possible. But I won't get into details how they operate, it is explained quite well on Ogre's API reference.
I've defined a line for each tile Tibor can walk on or collide with and then generate one line for each such tile in the level and use that for collision detection.
A line is defined similar to a Plane equation, consisting of three elements:
- center point - the center of a line
- normal vector - the direction it faces
- radius - distance from the center to the end of the line (imagine a circle of radius r originating at the center point)
This way I can easily detect on which side of the line a point is on.
So, collision detection is done in a way that a CollidableObject ( a base class used in a couple of classes, including Tibor of course) saves it's current node position in a temporary variable, moves the node a bit in the direction it has to move (desired direction + gravity) and compares these two points on all the surrounding lines.
If a line is found on which both points are not on the same side of the line - collision has occurred!
I've thought long and hard how to represent characters and objects in collision detection, bounding sphere, box, etc, but a colleague has accidentally given me a perfect solution - a point! Each character is represented only with one point (only for tile collision ofcourse)! perfect for a platformer.
So, when colliding against a line normal, a character is stopped, but when colliding along the normal, the character is allowed to move. This way, the character can jump on platforms from below, which is a common thing in platform games.
While designing the object system I tried to use as much benefits from Object-Oriented programming as possible, which turned out to be very efficient.
Every possible object class is derived from a single class, containing some variables and functions which every object should implement (or inherit the default behavior from this class) which the Object Manager class can operate with.
The most commonly used object is a StaticObject, a simple textured, unlit quad which can be scaled, tiled, inverted and rotated. That simple set of features enabled us to create very diverse and un-repetitive levels.
Background class is a simple quad mesh which uses Identity Projection (coordinates are in screen space) and its material is scrolled by the camera position (scaled by a given x and y factor) and optionally a scaled time function (for eg. clouds)
Coin class is derived from StaticObject, making use of StaticObject's tiling capabilities. It's geometry is dynamically updated when playing, recreating the entire object every time a coin is picked up.
EnemyHorz is a class that handles an animated mesh, an enemy that walks left to right and can be jumped on.
Other classes are derived from these and provide some minor altered functionality. The ones listed above are the most important
Every class has different behavior in editor mode and in game mode, allowing easy placement and manipulation in the editor and optimized usage in the game.
The editor is tightly integrated into the codebase. Most object classes have separate code for editor behavior and separate for the game. For example, a StaticObject class is represented as a ManualObject in the editor, where as it is compiled into StaticGeometry in the game.
The editor's user interface is made using -CEGUI (WindowsLook scheme). Here you can easily lay tiles and objects, manipulate object properties, rotate, scale, move them etc.
Once ready, the level is saved into several XML files.
Everything is designed for efficient level editing. most actions have keyboard shortcuts and navigation, selection, movement etc is available in key-hold+click.
An interesting thing that I've made for the editor is PDF-style navigation: click-and drag. it has proven to be very efficient and intuitive. If you need to move the camera, you can zoom in or out (with the mouse wheel) and drag the level in the desired direction.
as explained in the Tiles paragraph, each tile has a collision line that is used in collision detection. But since there are a lot of tiles, collision detection becomes expensive. Therefore, some optimizations have to be made!
The simplest solution is to split the lines in a quad-tree, which, obviously, greatly improved collision performance, but wasn't enough.
So I had to write a program that would connect all collinear lines into single bigger lines. You can see on the screenshot in the Tiles paragraph that there are only a few longer lines. There were ten times more before this optimization.
Updating all objects each frame takes time, so we decided only to update those objects within a certain distance of the main character. Secondly, we figured that not all objects need to be updated each frame, so update some objects once in every 2,3 or 4 frames (depending on object type).
Additionally, those objects that are not updated every frame are evenly divided across the frames, so one object is updated on eg. odd frames and another on even frames.
Object orientation is a nice thing, but when it comes to performance, some compromises have to be made. In the editor, each object is constructed as a manual object (so it can easily be edited), while in the game, StaticObjects(Still images) and similar objects are compiled into StaticGeometry, while others are represented as entities.
Each time Tibor picks up a coin, a score texture rises up and fades out. This was first done by creating another StaticObject with the appropriate texture (which meant adding another SceneNode and Entity to the Scene Graph). Since there are a lot of such instances, we had to think of something.
And the winner was ParticleSystem!
A particle is manually added into the system whenever a coin is picked up. This way, all the score images are bundled together into one Draw call and thus rendered faster.
-CEGUI is great, but our in-game ui took 15 batches! Which took a toll on our FPS. To solve the problem, we construct a 2D ManualObject and update it whenever some ui information changes (like score changed).
I made a Coin class that is capable of grouping M x N coins into one batch, as rectangular groups.
However, since there was a need to scatter coins widely throughout the level, this 'optimization' did not help much.
So, I introduced a larger grouping method that splits all coins in a level into a simple quad tree, grouping coins into larger objects.
Coin textures were grouped into two larger textures so they could be better batched.
This optimization reduced the batch count significantly.
We decided to port the game to the iPhone which means rewriting the whole game in C++ using OpenGL.
We decided to write some ogre classes we use in the PC version of the game. These classes only support basic functionality and are there to maximize the portability of the codebase.
We used py2exe to compile our python code into a Windows executable.
I made a script that calls py2exe, copies all the data files into the distribution folder, removes all debug files, and does a few tweaks.
Thus, compiling a stand-alone build is as simple as running a python script.
The final product is packaged with the installer software NSIS and bundled with Visual Studio 2008 redistributable as a side-by-side assembly.