gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

DS development > sprites on subscreen

#152528 - ben0bi - Mon Mar 17, 2008 8:03 am

i write a game wich uses both screens for the same.

so, is it possible to overlap the memory from the sub and main screen bg's/sprites?

i have read and re-coded a tutorial to use OAM for sprites. is it possible to use these (OAM) sprites on the sub screen, too?

i mapped a VRAM bank wich is capable of sub_sprite to VRAM_X_SUB_SPRITE and enabled sprites on the sub screen but what do i have to do next?

here is the source from the tutorial. i really do not get, where i should change something. i want to use the same memory for both screens, if possible (for both, bg and sprites...if possible) - or else, how do i set up sprites for the sub engine?

thanx for your help.

here is the code from the tutorial, i am sorry for that much, but so you have exactly what i have...(ok, my code is encapsulated in classes and so on but its from this base code):

Code:

/*
 *  main.cpp
 * 
 *  Created by Jaeden Amero on 11/12/07.
 *  Copyright 2007. All rights reserved.
 *
 */

#include <nds.h>
#include <assert.h>
#include "sprites.h"

/* Backgrounds */
#include "starField.h"
#include "planet.h"
#include "splash.h"
/* Sprites */
#include "orangeShuttle.h"
#include "moon.h"

/* Select a low priority DMA channel to perform our background copying. */
static const int DMA_CHANNEL = 3;

void initVideo() {
    /*
     *  Map VRAM to display a background on the main and sub screens.
     *
     *  The vramSetMainBanks function takes four arguments, one for each of the
     *  major VRAM banks. We can use it as shorthand for assigning values to
     *  each of the VRAM bank's control registers.
     *
     *  We map banks A and B to main screen  background memory. This gives us
     *  256KB, which is a healthy amount for 16-bit graphics.
     *
     *  We map bank C to sub screen background memory.
     *
     *  We map bank D to LCD. This setting is generally used for when we aren't
     *  using a particular bank.
     *
     *  We map bank E to main screen sprite memory (aka object memory).
     */
    vramSetMainBanks(VRAM_A_MAIN_BG_0x06000000,
                     VRAM_B_MAIN_BG_0x06020000,
                     VRAM_C_SUB_BG_0x06200000,
                     VRAM_D_LCD);

    vramSetBankE(VRAM_E_MAIN_SPRITE);

    /*  Set the video mode on the main screen. */
    videoSetMode(MODE_5_2D | // Set the graphics mode to Mode 5
                 DISPLAY_BG2_ACTIVE | // Enable BG2 for display
                 DISPLAY_BG3_ACTIVE | // Enable BG3 for display
                 DISPLAY_SPR_ACTIVE | // Enable sprites for display
                 DISPLAY_SPR_1D       // Enable 1D tiled sprites
                 );

    /*  Set the video mode on the sub screen. */
    videoSetModeSub(MODE_5_2D | // Set the graphics mode to Mode 5
                    DISPLAY_BG3_ACTIVE); // Enable BG3 for display
}

void initBackgrounds() {
    /*  Set up affine background 3 on main as a 16-bit color background */
    BG3_CR = BG_BMP16_256x256 |
             BG_BMP_BASE(0) | // The starting place in memory
             BG_PRIORITY(3); // A low priority

    /*  Set the affine transformation matrix for the main screen background 3
     *  to be the identity matrix.
     */
    BG3_XDX = 1 << 8;
    BG3_XDY = 0;
    BG3_YDX = 0;
    BG3_YDY = 1 << 8;

    /*  Place main screen background 3 at the origin (upper left of the screen)
     */
    BG3_CX = 0;
    BG3_CY = 0;

    /*  Set up affine background 2 on main as a 16-bit color background */
    BG2_CR = BG_BMP16_128x128 |
             BG_BMP_BASE(8) | // The starting place in memory
             BG_PRIORITY(2);  // A higher priority

    /*  Set the affine transformation matrix for the main screen background 3
     *  to be the identity matrix.
     */
    BG2_XDX = 1 << 8;
    BG2_XDY = 0;
    BG2_YDX = 0;
    BG2_YDY = 1 << 8;

    /*  Place main screen background 2 an interesting place. */
    BG2_CX = -(SCREEN_WIDTH / 2 - 32) << 8;
    BG2_CY = -32 << 8;

    /*  Set up affine background 3 on the sub screen as a 16-bit color
     *  background
     */
    SUB_BG3_CR = BG_BMP16_256x256 |
                 BG_BMP_BASE(0) | // The starting place in memory
                 BG_PRIORITY(3); // A low priority

    /*  Set the affine transformation matrix for the sub screen background 3
     *  to be the identity matrix.
     */
    SUB_BG3_XDX = 1 << 8;
    SUB_BG3_XDY = 0;
    SUB_BG3_YDX = 0;
    SUB_BG3_YDY = 1 << 8;

    /*
     *  Place main screen background 3 at the origin (upper left of the screen)
     */
    SUB_BG3_CX = 0;
    SUB_BG3_CY = 0;
}

void initSprites(tOAM * oam, SpriteInfo *spriteInfo) {
    /*  Define some sprite configuration specific constants.
     *
     *  We will use these to compute the proper index into memory for certain
     *  tiles or palettes.
     *
     *  OFFSET_MULTIPLIER is calculated based on the following formula from
     *  GBATEK (http://nocash.emubase.de/gbatek.htm#dsvideoobjs):
     *      TileVramAddress = TileNumber * BoundaryValue
     *  Since SPRITE_GFX is a uint16*, the compiler will increment the address
     *  it points to by 2 for each change in 1 of the array index into
     *  SPRITE_GFX. (The compiler does pointer arithmetic.)
     */
    static const int BYTES_PER_16_COLOR_TILE = 32;
    static const int COLORS_PER_PALETTE = 16;
    static const int BOUNDARY_VALUE = 32; /* This is the default boundary value
                                           * (can be set in REG_DISPCNT) */
    static const int OFFSET_MULTIPLIER = BOUNDARY_VALUE /
                                         sizeof(SPRITE_GFX[0]);

    /* Keep track of the available tiles */
    int nextAvailableTileIdx = 0;

    /* Create the ship sprite. */
    static const int SHUTTLE_OAM_ID = 0;
    assert(SHUTTLE_OAM_ID < SPRITE_COUNT);
    SpriteInfo * shuttleInfo = &spriteInfo[SHUTTLE_OAM_ID];
    SpriteEntry * shuttle = &oam->spriteBuffer[SHUTTLE_OAM_ID];

    /* Initialize shuttleInfo */
    shuttleInfo->oamId = SHUTTLE_OAM_ID;
    shuttleInfo->width = 64;
    shuttleInfo->height = 64;
    shuttleInfo->angle = 462;
    shuttleInfo->entry = shuttle;

    /*
     *  Configure attribute 0.
     *
     *  OBJCOLOR_16 will make a 16-color sprite. We specify that we want an
     *  affine sprite (via isRotoscale) here because we would like to rotate
     *  the ship.
     */
    shuttle->posY = SCREEN_HEIGHT / 2 - shuttleInfo->height;
    shuttle->isRotoscale = true;
    /* This assert is a check to see a matrix is available to store the affine
     * transformation matrix for this sprite. Of course, you don't have to have
     * the matrix id match the affine id, but if you do make them match, this
     * assert can be helpful. */
    assert(!shuttle->isRotoscale || (shuttleInfo->oamId < MATRIX_COUNT));
    shuttle->rsDouble = false;
    shuttle->objMode = OBJMODE_NORMAL;
    shuttle->isMosaic = false;
    shuttle->colMode = OBJCOLOR_16;
    shuttle->objShape = OBJSHAPE_SQUARE;

    /*
     *  Configure attribute 1.
     *
     *  rsMatrixId refers to the loation of affine transformation matrix. We
     *  set it to a location computed with a macro. OBJSIZE_64, in our case
     *  since we are making a square sprite, creates a 64x64 sprite.
     */
    shuttle->posX = SCREEN_WIDTH / 2 - shuttleInfo->width * 2 +
                    shuttleInfo->width / 2;
    shuttle->rsMatrixIdx = ATTR1_ROTDATA(shuttleInfo->oamId);
    shuttle->objSize = OBJSIZE_64;

    /*
     *  Configure attribute 2.
     *
     *  Configure which tiles the sprite will use, which priority layer it will
     *  be placed onto, which palette the sprite should use, and whether or not
     *  to show the sprite.
     */
    shuttle->tileIdx = nextAvailableTileIdx;
    nextAvailableTileIdx += orangeShuttleTilesLen / BYTES_PER_16_COLOR_TILE;
    shuttle->objPriority = OBJPRIORITY_0;
    shuttle->objPal = shuttleInfo->oamId;

    /* Rotate the sprite */
    rotateSprite(&oam->matrixBuffer[shuttleInfo->oamId],
                 shuttleInfo->angle);

    /*************************************************************************/

    /* Create the moon sprite. */
    static const int MOON_OAM_ID = 1;
    assert(MOON_OAM_ID < SPRITE_COUNT);
    SpriteInfo * moonInfo = &spriteInfo[MOON_OAM_ID];
    SpriteEntry * moon = &oam->spriteBuffer[MOON_OAM_ID];

    /* Initialize moonInfo */
    moonInfo->oamId = MOON_OAM_ID;
    moonInfo->width = 32;
    moonInfo->height = 32;
    moonInfo->angle = 462;
    moonInfo->entry = moon;

    /*
     *  Configure attribute 0.
     *
     *  OBJCOLOR_16 will make a 16-color sprite. We won't specify that we want
     *  an affine sprite here because we don't want one this time.
     */
    moon->posY = SCREEN_WIDTH / 2 + moonInfo->height / 2;
    moon->isRotoscale = false;
    /* This assert is a check to see a matrix is available to store the affine
     * transformation matrix for this sprite. Of course, you don't have to have
     * the matrix id match the affine id, but if you do make them match, this
     * assert can be helpful. */
    assert(!moon->isRotoscale || (moonInfo->oamId < MATRIX_COUNT));
    moon->isHidden = false;
    moon->objMode = OBJMODE_NORMAL;
    moon->isMosaic = false;
    moon->colMode = OBJCOLOR_16;
    moon->objShape = OBJSHAPE_SQUARE;

    /*
     * Configure attribute 1.
     *
     * OBJSIZE_32 will create a sprite of size 32x32, since we are making a
     * square sprite. Since we are using a non-affine sprite, attribute 1
     * doesn't have an rsMatrixIdx anymore. Instead, it has the ability to flip
     * the sprite vertically or horizontally.
     */
    moon->posX = SCREEN_WIDTH / 2 + moonInfo->width + moonInfo->width / 2;
    moon->hFlip = false;
    moon->vFlip = false;
    moon->objSize = OBJSIZE_32;

    /*
     *  Configure attribute 2.
     *
     *  Configure which tiles the sprite will use, which priority layer it will
     *  be placed onto, which palette the sprite should use, and whether or not
     *  to show the sprite.
     */
    moon->tileIdx = nextAvailableTileIdx;
    nextAvailableTileIdx += moonTilesLen / BYTES_PER_16_COLOR_TILE;
    moon->objPriority = OBJPRIORITY_2;
    moon->objPal = moonInfo->oamId;

    /*************************************************************************/

    /* Copy over the sprite palettes */
    dmaCopyHalfWords(SPRITE_DMA_CHANNEL,
                     orangeShuttlePal,
                     &SPRITE_PALETTE[shuttleInfo->oamId *
                                     COLORS_PER_PALETTE],
                     orangeShuttlePalLen);
    dmaCopyHalfWords(SPRITE_DMA_CHANNEL,
                     moonPal,
                     &SPRITE_PALETTE[moonInfo->oamId * COLORS_PER_PALETTE],
                     moonPalLen);

    /* Copy the sprite graphics to sprite graphics memory */
    dmaCopyHalfWords(SPRITE_DMA_CHANNEL,
                     orangeShuttleTiles,
                     &SPRITE_GFX[shuttle->tileIdx * OFFSET_MULTIPLIER],
                     orangeShuttleTilesLen);
    dmaCopyHalfWords(SPRITE_DMA_CHANNEL,
                     moonTiles,
                     &SPRITE_GFX[moon->tileIdx * OFFSET_MULTIPLIER],
                     moonTilesLen);
}

void displayStarField() {
    dmaCopyHalfWords(DMA_CHANNEL,
                     starFieldBitmap, /* This variable is generated for us by
                                       * grit. */
                     (uint16 *)BG_BMP_RAM(0), /* Our address for main
                                               * background 3 */
                     starFieldBitmapLen);
}

void displayPlanet() {
    dmaCopyHalfWords(DMA_CHANNEL,
                     planetBitmap, /* This variable is generated for us by
                                    * grit. */
                     (uint16 *)BG_BMP_RAM(8), /* Our address for main
                                               * background 2 */
                     planetBitmapLen);
}

void displaySplash() {
    dmaCopyHalfWords(DMA_CHANNEL,
                     splashBitmap, /* This variable is generated for us by
                                    * grit. */
                     (uint16 *)BG_BMP_RAM_SUB(0), /* Our address for sub
                                                     background 3 */
                     splashBitmapLen);
}

int main() {
    /*  Turn on the 2D graphics core. */
    powerON(POWER_ALL_2D);

    /*
     *  Set up interrupts.
     *
     *  We don't really get into what these do exactly at this point in the
     *  manual, but we still need to do them for now.
     */
    irqInit();
    irqSet(IRQ_VBLANK, 0);

    /*
     *  Configure the VRAM and background control registers.
     *
     *  Place the main screen on the bottom physical screen. Then arrange the
     *  VRAM banks. Next, confiure the background control registers.
     */
    lcdMainOnBottom();
    initVideo();
    initBackgrounds();

    /* Set up a few sprites. */
    SpriteInfo spriteInfo[SPRITE_COUNT];
    tOAM *oam = new tOAM();
    initOAM(oam);
    initSprites(oam, spriteInfo);

    /* Display the backgrounds. */
    displayStarField();
    displayPlanet();
    displaySplash();

    /*
     *  Update the OAM.
     *
     *  We have to copy our copy of OAM data into the actual
     *  OAM during VBlank (writes to it are locked during
     *  other times).
     */
    swiWaitForVBlank();
    updateOAM(oam);

    return 0;
}


Code:

/*
 *  Sprites.h
 * 
 *   Adapted from Liran Nuna's sprite handling code. Special thanks to Liran
 *   Nuna.
 *
 *  Created by Jaeden Amero on 3/12/06.
 *  Copyright 2006. All rights reserved.
 *
 */

#include <nds.h>

#ifndef SPRITES_H
#define SPRITES_H

#define SPRITE_ANGLE_MASK 0x01FF
static const int SPRITE_DMA_CHANNEL = 3;

typedef struct {
    int oamId;
    int width;
    int height;
    int angle;
    SpriteEntry * entry;
} SpriteInfo;

/*
 *  updateOAM
 *
 *  Update the OAM.
 *
 */
void updateOAM(tOAM * oam);

/*
 *  initOAM
 *
 *  Initialize the OAM.
 *
 */
void initOAM(tOAM * oam);

/*
 *  rotateSprite
 *
 *  Rotate a sprite.
 *
 */
void rotateSprite(SpriteRotation * spriteRotation, u16 angle);

/*
 *  setSpriteVisibility
 *
 *  Hide or show a sprite of a certain type: affine double bound, affine
 *  non-double bound, or ordinary.
 *
 */
void setSpriteVisibility(SpriteEntry * spriteEntry, bool hidden,
                         bool affine = false, bool doubleBound = false);

#endif

Code:


/*
 *  Sprites.cpp
 * 
 *  Adapted from Liran Nuna's sprite handling code. Special thanks to Liran
 *  Nuna.
 *
 *  Created by Jaeden Amero on 3/12/06.
 *  Copyright 2006. All rights reserved.
 *
 */

#include <nds.h>
#include <nds/arm9/trig_lut.h>
#include "sprites.h"

void updateOAM(tOAM * oam) {
    DC_FlushAll();
    dmaCopyHalfWords(SPRITE_DMA_CHANNEL,
                     oam->spriteBuffer,
                     OAM,
                     SPRITE_COUNT * sizeof(SpriteEntry));
}

void initOAM(tOAM * oam) {
    /*
     * For all 128 sprites on the DS, disable and clear any attributes they
     * might have. This prevents any garbage from being displayed and gives
     * us a clean slate to work with.
     */
    for (int i = 0; i < SPRITE_COUNT; i++) {
        oam->spriteBuffer[i].attribute[0] = ATTR0_DISABLED;
        oam->spriteBuffer[i].attribute[1] = 0;
        oam->spriteBuffer[i].attribute[2] = 0;
    }
    for (int i = 0; i < MATRIX_COUNT; i++) {
        /* If you look carefully, you'll see this is that affine trasformation
         * matrix again. We initialize it to the identity matrix, as we did
         * with backgrounds
         */
        oam->matrixBuffer[i].hdx = 1 << 8;
        oam->matrixBuffer[i].hdy = 0;
        oam->matrixBuffer[i].vdx = 0;
        oam->matrixBuffer[i].vdy = 1 << 8;
    }
    /* Be sure to wait for vblank before trying to update the OAM. */
    swiWaitForVBlank();
    updateOAM(oam);
}

void rotateSprite(SpriteRotation * spriteRotation, u16 angle) {
    s16 s = SIN[angle & SPRITE_ANGLE_MASK] >> 4;
    s16 c = COS[angle & SPRITE_ANGLE_MASK] >> 4;
   
    spriteRotation->hdx = c;
    spriteRotation->hdy = s;
    spriteRotation->vdx = -s;
    spriteRotation->vdy = c;
}

void setSpriteVisibility(SpriteEntry * spriteEntry, bool hidden, bool affine,
                         bool doubleBound) {
    if (hidden) {
        /*
         * Make the sprite invisible.
         *
         * An affine sprite cannot be hidden. We have to turn it into a
         * non-affine sprite before we can hide it. To hide any sprite, we must
         * set bit 8 and clear bit 9. For non-affine sprites, this is a bit
         * redundant, but it is faster than a branch to just set it regardless
         * of whether or not it is already set.
         */
        spriteEntry->isRotoscale = false; // Bit 9 off
        spriteEntry->isHidden = true; // Bit 8 on
    } else {
        /* Make the sprite visible.*/
        if (affine) {
            /* Again, keep in mind that affine sprites cannot be hidden, so
             * enabling affine is enough to show the sprite again. We also need
             * to allow the user to get the double bound flag in the sprite
             * attribute. If we did not, then our sprite hiding function would
             * not be able to properly hide and restore double bound sprites.
             * We enable bit 9 here because we want an affine sprite.
             */
            spriteEntry->isRotoscale = true;

            /* The double bound flag only acts as the double bound flag when
             * the sprite is an affine sprite. At all other times, it acts as
             * the sprite invisibility flag. We only enable bit 8 here if we want
             * a double bound sprite. */
            spriteEntry->rsDouble = doubleBound;
        } else {
            /* Bit 9 (the affine flag) will already be off here, so we don't
             * need to clear it. However, bit 8 (the sprite invisibility flag)
             * will need to be cleared. */
            spriteEntry->isHidden = false;
        }
    }
}

#152530 - TwentySeven - Mon Mar 17, 2008 8:53 am

I havn't read the code yet, but No, its one OAM entry per screen.

What you'll probably want to do is create your own sprite entity table, and then figure out which screen your "virtual" sprite is on (based on world coordinates?) and then fill out an appropriate OAM slot.

This doesn't take the sprite graphics themselves into account, but thats the general gist of what you'll need to do.

#152536 - ben0bi - Mon Mar 17, 2008 12:18 pm

yes but how?

i don't see where i should write some changes.
is there something like sub_oam or what?

how can i determine on wich screen the sprite is?

#152539 - tepples - Mon Mar 17, 2008 1:06 pm

ben0bi wrote:
i want to use the same memory for both screens

As far as I can tell, no.

Quote:
if possible (for both, bg and sprites...if possible) - or else, how do i set up sprites for the sub engine?

The same way you do so for the main engine. You pick a VRAM bank (D or I) for the sub engine's sprite layer, load in the sprite cels (even if they're duplicate copies of the ones for the main engine), set the palette, set OAM, and then turn on the sprite layer in vramSetModeSub.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#152543 - ben0bi - Mon Mar 17, 2008 3:50 pm

i did that but it doesnt work.

videoSetModeSub(SPRITES, 1d...);
vramSetBankI(VRAM_I_SUB_SPRITE);
...

dmaCopy(...SPRITE_PALETTE_SUB[]...);
dmaCopy(...SPRITE_GFX_SUB[]...);

all other things are the same, because i really do not know what i should change more...

..??..

#168534 - renatobs - Sun May 03, 2009 5:34 am

I?d like to see the final of this...

#168538 - sverx - Mon May 04, 2009 12:02 pm

ben0bi wrote:
is there something like sub_oam or what?


... of course! Two engines, two OAMs. ( it's OAM_SUB )