Background size

SGDK only sub forum

Moderator: Stef

Post Reply
tobeder
Interested
Posts: 10
Joined: Tue Sep 09, 2014 4:40 pm

Background size

Post by tobeder » Tue Oct 21, 2014 10:34 pm

Hi!

I was playing with the sprite demo included with the sgdk download. I was trying to make the "level" bigger than it actually is so I modified the following lines of main.c:

Line 25:

Code: Select all

#define MAX_POSX        FIX32(3200)
and

Line 190:

Code: Select all

// clip camera position
    if (npx_cam < FIX32(0)) npx_cam = FIX32(0);
    else if (npx_cam > FIX32(3200)) npx_cam = FIX32(3200);
    if (npy_cam < FIX32(0)) npy_cam = FIX32(0);
    else if (npy_cam > FIX32(100)) npy_cam = FIX32(100);
The "level" is bigger now but the background images aren't complete:

Imagebackground_issue by tobeder, on Flickr

djcouchycouch
Very interested
Posts: 683
Joined: Sat Feb 18, 2012 2:44 am

Post by djcouchycouch » Wed Oct 22, 2014 12:23 am

You can scroll the plane as much as you want, but the actual background data is limited to the size configuration setup in the VDP. So something like 64x32, 64x64, or 32x128. So you're seeing that the edge of the plane isn't seamless with the other edge. You're probably noticing that the level loops, right?

If you want to create levels with bigger/longer backgrounds, you have to treat the plane as a window into your level data, updating the edges of the plane as you scroll. It's a commonly used technique.

tobeder
Interested
Posts: 10
Joined: Tue Sep 09, 2014 4:40 pm

Post by tobeder » Wed Oct 22, 2014 5:57 pm

Thank you!

I see that VDP_setPlanSize can be used to change the background size.

So if I understand, I can make a bigger background image and by code I need to update the plane when the a full chunk (maybe a 32 tile width chunk) of that image has been scrolled. Maybe using u16 VDP_drawImageEx and changing the base tile index (ind).

Code: Select all

VDP_drawImageEx(APLAN, &bga_image, TILE_ATTR_FULL(PAL1, FALSE, FALSE, FALSE, ind), 0, 0, FALSE, TRUE);
    ind += bga_image.tileset->numTile;

djcouchycouch
Very interested
Posts: 683
Joined: Sat Feb 18, 2012 2:44 am

Post by djcouchycouch » Wed Oct 22, 2014 11:53 pm

Usually, you only need to update the column or row of tiles that is going to appear on screen.

If you're scrolling from right to left, you'd only need to update the column at the right of the screen.

Assuming your vertical (y) scrolling is 0 and that the plane is 64 tiles wide, the position of the column to be updated will be something like:

Code: Select all


vdp_column_to_update = ((your_scroll_position + screen_width) >> 3) & 63

">> 3" converts the pixel position to tile space in the VDP plane and the & 63 ensures that it wraps around the VDP plane.

It could be off by one. Going by memory.

And x scrolling is negative so make sure to convert it to positive before indexing into the VDP.

Then you'd copy a column from your level map data to the VDP at that position.

Stef
Very interested
Posts: 2993
Joined: Thu Nov 30, 2006 9:46 pm
Location: France - Sevres
Contact:

Post by Stef » Thu Oct 23, 2014 7:57 am

Ad you noticed i used this method to display the background in the Sonic sprite example :

Code: Select all

VDP_drawImageEx(APLAN, &bga_image, TILE_ATTR_FULL(PAL1, FALSE, FALSE, FALSE, ind), 0, 0, FALSE, TRUE);
It was ok because the size of my image was below the size of the plan and also i used it to upload the tile data to VRAM.
As djcc said, when you deal with larger map you need to update only part of the plan while you're moving your sprite.
For that you can use the following SGDK method :

Code: Select all

u16 VDP_setMapEx(u16 plan, const Map *map, u16 basetile, u16 x, u16 y, u16 xm, u16 ym, u16 wm, u16 hm);
Here's the description of this method :

Code: Select all

/**
 *  \brief
 *      Load Map region at specified position.
 *
 *  \param plan
 *      Plan where we want to load Map.<br/>
 *      Accepted values are:<br/>
 *      - VDP_PLAN_A<br/>
 *      - VDP_PLAN_B<br/>
 *  \param map
 *      Map to load.
 *  \param basetile
 *      Base index and flags for tile attributes (see TILE_ATTR_FULL() macro).
 *  \param x
 *      Plan X destination position (in tile).
 *  \param y
 *      Plan Y destination position (in tile).
 *  \param xm
 *      Map region X start position (in tile).
 *  \param ym
 *      Map region Y start position (in tile).
 *  \param wm
 *      Map region Width (in tile).
 *  \param hm
 *      Map region Heigh (in tile).
 *
 *  Load the specified Map region at specified plan position.
 */
So you may wonder what is the Map structure, actually you have it from your Image :

Code: Select all

typedef struct
{
    Palette *palette;
    TileSet *tileset;
    Map *map;
} Image;
But be careful as Image and Map can be compressed so if you don't decompress them manually before the VDP_setMapEx(..) method will decompress it every time which is time consuming !

To decompress an image :

Code: Select all

Image *unpackImage(const Image *src, Image *dest);
Don't forget to release the dynamically allocated Image when you don't need it anymore ;)

Code: Select all

MEM_free(image);

When you use the setMap(..) methods you need to ensure that all tiles are already present in VRAM, if you used the drawImage(..) method first it will be the case else you have to use VDP_loadTileSet(..) method first... I realize that i really need to write a good tutorial about all that as it can be very confusing for someone which is not used to the Megadrive hardware :?

bastien
Very interested
Posts: 208
Joined: Mon Jun 25, 2007 7:19 pm
Location: Besançon,France
Contact:

Post by bastien » Thu Oct 23, 2014 1:01 pm

Yes i also think an exemple will be very usefull :D

tobeder
Interested
Posts: 10
Joined: Tue Sep 09, 2014 4:40 pm

Post by tobeder » Fri Oct 31, 2014 5:05 pm

Hi,

I've been trying to display correctly the background with your suggestions. I made some advance but seems that I need to reset something when the background reaches the width of the plan, because its loading the background but one tile below position 0 each time. Here's is the code:

Code: Select all

#include <genesis.h>

#include "gfx.h"
#include "music.h"

#define ANIM_STAND      0
#define ANIM_WAIT       1
#define ANIM_WALK       2
#define ANIM_RUN        3
#define ANIM_BRAKE      4
#define ANIM_UP         5
#define ANIM_CROUNCH    6
#define ANIM_ROLL       7

#define MAX_SPEED       FIX32(8)
#define RUN_SPEED       FIX32(6)
#define BRAKE_SPEED     FIX32(2)

#define JUMP_SPEED      FIX32(-8)
#define GRAVITY         FIX32(0.4)
#define ACCEL           FIX32(0.1)
#define DE_ACCEL        FIX32(0.15)

#define MIN_POSX        FIX32(10)
#define MAX_POSX        FIX32(3200)
#define MAX_POSY        FIX32(156)
#define WIDTH			FIX32(600)

// forward
static void handleInput();
static void joyEvent(u16 joy, u16 changed, u16 state);

static void updatePhysic();
static void updateAnim();
static void updateCamera(fix32 x, fix32 y);

// sprites structure
Sprite sprites[2];

fix32 tilesetX = 320;
fix32 camposx;
fix32 camposy;
fix32 posx;
fix32 posy;
fix32 movx;
fix32 movy;
fix32 limite = 11420;
s16 xorder;
s16 yorder;
s16 dibujar = 0;
s16 posXTxt = 200;
s16 posYTxt = 0;
char text[8];
Image* decompressed;
Image* test = NULL;

int main()
{
	u16 x = 0;
    u16 palette[64];
    u16 ind;
	u16 bgWidth = 32;
	u16 bgHeight = 32;
	u16 txtPosX = 10;
	u16 txtPosY = 13;

    // disable interrupt when accessing VDP
    SYS_disableInts();
    // initialization
    VDP_setScreenWidth320();
	VDP_setPlanSize(bgWidth, bgHeight);

    // start music
    SND_startPlay_VGM(sonic_music);

    // init sprites engine
    SPR_init(256);

    // set all palette to black
    VDP_setPaletteColors(0, palette_black, 64);
    
	VDP_loadTileSet(bga_image.tileset, WIDTH, TRUE);
	
	Map *map = unpackMap(bga_image.map, NULL); 

    // VDP process done, we can re enable interrupts
    SYS_enableInts();

    camposx = -1;
    camposy = -1;
    posx = FIX32(48);
    posy = MAX_POSY;
    movx = FIX32(0);
    movy = FIX32(0);
    xorder = 0;
    yorder = 0;

    // init scrolling
    updateCamera(FIX32(0), FIX32(0));

    // init sonic sprite
    SPR_initSprite(&sprites[0], &sonic_sprite, fix32ToInt(posx - camposx), fix32ToInt(posy - camposy), TILE_ATTR(PAL2, TRUE, FALSE, FALSE));
    SPR_update(sprites, 1);

    // prepare palettes
    //memcpy(&palette[0], bgb_image.palette->data, 16 * 2);
    memcpy(&palette[0], bga_image.palette->data, 16 * 2);
    memcpy(&palette[32], sonic_sprite.palette->data, 16 * 2);

    // fade in (disable interrupts because of the passive fade in)
    SYS_disableInts();
    VDP_fadeIn(0, (3 * 16) - 1, palette, 20, FALSE);
    SYS_enableInts();

    JOY_setEventHandler(joyEvent);

    while(TRUE)
    {
		intToStr((fix32ToInt(posx)/8), text, 2);
        handleInput();

        updatePhysic();
        updateAnim();
		SYS_disableInts();
		VDP_drawText(text, 10, 13);
		VDP_setMapEx(APLAN, map, TILE_ATTR(PAL0, FALSE, FALSE, FALSE), (fix32ToInt(posx)/8)-1, 0, (fix32ToInt(posx)/8)-1, 0, 1, 32); 
		SYS_enableInts();
		
        // update sprites (only one to update here)
        SPR_update(sprites, 1);

        VDP_waitVSync();
    }
    MEM_free(map);
    return 0;
}

static void updatePhysic()
{
    if (xorder > 0)
    {
        movx += ACCEL;
        // going opposite side, quick breaking
        if (movx < 0) movx += ACCEL;

        if (movx >= MAX_SPEED) movx = MAX_SPEED;
    }
    else if (xorder < 0)
    {
        movx -= ACCEL;
        // going opposite side, quick breaking
        if (movx > 0) movx -= ACCEL;

        if (movx <= -MAX_SPEED) movx = -MAX_SPEED;
    }
    else
    {
        if ((movx < FIX32(0.1)) && (movx > FIX32(-0.1)))
            movx = 0;
        else if ((movx < FIX32(0.3)) && (movx > FIX32(-0.3)))
            movx -= movx >> 2;
        else if ((movx < FIX32(1)) && (movx > FIX32(-1)))
            movx -= movx >> 3;
        else
            movx -= movx >> 4;
    }

    posx += movx;
    posy += movy;

    if (movy)
    {
        if (posy > MAX_POSY)
        {
            posy = MAX_POSY;
            movy = 0;
        }
        else movy += GRAVITY;
    }

    if (posx >= MAX_POSX)
    {
        posx = MAX_POSX;
        movx = 0;
    }
    else if (posx <= MIN_POSX)
    {
        posx = MIN_POSX;
        movx = 0;
    }

    fix32 px_scr, py_scr;
    fix32 npx_cam, npy_cam;

    // get sprite position on screen
    px_scr = posx - camposx;
    py_scr = posy - camposy;

    // calculate new camera position
    if (px_scr > FIX32(240)) npx_cam = posx - FIX32(240);
    else if (px_scr < FIX32(40)) npx_cam = posx - FIX32(40);
    else npx_cam = camposx;
	
    if (py_scr > FIX32(160)) npy_cam = posy - FIX32(160);
    else if (py_scr < FIX32(100)) npy_cam = posy - FIX32(100);
    else npy_cam = camposy;

    // clip camera position
    if (npx_cam < FIX32(0)) npx_cam = FIX32(0);
    else if (npx_cam > FIX32(3200)) npx_cam = FIX32(3200);
    if (npy_cam < FIX32(0)) npy_cam = FIX32(0);
    else if (npy_cam > FIX32(100)) npy_cam = FIX32(100);

    // set camera position
    updateCamera(npx_cam, npy_cam);
    // set sprite position
    SPR_setPosition(&sprites[0], fix32ToInt(posx - camposx), fix32ToInt(posy - camposy));
}

static void updateAnim()
{
    // jumping
    if (movy) SPR_setAnim(&sprites[0], ANIM_ROLL);
    else
    {
        if (((movx >= BRAKE_SPEED) && (xorder < 0)) || ((movx <= -BRAKE_SPEED) && (xorder > 0)))
            SPR_setAnim(&sprites[0], ANIM_BRAKE);
        else if ((movx >= RUN_SPEED) || (movx <= -RUN_SPEED))
            SPR_setAnim(&sprites[0], ANIM_RUN);
        else if (movx != 0)
            SPR_setAnim(&sprites[0], ANIM_WALK);
        else
        {
            if (yorder < 0)
                SPR_setAnim(&sprites[0], ANIM_UP);
            else if (yorder > 0)
                SPR_setAnim(&sprites[0], ANIM_CROUNCH);
            else
                SPR_setAnim(&sprites[0], ANIM_STAND);
        }
    }

    if (movx > 0)
        SPR_setAttribut(&sprites[0], TILE_ATTR(PAL2, TRUE, FALSE, FALSE));
    else if (movx < 0)
        SPR_setAttribut(&sprites[0], TILE_ATTR(PAL2, TRUE, FALSE, TRUE));
}

static void updateCamera(fix32 x, fix32 y)
{
    if ((x != camposx) || (y != camposy))
    {
        camposx = x;
        camposy = y;
        VDP_setHorizontalScroll(PLAN_A, fix32ToInt(-camposx));
        VDP_setHorizontalScroll(PLAN_B, fix32ToInt(-camposx) >> 3);
        VDP_setVerticalScroll(PLAN_A, fix32ToInt(camposy));
        VDP_setVerticalScroll(PLAN_B, fix32ToInt(camposy) >> 3);
    }
}


static void handleInput()
{
    u16 value = JOY_readJoypad(JOY_1);

    if (value & BUTTON_UP) yorder = -1;
    else if (value & BUTTON_DOWN) yorder = +1;
    else yorder = 0;

    if (value & BUTTON_LEFT) xorder = -1;
    else if (value & BUTTON_RIGHT) xorder = +1;
    else xorder = 0;
}

static void joyEvent(u16 joy, u16 changed, u16 state)
{
    // START button state changed
    if (changed & BUTTON_START)
    {

    }

    if (changed & state & (BUTTON_A | BUTTON_B | BUTTON_C))
    {
        if (movy == 0) movy = JUMP_SPEED;
    }
}

djcouchycouch
Very interested
Posts: 683
Joined: Sat Feb 18, 2012 2:44 am

Post by djcouchycouch » Fri Oct 31, 2014 6:16 pm

I think it's because you're going over the plan width. You need to mod (%) the position you're writing to so that it lands properly into the plane.

change

Code: Select all

VDP_setMapEx(APLAN, map, TILE_ATTR(PAL0, FALSE, FALSE, FALSE), (fix32ToInt(posx)/8)-1, 0, (fix32ToInt(posx)/8)-1, 0, 1, 32);
to

Code: Select all

VDP_setMapEx(APLAN, map, TILE_ATTR(PAL0, FALSE, FALSE, FALSE), ((fix32ToInt(posx)/8)-1) % 32, 0, ((fix32ToInt(posx)/8)-1) %32, 0, 1, 32);

but using % is expensive, so you can do this instead

Code: Select all

VDP_setMapEx(APLAN, map, TILE_ATTR(PAL0, FALSE, FALSE, FALSE), ((fix32ToInt(posx)/8)-1) & 31, 0, ((fix32ToInt(posx)/8)-1) &31, 0, 1, 32);

A width of 32 doesn't cover the whole screen. Is this what you wanted?
Last edited by djcouchycouch on Mon Nov 03, 2014 11:41 pm, edited 1 time in total.

tobeder
Interested
Posts: 10
Joined: Tue Sep 09, 2014 4:40 pm

Post by tobeder » Mon Nov 03, 2014 6:06 pm

Hi,

I made the changes with & 31 and & 127. My background image is 680 px wide and with the "& 127" modification I can't display the entire image. So maybe I need to charge another background image (maybe is less memory consuming) when the posx is bigger than 128 tiles each time or I need to place another part of the map when this happens.

I would like to display a background that covers the entire screen, the 32 value was just for learning purposes.

Thanks!

djcouchycouch
Very interested
Posts: 683
Joined: Sat Feb 18, 2012 2:44 am

Post by djcouchycouch » Mon Nov 03, 2014 11:54 pm

In the code you originally posted, the call to VDP_setPlanSize sets the background plane size to 32x32 tiles. At this setting, it's too small to fit your 680 pixel image. You'd have to set the plane size to 128x32 which would give you a maximum size of 1024 x 256. If your image fits into that, then you can scroll the plane around without having to update anything.

Note that the plane size settings are 32, 64 and 128 but that they can only work in combinations that are less than 4096. 32x32 and 64x64 work, but not 64*128 or 128x128.

tobeder
Interested
Posts: 10
Joined: Tue Sep 09, 2014 4:40 pm

Post by tobeder » Tue Nov 04, 2014 12:37 am

djcouchycouch wrote:In the code you originally posted, the call to VDP_setPlanSize sets the background plane size to 32x32 tiles. At this setting, it's too small to fit your 680 pixel image. You'd have to set the plane size to 128x32 which would give you a maximum size of 1024 x 256. If your image fits into that, then you can scroll the plane around without having to update anything.

Note that the plane size settings are 32, 64 and 128 but that they can only work in combinations that are less than 4096. 32x32 and 64x64 work, but not 64*128 or 128x128.
Yes, but in the case that I wanted to make a 5000 px. wide background, do I need to reload?

Code: Select all

VDP_loadTileSet(bga_image.tileset, WIDTH, TRUE); 
    
Map *map = unpackMap(bga_image.map, NULL); 
when the player/camera reaches the end of the first background.

Or how can I handle both maps loaded/displayed at the same time? :?

Thank you! :)

Post Reply