domingo, 9 de abril de 2017

Last Crown Warriors #1: Building the scroll system

In the April 1 screenshotsaturday I posted a short video in which the Last Crown Warriors scrolling and collision system could be seen in action.
In the video we can see Lilian moving at a speed of 7 pixels per frame along a stage of 512 by 512 pixels. In this post I will explain the logic behind the scroll system, which as you will see below, allows a scrolling of up to 8 pixels per frame.

The first thing to understand when it comes to setting up a scrolling system for Game Boy is that, in this console, any graphical update that is performed with the LCD switched on must occur during the VBlank interrupt (also during the HBlank interrupt, although is not considered for the scroll). And this interrupt marks the beginning of a period of time in which the system is not using the video ram, period in which therefore we have available that memory in order to modify it.

This interruption occurs 59.7 times per second (one for each frame of the game) in a Game Boy. This graph represents the duration of the interrupt (4,560 clock cycles) with respect to the total time of the frame (70,224 clock cycles).

Based on this graph we can conclude that the graphical update must be highly optimized.

And the second thing we need to understand is how the background scrolling works in this system. The scrolling of the background works at a hardware level, that is, changing the value of a register can alter the position of the background we have loaded into memory. But the size of this background is limited, 256 by 256 pixels, so for maps that exceed those dimensions it will need to be modified.

The base of the scroll system will then be the redrawing of the background. And taking into account that the graphic modification should be as optimum as possible, it only remains to decide which part of the background should be updated during the interruption. In Last Crown Warriors, we update the visible column and row of the part of the background to which we are going. For example, if you go up, left, or up and left, we will update the background bar highlighted in the next image. The blue box represents the visible background.


For the rest of directions we will update the corresponding bar following the same principle: update the row and column closest to being visible. This full modification of column and row allows us to scroll the background up to 8 pixels per iteration.

Once we are clear about what tiles need to be replaced, it remains to answer the question of how to do it. As we have seen, the time available to modify the video memory is very limited. So in Last Crown Warriors we prepare a data structure with the following information, in order to minimize the time it takes to replace the tiles of the background:
  • byte 0: low byte of the address of the tile to be replaced;
  • byte 1: high byte of the address of the tile to be replaced;
  • byte 2: new tile value.
This structure is repeated for each of the tiles that we have to update, 41 in total. Thus, during the interruption of VBlank we must only execute the following code, pointing to the beginning of the previously prepared data structure.


We use REPT so that the indicated code is repeated for each of the tiles. Discarding a possible loop, which would consume additional processing.

Also it is important that in case we use other Game Boy interrupts we execute "ei" (enable interrupts) at the start of them. So we make sure that the VBlank interrupt is called at the right time and that no part of it will ever run outside the VBlank period.

And this would be a conceptual explanation of how the scrolling system works in this game. In future posts I will try to cover other aspects of the development. For these posts it will not be necessary to have an advanced knowledge of the system in order to understand the exposed ideas.

6 comentarios:

  1. Very interesting article, thanks a lot
    A few questions after re-re-reading it.
    - Do you always update 41 tiles even if the scroll only moves in one axis?
    - I guess not but since I am already asking: do you update 41 tiles in every update or just when you detect that it is required?

    Thanks Imanol!

    ResponderEliminar
    Respuestas
    1. Thanks for reading it Zalo! I'm glad you liked it.
      In fact, I have only focused on optimizing the worst-case scenario when scrolling the background. But adding that kind of checks should save battery and increase the framerate if any slowdown occurs. I will take note of that and see if it can be added.
      Regardless of how it's done, the ideal approach for that should have the background tiles to draw differentiated between the ones that go in the column and the ones that go in the row (one variable for each list). Only when the player changes from one column or row to another we update the corresponding variable and raise a certain flag.
      Although the VBlank execution must be extremely optimized, adding the flag check when updating the VRAM should not affect the performance, so we could add that to the draw_bkg_tiles routine.
      I hope this explanation serves as an addition to the concept explained in the post in terms of possible optimization.

      Eliminar
  2. Even without those checks the performance in Last Crown Warriors when updating the scroll is awesome. I have also seen than you are not updating the last visible row since it is covered by the window. This game looks really promising

    ResponderEliminar
    Respuestas
    1. Thanks Zalo! We wil keep working on it. And good luck with your projects too.

      Eliminar
  3. Something I was thinking... could this be done using DMA transfer?

    ResponderEliminar
    Respuestas
    1. The regular DMA Transfer destination is between 0xFE00-0xFE8F (OAM RAM). So sadly, no.
      Although, the Game Boy Color exclusive additional DMA Transfer destination is between 0x8000 and 0x9FFF (VRAM, SCRN0, and SCRN1). So it could be used for that kind of purpose.
      In Last Crown Warriors, I use this last transfer to update the colors of each tile at the backgrounds.
      https://gist.github.com/drhelius/3394856

      Eliminar