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
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
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.
This is a lovely surprise as I really enjoy this type of game.
I think current rendering can be optimised, but given that you are scrolling the whole screen partial redraws will not be as effective as you might hope. One of those things that need to be tried to see.
The way I would proceed would be to render the screen to a sprite to see if the SDK sprite system gives any quick gains. It will get better, but by how much is the question.
The best way would probably be to use the SDK tile map and sprites, but that sounds like a rather large rewrite of what you have already. it plays OK at 14fps, but it would play amazing at 30fps+.
Next I would aim to reduce the flickering (pixels that change every frame) by using careful sprite work to make sure shading and dithering align to an odd (or even) pixel grid - rather than random like Atkinson - and scrolling in multiples of 2 to align the scrolling with the dither pattern. This will reduce the amount of pixels changing every frame. Visually, it will look better on device. Whether it results in performance improvement I am not sure as the device sends dirty rows to the screen rather than dirty pixels. But, again, it cannot hurt. Preventing dither flashing/flickering on moving objects by snapping to even pixels
Thanks, that's great news. So the 20fps target is achieved 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
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?
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)
First, there's a Lua crash when I run out of lives.
Some great optimisation here.
Runs at a steady 30fps. At unlimited setRefreshRate(0) it's at 34โ40fps! could be locked 35 easily. let's push for 40!
gameplay:
When crashing the scrolling just stops and the explosion animation plays. This feels at odds with such dynamic movement during the game. I'd expect the scrolling to continue and the craft to explode dramatically into pieces or particles or something?
feels unfair to be able to die when taking of by turning too soon.
No, I can't trigger out of memory by changing through all the levels.
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.
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.
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
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.
Had a blast implementing a nice explosion effect, thanks for the inspiration @matt
Hope the framerate is not too terrible, but maybe I could sell it as a slow-mo crash cam?
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