It has been about a month in the making, but I’m happy to announce the release of my first game for Ringo, RingoSki!
RingoSki is somewhat derived from my MAKERbuino game Skibuino, but I wouldn’t call it a port as it has many new features and required significant changes for the Ringo. Both RingoSki and Skibuino use techniques from a game engine I developed in C about a decade ago. Inspiration comes from the 1991 game SkiFree for Windows and DOS.
The GitHub repo is located here for source code and release downloads: https://github.com/delpozzo/ringoski
If you want to directly download the release, you can simply click here: RingoSki-1.0.0.zip.
Installation
Step 1: Download RingoSki-1.0.0.zip from the release folder.
Step 2: Unzip the RingoSki-1.0.0.zip archive.
Step 3: Copy the resulting RingoSki folder to the root directory of your Ringo’s SD card.
Controls
• Joystick L/R - Ski to the left or right.
• A Button - Increase speed. Turing while this button is held results in a power slide.
• B Button - Jump. Used to jump over logs.
• Function L/R - Pause game (these are the buttons next to the power and home buttons).
• 0 - Debug mode. Draws bounding boxes and some debug information.
Gameplay
• Avoid trees and penguins by using Joystick L/R to ski to the left and right.
• Hold down A to increase your speed, this is essential to outrun the Yeti.
• Turning while holding A results in a power slide to quickly maneuver around obstacles.
• Jump over logs by pressing B.
• The game ends when the Yeti catches you.
• Your score is based off of the amount of meters traveled.
• The map is randomly generated for a unique experience each time.
----------------------------------------------------
Lessons Learned / Tips for Ringo Developers
First off, I want to say that I had an absolute blast developing for the Ringo. The ESP32 SoC with Xtensa microprocessor that powers it is extremely capable. A full blown retro-RPG title would definitely be possible on this platform if developed properly. With that said, there were some challenges I encountered along the way:
SD Card I/O and Sound FX
Care must be taken when reading/writing to/from the SD card. The current MAKERphone library only allows you to have 4 MPTracks loaded at any given point in time.
I found out the hard way that reading/writing files to the SD card (for instance, when saving or reading a high score) also shares one of these 4 “slots” with MPTrack. By observing the serial output, I noticed the following error when this condition happened: vfs_fat: open: no free file descriptors. I was able to solve this problem by unloading all sounds whenever I needed to save or read the high score, followed by reloading the sounds after the file operation completed.
initSounds() function:
void initSounds() { // Load menu sounds if at the menu if(activeLoop == MENULOOP) { sndMenuMusic = new MPTrack("/RingoSki/menu.wav"); sndMenuMusic->setVolume(map(mp.mediaVolume, 0, 14, 100, 300)); addTrack(sndMenuMusic); sndStart = new MPTrack("/RingoSki/start.wav"); sndStart->setVolume(map(mp.mediaVolume, 0, 14, 100, 300)); addTrack(sndStart); } else // Otherwise load normal game sounds { sndJump = new MPTrack("/RingoSki/jump.wav"); sndJump->setVolume(map(mp.mediaVolume, 0, 14, 100, 300)); addTrack(sndJump); sndSlide = new MPTrack("/RingoSki/speed.wav"); sndSlide->setVolume(map(mp.mediaVolume, 0, 14, 100, 300)); addTrack(sndSlide); sndCrash = new MPTrack("/RingoSki/crash.wav"); sndCrash->setVolume(map(mp.mediaVolume, 0, 14, 100, 300)); addTrack(sndCrash); sndGameOver = new MPTrack("/RingoSki/gameover.wav"); sndGameOver->setVolume(map(mp.mediaVolume, 0, 14, 100, 300)); addTrack(sndGameOver); } }
reloadSounds() function:
void reloadSounds() { closeSounds(); initSounds(); }
closeSounds() function:
void closeSounds() { for(int i = 0; i < 4; i++) { if(tracks[i] != NULL) { removeTrack(tracks[i]); } } }
Another strange behavior I encountered with the sound FX is that sometimes they get randomly unloaded. If you try to play an unloaded MPTrack, your Ringo will crash. I deduced that this probably has something to do with what is happening in mp.update(), such as accessing the main menu, power save mode activating, an incoming call, etc. trying to use one of the 4 slots. I made a “safe” playSound() function to solve this problem:
void playSound(MPTrack *snd) { // safety check (sometimes things go wrong with sounds loaded from SD card) if(snd == NULL || snd->getSize() <= 0) { reloadSounds(); } snd->rewind(); snd->play(); }
Oh and one last thing about sound FX: Ringo is very picky regarding what type of .wav file it will play. Keep them short in duration, otherwise they will playback very slowly. Also, it appears that the bitdepth must be 16 or less, higher bitdepths result in static.
Update from @robertCM regarding .wav properties: The ideal .wav is 16-bit , 44100 Hz, and mono for Ringo.
Saving a Simple High Score Value
If you want to save multiple high scores in JSON format to the SD card, SpaceRocks, Snake, and Invaderz have excellent examples of this.
However, if you simply want to store an integer (like in RingoSki), you can do so like this:
int value = 1000; // example value of 1000 to save to SD File file = SD.open(highScorePath, "w"); file.write((byte*)&value, sizeof(int)); // write 4 bytes file.close();
Reading the value back is just as easy:
int value = 0; // initialize value to something known in case something goes wrong File file = SD.open(highScorePath); file.read((byte*)&value, sizeof(int)); // read 4 bytes file.close();
Important: As I mentioned above, make sure you unload other objects (such as MPTracks) that are using the SD card before you read/write to it, you can reload them after your read/write operation completes.
Collision Detection
If you developed for the Gamebuino/MAKERbuino, you probably noticed that their library provides bitmap to bitmap collision detection for you.
When developing for the Ringo, it is up to you to roll your own collision detection. For RingoSki I decided on two collision boxes per Sprite, which is plenty to provide “good enough” collision detection while also giving a slight advantage to the player by making the bounding boxes a bit on the small side.
If you want to see the collision boxes in action in RingoSki, press ‘0’ on the numpad while playing to bring up the debugger.
----------------------------------------------------
Closing Remarks
Well, I think that is about all I have for now! If I think of anything else I’ll reply here. Enjoy, and please share your thoughts (and high scores ).