Sei sulla pagina 1di 15

codeslinger.co.

uk
H o mB e a s Zi u c k sM o e g a D r M i a v e s /t G e re n S e y s Cs i ht s ie Bpm l 8 o Gamebo y g

codeslinger.co.uk
Gamebo y - Graphics Emulatio n.

Graphics Emulation:
The gamebo y uses the tile and sprite metho d o f sto ring and drawing graphics to the screen. The tiles are what fo rm the backgro und and are no t interactive. Each tile is 8 x8 pixels. The sprites are the interactive graphics o n the display. An example is the game Mario . The character mario is a sprite. The graphic can mo ve and it can co llide with the o ther sprites. The bad guys are also sprites as they can fly aro und and attack mario . The tiles are the backgro und which defines the level and its terrain. As stated previo usly the reso lutio n o f the gamebo y is 16 0 x144 ho wever this is just what can be displayed o n the screen. The real reso lutio n is 256 x256 (32x32 tiles). The visual display can sho w any 16 0 x144 pixels o f the 256 x256 backgro und, this allo ws fo r scro lling the viewing area o ver the backgro und. Aswell as having a 256 x256 backgro und and a 16 0 x144 viewing the display the gamebo y has a windo w which appears abo ve the backgro und but behind the sprites (unless the attributes o f the sprite specify o therwise, discussed later). The purpo se o f the windo w is to put a fixed panel o ver the backgro und that do es no t scro ll. Fo r example so me games have a panel o n the screen which displays the characters health and co llected items, (no tably links awakening) and this panel do es no t scro ll with the backgro und when the character mo ves. This is the windo w. This part o f the site sho ws ho w to emulate everything I have just discussed. The examples I give belo w

Gameboy Emulation:
Getting Started The Hardware Memory Control and Map ROM and RAM Banking The Timers Interupts LCD DMA Transfer Graphic Emulation Joypad Emulation Opcode Examples PDFmyURL.com

were designed to give the easiest understanding o f ho w the tile and sprite system wo rks but it is far fro m efficient. Luckily graphic emulatio n is the area where mo st speed o ptimizatio n can be achieved with the least amo unt o f effo rt (by using dirty rectangles and the likes) but this wo uld no t give a go o d demo nstratio n.

Finished Product

The LCD Control Register:


In the LCD chapter I briefly to uched upo n the LCD Co ntro l register. This register co ntains a lo t o f data that we need to understand befo re we can emulate graphics. This is the breakdo wn o f the 8 -bit special register:

Taken fro m the pando cs: Bit 7 - LCD Display Enable (0 =Off, 1=On) Bit 6 - Windo w Tile Map Display Select (0 =9 8 0 0 -9 BFF, 1=9 C0 0 -9 FFF) Bit 5 - Windo w Display Enable (0 =Off, 1=On) Bit 4 - BG & Windo w Tile Data Select (0 =8 8 0 0 -9 7FF, 1=8 0 0 0 -8 FFF) Bit 3 - BG Tile Map Display Select (0 =9 8 0 0 -9 BFF, 1=9 C0 0 -9 FFF) Bit 2 - OBJ (Sprite) Size (0 =8 x8 , 1=8 x16 ) Bit 1 - OBJ (Sprite) Display Enable (0 =Off, 1=On) Bit 0 - BG Display (fo r CGB see belo w) (0 =Off, 1=On)

Bit 7 : I have already discussed Bit7. Basically it says if the lcd is enabled, if no t we do nt draw anything. This is already handled in the UpdateGraphics functio n. Bit 6 : This is where to read to read the tile identity number to draw o nto the windo w Bit 5 : If this is set to 0 then the windo w is no t enabled so we do nt draw it Bit 4 : Yo u use the identity number fo r bo th the windo w and the backgro und tiles that need to be draw to the screen here to get the data o f the tile that needs to be displayed. The impo rtant thing to remember abo ut this bit is that if it is set to 0 (i.e. read fro m address 0 x8 8 0 0 ) then the tile identity number we lo o ked up is actually a signed byte no t unsigned (explained fully later) Bit 3: This is the same as Bit6 but fo r the backgro und no t the windo w Bit 2: This is the size o f the sprites that need to draw. Unlike tiles that are always 8 x8 sprites can be 8 x16 Bit 1: Same as Bit5 but fo r sprites Bit 0 : Same as Bit5 and 1 but fo r the backgro und
PDFmyURL.com

Yo u may remember that the UpdateGraphics functio n called the DrawScanLine functio n which I've yet to implement. Ho wever with the abo ve lcd co ntro l register info I no w can: void Emulator::DrawScanLine( ) { BYTE control = ReadMemory(0xFF40) ; if (TestBit(control,0)) RenderTiles( ) ; if (TestBit(control,1)) RenderSprites( ) ; }

Rendering the Tiles Part 1:


As previo usly stated the backgro und is made up o f 256 x256 pixels (32x32 tiles) ho wever as we o nly display 16 0 x144 pixels there is no need to draw the rest. Befo re we can start drawing the backgro und we need to kno w where to draw the backgro und, i.e. which o f 16 0 x144 o f the 256 x256 backgro und is go ing to be displayed?

Scro llY (0 xFF42): The Y Po sitio n o f the BACKGROUND where to start drawing the viewing area fro m Scro llX (0 xFF43): The X Po sitio n o f the BACKGROUND to start drawing the viewing area fro m Windo wY (0 xFF4A): The Y Po sitio n o f the VIEWING AREA to start drawing the windo w fro m Windo wX (0 xFF4B): The X Po sitio ns -7 o f the VIEWING AREA to start drawing the windo w fro m

So no w we kno w where to draw the viewing area in relatio n to the backgro und and where to draw the windo w in relatio n to the viewing area. The minus 7 o f the windo wX po s is necessary. So if yo u wanted to start drawing the windo w in the upper left hand co rner (co o rdinates 0 ,0 ) o f the viewing area yo u'd set Windo wY to 0 and Windo wX to 7. Well when I say "when yo u want to draw the windo w" its actually the game decides no t the emulato r. On a side no te if the windo w was drawn in the to p left co rner then the windo w wo uld co mpletely co ver the backgro und. If the windo w wanted to co ver o nly the bo tto m half o f the backgro und the Windo wY wo uld bet set to 72 (144/2) and Windo wX 7. So no w we kno w where to draw the backgro und and the windo w we need to determine what we need to draw. The gamebo y has two regio ns o f memo o ry fo r the backgro und layo ut which is shared by the windo w. The memo ry regio ns are 0 x9 8 0 0 -0 x9 BFF and 0 x9 C0 0 -9 FFF. We need to check bit 3 o f the lcd co nto l register to see which regio n we are using fo r the backgro und and bit 6 fo r the windo w. Each byte in the memo ry regio n is a tile identificatio n number o f what needs to be drawn. This identificatio n number is used to lo o kup the tile data in video ram so we kno w ho w to draw it. Lo o king at bit 4 o f the LCD co ntro l register we can see that there are two places where the tile date is
PDFmyURL.com

lo cated 0 x8 0 0 0 -0 x8 FFF and 0 x8 8 0 0 0 x9 7FF. Each o f these memo ry regio ns gives us 40 9 6 (0 x10 0 0 ) bytes o f data to sto re all the tiles. Each tile is sto red in memo ry as 16 bytes. Remember that a tile is 8 x8 pixels and that in memo ry each line o f the tile requires two bytes to represent, hence the 16 bytes per tile. So no w we kno w where the tile data is sto red and where the backgro und layo ut is sto red. The backgro und layo ut gives the tile identificatio n number to lo o k up the tile in the tile data area. Ho wever this is the tricky part, if the tile data memo ry area we are using is 0 x8 0 0 0 -0 x8 FFF then the tile identifier read fro m the backgro und layo ut regio ns is an UNSIGNED BYTE meaning the tile identifier will range fro m 0 - 255. Ho wever if we are using tile data area 0 x8 8 0 0 -0 x9 7FF then the tile identifier read fro m the backgro und layo ut is a SIGNED BYTE meaning the tile identifier will range fro m -127 to 127. The is the algro ithm to lo cate the tile in memo ry regio n 0 x8 0 0 0 -0 x8 FFF const WORD memoryRegion = 0x8000 ; const int sizeOfTileInMemory = 16 ; WORD tileDataAddress = memoryRegion + (tileIdentifier*sizeOfTileInMemory) ;

So if the backgro und layo ut gave us the unsigned tile identifier as 0 then the tile data wo uld be between 0 x8 0 0 0 -0 x8 0 0 F. Ho wever if we needed to lo o kup tile identifier 37 then it wo uld be in memo ry regio n 0 x8 250 -0 x8 25F. This is the algo rithm fo r calculating where the tile data is in memo ry regio n 0 x8 8 0 0 0 x9 7FF const WORD memoryRegion = 0x8800 ; const int sizeOfTileInMemory = 16 ; const int offset = 128 ; WORD tileDataAddress = memoryRegion + ((tileIdentifier+offset)*sizeOfTileInMemory) ;

So if the tile identier was 0 then the tile wo uld be in memo ry regio n 0 x9 0 0 0 -0 x9 0 0 F.

How to draw a tile/sprite from memory:


This area fo llo ws o n fro m the abo ve tile data rendering but it is also the same fo r sprites as yo u will see later o n. So what do we no w kno w abo ut the tile data? First is that the backgro und layo ut regio n identifies each tile in the current backgro und that needs to be drawn. The tile identity number o btained fro m the backgro und layo ut is used to lo o kup the tile data in the tile data regio n. We kno w that each tile is 8 x8 pixels and that each ho rizo ntal line in the 8 x8 takes up two bytes o f memo ry meaning that each tile in memo ry needs 16 bytes o f data.

PDFmyURL.com

So if two bytes o f data fo rm o ne line o f the tile then we need to co mbine these two bytes to fo rm a break do wn o f each pixel in the 8 pixel line. So if byte 1 lo o ked like so : 0 0 110 10 1 and data 2 lo o ked like this 10 10 1110 then we can co mbine the two to gether to get the fo llo wing co lo ur info rmatio n:

pixel# = 1 2 3 4 5 6 7 8 data 2 = 1 0 1 0 1 1 1 0 data 1 = 0 0 1 1 0 1 0 1 Pixel 1 colour id: 10 Pixel 2 colour id: 00 Pixel 3 colour id: 11 Pixel 4 colour id: 01 Pixel 5 colour id: 10 Pixel 6 colour id: 11 Pixel 7 colour id: 10 Pixel 8 colour id: 01

As yo u can see there are o nly 4 po ssible co lo ur id's (0 0 ,0 1,10 ,11) which map to the 4 gamebo y co lo urs (white, light grey, dark grey, black). Ho wever we still need to determine ho w to map the pixel co lo urs id's to the co rrect co lo urs. This is what palettes are used fo r. Palettes are no t fixed, the pro grammer can change the mapping. This means yo u can change the co lo ur o f tiles and sprites witho ut changing the tile data because in o ne state co lo ur id 0 0 might represent dark grey but it might change to represent white. This is ho w they do co o l special effects like when Mario beco mes invincible they invert his co lo urs every half a seco nd to give him that flashing effect. The backgro und tiles o nly have the o ne mo no chro me co lo ur palette sto red in memo ry address 0 xFF47. Ho wever sprites can have two palettes (discussed later). They wo rk exactly the same as the backgro und palettes except that co lo ur white is actually transparent. The sprite palettes are lo cated 0 xFF48 and 0 xFF49 . Every two bits in the palette data byte represent a co lo ur. Bits 7-6 maps to co lo ur id 11, bits 5-4 map to co lo ur id 10 , bits 3-2 map to co lo ur id 0 1 and bits 1-0 map to co lo ur id 0 0 . Each two bits will give the co lo ur to use like so : 0 0 : White 0 1: Light Grey 10 : Dark Grey 11: Black

So no w we have o ur pixel co lo ur ids and a way to map these id's to co lo urs. If we take the abo ve example o f the pixel co lo ur id's and assume that the fo llo wing is the palette data byte: 110 0 10 0 1 we can no w wo rk
PDFmyURL.com

o ut each co lo ur o f the pixels. Pixel 1 co lo ur id: 10 : Means lo o k at bits o f palette data 5-4 gives co lo ur whit e Pixel 2 co lo ur id: 0 0 : Means lo o k at bits o f palette data 1-0 gives co lo ur light gre y Pixel 3 co lo ur id: 11: Means lo o k at bits o f palette data 7-6 gives co lo ur black Pixel 4 co lo ur id: 0 1: Means lo o k at bits o f palette data 3-2 gives co lo ur dark gre y Pixel 5 co lo ur id: 10 : Means lo o k at bits o f palette data 5-4 gives co lo ur whit e Pixel 6 co lo ur id: 11: Means lo o k at bits o f palette data 7-6 gives co lo ur black Pixel 7 co lo ur id: 10 : Means lo o k at bits o f palette data 5-4 gives co lo ur whit e Pixel 8 co lo ur id: 0 1: Means lo o k at bits o f palette data 3-2 gives co lo ur dark gre y

So no w we kno w what the co lo ur ares fo r the tile data line. Ho wever if the palette changed fro m 110 0 10 0 1 to a different value then the co lo urs o f the tile wo uld all co mpletely change witho ut the tile data itself changing.

Rendering the Tiles Part 2:


So no w we have all the info rmatio n needed to render the backgro und and the windo w. So taking all this info rmatio n we can implement it:

void Emulator::RenderTiles( ) { WORD tileData = 0 ; WORD backgroundMemory =0 ; bool unsig = true ; // where to draw the visual area and the window BYTE scrollY = ReadMemory(0xFF42) ; BYTE scrollX = ReadMemory(0xFF43) ; BYTE windowY = ReadMemory(0xFF4A) ; BYTE windowX = ReadMemory(0xFF4B) - 7; bool usingWindow = false ; // is the window enabled? if (TestBit(lcdControl,5)) { // is the current scanline we're drawing // within the windows Y pos?,
PDFmyURL.com

// within the windows Y pos?, if (windowY <= ReadMemory(0xFF44)) usingWindow = true ; } // which tile data are we using? if (TestBit(lcdControl,4)) { tileData = 0x8000 ; } else { // IMPORTANT: This memory region uses signed // bytes as tile identifiers tileData = 0x8800 ; unsig= false ; } // which background mem? if (false == usingWindow) { if (TestBit(lcdControl,3)) backgroundMemory = 0x9C00 ; else backgroundMemory = 0x9800 ; } else { // which window memory? if (TestBit(lcdControl,6)) backgroundMemory = 0x9C00 ; else backgroundMemory = 0x9800 ; } BYTE yPos = 0 ; // yPos is used to calculate which of 32 vertical tiles the // current scanline is drawing
PDFmyURL.com

if (!usingWindow) yPos = scrollY + ReadMemory(0xFF44) ; else yPos = ReadMemory(0xFF44) - windowY; // which of the 8 vertical pixels of the current // tile is the scanline on? WORD tileRow = (((BYTE)(yPos/8))*32) ; // time to start drawing the 160 horizontal pixels // for this scanline for (int pixel = 0 ; pixel < 160; pixel++) { BYTE xPos = pixel+scrollX ; // translate the current x pos to window space if necessary if (usingWindow) { if (pixel >= windowX) { xPos = pixel - windowX ; } } // which of the 32 horizontal tiles does this xPos fall within? WORD tileCol = (xPos/8) ; SIGNED_WORD tileNum ; // get the tile identity number. Remember it can be signed // or unsigned WORD tileAddrss = backgroundMemory+tileRow+tileCol; if(unsig) tileNum =(BYTE)ReadMemory(tileAddrss); else tileNum =(SIGNED_BYTE)ReadMemory(tileAddrss ); // deduce where this tile identifier is in memory. Remember i // shown this algorithm earlier WORD tileLocation = tileData ;
PDFmyURL.com

if (unsig) tileLocation += (tileNum * 16) ; else tileLocation += ((tileNum+128) *16) ; // find the correct vertical line we're on of the // tile to get the tile data //from in memory BYTE line = yPos % 8 ; line *= 2; // each vertical line takes up two bytes of memory BYTE data1 = ReadMemory(tileLocation + line) ; BYTE data2 = ReadMemory(tileLocation + line + 1) ; // pixel 0 in the tile is it 7 of data 1 and data2. // Pixel 1 is bit 6 etc.. int colourBit = xPos % 8 ; colourBit -= 7 ; colourBit *= -1 ; // combine data 2 and data 1 to get the colour id for this pixel // in the tile int colourNum = BitGetVal(data2,colourBit) ; colourNum <<= 1; colourNum |= BitGetVal(data1,colourBit) ; // now we have the colour id get the actual // colour from palette 0xFF47 COLOUR col = GetColour(colourNum, 0xFF47) ; int red = 0; int green = 0; int blue = 0; // setup the RGB values switch(col) { case WHITE: red = 255; green = 255 ; blue = 255; break ; case LIGHT_GRAY:red = 0xCC; green = 0xCC ; blue = 0xCC; break ; case DARK_GRAY: red = 0x77; green = 0x77 ; blue = 0x77; break ;
PDFmyURL.com

} int finaly = ReadMemory(0xFF44) ; // safety check to make sure what im about // to set is int the 160x144 bounds if ((finaly<0)||(finaly>143)||(pixel<0)||(pixel>159)) { continue ; } m_ScreenData[pixel][finaly][0] = red ; m_ScreenData[pixel][finaly][1] = green ; m_ScreenData[pixel][finaly][2] = blue ; } }

I kno w at first that can seem daunting but as lo ng as yo u understand the lo gic to drawing the tiles just keep lo o king at the abo ve co de and it'll make sense. I've yet to implement the GetCo lo ur functio n which is used in the abo ve co de. This functio n takes a co lo ur ID then uses the mo no chro me palette to deduce what co lo ur that co lo ur ID relates to . Remember that this is a dynamic palette so the co lo ur id's will map to different co lo urs during different parts o f the game. void COLOUR Emulator::GetColour(BYTE colourNum, WORD address) const { COLOUR res = WHITE ; BYTE palette = ReadMemory(address) ; int hi = 0 ; int lo = 0 ; // which bits of the colour palette does the colour id map to? switch (colourNum) { case 0: hi = 1 ; lo = 0 ;break ; case 1: hi = 3 ; lo = 2 ;break ; case 2: hi = 5 ; lo = 4 ;break ; case 3: hi = 7 ; lo = 6 ;break ; }

PDFmyURL.com

// use the palette to get the colour int colour = 0; colour = BitGetVal(palette, hi) << 1; colour |= BitGetVal(palette, lo) ; // convert the game colour to emulator colour switch (colour) { case 0: res = WHITE ;break ; case 1: res = LIGHT_GRAY ;break ; case 2: res = DARK_GRAY ;break ; case 3: res = BLACK ;break ; } return res ; }

No w yo u sho uld have everything yo u need to render the tiles

Rendering the Sprites:


Rendering the sprites is a bit mo re difficult then the tiles but luckily the sprite data is lo cated in memo ry address 0 x8 0 0 0 -0 x8 FFF which means the sprite identifiers are all unsigned values which makes finding them easier. There are 40 tiles lo cated in memo ry regio n 0 x8 0 0 0 -0 x8 FFF and we need to scan thro ugh them all and check their attributes to find where they need to be rendered. The sprite attributes are fo und in the sprite attribute table (DUH!) lo cated in memo ry regio n 0 xFE0 0 -0 xFE9 F. In this memo ry regio n each sprite has 4 bytes o f attributes asso citated to it, these are:

0 : Sprite Y Po sitio n: Po sitio n o f the sprite o n the Y axis o f the viewing display minus 16 1: Sprite X Po sitio n: Po sitio n o f the sprite o n the X axis o f the viewing display minus 8 2: Pattern number: This is the sprite identifier used fo r lo o king up the sprite data in memo ry regio n 0 x8 0 0 0 -0 x8 FFF 3: Attributes: These are the attributes o f the sprite, discussed later.

A sprite can either be 8 x8 pixels o r 8 x16 pixels, this can be determined by the sprites attributes. This is a break do wn o f the sprites attributes: Bit7: Sprite to Backgro und Prio rity
PDFmyURL.com

Bit6 : Y flip Bit5: X flip Bit4: Palette number Bit3: No t used in standard gamebo y Bit2-0 : No t used in standard gamebo y

Sprit e t o Backgro und Prio rit y: If this flag is set to 0 then sprite is always rendered abo ve the backgro und and the windo w. Ho wever if it is set to 1 then the sprite is hidden behind the backgro und and windo w unless the co lo ur o f the backgro und o r windo w is white, then it is still rendered o n to p. Y f lip: If this bit is set then the sprite is mirro red vertically. This will be used in the game to turn sprites upside do wn. X f lip: If this bit is set then the sprite is mirro red ho rizo ntally. This will be used in the game to change the directio n o f the characters etc Pale t t e Num be r: Sprites can either get their mo no chro me palettes fro m 0 xFF48 o r 0 xFF49 . If this bit is 0 then it gets it palette fro m 0 xFF48 o therwise 0 xFF49 I find the best way to handle the X and Y flipping is to read the sprite data in backwards as this will give the flip effect. We no w have eno ugh info rmatio n to render the sprites. This is do ne almo st identically to the rendering o f the tiles, except that instead o f lo o ping thro ugh a layo ut regio n in memo ry to get the next identifier o f the tile to draw, we have to lo o p thro ugh all 40 sprites and detect which o nes are visible and are intercepting with the current scanline. void Emulator::RenderSprites( ) { bool use8x16 = false ; if (TestBit(lcdControl,2)) use8x16 = true ; for (int sprite = 0 ; sprite < 40; sprite++) { // sprite occupies 4 bytes in the sprite attributes table BYTE index = sprite*4 ; BYTE yPos = ReadMemory(0xFE00+index) - 16; BYTE xPos = ReadMemory(0xFE00+index+1)-8; BYTE tileLocation = ReadMemory(0xFE00+index+2) ; BYTE attributes = ReadMemory(0xFE00+index+3) ; bool yFlip = TestBit(attributes,6) ; bool xFlip = TestBit(attributes,5) ;
PDFmyURL.com

int scanline = ReadMemory(0xFF44); int ysize = 8; if (use8x16) ysize = 16; // does this sprite intercept with the scanline? if ((scanline >= yPos) && (scanline < (yPos+ysize))) { int line = scanline - yPos ; // read the sprite in backwards in the y axis if (yFlip) { line -= ysize ; line *= -1 ; } line *= 2; // same as for tiles WORD dataAddress = (0x8000 + (tileLocation * 16)) + line ; BYTE data1 = ReadMemory( dataAddress ) ; BYTE data2 = ReadMemory( dataAddress +1 ) ; // its easier to read in from right to left as pixel 0 is // bit 7 in the colour data, pixel 1 is bit 6 etc... for (int tilePixel = 7; tilePixel >= 0; tilePixel--) { int colourbit = tilePixel ; // read the sprite in backwards for the x axis if (xFlip) { colourbit -= 7 ; colourbit *= -1 ; } // the rest is the same as for tiles int colourNum = BitGetVal(data2,colourbit) ; colourNum <<= 1; colourNum |= BitGetVal(data1,colourbit) ;
PDFmyURL.com

WORD colourAddress = TestBit(attributes,4)?0xFF49:0xFF48 ; COLOUR col=GetColour(colourNum, colourAddress ) ; // white is transparent for sprites. if (col == WHITE) continue ; int red = 0; int green = 0; int blue = 0; switch(col) { case WHITE: red =255;green=255;blue=255;break ; case LIGHT_GRAY:red =0xCC;green=0xCC ;blue=0xCC;break ; case DARK_GRAY:red=0x77;green=0x77;blue=0x77;break ; } int xPix = 0 - tilePixel ; xPix += 7 ; int pixel = xPos+xPix ; // sanity check if ((scanline<0)||(scanline>143)||(pixel<0)||(pixel>159)) { continue ; } m_ScreenData[pixel][scanline][0] = red ; m_ScreenData[pixel][scanline][1] = green ; m_ScreenData[pixel][scanline][2] = blue ; } } } }

PDFmyURL.com

Thats everything yo u need to get the graphics emulating co rrectly. No w all yo u have to do is bit-blit m_ScreenData to the display. I perso nally use glDrawPixels but yo u can use what yo u want. Head o ver here to the next chapter o n Jo ypad

Copyright 2008 codeslinger.co.uk

PDFmyURL.com

Potrebbero piacerti anche