ArduboyG Tutorial: Difference between revisions

From Arduboy Wiki
Jump to navigation Jump to search
No edit summary
Line 228: Line 228:


Rebuild the sketch using the standard Arduino commands and test the code by dragging the resultant <code>.hex</code> file and the <code>fxdata.bin</code> file onto Ardens.  You should be able to see the PPOT logo!
Rebuild the sketch using the standard Arduino commands and test the code by dragging the resultant <code>.hex</code> file and the <code>fxdata.bin</code> file onto Ardens.  You should be able to see the PPOT logo!
== Hints and Tricks ==
=== Using Images with Multiple Frames / Tile Sets ===
Like the original Sprites library, the ArduboyG library can handle images with multiple frames.  The sample code found here <nowiki>https://github.com/filmote/ArduboyG_Tileset</nowiki> extends the simple sketch above to show how to convert multi-framed images and how to display each frame using its index.
When converting an image that contains multiple frames, the dimensions of a single frame must  be specified in the conversion script as shown below:
convert_header(IMAGES + 'PPOT.png', BASE + 'Images.hpp', 'PPOT', 4, 128, 64)
The frames of an image are indexed from left to right then top to bottom as shown in the image below:
[[File:PPOT Arrows.png|frameless|349x349px]]
When generating the encoded data for the graphics with the convert_srites.py script, the output will contain the white plane of the first image followed by the light grey plane and then finally the dark grey plane. This is then repeated for the three remaining images resulting in 12 logical planes - three for the first image, three for the second and so on.
When rendering the image using SpritesU::drawOverwrite(), or any of the other rendering functions, the nth image can be selected by altering the call as shown below. The literal 3 represents the number of planes per image (which is 3 when using ABG_Mode::L4_Triplane).
SpritesU::drawOverwriteFX(0, 0, PPOT, (frameToRender * 3) + currentPlane);   

Revision as of 09:02, 4 August 2024

This tutorial shows how to use the ArduboyG library to create grey scale games.

This tutorial assumes you have mastered the art of rendering black and white graphics to the Arduboy using the drawOverwrite() and drawPlusMask() functions that are contained in the Sprites library.  If you haven't got this far I recommend you master those before moving on.

History of Greyscale on the Arduboy

The production Arduboy comes with an SSD1306 screen - a monochrome screen of 128x64 pixels.  Many developers have attempted to produce a grey effect by alternating images with different dithering patterns to produce a grey colour.  This can be seen on earlier games, such as 'Ard-Drivin, and can be quite effective if the frame rate is fast enough.

Following the lead of what developers on the Thumby site were doing, @brow1067 developed a library for the Arduboy that uses a very high frame rate (above 150 fps) and the persistence properties of the screen to simulate both 3-shades and 4-shades of grey.  The library comes with methods to render the images from PROGMEM or directly from the FX chip in multiple modes, including images with transparent backgrounds. The methods that use the FX chip are extremely useful due to the larger size of the graphics.

The following tutorial will focus on @brow1067's library and introduce advanced concepts as it progresses.  This thread on the Arduboy forum describes the library in detail and is a great reference when attempting to solve problems you may face when using the library.

Getting Started

The ArduboyG library itself can be downloaded from @brow1067's GitHub repository here.  However, I have built a stripped down sample that this tutorial references which can be downloaded here.  I have focused on using only one mode - four shades of grey - but even the stripped down demo has a lot of moving parts!

As mentioned, grey scale graphics are much larger than those used by the Arduboy2 library and you will probably want to stoer them on the FX chip.  To do this, you will need to install the ArduboyFX library following these instructions.

Before jumping into the code, it is important to understand how the grey scale library works and to compare this to the standard Arduboy2 library.

A typical Arduboy2 program might look like this:

000  #include <Arduboy2.h>
010 
020  Arduboy2 arduboy;
030  uint8_t y = 0;
040
050  void setuo() {
060    arduboy.begin();
070    arduboy.setFrameRate(60);
080  }
090
100  void loop() {
110
120    if (!(arduboy.nextFrame())) return;
130   
140    arduboy.clear();
150
160    // Handle movements ..
170  
180    if (arduboy.pressed(UP_BUTTON) && y > 0) {
190      y--;
200    }
210  
220    if (arduboy.pressed(DOWN_BUTTON) && y < 56) {
230      y++;
240    }
250
260    // Render image at y position ..
270  
280    Sprites::drawOverwrite(64, y, img, 0);
290  
300    arduboy.display();
310     
320  }

Let's pull that sketch apart.

At line 000 we import the standard Arduboy2 library and create an instance of it at line 020.  Within the setup() function, we initalise the library and set any other parameters - such as setting the frame rate to 60 FPS, at line 070.  This should all be familiar code so far.

In the loop() function, we enforce the frame rate using the standard Arduboy2 approach (line 120). If it is time to render the next frame, the screen buffer is clear at line 140 before updating the game logic, such as handling any user input, before rendering images and other graphics into the screen buffer (line 260).  Finally we push the screen buffer to the actual screen at line 280 and clear the buffer ready for the next iteration.

The cycle of applying game logic, updating player positions then rendering the screen is a typical pattern for Arduboy2 games.

Compare that to the same ArduboyG library sketch (simplified):

000  #include "src/ArduboyG.h"
010  #include "src/SpritesU.hpp"
020
030  extern ArduboyGBase_Config<ABG_Mode::L4_Triplane> arduboy;
040  decltype(arduboy) arduboy;
050  uint8_t y;
060
070  void setup() {      
080    arduboy.boot();
090    arduboy.startGray();
100  }
110
120  void update() {
130    
140    if (arduboy.pressed(UP_BUTTON) && y > 0) {
150      y--;
160    }
170    
180    if (arduboy.pressed(DOWN_BUTTON) && y < 56) {
190      y++;
200    }
210    
220  }
230
240  void loop() {
250 
260    FX::enableOLED();
270    a.waitForNextPlane(BLACK);
280    FX::disableOLED();
290
300    if (arduboy.needsUpdate()) update();
310 
320    uint16_t currentPlane = a.currentPlane();
330
340    SpritesU::drawOverwriteFX(0, y, img, currentPlane);    
350
360  }

The two sketches look similar.

As with the original sketch, the lines 000 - 040 import the required libraries and declare an instance of the main library. You will notice that the declaration includes the ABG_Mode::L4_Triplane constant indicating that we are using 4 shades of grey (white, light grey, dark grey and black).  There are other modes but these are beyond the scope of this tutorial.

The setup() loop initialises and starts the library.  Note that there is no code to set the frame rate as all ArduboyG games run at a high 150+ FPS!

Jumping to the loop() function, you will notice the code starts varying from the original Arduboy2 version.  It is important to understand that the grey scale images are painted in ‘layers’ or 'planes' building up the colors as it goes. When using the L4_Triplane (4 colour) mode, each image contains 3 planes - white, light grey and dark grey - and these are painted in that order and the planes build on each other. If a single pixel is only rendered in one plane, it comes out as dark grey. If it is rendered in two planes, it is light grey. Finally, if it is rendered in all three planes then, of course, it is white.

At line 320, you the code uint16_t currentPlane = a.currentPlane(); which returns the current plane being drawn. When the code renders the image, using the SpritesU::drawOverwriteFX() command, it passes the coordinates, the image name and the current plane being drawn.

The loop() function must be executed three times to render a single image!

As images must be rendered over multiple iterations of the loop() function, it is important that they are not moved mid-rendering. The ArduboyG library provides a simple test to ensure that your game state logic is called only every 3rd iteration of the loop() function - as can be seen at line 300.  Thus the logic to move the image is broken out into a function to be called every 3rd iteration rather than left inline as per the original Arduboy2 version of the code.

Creating Graphics

The ArduboyG library comes with a Python script to convert .png files into the correct format for the library, named convert_sprite.py. As mentioned earlier, the ArduboyG library comes with methods to render the images from PROGMEM or directly from the FX chip in multiple modes, including images with transparent backgrounds.

The tutorial uses a simple project that shows how to render a single image from the FX chip to the screen.  The convert_sprite.py script, mentioned above, has been hacked to allow multiple images to be converted into a single source file for inclusion onto the FX chip. Download the sample code here.


Navigating the Sample Code

The following section describes the contents of the sample code structure. It glosses over the use of the FX chip but should provide enough detail to allow you to continue with this tutorial.  For more information regarding the use of the FX chip, refer to the information on the ArduboyFX library here.

In the \fxdata directory, you will see three subfolders and a couple of files that are necessary for an FX program to load and save data. The fxdata-data.bin and the fxdata-save.bin are only used when you distribute your program and can be ignored for now. The fxdata.bin file is only used for development and it and the .hex file can be dragged and dropped onto the Ardens environment to test a game.

The Images.hpp file contains the actual graphics data to be displayed.  Its contents are automatically generated by a script and will need to be regenerated as new images are added to the sketch.  This process is detailed in the section Converting the Graphics, below.

The \fxdata\Arduboy-Python-Utilities-master directory is a copy of Mr.Blinky's FX library and is included here to make compiling easier.

The \fxdata\images directory contains the .png files you want to add to the FX chip and will later render in your game. Note that they are black, white and two shades of grey. Images can be any width but must be a multiple of 8 bytes high. The image provided is opaque but you can have a transparent color in them. For now, lets just concentrate on non-transparent colors.

The scripts directory contains … well … scripts! The convert_sprites.py script is a butchered version of @brow1067's original script and allows you to nominate one or more images that you have sitting in the images` directory for conversion into the ArduboyG format.


Creating Grey Scale Graphics

Graphics can be created in your favourite graphics tool.  As mentioned earlier, they must be a multiple of 8 pixels high and have only four colours - black, white, light grey and dark grey.

The convert_srites.py script converts one or more .png files into the correct format for the library.  It is quite good at converting the images but occasionally gets confused if the colours are too close together.  To prevent any confusion, I typically use a palette using the following RGB values:

White       #FFFFFF or decimal 255, 255, 255
Light Grey  #939393 or decimal 147, 147, 147
Dark Grey   #555555 or decimal 85, 85, 85
Black       #000000 or decimal 0, 0, 0

Once your graphics are complete, place them within the \fxdata\images directory.


Converting the Graphics

In the sample program, the images are converted into a file named Images.hpp for inclusion in the FX data file.  This is performed by the convert_sprites.py script and it allows one or more images to be converted into a single source file.  

Reviewing the script itself reveals a number of functions and then the following lines at the bottom which coordinate the conversion. It starts by deleting the Images.hpp file then converting each image and adding the resultant data to the end of the file.

BASE = '../'
IMAGES = '../images/'

deleteFile(BASE + 'Images.hpp')

convert_header(IMAGES + 'BG.png', BASE + 'Images.hpp', 'BG', 4)

Additional graphics can be converted in a single execution of the script by adding additional lines to the bottom, such as:

convert_header(IMAGES + 'BG.png', BASE + 'Images.hpp', 'BG', 4)
convert_header(IMAGES + 'BG1.png', BASE + 'Images.hpp', 'BG1', 4)
convert_header(IMAGES + 'BG2.png', BASE + 'Images.hpp', 'BG2', 4)

The parameters to the call are the original image location and name, the file name to create the data into, the name that you will refer to the image within your code and the grey scale mode to use.  There are additional parameters that can be used to handle sprite sheets, etc,but we will ignore those for the moment.

Using a command window, navigate to the /fxdata/scripts directory of the sample code and run the command python3 ./convert_sprites.py  If the code runs successfully it will echo out the images it has created and you should notice that the Images.h file has been updated to include any new graphics you included at the end of the script.  You can delete the Images.h file and re-run the script to prove that it being recreated properly.


Adding Graphics to the fxdata.bin File

When rendering images from the FX chip, the converted data from the previous step must be compiled into the fxdata.bin file.  This is done using a script provided within the ArduboyFX library, written by Mr Blinky.

The script accepts a parameter that indicates the data to convert - typically this file is named fxdata.txt and resides in the \fxdata directory. Reviewing the contents of the file shows it 'includes' the output file from the previous step, Images.hpp.

include "Images.hpp"
savesection  // Anything below here will be stored in the save block
uint16_t 0xFFFF // Save state  end marker

Using a command window, navigate to the /fxdata/scripts directory of the sample code and run the command python3 ../Arduboy-Python-Utilities-master/fxdata-build.py ../fxdata.txt  

If the code runs successfully it will echo out a confirmation similar to that below:

FX data build tool version 1.13 by Mr.Blinky May 2021 - Jan 2023
Building FX data using /projects/ArduboyG/fxdata/fxdata.txt
Including file /projects/ArduboyG/fxdata/Images.hpp
Saving FX data header file /projects/ArduboyG/fxdata/fxdata.h
Saving 3074 bytes FX data to /projects/ArduboyG/fxdata/fxdata-data.bin
Saving 2 bytes FX savedata to /projects/ArduboyG/fxdata/fxdata-save.bin
Saving FX development data to /projects/ArduboyG/fxdata/fxdata.bin

You should also notice that the three FX files - fxdata.bin, fxdata-data.bin and fxdata-save.bin - have all been updated.  You can delete any of these files and re-run the script to prove that they are being recreated properly.


Running both Scripts Together

Included in the sample program is another script that simply chains the two commands together named build.sh.  When developing, it is very convenient to have a command window open in the fxdata\scripts sub-directory and run the script using ./build.sh and it creates the rest.

The build.sh script is for Linux / MacOS but could easily be changed into a batch file for Windows.

Displaying the Graphics

Now that we have created our graphics and regenerated the fxdata.bin file, we can use them within our sketch.

Review the code in the ArduboyG.ino file.  It looks very similar to the code shown in the comparison above however it includes a few more libraries and a number of #defines to make it all work. Its pretty much the minimum requirements to get the code to work.

Rebuild the sketch using the standard Arduino commands and test the code by dragging the resultant .hex file and the fxdata.bin file onto Ardens.  You should be able to see the PPOT logo!

Hints and Tricks

Using Images with Multiple Frames / Tile Sets

Like the original Sprites library, the ArduboyG library can handle images with multiple frames.  The sample code found here https://github.com/filmote/ArduboyG_Tileset extends the simple sketch above to show how to convert multi-framed images and how to display each frame using its index.

When converting an image that contains multiple frames, the dimensions of a single frame must  be specified in the conversion script as shown below:

convert_header(IMAGES + 'PPOT.png', BASE + 'Images.hpp', 'PPOT', 4, 128, 64)

The frames of an image are indexed from left to right then top to bottom as shown in the image below:



When generating the encoded data for the graphics with the convert_srites.py script, the output will contain the white plane of the first image followed by the light grey plane and then finally the dark grey plane. This is then repeated for the three remaining images resulting in 12 logical planes - three for the first image, three for the second and so on.

When rendering the image using SpritesU::drawOverwrite(), or any of the other rendering functions, the nth image can be selected by altering the call as shown below. The literal 3 represents the number of planes per image (which is 3 when using ABG_Mode::L4_Triplane).

SpritesU::drawOverwriteFX(0, 0, PPOT, (frameToRender * 3) + currentPlane);