[DevLog][Cute&Demake] Incipient

Demake of Flow (PS4)

In Flow you control a small organism from a top-down perspective and you move around the screen eating other organisms. Single-cell organisms help replenish your health while bigger might try and attack you too. When you defeat a bigger organism you can then eat the core pieces to increase your size. Each creature is simply a series of small pieces connected together in a chain. In Flow there is no dieing, if you all of your core pieces get destroyed you simply jump up to the previous stage and slowly regenerate.

Here’s a video showing off the gameplay of Flow for those who haven’t played it:

So how will this demake for the Pokitto be done?

To start out I figured out a fast-ish way to draw triangles to the screen using a 16bpp scanline with alpha support (I can comfortably have about 128 while maintaining good FPS and 256 is still somewhat playable if a bit slow).

Then I figured out a nice Procedural Music system that will generate some never ending relaxing tunes where I can adjust the pacing (how fast it is) and tone (how chaotic the melodies are). This way the pacing and tone can be set based on the player’s health and proximity to nearby enemies.

From there I wanted to create a pure triangle-based font that is crisp, but still clearly made of triangles:

Next was to design my first basic organism that features a head with an “mouth” that can animate between open/closed (two models with identical number of vertices/triangles that it interpolates between) and a body piece to create a chain:
image

Finally I didn’t want a static background color/image so I found a decent tutorial on a palette-based plasma effect (creates the image once then just rotates the palette):

Now that I’ve got my font, I’ve got my plasma, and I’ve got my pieces for a creature it was time to put everything together and get something moving around the screen:
Demo
The organism can be controlled with either the DPad or the JoyHat. The way the movement works is the head piece slowly rotates to face in the direction you wish to move (diagonals are supported for the DPad) while also slowly moving forward in the direction it’s currently facing (giving a shallow curved motion). The pieces of the chain use a Vector Slurp method to interpolate between the direction they’re currently facing and the direction their parent piece is facing (rotating faster if you’re moving).

The plan is that the player would progress through multiple stages getting bigger and bigger (more pieces in their chain) and when you beat the final stage for a creature you start back at the title and unlock a new creature to choose from. This makes it somewhat easy enough to at least get a basic game made with just one organism while allowing me to expand it with new organisms (possibly loaded from SD to allow user made organisms) throughout the jam as long as I still have time available.

EDIT: Here’s a demo bin so people can get a feel for how the movement will work.
Incipient.bin (104.6 KB)

10 Likes

Sounds like a very nice idea. Great to have a game where my Pokitto can use a joyhat, too.

I guess you need the vector font bc you are drawing directly on screen and direct text draw on it would cause flicker?

1 Like

Actually I could have gone with a 1bpp font easily enough since I’m using scanline rendering, but thought it might be neater to try and do a triangle based font. I might touch up some of the letters though.

1 Like

So doing some more testing and I’ve figured out a nice “wander” mode for the organisms. At first I picked a random point and then had the organism slowly rotate towards that point while moving forward. Once it reached that point it would pick a new one. This had issues where often the organism couldn’t ever reach the point so I added a delay before automatically picking a new point to move towards. However, I forgot to update the last time the point was set and so it was getting a new location each frame, but this ended up giving a lot more natural “wander” effect as opposed to moving towards a clear destination before moving off elsewhere.
Incipient.bin.1

Next up I want to add line drawing routines and I think I figured out a way to do them using the Bresenham algorithm but with scanlines. If I’m correct this method could also drastically speed up the triangle rendering routine as well.

Here’s a piece of sudo code for the Bresenham Line Algorithm from Wikipedia:

plotLine(x0, y0, x1, y1)
    dx = x1 - x0
    dy = y1 - y0
    D = 2*dy - dx
    y = y0

    for x from x0 to x1
        plot(x,y)
        if D > 0
            y = y + 1
            D = D - 2*dx
        end if
        D = D + 2*dy

So all I’d need to do is store x, dx, dy, and D instead of the line’s original vertex coordinates. Then each scanline between the lines start and end y-coordinates it would do something like:

drawLine(uint32_t *scanline, const int &y, InternalLine &line)
{
  uint32_t *pixel = scanline + line.x;
  while (line.D < 0)
  {
    *pixel++ = line.color;
    line.D += 2 * line.dy;
  }
  line.D -= 2 * line.dx;
}

This same method could speed up the triangles by having separate flat-top and flat-bottom triangles (irregular triangles can easily be split into one flat-top and one flat-bottom triangle). From there it would simply step two lines for each triangle finding the smallest and largest values of X and then fill between those.

Thought I’d share this potential speedup to see if anyone has any further input.

I’ve uploaded the code to github now, but be forewarned the code is very messy at the moment. I tend to focus on getting a working concept first since this often involves more than a few rewrites (three in this case). Then I’ll go back and organizing the code to make it easier to follow and easier to expand upon (which is what I’m doing right now with the Types.h file).

6 Likes

Oh, flow is among my most loved games of all times - great idea.

2 Likes

Spent the day getting the line drawing routine working and then tried changing the triangle routine to draw two lines and filling between them but it ended up being slower than my current method.

The issue I ran into with the lines is that the first formula mentioned on the wikipedia page for the Bresenham algorithm only works for shallow lines where x will always step by either 0 or 1 and y will always step by 0 or 1. To remedy this I sorted the two points by their y-coordinate then used the later formula:

Sudo Code
plotLine(int x0, int y0, int x1, int y1)
    dx =  abs(x1-x0);
    sx = x0<x1 ? 1 : -1;
    dy = -abs(y1-y0);
    sy = y0<y1 ? 1 : -1;
    err = dx+dy;  /* error value e_xy */
    while (true)   /* loop */
        plot(x0, y0);
        if (x0 == x1 && y0 == y1) break;
        e2 = 2*err;
        if (e2 >= dy) /* e_xy+e_x > 0 */
            err += dy;
            x0 += sx;
        end if
        if (e2 <= dx) /* e_xy+e_y < 0 */
            err += dx;
            y0 += sy;
        end if
    end while

Since my lines are already sorted by their y-coordinate then sy will always be 1 so I can optimize that out. Also err is always incremented by either dx or dy but only err*2 is ever used so I baked that into err instead of creating another variable. Finally I discovered that for some lines it would skip over the end point and then if (x0 == x1 && y0 == y1) will always be false. To fix that I added an additional check to the if (e2 <= dx) as if (e2 <= dx && y0 != y1).

Optimized Sudo Code
plotLine(int x0, int y0, int x1, int y1)
    dx =  abs(x1-x0);
    dy = -abs(y1-y0);
    sx = x0<x1 ? 1 : -1;
    err = (dx+dy)*2;
    while (true)   /* loop */
        plot(x0, y0);
        if (x0 == x1 && y0 == y1) break;
        if (err >= dy) /* e_xy+e_x > 0 */
            err += dy*2;
            x0 += sx;
        end if
        if (err <= dx) /* e_xy+e_y < 0 */
            err += dx*2;
            ++y0;
        end if
    end while

To translate that to a scanline filler I simply store the current coordinates, end coordinates, dx, dy, sx, and err in an object and then for each scanline I do the following:

Line Filler Routine
struct InternalLine
{
  int16_t x;
  int16_t y;
  int16_t dx;
  int16_t sx;
  int16_t dy;
  int16_t err;
  int16_t endX;
  int16_t endY;
  uint16_t color;
  uint8_t alpha;
};
void drawLines(uint16_t *scanline, int y)
{
  InternalLine *line = lines;
  int16_t start, end;
  uint16_t *pixel;
  for (int i = 0; i < numLines; ++i, ++line)
  {
    pixel = scanline + line->x;
    while (line->y == y) //as long as we're plotting points on the current scanline
    {
      if (line->x >= 0 && line->x < 220) //only plot coordinates that are actually on the screen
        *pixel = alphaBlendRGB565(line->color, *pixel, line->alpha);
      if (line->x == line->endX && line->y == line->endY) break;
      if (line->err >= line->dy)
      {
        line->err += line->dy * 2;
        line->x += line->sx;
        pixel += line->sx;
      }
      if (line->err <= line->dx && line->y != line->endY)
      {
        line->err += line->dx * 2;
        ++line->y;
      }
    }
  }
}

For my triangles so far the fastest method I’ve found is to simply store the 3 vertex coordinates and then do the following for each scanline:

Triangle Line Filler
bool Display::getIntersect(int16_t &x /*OUT*/, int16_t y, int16_t x1, int16_t y1, int16_t x2, int16_t y2)
{
  if (y1 < y && y2 < y)
    return false;
  if (y1 > y && y2 > y)
    return false;
  if (y1 == y2) //shouldn't receive this value
    return false;
  x = x1 + (x2 - x1) * (y - y1) / (y2 - y1);
  return true;
}

void Display::getTriangleStartEnd(int16_t &start /*OUT*/, int16_t &end /*OUT*/, int16_t y, int16_t x1, int16_t y1, int16_t x2, int16_t y2, int16_t x3, int16_t y3)
{
  if (y1 == y)
  {
    if (y2 == y)
    {
      start = x1;
      end = x2;
    }
    else if (y3 == y)
    {
      start = x1;
      end = x3;
    }
    else
    {
      start = x1;
      end = x1; //in case p2->p3 doesn't intersect scanline
      getIntersect(end, y, x2, y2, x3, y3);
    }
  }
  else if (y2 == y)
  {
    if (y3 == y)
    {
      start = x2;
      end = x3;
    }
    else
    {
      start = x2;
      end = x2; //in case p3->p1 doesn't intersect scanline
      getIntersect(end, y, x3, y3, x1, y1);
    }
  }
  else if (y3 == y)
  {
    start = x3;
    end = x3; //in case p1->p2 doesn't intersect scanline
    getIntersect(end, y, x1, y1, x2, y2);
  }
  else
  {
    if (getIntersect(start, y, x1, y1, x2, y2))
    {
      if (!getIntersect(end, y, x2, y2, x3, y3))
        getIntersect(end, y, x3, y3, x1, y1);
    }
    else
    {
      getIntersect(start, y, x2, y2, x3, y3);
      getIntersect(end, y, x3, y3, x1, y1);
    }
  }
  if (start > end)
    swapWT(int16_t,start,end);
  if (start < 0)
    start = 0;
  if (end > 219)
    end = 219;
}

void Display::drawTriangles(uint16_t *scanline, int y, uint8_t &max)
{
  InternalTriangle *triangle = triangles;
  int16_t start, end;
  uint16_t *pixel;
  for (int i = 0; i < numTriangles; ++i, ++triangle)
  {
    if (y < triangle->firstLine || y > triangle->lastLine)
      continue;
    getTriangleStartEnd(start, end, y, triangle->p1[0], triangle->p1[1], triangle->p2[0], triangle->p2[1], triangle->p3[0], triangle->p3[1]);
    pixel = scanline + start;
    for (int16_t x = start; x <= end; ++x, ++pixel)
      *pixel = alphaBlendRGB565(triangle->color, *pixel, triangle->alpha);
  }
}

Now that I have all 4 of the basic primitives I need implemented I can finish organizing the code more and start implementing the actual game.

5 Likes

The fun stuff!

1 Like

Isn’t that how one runs VSCode with admin permissions on Linux? :P

4 Likes

Ok time for an update. I’ve been struggling to think much lately due to another major heat wave we had. Finally figured out how I want the core gameplay mechanics to work.

Here’s a GIF of my menu transitions (obviously some loss of quality):
Demo

The idea with the menu system is that you eat the creature according to where you want to go (blue for scores menu and red to start the game).

For the gameplay I plan on making various mouth models, body models, and tail models and have enemies of various lengths using a random mouth, tail, body piece, and color. The difficulty will slowly increase as you’re score increases. Enemies will continuously spawn off-screen and make their way to the screen (the player can go off-screen but no attacking can be done while off-screen). I have an indicator (solid circle of the creature’s color) along the edge of the screen indicating where everything is at when off-screen.

For the combat I planned on each body piece having a state. Normally the body pieces will have a solid color outline (100% opacity) and semi-transparent (50% opacity) filled shapes. When a body piece is attacked it will drop to 0% opacity for the filled shapes (leaving just an outline) and if attacked again it would go into the Breaking state where the outline disappears and the remaining pieces in the chain close the gap (by simply scaling the invisible piece down to 0 before removing it entirely). When a body piece is consumed the head piece of the attacking creature goes into the Digesting state where the piece will slowly increase to 100% opaque filled shapes and then transfer the Digesting state to the next piece in the chain and slowly return to 50% opacity. When a regular body piece is digested it will heal the first damaged body piece ending the digestion phase (piece slowly returns from 0% opacity to 50% opacity). When a creature has no remaining body pieces then one more attack will destroy that creature and digest it. When digesting a creature the digestion will travel down to the tail where it will “assimilate” the digested creatures body shape and color (the player will gain a new piece matching the shape/color of the enemy creature). If the player’s body size is maxed out then bonus points will be awarded.

The difficulty will scale up as your score increases starting with only one single-piece creature at a time, then increase the creature’s size as well as slowly increasing the maximum number of creatures at one time.

As you take damage the “pacing” will go between 0 (calm) and 3 (fast) based on how many body pieces are damaged. As more enemies appear on screen the “tone” will go between 0 (methodical) and 3 (chaotic). The “pacing” changes the tempo and note duration of the music as well as the speed of the background animation. The “tone” changes the skipping of the music (jumping more than 1 note) and changes the color of the background animation from a soft blue to a dark red.

Enemy creatures will go between one of three states (wandering, chasing, and fleeing). In the wandering they will move around the screen aimlessly. In the chasing state they will pick a target body piece of the player and chase after that piece, but will give up the chase after a short while. In the fleeing state enemies will attempt to move off screen for a short duration before returning in the wandering state.

What’s left to implement:

  • Spawning enemies
  • Fine-tune combat mechanics
  • Finalize the body part states
  • Make more creature shapes
  • Highscore entry

VERY IMPORTANT NOTE: @torbuntu is expressly FORBIDDEN from having a higher score than me in MY GAME :laughing: :stuck_out_tongue:

6 Likes

What mechanics have been put in place to ensure this eventuality? :eyes:

3 Likes

My thumbs have healed a bit :laughing:

1 Like

Drat. An advantage then.

2 Likes

Looks stunning! I also like the Vectrex wipe it has :grinning: I am happy that you decided to make also the fonts with vectors.

2 Likes
if(player != torbuntu)
	score += 10;
4 Likes

Bravo, I completely love it, especially how you create the triangle font to exploit the fact that you already have the triangle drawing algorithm. It makes the game beautifully self contained, recycling code and staying independent of third party fonts and extra code for it while also giving the game a characteristic look. That’s some demo-level creative thinking. I’ve also seen this in a game called Azimuth, it’s also great because font licenses introduce mess, this is just KISS.

As I understand by scanline you mean no screen buffer, just top-dow left-right direct screen drawing? And for each pixel you’re testing whether it covers every triangle in the scene? That could be accelerated by dividing a screen into regions, let’s say 16 * 16, then create a bit array which for each region would say whether it’s empty or not, then you could quickly skip each empty region. (Or in a more complex case have the regions contain lists of triangles to test). But perhaps you’re already doing something like that? EDIT: Hmmm I think probably that the scanline triangle algorithm is already precomputing something similar before rendering each line… Pretty interesting to be rediscovering these ancient algorithms.

7 Likes

Getting ready to upload the release version.

Thought I’d go into a little technical details regarding my render pipeline.

That is correct. There’s no screen buffer, but unlike TAS which uses an 8-bit scanline (uint8_t scanline[220]) mine uses a 16-bit one (uint16_t scanline[220]).

Not quite as that would be painfully slow to test each pixel against each shape (I have points, lines, circles, and triangles). So the way it works is each shape defines it’s first and last scanlines and each scanline has a maxShapes value to help optimize rendering.

Before I dive into the shapes I have a 16-bit alpha blending routine borrowed from Adafruit’s graphics library:

Alpha Blending Routine
 //uint16_t converted to uint32_t temporarily so as to separate each color and give room for overflow.
uint16_t Display::alphaBlend(uint32_t fg, uint32_t bg, uint8_t alpha)
{
  //Fully transparent so return background color
  if (alpha == 0)
    return (uint16_t)bg;
  //Fully opaque so return foreground color
  if (alpha == 32)
    return (uint16_t)fg;
  fg = (fg | fg << 16) & 0x07e0f81f;
  bg = (bg | bg << 16) & 0x07e0f81f;
  bg += (fg - bg) * alpha >> 5;
  bg &= 0x07e0f81f;
  return (uint16_t)(bg | bg >> 16);
}

From there points are obviously simple:

Draw Point
void Display::drawPoint(uint16_t *scanline, int y, InternalPoint &point, uint16_t color, uint8_t alpha)
{
  if (y == point.y)
    scanline[point.x] = alphaBlend(color, scanline[point.x], alpha);
}

Lines are semi-complex as they use Bresenham’s Line Algorithm but they store the current point, dx, dy, sx, and end point (more RAM usage but much faster frame rate):

Draw Line
struct InternalLine
{
  InternalPoint p;
  InternalPoint end;
  InternalPoint d;
  int16_t sx;
  int16_t err;
};
void Display::drawLine(uint16_t *scanline, int y, InternalLine &line, uint16_t color, uint8_t alpha)
{
  uint16_t *pixel = scanline + line.p.x;
  while (line.p.y == y)
  {
    if (line.p.x >= 0 && line.p.x < 220)
      *pixel = alphaBlend(color, *pixel, alpha);
    if (line.p.x == line.end.x && line.p.y == line.end.y) break;
    if (line.err >= line.d.y)
    {
      line.err += line.d.y * 2;
      line.p.x += line.sx;
      pixel += line.sx;
    }
    if (line.err <= line.d.x && line.p.y != line.end.y)
    {
      line.err += line.d.x * 2;
      ++line.p.y;
    }
  }
}

Circles aren’t terribly complicated as each line starts with a width equal to the circle’s radius and then shrinks both ends until they are within the circle:

Draw Circle
void Display::drawCircle(uint16_t *scanline, int y, InternalCircle &circle, uint16_t color, uint8_t alpha)
{
  int16_t start = circle.center.x - circle.radius;
  int16_t end = circle.center.x + circle.radius;
  int16_t dist2 = (circle.center.y - y) * (circle.center.y - y);
  int16_t r2 = circle.radius * circle.radius;
  uint16_t *pixel;
  while ((circle.center.x - start) * (circle.center.x - start) + dist2 > r2)
  {
    ++start;
    --end;
  }
  if (start < 0)
    start = 0;
  if (end > 219)
    end = 219;
  pixel = scanline + start;
  for (int16_t x = start; x <= end; ++x, ++pixel)
    *pixel = alphaBlend(color, *pixel, alpha);
}

Triangles is where things get a little interesting as I played around with several ways to do them and this is the fastest method I’ve found:

Draw Line
struct InternalPoint
{
  int16_t x;
  int16_t y;
};
struct InternalTriangle
{
  InternalPoint p1;
  InternalPoint p2;
  InternalPoint p3;
};

bool Display::getIntersect(int16_t &x /*OUT*/, int16_t y, const InternalPoint &p1, const InternalPoint &p2)
{
  if (p1.y < y && p2.y < y)
    return false;
  if (p1.y > y && p2.y > y)
    return false;
  if (p1.y == p2.y) //shouldn't receive this value
    return false;
  x = p1.x + (p2.x - p1.x) * (y - p1.y) / (p2.y - p1.y);
  return true;
}

void Display::getTriangleStartEnd(int16_t &start /*OUT*/, int16_t &end /*OUT*/, int16_t y, InternalTriangle &triangle)
{
  if (triangle.p1.y == y)
  {
    if (triangle.p2.y == y)
    {
      start = triangle.p1.x;
      end = triangle.p2.x;
    }
    else if (triangle.p3.y == y)
    {
      start = triangle.p1.x;
      end = triangle.p3.x;
    }
    else
    {
      start = triangle.p1.x;
      end = triangle.p1.x; //in case p2->p3 doesn't intersect scanline
      getIntersect(end, y, triangle.p2, triangle.p3);
    }
  }
  else if (triangle.p2.y == y)
  {
    if (triangle.p3.y == y)
    {
      start = triangle.p2.x;
      end = triangle.p3.x;
    }
    else
    {
      start = triangle.p2.x;
      end = triangle.p2.x; //in case p3->p1 doesn't intersect scanline
      getIntersect(end, y, triangle.p3, triangle.p1);
    }
  }
  else if (triangle.p3.y == y)
  {
    start = triangle.p3.x;
    end = triangle.p3.x; //in case p1->p2 doesn't intersect scanline
    getIntersect(end, y, triangle.p1, triangle.p2);
  }
  else
  {
    if (getIntersect(start, y, triangle.p1, triangle.p2))
    {
      if (!getIntersect(end, y, triangle.p2, triangle.p3))
        getIntersect(end, y, triangle.p3, triangle.p1);
    }
    else
    {
      getIntersect(start, y, triangle.p2, triangle.p3);
      getIntersect(end, y, triangle.p3, triangle.p1);
    }
  }
  if (start > end)
    swapWT(int16_t,start,end);
  if (start < 0)
    start = 0;
  if (end > 219)
    end = 219;
}

void Display::drawTriangle(uint16_t *scanline, int y, InternalTriangle &triangle, uint16_t color, uint8_t alpha)
{
  int16_t start, end;
  uint16_t *pixel;
  getTriangleStartEnd(start, end, y, triangle);
  pixel = scanline + start;
  for (int16_t x = start; x <= end; ++x, ++pixel) //TODO enable alpha blending
    *pixel = alphaBlend(color, *pixel, alpha);
}

All of the shapes are passed with a Transform type:

struct Transform
{
  VectorF offset;
  VectorF xAxis;
  VectorF yAxis;
};

This way all the models can be stored in FLASH space and transformed to screen space based on individual locations/rotations. To optimize the routines I’m using a 2D matrix in the form of the localized X-axis and Y-axis plus offset.

The movement mechanics were rather interesting to figure out and came out incredibly smoothly even when using the dpad. To accomplish this I first determine which direction the player wants to go based on the combination of dpad buttons (diagonals are implemented too). From there I do a vector slerp (spherical linear interpolation) between the player’s current rotation and the desired direction (each frame it steps 0.15 closer to the desired direction). Then I just move the player forward along their y-axis causing a smooth curved movement.

Body parts along the chain are updated based on the model’s link coordinate (location where next model’s origin should be). Then they’re slerped towards the previous piece’s direction so they slowly rotate to catch up.

I’ve updated github with the latest version for any who wish to examine the source (look for the link in the Game’s page).

5 Likes

Aaah so you have a line buffer, that’s a nice middle way between a full screen buffer and a completely sequential rending… yeah, that looks like a very good approach. Like maybe some small optimization could be achieved in the alphaBlend function as that is what you’re calling very often, so each shape might have some values precomputed, e.g. the (fg | fg << 16) & 0x07e0f81f (like what you’re doing with the dx and dy variables). But it seems pretty well programmed already, thanks for sharing this insight.

3 Likes