ArduboyG Tutorial: Difference between revisions

From Arduboy Wiki
Jump to navigation Jump to search
Line 53: Line 53:
Let's pull that sketch apart.
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.
At line <code>000</code> we import the standard Arduboy2 library and create an instance of it at line <code>020</code>.  Within the <code>setup()</code> function, we initalise the library and set any other parameters - such as setting the frame rate to 60 FPS, at line <code>070</code>.  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
In the <code>loop()</code> function, we enforce the frame rate using the standard Arduboy2 approach (line <code>120</code>). If it is time to render the next frame, the screen buffer is clear at line <code>140</code> before updating the game logic, such as handling any user input, before rendering images and other graphics into the screen buffer (line <code>260</code>).  Finally we push the screen buffer to the actual screen at line <code>280</code> and clear the buffer ready for the next iteration.
 
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.
The cycle of applying game logic, updating player positions then rendering the screen is a typical pattern for Arduboy2 games.
Line 105: Line 103:
The two sketches look similar.
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.
As with the original sketch, the lines <code>000 - 040</code> import the required libraries and declare an instance of the main library. You will notice that the declaration includes the <code>ABG_Mode::L4_Triplane</code> 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 156 FPS!
The <code>setup()</code> 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 156 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.
Jumping to the <code>loop()</code> 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 <code>L4_Triplane</code> (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.
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.  
At line <code>320</code>, you the code <code>uint16_t currentPlane = a.currentPlane();</code> which returns the current plane being drawn. When the code renders the image, using the <code>SpritesU::drawOverwriteFX()</code> 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!
The <code>loop()</code> 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.
As images must be rendered over multiple iterations of the <code>loop()</code> 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 <code>loop()</code> function - as can be seen at line <code>300</code>.  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.

Revision as of 03:01, 4 August 2024

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 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!

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 156 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.