Before we jump head-first into using Sprites, it’s worth to know a little bit about how the Sprite system works on the DS. The name
OAM stands for
Object Attribute Memory – it’s a dedicated space in memory that the DS uses to track and control all Sprites (their current Frame or all of the Frames, depending on the code used) and their
Attributes (Mosaic, Rotation, Double Size etc.). All in all, we have space for up to
128 individual Sprite data per screen,
32 of which can be freely rotated using the built-in rotation and scaling engine. The Sprite’s Graphics Data (GFX) is stored in VRAM Banks, the Sprite sheets are cut up into 8 by 8 squares, meaning tiles, similarily to the previously discussed Backgrounds. Each engine
(Main and Sub, Main for the Top Screen, Sub for the Bottom one) can support up to
1024 tiles at a time. Sprites can use
16 or 256 colours palettes - do note that all sprites use the same tile data, however each individual Sprite may technically use a different palette, so Sprites created from the same tiles may have entirely different colour schemes to save space rather than add additional tiles. OAM has to be refreshed each frame so that all the on-screen Sprites (their frame, position etc.) are displayed properly. Note the word “individual” – you can in fact display more than 128 Sprites at a time! What’s meant by that is that within memory, you can store
128 *different* Sprites using different attributes – you can re-use the same Sprite as many times as your own code or the library used allows you to. In case of NightFox Lib which we’re using, it’s
256 Sprites per Screen.
Much like in the case of backgrounds, we will be using GRIT to convert our Sprites. Sprites are a composition of frames of pre-defined size and shape on one Sprite Sheet – the shapes allowed are
Square (width is the same as height),
Wide (width is larger than height) and
Tall (height is larger than width), while the sizes allowed are
8x8, 16x16, 32x32, 64x64, 16x8, 32x8, 32x16, 64x32, 8x16, 8x32, 16x32, 32x64 pixels. As you probably already noticed, those
sizes are all divisible by 8. The way tiles are cut up is pretty simple – as with backgrounds, starting from the upper left-hand corner,
8x8 pixel big parts of the original image are converted into
tiles, the conversion takes place to the right from that point, and once it reaches the edge of the image, it goes a row lower until the entire image is converted. Knowing that, you can deduce that the easiest image to convert would be a Strip with the width of the original frame.
Once we have our Sprite Strip created, just like with Tiled Backgrounds, we put it in GRIT’s bmp folder. Once our Sprite or Sprites are in it, we run the Convert_Sprites batch file, and after the process is complete, the resulting binary files representing the Tiles and the Palette data can be recovered from the sprites folder. Alternatively, you can convert Sprites using
Joint Palettes – this way, you can use several Sprites using just one Palette. Keep in mind though that it limits the total number of colours you can use to 256 rather than 256 per Sprite, however when using a large number of them, you may end up having to resort to that.
You probably already noticed that the
Sprites do not have a Map file – this is because the hardware itself calculates which Tiles are supposed to be used at any given time, copies them into VRAM if needed and OAM updates the displayed image. With the size and shape parameters pre-set, it can do it with relative ease, hence a Map is not necessary at all.
Certain limitations mentioned above, such as the size of Sprites can be overcome by using the 3D hardware of the DS, we’ll eventually get there, but for now we’re going to focus on using the 2D hardware.
Now that we have some basic knowledge about how Sprites work in general, we can have a look at how the system works in NightFox Lib. By the end of this Tutorial, we will modify the previously written code to include a Sprite. First and foremost, we place our GRIT-converted Sprite in the “nitrofiles” folder. From here, we can move on to the coding. Seeing that in NightFox, Sprites are loaded from NitroFS, just like with Backgrounds, we start by Initializing the Buffers used by the library.
NF_InitSpriteBuffers();
NF_InitSpriteSys(Screen);
These two are pretty self-explainatory – the first function Initializes the Buffers, the second Initializes the system that controls them for the Screen of your choice –
0 for Top Screen, 1 for the Bottom one.
NF_LoadSpriteGfx("Sprite", RAM_Slot, Width, Height);
NF_LoadSpritePal("Sprite", RAM_Slot);
These two functions load the Gfx data and the Palette of our Sprite into a selected
Slot in RAM memory. We have
256 (0-255) Slots for Sprites and
64 (0-63) for Palettes. We’re not ready to display it though – as we learned earlier,
our resources have to be in VRAM to be displayed. To transfer them there, we will use these functions:
NF_VramSpriteGfx(Screen, RAM_Slot, VRAM_Slot, Transfer_Flag);
NF_VramSpritePal(Screen, RAM_Slot, VRAM_Slot);
The Screen is self-explainatory and we already know of the
RAM Slots used in NFLib, but we need to discuss the
VRAM Slots and the
Transfer Flag. As we already know, we can simultainously have up to
128 (0-127) Sprites in VRAM – that’s the value we are looking for then. As for Palettes, we can use one of
16 (0-15) Palette Slots for the Sprite to use. “Wait!” – you’re going to say, “You said each Sprite can use a dedicated Palette!” – that still applies, however one has to keep in mind that Palettes take space in their Bank – you can’t use more simply because you’d run out of space – this is why I mentioned that it’s often useful to use
Joint Palettes for sprites with similar colour schemes. Finally, we have the Transfer_Flag – as I said,
you can either transfer one Frame at a time, or you can transfer all frames into VRAM at once (the arguments are
true and
false depending on whether we want to transfer all the frames or not). Both options have their pro’s and con’s – using just one Frame at a time conserves more space, however this makes our Gfx Individual.
If you re-use the same Gfx, all the Sprites using that Gfx will animate to the Frame currently in VRAM. If you copy all the frames into VRAM,
each Sprite using that Gfx will be able to animate separately, meaning that the Gfx can be Shared. When to use which then, you ask? Well, it’s really up to you, and it’s highly-dependant on the design of your game. The general rule should be that if a given Sprite has more than one frame and the number of its occurances on-screen is higher than the number of its Frames, one should transfer all of the Frames to VRAM. It’s relatively easy to calculate why – say, you have a Sprite that has 15 Frames and it occurs on-screen 50 times. If each of these 50 instances were to be Individual, you’d use up 50 Slots in VRAM. If you transfer the entirety of Gfx data at once, regardless of whether you use it 5 or 250 times, it will use up the same amount of VRAM. On the other hand though, if you have a Sprite that has 100 Frames and it occurs 10 times on-screen, there’s no good reason as to why you’d transfer all 100 Frames – you can just as well create 10 Individual Gfx for them and use less Memory all-in-all. Always have memory in-mind – you have 128kb of VRAM for your Sprites per screen – use it wisely and strive towards conserving it.
Now that our data is in VRAM, we’re ready to display it properly.
NF_CreateSprite(Screen, ID, VRAM_Gfx_Slot, VRAM_Palette_Slot, X, Y);
By now, all of those Arguments should be clear to us. This function will spawn our Sprite at the position indicated by X and Y.
This concludes the basic tutorial – now, it’s time for some practice. Let’s have a look at some code.
Let's observe our Sample Code:
Code:
/*
#############################################
##DS Programming Guide - From Zero To Hero!##
####Example #3 - MODE 0 Tiled Sprites ####
#############################################
*/
/*
############
##Includes##
############
*/
// Include C
#include <stdio.h>
// Include Libnds
#include <nds.h>
// Include NFLib
#include <nf_lib.h>
/*
###############
##Main(){...}##
###############
*/
int main(int argc, char **argv) {
// Turn on MODE 0 on the Top Screen
NF_Set2D(0, 0);
// Set the Root Folder
NF_SetRootFolder("NITROFS");
// Initialize the Tiled Backgrounds System on the Top Screen
NF_InitTiledBgBuffers();
NF_InitTiledBgSys(0);
// Initialize the Tiled Sprites System on the Bottom Screen
NF_InitSpriteBuffers();
NF_InitSpriteSys(0);
// Load and Create the Tiled Background
NF_LoadTiledBg("Background", "Background", 256, 256);
NF_CreateTiledBg(0, 3, "Background");
// Load our Tiled Sprite
NF_LoadSpriteGfx("Sprite", 0, 64, 64);// Tempy!
NF_LoadSpritePal("Sprite", 0);
// Transfer our sprite to VRAM
NF_VramSpriteGfx(0, 0, 0, false);
NF_VramSpritePal(0, 0, 0);
// Create the Sprite!
NF_CreateSprite(0, 0, 0, 0, 0, 0);
while(1){
swiWaitForVBlank();
}
return 0;
}
We compile the code and… the Sprite didn’t spawn! Why…? What have we forgotten? Let’s come back to OAM. To properly display our Sprites,
we need to refresh its state. To do so, we will have to update our OAM contents
in-between of our VBlanks:
Code:
//Update NF OAM Settings
NF_SpriteOamSet(0);
swiWaitForVBlank();
//Update OAM!
oamUpdate(&oamMain);
With these lines altered at the end of our applications, it should start displaying our Sprites properly!
EDIT: I've been informed (on good authority) that libnds has been modified to include 16 Ext.Palettes for backgrounds stock, which didn't use to be the case and I did not know that at the time of writing this guide. It used to be handled by means of setting the start adress of the Palette, now it's done by stating the Slot number. With that in mind, the sentence stating that this is a NightFox Lib limitation has been removed.