Click here to Skip to main content
13,511,029 members
Click here to Skip to main content
Add your own
alternative version

Tagged as


17 bookmarked
Posted 29 Nov 2017

Game engine using SDL2 and ZetScript

, 26 Feb 2018
Rate this:
Please Sign up or sign in to vote.
Example game engine by using SDL2 and ZetScript

Update: Is also possible run the demo with the following link,


This article aims to show how easy you can build a simple game engine in C++ application with SDL2 and ZetScript script engine. The engine will have a few set of functions for paint graphics, read input keys and play sounds using Simple DirectMedia Layer(SDL). Later we will show some pieces of script code about how to make a demo of Invader game.




Note: This is article is not gonna to go in depth detail of function implementions for the engine we are presenting in this article but it will show class headers and some pieces of code from main function to have a basic idea how everything fits together.

How to use the demo

In order to run the Invader demo game execute the following steps,

  1. decompress in some directory
  2. drag invader.zs into engine.exe


  • Left/Right: move ship
  • Space: start game/shoot
  • F5: reload script invader.zs
  • F9: Toggle fullscreen
  • ESC: Quit engine



To compile the code you need ZetScript library, SDL2 librarycmake application, and MinGW or Linux with gnu 4.8 compiler or MSVC 2015/2017 or build tools v141

If you satisfies the requirements, go to the directory where you have descompressed the source code and do the following,


After cmake operation the configuration files will be created.


Note: In case of configure project for MVC++ you have also to provide include paths and library paths in order to find SDL and ZetScript.  


The version of the engine we are presenting in this article will draw graphics, sprites, fonts, will play sounds and will read input keys from keyboard. The engine it has the following classes.

  • CInput
  • CImage
  • CSprite
  • CFont
  • CRender
  • CSound
  • CSoundPlayer


CInput class Implements functions to read input keys.

#define  T_ESC          CInput::getInstance()->key[KEY_ESCAPE]
#define  T_F5           CInput::getInstance()->key[KEY_F5]
#define  T_F9           CInput::getInstance()->key[KEY_F9]
#define  T_SPACE        CInput::getInstance()->key[KEY_SPACE]

#define  TR_UP          CInput::getInstance()->keyR[KEY_UP]
#define  TR_LEFT        CInput::getInstance()->keyR[KEY_LEFT]
#define  TR_RIGHT       CInput::getInstance()->keyR[KEY_RIGHT]
#define  TR_DOWN        CInput::getInstance()->keyR[KEY_DOWN]

typedef struct{
    Uint32 codeKey;

class CInput{
    static CInput *input;
    SDL_Event Event;

    bool            key[KEY_LAST];
    bool            keyR[KEY_LAST];

    static CInput * getInstance();
    static void destroy();

    void update();


List 1.1


CImage class implements functions to load bmp images and create dinamic images based on binary indices.

#pragma once

#include   <SDL2/SDL.h>
#include <CZetScript.h>

class CImage{

    SDL_Texture *texture;
    void destroy();
    int mWidth,mHeight;

    static SDL_Texture * SurfaceToTexture(SDL_Surface *srf);
    bool createSquarePixmap(const vector<int> & pixmap);
    CImage(const vector<int> & pixmap);

    bool load(const char *file);

    SDL_Texture *getTexture();

    // create image from script ...
    bool createSquarePixmap(zetscript::CVectorScriptVariable *vector);

    int getWidth();
    int getHeight();


List 1.2 


CSprite class Implements functions to setup and update sprites in a xy coordiante and paints it the current frame it has.

#include "CImage.h"

class CSprite{
    static int synch_time;

    unsigned currentframe;
    int current_time_frame,time_frame;


    typedef struct{
        CImage *image;
        Uint32 color;

    static void synchTime();

    int x, y;
    int dx, dy;
    int width, height;
    std::vector<tFrameInfo> frame;

    static bool checkCollision(CSprite *spr1, CSprite *spr2);
    static bool checkCollision(int offsetx,int offsety,CSprite *spr1, CSprite *spr2);


    void addFrame(CImage *fr, int rgb);
    void setFrame(int n);
    void setTimeFrame(int time);
    int getWidth();
    int getHeight();
    CSprite::tFrameInfo * getCurrentFrame();

    void update();



List 1.3


CFont class implements functions to load bmp fonts. The user has to pass the dimension of each character in order to align correctly all characters its render.


#include "CImage.h"

class CFont:public CImage{

    int totalchars_x, totalchars_y,totalchars;
    SDL_Rect m_aux_rect;
    int char_width,char_height;


    bool load(const char * file,int char_width,int char_height);

    int getCharWidth();
    int getCharHeight();
    int getTextWith(const string & str);
    SDL_Rect * getRectChar(char c);



List 1.4


CRender class implements functions to paint images, sprites and texts usign a font

class CRender{

    int m_width, m_height;

    static CRender *render;
    SDL_Renderer *pRenderer = NULL;
    SDL_Event event;
    bool fullscreen;

    SDL_Window* pWindow = NULL;


    static CRender *getInstance();

    static void destroy();

    int getWidth();
    int getHeight();

    void createWindow(int width, int height);
    void toggleFullscreen();
    SDL_Renderer *getRenderer();
    SDL_Window *getWindow();

    void clear(Uint8 r, Uint8 g, Uint8 b);

    void drawImage(int x, int y, CImage *img);
    void drawImage(int x, int y, CImage *img, int color);

    void drawText(int x,int y, CFont * font, const char * text);

    void drawSprite(CSprite *spr);
    void drawSprite(int x, int y, CSprite *spr);

    void update();

List 1.5


CSound class implements function to load sound file (Only supports wave). 


class CSound{


    Uint32 wav_length;
    Uint8 *wav_buffer;


    bool load(string * file);



List 1.6


CSoundPlayer implements functions to play CSound objects and init the sound system.



class CSoundPlayer{

    SDL_AudioSpec wav_spec;

    typedef struct {
        Uint8 *audio_pos;
        Uint32 audio_len;

    static tSoundData SoundData[MAX_PLAYING_SOUNDS];

    static CSoundPlayer * singleton;
    SDL_AudioDeviceID dev;

    static void CallbackAudio(void *userdata, Uint8* stream, int len);


    static CSoundPlayer * getInstance();
    static void destroy();

    void setup(SDL_AudioFormat format=AUDIO_S16SYS, Uint16 Freq=22050, Uint16 samples=4096, Uint8 channels=2);

    void play(CSound *snd);


List 1.6

bind c++ code to use in script

In the last sections we have seen the classes that makes up the functionallity to manage graphics, input and sound for the game engine. This section and, maybe the most exciting part, we will see how easy is to binds main parts of the C++ headers to use in script side through ZetScript API.

We have register_C_Class and register_C_SingletonClass to register classes and singletons repectively. register_C_VariableMember, register_C_FunctionMember for register its class members. Also register_C_FunctionMember is used to register C functions. 

For example, we register CImage to use in the script by doing the following,


And in the similar way to register its function member CImage::load,


The following code shows the all functions and variables we need to be used in the script side. We will see that in a few lines of code we have registered them all!

    // Binds CImage class...
    if(!register_C_Class<CImage>("CImage")) return false;

    // bind a custom constructor...
    register_C_FunctionMember<CImage>("CImage",static_cast<bool (CImage::*)(zetscript::CVectorScriptVariable * )>(&CImage::createSquarePixmap));

    // Binds CSprite class and it members...
    if(!register_C_Class<CSprite>("CSprite")) return false;
    if(!register_C_Function("checkCollision",static_cast<bool (*)(int, int, CSprite *, CSprite *)>(&CSprite::checkCollision))) return false;
    if(!register_C_Function("checkCollision",static_cast<bool (*)(CSprite *, CSprite *)>(&CSprite::checkCollision))) return false;
    if(!register_C_VariableMember<CSprite>("x",&CSprite::x)) return false;
    if(!register_C_VariableMember<CSprite>("y",&CSprite::y)) return false;
    if(!register_C_VariableMember<CSprite>("dx",&CSprite::dx)) return false;
    if(!register_C_VariableMember<CSprite>("dy",&CSprite::dy)) return false;
    if(!register_C_VariableMember<CSprite>("width",&CSprite::width)) return false;
    if(!register_C_VariableMember<CSprite>("height",&CSprite::height)) return false;

    // CSprite functions
    if(!register_C_FunctionMember<CSprite>("setTimeFrame",&CSprite::setTimeFrame)) return false;
    if(!register_C_FunctionMember<CSprite>("update",&CSprite::update)) return false;
    if(!register_C_FunctionMember<CSprite>("addFrame",&CSprite::addFrame)) return false;

    // Binds CFont class
    if(!register_C_Class<CFont>("CFont")) return false;
    if(!register_C_FunctionMember<CFont>("load",&CFont::load)) return false;

    // Binds CSound class
    if(!register_C_Class<CSound>("CSound")) return false;
    if(!register_C_FunctionMember<CSound>("load",&CSound::load)) return false;

    if(!register_C_SingletonClass<CRender>("CRender")) return false;
    if(!register_C_Function("getRender",CRender::getInstance)) return false;
    if(!register_C_FunctionMember<CRender>("drawImage",static_cast<void (CRender:: *)(int, int, CImage *)>(&CRender::drawImage))) return false;
    if(!register_C_FunctionMember<CRender>("drawImage",static_cast<void (CRender:: *)(int, int, CImage *,int)>(&CRender::drawImage))) return false;
    if(!register_C_FunctionMember<CRender>("drawText",&CRender::drawText)) return false;
    if(!register_C_FunctionMember<CRender>("drawSprite",static_cast<void (CRender:: *)(int, int, CSprite *)>(&CRender::drawSprite))) return false;
    if(!register_C_FunctionMember<CRender>("drawSprite",static_cast<void (CRender:: *)(CSprite *)>(&CRender::drawSprite))) return false;
    if(!register_C_FunctionMember<CRender>("getWidth",&CRender::getWidth)) return false;
    if(!register_C_FunctionMember<CRender>("getHeight",&CRender::getHeight)) return false;

    // Binds singleton CSoundPlayer...
    if(!register_C_SingletonClass<CSoundPlayer>("CSoundPlayer")) return false;
    if(!register_C_Function("getSoundPlayer",&CSoundPlayer::getInstance)) return false;
    if(!register_C_FunctionMember<CSoundPlayer>("play",&CSoundPlayer::play)) return false;

    // Binds input global vars...
    if(!register_C_Variable("TR_UP",TR_UP)) return -1;
    if(!register_C_Variable("TR_DOWN",TR_DOWN)) return -1;
    if(!register_C_Variable("TR_LEFT",TR_LEFT)) return -1;
    if(!register_C_Variable("TR_RIGHT",TR_RIGHT)) return -1;
    if(!register_C_Variable("T_SPACE",T_SPACE)) return -1;

List 1.7

Save state

At this point we have registered all C functions needed for the script side. ZetScript it saves them in the current state. I we want to reevaluate the file (very usefull to test you game development) we have to save current state and set the state later before reeval script file as we can see,

int idxSt= CZetScript::saveState();

save state will return the index of saved state (idxSt) that will set before reevaluate the file. 


Load script file

ZetScript provides a function to load script file called eval_file as we can see in the following code,



Bind script functions

After the file is loaded we need to expose the script functions from the loaded file to be used in game engine. The script file should implement these three functions,

  • init: Will initialize variables, structs, sprites, load images, etc.
  • update: Will be called in the C++ game engine loop.
  • unload: Will be called when the game is unloaded.


To bind a script function is done by bind_function passing the function cast we want to be in the script side. Generally all script function does won't pass any arguments and won't return any value so the funtion cast will be void(void)

To bind these functions is shown in the following code,

std::function<void()> * init=bind_function<void ()>("init");
std::function<void()> * update=bind_function<void ()>("update");
std::function<void()> * unload=bind_function<void ()>("unload");

List 1.8


Game Engine loop

Finally we present the game engine loop. It would be the following code,


(*init)(); // <-- it calls script function init.


  render->clear(0,0,0); // clears screen with black color

  input->update(); // read events from keyboard.

  (*update)(); // <-- it calls script function update.

  render->update(); // flip screen


(*unload)(); // <-- ita calls script function unload

List 1.9


Reevaluating script file

As we said before, some time is interesting to reevaluate script file for developing purposes. Is faster reevaluate in the fly than reexecute the engine, presing simple key. We modify the code 1.8 presented as follows,

    // clear screen...

    // update keyboard events...

    // if press F5 then reload file...
    if(T_F5) {


        CState::setState(idxSt); // restore initial state

        //Now, the state is the same as we had before eval the script...


        // Recreate script functions...
            init=bind_function<void ()>("init");
            update=bind_function<void ()>("update");
            unload=bind_function<void ()>("unload");

        // Call init function...
        printf("State restored\n");


    // update screen...

List 1.10

The code 1.10 implements a refresh behaviour like it was as browser, i.e, when the user press the F5 key the script is reevaluated, reseting it by setting the state when was saved by  saveState. The state is set by idxSt and after the file is reevaluated the binding functions ini, update and unload must be recreated due changes in memory by reevaluating file.


Note: About the list 1.10, I have removed try/catch and some check conditions from the original source code to see more clear the code we presented. 

Invader game

We have presented the implementation of our C++ game engine to process a implemented games from script files. Now, we will show how implement an Invader game using the game engine.

First of all, as we have told before, the engine expects to have implement three functions in the script side:

  • init
  • update
  • unload

These three functions are declared in the file as shown in the following code,

function init(){


function update(){


function unload(){


List 2.1

loading images

In the engine supports two types of images: binary and bitmap images. 

To load bitmap we can use CImage::load so for example Invader game loads title game bitmap called title.bmp. The variable declaration and its loading is done doing the following,

var invader_title;

function init(){

    invader_title=new CImage();


To load bit based images we have to use the CImage constructor function CImage::createScquarePixmap we have registered.

If we remember the code from list 1.7, we registered CImage::createSquarePixmap function member called as CImage (the same name as the class),

register_C_FunctionMember<CImage>("CImage",static_cast<bool (CImage::*)(zetscript::CVectorScriptVariable * )>(&CImage::createSquarePixmap));

What it means that CImage::createSquarePixmap is declared as CImage constructor in script side because the function name exposed in script side is the same as the class name, i.e CImage. CVectorScriptVariable is the vector type of ZetScript (for further information visit

For example, the following code creates CImage passing a vector with binary integers as parameter,


new CImage( <-- CImage constructor 
  [ // <-- vector variable

List 2.2

That it represents the following image,


The 1s in binary format paints and 0s doesn't



Note: The game implements more images but to not fill too much these article with code, I keep out them.


Implementing our custom sprite class

We have seen in list 1.3 the CSprite class. ZetScript it has the feature to inherit C++ classes. We define  CMyClassSprite class that inherits CSprite (from c++),

//Defines CMySprite class. inherits CSprite (from C++)
class CMySprite: CSprite{
    var active;     // tells if sprite is active or not
    var points;     // tells the value to add in the score when sprite is destroyed.
    var color;      // tells sprite color 
    var attack_time;// tells time to do next attack
    var time_life;  // tells remaining time to have this sprite living
    function CMySprite(){ // constructor with no parameters
    function CMySprite(_points,_color){ // constructor with arguments;
        this.time_life = 0;
    // adds image frame...
    function addFrame(_img){
        super(_img, this.color); // <-- calls CImage::addFrame (from C++)
    // updates sprite
    function update(){
            if(this.time_life < currentTime()){
        super(); // it calls CSprite::update (from C++)

List 2.3

Sprite Manager

After we have defined our custom CMySprite class for script, we are presenting another important class that will manage the logic flow of sprite type. SpriteManager will create and update a set of sprites of  the same type.

In invader game we have the following sprite types:

  • Enemy bullets sprites
  • Hero bullets sprites
  • Enemy sprites

In order to explain better how CSpriteManager it works, we present a simplified code of CSpriteManager class with the important content from the original source,


class CSpriteManager{
    var sprite;        // vector of sprites
    var free_index;    // vector telling free sprite slots
    var max_time_life; // tells time life for each sprite created.
    var next_mov;      // tells time to do next move
    var x_base;        // offset x of sprites
    var y_base;        // offset y of sprites
    var dx;            // dx update basex foreach iteration.
    var dy;            // dy update basey foreach iteration.

    // it creates spritemanager with a vector of sprite type defined by max_sprites
    function CSpriteManager(max_sprites, image,_max_time_life){
        for(var i=0; i < max_sprites; i++){
            var spr=new CMySprite();
    // it creates a sprite at start_x, start_y
    function create(start_x, start_y, _dx, _dy){
        var index;
            // pops the last value and set sprite as active ...
            index= this.free_index.pop();
            this.sprite[index].x= start_x;
    // check collision of sprite given within sprites (to override)
    function check_collision(spr)
    // function doAttack (to override)
    function doAttack(spr){

    // removes sprite at index i
    function remove(i){
    function update(){
        for(var i=0; i < this.sprite.size(); i++)
            var spr=this.sprite[i];

                    // check if sprite collides with CSpriteManager::check_collision

                    // updates sprite

                    // check if sprite attacks with CSpriteManager::doAttack
                    // if sprites goes outside screen or its lifetime ends, remove it.
                            (spr.y<-spr.height || spr.y>render.getHeight()) 
                         || (spr.time_life>0 && spr.time_life<currentTime())
                          || (spr.x<-spr.width   || spr.x> render.getWidth()) 
                render.drawSprite(this.x_base,this.y_base,spr); // paint always..

List 2.4

With CSpriteManager we can instance enemy bullets, hero bullets and explosion as CSpriteManager objects,


var enemy_bullet;
var hero_bullet;
var explosion;

function init(){


    enemy_bullet=new CSpriteManager(MAX_ENEMY_BULLETS,image[7],0);
    hero_bullet=new CSpriteManager(MAX_ENEMY_BULLETS,image[9],0);
    explosion=new CSpriteManager(MAX_EXPLOSIONS,image[8],200);



List 2.5

In other hand, enemy sprite manager it has a custom implementation from CSpriteManager due each enemy is a sprite with two frames used for animation. Also, enemy CSpriteManager it has to implement some functions as doAttack or check_collision. 

Next, the following code presents a simplied implementation of CEnemyManager  from original source (to see clearly the overrides doAttack and checkcollision),

class CEnemyManager:CSpriteManager{
    var time_mov;
    var y_top;

    function CEnemyManager(){

        var x=0;
        // creating 15*3 invaders...
        for(var i=0; i < 3; i++){
            //var j=0;
            for(var j=0; j < 15; j++){
                var color=0;
                var frame1=0;
                var frame2=0
                if(i == 0){ // invader type 0
                    color = 0x00FF00; // green color
                    frame1=0; // image index 0 as frame1
                    frame2=1; // image index 1 as frame1
                }else if(i==1){ // invader type 1
                    color = 0x00FFFF; // yellow color 
                    frame1=2; // image index 2 as frame1
                    frame2=3; // image index 3 as frame1
                }else if(i == 2){ // invader type 2
                    color = 0xFFFF00; // cyan color
                    frame1=4; // image index 4 as frame1
                    frame2=5; // image index 5 as frame1
                // creates sprite and sets parameters
                var spr=new CMySprite(100,color);
    // implement check_collision. It check collision for all hero bullets...
    function check_collision(spr){
            for(var i=0;i < hero_bullet.sprite.size(); i++){
    // implements doAttack for enemy sprite.
    function doAttack(spr){
                // it creates enemy bullet at enemy position with 2 as y velocity


List 2.6

In the list 2.6 we can see that sprite manager implementation for enemies it checks collision for each sprite with hero bullet, when hero bullet it collides with some enemy sprites it adds score and creates explosion at the same position where the enemy died.

In doAttack function it implements if the enemy is ready to do an attack that basically it creates a bullet at the same position where the enemy is.

Update function

The update function is the main update function that engine calls in each iteration and will update all CSpriteManager objects instanced in the init function as we can show in the following code,


In the same function we have to know if any enemy bullet collides with hero sprite and then do the respectively actions,

for(var i=0;i < enemy_bullet.sprite.size(); i++){



            // do actions when hero is destroyed.

Furthermore, the update function will check whether left or right is pressed through T_LEFT/T_RIGHT variables already registered in the main.cpp. If one of these keys are pressed the hero will move to left/right respectevely. About hero bullets, if the key SPACE is pressed a hero bullet will be created at hero position as it shows the following code,

if(T_SPACE){ // creates a bullet hero with -5 as velocity in y
   hero_bullet.create(hero.x, hero.y, 0, -5);


In this article we have seen a complete example of application made in C++ as game engine combined with ZetScript that allowed us bind C++ functions and variables in script side. I would have liked to go more in depth detail how game engine works but it would go out of the main purpouses of this article and maybe it would have been a bit tedious to follow it.

Invader game consist about 800 lines and it would have could be less If  CSpriteMannager would have be implemented in the C++ side but I prefered to show the combination of C++ with ZetScript as mixed as possible to see its benefits.



  • 2018-02-26 Port invader demo to online version thanks to emscripten
  • 2018-02-21 ZetScript Game Engine 1.1.0: Changes since 1.2
  • 2017-11-29 ZetScript Game Engine 1.0.0: First release


This article, along with any associated source code and files, is licensed under The MIT License


About the Author

Software Developer (Senior)
Spain Spain
I'm a happy person who likes programming. It has been a passion since my nineteen's Smile | :) Smile | :) .

You may also be interested in...


Comments and Discussions

PraiseUpvote = Space Invaders Pin
Slacker00721-Feb-18 3:04
professionalSlacker00721-Feb-18 3:04 
GeneralRe: Upvote = Space Invaders Pin
jespa00721-Feb-18 7:53
memberjespa00721-Feb-18 7:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.180417.1 | Last Updated 26 Feb 2018
Article Copyright 2017 by jespa007
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid