If people do not remember Pebble anymore, it was a Kickstarter funded smartwatch which gained quite the cult following. Sadly pebble went out of business at the end of 2016, but the community has since maintained parallel infrastructure such as a copy of the app store Pebble Appstore
Native pebble development was in C, and as Playdate publishes a C API in addition to the Lua one, I have explored porting one of my old Pebble games over to Playdate. Here is 'You Cannot Go Back' - a short memory/puzzle/adventure game running on the Pebble Time smartwatch emulator.
And here it is (work-in-progress) on the Playdate simulator
I managed to get a first version working on the simulator from just one weekend of work, since then I have been adding more features, more quality of life and some more polish.
At 400x240 with 1-bit colour the playdate is close to (but annoyingly not quite twice) the resolution of the Pebble Time's 144x168 "e-paper" display with 8-bit colour (64 colours).
In regular landscape mode I can centre the game window on screen and add some large borders left and right. For portrait mode I can render to a bitmap which then gets rotated 90 degrees and drawn at
setScale(2). Vertically there are another 64 pixels to play with, but horizontally 288 is sadly 48 pixels larger than 240 so I have experimented with a horizontal scroll which cuts off either the left or right dungeon walls. It's a bit annoying but shouldn't really affect the gameplay for this particular game, and is a lot less work than re-sizing the dungeon.
Lines, stroke or fill of circles or rectangles - all easy to port. Bitmap text rendering was easy too. It looks like text centring has to be done manually with the C API by querying
getTextWidth(), and this only then works for single-line strings, but this is true for the majority of the strings in the game anyway.
Pebble had a rich API for window, layer and custom menu management. Thankfully in this game I was barely using it at all! Stripped this all out.
One change is which caused some trouble is that in Pebble I could load my sprite sheet as a single image and then call
gbitmap_create_as_sub_bitmap(m_spriteMap, GRect(x, y, w, h)) to get
GBitmap* which reference a region of the parent image - I used this heavily as while the majority of my sprites were 16x16, some were larger, and some were 8x8. As Playdate's compiler chunks the sprite sheet at compile time, I have had to split everything into 8x8 tiles and added a wrapper such that every call to draw a regular 16x16 tile now needs to make 4 separate calls to
drawBitmap. So that's up from 48 to 192 draw calls now just to draw the floor. I could split up my sheet into multiple sheets based on size, it remains to be seen if this will be needed...
Audio / Haptic Feedback:
Pebble had haptic feedback, but Playdate doesn't - it is easily removed. The Pebble did not have the ability to make any sounds, not even a buzzer. So all music and sound effects are being added from scratch.
In Pebble I would subscribe to button presses by supplying a call-back e.g
window_single_click_subscribe(BUTTON_ID_UP, gameClickConfigHandler); and subscribe to the accelerometer with
accel_data_service_subscribe(1, dataHandler); accel_service_set_sampling_rate(ACCEL_SAMPLING_25HZ);
For playdate I am currently polling buttons and the accelerometer (which I am using for auto-rotation to portrait or landscape mode), though (as also noted on eventHandler not called for buttons - #7 by dave ) there is also a
PDSystemEvent kEventKeyPressed defined - it would be a bit nicer if input could be handled this way rather than directly inside the game loop.
On the game loop, I have separate update and render functions. In Pebble I would us a timed callback to keep my update function being called, to request redraw I would
layer_mark_dirty(s_dungeonLayer) as I had previously registered
With playdate I can get the system to call my update function with
playdate->system->setUpdateCallback(gameLoop, NULL); from which I can call
dungeonUpdateProc when I need to re-render.
One issue is that returning 0 from the update function to signify that the screen does not need to be redrawn looks to be broken (reported on C API: setUpdateCallback function documentation misleads about the meaning of a return value of 0 from the callback )
Improvements to the playdate version
I had to stop adding features to the Pebble game when I hit the 64 kb RAM ceiling of apps on the watch! Playdate's 16 Mb is practically infinite in comparison. I am already working on adding three more room types, new sprites, and all the audio. The
drawRotatedBitmap call also means that the buzz saws in the corridor of blades can actually rotate in this version!
To be seen... I don't have a physical device - nor have I managed to compile for ARM yet. I am planning soon on asking if the community can give some feedback on the hardware performance. But going from a Pebble watch's STM32_F2 to Playdate's STM32_F7 should hopefully mean that I don't have to worry here!