This tutorial is part of the Marmalade SDK tutorials collection. To see the tutorials index click here
Well, another busy day and as usual I have too much to do and there are only 24 hours in a day (I could do with coding and blogging on a ship travelling faster then the speed of light, so I can finish before I even started!). As usual if you just want the code then grab it from here. if you want the details then please read on
I’m getting quite excited about the tutorial series as we are finally building up to something much more useful than a simple bunch of loosely connected tutorials. That something is a “2D game engine” that will allow you to create cool fast games using the Marmalade SDK that you can deploy to a bucket load of platforms simultaneously and hopefully earn an even bigger bucket load of cash from!
Today we are going to begin our game engine by implementing one of the most basic components of a 2D game engine, The Sprite.
The Sprite
From our game engines point of view, we are going to define a sprite as a visual component that can move and animate on the users screen. I wont go as far as defining a sprite as a bitmap image because our sprites are special, they can be anything we define them to be. They can be anything from a simple point, line or bitmap to something as extravagant as a vector based image. We basically want to leave our options open and offer as much extensibility to our game engine as possible.
One very important point to make at this point. A sprite is only a visual component and should not be made to deal with game logic, collision detection, playing audio etc. It should only be concerned with drawing itself. Some people like this separation of concerns style programming but others (like me), whilst others do not.
Ok, so what do we want our basic sprite to do? Lets make a list:
- Move around the screen
- Scale
- Rotate
- Change colour / transparency (allows flashing, fades etc..)
- Draw itself
- Managed by some controller class (so we don’t have to deal with allocation / deletion)
- Ability to be pooled to reduce memory fragmentation
In rides CIwGameSprite our new sprite class
CIwGameSprite – The mother of all sprite classes
Erm, put down that Marmalade SDK documentation, don’t let the name fool you, you wont find this class in there. I have named the class as such so that it feels more like its part of the Marmalade SDK.
CIwGameSprite is the name of our basic sprite class that we are going to base our 2D game engine around. CIwGameSprite is not actually a usable class in the sense that you can create one and do something with it. CIwGameSprite acts as a base class for other types of sprite classes and defines some basic sprite information that is going to be common to all types of sprites. Lets take a quick look at CIwGameSprite (defined in the IwGameSprite.h header file) to see what it does.
class CIwGameSprite { /// Properties protected: CIwGameSpriteManager* Parent; // Parent sprite manager CIwSVec2 Position; // Position of the sprite iwangle Angle; // Rotation of sprite (IW_ANGLE_2PI = 360 degrees) iwfixed Scale; // Scale of sprite (IW_GEOM_ONE = 1.0) CIwColour Colour; // Colour of sprite bool Visible; // Sprites visible state bool Pooled; // Tells system if we belong to a sprite pool or not bool InUse; // Used in a memory pooling system to mark this sprite as in use public: void setParent(CIwGameSpriteManager* parent) { Parent = parent; } void setPosAngScale(int x, int y, iwangle angle, iwfixed scale) { Position.x = x; Position.y = y; Angle = angle; Scale = scale; TransformDirty = true; } void setPosition(int x, int y) { Position.x = x; Position.y = y; TransformDirty = true; } CIwSVec2 getPosition() const { return Position; } void setAngle(iwangle angle) { Angle = angle; TransformDirty = true; } iwangle getAngle() const { return Angle; } void setScale(iwfixed scale) { Scale = scale; TransformDirty = true; } iwfixed getScale() const { return Scale; } void setColour(CIwColour colour) { Colour = colour; } CIwColour getColour() const { return Colour; } void setVisible(bool show) { Visible = show; } bool isVisible() const { return Visible; } void forceTransformDirty() { TransformDirty = true; } void setPooled(bool pooled) { Pooled = pooled; } bool isPooled() const { return Pooled; } void setInUse(bool in_use) { InUse = in_use; } bool isUsed() const { return InUse; } /// Properties End protected: CIwMat2D Transform; // Transform bool TransformDirty; // Dirty when transform change void RebuildTransform(); // Rebuilds the display transform public: CIwGameSprite() : Pooled(false) { Init(); } virtual ~CIwGameSprite() {} virtual void Init(); // Called to initialise the sprite, used after construction or to reset the sprite in a pooled sprite system virtual void Draw() = 0; // Pure virtual, need to implement in derived classes };
Ok, we see that we have a bunch of properties for our sprite:
- Parent – Our sprites are managed by a sprite manager, the parent of our sprites will be the sprite manager that is looking after them (more on the sprite manager later)
- Position – The 2D position of our sprite in 2D space
- Angle – The orientation of our sprite in 2D space (Marmalade SDK angles range from 0 to IW_ANGLE_2PI)
- Scale – The scale of our sprite in 2D space (Marmalade SDK used IW_GEOM_ONE as the value of 1.0f)
- Colour – The colour and transparency of our sprite
- Visible – The visible state of our sprite (can the user see it?)
- Pooled – Should our sprites data be deleted when it is removed from the sprite manager (more on sprite pooling later)
- InUse – Used to mark a sprite as being used in a pooled sprite system
We also have a few private member variables in there that deal with the transform for our sprite. We covered the Iw2D transform in our Iw2D sprite example code in a previous tutorial. Note that building transforms can be expensive in terms of time when we are rebuilding lots of them every frame, so in our sprite class we only going to rebuild the sprites transform when the sprites position, scale or angle changes (no sense doing all that work if nothings changed).
Note that our Draw() method is a pure virtual method which makes the class abstract. This means that you cannot and are not supposed to create instances of this class. Instead it serves as an interface to defining classed based upon that class. Now thats said lets take a look at a class that we have derived from CIwGameSprite called CIwGameBitmapSprite.
CIwGameBitmapSprite – In Comes the Meat
Here is the class definition for CIwGameBitmapSprite:
class CIwGameBitmapSprite : public CIwGameSprite { // Properties protected: CIw2DImage* Image; // Bitmapped image that represents this sprite int Width, Height; // Destination width and height int SrcX, SrcY; // Top left position in source texture int SrcWidth, SrcHeight; // Width and height of sprite in source texture public: void setImage(CIw2DImage* image) { Image = image; } void setDestSize(int width, int height) { Width = width; Height = height; } void setSrcRect(int x, int y, int width, int height) { SrcX = x; SrcY = y; SrcWidth = width; SrcHeight = height; } // Properties End public: CIwGameBitmapSprite() : CIwGameSprite(), Image(NULL) {} virtual ~CIwGameBitmapSprite() {} void Draw(); };
As you can see we have derived CIwGameBitmapSprite from CIwGameSprite, basically borrowing all of its functionality and then adding on some more. This class now represents a visual component that is represented by a bitmap, or in this case a CIw2DImage.
You can see that we have added a few additional properties:
- Image – Our Iw2D bitmap image that we have previously loaded somewhere
- Width – The width that the sprite will appear on screen at a scale of 1.0
- Height – The height that the sprite will appear on screen at a scale of 1.0
- SrcX, SrcY, SrcWidth and SrcHeight – These 4 variables are all inter-related. They mark a rectangular area within our source Iw2D image, which allows us to render just a portion of a large image instead of the whole thing, allowing us to use sprite sheets. If you haven’t heard of sprite sheets (or sprite atlases) then you can think of them as a collection of images arranged onto one large image. For example, you may have arranged a whole bunch of animation frames of one of your characters onto one large bitmap. This system will allow us to pick out the smaller images and render them without worrying about the rest.
Ok, we now have a fully functional bitmapped sprite class that will allow us to draw bitmapped sprites to the screen. We can also spin, scale, move, hide and even change the colour or transparency of these sprites
CIwGameSpriteManager – Managing our Little Sprite Children
I like to compare instantiated classes to children. if you allow them, they will misbehave and be difficult to manage. To help prevent your unruly sprites from misbehaving and crashing your awesome game, you need something to manage them, a common place to examine them, draw them and delete them when no longer needed (ok the comparison with children stops at that one!)
So to manage our sprites we create a sprite manager (CIwGameSpriteManager). Lets take a quick look at the CIwGameSpriteManager class:
class CIwGameSpriteManager { public: // Provide public access to iteration of the sprite list typedef CIwList::iterator Iterator; Iterator begin() { return Sprites.begin(); } Iterator end() { return Sprites.end(); } // Properties protected: CIwMat2D Transform; // Transform CIwList Sprites; // Our list of sprites public: void addSprite(CIwGameSprite* sprite); void removeSprite(CIwGameSprite* sprite, bool delete_sprites = true); void setTransform(const CIwMat2D& transform) { Transform = transform; DirtyChildTransforms(); } const CIwMat2D& getTransform() const { return Transform; } // Properties End protected: void DirtyChildTransforms(); public: CIwGameSpriteManager() { // Set up default rotation, scaling and translation Transform.SetIdentity(); Transform.m[0][0] = IW_GEOM_ONE; Transform.m[1][1] = IW_GEOM_ONE; } ~CIwGameSpriteManager() { Release(); } void Draw(); void Release(bool delete_sprites = true); };
Ok, if you aren’t fond on collections / iterators and the likes then you probably won’t like this class very much. I will admit, i am quite obsessed with them. I find them “too” useful.
I chose to use Marmalades CIwList class, which is basically a templated linked list class that lets you define a list of objects / data that is somehow related. In our case we have a list of sprites (A linked list is basically a list of objects where each object points to another and then that object points to another and so on).
Ok, so this class provides some basic functionality:
- We can add sprites to our manager using addSprite() and not worry about having to delete them when we are done with them. Simply deleting the sprite manager will delete all of the sprites for us
- We can remove individual sprites from our manager using removeSprite()
- We can draw all of our sprites in one go
- We can set a base transform that all of our sprites that are managed by this sprite manager are transformed by. This allows us to rotate, move and scale all of the sprites in one go by the same amount. This can be great for applying effects to all of your sprites and / or simply ensuring that all sprites are scaled and translated to fit on any sized screen.
What’s changed in the example code
If you build and run the Sprite project you will see 100 sprites spinning around their centres with the whole scene of sprites spinning and scaling around the centre of the screen:
We have re-used the code from our previous Audio tutorial but ripped a few bits out of the main loop to clarify what’s going on.
Our first change involves creating a sprite manager and then adding some sprites:
// Create a sprite manager and a bunch of sprites CIwGameSpriteManager* sprite_manager = new CIwGameSpriteManager(); for (int t = 0; t < 100; t++) { // Create sprite CIwGameBitmapSprite* sprite = new CIwGameBitmapSprite(); // Set sprite position, angle and scale sprite->setPosAngScale(IwRandMinMax(-max_size, max_size), IwRandMinMax(-max_size, max_size), 0, IW_GEOM_ONE); // Give sprite a random colour CIwColour colour; colour.r = 55 + IwRandMinMax(0, 200); colour.g = 55 + IwRandMinMax(0, 200); colour.b = 55 + IwRandMinMax(0, 200); colour.a = 255; sprite->setColour(colour); // Set sprite image if ((t & 1) == 0) sprite->setImage(image1); else sprite->setImage(image2); // Add sprite to sprite manager sprite_manager->addSprite(sprite); }
Note that the code is a lot simpler than it looks. We are basically create 100 sprites giving each one a random position, random colour and alternative between two different bitmaps.
I want to point out here that using two separate images is bad practice as the underlying Iw2D render will need to switch states continually to switch between both images. To improve performance we would combine both images into a sprite sheet and then change the rectangular area within the image for each sprite.
Next we define a world transform matrix and world angle that we can modify on a per frame basis:
// Dynamic variables CIwMat2D WorldTransform; iwangle WorldAngle = 0;
Next we spin our sprites by walking the sprites list adjusting the angle of each sprite at a different rate (it would be boring if all of our sprites rotated at the same rate)
// Update all sprite rotations int speed = 0; for (CIwGameSpriteManager::Iterator it = sprite_manager->begin(); it != sprite_manager->end(); ++it, speed++) { (*it)->setAngle((*it)->getAngle() + speed); }
Now we do a bit of matrix math jiggery pokery to spin and scale our sprite manager:
// Spin and scale the sprite manager WorldTransform.SetIdentity(); WorldTransform.SetRot(WorldAngle); WorldTransform.ScaleRot((IW_GEOM_ONE / 2) + (IW_GEOM_COS(WorldAngle) / 3)); WorldTransform.SetTrans(CIwSVec2(surface_width / 2, surface_height / 2)); sprite_manager->setTransform(WorldTransform); WorldAngle += 20;
We firstly reset our world transform using SetIdentity(), think of this as setting a variable to its default value. We then set the rotation, scale and translation that all sprites within the sprite manager will transform by. Note that we set the translation position to the middle of the screen because when we created our sprites earlier we defined their positions based around the origin being at 0,0 (top-left hand corner of screen). The world translation will move them back to the middle of the screen.
We now send the world transform to the sprite manager and adjust our world angle, so everything spins.
lastly, we tell our sprite manager to go away and draw its child sprites:
// Draw our sprite manager sprite_manager->Draw();
And finally before we exit the game, we delete the sprite manager which in turn deletes all of our sprites.
// Clean up sprite manager delete sprite_manager;
I Love Concatenating Matrix Transforms
If you are wondering how we manage to spin a sprite individually as well as transform it by the worlds scale and rotation then open up IwGameSprite.cpp and take a look at CIwGameSprite::RebuildTransform()
void CIwGameSprite::RebuildTransform() { // Build the transform // Set the rotation transform Transform.SetRot(Angle); // Scale the transform Transform.ScaleRot(Scale); // Translate the transform Transform.SetTrans(Position); Transform.PostMult(Parent->getTransform()); TransformDirty = false; }
We use a matrix math track called matrix concatenation to multiple two matrices together, effectively combing both transforms into the one single transform. This following line multiplies out two matrices together:
Transform.PostMult(Parent->getTransform());
Object Pooling and Sprites
As your projects get larger and more complex you will find that you are constantly creating and deleting many objects. The constant process of allocating and deleting objects can take its toll on the memory management system, which causes something called fragmentation. Fragmentation is when your available memory pool consists of many small chunks instead of a few large chunks. Depending on the memory management system in use, this can increase the overhead of allocating future objects and risk the chance of running out of memory even though the system is reporting plenty of free memory (this happens because there isn’t a large enough chunk of contiguous memory available to allocate).
To help alleviate this problem we can pre-allocate a large number of objects in one go instead of lots of little ones at random times during the game. This is usually called an object pool.
The problem with C++ is that it doesn’t work very well with object pools because:
- The objects in the pool usually all need to be of the same type
- Constructors are usually called to construct and set up your object
- Destructor’s are usually called to destroy and clean-up your object
We can get around the last two problems quite easily by emptying out our constructors and destructors and putting the code into Init() and Release() methods instead. This will allow us to setup and tear down objects without every having to recreate or delete them. Also, for certain types of objects (those that need to be reset to a default state) I like to add a Reset() method which allows me to set the object back to its original state.
The first problem is a lot more difficult to deal with, but the easiest solution is to simply create object pools for each type of object that you want to pool.
Pooling objects is as easy as allocating a bunch of them during game boot and then using some kind of marking system to mark them as in use or not in use. Instead of creating a new object you would simply search the object pool for a object that is not in use then reset it and use it. The only thing to do is to delete the object pool before you exit your game.
Well that’s it for this tutorial. Our next tutorial will handle adding frame based animations to our bitmapped sprite system which will allow us to truly walk the path of making a cool game.
You can download the code that accompanies this article from here.
Hope you all find this blog useful and until next time, don’t forget, dont read my blog at the wheel!
Mixing your framework code with your game engine code seems to me like a huge loss of code clarity. Otherwise I enjoyed the tutorial even though I would have done it differently.
I agree, although its a bit patched at the moment so I can introduce the features based on the previous tutorials. By the time I have finished actors / scenes the code base will be a lot cleaner.
I have to say that it is a huge help to have this kind of tutorials. The marmalade documentation is vast but it is also extremely unfriendly and uninformative, the demo games are also over complicated and your tutorials help bridge the gap between the power of the SDK and the ability to get started.
So a toast for your tutorials that help ease the learning curve. Kudos and I will continue to visit this page.
Thanks, its been fun creating the tutorial so far, just wish I could take a few weeks away from work to finish the series 🙂
Thanx for the great tutorials! Your examples are very useful, especial for beginers.
I am the beginner 🙂
But, this one I can’t complete. I’ve downloaded the source, compile and deploy into Playbook.
And see only gray backround and sound. No graphix.
Then, by the ssh, i’ve got log
SURFACE: s3eSurfaceSetup: does not match native type (requested=RGB565 native=RGB888)
And next
ERROR: S3E_EXT_ERR_NOT_FOUND in s3eExtGetHash – can’t find extension: 0x2bc8e047
Could your help me with this trouble?
Hi Max,
Just tried the example on Playbook with Marmalade 6.1.2 and it works fine.
Mat
Sorry, Mat. It was my mistake.
I’ve selected “ARM GCC Debug” instead “ARM GCC Release” in Marmalade Deployment Tool.
And now it work fine. Thanks again.
Glad you sorted it