Affine bg | Contents | DMA |
So you know how to put sprites and backgrounds on screen, do ya? Now, how about some extra effects to liven up the place? When discussing sprites and backgrounds, we left some flags untouched, namely the mosaic and blending flags. There will be covered here. We'll also be looking into windowing, with which you can create regions to mask out backgrounds or sprites.
The best description of mosaic is that it makes sprites or tiles look blocky. A mosaic works in two dimensions with parameters wm and hm. These numbers divide your sprite or background into blocks of wm × hm pixels. The top-left pixel of each block is used to fill the rest of that block, which makes it blocky. Fig 13.1 shows a 1x4 mosaic for a metroid sprite. The blue lines indicate the vertical block-boundaries. The first line of each block is copied to the rest of the block, just like I said. Other examples of the mosaic effect are Zelda:LTTP when you hit an electric baddie, or Metroid Fusion when an X changes shape.
To use mosaic you must do two things. First, you need to
enable mosaic. For individual sprites, set
OBJ_ATTR.attr0
{C}. For backgrounds, set
REG_BGxCNT
{7}. The set the mosaic levels through
REG_MOSAIC
, which looks like this:
F E D C | B A 9 8 | 7 6 5 4 | 3 2 1 0 |
Ov | Oh | Bv | Bh |
bits | name | define | description |
---|---|---|---|
0-3 | Bh | MOS_BH# | Horizontal BG stretch. |
4-7 | Bv | MOS_BV# | Vertical BG stretch. |
8-B | Oh | MOS_OH# | Horizontal object stretch. |
C-F | Ov | MOS_OV# | Vertical object stretch. |
The stretch is across how many pixels the base-pixel is stretched. This corresponds to wm−1 or hm−1. With a nybble for each effect, you have stretches between 0 and 15, giving mosaic widths and heights between 1 and 16.
For backgrounds, set bit 7 of REG_BGxCNT. For sprites, set bit 12 in attribute 0. Then set the mosaic levels in REG_MOSAIC.
There is a demo called mos_demo that illustrates the use of mosaic for both objects and backgrounds.
// mos_demo.c // bg 0, cbb 0, sbb 31, pb 0: text // bg 1, cbb 1, sbb 30, pb 1: bg metroid // oam 0: tile 0-63: obj metroid #include <stdio.h> #include <tonc.h> #include "metr.h" void test_mosaic() { tte_printf("#{P:48,8}obj#{P:168,8}bg"); tte_set_margins(4, 130, 128, 156); POINT pt_obj={0,0}, pt_bg={0,0}; POINT *ppt= &pt_obj; while(1) { vid_vsync(); // control the mosaic key_poll(); // switch between bg or obj mosaic ppt= key_is_down(KEY_A) ? &pt_bg : &pt_obj; ppt->x += key_tri_horz(); // inc/dec h-mosaic ppt->y -= key_tri_vert(); // inc/dec v-mosaic ppt->x= clamp(ppt->x, 0, 0x80); ppt->y= clamp(ppt->y, 0, 0x80); REG_MOSAIC= MOS_BUILD(pt_bg.x>>3, pt_bg.y>>3, pt_obj.x>>3, pt_obj.y>>3); tte_printf("#{es;P}obj h,v: %2d,%2d\n bg h,v: %2d,%2d", pt_obj.x>>3, pt_obj.y>>3, pt_bg.x>>3, pt_bg.y>>3); } } void load_metr() { int ix, iy; memcpy32(&tile_mem[1][0], metrTiles, metrTilesLen/4); memcpy32(&tile_mem[4][0], metrTiles, metrTilesLen/4); memcpy32(pal_obj_mem, metrPal, metrPalLen/4); // create object: oe0 OBJ_ATTR *metr= &oam_mem[0]; obj_set_attr(metr, ATTR0_SQUARE | ATTR0_MOSAIC, ATTR1_SIZE_64, 0); obj_set_pos(metr, 32, 24); // left-center // create bg map: bg1, cbb1, sbb 31 for(ix=1; ix<16; ix++) pal_bg_mem[ix+16]= pal_obj_mem[ix] ^ CLR_WHITE; SCR_ENTRY *pse= &se_mem[30][3*32+18]; // right-center for(iy=0; iy<8; iy++) for(ix=0; ix<8; ix++) pse[iy*32+ix]= (iy*8+ix) | SE_PALBANK(1); REG_BG1CNT= BG_CBB(1) | BG_SBB(30) | BG_MOSAIC; } int main() { // setup sprite oam_init(oam_mem, 128); load_metr(); REG_DISPCNT= DCNT_BG0 | DCNT_BG1 | DCNT_OBJ | DCNT_OBJ_1D; // set-up text: bg0, cbb0, sbb31 tte_init_chr4_b4_default(0, BG_CBB(2)|BG_SBB(31)); tte_init_con(); test_mosaic(); return 0; }
I use two metroids in this demo. The sprite metroid is on the left,
and the background metroid with inverted colors is on the right.
I've shown how to set-up sprites and backgrounds before, so you
should be able to follow the steps here because it's nothing new.
Well, except setting the mosaic flags in
OBJ_ATTR.attr0
and REG_BG0CNT, which I've put in bold
here.
The mosaic effect is regulated inside the test_mosaic()
.
I use two 2d points to keep track of the current level of mosaic.
The D-pad is used to increase or decrease the mosaic levels; just the
D-pad sets the object's mosaic and holding down A sets that of the
background.
On a code design note, I could have used two if-blocks here, one for objects and one for the background, but I can also switch the mosaic context via a pointer, which saves me some code. Hurray for pointers. Also, the coordinates are in .3 fixed point format, which is how I slow down the changes in the mosaic levels. Again, I could have used timer variables and more checks to see if they had reached their thresholds, but fixed-point timers are much easier and in my view cleaner too.
You should really see the demo on hardware, by the way. Somehow both VBA and no$gba are both flawed when it comes to mosaic. After VBA 1.7.2, it has a problem with horizontal sprite mosaic. I do believe I've seen inconsistencies between hardware and scrolling mosaiced backgrounds, but can't remember where I saw it. As for no$gba, vertical mosaic appears to be disabled for both sprites and backgrounds.
VBA and no$gba, the most popular GBA emulators both have problems with mosaic. Watch your step.
If you're not completely new to gaming or graphics, you may have heard of alpha blending. It allows you to combine the color values two overlapping layers, thus creating transparency (also known as semi-transparency, because something that's completely transparent is invisible). Some bitmap types also come with an alpha channel, which indicates either the transparency or opacity of the pixel in question.
The basic idea behind blending is this. You have two layers, A and B, that overlap each other. Consider A to be on top of B. The color-value of the a pixel in this region is defined as
(13.1) | C = wA·A + wB·B, |
where wA and wB are the weights of the layers. The weights are generally normalised (between 0 and 1), with 0 being fully transparent and 1 being fully visible. It is also convenient to think of color-components in this way. Here's a few things you can do with them:
wA | wB | effect |
---|---|---|
1 | 0 | layer A fully visible (hides B; standard) |
0 | 1 | layer B fully visible (or A is invisible) |
α | 1−α | Alpha blending. α is opacity in this case. |
Note that in these examples the sum of the weights is 1, so that the final color C is between 0 (black) and 1 (white) as well. As we'll see, there are instances where you can drop out of these ranges; if this happens the values will be clipped to the standard range.
Backgrounds are always enabled for blending. To enable sprite-blending,
set OBJ_ATTR.attr0
{a}. There are three registers
that control blending, which unfortunately go by many different names.
The ones I use are REG_BLDCNT
, REG_BLDALPHA
and REG_BLDY
. Other names are REG_BLDMOD
,
REG_COLEV
and REG_COLEY
, and sometimes the
‘E’ in the last two is removed. Be warned. Anyway, the
first says how and on which layers the blend should be performed, the
last two contain the weights. Oh, since the GBA doesn't do floating
point, the weights are
fixed-point numbers in 1.4
format. Still limited by 0 and 1, of course, so there are 17 blend
levels.
F E | D | C | B | A | 9 | 8 | 7 6 | 5 | 4 | 3 | 2 | 1 | 0 |
- | bBD | bOBJ | bBG3 | bBG2 | bBG1 | bBG0 | BM | aBD | aObj | aBG3 | aBG2 | aBG1 | aBG0 |
bits | name | define | description |
---|---|---|---|
0-5 | aBG0-aBD | BLD_TOP# | The A (top) layers. BD, by the way, is the back drop, a solid plane of color 0. Set the bits to make that layer use the A-weights. Note that these layers must actually be in front of the B-layers, or the blend will fail. |
6-7 | BM | BLD_OFF, BLD_STD, BLD_WHITE, BLD_BLACK, BLD_MODE# | Blending mode.
|
8-D | bBG0-bBD | BLD_BOT# | The B (bottom) layers. Use the B-weights. Note that these layers must actually lie behind the A-layers, or the blend will not work. |
The REG_BLDALPHA
and REG_BLDY
registers
hold the blending weights in the form of
eva,
evb and
ey,
all in 1.4 fixed-point format. And no, I do not know why they are
called that; they just are.
F E D | C B A 9 8 | 7 6 5 | 4 3 2 1 0 |
- | evb | - | eva |
bits | name | define | description |
---|---|---|---|
0-4 | eva | BLD_EVA# | Top blend weight. Only used for normal blending |
8-C | evb | BLD_EVB# | Bottom blend weight. Only used for normal blending |
F E D C B A 9 8 7 6 5 | 4 3 2 1 0 |
- | ey |
bits | name | define | description |
---|---|---|---|
0-4 | ey | BLDY# | Top blend fade. Used for white and black fades. |
Blending is a nice feature to have, but keep these points in mind.
REG_BLDCNT
{6,7} is
applied only to the non-overlapping sections (so that
effectively only fading works). For the overlapping pixels, the
standard blend is always in effect, regardless of the
current blend-mode.
// bld_demo.c // bg 0, cbb 0, sbb 31, pb 15: text // bg 1, cbb 2, sbb 30, pb 1: metroid // bg 2, cbb 2, sbb 29, pb 0: fence // oam 0: tile 0-63: obj metroid #include <stdio.h> #include <tonc.h> #include "../gfx/metr.h" void test_blend() { tte_printf("#{P:48,8}obj#{P:168,8}bg"); tte_set_margins(16, SCR_H-4-4*12, SCR_W-4, SCR_H-4); u32 mode=0; // eva, evb and ey are .4 fixeds // eva is full, evb and ey are empty u32 eva=0x80, evb= 0, ey=0; REG_BLDCNT= BLD_BUILD( BLD_OBJ | BLD_BG0, // Top layers BLD_BG1, // Bottom layers mode); // Mode while(1) { vid_vsync(); key_poll(); // Interactive blend weights eva += key_tri_horz(); evb -= key_tri_vert(); ey += key_tri_fire(); mode += bit_tribool(key_hit(-1), KI_R, KI_L); // Clamp to allowable ranges eva = clamp(eva, 0, 0x81); evb = clamp(evb, 0, 0x81); ey = clamp(ey, 0, 0x81); mode= clamp(mode, 0, 4); tte_printf("#{es;P}mode :\t%2d\neva :\t%2d\nevb :\t%2d\ney :\t%2d", mode, eva/8, evb/8, ey/8); // Update blend mode BFN_SET(REG_BLDCNT, mode, BLD_MODE); // Update blend weights REG_BLDALPHA= BLDA_BUILD(eva/8, evb/8); REG_BLDY= BLDY_BUILD(ey/8); } } void load_metr() { // copy sprite and bg tiles, and the sprite palette memcpy32(&tile_mem[2][0], metrTiles, metrTilesLen/4); memcpy32(&tile_mem[4][0], metrTiles, metrTilesLen/4); memcpy32(pal_obj_mem, metrPal, metrPalLen/4); // set the metroid sprite OBJ_ATTR *metr= &oam_mem[0]; // use the first sprite obj_set_attr(metr, ATTR0_SQUARE | ATTR0_BLEND, ATTR1_SIZE_64, 0); obj_set_pos(metr, 32, 24); // mid-center // create the metroid bg // using inverted palette for bg-metroid int ix, iy; for(ix=0; ix<16; ix++) pal_bg_mem[ix+16]= pal_obj_mem[ix] ^ CLR_WHITE; SCR_ENTRY *pse= &se_mem[30][3*32+18]; // right-center for(iy=0; iy<8; iy++) for(ix=0; ix<8; ix++) pse[iy*32+ix]= iy*8+ix + SE_PALBANK(1); REG_BG0CNT= BG_CBB(0) | BG_SBB(30); } // set-up the fence background void load_fence() { // tile 0 / ' ' will be a fence tile const TILE fence= {{ 0x00012000, 0x00012000, 0x00022200, 0x22220222, 0x11122211, 0x00112000, 0x00012000, 0x00012000, }}; tile_mem[2][64]= fence; se_fill(se_mem[29], 64); pal_bg_mem[0]= RGB15(16, 10, 20); pal_bg_mem[1]= RGB15( 0, 0, 31); pal_bg_mem[2]= RGB15(16, 16, 16); REG_BG2CNT= BG_CBB(2) | BG_SBB(29); } int main() { oam_init(oam_mem, 128); load_metr(); load_fence(); tte_init_chr4_b4_default(0, BG_CBB(0)|BG_SBB(31)); tte_init_con(); REG_DISPCNT= DCNT_MODE0 | DCNT_BG0 | DCNT_BG1 | DCNT_BG2 | DCNT_OBJ | DCNT_OBJ_1D; test_blend(); return 0; }
As always, there's a demo that goes with all this stuff. bld_demo features 2 metroids (the left one is a sprite, the right one (palette inverted) is on background 0) on a fence-like background (bg 1 to be precise) and lets you modify the mode, and the 3 weights independently. The mode, by the way, is given in the top left corner. The controls are:
left, right | changes eva . Note that
eva is at maximum initially.
|
---|---|
down,up | changes evb .
|
B,A | Changes ey
|
L,R | Changes mode. |
The function of interest is test_blend()
. This is
where the key handling takes place and where the blend settings
are altered. Similar to mos_demo, .3 fixeds are used for the
blend weight variables to slow the rate of change to more comfortable
levels. To set the blend registers themselves I'm using
BUILD()
macros and BF_SET()
, which work well
enough for these purposes. It would be trivially easy to write
wrapper functions here of course. Most of the code is pretty standard;
just play around with the blend modes and weights and see what happens.
Do take note of how, like I said earlier, the sprite metroid is affected differently than the bg-metroid. The background-background blend behaves exactly as the mode says it should; the sprite, on the other hand, always has a blend if they overlap with the fence's pixels, and the rest obeys the mode, which is what I told you in the caveats.
Windowing allows you to divide the screen into rectangular areas
known as, well, windows. There are two basic windows: win0
and win1. There's also a third type of window, the
object window. This creates a window out of the visible
pixels of the sprites. You can enable the windows by setting
REG_DISPCNT
{d,e,f}, respectively.
A rectangular window is defined by its left, right, top and bottom sides. Unless you're one of those people, who think it's funny to say that a rectangle has only two sides: an inside and an outside. In fact, this is truer than you think. The union of win0 and win1 is the inside window. There's also the outside window, which is everything else. In other words:
winIn = win0 | win1 winOut = ~(winIn) |
![]() |
![]() |
Both win0 and win1 have 2 registers that define their boundaries.
In order these are REG_WIN0H
(0400:0040h
),
REG_WIN1H
(0400:0042h
),
REG_WIN0V
(0400:0044h
) and
REG_WIN1V
(0400:0046h
), which have the
following layout:
reg | F E D C B A 9 8 | 7 6 5 4 3 2 1 0 |
---|---|---|
REG_WINxH
| left | right |
REG_WINxV
| top | bottom |
bits | name | description | |
---|---|---|---|
0-7 | right | Right side of window (exclusive) | |
8-F | left | Left side of window (inclusive) | |
- | |||
0-7 | bottom | Bottom side of window (exclusive) | |
8-F | top | Top side of window (inclusive) |
So you have one byte for each value. That's bytes as in unsigned chars. The contents of a window are drawn from starting at the top-left up to, but not including, the bottom-right. What you have to realize is that this is also true when, say, the right value is lower than the left value. In such a case, there's a wrap-around and everything on that line is inside the window, except the pixels between R and L. If both R < L and B < T then you get a window in the shape of a cross.
The possible content for the windows are backgrounds 0-3 and objects.
No suprise there, right? In total, we have regions: win0, win1, winOut
and winObj. REG_WININ
(0400:0048h
) controls
win0 and win1, REG_WINOUT
(0400:004ah
)
takes care of winOut and winObj. There's one bit for each
content-type, plus one for blending, which you will need if
you intend to use blending on the contents of that particular window.
register | F E | D | C | B | A | 9 | 8 | 7 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
bits | - | Bld | Obj | BG3 | BG2 | BG1 | BG0 | - | Bld | Obj | BG3 | BG2 | BG1 | BG0 |
REG_WININ
| - | win1 | - | win0 | ||||||||||
REG_WINOUT
| - | winObj | - | winOut |
bits | name | define | description |
---|---|---|---|
0-5 | BGx, Obj, Bld | WIN_BGx, WIN_OBJ, WIN_BLD, WIN_LAYER# | Windowing flags. To be used with all bytes in REG_WININ and REG_WINOUT. |
There is little in the way of macros or bit-defines here because they're not really necessary. Do have these in tonc_memdef.h though:
#define WIN_BUILD(low, high) \ ( ((high)<<8) | (low) ) #define WININ_BUILD(win0, win1) WIN_BUILD(win0, win1) #define WINOUT_BUILD(out, obj) WIN_BUILD(out, obj)
There are still a few things you should know about windows. First of
all, when you turn on windowing in REG_DISPCNT
, nothing
will show up. There are two reasons for this. Firstly, the boundary
registers are all 0, so the whole screen is basically winOut. Secondly,
and this is really important: a background or object will only
show up in the windows in which it is enabled! This
means that unless at least some bits have been set in
REG_WININ
or REG_WINOUT
nothing will show.
This presents you with an effective way of hiding stuff, as we'll see in
the demo. There is a third thing that you must remember, namely that win0
takes precedence over win1, which in turn takes precedence over winOut.
I'm not sure how winObj fits into this yet.
To make windowing work for you, you need to do the following things:
REG_DISPCNT
REG_WININ
and
REG_WINOUT
. You must set at least some bits here
if you have windowing enabled, or nothing will show up at all!
REG_WINxH/V
. If you don't,
everything will be considered in the Out-window.
There's something really weird going on when either the top or bottom is outside of the screen. Multiple somethings in fact, see the demo on hardware! for details.
VBA clips the windows, like common sense would lead you to believe. (Of course, common sense also tells you that the Sun orbits the Earth or that the stars are pinpricks on a large black canvas. Common sense is hardly common).
MappyVM and BoycottAdvance simply remove the window if any of the boundaries goes off the screen.
In case you hadn't noticed yet, I like the Metroid series. I really like the Metroid series. If you have ever played Super Metroid chances are that you've used the X-ray scope, which let's you see through the layers and find items and secret passages with much more ease. Guess how that was done? Yup, windowing. The windowing demo win_demo essentially does the same thing. There's a rocket-item hidden behind the background layers and you have an X-ray rectangle which you can move around the screen so you can find it.
The controls are simple: use the D-pad to move the window around; START repositions the rocket. I've also added finer movement (A + D-pad) so you can see the strange behaviour the windows seem to exhibit at certain positions.
dir | Moves the rectangle. |
---|---|
A + dir | Move rectangle by tapping for finer control. |
start | Randomly change the position of the rocket. |
What follows below is the majority of the demo's code. I have removed the functions that set up the backgrounds and sprite because there's nothing in them that you haven't seen before already. The earlier fig 13.4a is a screenshot of the demo in action.
// win_demo.c // bg 0, cbb 0, sbb 2, pb 0: numbered forground // bg 1, cbb 0, sbb 3, pb 0: fenced background // oam 0: tile 0-3: rocket // win 0: objects // win 1: bg 0 // win out : bg 1 #include <tonc.h> #include "nums.h" #include "rocket.h" typedef struct tagRECT_U8 { u8 ll, tt, rr, bb; } ALIGN4 RECT_U8; // window rectangle regs are write only, so buffers are necessary // Objects in win0, BG 0 in win1 RECT_U8 win[2]= { { 36, 20, 76, 60 }, // win0: 40x40 rect { 12, 12 ,228, 148 } // win1: screen minus 12 margin. }; // gfx loaders omitted for clarity void init_front_map(); // numbers tiles void init_back_map(); // fence void init_rocket(); // rocket void win_copy() { REG_WIN0H= win[0].ll<<8 | win[0].rr; REG_WIN1H= win[1].ll<<8 | win[1].rr; REG_WIN0V= win[0].tt<<8 | win[0].bb; REG_WIN1V= win[1].tt<<8 | win[1].bb; } void test_win() { win_copy(); while(1) { key_poll(); vid_vsync(); // key_hit() or key_is_down() 'switch' // A depressed: move on direction press (std movement) // A pressed : moves on direction hit (fine movement) int keys= key_curr_state(); if(key_is_down(KEY_A)) keys &= ~key_prev_state(); if(keys & KEY_RIGHT) { win[0].ll++; win[0].rr++; } else if(keys & KEY_LEFT ) { win[0].ll--; win[0].rr--; } if(keys & KEY_DOWN) { win[0].tt++; win[0].bb++; } else if(keys & KEY_UP ) { win[0].tt--; win[0].bb--; } // (1) randomize rocket position if(key_hit(KEY_START)) obj_set_pos(&oam_mem[0], qran_range(0, 232), qran_range(0, 152)); win_copy(); } } int main() { // obvious inits oam_init(); init_front_map(); init_back_map(); init_rocket(); // (2) windowing inits REG_DISPCNT= DCNT_BG0 | DCNT_BG1 | DCNT_OBJ | DCNT_OBJ_1D | DCNT_WIN0 | // Enable win 0 DCNT_WIN1; // Enable win 1 REG_WININ= WININ_BUILD(WIN_OBJ, (WIN_BG0); REG_WINOUT= WINOUT_BUILD(WIN_BG1, 0); win_copy(); // Initialize window rects test_win(); return 0; }
Initializing the windows is done at point 2: both win0 and win1 in
REG_DISPCNT
, objects in win 0, bg 0 in win 1 and
bg1 in winOut. The windows' sizes are
set using win_copy()
in each frame. I am using two
rectangle variables to keep track of where the windows are, because
the window-rectangle registers themselves are write only. See
fig 13.4 again for the result.
Usually, objects are shown in front of backgrounds. However, because objects are now only set to appear inside win 0, they are effectively hidden everywhere else: you will only see the rocket or parts of it if the rocket and win 0's rectangle overlap. Furthermore, you will notice that because only objects are set for win 0, the window itself is completely black.
The rest of the demo is rather uneventful. I could explain that
the way mask the variable keys
with the previous keystate
when A is held down lets me switch between the
key_hit()
and key_is_down()
functions,
giving me the functionality I require to switch between direct and
fine motion for the X-ray window, but it's not all that interesting
and quite besides the point of the demo. What's also beside the point
of the demo, but is interesting to mention, is the
randomization of the rocket's position.
Random numbers on a computer is a somewhat quaint notion. The whole point of a computer is to have a reliable calculator, and random numbers are pretty much the antithesis of that. Computer generated random numbers are also called pseudo-random, because they aren't intrinsically random, just deterministically generated to seem that way. There are statistical tests to see if a given routine is sufficiently random. However, this isn't nuclear physics we're talking about, this is game programming. We mostly need something that can, say, make an enemy zig or zag without any discernable pattern; that it can kill a Monte Carlo simulation is totally irrelevant.
One class of generators are linear congruential generators,
which follow the pattern Ni+1 =
(a·Ni + c)%m, with
Ni∈[0, m〉. With properly picked
parameters a, c and m, the routine can be quite
adequate. If you ever encounter a rand()
function in any
kind of standard library, chances are it's one of these. Not only are
these easy to implement, they are likely to be fast as well.
The following routine qran()
is taken from my numerical
methods book,
Numerical Recipes,
pp 275, where it is labelled a quick and dirty generator, but an adequate one.
Consisting of one addition and one multiply (m=232,
so done automatically), it is very fast. The actual number
returned are the top 15 bits from N, because the upper bits
are apparently more random than the lower, and also because 15 gives
a [0,32767] range, which is something of an unofficial standard,
AFAIK. Note that there is a second function, sqran()
used to seed the generator. Since the process itself is
still deterministic, you need a seed to ensure that you don't get the
same sequence every time. Unless, that is, you actually want
that to happen. This isn't such a strange idea if you think about it:
you could use it to generate maps, for example. Instead of storing
the whole map so that it looks the same every time you load it, you
just store the seed and you're done. This is how the planetary
terrains in Star Control 2
are made; I very much doubt it would have been possible to store
bitmaps of all the 1000+ planets it had. This is why
sqran()
also returns the current N, so you can
reset it later if necessary.
// from tonc_core.h/.c // A Quick (and dirty) random number generator and its seeder int __qran_seed= 42; // Seed / rnd holder // Seed routine int sqran(int seed) { int old= __qran_seed; __qran_seed= seed; return old; } //! Quick (and very dirty) pseudo-random number generator /*! \return random in range [0,8000h> */ INLINE int qran() { __qran_seed= 1664525*__qran_seed+1013904223; return (__qran_seed>>16) & 0x7FFF; }
I'll say again, this is not a very advanced random generator, but it'll be enough for what I need. If you want a better (but slower) one, try the Mersenne Twister. You can find a nice implementation on it on PERN's new sprite page.
Getting a random number is one thing; getting a random number in a particular range is another. It seems simple enough, of course: for a number between, say, 0 and 240 you'd use modulo 240. However, as the GBA doesn't have a hardware divide, it'll cost quite a number of cycles. Fortunately, there is a simple way out of it.
I said that qran()
, like stdlib's rand()
has a range between 0 and 0x8000. You can also see this as a range
between 0 and 1, if you interpret them as .15 fixed point numbers.
By multiplying with 240, you'll have the desired ranged random
number, and it only costs a multiplication and a shift. This
technique works for every random number generator, as long as you
pay attention to its maximum range and integer overflow (which
you should pay attention to anyway). Tonclib's version of this
is called qran_range()
.
//! Ranged random number /*! \return random in range [\a min, \a max> * \note (max-min) must be lower than 8000h */ INLINE int qran_range(int min, int max) { return (qran()*(max-min)>>15)+min; }
In the demo, I'm using qran_range()
twice to keep the sprite
position inside the screen at all times. While the position itself
could be predicted beforehand with some investigation, I don't
think it'll be that easy. And if you really put that kind of effort
in it, I'd say you would deserve something for your troubles. If you
reload the demo a few times, you will notice that the sequence of
positions is always the same. This is why they're called
pseudo-random. To get a different sequence, the seed value
should be different. I haven't even seeded it once here because it's
not really important for this, but the usual trick to seed it with
something involving time: for example, the number of frames or
cycles before one actually starts a game, counted from the various
intro screens that may precede it. Even a small difference in the
seed can produce wildly varying sequences.
Technically speaking you probably won't really need mosaic, blending or windowing in games, but they're great for subtle effects, like a ‘shock-hit’ or spotlights. They're also of great use for various types of scene transitions; a fade to black can be easily implemented using the blend registers. Also fun are various HBlank effects using windows, changing the rectangles every HBlank to give beams, side-way wipes or circlular windows. However, to be able to do that you need to know how to work with interrupts. Or a special case of DMA known as HDMA, which just happens to be up next.
Prev | Contents | Next |
Affine bg | DMA |