[Game]Planet Escape [wip]

adventure
game
release

#1

planet

Planet Escape is inspired by 2D building, crafting, and exploration games. Right now its procedurally generated world features some different materials and ores, some critters to find, and a small amount of craftable tiles.

Now with a nice parallax scrolling background. Many thanks to Vampirics for the background tiles!

Original Post

It’s been quite a while since I last posted on here, but I happened to start working on this game in some free time after playing some Starbound. I wanted to make a scaled-down version of the exploring, digging, and building you can do in games like these. It’s quite a long way from done, but I figured I should share an early version instead of waiting until it’s done (who knows when that will be—I have a lot of side projects and not enough time, so don’t hold your breath).

I eventually would like to add crafting, enemies, dungeons, and more stuff, but for now it’s possible to run around the world, destroy blocks, and place the blocks down again.

I have been developing this for the computer using SDL for porting compatibility, and I was able to convert it over for the Pokitto in only a few hours. The Pokitto version has a visual glitch that the computer version doesn’t (vertical stripes of tiles missing), but other than that it seems to work surprisingly well.

In this early prototype, there are a few bugs I’m aware of:

  • I wrap the world to the left and right so it appears endless, but the seam between the two isn’t correctly implemented. If you get some weird glitches trying to destroy blocks, or fall through the floor, it’s probably this. mostly fixed
  • Trying to destroy blocks outside the world bounds (above or below) will write outside the world buffer and corrupt memory, freezing the Pokitto. I’ll add proper bounds checks for this later. fixed!
  • A small percentage of the time, the Pokitto freezes when the game starts, or just shows a black screen. I don’t know why this is.
  • There’s a weird visual glitch where sometimes a single column of pixels on the left and right are missing from the world tiles, and it appears to be influenced by how you move. I’m not sure what’s causing this. partially fixed

The game is in a very early state, so let me know if there’s something you might want in the game. I can’t promise I will implement a suggestion, but I appreciate feedback!

New version now available with crafting!

Current version
planet_escape_0.1.8.bin (50.0 KB)

Old versions
planet_escape_0.1.1.bin (46.6 KB)
planet_escape_0.1.0.bin (45.9 KB)
planet_escape_0.0.2.bin (43.4 KB)
planet_escape_0.0.1.bin (42.7 KB)

Controls
In main gameplay:
Left/Right: move character
Up/Down: Shift cursor up or down
A(hold): destroy block when no block is selected
A(press): place block when a block is selected
A(tap when cursor is over workbench): open workbench menu
B: jump
C: menu
In inventory menu:
Arrows: move menu cursor
A: select block
B: select nothing
C: back to game
In crafting menu:
Arrows: move menu cursor
A: craft selected item (if you have the materials)
C: back to game
In chest menu:
Arrows: move menu cursor
A: transfer item between chest/inventory
B: switch between chest and inventory
C: back to game


#2

downloaded it, and I will post about it tomorrow!

Edit, I think I like it better than terraria.


#3

How can I quickly reproduce the error?


#4

Tap left or right a few times on a flat area, and a vertical one-pixel stripe should appear of mostly the background color on the left and right side of the screen. It appears to correspond with the rightmost pixels of the leftmost and rightmost tiles.


#5

It reminds me of something similar that used to show up in a lot of old nes games


#6

For me to debug this, I’n need a bit of code that produces the error sothat I can step through it with a debugger. If you have a “code sniplet” that produces the error OR you can provide an source code link I promise to take a look


#7

I’m using a shim layer for compatibility, and the function calls in the game look a bit different than Pokitto library calls because of this, so I had to try to extract the tile drawing and camera movement to a minimal working example. Fortunately, it still results in the same error.

The error didn’t appear until I added the code for friction. It looks like it’s something to do with truncation from integer division, depending on whether the x coordinate is even or odd. That’s just my guess, and it’s probably something I’m doing wrong, but it didn’t appear in the SDL version.

It seems like the problem is to do with the expression (w-x/8)*8-x%8 for the x coordinate to drawBitmap.

main.cpp
#include "PokittoCore.h"
#include "PokittoPalettes.h"
#include "tiles.h"

Pokitto::Core gb;

#define VEL_MAX (2)
#define FRICTION 1

#define WORLD_SIZE 128
uint8_t world[WORLD_SIZE][WORLD_SIZE];

int16_t vx = 0;
int16_t camx = 32;
int16_t camy = 8;

void world_step();
void world_draw(int16_t x, int16_t y);

void setup(){
  gb.begin();
  gb.display.loadRGBPalette(palettePico);
  gb.display.setColor(0,0);
  gb.display.setInvisibleColor(0);
  gb.setFrameRate(30);
  for( uint8_t i = 0; i < WORLD_SIZE; i++ ){
    for( uint8_t j = 0; j < WORLD_SIZE; j++ ){
      world[i][j] = 1;
    }   
  }
}

void loop(){
  if( gb.update() ){
    gb.display.clear();
    gb.display.setColor(12);
    gb.display.fillRect(0,0,110,88);
    gb.buttons.pollButtons();
    world_step();
    world_draw(camx,camy);
  }
}

void world_step(){
  if( vx > 0 ) vx -= FRICTION;
  if( vx < 0 ) vx += FRICTION;
  if( gb.buttons.repeat(BTN_LEFT,1) ){
    vx -= 2;
    if( vx < -VEL_MAX ) vx = -VEL_MAX;
  }else if( gb.buttons.repeat(BTN_RIGHT,1) ){
    vx += 2;
    if( vx > VEL_MAX ) vx = VEL_MAX;
  }
  
  camx += vx;    
}

void world_draw(int16_t x, int16_t y){
    int16_t w,h;
    for( h = (y/8); h < (y/8)+16; h++ ){
      for( w = (x/8); w < (x/8)+16; w++ ){
        if( h >=0 && h < WORLD_SIZE ){
          if( w < 0 )
            gb.display.drawBitmap( (w-x/8)*8-x%8, (h-y/8)*8-y%8, &tiles[world[h][w+WORLD_SIZE]*34]);
          else
            gb.display.drawBitmap( (w-x/8)*8-x%8, (h-y/8)*8-y%8, &tiles[world[h][w%WORLD_SIZE]*34]);
        }
      }
    }
}

int main(int argc, char** argv){
  setup();
  while( gb.isRunning() ){
    loop();
  }
  return 0;
}


tiles.h

const uint8_t tiles[] = {
8,8,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,

8,8,
68,68,68,68,
68,69,68,68,
68,68,84,68,
68,68,68,68,
68,68,68,68,
69,68,68,84,
68,68,69,68,
68,68,68,68,

8,8,
187,187,187,187,
51,51,51,51,
67,67,52,52,
68,67,68,68,
68,68,68,68,
69,68,68,84,
68,68,69,68,
68,68,68,68,

8,8,
244,244,68,245,
244,68,79,69,
244,68,79,69,
244,244,68,69,
244,79,68,69,
244,79,79,69,
244,244,79,69,
244,244,68,69,

8,8,
51,51,51,51,
51,59,51,51,
51,179,59,179,
51,51,59,51,
51,51,51,51,
51,187,51,51,
51,179,51,179,
51,51,51,51,

8,8,
68,68,68,68,
68,74,68,68,
68,73,148,68,
68,68,68,68,
68,68,68,164,
74,148,74,148,
73,68,73,68,
68,68,68,68,

8,8,
68,68,68,68,
68,102,68,68,
68,69,84,68,
68,68,68,68,
68,68,68,100,
68,100,70,84,
70,84,68,68,
68,68,68,68,

8,8,
68,68,68,68,
68,122,68,68,
71,164,68,68,
68,68,68,68,
68,68,68,116,
68,119,71,164,
68,74,68,68,
68,68,68,68,

8,8,
68,68,68,68,
68,116,75,68,
68,76,68,68,
78,196,228,180,
68,180,68,68,
68,68,68,196,
76,68,235,68,
68,68,68,68,
};

My_settings.h

#define PROJ_HIRES 0
#define PROJ_ENABLE_SOUND 0


#8

Aha! Then it was not me! Because I would have:

(w-x>>3)<<3-x&7

:wink: (yes yes I know ints need to be signed. Just a joke.)


#9

Haha, I see you’re a big fan of bitwise logic. I probably would have used the division/multiplication/modulus even if the variables were unsigned, just because I just was thinking about it that way.

I have been experimenting with the drawing code, and it turns out I was overcomplicating how I was shifting the tiles. Instead of (w-x/8)*8-x%8 I can simply use w*8-x with identical results. However, the visual glitch still appears.

Thanks for such a high compliment, although I’m not sure how it deserves that, heh. Not much “game” in the game just yet.


#10

Just posted an updated version in the main post. I fixed some bugs, but unfortunately not the bug we have been talking about. I don’t think the problem is in the snippet I was looking at before. At least the minimal example I included in my earlier post isn’t very big, so the problem must be with the basic world drawing code, but I’m seriously not sure what’s causing it.

The updated version also adds a rock tile and a rock layer to world generation. Plus there’s a little secret somewhere along the surface for anybody who wants to go find it!


#11

Add saving to this as it is and my daughter can play this forever already. :wink: She just loves to build out of blocks. And I do too :sweat_smile:

Oh and she loved that thing on the surface.


#12

When I can save it ?


#13

There’s no saving yet in the game.


#14

Fun fact: “For negative a, the value of a >> b is implementation-defined”.

I agree.
One of my golden rules when programming is “express your intent”, which for operating on integers means:

  • If you intended to treat an integer as a string of bits, use bitwise operators.
  • If you intended to operate on an integer as if it were a number, use arithmetic operators.
    • Let the compiler decide when and where to replace arithmetic operators with bitwise operators.

I’d be happy to have a look at the code when you’re ready to post it all.

A few small tips:

  • Using int16_t for arguments and locals doesn’t actually give you any benefit over int because the processor is 32 bit.
  • drawBitmap has an overload that accepts a ‘frame’ parameter which you could use instead of your tile array. (In fact, I suspect you might already be trying to do this, if tiles is actually const uint8_t tiles[].)
  • You don’t need to create an instance of Pokitto::Core, you can just use static functions, e.g. gb.display.drawBitmap becomes Pokitto::Display::drawBitmap

Also, but your world drawing function can be greatly simplified:

void world_draw(int x, int y)
{
	// Precalculate and cache loop bounds
	const int hStart = (y / 8);
	const int hEnd = (hStart + 16);

	const int wStart = (x / 8);
	const int wEnd = (wStart + 16);

	// Precalculate and cache loop offset;
	const int xOff = x + (x % 8);
	const int yOff = y + (y % 8);

	for(int h = hStart; h < hEnd; ++h)
		for(int w = wStart; w < wEnd; ++w)
		{
			if((h < 0) || (h >= WORLD_SIZE))
				continue;
			
			const std::size_t width = (w < 0) ? (w + WORLD_SIZE) : (w % WORLD_SIZE);
			Pokitto::Display::drawBitmap((w - xOff), (h - yOff), &tiles[world[h][width] * 34]);			
		}
}

(I think that works, but I can’t guarantee that I haven’t broken something.)


#15

Sorry, I didn’t mean that the code doesn’t contain the problem, I think I was just focusing on the wrong part. The working example I posted does still exhibit the visual error. I changed the step and draw functions so that now the error appears by simply tapping left or right.

void world_step(){
  if( gb.buttons.repeat(BTN_LEFT,1) ){
    vx = -1;
    if( vx < -VEL_MAX ) vx = -VEL_MAX;
  }else if( gb.buttons.repeat(BTN_RIGHT,1) ){
    vx = 1;
    if( vx > VEL_MAX ) vx = VEL_MAX;
  }
  
  camx += vx;    
}

void world_draw(int16_t x, int16_t y){
    int16_t w,h;
    for( h = (y/8); h < (y/8)+16; h++ ){
      for( w = (x/8); w < (x/8)+16; w++ ){
        if( h >=0 && h < WORLD_SIZE ){
          if( w < 0 )
            gb.display.drawBitmap( w*8-x, h*8-y, &tiles[world[h][w+WORLD_SIZE]*34]);
          else
            gb.display.drawBitmap( w*8-x, h*8-y, &tiles[world[h][w%WORLD_SIZE]*34]);
        }
      }
    }
}

Oops, I think I had assumed it was 16-bit. I had been considering the possibility of porting it to 16/8-bit systems, so I might keep it that way for now.

Thanks, I might try that.

I’ll also take a closer look at adding your improvements to the drawing function.

Glad you both like it! I might try to add saving soon, but I have to figure out how I want to set up the menu option for that first. I want to keep the menu simple and quick to use.


#16

If VEL_MAX is (2) and -VEL_MAX is -(2) then those tests are redundant because you’re setting vx to -1 or 1, so it will never be -2 or 2 by the time you do the tests.

The compiler probably even figures that out and removes the tests itself.

In that case, consider using int_fast16_t.
That will let the compiler use a 32-bit value if it thinks it will be faster.

If you want to question any of my changes, feel free to do so.


#17

Those comparisons were there from before I simplified the example. I wasn’t setting vx to 1 and -1 before, but this simplified version will show the graphical glitch on the pokitto more consistently. The code I have has some other artifacts that do nothing from the code I am actually using for the game, but that’s because I was simplifying the code to try to narrow down what’s causing the glitch.

I’m actually starting to wonder whether my code is the culprit, because the correct bitmaps are being drawn in the correct locations, even the ones affected by the glitch. The problem is with the drawn bitmaps themselves, and it appears to be caused by bitmaps drawn partway off the screen. Very strange!


#18

I have an idea of what the problem might be but it will take some time for me to get to look at it


#19

@wuuff : I found the bug, and it was quite subtle. And it was my mistake.

Source of issue:

In the screenmode that you are using (Mode 2 , 110x88 pixels 16 colors), two 4-bit “nibbles”, in other words two pixels are packed into one byte. Leftmost pixel is the high nibble (top 4 bits) and right pixel is the low 4 bits.

I had the routine drawing pixels on the target pixel like so: targetpixel |= (sourcepixel & 0xF)<<4;

Now, the problem comes, when the background is not 0x00. (In your case it was 0xCC)

If the sourcepixel is 0x4 and background pixel is 0xCC, then targetpixel becomes 0xCC | (0x4<<4) = 0xCC, ie. it does not change

Solution
The top nibble of the target pixel needs to be first ‘emptied’ with targetpixel &= 0x0F, so that the top nibble bits all get reset to zero!

I will push the fix into the repository in the afternoon


#20

I’m glad to hear that I was accidentally able to help find a bug in the drawing code! That’s a surprising edge case. I guess nobody had tried drawing over just the right background color in this mode before.