Implementing a scene director

Started by
15 comments, last by agentultra 11 years, 1 month ago

I figured it out (I was using a forward declaration and forgot to include the header in my implementation of the derived class).

Only now I have to figure out how to push new scenes from a scene...


void BlankScene::onKeyDown(SDLKey key, SDLMod mod, Uint16 unicode)
{
    switch (key) {
        case SDLK_SPACE:
            std::cout << "You pressed space in the blank scene!" << std::endl;
            app->pushScene(ScenePtr(new YellowScene()));
            break;
            
        default:
            break;
    }
}

This will throw EXC_BAD_ACCESS.

I'm going to assume it has something to do with my implementation of pushScene and unique_ptr?


void App::pushScene(ScenePtr scene)
{   
    scene_stack.push(std::move(current_scene)); // current_scene becomes NULL
    next_scene = std::move(scene);
    next_scene->app = this;
}
Advertisement

Ok, solved my own problem. Funny that.

Okay, getting close to something useable. I'll post the rough framework once it's done so others may benefit.

Ok, time to post my finished bare-bones scene management system in all its glory. I'm not sure if any of this is "right," or "good," so please poke holes where you find them.

You'll see references to the IEvent class -- it's my adaptation of the CEvent class from sdltutorials.com; a fairly straightforward design I think is pretty useful.

If you're just here for the code, feel free to skip over the next section to "Getting Down to Business."

My Story So Far...

My story is pretty simple. I'm making a game. I've been a programmer for almost 10 years now and I've pretty much spent my time working with dynamic languages building websites and services. It got a little old and I don't find it very challenging or fulfilling anymore. I'm also getting a little old and trying to rediscover what it is about programming I like so much so I thought I'd start by re-examining the reason I decided to learn how to program in the first place: to make computer games.

I made my first computer games in BASIC on my Amiga. It was fun and I had no idea what I was doing. I was just typing in games from magazines I had found at the library and fiddling around with them. I was like 10 at the time, so sue me. I would change some values and re-run it. Over and over. That was fun and all but I lost interest by the time I started noticing girls.

I eventually got back into programming and the rest is history... but when I decided to try making games I wanted to start with the fundamentals and really get my hands dirty. That meant learning C++. I already "knew" C++ in so far as I knew C and had experience with OOP in other languages *cough*Python*cough*Perl*cough*. But that's not really knowing C++. And I wanted to learn it because it's still very portable and seems to still be the lingua-franca of the games programming world.

Fair enough... but my next cop-out was to search for a game engine I could use so I didn't have to spend all that time implementing things like entities, texture streaming, buffers, and rendering. And what I found was pretty good actually and if I went with one of them I might have more of a game today than I do now.

But it's still not enough. I really want to know this stuff and the best way to know how something works is to pick it apart and build it from scratch. So I've been rolling my sleeves up and throwing code at my compiler and trying to figure this all out.

I've read enough tutorials on SDL over the years of "trying to make a game," (I'm serious now of course) that I knew how to get a basic window up and handle some events. I knew what a game loop was, frame rates, and all that. But now I had to do it in C++ and that presented some interesting challenges. I'm doing my best to over come those hurdles one step at a time and I've learned quite a lot about the language so far. Here's a small, humble attempt at sharing what I have learned (and give thanks to those who've helped me so far).

Getting Down to Business

So after you get a basic game loop going and you can see a nice, black screen on your desktop the usual question is, "what next?" A good place is to get a sprite on the screen and start getting it to move around. Add a score and a game over and you've got your "Hello, world!" of graphical game programming.

If you're a little ambitious as I am then you will soon ask the same question again. Here is your answer: structure. If you've got a little arcade game, say a paddle hitting a ball around, you may have noticed that to add a little polish you need to have a menu screen, the main game screen, a pause menu, and maybe a game-over screen. Your first instinct and experiment might be to try and implement it in the main game loop... calling different rendering functions or something. Instead you should think about how you can structure your game to be aware of these "scene" objects. Here's how I came to think about it and implement it in my own game engine that I've begun to write.

First the Scene definition:


#ifndef __My__Scene__
#define __My__Scene__

#include <iostream>

#include <SDL/SDL.h>

#include "Event.h"

// forward declarations
class App;


class Scene : public IEvent {
    
public:
    Scene();
    virtual ~Scene();
    
    App* app;
    
    virtual void onInit();
    virtual void onUpdate() = 0;
    virtual void onRender(SDL_Surface* display, float interp) = 0;
    virtual void onCleanup() = 0;
};

#endif /* defined(__My__Scene__) */

If you take a look at the tutorial I mentioned, you'll see a common pattern emerging here. Scene objects look a lot like my App class which I'll show you later on. This is on purpose. The reason for this is that I like that structure. Each Scene object is like a little SDL app. It has a bunch of game objects in it, it receives events, updates the game objects based on those events, renders things to the display... you get the idea. So I just reuse the common parts of that structure and turn it into a Scene object.

What I leave behind in the App class are things like the main game loop. The App as you will see still handles the game loop. It just passes on the events it receives from SDL to the current scene. And that's the final trick of the puzzle that I struggled through... we need a way to change between scenes. I'll show you how later.

Here's the implementation for the Scene base class. It's pretty sparse because it's meant to be sub-classed. The sub-classed objects will be the actual scenes in my game. This base class just makes sure they all have the interface my App needs:


#include "Scene.h"


Scene::Scene()
{
    app = NULL;
}

Scene::~Scene()
{
}

void Scene::onInit()
{
}

An example class extending Scene might be:


#ifndef __My__BlankScene__
#define __My__BlankScene__

#include <iostream>

#include <SDL/SDL.h>

#include "Scene.h"
#include "YellowScene.h"


class BlankScene : public Scene {
    int r;
    int g;
    int b;
    
public:
    virtual void onInit();
    void onKeyDown(SDLKey key, SDLMod mod, Uint16 unicode);
    virtual void onUpdate();
    virtual void onRender(SDL_Surface* display, float interp);
    virtual void onCleanup();
};

#endif /* defined(__My__BlankScene__) */

And


#ifndef __My__YellowScene__
#define __My__YellowScene__

#include <iostream>

#include <SDL/SDL.h>

#include "Scene.h"


class YellowScene : public Scene {
    
public:
    virtual void onInit();
    void onKeyDown(SDLKey key, SDLMod mod, Uint16 unicode);
    virtual void onUpdate();
    virtual void onRender(SDL_Surface* display, float interp);
    virtual void onCleanup();
};

#endif /* defined(__My__YellowScene__) */

So in order to test this all out I just created the most simple Scenes I could imagine. I'm a big fan of Donald Knuth and in his book Concrete Mathematics he taught me to always start with the most simple case and work your way up from there. If you do it right (at least in mathematics) your solution will scale up for all the values your parameters can take (this isn't always true in Computer Science, but we try damnit). BlankScene started life as literally a black screen. I just added a little fanciness to demonstrate some things in this little tutorial.

Their implementations:


#include "App.h"
#include "BlankScene.h"


void BlankScene::onInit()
{
    std::cout << "In blank scene!" << std::endl;
    r = 0;
    g = 0;
    b = 0;
}

void BlankScene::onKeyDown(SDLKey key, SDLMod mod, Uint16 unicode)
{
    switch (key) {
        case SDLK_SPACE:
            app->pushScene(ScenePtr(new YellowScene()));
            break;
            
        default:
            break;
    }
}

void BlankScene::onUpdate()
{
    int dr = r + 9;
    int db = b + 9;
    if ((dr < 255) && (db < 255)) {
        r += 9;
        b += 9;
    }
}

void BlankScene::onRender(SDL_Surface* display, float interp)
{
    SDL_FillRect(display, NULL, SDL_MapRGB(display->format, r, g, b));
}

void BlankScene::onCleanup()
{
    
}

And


#include "App.h"
#include "YellowScene.h"


void YellowScene::onInit()
{
    std::cout << "In yellow scene!" << std::endl;
}

void YellowScene::onKeyDown(SDLKey key, SDLMod mod, Uint16 unicode)
{
    switch (key) {
        case SDLK_SPACE:
            //std::cout << "You pressed space in the blank scene!" << std::endl;
            app->popScene();
            break;
            
        default:
            break;
    }
}

void YellowScene::onUpdate()
{
    
}

void YellowScene::onRender(SDL_Surface* display, float interp)
{
    SDL_FillRect(display, NULL, SDL_MapRGB(display->format, 255, 255, 0));
}

void YellowScene::onCleanup()
{
    
}

The astute reader will notice that I #include the App.h header in my implementation. You should ask yourself why I would do that. I scratched my head over this for a while when I was trying to figure out how to wire up Scene objects so that they could tell the App to switch to a new Scene. I'm still not sure this is the best way but it's what I've got right now and it works. Did you notice in the Scene.h header I created something called a forward declaration? I did that in order to add a pointer member to the App in the Scene object. And after scratching my head and searching my reference documentation I can tell you that when you do this you need to include the header that defines the implementation that forward-declaration refers to. What I'm doing here by including that header is telling the compiler where to find the definition for that name I'm referring to.

It sounds weird so I recommend you try convincing yourself that it's true. If you can't get back to me. I may have not understood what's going on. This is the gist of it from what I've learned so far... so don't take any of this as gospel. So go now, look up forward declarations of classes and the like. I can wait.

... and when you get back let's start tying this together into the App!

Just try to ignore the fluff like window sizing, scaling, and the like. It's here for brevity and I might have to revisit it when I notice my sprites aren't scaling as I expect them to. What is important are the member variables that have to do with scene state management. I've commented them here for you, dear reader, so you do not get lost in the quagmire that is my code.


#ifndef __My__App__
#define __My__App__

#include <assert.h>
#include <iostream>
#include <memory>
#include <stack>
#include <vector>

#include <SDL/SDL.h>

#include "Event.h"
#include "Scene.h"

const int D_WINDOW_WIDTH = 300;
const int D_WINDOW_HEIGHT = D_WINDOW_WIDTH / 16 * 9;
const int D_SCALE = 3;

const int TICKS_PER_SECOND = 25;
const int SKIP_TICKS = 1000 / TICKS_PER_SECOND;
const int MAX_FRAMESKIP = 5;

typedef std::unique_ptr<Scene> ScenePtr;


class App : public IEvent {
    const int window_w;
    const int window_h;
    const int window_scale;
    const char* window_caption;
    bool running;
    SDL_Surface* display;
    
    // fps / update management
    Uint32 next_game_tick;
    int loop_count;
    float time_interp; // interpolation between frames
    
    
    // scene state management... look here!
    std::stack<ScenePtr, std::vector<ScenePtr>> scene_stack;
    ScenePtr current_scene;
    ScenePtr next_scene;
    void onSceneUpdate();
    
    int run();
    
    App(const App& other);

public:
    App();
    App(int size, int scale, const char* win_cap);
    ~App();
    
    // state management... and look here too!
    int runWithScene(ScenePtr scene);
    void pushScene(ScenePtr scene);
    void popScene();
    void replaceScene(ScenePtr scene);
    
    // IEvent implementation
    bool onInit();
    void onEvent(SDL_Event* event);
    void onKeyDown(SDLKey key, SDLMod mod, Uint16 unicode);
    void onUpdate();
    void onRender(float interp);
    void onExit();
    void onCleanup();
};

#endif /* defined(__DungeonSoft__App__) */

I defined the typedef for ScenePtr here but I might move it to Scene.h later on if that makes sense. So what's going on here? Well a typedef is just an alias to some type definition (there's a difference between definition and declaration. Standard C stuff and makes for fun reading. If you don't know the difference I recommend looking it up). But what's that unique_ptr business?

SMART POINTERS ARE COOL, KIDS.

It's a C++11 feature. I have no regrets. Up until this point you might have read lots of tutorials and articles on C++ that use plain, old pointers. You might've heard of "smart pointers," and wondered what they are. Well, they're better pointers than plain-old pointers because they're smart. Think about what a pointer does. It's an address in memory somewhere, right? And addresses point to things... locations in memory, duh. Pedantic, I know. What you're able to do is have many addresses in your program for the same bit of memory. This lets your program have references to the same object from various different places. And to understand why smart pointers are smarter than plain-old pointers you have to think about what can happen with all those addresses dangling around while your program runs... particularly in C++.

Objects get destroyed. The pointers don't care. They just point to an address. You don't know what's on the other side until you try to resolve that address and fetch what's there. Or what should be there. Your function might expect an integer to be at that address... but instead they find a funny little creature with no discernible geometry staring into their souls. This is where we start heading into territory known as, undefined behaviour; the great boogeyman of C programming.

Smart pointers try their best to keep us fleshy programmers safe from wandering off into the woods. There are a few different kinds of smart pointers and as you read about them you'll find they have different properties and use cases. Here I use unique_ptr which is a wonderful little pointer. It ensures that one, and only ever one, thing will ever point to the object it points to: itself. At least nobody else can reference the thing it points to through it. There are ways around unique_ptr if you really want to go into the woods... but if you lose your mind by doing so you will have no one else to blame.

For this little framework it means that only one pointer will ever point to a Scene object at a time.

Another benefit of smart pointers is they will take some responsibility for destroying the object they point to when it's appropriate.

Neat... so we get a helping hand making sure that there are no "dangling references," to the Scene objects and that someone is going to take responsibility for destroying them for us. HOW AWESOME IS THAT? Sorry. I get excited over little things.

Anyway this was all a little tricky for me at first. I couldn't figure out how to pass a unique_ptr into a function as a parameter at first. You see, the unique_ptr is smart -- it doesn't allow itself to be copied. Objects passed in to a functions argument parameters will have their copy-constructors invoked. Damn.

But there's a way around it which you might've noticed in the implementation of my fancy BlankScene class and will get the chance to see again in my main() function. You simply construct the pointer in the argument invocation... if that makes any sense. So instead of creating an instance of your smart pointer and passing it into the function when you call it, you call the function and invoke the unique_ptr constructor inside the function call... just keep your eyes peeled when we look at main().

So the following is our implementation of the App class. The important parts are the state management functions we defined above: runWithScene, pushScene, and popScene. We also hook up the event handling, updating, and rendering methods to the current_scene. The astute reader will notice that I have not figured out how to handle calling the Scene objects' onCleanUp method yet... which might be important if your scene manages some extra resources like SDL_Surface objects and the like. However this is just a bare-bones-get-it-to-work-and-make-it-better-later kind of thing so just roll with it (and if you have some suggestions, I'm all ears).


#include "App.h"


App::App()
: window_w(D_WINDOW_WIDTH),
  window_h(D_WINDOW_HEIGHT),
  window_scale(D_SCALE),
  window_caption(NULL)
{
    running = false;
    display = NULL;
    window_caption = NULL;
    
    current_scene = NULL;
    next_scene = NULL;
}

App::App(int size, int scale, const char* win_cap)
: window_w(size),
  window_h(size / 16 * 9),
  window_scale(scale)
{
    running = false;
    display = NULL;
    window_caption = win_cap;
    
    current_scene = NULL;
    next_scene = NULL;
}

App::~App()
{
}

bool App::onInit()
{
    if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
        return false;
    }
    
    if ((display = SDL_SetVideoMode(window_w * window_scale,
                                    window_h * window_scale,
                                    32, SDL_DOUBLEBUF)) == NULL) {
        return false;
    }
    
    SDL_WM_SetCaption(window_caption, "icon.ico");
    return true;
}

void App::onEvent(SDL_Event* event)
{
    IEvent::onEvent(event);
    current_scene->onEvent(event);
}

void App::onKeyDown(SDLKey key, SDLMod mod, Uint16 unicode)
{
    switch (key) {
        case SDLK_ESCAPE:
            onExit();
            break;
            
        default:
            break;
    }
}

void App::onUpdate()
{
    current_scene->onUpdate();
}

void App::onRender(float interp)
{
    current_scene->onRender(display, interp);
    SDL_Flip(display);
}

void App::onExit()
{
    running = false;
}

void App::onCleanup()
{
    SDL_FreeSurface(display);
    SDL_Quit();
}

int App::runWithScene(ScenePtr scene)
{
    assert(scene_stack.empty() == true);
    assert(current_scene == NULL);
    
    current_scene = std::move(scene);
    current_scene->app = this;
    current_scene->onInit();

    return run();
}

void App::pushScene(ScenePtr scene)
{
    assert(current_scene != NULL);
    next_scene = std::move(scene);
    next_scene->app = this;
}

void App::popScene()
{
    if (!scene_stack.empty()) {
        next_scene = std::move(scene_stack.top());
        scene_stack.pop();
    }
}

void App::onSceneUpdate()
{
    if (next_scene != NULL) {
        scene_stack.push(std::move(current_scene));
        current_scene = std::move(next_scene);
        current_scene->onInit();
        next_scene = NULL;
    }
}

int App::run()
{
    if (onInit() == false) {
        return -1;
    } else {
        running = true;
    }
    
    SDL_Event event;
    while(running) {
        // SDL_FillRect(display, NULL, SDL_MapRGB(display->format, 0, 0, 0));
        while (SDL_PollEvent(&event)) {
            onEvent(&event);
        }
        loop_count = 0;
        while ((SDL_GetTicks() > next_game_tick) && (loop_count < MAX_FRAMESKIP)) {
            onUpdate();
            next_game_tick += SKIP_TICKS;
            loop_count++;
        }
        time_interp = float(SDL_GetTicks() + SKIP_TICKS - next_game_tick) / float(SKIP_TICKS);
        onRender(time_interp);
        onSceneUpdate();
    }
    onCleanup();
    
    return 0;
}

And finally the way I start an App is:


#include <SDL/SDL.h>
#include "SDLMain.h"

#include "App.h"
#include "Map.h"

#include "BlankScene.h"


int main(int argc, char *argv[])
{
    App app(300, 3, "My SDL Game, WOO");
    return app.runWithScene(ScenePtr(new BlankScene()));
}

And there you go. You should see a black screen that eventually fades to an atrocious pink. If you press the Space key it will push the BlankScene onto the stack and start running YellowScene as the current scene. If you press Space again it will pop the BlankScene off the stack and start running it again. YellowScene will just go out of scope and be destroyed. Back and forth, back and forth. It's really quite amusing.

Eventually I need to add a replaceScene method. And the point of all this is: when you're running your GameScene and the user pressed the "P" key to pause the game, the GameScene can just push the PauseScene onto the stack. The game loop then just runs the PauseScene as the current scene. When the user pressed the "Continue" button in your clever pause scene we just pop the scene on the top of the stack off and make it the current scene (discarding the old one... and ultimately resuming from where we left off... which doesn't work yet, but one thing at a time). We need replaceScene because the stack is not meant to hold all of your scenes... it's just useful for things like menus or sub-screens or something. replaceScene will just replace the current running scene with a new one, no stack involved. You'll want to avoid pushing too many scenes onto the stack because they'll all take up memory.

Another thing we might need in order to round this off is a way for a scene to return some value for the next scene to use... for example, which menu item was selected in a sub-menu or something. I'll get to that later.

And that's what I like most about writing my game framework from scratch. I've built this from the ground up and have learned quite a lot by doing it. It's not perfect but I know where the cracks are and I'm confident that I can figure out how to fix them eventually. When I get a simple feature like this working I just feel an immense satisfaction. And making computer games is a pretty complicated endeavour... I think it's important to win small victories from time to time.

So there you go... I hope this little tale has helped you out a bit. In the future I hope to expand my engine to handle an entity-component system for handling game objects and maybe switch to an OpenGL backend based on GLFW, GLEW, GLSL, and OpenGL 3.x... but one step at a time.

I'd like to thank the gamedev.net community here again for their helpful responses to my newbish posts! I'll have a few more for you on the way as I dig into entities, components, animation, and some ideas I have for defining game entities via configuration files.

Hah, there's a bug! See if you can spot it. :)

[spoiler]Hint: what's really happening when we hit onUpdateScene? (Ignore the unfinished game timer stuff... not important. ;)[/spoiler]

Well success, I've cleaned out the bugs and added the cleanup calls in the right places. It runs under Valgrind and Clang's static analyzer with no warnings or errors. I leave it up to the reader if they're interested in using this code to figure out where those bugs are and how to fix them. It's pretty simple really.

The next step (probably after I get sprites and animations happening) is to abstract out the scene definitions into a sqlite file or something so that I can define new scenes on the fly without touching the engine code.

But one step at a time. Small victories.

I skipped trough your post, mostly interested by how you write, it's very Andre Lamothe and will be great for beginners to game dev and c++ - once you get your code rock solid and you're more confident that what you've written isn't necessarily the 'right' way to do things, but perfectly acceptable, you should consider writing a full blog or something - you have a talent for writing.

Whilst skipping very quickly through the code, I noticed that your scenes implement IEvent. Conceptually that doesn't really make sense. I would assume Scenes can accept IEvents, but they aren't an event themselves. I would consider renaming this particular IEvent interface to IEventHandler or something similar. It's the IEventHandler that would accept IEvent implemented objects and would likely impose a HandleEvent(IEvent event) type method.

It's a cosmetic thing but might be a bit confusing for beginners if they are just picking up OO concepts or C++

Keep up the good work.

Well thank you very much. smile.png


Also of note, I've learned quite a bit more about smart pointers since I wrote this: http://agentultra.com/sdl-and-modern-c-plus-plus-images.html


One thing to be careful about when passing unique_ptr objects into function calls as I do in the examples above is: argument evaluation order. Some languages have a defined order in which to evaluate function arguments. C++ does not. Here it's probably not an issue but imagine a function that takes a unique_ptr and a couple of other values:


void myFunc(std::unique_ptr<MyType> x, int y)
{
...
}

// and then later on we call it like so...

myFunc(std::unique_ptr<MyType>(new MyType()), computeY());
 


What do you think will happen here if computeY throws an exception?


The answer: who knows? However depending on the order that the run-time chooses to evaluate the arguments in we could leave a dangling reference on the heap to that shiny, new MyType. Scott Meyers recommends (and I whole-heartedly agree) that you should declare your smart pointers in their own expression to avoid such awkward situations.


std::unique_ptr<MyType> my_ptr(new MyType());
myFunc(std::move(my_ptr), computeY());


Will work. Just look up Item 17 in Effective C++. You do already have three copies of that book, right? One for beside your bed, one for your desk, and one in the bathroom? If you want to program your games in C++ then you should have at least one copy. It's indispensable.


Anyway, my code is getting a little better. My 'engine' is coming along (if you can call it that). I'll post more tutorials as I come up with them.

This topic is closed to new replies.

Advertisement