SDL Entities

February 22nd, 2008 by Tim Jones Leave a reply »

In this new tutorial, as I had promised before, we are going to take our hand at creating entities. Entities, for all gaming purposes, are anything that can be interacted with in any way, shape, or form. Some examples might be a monster or a treasure chest that you can open. In this sense, practically everything within the game that moves is an Entity. A rock that is part of a map, which never moves, is not an entity. But if you wanted that rock to move for whatever reason, then we’d make it an Entity. This tutorial will be split into 3 different tutorials. The first, this one you are reading, will deal with a basic Entity class structure. The next tutorial will veer off slightly to build a Map class via a tileset. Then, the last tutorial, which is what a lot of people have trouble with, will deal with Entity to Map collision, and Entity to Entity Collision.

Update:
- Fixed class below to have a virtual destructor. (Thanks Andras!)

Lets get started by creating two new files called CEntity.cpp and CEntity.h. Open up the header file and add the following:

#include <vector>

#include "CAnimation.h"
#include "CSurface.h"

class CEntity {
    public:
        static std::vector<CEntity*>    EntityList;

    protected:
        CAnimation      Anim_Control;

        SDL_Surface*    Surf_Entity;

    public:
        float           X;
        float           Y;

        int             Width;
        int             Height;

        int             AnimState;

    public:
        CEntity();

        virtual ~CEntity();

    public:
        virtual bool OnLoad(char* File, int Width, int Height, int MaxFrames);

        virtual void OnLoop();

        virtual void OnRender(SDL_Surface* Surf_Display);

        virtual void OnCleanup();
};
 

Now, open up the cpp file and add the following:

#include "CEntity.h"

std::vector<CEntity*> CEntity::EntityList;

CEntity::CEntity() {
    Surf_Entity = NULL;

    X = Y = 0.0f;

    Width = Height = 0;

    AnimState = 0;
}

CEntity::~CEntity() {
}

bool CEntity::OnLoad(char* File, int Width, int Height, int MaxFrames) {
    if((Surf_Entity = CSurface::OnLoad(File)) == NULL) {
        return false;
    }

    CSurface::Transparent(Surf_Entity, 255, 0, 255);

    this->Width = Width;
    this->Height = Height;

    Anim_Control.MaxFrames = MaxFrames;

    return true;
}

void CEntity::OnLoop() {
    Anim_Control.OnAnimate();
}

void CEntity::OnRender(SDL_Surface* Surf_Display) {
    if(Surf_Entity == NULL || Surf_Display == NULL) return;

    CSurface::OnDraw(Surf_Display, Surf_Entity, X, Y, AnimState * Width, Anim_Control.GetCurrentFrame() * Height, Width, Height);
}

void CEntity::OnCleanup() {
    if(Surf_Entity) {
        SDL_FreeSurface(Surf_Entity);
    }

    Surf_Entity = NULL;
}
 

Okay, now for some basic explanation. What we are doing here is encapsulating the basic 5 components I mentioned within the first lesson (excluding Events, which will be handled in a later lesson). This allows us to handle Entities within the game much more easily, rather than clumping them together with everything else in the game within the main CApp class. This will also be the way we handle other things as well. The first thing you may notice is a static vector called EntityList. This vector will hold all of our entities, easily accessible through CEntity::EntityList, because it’s declared as a static. I should make a special note here: we declare this EntityList within CEntity because it prevents from circular dependencies later on. An example of this is trying to get a Map to communicate with Entities, and Entities to get to communicate with the Map. Such as CMap declaring a CEntity member, and CEntity declaring a CMap member. It would cause problems on the compile level.

So this vector contains all of our Entities within the game. Notice that each member of the vector is a pointer. This is because later on we are going to inherit this CEntity class for Entity specific classes. So, for example, if we were going to make a Megaman game, we would have a CMegaMan class inheriting the CEntity class. And, via polymorphism, we can store that CMegaMan class within the EntityList. This is the very reason why we declared the functions above as virtuals, and certain members as protected.

Next, we have basic information about the Entity, common to all Entities, coordinates, dimensions, and a surface for its image. Next, we have a loading function that basically takes a filename, and loads the image. By default, we have it setting a transparent color. I’d like to step aside here for a moment to let you all know that certain things I do aren’t set in stone. You can, and are encourage, to take this code and modify to your liking. You may want more parameters on your OnLoad function, or you may want less. You may not want a default transparent color, who knows. I encourage you to test different things. Don’t worry, my code will still be here if you mess things up.

Next, we have a basic OnLoop function that handles basic calculations. Right now we are only calculating Animation. Also please note that we have only set the MaxFrames for the Animation, and left the defaults in place. Next, we have the OnRender function. Instead of making it render to the display only, I’ve allowed a parameter to specify where to render this entity. This could be any surface you want. So you could, if you wanted, render one entity onto another.

Lastly, we have an OnCleanup function that restores memory and all that stuff.

Like I mentioned in the beginning, this is a basic Entity class structure, it basically doesn’t do much yet, but don’t fret, it soon will in coming lessons. So lets get it working. Open up CApp.h and add the header file to the top, and declare two Entities:

#include "CEntity.h"

//…

private:
    CEntity         Entity1;
    CEntity         Entity2;
 

Now, lets load these two Entities. Open up CApp_OnInit.cpp and add the following:

if(Entity1.OnLoad("./entity1.bmp", 64, 64, 8) == false) {
    return false;
}

if(Entity2.OnLoad("./entity2.bmp", 64, 64, 8) == false) {
    return false;
}

Entity2.X = 100;

CEntity::EntityList.push_back(&Entity1);
CEntity::EntityList.push_back(&Entity2);
 

Now, depending on the images you use, you need to set the values appropriately on the OnLoad function. I’ve reused the yoshi image from the previous lesson, and if you still need it:

Now, remember how I stated we are basically encapsulating the basic functions of a game within the Entity class? We have to call those functions now in the respective CApp functions. So, open up CApp_OnLoop.cpp and add the following:

for(int i = 0;i < CEntity::EntityList.size();i++) {
    if(!CEntity::EntityList[i]) continue;

    CEntity::EntityList[i]->OnLoop();
}
 

We are basically running through each Entity in our vector, and calling the OnLoop function. Simple enough! (And we’re doing an error checking so we don’t call any NULL pointers). Now, lets do the same things in CApp_OnRender.cpp:

for(int i = 0;i < CEntity::EntityList.size();i++) {
    if(!CEntity::EntityList[i]) continue;

    CEntity::EntityList[i]->OnRender(Surf_Display);
}
 

And the same thing in CApp_OnCleanup.cpp:

for(int i = 0;i < CEntity::EntityList.size();i++) {
    if(!CEntity::EntityList[i]) continue;

    CEntity::EntityList[i]->OnCleanup();
}

CEntity::EntityList.clear();
 

Note that I added the clear function call, which clears out the vector to nothing. Basically a reset.

Great, now try getting this thing to compile. You should see two yoshis running together on the screen. In the next lesson we’re going to looking at making Maps, and creating a basic file format for our Maps.

SDL Entities – Tutorial Files:
Win32: Zip, Rar
Linux: Tar (Thanks Gaten)

Share and Enjoy:
  • Digg
  • del.icio.us
  • DZone
  • MisterWong
  • Reddit
  • StumbleUpon
  • Technorati
  • Slashdot
  • LinkedIn
  • MySpace
  • Yahoo! Buzz

Did you like this tutorial/blog post? Feel free to donate to keep more comin', and have more contests.

Advertisement

73 comments

  1. Tim Jones says:

    J,
    No problem at all. I wish more people would help each other out. :)

  2. marcin says:

    Thank you J your answear helps a lot

  3. J says:

    Hey marcin,
    You’ll notice that CEntity is using an SDL_Surface* for it’s surface. Pointers are great for allowing you to access onE copy of something instead of making lots of copies of the same item.

    What you will need to do is create a function to set the new entities surface based on an SDL_Surface*.

    An easy way to do this would be to overload the OnLoad function so that there are two versions, one which accepts a file name, and another which accepts an SDL_Surface*.

    i.e.

    void OnLoad(char* file, int x, int y, int maxframes);
    void OnLoad(SDL_Surface* surface, int x, int y, int maxframes);

    Remember to keep all the other arguments as you still need to set the height etc.

    You’ll need access to a copy of the loaded surface, you could do this easily by making a ‘master’ entity that sits at entityList[0] but isn’t displayed on screen for ease of access (there would be a more robust way to do it, but this is the quick for testing).

    Also, if you don’t want your program to crash on exit, you’ll need to change the CleanUp function in CEntity so that it’s not trying to perform SDL_FreeSurface on an invalid pointer.

    (Not trying to step on your toes Tim, just trying to save you time so you can provide us with more great SDL tutorials :) )

  4. marcin says:

    Sorry for my spelling – am not native english.
    Thank you for really good tutorial.
    I have question related to entitie class. Let say we want to create 100 entities that will be using the same sprite sheet (SDL_Surface) – how to load this sprite sheet only once and use it for all the entities insted of loading the same file 100 times and waste memory?

  5. J says:

    oops, just realised that this won’t work if there isn’t already something in the entityList – move the push_back command outside of the if(CEntity:entityList.back()) condition if you’re starting out with an empty list

  6. J says:

    Hey David,
    I was intrigued by your question and so tried it out myself and got the following to work (sorry, my variable names/coding style is different from Tim’s so you’ll have to decipher it)

    CEntity* NewYoshi;
    NewYoshi = new CEntity();

    if((NewYoshi->OnLoad(“yoshi.bmp”, 64, 64, 8)))
    {
    if(CEntity::entityList.back())
    {
    NewYoshi->fltX = (CEntity::entityList.back()->fltX) + 100;
    CEntity::entityList.push_back(NewYoshi);
    }
    }

    it’s possible that you were achieving your goal but just placing it over the original yoshi? if you weren’t updating the X value i believe that would be the outcome. just a thought.

    p.s. this only alters the X var, i’ll leave you to tackle changing the Y var when appropriate ;)

  7. Bakkon says:

    Found the solution to (37) Emilio’s post. Had the problem myself and figured if someone else did they could use this.

    Go to your project properties:
    Configuration Properties > C/C++ > Preprocessor

    Remove _DEBUG from your Preprocessor Definitions. Visual Studio seems to have some problems with storing SDL information in vectors with that in there.

    Hope this helps someone. :)

  8. David says:

    Hi There,

    I’m trying to dynamically generate some entities e.g. ‘make more yoshi’s when I left-click’

    I’m trying things such as:

    CEntity * NewYoshi = new CEntity;
    CEntity::EntityList.push_back(NewYoshi);

    with absolutely no luck

    I’m pretty new to C++ (if you can’t guess!) but am experienced with PHP (if that helps)

    Can you please elaborate on how to dynamically generate entities as-needed and put them into the entity list for processing?

    Kind regards,
    David

  9. Tim Jones says:

    Can I see your vector declaration, your entity declarations, and how you are pushing them into the vector?

  10. dustin says:

    I’ve got a problem with the code that loops through the entities to run OnLoop, OnRender(…), etc etc.

    When I loop through, the program runs for a bit and crashes never getting past the point of running the functions on entities for the first index.

    I’ve tried eliminating the loop, pushing one entity into the vector, and accessing it this way and it still fails. What’s strange is that entitylist.size() returns the correct value, however I can’t run any functions without it crashing. Also my instance variables are non-existent.

    This is very weird that it gets to this point because I have the NULL check in my loops… strange.

Leave a Reply