Short introduction to Pixel Game Engine

Short introduction to Pixel Game Engine

Fast and easy game and 2-D graphics prototyping

Introduction

While lurking in One Lone Coders discord channel I found his header only implementation of a 2-D game graphics engine called Pixel Game Engine, short PGE from this on. The author, javidx9, offers it free for anyone to use provided that the correct License is mentioned in products deriving from PGE. Currently at version 2.15 it offers very nice features for everyone to experiment with, and even implement full featured 2-D games in portable and modern C++. It is multi platform and portable and as it's a single header implementation it's very easy to include in your own projects. As it's very portable, the author has already succesfully used Emscriptem to compile C++ applications made with PGE to be ran in a Web Browser with WebAssembly.

Saying it only provides 2-D graphics may be wrong actually because there are many extensions which provide things like Networking, Sound, Rasterized 3-D and Transformations. But if I am right PGE started as a 2-D pixel graphics framework. And if you only need pixel 2-D graphics with or without GPU acceleration you can achieve this by just including one C++ header file in your project.

It can also load certain types of assets like PNG images for example so using things like Sprites is pretty straightforward. There also is a concept called ResourcePacks which allow you to pack your assets and resources in easily accessible container.

PGE compiles with C++14 and C++17 but the recommended language standard is C++17. It should also compile with C++11 but I have not tried it.

First steps

I have only scratched a surface of PGE as I've only been using and studying it for few hours but I already noticed it can offer quite nice features for you to quickly experiment and create prototypes, or even full blown games.

As always, when I start to learn any new system I tend and want to start doing practical things with it. I have found it the most suitable way to learn new things, learning by doing. So in this blog article I go through a small project. It is by no means the best design or implementation but should provide enough butt-kick to get you going further.

There is a pretty good Wiki with instructions for the PGE but I go the basics here and go forward with my test project.

Basic PGE application implementation

To start using PGE, in your main cpp, include the needed header:

#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"

This brings you all the base features of the engine.

Implementing a basic application and game/main loop is very simple with PGE. To start up your application you just do this:

int main()
{
    Example demo;
    if (demo.Construct(256, 240, 4, 4))
        demo.Start();

    return 0;
}

The main() just calls the Construct() method of the PixelGameEngine which creates the application with the parameters you give to it. Here's the footprint of that method.

Construct(int32_t screen_w, int32_t screen_h, int32_t pixel_w, int32_t pixel_h,
            bool full_screen = false, bool vsync = false, bool cohesion = false);

So you have the following arguments

  • Screen width (screen_w)
  • Screen height (screen_h)
  • Pixel width (pixel_w)
  • Pixel height (pixel_h)
  • Full screen flag (full_creen, defaults to windowed)
  • Vertical sync flag (vsync, defaults to false)
  • Pixel cohesion (cohesion, defaults to false)

I am not really sure about the cohesion argument but I think it relates how drawn pixels relate to the actual monitor pixels. Especially when you resize the render window. But, I have to experiment with this later.

The PGE application class can be implemented as follows:

class Example : public olc::PixelGameEngine
{
public:
    Example()
    {
        sAppName = "Example";
    }

public:
    bool OnUserCreate() override
    {
        // Called once at the start, so create things here
        return true;
    }

    bool OnUserUpdate(float fElapsedTime) override
    {
        // called once per frame
        for (int x = 0; x < ScreenWidth(); x++)
            for (int y = 0; y < ScreenHeight(); y++)
                Draw(x, y, olc::Pixel(rand() % 256, rand() % 256, rand()% 256));    
        return true;
    }

    bool OnUserDestroy() override
    {
        return true;
    }
};

Derive a class from olc::PixelGameEngine and override the needed call backs. The class constructor sets the application name in this example.

  • OnUserCreate() gets called once when you start the application with Start().
  • OnUserDestroy() gets called once when the application is asked to close. If this method returns false the main loop will continue and app will not close.
  • OnUserUpdate() gets called for every frame by the main loop. You get the time passed from previous frame as an argument fElapsedTime so you can calculate your frame rate and limit it if you so desire. You must return true from the method if you want your game loop to continue running.

Note that you can close the PGE application by closing the window manually or by returning false from the OnUserUpdate() callback.

In the above example the main loop just draws a screen full of randomly colored pixels in every frame.

TextureAtlas class implementation

I noticed PGE has Sprites and Decals. Sprites are a CPU renderable resource and Decals are Sprites which are GPU rendered. The Sprites and Decals can be easily drawn to the screen by using Draw() calls. For example:

void DrawDecal(const olc::vf2d& pos, olc::Decal* decal, 
    const olc::vf2d& scale = { 1.0f,1.0f }, const olc::Pixel& tint = olc::WHITE);

These entities also support drawing parts of the Sprite/Decal by taking the source rectangle from the source Sprite to be drawn.

void DrawPartialDecal(
    const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& source_pos, 
    const olc::vf2d& source_size, 
    const olc::vf2d& scale = { 1.0f,1.0f }, 
    const olc::Pixel& tint = olc::WHITE);
void DrawPartialDecal(const olc::vf2d& pos, const olc::vf2d& size, 
    olc::Decal* decal, const olc::vf2d& source_pos, 
    const olc::vf2d& source_size, 
    const olc::Pixel& tint = olc::WHITE);

So you need to give a source coordinate and size of the sub sprite within your decal. As a practice project I wanted to simplify this a bit. I wanted to make an API which you can just give the index of a sub image and my TextureAtlas class would calculate the source position automatically. This would simplify the use of this texture atlas because after constructing the atlas you can trust the atlas itself and just ask a sub image with an index. I came up with the following prototype for my TextureAtlas

Constructing it can be done like this

std::shared_ptr<olc::Sprite> atlassprite = 
    std::make_shared<olc::Sprite>("Assets\\soniccd.png");
m_atlas = std::make_unique<olc::TextureAtlas>(atlassprite, 11, 1);

First I load a Sprite from external asset file. Then I construct an instance of my TextureAtlas class by passing the loaded sprite to it and telling that I have 11 sub images in one row within that sprite. The constructor of the atlas does the following to prepare the atlas for usage:

m_atlasDecal = std::make_unique<olc::Decal>(m_atlas.get());        
m_partialSize = olc::vi2d(atlasSprite->width / cols, atlasSprite->height / rows);
m_spriteCount = rows * cols;

It creates a Decal from the given sprite. As mentioned earlier, a Decal is GPU renderable sprite. Then it calculates the size of a sub image based on the arguments. The atlas can have any number of rows having any number of sub images in each row. So for the above code I calculate the size based on the atlas dimensions and how many rows and columns of sub images it has.

Now I have to implement a Draw() method which can draw any sub image from this atlas.

    void Draw(const olc::vi2d& pos, const olc::vf2d& scale,
        std::uint16_t x, std::uint16_t y)
    {
        olc::vf2d fpos((float)pos.x, (float)pos.y);
        if (y < m_rows && x < m_columns) {
            olc::vf2d size(m_partialSize.x * scale.x, m_partialSize.y * scale.y);
            olc::vi2d sourcepos(x * m_partialSize.x, y * m_partialSize.y);
            pge->DrawPartialDecal(fpos, size, m_atlasDecal.get(), sourcepos, m_partialSize);
        }
    }

The type olc::vi2d is a vector where the x and y components are integers. Similarly, the type olc::vf2d is a vector where the components are of type float. They are defined in the PGE and usable after you have included the header of the engine.

The Draw() method takes following arguments for now:

  • pos tells where to position the decal on screen
  • scale tells the size of the drawn decal. 1.0 is original size, 0.5 is half of the size etc.
  • x tells the index of the sub image in certain row (coordinate)
  • y tells the row of of sub images (coordinate). If you have only one row of images, pass 1.

The method calculates the scaled size based on the scale factor. Then it calculates the sub image source position (rectangle) using the coordinates passed to the Draw() method..

Here is a video of how it works. I didn't include the controller which does the actual animation but you see it drawing sub images from the TextureAtlas as an sequence.

You can see the full implementation of this class and the tester application in my GitHub . It's a Visual Studio 2019 project.

Final words and disclaimer

This article just barely scraches the surface of PGE features. It is by no means meant to be a comprehensive guide, just to provide you some info if you have not heard about it so far. You can download the engine it from javidx9's GitHub and start experienting, maybe you will find it useful for your purposes.

I might write more posts about PGE later if I continue to do things with it, which I am pretty sure I will because of how easy it is to get into and use. I must here congratulate javidx9 for doing this and thank you for the work you are doing for the programming community with your tutorials an sample code.

Please note that I am by no way affiliated with the author of PGE or One Lone Coder other than being a part of the nice community there.

Thank you for reading!