Shabadoo - a last devlog
https://ldjam.com/events/ludum-dare/51/shabadoo

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;
}
```

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