Shabadoo - a last devlog

https://ldjam.com/events/ludum-dare/51/shabadoo

4fa8b.png

Hello. Today I have the time in order to rate games - and also explain how I created my project - Shabadoo. Its main particularity is the template which I made so as to simplify the game development. At first I thought that the OLC::PixelGameEngine (Javidx9) could be an interesting way to create my game. I don't know if you know it, but it is a single header file in pure C++ without any dependencies using OpenGL as a way to create inside a GDI+ frame a single texture on which you control every pixel. I quickly resumed how it works, but it is a great tool with many functions and a clever ECS system which reminds us of some Unity script including an appUpdate() section. However (like with Pico-8) there are harsh limitations. Ok! Let's go...

I said to myself that it could be fun to create my own Template for GameJam(s). Fundamentally it is not too hard to create a 2d game engine from scratch in pure C++ and inspired by the PGE template. The core can be developed in less than 500 code lines, but after this work I struggled hard in order to draw, shape and compute game objects. If we can understand easily what a line is (x1,y1 to x2, y2), just a moment think about a hexagon or a filled non-regular polygon... At first, I show you what is a pixel structure according to this template - and how it draws them on screen : ```(c++) struct Pixel { union { // opaque black pixel uint32_t n = 0xFF000000;

        struct
        {
            uint8_t r;
            uint8_t g;
            uint8_t b;
            uint8_t a; // alpha
        };
    };
};

(...)

bool xGameEngine::Draw(const gck::vi2d& pos, Pixel p)
{
    if (!pDrawTarget) 
        return false;

    Pixel d = pDrawTarget->GetPixel(pos);
    // escape quickly if the pixel has the same color
    if (p == d) 
        return false;

    float a = (float)(p.a / 255.0f);
    float c = 1 - a;
    float r = a * (float)p.r + c * (float)d.r;
    float g = a * (float)p.g + c * (float)d.g;
    float b = a * (float)p.b + c * (float)d.b;
    // new pixel at pos
    return pDrawTarget->SetPixel(pos, Pixel((uint8_t)r, (uint8_t)g, (uint8_t)b));
}

When I started my project canvas, I thought about a clean font in-game without any third part. I remembered a script which I made under Unity in order to write text as a single sprite. If you remember TakeYourMedsDarling (a previous entry for the LD 49), I used a system like this... How does it work? In fact, it is more or less similar to the Chip-8 font system (lol). Imagine a sprite 5x7 pixels (chip-8 used a 4x4 pixels size). Imagine now 5 bytes and their bits. For each column the function reads a hexadecimal value and each converted bit 1 becomes a colored pixel - a bit 0 becomes an alpha null pixel. So the letter A can be write with the bits : std::vector keyA{ 0x7E, 0x09, 0x09, 0x09, 0x7E }; For each letter, number or special char, I created them using 5 hexadecimal values. I wasted many time, but this font is mine – and it works as expected giving retro vibes to the game. Take a look at the function – notice how it converts a char to booleans : void xGameEngine::DrawString(gck::vi2d pos, std::string &str, gck::Pixel p, int32_t scale) { // our letters scale - only 5x7 pixels short fontX = 5; short fontY = 7; short pp = pos.x;

    if (stamp == nullptr) 
        DrawFont(fontX, fontY);
    // only lower case
    std::vector<unsigned char> byte;
    transform(str.begin(), str.end(), str.begin(), ::toupper);

    for (size_t i = 0; i < str.length(); i++)
    {
        if (str[i] == '

') { pos.y += (fontY + 1) * scale; pos.x = pp * scale; continue; }

        byte = checkChar(str[i]);

        for (int x = 0; x < fontX; x++)
            for (int y = 0; y < fontY; y++)
                if ((byte[x] >> y) & 0x1)
                    stamp->SetPixel(gck::vi2d(x, y), p); // this one !!!
                else
                    stamp->SetPixel(gck::vi2d(x, y), gck::BLANK);

        DrawSprite(pos, stamp, scale);
        pos.x += (fontX + 1) * scale; // shift to the next char
    }
}

The main difficulty had been to draw some non-regular polygons - the levels on which rebounds the colored balls. It was not too hard to draw a shape, just lines using a main **std::vector** with coordinates (x,y). I could use **std::pair**, but no... However I had no clue how I could fill shapes. There are many ways in order to fill a shape, but they wasted my tiny CPU resources - and as I said I had to use them carefully. Then I saw a raylight in the darkest fog! According to the same **std::vector** I implemented a function which checks all pixels and stores them if they are inside a non-regular polygon coordinates. However I called this function in the appCreate() section - so storing pixels during the first frame, drawing them in the appUpdate() section. The renderer was really smooth - and the resources were saved. // function in order to check if a point is inside a polygon bool xGameEngine::isPointInsidePolygon(std::vector vertices, gck::vi2d pos) { bool isInside = false; auto num_verts = vertices.size();

    for (size_t i = 0, j = num_verts - 1; i < num_verts; j = i++)
    {
        double x1 = vertices[i].x;
        double y1 = vertices[i].y;
        double x2 = vertices[j].x;
        double y2 = vertices[j].y;

        if (((y1 > pos.y) != (y2 > pos.y)) && (pos.x < (x2 - x1) * (pos.y - y1) / (y2 - y1) + x1))
            isInside = !isInside;
    }

    return isInside;
}

``` Sans titre.png

I really like the background under some games developed using Pico-8. It is like a canvas or a curtain. In my template, I had a function in order to apply a single color to each pixel so as to « clean » the main screen, but I wanted more...

In C++, we can use some ternary operator and I really like it. It is more than a simple condition variable – it allows us to develop some behavior according to a condition. I said to myself that using coordinates I could apply another color to the pixel if it has a particular feature. So I check pixels using this ternary operator :

So I got vertical lines according to the width. Then I found a tip. Simply I add +1 pixel to my game X resolution and it shifts everything on screen. Again I used a ternary operator. Finally I have some zebra lines. Sometimes a little thing can change everything...
``` // clear the entire sprite (or screen) according a specific color void xGameEngine::Clear(Pixel p, Pixel pp, int stripe) { int pixels = pDrawTarget->width * pDrawTarget->height; Pixel* m = GetDrawTarget()->GetData();

    for (int i = 0; i < pixels; ++i)
        stripe != 0 ? (i % stripe ? m[i] = p : m[i] = pp) : m[i] = p;
}

At the end, my template was able to draw many shapes. below you can see every declaration which are explicit : /////////////////////////////////////////////////////// // useful functions declarations /////////////////////////////////////////////////////// inline float map(int s, float a1, float a2, float b1, float b2); inline float computeDistance(const gck::vi2d& p1, const gck::vi2d& p2); inline float lerp(float a, float b, float f); inline vf2d GetVector(int32t nIndex, int32t fRadius, int32t fFactor); vi2d rotatePoint(gck::vi2d p, gck::vi2d axis, float angle); bool inTriangle(const gck::vi2d& p, const gck::vi2d& pos1, const gck::vi2d& pos2, const gck::vi2d& pos3); bool isPointInsidePolygon(std::vector vertices, gck::vi2d px); /////////////////////////////////////////////////////// // functions declarations in order to draw everything /////////////////////////////////////////////////////// virtual bool Draw(const gck::vi2d& pos, Pixel p = gck::WHITE); void DrawLine(const gck::vi2d& pos1, const gck::vi2d& pos2, Pixel p = gck::WHITE); void DrawFrame(const gck::vi2d& pos1, const gck::vi2d& pos2, Pixel p = gck::WHITE, bool fill = false); // simple rectangle void DrawRect(const gck::vi2d& pos, const int32t w, const int32t h, const int& angle = 0, Pixel p = gck::WHITE, bool fill = false); // rotation available void DrawTris(const gck::vi2d& pos1, const gck::vi2d& pos2, const gck::vi2d& pos3, const int& angle, Pixel p = gck::WHITE, bool fill = false); void DrawCircle(const gck::vi2d& pos, const int& fRadius, Pixel p = gck::WHITE, bool fill = false, const int& thickness = 0); void DrawPolygon(const gck::vi2d& pos, const int& fRadius, int32t fFactor, const int& angle, Pixel p = gck::WHITE, bool fill = false); // regular polygon void DrawPolygon(const std::vector vertices, Pixel p = gck::WHITE, bool fill = false); // no regular polygon void DrawSprite(const gck::vi2d& pos, Sprite* sprite, uint32t scale = 1); void DrawFont(int32t x, int32t y); // in order to write string std::vector checkChar(char c); void DrawString(gck::vi2d pos, std::string &str, gck::Pixel p = gck::WHITE, int32t scale = 1); void Clear(Pixel p, Pixel pp = VERYDARKGREY, int stripe = 0); // to clear screen each frame ``` And it is enough so as to create a game :golf:

Among all these functions I really like this one which allows to draw a regular polygon. One single function to draw a square, a pentagon, a hexagon, a heptagon or an octagon - really useful. Take a look at the way in order to fill them. Each edge has two points and the third is the center of the polygon. Then I fill these triangular shapes (as you can see, there is a function to rotate each shape or point) : ``` // check if a point is inside a triangle (used to fill regular polygons) bool xGameEngine::inTriangle(const gck::vi2d& p, const gck::vi2d& pos1, const gck::vi2d& pos2, const gck::vi2d& pos3) { int s = (pos1.x - pos3.x) * (p.y - pos3.y) - (pos1.y - pos3.y) * (p.x - pos3.x); int t = (pos2.x - pos1.x) * (p.y - pos1.y) - (pos2.y - pos1.y) * (p.x - pos1.x);

    if ((s < 0) != (t < 1) && s != 0 && t != 1)
        return false;

    int d = (pos3.x - pos2.x) * (p.y - pos2.y) - (pos3.y - pos2.y) * (p.x - pos2.x);

    return d == 0 || (d < 0) == (s + t <= 0);
}

(...)

// draws a regular polygon according to a single point, a factor and a radius
void xGameEngine::DrawPolygon(const gck::vi2d& pos, const int& fRadius, int32_t fFactor, const int& angle, Pixel p, bool fill)
{
    if (fFactor > 8) 
        fFactor = 8; // beyond 8 it's almost a circle no ?
    // sometimes a point at the center is required
    if (fill) 
        Draw(pos, p);

    for (int i = 0; i < fFactor; i++)
    {
        gck::vf2d vPoint = rotatePoint(GetVector(i, fRadius, fFactor) + pos, pos, static_cast<float>(angle));
        gck::vf2d pPoint = rotatePoint(GetVector(i - 1, fRadius, fFactor) + pos, pos, static_cast<float>(angle));

        fill ? DrawTris(pos, vPoint, pPoint, 0, p, true) : DrawLine(vPoint, pPoint, p);
    }
}

``` I could continue to explain many, many, many things about this template - and especially how I developed the physics for Shabodoo, but I guess that for now it is enough. You can try Shabadoo - and of course you can rate it. Leave a comment - I really appreciate it, and then I have a link in order to rate your own project. I hope that you liked those explanations. If I can improve some parts of my template I will share it on GitHub, but right now it is still bugged and not clearly coded. Stay tuned ++

https://ldjam.com/events/ludum-dare/51/shabadoo