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 > large scrolling backgrounds

#171754 - radiodee1 - Tue Dec 22, 2009 1:23 am

HI,

Is it anywhere discussed how to use maps for backgrounds that are bigger than your "official" background dimensions? I want to place a 64x64 tile map on a 64x32 background. I want to allow the player to scroll around the screen and see areas that were previously not mapped into vram by copying in the values as needed from the larger map. Obviously I'm having problems. I can place my map on the screen, scroll around, but when I try to copy from the larger map I have trouble. What's the preferred method? do you use the built in scroll registers and then copy only areas that you feel are about to be visited, or do you not use the scroll registers and copy a whole 32x24 chunk every time the user moves? I could see how the 'whole chunk' method could work but it would be very jerky, wouldn't it?

#171771 - sverx - Tue Dec 22, 2009 4:13 pm

Actually in my only test program with a larger (say 150x150) map I'm using two 32x32 map and refilling the whole of the hidden one (the one I'm not showing on screen) every frame. May not be the best way but it's simple.

#171773 - gauauu - Tue Dec 22, 2009 5:28 pm

This has been discussed a number of times here. Do a search for scrolling backgrounds.

This thread, in particular, looks like it might be helpful:
http://forum.gbadev.org/viewtopic.php?t=16730&highlight=scrolling+backgrounds

#171784 - radiodee1 - Wed Dec 23, 2009 12:37 pm

Thanks alot.

#171822 - sgeos - Sun Dec 27, 2009 12:53 am

sverx wrote:
Actually in my only test program with a larger (say 150x150) map I'm using two 32x32 map and refilling the whole of the hidden one (the one I'm not showing on screen) every frame. May not be the best way but it's simple.

Wow. And I futzed around with trying to figure out what to refresh when the viewport moved. I hail your unoptimized simplicity.

Using this method, you can split the screen into five regions.
Code:

11111111
22000033
22000033
44444444

0- The viewport (everything on screen).
1- Everything above the viewport.
2- Everything level with the viewport, but offscreen to the left.
3- Everything level with the viewport, but offscreen to the right.
4- Everything below the viewport.

Every frame, update your viewport position and then copy what you need from your logical map into physical memory.
Regions 1 and 4 can be efficiently copied into place.
Regions 2 and 3 are less efficient, but the principle is the same; I suppose you could combine them into one loop the skips over the viewport.
If you use a 64x32 sized map, you will have enough space unless your metatiles are huge.
Unless you are moving really fast, this system should be pretty solid.
The optimizations are to shrink regions 1 ~ 4, and to only update when the viewport moves.

Keep in mind that all of this is going on in modular space, so your physical ram could have any of these layouts at any given time:
Code:
00003322
00003322
44444444
11111111

00332200
44444444
11111111
00332200

44444444
11111111
03322000
03322000

#171825 - radiodee1 - Sun Dec 27, 2009 3:50 pm

HI,

I've been trying to work out a solution similar to sgeos post. Here's a function. believe it or not, this is pared down. The original is larger.
Code:

/* level            a global struct
 * level.scrollX    the current contents of the scroll H register
 * level.scrollY    the current contents of the scroll V register
 * scrollX          what I am going to set the scroll H register to next
 * scrollY          what I am going to set the scroll V register to next
 * level.level1[][] the larger map that I have stored in memory (75w x 40h)
 * level.mapH       75
 * level.mapV       40
 * SCREEN_TILES_V   32
 * SCREEN_TILES_H   64
 *
 * This function is called every time the character moves, and just before the
 * background is scrolled with bgSetScroll(). The problem I'm having is that if
 * the character moves quickly around the edges of the screen, especially the
 * bottom edge, junk appears at the bottom edge of the screen. This happens if
 * the character's movement makes the screen scroll away from and then back to
 * an item on the map very quickly.
 */

void bgMapScroll(int scrollX, int scrollY) {
  int topArea, bottomArea, leftArea, rightArea;
  int mapCheat = 1;
  int i,j,k, p, m;
 
  topArea = (level.scrollY / 8) - 1;
  bottomArea = (level.scrollY / 8) + (24 ) - 1 ;
  leftArea = (level.scrollX / 8) - 1;
  rightArea = (level.scrollX / 8) + (32  ) - 1 ;
 
  u16* mapMemory = (u16*)BG_MAP_RAM(16 );
 
  // left/right
  if (scrollX > level.scrollX ) {
    //scroll right... replace at right.
    if ( (scrollX & 7) == 0 ) {
      for(i = level.scrollY / 8 - 4; i< (level.scrollY/8) + SCREEN_TILES_V + 4 ; i ++) { // V
        for ( j = rightArea; j < rightArea + 4; j ++) {       
          if (i < level.mapV && j < level.mapH && i >= 0 && j >= 0) {

            if ( (( j)& 63 ) >= 32) p = 32 * 32;
            else p = 0;
            k = p + ((i & 31) * 32 ) + ((j) & 31);
            m = level.level1[i][j];
            mapMemory[k] = m;
          }
        }
      }
    }
  }
  else if (scrollX < level.scrollX ) {
    //scroll left... replace at left.
    if ((scrollX & 7) == 0 ) {
      for(i = level.scrollY / 8 - 4; i< (level.scrollY/8) + SCREEN_TILES_V + 4; i ++) {
        for ( j = leftArea - 4; j < leftArea ; j ++) {
          if (i < level.mapV && j < level.mapH && i >= 0 && j >= 0) {

            if (( (j) & 63) >= 32) p = 32 * 32;
            else p = 0;
            k = p + ((i & 31) * 32 ) + (j & 31);
            m = level.level1[i ][j];
            mapMemory[k] = m;
          }
        }
      }
    }
  }
 
  // up/down
  if (scrollY > level.scrollY) {
    //scroll down... replace at bottom.
    if ((scrollY & 7) == 0  ) {
      for(j = level.scrollY / 8 - 4; j < (level.scrollY/8) + SCREEN_TILES_H + 4; j ++) {
        for ( i = bottomArea; i < bottomArea + 4; i ++) {
          if (i < level.mapV && j < level.mapH && i >= 0 && j >= 0) {

            if (( (j) & 63) >= 32) p = 32 * 32;
            else p = 0;
            k = p + ((i&31) * 32 ) + (j & 31);
            m = level.level1[i ][j];
            mapMemory[k] = m;
          }
        }
      }
    }
  }
  else if (scrollY < level.scrollY) {
    //scroll up... replace at top.
    if ((scrollY & 7) == 0 ) {
      for(j = level.scrollY / 8 - 4 ; j < (level.scrollY/8) + SCREEN_TILES_H + 4; j ++) {
        for ( i = topArea - 4; i < topArea ; i ++) {
          if (i < level.mapV && j < level.mapH && i >= 0 && j >= 0) {   

            if (( (j) & 63) >= 32) p = 32 * 32;
            else p = 0;
            k = p + ((i & 31 ) * 32 ) + (j & 31);
            m = level.level1[i ][j];
            mapMemory[k] = m;
          }
        }
      }
    }
  }
   
  return;
}

I'm obviously still having problems with this. If anyone can spot a glaring error, please drop me a line. My coding style may leave a little to be desired also. Thanks in advance.

#171833 - sverx - Mon Dec 28, 2009 2:14 pm

sgeos wrote:
I hail your unoptimized simplicity.


lol! :) I think it's because I'm a beginner almost completely self-taught, which shows its limits quite often... you know...

For instance I'm not sure I really understood your explanation. In the code I'm talking about I'm doing something really really simple: showing just a part of the 'big' map (say 150x150) on a 32x32 (well, only 24 lines out of 32 actually...) writing to each of these locations the value found at (x+dx,y+dy) of the 'big' map. Or a specific value (say zero) if x+dx or y+dy is out of bounds.
All this on a bg map not accessed by the 2D core, so that I'll be sure what's onscreen doesn't change because I'm late in the VBlank...

(btw I'm also not using scrolling at all, because I didn't need it, so I didn't use larger bg maps like 64x32 ... and it was just a test program, also)

#171853 - radiodee1 - Tue Dec 29, 2009 10:17 pm

well, I've determined that the code that read (in two places)
Code:
      for(j = level.scrollY / 8 - 4; j < (level.scrollY/8) + SCREEN_TILES_H + 4; j ++) {

should probably read
Code:
      for(j = level.scrollX / 8 - 4; j < (level.scrollX/8) + SCREEN_TILES_H + 4; j ++) {

and this solves some of my problems. Now, when I move directly in any given direction the screen scrolls properly, but I seem to still have some problems. I know from observation that the screen doesn't scroll properly all the time, and I think it's from when I'm moving in two directions at once. Has anyone ever done this before? I mean, I know it's possible, but is it possible by me? I seem to see a lot of discussion about scrolling in one direction, but not two.

#171864 - wintermute - Wed Dec 30, 2009 12:43 pm

You just need to update the corner when you're moving diagonally - you have one 32 tile row updated for the vertical scroll and one 24 tile column for the horizontal scroll.
_________________
devkitPro - professional toolchains at amateur prices
devkitPro IRC support
Personal Blog

#171868 - sverx - Wed Dec 30, 2009 2:52 pm

radiodee1 wrote:
I seem to see a lot of discussion about scrolling in one direction, but not two.


well, basically you're scrolling left or right and after that -but in the same frame- you also scroll up or down, so I don't see what could be wrong...

#171869 - Dwedit - Wed Dec 30, 2009 3:04 pm

Yeah, you'd need to update a 33 length row, or a 25 length column in order to handle diagonal scrolling.
_________________
"We are merely sprites that dance at the beck and call of our button pressing overlord."

#171880 - radiodee1 - Thu Dec 31, 2009 12:42 am

I got it to work finally. I had to remove the code that checked in this way:
Code:
if ((scrollX & 7) == 0) {

and
Code:
if ((scrollY & 7) == 0) {

in all the places that it showed up, along with the closing "}" marks. I think this is because the angle and speed of the diagonal movement wasn't always the same. I think I also set SCREEN_TILES_V to 24 and SCREEN_TILES_H to 32. Thanks for all the help. It finally worked. :)

#171932 - sgeos - Sun Jan 03, 2010 6:02 am

Re: sverx
So you are double buffering your map and refreshing the whole visible part of the back buffer each frame. I hail your even more unoptimized simplicity. Having a picture of "the solution" in your head is not always a good thing.

Re: radiodee1
Here is a visual representation of diagonal movement. As sverx said, you update in one direction at a time, so it is no different from updating a single direction. Note that update order is unimportant, but pay close attention to the location of the second update in each case. One off screen diagonal is updated and one is not.
Code:
-X First-   -Y First-

1111*000    11110000
1111*000    11110000
00000000    ****0000
00000000    00000000

12222000    11110000
12222000    2222*000
0****000    2222*000
00000000    00000000

12222000    11110000
13333000    23333000
03333000    23333000
00000000    00000000

* = Updated Locations
0 = Off Screen
1 = On Screen Last Frame
2 = On Screen After Intermediate Update
3 = On Screen Next Frame

As for style, when and if you refactor your code, I'd break the large routine up into a few smaller ones.

#172100 - dovoto - Thu Jan 14, 2010 11:53 pm

Not sure if it is useful but there are examples included with libnds which demonstrate scrolling, one in particular does just about every sort of simple 2/4 way scrolling.

http://libnds.devkitpro.org/a00003.html

each function is a self contained demo, i recommend:

void scroll4wayText(void)

and

void scroll4wayExRotation(void)

The DS is more than capable of doing all 4 backgrounds by a simple brute force "redraw the entire background map each frame" which is what i recommend as a first time approach. The above only draws the part of the background that is about to be on screen which is more efficient but somewhat of a headache to visualize.
_________________
www.drunkencoders.com