Jolie Rouge


all ur parentheses Я belong to me


SDL 1.2 Game Development

I finally decided to sit down and learn game development. From my research there is only one way to do it, and that is SDL. If it’s good enough for Valve and Steam, it’s good enough for me. As it turns out, it’s surprisingly easy to get up and running with SDL. There are plenty of tutorials about the basic SDL game setup, so I won’t repeat the ubiquitous info and go straight to some basic problems of making a game that no one really seems to discuss.

There are two main problems when designing a game:

  1. How do I save and restore game state?
  2. How do I make my game configurable?

C is a great language for speed and algorithms, but it is really shitty when you want to handle data. Games are both algorithm and data intensive. So for half the game development, C rocks. But for the other half, it’s a slog.

Saving and restoring game state means populating a structure in your C code with values from a human editable, dynamically loaded file. Things like the x,y or even z coordinates of the player in game space, their health, equipped weapon, inventory items and so on.



One solution for solving this problem is to use some kind of config file format like INI, or XML. INI is a good choice for very simple games, and it’s dead simple to write an INI parser in C. The other common option is XML, which means something like libXML, which means I’d rather have my eyes gouged out with rusty spoons. On top of generally hating XML, interfacing from C with XML is a nightmare and 1/2.

There is a third option though, which can be better or worse: Embedded Scripting Language.

Using an embedded scripting language to setup game state is pretty standard practice, most big games use either Lua, or Python.

I’ve embedded Lua in a C application before, and I never want to do it again. I’ve also embedded Python, and it’s better. Also, I like Python a lot, so it would have been my fallback choice here. I also like Lua, a lot, but embedding it is not as fun as you’d think. I am overstating the problem of embedding Lua, it’s not that hard.

There are of course other options, one of them will probably scare you to death. Don’t run, you’ll only die tired.

In the case of Lua and Python, and libXML and just about any other scripting language, that’s yet another DLL you have to distribute.

When first trying to solve this problem I found JSMN, which is a self-contained dependency free JSON parser. Since I love JSON as a format for storing info I plugged it in and tried it out. It’s worth considering, but it’s a bit like embedding Lua(actually a bit worse). There are no convenience methods for working with the data, and you’ll have to build the worlds biggest State Machine to do anything with it. I dumped Jasmine pretty quick when I found TinyScheme.

I can hear your footsteps already on your way to the door. But hear me out. Scheme is actually the perfect solution because XML is just Retarded Lisp without any functionality. TinyScheme, which is a dialect of LISP has all the power of representing Hierarchical Data, plus if you’re hardcore, you can mix in some programming logic too.

Since TinyScheme is small, fast, and has 0 dependencies, it really appealed to me. The best thing about it, it’s the easiest embed of a language I’ve ever done.

Basically you download TinyScheme from SourceForge and you plop the files into your src directory, add them to your project and boom, you’re ready to go. You’ll need to make a few changes to the header file scheme.h

#define USE_DL 1
#if USE_DL
#define USE_INTERFACE 1
#endif

After that, I made a small addition to scheme.c around 2288 that allows for the writing of script errors to an error log.

/* ========== Evaluation Cycle ========== */
#include stdio.h
#include stdarg.h
#include stdlib.h
void scheme_log_info(char *msg, ...)
{
    FILE* lf = fopen("scheme_log.txt","a");
    va_list argp;

    va_start(argp, msg);
    vfprintf(lf, msg, argp);

    va_end(argp);

    fprintf(lf, "\n");
    fclose(lf);
}
static pointer _Error_1(scheme *sc, const char *s, pointer a) {
...

Then at line 2330, I’ve added scheme_log_info(str); which effectively appends errors to a text file, which helps when debugging, especially if your aren’t familiar with Scheme. The errors are largely unhelpful, but at least you get a line number.

REMEMBER: You want to include scheme-private.h, not scheme.h

Basic Theory of Embedding Scripting Languages

The basic idea of an embedded scripting language is that you have an init function that creates a context or environment which is basically a little sandbox scripting world into which you will load strings of that scripting language and they will be executed.

Some scripting languages allow you to evaluate code and receive a result, and others focus more on executing code.

With a fresh vanilla context you can’t do much, so you need to register some callback methods into the environment. These methods, or functions, are usually just pointers to your C functions which usually have to match the recommended signature expected by the scripting language. Almost universally the signature is pointer_type myFunc(lang* context, pointer_type args).

The pointer_type is usually defined by the language, and some might require that the callback be void. When a scripting language requires a pointer_type returned it usually means it’s going to actually use the return value, possibly in another callback call.

Once your callback is called you will need to unwrap the pointer_type to a sensible datatype for C, like int, char* and so on, and most scripting languages that support embedding have a bunch of helper functions to get at useful datatypes.

With these things understood and in place, initializing the context, and then providing it code to execute, and callbacks that make the execution worthwhile, and implementing the unwrapping, you’re halfway there.

Thankfully, for our purposes we only need 1 way communication. We just want to get things into C from the scripting environment, not the other way around. At least not yet.

Why Scheme as good as XML

In this particular situation Scheme is as good as XML because it’s easy to represent data structures as S-Expressions. Here is an example of my Player Object, including it’s sprite sheet and clipping info in Scheme:

(set-player-attributes 
   'x 640.000000 
   'y 400.000000 
   'speed 4.000000 
   'height 0 
   'width 0 
   'health 0 
   'current_frame 2 
   'total_frames 7 
   'base_frame 0 
   'idle_frame 0 
   'idle_total_frames 7 
   'walking_frame 8 
   'walking_total_frames 5 
   'action1_frame 0 
   'action1_total_frames 0 
   'action2_frame 0 
   'action2_total_frames 0 
   'action3_frame 0 
   'action3_total_frames 0 
   'action4_frame 0 
   'action4_total_frames 0 
   'sprite_sheet "assets/images/player.bmp" 
)
(set-player-sprite-clip 0 'x 0 'y 0 'w 32 'h 100)
(set-player-sprite-clip 1 'x 0 'y 0 'w 32 'h 100)
(set-player-sprite-clip 2 'x 0 'y 0 'w 32 'h 100)
(set-player-sprite-clip 3 'x 0 'y 0 'w 32 'h 100)
(set-player-sprite-clip 4 'x 0 'y 0 'w 32 'h 100)
(set-player-sprite-clip 5 'x 0 'y 0 'w 32 'h 100)
(set-player-sprite-clip 6 'x 0 'y 0 'w 32 'h 100)
(set-player-sprite-clip 7 'x 0 'y 0 'w 32 'h 100)
(set-player-sprite-clip 8 'x 244 'y 0 'w 41 'h 100)
(set-player-sprite-clip 9 'x 285 'y 0 'w 30 'h 100)
(set-player-sprite-clip 10 'x 315 'y 0 'w 29 'h 100)
(set-player-sprite-clip 11 'x 344 'y 0 'w 36 'h 100)
(set-player-sprite-clip 12 'x 382 'y 0 'w 32 'h 100)
(set-player-sprite-clip 13 'x 414 'y 0 'w 32 'h 100)
(set-player-sprite-clip 14 'x 0 'y 0 'w 0 'h 0)
(set-player-sprite-clip 15 'x 0 'y 0 'w 0 'h 0)
(set-player-sprite-clip 16 'x 0 'y 0 'w 0 'h 0)
(set-player-sprite-clip 17 'x 0 'y 0 'w 0 'h 0)
(set-player-sprite-clip 18 'x 0 'y 0 'w 0 'h 0)
(set-player-sprite-clip 19 'x 0 'y 0 'w 0 'h 0)
(set-player-sprite-clip 20 'x 0 'y 0 'w 0 'h 0)
(set-player-sprite-clip 21 'x 0 'y 0 'w 0 'h 0)
(set-player-sprite-clip 22 'x 0 'y 0 'w 0 'h 0)
(set-player-sprite-clip 23 'x 0 'y 0 'w 0 'h 0)
(set-player-sprite-clip 24 'x 0 'y 0 'w 0 'h 0)
(set-player-sprite-clip 25 'x 0 'y 0 'w 0 'h 0)
(set-player-sprite-clip 26 'x 0 'y 0 'w 0 'h 0)
(set-player-sprite-clip 27 'x 0 'y 0 'w 0 'h 0)
(set-player-sprite-clip 28 'x 0 'y 0 'w 0 'h 0)
(set-player-sprite-clip 29 'x 0 'y 0 'w 0 'h 0)

This isn’t the only way to do it, it could be even fancier, I but I elected for a direct Scriptable style, where callbacks are used to set data. The other way to do it would be to quote the whole S-Expression and then navigate it as a true List. If I did that, we’d lose the simplicity angle for people who aren’t into Lisp/Scheme. With the above, you don’t even have to know it’s LISP, it’s Just a Text Format. Don’t ask why things are the way they are, you just do it and it works.

Here’s how it might look as an XML file:

<player>
   <attributes>
      <x>640.000000</x>
      <y>400.000000</y>
      <speed>4.000000</speed>
      <height>0</height>
      ...
   </attributes>
   <sprites>
      <clip>
        <index>0</index>
        <x>0</x>
        <y>0</y>
        <w>32</w>
        <h>100</h>
      </clip>
      ...
   </sprites>
<player>

I didn’t write out the full example, because it’s verbose…yeah, another reason I don’t like XML.

With the Scheme version, I made it so that each individual clip is also scriptable, so extensions and community content can alter it, including creating entirely different sprite sheets and animations.

This also means that I can reuse this code in any game to make players and even enemies.

The core idea behind all programming is: Never Repeat Yourself. Write it once, use it many times. We are always building frameworks for our software.

The C Side

You should know by now the basic structure of a Game.

BasicGameDesign

Our general idea with a game is to get from main to the GameLoop as quickly as possible, and simply rendering elements to the screen.

Our GameLoop function is a generalized recursive (or while loop if you must) function that gets called indefinitely and divies out the game logic duties for our program.

The game loop performs these tasks:

  1. Should I continue? If not, Quit(freeing resources) and return (therefore exit program)
  2. Call the function that calculates the current frame of animation for all game objects (including the player)
  3. Loop over all game objects and draw them to the screen
  4. Should I cap the Frame Rate? If so, Delay

Here is an example of an actual GameLoop I am using in a prototype side-scroller game with SDL 1.2

BasicGameLoop

 

Now you need a place to store State. State is universally important to a game, and you need a couple of structures in your program. The most important one is the Generalized GameObject:

GameObject

This big struct has everything you need to represent some GameObject in the game.

The sprites member of the struct is where we keep all of the Clips that get created with the Scheme call (set-player-sprite-clip 0 'x 0 'y 0 'w 32 'h 100). The first argument 0 is the index of the array, and the rest are the named 2by2 pairs of attributes.

The sGameObject struct also has information about frames. current_frame and total_frames and base_frame deal with the currently running animation.

idle_frame, walking_frame and action1-4_frame deal with different generic animations, in this case aside from walking and idle, I have these 4 generic actions that could be shooting different types of guns, or punching, throwing, high and low kick etc. Compairing base_frame with idle_frame will tell you that the GameObject is idle, if comparing base_frame with action3_frame will tell you the GameObject is performing that action and so on.

Since this game is still in the prototype phase, this structure could evolve over time.

The most important member is the id, which lets you know what kind of GameObject it is. An id of 0 could mean player, and id of 1 a certain type of enemy, etc.

Notice sGameObject is not typedef’d. Don’t abuse typedef. If you don’t actually need it, don’t use it.

A note on GameObject

Everything in your game is a GameObject, or nearly everything. The player, all the enemies, all the bullets, ammo refils, and anything that can be interacted with, like a door or a switch. To reach the end of a level, you can place a blank GameObject for the player to collide with that ends the game.