Gravity Express

In Gravity Express, you battle gravity while transporting cargo through abandoned mines.

To learn more about the game and be notified when the game is released (january 2023), click the 'Follow' button on Gravity Express by Nino [Gravity Express]

Below you will find the devlog, where I will go into technical detail of various aspects of developing this game.

Original topic start below

A port of Crazy Gravity Portable I made some 12 years ago in my stereotypical bedroom.
The original game was made for the PlayStation Portable, which had a lively homebrew scene. Only C++ was available as a toolchain, but my game ran in a Lua interpreter. It achieved some 20fps.

With the original system having a resolution of 480x272, porting is a matter of resizing the viewport, replacing all the game engine calls and converting the sprites to 1-bit. I also found out that not all Lua functions are available, but found a way to probably repackage and load level files faster than before. Thanks @Nic :slight_smile:

I used Canvas Dither to convert the colored sprite to 1-bit using atkinson. Still thinking about how to translate the color-coded gate keys to this platform

Project is available at GitHub - ninovanhooff/Crazy-Gravity-Playdate: Battle gravity while transporting cargo through abandoned mines. · GitHub and completely unoptimized right now (full screen redraw every frame).

I would love to see this being played on hardware, plz post a video :slight_smile:
cgpd.pdx.zip (1.1 MB)

ezgif-1-52d0cc879d

The game runs a bit slow on the device (around 14fps) but all works pretty nicely. :+1:

Great, thanks. There is ample room for optimization, so no worries. Worst case I'll aim for a solid 20fps, since that is the original speed. Somewhat surprised to not see the framerate go up when there is no sound playing, as the docs warn that's heavy on cpu.

@matt

I saw in another thread that you dabbled with optimizing the game. I always appreciate pull requests on github :slight_smile:

Even though it's silly to optimize without owning the hardware, I still liked it as a weekend challenge. My approach:

Game initialisation (expensive):

  • create two buffer images that are 1 tile wider/higher than the screen
  • full draw the camera view to one buffer image

Single frame render:

  • clear the screen (solid color) (cheap)

IF the camera moved over a full tile:

  • copy the active buffer to the inactive buffer, shifted in x and/or y direction by one tile (not sure how expensive)
  • fill the empty space (pretty cheap. Usually just a single row/column of tiles)
  • swap the active and inactive buffers

draw the active buffer as a single draw call to the screen (cheap)

These changes seem to be massive in the profiler on Mac. Would love to see another profiler screenshot and fps count from real hardware @matt :slight_smile:


cgpd.pdx.zip (1.1 MB)

Added debug menu options for

  • fps
  • level select
  • dither pattern

cgpd.pdx.zip (1.1 MB)

I'm getting 30fps (with occasional dips to 24) from that build, which looks fantastic. I'm assuming collisions were turned off on purpose?

Thanks, that's great news. So the 20fps target is achieved :tada: and the the 30fps may be in reach. Note that the level 03 that gets loaded by default might be the heaviest, along with 10.

todo:

  • profile impact of audio: disable as test; and compress to adpcm audio

  • DONE optimize the OptimizeLevel() function further so render code skips over empty spaces in both directions (currently only one direction). Really this should be baked into the level files, but doing it on level load is practical for experimentation for now.

  • replace all pgeDraw calls by sprite:draw calls

  • perhaps limit the maximum player or camera velocity in such a way that the player will only move 1 tile (8px) per frame. This rate-limits the amount that needs to be drawn.

It really doesn't make sense for me to do this work, since I cannot profile on my own. So if anyone wants a challenge: have at it and create a pull request :slight_smile:

Is there a way to drill down in the profiler, like only keep the samples for the 10% slowest frames or even better: the frames where the target was not achieved?

@kyle yes the collisions are disabled in debug mode. Compile from source to tweak the variables :slight_smile:

Another performance update (I hope).

  • Did some checking on whether bits are actually on screen before drawing
  • optimized the level format so that more empty areas can be ignored by render calls
  • Fixed out of memory by garbage collecting before loading a level
  • do not redraw the HUD when a lot of tiles were drawn in the current frame
  • disabled debug mode, so that more realistic scenarios are tested. (Flying full speed diagonal without crashing will be difficult)
  • pre-calculated some math (cos and sin)

Questions:

  • Let's say I have 20 8x8 pixel cannonballs to draw. Half of those are offscreen. Will it benefit performance to check their pixel coordinates before drawing, or can I draw them all and let the clipRect cull the offscreen draws?
  • Would love to know what the current fps stats are
  • Could you give me some gameplay feedback?
  • Can an out of memory situation be created by switching levels 10 times? Not on the 16MB simulator. (press menu button)
  • how long does loading a level take?

cgpd.pdx.zip (1.3 MB)

That makes me really happy @matt , thanks so much!

  • Runs at a steady 30fps. At unlimited setRefreshRate(0) it's at 34–40fps! could be locked 35 easily. let's push for 40!

Don't tempt me! I spent a bit more time already, and if 40 is the max right now, a solid 40 would be very difficult. Also, the game physics is frame-based, not time based. So increasing fps would require re-tuning physics etc. Also, we humans are very accustomed to 30, 40 might feel weird. In addition, pushing the display past it's recommended fps might introduce ghosting or whatever.

I'd expect the scrolling to continue and the craft to explode dramatically into pieces or particles or something?

YES. I know the collision point, so could break up the sprite at that point and animate some parts to continue their momentum. This could be very cool.

  • feels unfair to be able to die when taking of by turning too soon.

Yeah, You'll get used to it, but I don't want to set a bad first impression. I'll revert the landing tolerance to the previous value

  • Levels take ~1–3 seconds to load depending on complexity/size. Did you consider saving their fully-loaded Lua state back to disk and loading that instead? I'll be doing that with my cars soon.

Yikes, I already do this. See https://github.com/ninovanhooff/Crazy-Gravity-Playdate/blob/main/Source/levels/LEVEL01.lua

Which gets compiled into binary lua by pdc. Note that it's very inefficient: most numbers could have been 8-bit, but lua only has 32-bit numbers. level 3 takes about 7MB of ram. Im thinking about a feature request for a more efficient playdate-level-format which can be loaded with a progress update callback

I knew about it, will look into it further

  • Please add a pdxinfo so at least the game name is shown in the launcher. Title card image would be even better!

Weird, is this not picked up?
https://github.com/ninovanhooff/Crazy-Gravity-Playdate/blob/main/Source/pdxinfo

  • What is "bob" dither type? I collect dither algorithms.

Best of Both. Manual cut and paste of atkinson as base + the bits from others where atkinson looks bad

According to the docs, pdxinfo is only read by the system, not displayed to the user in the launcher.
In the meantime, I did make a simple card though.

explosion_demo

Had a blast implementing a nice explosion effect, thanks for the inspiration @matt :slight_smile:
Hope the framerate is not too terrible, but maybe I could sell it as a slow-mo crash cam? :playdate_cry_laugh:

Also:

level loading should be quite a bit faster.
simple game card
converted some globals into locals for performance
changed some 2-bit images to 1-bit. Perhaps it'll improve performance

Up next
Game Over screen
Level Select screen

Get it here:

Start Screen effect

Figured the plane looks like a cursor, and thought of this approach to a Start Screen, which gets the player familiar with the physics as well :slight_smile:

Button gets activated when it fills up completely

startScreen

This is giving me Sub-Terrania vibes and I love that. Also the thruster sounds like the jetpack in Club Penguin so you get bonus points for that.

Two thumbs up. Love it.

Thanks @naltohq !. Do you maybe have suggestion for a new name for this game? Post them here: Name this game: futuristic rocket transport platformer

Hi time for another update:

  • level select screen
  • GameOver And level cleared dialog screens

level_unlock

This is great! I had never played the original, but I've beaten all the levels twice now on my playdate and it is great fun. The easier levels are very meditative.

I have three questions.

  1. Have you considered making the strength of gravity a changeable parameter? Maybe as an achievement you could need to explore a level while the gravity was a strong as Jupiter or null gravity.

  2. I read somewhere the original had twenty levels. Any plans to update the game to include the other 10?

  3. In level 3, off to the right, there's a platform with a bonus crate. If you take the crate it makes the same sound as the +1 life box, but it doesn't increment your lives when you take it, nor does it increase your carrying capacity. Is this a bug or does the box do something I'm missing?

Seriously, though, this is a great addition to my playdate and has given me several hours of enjoyment already. The game has been very stable for me. It crashes sometimes when I re-load the same level I'm currently on, but otherwise I've had no crashes.

Any chance to get a compiled .pdx for the .5 update to try out the menu selection screen?

This is great feedback!

Reading that my work entertained you for more than an hour made my day!

  1. Great suggestion! I might add a 1.5x gravity challenge.
  2. I have the source code for loading the original levels, in c++. I'll have a crack at using that. If someone reads this and wants to take the challenge: let me know
  3. VERY valuable feedback. I'll look into giving all pickups their own sound. In this case a turbo whoosh. I think you've been playing an older version, because the sprites for pickups are redone, and also the HUD icon for speed blinks and gets replaced by a fast forward symbol. Please let me know whether this is clear to you.

Crash: I would like to get to the bottom of this. Can you make sure you are using version 0.5 and tell me the steps to reproduce? If it is not too much effort, I'd like to know whether you can reproduce it in the simulator and what the error message is in the console.
@metafish I will upload a pdx this evening.

Here's the zipped pdx of v0.5:

btw, I noticed I forgot to actually prevent you from starting a locked level :sweat_smile:
Anyways, that would be trivial to add later. For now it's convenient for playtestesting.
@metafish @karljpsmith

cgpd_0_5_1.pdx.zip (2.5 MB)
edit: actually made a change to properly reset the game state when selecting retry, which might possibly fixes the crash. Calling this v0.5.1.