Anatomy
1,752
0
Advertisement
So I'm hard at work on my object composition-driven rapid development framework. I reached a significant milestone in my career as a hobbyist developer today: I actually backed up the solution to another hard drive. It may seem like a normal thing to do for most people, but I've deleted dozens of projects throughout my life; projects that seemed like they could be redone better, projects that were erased through time and reformats...
I'm not entirely satisfied with the project in its current state, but I'm definitely satisfied enough that I probably won't be doing another full rewrite, and I think that's worth celebrating. Hooray, or something.
In an effort to collect my thoughts and perhaps provide something of value, here's a description of the system.
[size="5"]EDBJ: An introduction
First of all, EDBJ stands for "Event-Driven BlackJack" -- I decided to get back into game programming by chance, when I became interested in the mechanics of the casino game BlackJack. I created a quick and dirty console version, but I wanted more -- so I embarked on this journey, to create a rapid development framework specifically tailored to facilitate common game tasks, providing simple interfaces and functionality that can be extended and combined into complex behaviors, all while taking the requirements of a little (polished) BlackJack game into account.
EDBJ is all about composition -- Absolutely everything is a node on a hierarchical scene tree, from the game state manager to something as simple as a sprite. Parent nodes implement game logic and behaviors, while more basic things, such as their ability to receive input and their graphical representation, are attached to them as child nodes.
For instance, a "Spinner" is made up of a TextLabel with two PushButtons (up and down). TextLabel is the on-screen graphical representation of the spinner's current value and refers to a font resource for rendering. PushButton itself is made up of several things: an optional TextLabel (for button text), a Sprite (for the button background), and a ClickRect that informs the button of mouse activity. The ClickRect itself has a MouseListener child that is part of a special list/map in the Scene Manager, specifically designed to propagate mouse input. Likewise, Sprite has a RenderListener child, a component that is referenced in the SceneManager's list of renderables.

[size="5"]1. The Actor class
Most, if not all, objects in the game extend from the base class Actor.
Actor provides for simple tree functionality -- parent item A to item B, detach A from B, recursively delete all children, etc.
It also provides the interface for 3 virtual functions that must be extended by all derived classes:
render() -- called on all renderables every frame
processTick() -- called on all items that implement some form of logic every frame
processMessage() -- used to receive messages from any other actor, either directly or through a global manager
[size="5"]2. The Scene Manager
The Scene Manager is the root of a given scene. It is responsible for a lot of things. Its main loop, which is run every frame, looks like this:
tick() -- makes all actors tick (execute behaviors they implement every frame)
runSequences() -- a bit obsolete now, as I pointed out in my previous blog post, but it executes sequential and times actions from a simple stack
readInput() -- reads and dispatches input from mouse and keyboard to the relevant objects
dispatchMessages() -- dispatches indirect/global messages to their relevant recipients
cleanup() -- destroys any objects marked for deletion
render() -- sorts the render list and draws all renderables to the screen
It basically does anything that objects cannot (or should not) do themselves.
[size="5"]3. State Manager
The state manager is an actor that defines an additional layer of hierarchy -- stackable game states. Each game state is responsible for the objects it contains. For instance, my BlackJack game's GameState_Opening has a Sprite child for a background image of a Casino scene. You stack GameState_TitleScreen on top of it to show the game's logo and buttons such as "New Game" "Options" and "Quit." New Game and Options are linked to StateSwitchers; little utility nodes that pop the game state stack and replace the "tip" state with a new one. So, to go from the Title Screen to the Options Screen, which both share that same Casino scene background, you pop the Title screen (remove the game logo and main screen buttons) and push the GameState_Options state to the StateManager -- the background remains, but the options screen is now shown.
This allows you to use sub-states that run concurrently -- for instance, you would push a state containing objects like the BlackJack table, the player's chips, etc... And then an additional state on top of that depending on what's happening -- a state for dealing/animating cards, a state for presenting the player with options (hit, stand, etc), a state when the Dealer's cards are revealed and the game concludes...
Game states must be stacked in a linear fashion, but nothing stops you from adding several state managers as children of game states... And that's what I like about the system -- while there's relatively few safeguards and few of the "rules" are enforced by the compiler/language, unexpectedly useful features keep popping up (although I'll admit they're far outnumbered by the myriad of predictably useless features).
[size="5"]4. The Resource Manager
[size="2"]The Resource manager is a monolithic singleton class that can load different formats and keeps a simple table of resources. All game resources - for now, just fonts and (optionally tiled) images, later, sounds, config files, game levels, etc - are derived classes of ResPtr, the interface for resource pointers. They all implement file-format-specific loading and rendering code that game nodes can use. When you create a new instance of a node, you usually feed the constructor a string (an arbitrary name for the resource that you set when you load), the resource manager will then do a (quite slow, at the moment) search and return the relevant resource. It's also possible to refer to resources by ID#, or even just keep a raw pointer to the resource.
[size="2"]Each resource type is heavily dependent on the API used - in my case, FontPtrs handle TTF_Fonts from SDL_ttf, and ImgPtrs handle SDL_Surfaces. If a different rendering subsystem is to be used, different resource types will have to be defined. The good thing, though, is that resources of a similar type come with the same interfaces -- for instance, both bitmap fonts and TTF fonts have a "drawText" function that behaves the same, derived from a more generic Font resource interface.
[size="5"]5. The GUI
[size="2"]GUI nodes benefit greatly from the object composition approach, as illustrated in the Spinner diagram above. It's still relatively incomplete at this point, but it's relatively easy to do simple things -- draw text to the screen, implement clickable and draggable items, change the mouse pointer, assign hotkeys...
[size="5"]6. The Future
At the moment, the goal is to finish the little BlackJack game. I'm working on content now -- graphics for the cards and chips, cohesive GUI elements, bitmap fonts. I still have to implement some game logic and sound code. Hopefully, next blog post will be a playable demo :-D
Once that's done, I can move on to bigger things. The next project is "Super Penny Thrower" -- the game I failed to present at Ludum Dare 12 a few years ago, where you throw pennies from a skyscraper just to see what'll happen (hint: stuff blows up). I will be switching to OpenGL for rendering, implementing "viewports" (for scrollable game worlds and GUI elements), and of course work on more realtime-game-specific stuff, such as physics.
I'm not entirely satisfied with the project in its current state, but I'm definitely satisfied enough that I probably won't be doing another full rewrite, and I think that's worth celebrating. Hooray, or something.
In an effort to collect my thoughts and perhaps provide something of value, here's a description of the system.
[size="5"]EDBJ: An introduction
First of all, EDBJ stands for "Event-Driven BlackJack" -- I decided to get back into game programming by chance, when I became interested in the mechanics of the casino game BlackJack. I created a quick and dirty console version, but I wanted more -- so I embarked on this journey, to create a rapid development framework specifically tailored to facilitate common game tasks, providing simple interfaces and functionality that can be extended and combined into complex behaviors, all while taking the requirements of a little (polished) BlackJack game into account.
EDBJ is all about composition -- Absolutely everything is a node on a hierarchical scene tree, from the game state manager to something as simple as a sprite. Parent nodes implement game logic and behaviors, while more basic things, such as their ability to receive input and their graphical representation, are attached to them as child nodes.
For instance, a "Spinner" is made up of a TextLabel with two PushButtons (up and down). TextLabel is the on-screen graphical representation of the spinner's current value and refers to a font resource for rendering. PushButton itself is made up of several things: an optional TextLabel (for button text), a Sprite (for the button background), and a ClickRect that informs the button of mouse activity. The ClickRect itself has a MouseListener child that is part of a special list/map in the Scene Manager, specifically designed to propagate mouse input. Likewise, Sprite has a RenderListener child, a component that is referenced in the SceneManager's list of renderables.

[size="5"]1. The Actor class
Most, if not all, objects in the game extend from the base class Actor.
Actor provides for simple tree functionality -- parent item A to item B, detach A from B, recursively delete all children, etc.
It also provides the interface for 3 virtual functions that must be extended by all derived classes:
render() -- called on all renderables every frame
processTick() -- called on all items that implement some form of logic every frame
processMessage() -- used to receive messages from any other actor, either directly or through a global manager
[size="5"]2. The Scene Manager
The Scene Manager is the root of a given scene. It is responsible for a lot of things. Its main loop, which is run every frame, looks like this:
tick() -- makes all actors tick (execute behaviors they implement every frame)
runSequences() -- a bit obsolete now, as I pointed out in my previous blog post, but it executes sequential and times actions from a simple stack
readInput() -- reads and dispatches input from mouse and keyboard to the relevant objects
dispatchMessages() -- dispatches indirect/global messages to their relevant recipients
cleanup() -- destroys any objects marked for deletion
render() -- sorts the render list and draws all renderables to the screen
It basically does anything that objects cannot (or should not) do themselves.
[size="5"]3. State Manager
The state manager is an actor that defines an additional layer of hierarchy -- stackable game states. Each game state is responsible for the objects it contains. For instance, my BlackJack game's GameState_Opening has a Sprite child for a background image of a Casino scene. You stack GameState_TitleScreen on top of it to show the game's logo and buttons such as "New Game" "Options" and "Quit." New Game and Options are linked to StateSwitchers; little utility nodes that pop the game state stack and replace the "tip" state with a new one. So, to go from the Title Screen to the Options Screen, which both share that same Casino scene background, you pop the Title screen (remove the game logo and main screen buttons) and push the GameState_Options state to the StateManager -- the background remains, but the options screen is now shown.
This allows you to use sub-states that run concurrently -- for instance, you would push a state containing objects like the BlackJack table, the player's chips, etc... And then an additional state on top of that depending on what's happening -- a state for dealing/animating cards, a state for presenting the player with options (hit, stand, etc), a state when the Dealer's cards are revealed and the game concludes...
Game states must be stacked in a linear fashion, but nothing stops you from adding several state managers as children of game states... And that's what I like about the system -- while there's relatively few safeguards and few of the "rules" are enforced by the compiler/language, unexpectedly useful features keep popping up (although I'll admit they're far outnumbered by the myriad of predictably useless features).
[size="5"]4. The Resource Manager
[size="2"]The Resource manager is a monolithic singleton class that can load different formats and keeps a simple table of resources. All game resources - for now, just fonts and (optionally tiled) images, later, sounds, config files, game levels, etc - are derived classes of ResPtr, the interface for resource pointers. They all implement file-format-specific loading and rendering code that game nodes can use. When you create a new instance of a node, you usually feed the constructor a string (an arbitrary name for the resource that you set when you load), the resource manager will then do a (quite slow, at the moment) search and return the relevant resource. It's also possible to refer to resources by ID#, or even just keep a raw pointer to the resource.
[size="2"]Each resource type is heavily dependent on the API used - in my case, FontPtrs handle TTF_Fonts from SDL_ttf, and ImgPtrs handle SDL_Surfaces. If a different rendering subsystem is to be used, different resource types will have to be defined. The good thing, though, is that resources of a similar type come with the same interfaces -- for instance, both bitmap fonts and TTF fonts have a "drawText" function that behaves the same, derived from a more generic Font resource interface.
[size="5"]5. The GUI
[size="2"]GUI nodes benefit greatly from the object composition approach, as illustrated in the Spinner diagram above. It's still relatively incomplete at this point, but it's relatively easy to do simple things -- draw text to the screen, implement clickable and draggable items, change the mouse pointer, assign hotkeys...
[size="5"]6. The Future
At the moment, the goal is to finish the little BlackJack game. I'm working on content now -- graphics for the cards and chips, cohesive GUI elements, bitmap fonts. I still have to implement some game logic and sound code. Hopefully, next blog post will be a playable demo :-D
Once that's done, I can move on to bigger things. The next project is "Super Penny Thrower" -- the game I failed to present at Ludum Dare 12 a few years ago, where you throw pennies from a skyscraper just to see what'll happen (hint: stuff blows up). I will be switching to OpenGL for rendering, implementing "viewports" (for scrollable game worlds and GUI elements), and of course work on more realtime-game-specific stuff, such as physics.
Advertisement
Advertisement
Advertisement
Discussion