Tuesday, August 9, 2011

Game Components

Well I'm back.  Been working on different projects and stuff.  So I haven't had time to post any thing in a while.  What this post is going to be about taking a really big nasty class and transform it into a set of smaller more manageable objects.  Here is the the simple example class we are going to cross cut.

class Test {
    public:
 
    Test() {
        r.init(100, 100, 32, 32);
    }
    void update() {
        Uint8* cur = SDL_GetKeyState(0);
     
        if(cur[SDLK_LEFT]) {
            r.x -= 1;
        } else if(cur[SDLK_RIGHT]) {
            r.x += 1;
        }
     
        if(cur[SDLK_UP]) {
            r.y -= 1;
        } else if(cur[SDLK_DOWN]) {
            r.y += 1;
        }
     
     
        glBegin(GL_QUADS);
            glVertex2f(r.x, r.y);
            glVertex2f(r.x+r.w, r.y);
            glVertex2f(r.x+r.w, r.y+r.h);
            glVertex2f(r.x, r.y+r.h);
        glEnd();
    }
 
    private:
 
    Rect r;
};


There are many problems with this class first off you have everything from input to graphic rendering in the same function.  So if you a value of the rect object you could potentially screw up how you render it.  And not just that its really ugly to look at too.   I've written this type of code many times in the past ( and on recent occasions too :( ).  So how do we fix this.

Enter Components.  Component Programming allows us to take a bigger class and make smaller class from it.  This means we can decouple features from a bigger object and reuse them.  Looking at this c++ class example I can already figure out what 2 components.  InputComponent and RenderComponent.   Here is the implementation

class InputComp {
    public:
 
    void update(Rect& r) {
        Uint8* cur = SDL_GetKeyState(0);
     
        if(cur[SDLK_LEFT]) {
            r.x -= 1;
        } else if(cur[SDLK_RIGHT]) {
            r.x += 1;
        }
     
        if(cur[SDLK_UP]) {
            r.y -= 1;
        } else if(cur[SDLK_DOWN]) {
            r.y += 1;
        }
     
        cur = 0;
    }
};

class RenderComp {
    public:
 
    void update(Rect& r) {
        glBegin(GL_QUADS);
            glVertex2f(r.x, r.y);
            glVertex2f(r.x+r.w, r.y);
            glVertex2f(r.x+r.w, r.y+r.h);
            glVertex2f(r.x, r.y+r.h);
        glEnd();
    } 
};

class Test {
    public:
  
    Test() {
        r.init(100, 100, 32, 32);
    }
  
    void update() {
        in.update(r);
        rend.update(r);
    }
  
    private:
  
    Rect r;
  
    InputComp in;
    RenderComp rend;
};

As you see I'm taking what was in the Test class and making smaller more manageable classes with them.  This will make the Test class easier to read and to implement.  Not just that I can reuse the functionality in other class such as an enemy class can reuse the RenderComp class in order to render it.
  Now lets say if you want to make the RenderComp more efficient (trust me using intermediate mode isn't a good Idea).  So lets use vertex arrays instead
 
class RenderComp {
    public:
  
    void update(Rect& r) {
        PointBuf vbuf;
      
        vbuf.push_back(Point(r.x, r.y));
        vbuf.push_back(Point(r.x+r.w, r.y));
        vbuf.push_back(Point(r.x+r.w, r.y+r.h));
        vbuf.push_back(Point(r.x, r.y+r.h));
      
        glVertexPointer(2, GL_FLOAT, 0, &vbuf[0]);
        glEnableClientState(GL_VERTEX_ARRAY);
        glDrawArrays(GL_QUADS, 0, vbuf.size());
        glDisableClientState(GL_VERTEX_ARRAY);
    }  
};

This will make rendering a lot more effectent because you are sending more data to the video card all at once now lets see if we did any changes to the Test class

class Test {
    public:
  
    Test() {
        r.init(100, 100, 32, 32);
    }
  
    void update() {
        in.update(r);
        rend.update(r);
    }
  
    private:
  
    Rect r;
  
    InputComp in;
    RenderComp rend;
};

Noda.  The classes that use this pattern won't need to change at all most of the time because I've change the internals the only change you'll notice is that its moving a lot faster.
  Hopefully this helps any one who wants to use component architecture in there game engine.  Its really ease to use once you get the whole concept of components.  Like all designs there are problems with, components make it where you have tons of smaller objects which can give you some overhead.  Allocation does become an issue so use components to because you are creating a whole bunch of objects.  So use it where you need and don't over design stuff to that will make it to complex.  But like wise do over simplify stuff either well later.


Here are the exmaple links
component.zip

(note: comment me if the file isn't available I'll do a re-post to re-upload the examples)