Y-Quest is a small remake of the dos game 'X-Quest'...
This was originally written as part of my Livestream series, and is now
being ported to other platforms
The Y-Quest code was bases on the simple
series, It's free and open source, so you can use it however you
wish
In this series, we'll look at the basic
codebase, and the platform specific modifications for each platform
Lesson
YQuest1 - Introduction and Data Structures
YQuest is a little game I wrote for multiple Z80 systems in my
Livestreams... it has now been ported to the 6502 machines
Lets start by having a quick look at the game, and the structure
of the game RAM, and the settings which define levels and enemies.
See
YQuest folder folder
Y-Quest!
Y-Quest is an Action-Puzzle game... to complete each stage you
need to collect all the 'crystals' onscreen, by navigating your
'Y' Icon around the screen.
The original X-quest was controlled by mouse, however this version
is joystick controlled.
Unlike the original, the edges of the play area do not kill you.
Change acceleration with the joystick / keys UDLR... You can
shoot with the firebutton ... if you have a second fire you can
immediately stop with it.
There are 16 levels, after which the levels will repeat.
Ram Definitions
All Sprite Objects uses the same 8 byte definition..
The Sprite Number defines the graphic image
of the sprite from the sprite data bank... this is a single byte,
and the address is calculated by multiplying the sprite number by
the size of an 8x8 sprite Xpos and Ypos are the
screen position of the sprite. Xacc and Yacc are the
Acceleration of the sprite (how fast it moves in each direction Program is the logic routine that handles
movement - this is used by enemies that change direction and fire. Collision program is the logic routine that
handles collision - this defines enemies, crystals, bullets etc
and how they affect the player... a value of 255 is an unused
object... 254 is a dead object which will later respawn
The Ram is defined as offsets from the 'UserRam' Definition -
this is to allow the code to work consistently on ROM based
systems.
All values will be zeroed on startup, and any values with other
required values will be initialized accordingly
CursorX and CursorY are
the position of the next character draw - this is used by string
printing routines.
SpriteFrame is used by the bitmap drawing
routine... this selects which bank of sprites to use... there are
up to 4 banks (depending on system memory)
The BulletArray allows for 8 player
bullets - this uses the standard 'Sprite Object Layout'
The EnemyBulletArray allows for 8 enemy
bullets - this uses the standard 'Sprite Object Layout'
The ObjectArray allows for up to 40 objects
including enemies, crystals and mines - this uses the standard
'Sprite Object Layout'
Invincibility is a counter for how long the
player is invincible, either at the start of a level, or after
being killed Random Seed is a 16 bit random seed - used by
the psuedorandom number generator KeyTimeout is used as a delay for key
processing - this is to reduce the speed of acceleration of the
player Lives is the number of lives of the player Level is the Level number - starts from 0 Crystals is the number of crystals left to
collect - note: due to the object limit only 5 are shown at a time
- they respawn when collected. PlayingSFX is used to control the current
sound PlayingSFX2 is the last sound played - when
it does not match PlayingSFX we need to update the sound Score is the current score, 8 binary coded
decimal packed digits (4 bytes) HiScore is the best score so far.
The PlayerObject handles the player
sprite, this uses the standard 'Sprite Object Layout', however
there are labels to the component parts of the player object
There are also a backup of the X,Y position called LastPosX
and LastPosY
Note:
All these addresses are relative to the base address
'UserRam'... this is so the RAM data can be located where ever
free ram exists on the target platform.
So the game can work on ROM machines, this is the only
writable data, The is no self modifying code or altered data
within the other areas of code.
Constants and Data definitions
The Player Object Backup is a copy of the
player settings, this is used to reset the player at the start of
a round
We have some constant definitions next.
Object Byte Size is the number of bytes in an
object... The Player,Enemies and Bullets are all 8 bytes per
object Enemies is the number of objects... this
includes Mines and Crystals. Bullet Count is the number of bullets for
Players and Enemies... 8 means the Player can shoot 8 bullets, and
enemies can shoot a separate 8 bullets Onscreen Crystals is the number of crystals
onscreen... this can be less than the number per level, as
crystals will respawn when collected as required
The Level Map is a bank of pointers to the
Level data.
Each Level Definition has the same format.
First is a pair of header bytes... the first byte is the crystal
count... the second is unused.
Next comes pairs of object definitions... the first byte is an object type... the second is a count.
The definition is ended by an object of 0 and a count of 255
Although the contents have the same purpose, The Object
Type definitions are not in the same format as the Sprite
Objects
Each definition is a different enemy type, 0 is the 'empty object'
and is used for the end of the level definition
Next we have some Random Number Lookup tables
We have a set of 255 terminated Text strings.
These are used for ingame messages - for the Sega GameGear there
are shortened versions
The BCD Definitions are used to add points
to the current score...
The BCD code requires source and destination value to be 4 bytes -
little endian
Finally we have the Title screen definition...
one tile per byte
These use the same sprite numbers (and sprite graphics) as the
enemy objects.
There are different versions depending on screen size.
YQ_Multiplatform.asm - the multiplatform
code
Yquest 6502 was ported from the Z80 version, Like with my main
tutorials, I use zero page entries to simulate z80 registers.
Each subroutine of the multiplatform code has been recreated, with
the same layout function and purpose... for that reason I will not
be covering the multiplatform code of the 6502 version - as it
would repeat the content of
lessons 2-5 of the Z80 series.
If you want to see videos of these, please see the z80 series here.
To assist
conversion the author uses a program he wrote called 'AsmConv'
(it's experimental, and is not publicly released)... this does a
crude job of converting the code, but much of the work is done
by hand.
To help convert the code many macros and functions are used...
these are found in BasicMacros.asm and BasicFunctions.ASM
Lesson
YQuest2 - BBC Specific code
The BBC version was the first port of the game on the 6502.
The BBC is a bitmap based system, with software sprites. Lets
take a look at the code.
See
YQuest folder folder
BBC Header
So we can support multiple platforms with different screen
sizes we have platform specific screen size definitions
Each system needs an area of ram for game variables, know as
'UserRam'
We use CollisionMasks - these will skip some bits of the
internal object positions, this is to ensure we don't detect a
collision that is not visible due to the low resolution of
screen co-ordinates.
Our program loads at address $3000 - but we need some of
this area for our screen, so we shift it lower to $0200
(overwriting system vars!)
We also turn off the sound, and set up our screen... this is
all basically the same as previous 'simple series'
We zero the game's ram data. using CLDIR0 - which fills an
area with zeros
Because the
BBC's screen moves down 8 lines in 8 consecutive bytes ,
we're locking the screen co-ordinates to an 8x4 pixel
grid... this means the movement will be a bit jerky, but
it's much easier to code!
ShowTitle
When The title screen starts, we first reset the game
settings for a new game to start,
Then we clear the screen.
We want to draw the tilescreen... it's held as a 1 byte per
tile data block in our code at 'TitlePic'
We use our ShowSprite Routine to draw it to the screen...
however we need to calculate the sprite address of the desired
tile, so we use GetSpriteAddr to do so
We need to show a few text items to the screen
A 'Press Fire' Message
The 'LearnASM.Net' URL (Very Important!)
The Highscore.
LevelStart
When the game or a new level starts, we first wait for fire
to be pressed.
Next we clear the screen,
Reset the player position
Initialize the level enemies with LevelInit
Level Loop
We have to pause a while during the game loop, so we read in
from the joystick during this time, and if the user presses a
key we'll store it for later.
we use Y for a loop counter - and X for any pressed button.
we use function call Player_ReadControlsDual - which will read
in the joystick and fire button
First we draw the UI (Score etc)
Next we clear the player sprite.
To slow down the acceleration, we have a 'timeout'... if a
key was pressed, for a short time we'll ignore any other keys.
We're going to process the input,
We process each direction, and alter the acceleration of the
player if a direction is pressed...
We have a 'Bullet fire' routine (in the common code) for Fire
1
Fire 2 will immediately stop the player
If any button is pressed, the key timeout is set
Finally we run the multiplatform 'Draw and Move' rotuine,
this draws the player, and handles movement and drawing of
enemies and bullets
Clear Screen and Print Char
To clear the screen (CLS), we need to wipe the bitmap area
at address $4180 - we use the CLDIR0 command to do this, which
wipes z_bc bytes from address z_hl
PrintChar will show a character to the screen, using a
bitmap font the same as the sprites...
We need to calculate the position in the font data of the
character
Each Character is 16 bytes, and there are no characters below
32 (space)
We also need to multiply up the X,Y pos into an 8x8 grid
position.
Once we've shown a character, we increment the X position for
the next character
Sprite routines
We have a couple of ShowSprite routines...
BlankSprite will draw the empty sprite (Space in the font),
using the object z_IX - to clear the old position of the object
GetSpriteAddr will calculate the address in ram of the sprite
data we want to show to the screen
We also handle the sprite frame... there are 4 frames of
animation for each sprite - each bank is 256 bytes
DoGetSpriteObj will show object IX to the screen, getting
Sprite, and XY pos from z_IX
ShowSprite will draw Sprite A onscreen (at XY position BC)
Note, due to the BBC screen layout, we're 'rounding' the Y
co-ordinates to an 8 line tall grid
Get Screen Pos
The Get ScreenPos function will take an XY pos in z_B and
z_C... it will return a screen memory address in z_de
Our screen address starts at $4180...
Because of the screen layout (8 Y lines are consecutive in ram),
we're only going to look at the top 5 bits of the Y position
Each line is 320 pixels, and there are 4 pixels per byte... this
means our screen is 80 bytes wide, and because the 8 lines of
each horizontal strip are combined, the top 5 bits must be
multiplied by 640 ($280) (80*8=640)
Since the 8 Y lines are consecutive in memory, between each
horizontal byte, we multiply the Xpos by 8
Our formula is:
$4180 + ({Top 5 bits of Y} * $280) + Xpos * 8
We achieve the multiplication by bitshifting ops.
Joystick Reading
We're going to read in from the joystick - the BBC joystick is
analog - which is a bit of a pain.
We need to select an axis by writing to $FEC0, then read in from
$FEC1
We need to see if the result is outside of the 'dead zone' and
set the bits in z_h depending on the result.
We also have a 'Wait for fire' function... this randomizes the
seed, and waits for fire to be released, then pressed.
It's used for menus and between levels.
Sprite Data and Palette
We have 5 different files of sprites
96 for the font
16x4 for the 4 banks of sprites
We have settings to define the palette, and screen size and
position... these are transferred to the graphics hardware
during startup
Lesson
YQuest3 - Atari Lynx Specific code
the Lynx version uses 16 color bitmap graphics, giving great
look and smooth movement.
Unfortunately, the screen size is rather small, so we'll
resize our graphics to 8x6 tiles rather than 8x8
See
YQuest folder folder
Atari Lynx Header
So we can support multiple platforms with different screen
sizes we have platform specific screen size definitions
Each system needs an area of ram for game variables, know as
'UserRam'
When our program loads we need to initialize the graphics
hardware and set up the screen.
Our screen will be at address $C000
Next we set up the palette... this is defined by 32 bytes
at label 'Palette'
We zero the game's ram data. using CLDIR0 - which fills an
area with zeros
ShowTitle
When The title screen starts, we first reset the game
settings for a new game to start,
Then we clear the screen.
We want to draw the tilescreen... it's held as a 1 byte
per tile data block in our code at 'TitlePic'
We use our ShowSprite Routine to draw it to the screen...
however we need to calculate the sprite address of the
desired tile, so we use GetSpriteAddr to do so
We need to show a few text items to the screen
A 'Press Fire' Message
The 'LearnASM.Net' URL (Very Important!)
The Highscore.
LevelStart
When the game or a new level starts, we first wait for
fire to be pressed.
Next we clear the screen,
Reset the player position
Initialize the level enemies with LevelInit
Level Loop
We have to pause a while during the game loop, so we read
in from the joystick during this time, and if the user
presses a key we'll store it for later.
we use Y for a loop counter - and X for any pressed button.
we use function call Player_ReadControlsDual - which will
read in the joystick and fire button
First we draw the UI (Score etc)
Next we clear the player sprite.
To slow down the acceleration, we have a 'timeout'... if a
key was pressed, for a short time we'll ignore any other
keys.
We're going to process the input,
We process each direction, and alter the acceleration of the
player if a direction is pressed...
We have a 'Bullet fire' routine (in the common code) for
Fire 1
Fire 2 will immediately stop the player
If any button is pressed, the key timeout is set
Finally we run the multiplatform 'Draw and Move' rotuine,
this draws the player, and handles movement and drawing of
enemies and bullets
Because the Lynx is so fast, we have a second delay to slow
things down with a delay loop.
Clear Screen and Print Char
To clear the screen (CLS), we need to wipe the bitmap area
at address $4180 - we use the CLDIR0 command to do this,
which wipes z_bc bytes from address z_hl
PrintChar will show a character to the screen, using a
bitmap font the same as the sprites...
We need to calculate the position in the font data of the
character
Each Character is 16 bytes, and there are no characters
below 32 (space)
We also need to multiply up the X,Y pos into an 8x8 grid
position.
Once we've shown a character, we increment the X position
for the next character
Sprite routines
We have a couple of ShowSprite routines...
BlankSprite will draw the empty sprite (Space in the font),
using the object z_IX - to clear the old position of the
object
GetSpriteAddr will calculate the address in ram of the
sprite data we want to show to the screen
We also handle the sprite frame... there are 4 frames of
animation for each sprite - each bank is 512 bytes
DoGetSpriteObj will show object z_IX to the screen, getting
Sprite, and XY pos from z_IX
ShowSprite will draw Sprite A onscreen (at XY position BC)
The Bitmap routine skips lines 1 and 5 - this scales the
graphics down from 8x8 to 8x6 - allowing smaller graphics on
the lynx...
Of course, another option would be to use smaller font and
bitmap files (native 8x6) - however in this case storage space
was not a problem, so this solution was quicker.
Get Screen Pos
We're going to use a function called "GetScreenPos"... this
will calculate the memory address of the pixel we want to
write from an X,Y co-ordinate:
Each line is 80 bytes wide ($50) and our screen starts at
$C000 so our formula is:
Address = $C000 + (Ypos * $50) + Xpos
We effect a multiply by bitshits... $50 in binary is %01010000
...
We will shift the Y bits into each '1' position then add to a
running total... then add the base and the Xpos
Joystick Reading
When we want to read in from the joystick we just use $FCB0
We also have a 'Wait for fire' function... this randomizes the
seed, and waits for fire to be released, then pressed.
It's used for menus and between levels.
Sprite Data and Palette
We have 5 different files of sprites
96 for the font
16x4 for the 4 banks of sprites
Finally we define our palette... in native format for the
lynx hardware.
Even with
the 8x6 graphics, the gameplay screen is a little small...
We could always scale down to 4x4, but this would make the
graphics very unclear!
Lesson
YQuest4 - C64 Specific code
The C64 version uses single byte wide (4 pixel) sprites - it
uses 4 color mode.
Lets take a look!
See
YQuest folder folder
Commodore 64 Header
So we can support multiple platforms with different screen
sizes we have platform specific screen size definitions
Each system needs an area of ram for game variables, know as
'UserRam'... we also have a 'SpritePointer' - used as temp
storage for the color lookup
We use CollisionMasks - these will skip some bits of the
internal object positions, this is to ensure we don't detect
a collision that is not visible due to the low resolution of
screen co-ordinates.
When our program loads we need to initialize the graphics
hardware and set up the screen.
As our program is so big...We need to offset the screen...
by default the screen ram is at $2000 - but we'll move it to
$6000 by setting a 'base' of $4000.
This also moves the colors from $400 to $4400... the $D800
color attributes are unmoved
We zero the game's ram data. using CLDIR0 - which fills an
area with zeros
ShowTitle
When The title screen starts, we first reset the game
settings for a new game to start,
Then we clear the screen.
We want to draw the tilescreen... it's held as a 1 byte
per tile data block in our code at 'TitlePic'
We use our ShowSprite Routine to draw it to the screen...
however we need to calculate the sprite address of the
desired tile, so we use GetSpriteAddr to do so
We need to show a few text items to the screen
A 'Press Fire' Message
The 'LearnASM.Net' URL (Very Important!)
The Highscore.
LevelStart
When the game or a new level starts, we first wait for
fire to be pressed.
Next we clear the screen,
Reset the player position
Initialize the level enemies with LevelInit
Level Loop
We have to pause a while during the game loop, so we read
in from the joystick during this time, and if the user
presses a key we'll store it for later.
we use Y for a loop counter - and X for any pressed button.
we use function call Player_ReadControlsDual - which will
read in the joystick and fire button
First we draw the UI (Score etc)
Next we clear the player sprite.
To slow down the acceleration, we have a 'timeout'... if a
key was pressed, for a short time we'll ignore any other
keys.
We're going to process the input,
We process each direction, and alter the acceleration of the
player if a direction is pressed...
We have a 'Bullet fire' routine (in the common code) for
Fire 1
Fire 2 will immediately stop the player
If any button is pressed, the key timeout is set
Finally we run the multiplatform 'Draw and Move' routine,
this draws the player, and handles movement and drawing of
enemies and bullets
Clear Screen and Print Char
To clear the screen (CLS), we need to wipe the bitmap area
at address $6000 - we use the CLDIR0 command to do this,
which wipes z_bc bytes from address z_hl
PrintChar will show a character to the screen, using a
bitmap font the same as the sprites...
We need to calculate the position in the font data of the
character
Each Character is 16 bytes, and there are no characters
below 32 (space)
We need to store the pointer to the palette entry for the
font (16*2)
We also need to multiply the Y position by 8
Once we've shown a character, we increment the X position
for the next character
Sprite routines
We have a couple of ShowSprite routines...
BlankSprite will draw the empty sprite (Space in the font),
using the object z_IX - to clear the old position of the
object
GetSpriteAddr will calculate the address in ram of the
sprite data we want to show to the screen
We also calculate the sprite color pointer
We also handle the sprite frame... there are 4 frames of
animation for each sprite - each bank is 128 bytes
DoGetSpriteObj will show object z_IX to the screen, getting
Sprite, and XY pos from z_IX
ShowSprite will draw Sprite A onscreen (at XY position BC)
Once we've done the sprite bitmap data we need to transfer
the colors to the color attribute ram areas.
we use the calculate sprite color pointer (SprPtn) to get the
correct color bytes from the palette
Get Screen Pos
The GetScreenPos calculation is pretty tricky!
Normally our screen address starts at $2000... however
we've offset the screen by $4000, so it's at $6000 now
Because of the screen layout (8 Y lines are consecutive in
ram), we're only going to look at the top 5 bits of the Y
position
Each line is 320 pixels, and there are 8 pixels per byte (160
pix and 4 px per byte in 4 color mode)... this means our
screen is 40 bytes wide, and because the 8 lines of each
horizontal strip are combined, the top 5 bits must be
multiplied by 640 ($140) (40*8=320)
Since the 8 Y lines are consecutive in memory, between each
horizontal byte, we multiply the Xpos by 8
Our formula is:
$6000 + (Y * 40) + Xpos * 8
We achieve the multiplication by bitshifting ops.
Each 8x8 square has it's own color attributes... these are
stored at two memory addresses $D800+ and $4400+
There is one byte per 8x8 block at each of these addresses...
we calculate these using formulas below:
We calculate these in pretty much the same way as before.
Joystick Reading
When we want to read in from the joystick we just use port
$DC01
We also have a 'Wait for fire' function... this randomizes the
seed, and waits for fire to be released, then pressed.
It's used for menus and between levels.
Sprite Data and Palette
We have 5 different files of sprites
96 for the font
16x4 for the 4 banks of sprites
We define a 2 byte palette for each sprite (0-15), and a
16th entry for the font.
Lesson
YQuest5 - Apple II Specific code
The Apple II version is basically black and white, it's actually
pretty straight forward,
Lets get the port done!
See
YQuest folder folder
Apple II Header
So we can support multiple platforms with different screen
sizes we have platform specific screen size definitions
Each system needs an area of ram for game variables, know as
'UserRam'
We use CollisionMasks - these will skip some bits of the
internal object positions, this is to ensure we don't detect a
collision that is not visible due to the low resolution of
screen co-ordinates.
Our program loads at address $0C00 .
We need to set up our screen by reading from a few ports...
the act of reading enables the option
We zero the game's ram data. using CLDIR0 - which fills an
area with zeros
A single byte on the Apple 2 has 7
pixels not 8!
For convenience, we'll rescale our sprites, and use 7x8
graphics, rather than the 8x8 we use on other systems
ShowTitle
When The title screen starts, we first reset the game
settings for a new game to start,
Then we clear the screen.
We want to draw the tilescreen... it's held as a 1 byte per
tile data block in our code at 'TitlePic'
We use our ShowSprite Routine to draw it to the screen...
however we need to calculate the sprite address of the desired
tile, so we use GetSpriteAddr to do so
We need to show a few text items to the screen
A 'Press Fire' Message
The 'LearnASM.Net' URL (Very Important!)
The Highscore.
LevelStart
When the game or a new level starts, we first wait for fire
to be pressed.
Next we clear the screen,
Reset the player position
Initialize the level enemies with LevelInit
Level Loop
We have to pause a while during the game loop, so we read in
from the joystick during this time, and if the user presses a
key we'll store it for later.
we use Y for a loop counter - and X for any pressed button.
we use function call Player_ReadControlsDual - which will read
in the joystick and fire button
First we draw the UI (Score etc)
Next we clear the player sprite.
To slow down the acceleration, we have a 'timeout'... if a
key was pressed, for a short time we'll ignore any other keys.
We're going to process the input,
We process each direction, and alter the acceleration of the
player if a direction is pressed...
We have a 'Bullet fire' routine (in the common code) for Fire
1
Fire 2 will immediately stop the player - but we don't have a
fire 2 set up on the Apple II version
If any button is pressed, the key timeout is set
Finally we run the multiplatform 'Draw and Move' rotuine,
this draws the player, and handles movement and drawing of
enemies and bullets
Clear Screen and Print Char
To clear the screen (CLS), we need to wipe the bitmap area
at address $4000 - we use the CLDIR0 command to do this, which
wipes z_bc bytes from address z_hl
PrintChar will show a character to the screen, using a
bitmap font the same as the sprites...
We need to calculate the position in the font data of the
character
Each Character is 8 bytes, and there are no characters below
32 (space)
We also need to multiply up the X,Y pos into an 8x8 grid
position.
Once we've shown a character, we increment the X position for
the next character
Sprite routines
We have a couple of ShowSprite routines...
BlankSprite will draw the empty sprite (Space in the font),
using the object z_IX - to clear the old position of the object
GetSpriteAddr will calculate the address in ram of the sprite
data we want to show to the screen
We also handle the sprite frame... there are 4 frames of
animation for each sprite - each bank is 256 bytes
DoGetSpriteObj will show object IX to the screen, getting
Sprite, and XY pos from z_IX
ShowSprite will draw Sprite A onscreen (at XY position BC)
Note, due to the BBC screen layout, we're 'rounding' the Y
co-ordinates to an 8 line tall grid
Get Screen Pos
Calculating our screen address is rather annoying on the Apple
II - the screen is split into 3rds... and each line in 8 is
separate... the result is a rather annoying formula:
We test the top 2 bits and branch out into sub-code that will do
additions to calculate the final address...
Joystick Reading
Apple Joysticks are annoying!
they are analog... we have to strobe the port then read
from the X and Y ports, and count up until the top bit
changes...
this is a 'timer'...using just 1 bit (the top one) it
effectively returns an 'analog' value from about 0-100
We're reading in both the X and Y axis at the same time,
skipping out of part of the loop when the X or Y part is
complete.
We also have a 'Wait for fire' function... this randomizes the
seed, and waits for fire to be released, then pressed.
It's used for menus and between levels.
Sprite Data
We have 5 different files of sprites
96 for the font
16x4 for the 4 banks of sprites
Lesson
YQuest6 - Atari 800 / Atari 5200 Specific code
The Atari 800/5200 version uses 4 color mode... due to the size
of the program, we'll have to position the Display list right at
the end of the ROM so it will work ok on the Atari 800.
The sprites will be half width 4x8 pixels
See
YQuest folder folder
Atari 800 / 5200 Header
So we can support multiple platforms with different screen
sizes we have platform specific screen size definitions
Each system needs an area of ram for game variables, know as
'UserRam'
We use CollisionMasks - these will skip some bits of the
internal object positions, this is to ensure we don't detect a
collision that is not visible due to the low resolution of
screen co-ordinates.
Our program start depends on the system we're building for,
Our first task is to clear the GITA and zero page,
Next we set up the screen Display List and colors
We zero the game's ram data. using CLDIR0 - which fills an
area with zeros
The Game is intended
to work in 4 color mode, however if you wish, you could run it
in 2 color mode instead, the Graphics routines which do the 2
color option have been left intact.
ShowTitle
When The title screen starts, we first reset the game
settings for a new game to start,
Then we clear the screen.
We want to draw the tilescreen... it's held as a 1 byte
per tile data block in our code at 'TitlePic'
We use our ShowSprite Routine to draw it to the screen...
however we need to calculate the sprite address of the
desired tile, so we use GetSpriteAddr to do so
We need to show a few text items to the screen
A 'Press Fire' Message
The 'LearnASM.Net' URL (Very Important!)
The Highscore.
LevelStart
When the game or a new level starts, we first wait for
fire to be pressed.
Next we clear the screen,
Reset the player position
Initialize the level enemies with LevelInit
Level Loop
We have to pause a while during the game loop, so we read
in from the joystick during this time, and if the user
presses a key we'll store it for later.
we use Y for a loop counter - and X for any pressed button.
we use function call Player_ReadControlsDual - which will
read in the joystick and fire button
First we draw the UI (Score etc)
Next we clear the player sprite.
To slow down the acceleration, we have a 'timeout'... if a
key was pressed, for a short time we'll ignore any other
keys.
We're going to process the input,
We process each direction, and alter the acceleration of the
player if a direction is pressed...
We have a 'Bullet fire' routine (in the common code) for
Fire 1
Fire 2 will immediately stop the player - or it would, but
we've not mapped a button to this function!
If any button is pressed, the key timeout is set
Finally we run the multiplatform 'Draw and Move' rotuine,
this draws the player, and handles movement and drawing of
enemies and bullets
The system is still too fast... so we slow things down a
little more.
Clear Screen and Print Char
To clear the screen (CLS), we need to wipe the bitmap area
at address $2060 - we use the CLDIR0 command to do this,
which wipes z_bc bytes from address z_hl
PrintChar will show a character to the screen, using a
bitmap font the same as the sprites...
We need to calculate the position in the font data of the
character
Each Character is 16 bytes, and there are no characters
below 32 (space)
We also need to multiply up the X,Y pos into an 8x8 grid
position.
Once we've shown a character, we increment the X position
for the next character
Sprite routines
We have a couple of ShowSprite routines...
BlankSprite will draw the empty sprite (Space in the font),
using the object z_IX - to clear the old position of the
object
GetSpriteAddr will calculate the address in ram of the
sprite data we want to show to the screen
We also handle the sprite frame... there are 4 frames of
animation for each sprite - each bank is 128 bytes
DoGetSpriteObj will show object IX to the screen, getting
Sprite, and XY pos from z_IX
ShowSprite will draw Sprite A onscreen (at XY position BC)
Note, due to the Atari 5200 screen layout, we're 'rounding'
the Y co-ordinates to an 8 line tall grid, and the X position
to to a 4 pixel wide (one character) grid
Get Screen Pos
GetScreenPos calculates the screen address from X,Y... our
screen base is $2060 - and each line is 40 bytes wide... so
our calculation is:
Address= $2060 + (Ypos * 40) + Xpos
Because the 6502 has no multiplication, we do bitshifts to
double the value... it's easiest to split (Ypos * 40) into
(Ypos * 32) + (Ypos * 8)
We also add the Xpos and $2060 - this calculates the final
address in memory...
Joystick Reading
On the Atari 800 , we can read in the UDLR controls of the
first joystick from the PIA
The top nibble is joystick 2 - so we ignore this for todays
example
On the 5200 we'll have to read from the analog hardware
We read in each axis from the POKEY ($E800/1)
We use a deadzone of 128 - and if the axis is outside of that
deadzone we set a bit of the resulting joystick value - this
converts analog to digital.
We also have a 'Wait for fire' function... this randomizes
the seed, and waits for fire to be released, then pressed.
It's used for menus and between levels.
Sprite Data and Display List
We have 5 different files of sprites
96 for the font
16x4 for the 4 banks of sprites
The Display list defines the screen layout... we need one
byte per line for the bitmap modes, and
becares/Yquest6_15_.pnguse of limitations of the hardware we
have to manually define an offset when the VRAM reaches
address $3000
The end of the list contains a loop back to the start ... this
defines the screen...
if we set Smode to $0E we will have a 4 color screen... if we
se Smode to $0F we will have a 2 color high res screen.
Finally we have the rom footer, with the start of the
program address.
Lesson
YQuest7 - PC Engine / Turbografix Specific code
Lets port Yquest to the PC-Engine
For simplicity, We're going to use the tilemap to do the graphics...
we're going to have some trouble though, as the default bank mapping
only gives us 8k ram!
See
YQuest folder folder
PC Engine Header
So we can support multiple platforms with different screen
sizes we have platform specific screen size definitions
Each system needs an area of ram for game variables, know as
'UserRam'
We use CollisionMasks - these will skip some bits of the
internal object positions, this is to ensure we don't detect a
collision that is not visible due to the low resolution of
screen co-ordinates.
We also need to define the memory address of the Direct page,
and set up some symbols for the common code.
Our Header needs to set up the pages of Rom...
each page is just 8k, but our program is too big for that, so we
need to page multiple banks in... when the system starts only
the first bank is paged in at $E000... so our first task is to
page in the other banks (with our code running from $E000) then
run the actual address (around $4000)
We page the last bank in after the jump, as that was the bank
our program was running from before the jump!
We need to turn on the screen, Reset the tilemap, init the
palette, and load the tile patterns
We zero the game's ram data. using CLDIR0 - which fills an
area with zeros
The "org $4000"
represents the running address the code will finally run from,
not the address it will run at startup...
Because of this before the "Restart" we can cannot use JSR or
JMP commands as they use absolute addresses, but we can use
branches OK if we wish
ShowTitle
When The title screen starts, we first reset the game
settings for a new game to start,
Then we clear the screen.
We want to draw the tilescreen... it's held as a 1 byte per
tile data block in our code at 'TitlePic'
We use our ShowSprite Routine to draw it to the screen...
GetSpriteAddr calculates the pattern number for the sprite.
We need to show a few text items to the screen
A 'Press Fire' Message
The 'LearnASM.Net' URL (Very Important!)
The Highscore.
LevelStart
When the game or a new level starts, we first wait for fire
to be pressed.
Next we clear the screen,
Reset the player position
Initialize the level enemies with LevelInit
Level Loop
We have to pause a while during the game loop, so we read in
from the joystick during this time, and if the user presses a
key we'll store it for later.
we use Y for a loop counter - and X for any pressed button.
we use function call Player_ReadControlsDual - which will read
in the joystick and fire button
First we draw the UI (Score etc)
Next we clear the player sprite.
To slow down the acceleration, we have a 'timeout'... if a
key was pressed, for a short time we'll ignore any other keys.
We're going to process the input,
We process each direction, and alter the acceleration of the
player if a direction is pressed...
We have a 'Bullet fire' routine (in the common code) for Fire
1
Fire 2 will immediately stop the player
If any button is pressed, the key timeout is set
Finally we run the multiplatform 'Draw and Move' rotuine,
this draws the player, and handles movement and drawing of
enemies and bullets
The system is still too fast... so we slow things down a
little more.
Clear Screen and Print Char
To clear the screen (CLS), we need to set all the tiles to 0
(Space in the font)
Printchar will show a character to the screen... the first
96 patterns of the tilemap are the font... the first character
is space (Char 32)
Sprite routines
We have a couple of ShowSprite routines...
BlankSprite will draw the empty sprite (Space in the font),
using the object z_IX - to clear the old position of the object
GetSpriteAddr will calculate the pattern we want to show to
the screen.
The first 96 are our font.
We also handle the sprite frame... there are 4 frames of
animation for each sprite - each bank is 16 tiles
DoGetSpriteObj will show object IX to the screen, getting
Sprite, and XY pos from z_IX
ShowSprite will draw Sprite A onscreen (at XY position BC)
Note, due to the Atari 5200 screen layout, we're 'rounding' the
Y co-ordinates to an 8 line tall grid, and the X position
to to a 4 pixel wide (one character) grid
Get VDPScreenPos, PrepareVram and
DefineTiles
We're going to define a function called GetVDPScreenPos to
select an X,Y Tilemap position in Vram memory
As the tilemap is at VRAM address $0000 and each line is 32
tiles wide, our formula is:
Address=(Ypos *32) + X
We multiply Y by 32 by bishifts, and select the calculated
address...
We're going to define a command called PrepareVram - which
will select a memory address to write to, we'll need this for
our define tiles function... we write the Low address byte to
$0102, and the High address to $0103 when register 0 is selected
with ST0
We'll use zero page entries z_de to store the address we want to
use.
When we want to define tiles -we'll store a source address in
VRAM z_de... a source address in ROM in z_hl, and a byte count
in z_bc
We use the PrepareVram function to select an address, then write
new bytes to $0102/3 after selecting Reg 0 with ST0
Joystick Reading
We need to reset the 'multitap' hardware by sending 1,3 to
port $1000
We then send a 1 - read in 4 bits... and a 0 and read in 4 bits
This returns a byte in the format:
Run / Start / B / A / Left / Down / Right / Up
We also have a 'Wait for fire' function... this randomizes the
seed, and waits for fire to be released, then pressed.
It's used for menus and between levels.
Sprite Data and First bank header
We have 5 different files of sprites
96 for the font
16x4 for the 4 banks of sprites
At the end of the first 8k bank we need the rom header, with
the start address,
This will end up at $FFFE when the system starts, and will
boot-strap our banking routine on startup
We now have our palette in native format
Finally we need to pad the rom out to an 8k boundary
Lesson
YQuest8 - VIC20 Specific code
The Vic-20 version uses custom characters to simulate Tiles /
Sprites... Lets look at the VIC-20 version of the game
See
YQuest folder folder
VIC 20 Header
So we can support multiple platforms with different screen
sizes we have platform specific screen size definitions
Each system needs an area of ram for game variables, know as
'UserRam'... we also have a 'SpritePointer' - used as temp
storage for the color lookup
We use CollisionMasks - these will skip some bits of the
internal object positions, this is to ensure we don't detect a
collision that is not visible due to the low resolution of
screen co-ordinates.
We're going to be defining a ROM cartridge, so our RAM for our
game is at $1000
We need to start our ROM, we need a pointer to the start of
our code...
The start of our code will Init the screen.
Next we transfer our bitmap data to $1C00 (the custom
characters)
We zero the game's ram data. using CLDIR0 - which fills an
area with zeros
ShowTitle
When The title screen starts, we first reset the game settings
for a new game to start,
Then we clear the screen.
We want to draw the tilescreen... it's held as a 1 byte per
tile data block in our code at 'TitlePic'
We use our ShowSprite Routine to draw it to the screen...
however we need to calculate the sprite address of the desired
tile, so we use GetSpriteAddr to do so
We're using the same title screen as the Atari Lynx... but our
screen is a little bigger, so we offset by 1 horizontal
character
We need to show a few text items to the screen
A 'Press Fire' Message
The 'LearnASM.Net' URL (Very Important!)
The Highscore.
LevelStart
When the game or a new level starts, we first wait for fire to
be pressed.
Next we clear the screen,
Reset the player position
Initialize the level enemies with LevelInit
Level Loop
We have to pause a while during the game loop, so we read in
from the joystick during this time, and if the user presses a
key we'll store it for later.
we use Y and B for a loop counter - and X for any pressed
button.
we use function call Player_ReadControlsDual - which will read
in the joystick and fire button
First we draw the UI (Score etc)
Next we clear the player sprite.
To slow down the acceleration, we have a 'timeout'... if a key
was pressed, for a short time we'll ignore any other keys.
We're going to process the input,
We process each direction, and alter the acceleration of the
player if a direction is pressed...
We have a 'Bullet fire' routine (in the common code) for Fire 1
Fire 2 would immediately stop the player, but we don't have a
2nd fire configured on the VIC
If any button is pressed, the key timeout is set
Finally we run the multiplatform 'Draw and Move' routine, this
draws the player, and handles movement and drawing of enemies
and bullets
Clear Screen and Print Char
To clear the screen (CLS), we need to wipe the screen area at
address $1E00 - we use the CLDIR command to do this, which wipes
z_bc bytes from address z_hl
To clear the screen, we use Character 32 - which has been offset
by 128 by our custom characters
Due to hardware limitations, we can't use our custom font, and
have to use the internal one.
PrintChar will show a character to the screen, the VIC does not
use ASCII, and some of the characters are in different places,
to compensate for this, we have to subtract 64 from A-Z... we
also strip the top 3 bits, as there is no lowercase characters
in the font.
We then add 128, as our custom characters are 0+ in the
characters set.
Once we've drawn the character, we need to set the color of the
matching screen character
Characters
and 'sprites' are basically the same thing on the VIC, as
we're using custom characters for our configured graphics.
When we show characters to the screen We're drawing the font
as White (Color 1)
Sprite routines
We have a couple of ShowSprite routines...
BlankSprite will draw the empty character (Space in the system
font), using the object z_IX - to clear the old position of the
object
GetSpriteAddr will calculate the address in ram of the sprite
data we want to show to the screen
We also calculate the sprite color pointer
We also handle the sprite frame... there are 4 frames of animation
for each sprite - each bank is 128 bytes
DoGetSpriteObj will show object z_IX to the screen, getting
Sprite, and XY pos from z_IX
ShowSprite will draw character A onscreen (at XY position BC)
After the character tile has been set we also need to set the
correct color at address $9600+
we use the calculate sprite color pointer (SprPtn) to get the
correct color bytes from the palette
Get Vdp Screen Pos
When we want to calculate the memory address our formula is:
Address= $1E00 + (Ypos * 22) + Xpos
As multiplying by 22 isn't so easy (and our screen is pretty
short) we'll use a loop and addition to effect the multiply
Joystick Reading
On the Vic 20, we ned to read in from ports $9120 and $911F to
get UDLR and Fire...
We'll need to ensure that port B is set to READ by writing to
$9122
Sprite Data, Palette and Screen Init sesttings
We have 4 different files of sprites
16x4 for the 4 banks of sprites
We define a 1 byte palette for each sprite (0-15)
We have 16 bytes of screen init data, these are transferred to
the screen hardware during startup
Lesson
YQuest9 - NES Specific code
Lets look at the NES port - We'll be using the tilemap to do the
ingame graphics.
Unfortunately because we can't write to VRAM except during Vblank,
we'll create a buffer to store the changes, then transfer the buffer
during Vblank
See
YQuest folder folder
NES Header
So we can support multiple platforms with different screen sizes
we have platform specific screen size definitions
Each system needs an area of ram for game variables, know as
'UserRam'
We use CollisionMasks - these will skip some bits of the internal
object positions, this is to ensure we don't detect a collision
that is not visible due to the low resolution of screen
co-ordinates.
We also need to define the memory address of the Direct page, and
set up some symbols for the common code.
We need a rom header... We selected Mapper 4 - it has 8k VRAM ,
8K Sram and 128k rom
We need to declare some more symbols...
We have an address for the VDPBuffer (Tilemap) the SpriteBuffer
(not actually needed for this program)
VDP_CT is the offset within the buffer of the next free byte of
the tilemap buffer.... the buffer will be sent to VRAM during next
vblank
We need to define an interrupt handler which will transfer the
data from our buffer to VRAM... this will run during VBLANK
We use a DMA to transfer the sprite data, and the data in the
tilemap buffer is transferred manually
The VDPBuffer contains 3 bytes per entry... HL VRAM Memory
address, and a new byte to write to that address
We need to turn on the screen, Define the palette and pattern
data (tiles)
We also need to turn on the extra 'in cartridge' vram with
register $A001
We zero the game's ram data. using CLDIR0 - which fills an area
with zeros
Our NMI routine will write some data to
VRAM, but speed is a problem, as we don't have much time... this
routine will write up to 32 tiles per vblank - too many more and
it will overrun the Vblank...
If you're being a smarty pants, you could write a RLE routine
that compresses the data, so you could set larger numbers of
tiles in one go!
ShowTitle
When The title screen starts, we first reset the game settings
for a new game to start,
Then we clear the screen.
We want to draw the tilescreen... it's held as a 1 byte per
tile data block in our code at 'TitlePic'
We use our ShowSprite Routine to draw it to the screen...
GetSpriteAddr calculates the pattern number for the sprite.
We need to show a few text items to the screen
A 'Press Fire' Message
The 'LearnASM.Net' URL (Very Important!)
The Highscore.
LevelStart
When the game or a new level starts, we first wait for fire to
be pressed.
Next we clear the screen,
Reset the player position
Initialize the level enemies with LevelInit
Level Loop
We have to pause a while during the game loop, so we read in
from the joystick during this time, and if the user presses a
key we'll store it for later.
we use Y and z_b for a loop counter - and X for any pressed
button.
we use function call Player_ReadControlsDual - which will read
in the joystick and fire button
First we draw the UI (Score etc)
Next we clear the player sprite.
To slow down the acceleration, we have a 'timeout'... if a key
was pressed, for a short time we'll ignore any other keys.
We're going to process the input,
We process each direction, and alter the acceleration of the
player if a direction is pressed...
We have a 'Bullet fire' routine (in the common code) for Fire 1
Fire 2 will immediately stop the player
If any button is pressed, the key timeout is set
Finally we run the multiplatform 'Draw and Move' routine, this
draws the player, and handles movement and drawing of enemies
and bullets
The system is still too fast... so we slow things down a little
more.
Clear Screen and Print Char
To clear the screen (CLS), we need to set all the tiles to 0
(Space in the font)
Printchar will show a character to the screen... the first 96
patterns of the tilemap are the font... the first character is
space (Char 32)
Sprite routines
We have a couple of ShowSprite routines...
BlankSprite will draw the empty sprite (Space in the font), using
the object z_IX - to clear the old position of the object
GetSpriteAddr will calculate the pattern we want to show to the
screen.
The first 96 are our font.
We also handle the sprite frame... there are 4 frames of animation
for each sprite - each bank is 16 tiles
DoGetSpriteObj will show object IX to the screen, getting
Sprite, and XY pos from z_IX
ShowSprite will draw Sprite A onscreen (at XY position BC).... It
doesn't do this directly however, GetVdpScreenPos calculates a
position in the VDPbuffer (The buffer for the tilemap) and sets
the byte corresponding to the tile...
It will be shown onscreen next vblank
Get VDPScreenPos, PrepareVram,
DefineTiles and More
We're going to define a function called GetVDPScreenPos to
select an X,Y Tilemap position in Vram memory
The tilemap is 32 tiles (bytes wide).. and the tilemap starts from
$2000, so the formula is...
Address=$2000 + (Ypos *32) + X
We multiply Y by 32 by bitshifts, and select the calculated
address...
We don't actually write into the Tilemap... we write to the buffer
'VDPBUFFER'
First We write the HL address in vram we want to write to, then we
write the byte to change ... writing the byte to change occurs in
the sprite routine.
GetVdpBufferCT calculates the next free entry in the buffer...
the buffer has a limit of 32 bytes to change (during vblank)... so
if it's reached 32x3 then we wait for the next vblank (when it
clears down)
We write a Zero to VDP_CT during processing - in case the VBLANK
runs while we're writing more data.
When we want to define tiles we do this directly to VRAM (not
via the buffer)... we use PrepareVram to select a destination VRAM
address
We write z_bc bytes of data from z_hl to the VRAM via port $2007
When we want to define tiles, we can turn off the screen, this
means we don't need to wait for VBLANK before writing.
We use $2001 to set the PPUMASK which turns off the tilemap layer
We use $2000 to turn off the NMI interrupt handler
PrepareVram will select a destination VRAM address for writing
to, we write the address (from z_de) to $2006 , High byte then Low
byte
Waitframe will wait for VBLANK... we check this by writing a 0
to zero page entry 'Vblanked' to change... this will occur when
our NMI runs.
Joystick Reading
We need to read in a sequence of 8 bits from port $4016 to get
each direction key... we also need to strobe the port by writing 1
then 0 to the same port...
The 8 reads from the port will return the directions:
Read 1 - A
Read 2 - B
Read 3 - Select
Read 4 - Start
Read 5 - Up
Read 6 - Down
Read 7 - Left
Read 8 - Right
We also have a 'Wait for fire' function... this randomizes the
seed, and waits for fire to be released, then pressed.
It's used for menus and between levels.
Sprite Data and First bank header
We have 5 different files of sprites
96 for the font
16x4 for the 4 banks of sprites
We now have our palette in native format
At the end of our cartridge we have the 'footer'... it has a
link to the Start of the program and the NMI interrupt handler
Lesson
YQuest10 - SNES Specific code
It's time for the SNES version... like the NES we need a buffer of
the tilemap data... unlike the NES version we can use a DMA to
copy a whole tilemap during vblank.
See
YQuest folder folder
SNES Header
So we can support multiple platforms with different screen
sizes we have platform specific screen size definitions
Each system needs an area of ram for game variables, know as
'UserRam'
We use CollisionMasks - these will skip some bits of the
internal object positions, this is to ensure we don't detect a
collision that is not visible due to the low resolution of
screen co-ordinates.
We also need to define the memory address of the Direct page,
and set up some symbols for the common code.
We also need a 'ScreenBuffer' in RAM - this will contain the
tilemap, and will be transferred fom RAM to VRAM
We need to turn on the Tile layer and set up the palette
We need to set the scroll position of the tilemap,
We clear the RAM buffer (Tilemap copy, transferred to VRAM
during Vblank)
We transfer our tile patterns to vram with 'DefineTiles',
Finally we turn on the screen.
We then initialize the 'ChibiSound' Sound driver - which copies
the program code to the SPC700 sound chip
We zero the game's ram data. using CLDIR0 - which fills an
area with zeros
Sound on the SNES is
painfully complex, the SPC700 is separate processor with
separate ram, and Is NOT a 6502 based chip... for more details
see here
ShowTitle
When The title screen starts, we first reset the game
settings for a new game to start,
Then we clear the screen.
We want to draw the tilescreen... it's held as a 1 byte per
tile data block in our code at 'TitlePic'
We use our ShowSprite Routine to draw it to the screen...
GetSpriteAddr calculates the pattern number for the sprite.
We need to show a few text items to the screen
A 'Press Fire' Message
The 'LearnASM.Net' URL (Very Important!)
The Highscore.
LevelStart
When the game or a new level starts, we first wait for fire
to be pressed.
Next we clear the screen,
Reset the player position
Initialize the level enemies with LevelInit
Level Loop
We have to pause a while during the game loop, so we read in
from the joystick during this time, and if the user presses a
key we'll store it for later.
we use Y and z_b for a loop counter to slow down the game -
and X for any pressed button.
we use function call Player_ReadControlsDual - which will read
in the joystick and fire button
First we draw the UI (Score etc)
Next we clear the player sprite.
To slow down the acceleration, we have a 'timeout'... if a
key was pressed, for a short time we'll ignore any other keys.
We're going to process the input,
We process each direction, and alter the acceleration of the
player if a direction is pressed...
We have a 'Bullet fire' routine (in the common code) for Fire
1
Fire 2 will immediately stop the player
If any button is pressed, the key timeout is set
Finally we run the multiplatform 'Draw and Move' routine,
this draws the player, and handles movement and drawing of
enemies and bullets
The system is still too fast... so we slow things down a
little more.
Clear Screen and Print Char
To clear the screen (CLS), we need to set all the tiles to 0
(Space in the font)
Printchar will show a character to the screen... the first
96 patterns of the tilemap are the font... the first character
is space (Char 32)
Sprite routines
We have a couple of ShowSprite routines...
BlankSprite will draw the empty sprite (Space in the font),
using the object z_IX - to clear the old position of the object
GetSpriteAddr will calculate the pattern we want to show to
the screen.
The first 96 are our font.
We also handle the sprite frame... there are 4 frames of
animation for each sprite - each bank is 16 tiles
DoGetSpriteObj will show object IX to the screen, getting
Sprite, and XY pos from z_IX
ShowSprite will draw Sprite A onscreen (at XY position BC)....
It doesn't do this directly however, GetVdpScreenPos calculates
a position in the tilemap buffer in z_hl...
We then transfer the two bytes that define the tiles in to ram.
It will be shown onscreen next vblank
Get VDPScreenPos, PrepareVram,
DefineTiles and More
We're going to define a function called GetVDPScreenPos to
select an X,Y Tilemap position in Vram memory
We need to calculate an address in the ScreenBuffer for the
tile.
Each tile is 2 bytes, and the tilemap is 32 tiles wide... so the
formula is:
When we want to define tiles we do this directly to VRAM (not
via the buffer)... we use PrepareVram to select a destination
VRAM address
We wait for Vblank before writing any data.
We write z_bc bytes of data from z_hl to the VRAM via ports
$2119 and $2118...
We have to write in this order, as we've set Autoinc to occur on
$2118
To Check if we're currently in Vblank, we test the top bit of
$4212 - while this is Zero we're not currently in vbank
PrepareVram will select a destination VRAM address for writing
to, we write the address (from z_de)... low byte to to
$2116 , High byte to $2117
We've defined a Custom NMI handler... this will run during
VBlank.... we're going to use a DMA to quickly transfer the
SnesScreenBuffer from ram to VRAM
We have to select a destination Vram address with $2116/7.
We define the autoinc mode to update on the write to $2119 - we
d this with port $2115
Next and select the destination port with $4301 - we select
$2118 - as we'll write in word pairs.
We specify the source (as a 24 bit number) using $4302/3/4...
and the number of bytes to transfer with $4305/6/7
We disable the H-DMA (it's a special function we don't want)
with $420C
Finally we start the DMA transfer with bit 0 of $420B.
Once we've finished, we set the AutoInc with port $2115 back to
update on $2118
Joystick Reading
We need to read in a sequence of 8 bits from port $4016 to get
each direction key... we also need to strobe the port by writing
1 then 0 to the same port...
The 8 reads from the port will return the directions:
Read 1 - A
Read 2 - B
Read 3 - Select
Read 4 - Start
Read 5 - Up
Read 6 - Down
Read 7 - Left
Read 8 - Right
We also have a 'Wait for fire' function... this randomizes the
seed, and waits for fire to be released, then pressed.
It's used for menus and between levels.
Sprite Data and First bank header
We have 5 different files of sprites
96 for the font
16x4 for the 4 banks of sprites
We now have our palette in native format
At the end of our cartridge we have the 'footer'... it has a
link to the Start of the program (Reset Vector) and the NMI
interrupt handler
We need an 8 and 16 bit set of Vectors
Lesson
YQuest11 - Hardware Sprites on the PC Engine / Turbografix
Our Yquest program currently only uses the Tilemap... Lets
extend it to use Hardware sprites!.
This will give the game nice smooth movement... lets learn how
to use PCE hardware sprites!
See
YQuest folder folder
Multiplatform Sprite code
We had an unused byte in the object definitions before...
We're going to use it now!
This will be the 'Hardware sprite number'...
A Zero will be the same as before - a tile will be used 1+ will
be a numbered hardware sprite - relating to one of the hardware
sprites the system is capable of.
This flexibility to use tiles or hardware sprites helps with
systems with small numbers of hardware sprites
We'll need to set banks of objects to consecutive sprite
numbers (EG bullets)...
We use SetHardwareSprites to do this.
We need to alter the DrawAndMove function and check the
'hardware sprite number'
Values 1-128 are hardware sprites... other values (0 / 255 etc)
are soft sprites
SATB - Sprite attribute table buffer
Sprites are stored in regular vram... the sprite definitions are
stored in special ram which we CANNOT ACCESS...however we can allocate
a bank of 256 addresses (each containing one word) called SATB, and
then get the hardware to copy that ram to the special ram... it's
suggested you use $7F00 for that purpose.
To start the copy we just write the address to control reg $13
The PC Engine Sprite table allows for up to 64 sprites... each one
has 4 words of data - making 256 words in total... the format is as
follows
Byte
F
E
D
C
B
A
9
8
7
6
5
4
3
2
1
0
Notes
1
-
-
-
-
-
-
Y
Y
Y
Y
Y
Y
Y
Y
Y
Y
Y=Ypos (64 is
first visible line)
2
-
-
-
-
-
-
X
X
X
X
X
X
X
X
X
X
X=Xpos (32 is
first visible line)
3
-
-
-
-
-
A
A
A
A
A
A
A
A
A
A
A
A=Address (Top
10 bits $trueaddress>>5 )
4
YF
-
YS
YS
XF
-
-
XS
F
-
-
-
P
P
P
P
YF=Yflip XF=Xflip
YS=Ysize XS=Xsize
F=Foreground
(infront of tilemap) P=Palette
PC Engine Sprite code
the PC-Engine hardware sprites use palette entries 256+...
We're going to set them to the same colors as our tiles.
The Hardware sprites are in a different format to the regular
patterns - They are 16x16... in 4 bitplanes
We can export the sprite data in the right format using
AkuSprite Editor...
We also need to 'space out' the sprites from 8x8 to 16x16
We load the sprites in VRAM address $2000
DoGetHSpriteObj will draw a hardware sprite to the screen from
object z_IX... like before, the XY pos will be read from object
z_IX, the sprite frame will also be calculated
We need to calculate the pattern number for the current sprite,
and the XY pos...
We need to set the hardware sprite in the SATB (Sprite
attribute table buffer) - but we can't write it directly, so we
write to VRAM $7F00-7FFF - then execute a transfer to copy these
to the video hardware (Showing the sprite)
The Clear Screen Routine needs to turn off all the hardware
sprites, we do this by moving the sprites off the visible screen
Until we set the 'Hardware Sprite Numbers' of the objects they
will still use the old tile code...
We do this in the 'StartLevel' routine... setting the player
object to Hsprite 1... the player bullets to 2-9... and so on...
As with most
things on the PC-Engine, the hardware sprites are pretty
poweful - and easy!
Each sprite is 16x16, and we have 64 - far more than Yquest
needs!
Lesson
YQuest12 - adding Hardware Sprites on the NES
Lets add hardware sprites to the NES... unfortunately, like the
Tilemap, we can't alter hardware sprites outside of VBlank - so
once again we'll use a buffer, and transfer it during VBlank
See
YQuest folder folder
Multiplatform Sprite code
We had an unused byte in the object definitions before...
We're going to use it now!
This will be the 'Hardware sprite number'...
A Zero will be the same as before - a tile will be used
1+ will be a numbered hardware sprite - relating to one of the
hardware sprites the system is capable of.
This flexibility to use tiles or hardware sprites helps with
systems with small numbers of hardware sprites
We'll need to set banks of objects to consecutive sprite
numbers (EG bullets)...
We use SetHardwareSprites to do this.
We need to alter the DrawAndMove function and check the
'hardware sprite number'
Values 1-128 are hardware sprites... other values (0 / 255
etc) are soft sprites
NES Sprites
Sprites on the NES are defined by 256 bytes of OAM
memory- 4 bytes per sprite
The byte is selected by setting the OAM-address with memory location
$2003 - effectively with 4x the sprite number... then by
writing the 4 bytes to $2004 (the OAM address autoincs)
Byte
Purpose
Bits
Meaning
1
Ypos
YYYYYYYY
2
Tilenum
TTTTTTTT
3
Attribs
VHB---PP
Vflip Hflip Background priority Palette
4
Xpos
XXXXXXXX
NES Sprite code
We need to copy our sprite buffer to the 'OAM' duirng our
NMI Vblank handler... we do this by writing the top byte of
the address to port $4014... this will transfer 256 bytes of
data to the video hardware
In this case the sprites use the same patterns, but separate
palettes - the first 4 palettes are for the tilemap... the
second 4 palettes are for the sprites.
We need to turn on the hardware sprites with port $2001, and
zero the sprite buffer
DoGetHSpriteObj will draw a hardware sprite to the screen
from object z_IX... like before, the XY pos will be read from
object z_IX, the sprite frame will also be calculated
We need to calculate the pattern number for the current
sprite, and the XY pos...
We've defined which of the 4 palettes to use for each of our
defined sprites, we store this in z_l
For sprites, The top left corner of the screen is 16 pixels
down, so we add 16 to z_iy
We need to set the hardware sprite in the SATB (Sprite
attribute table buffer) - but we can't write it directly, so
we write to VRAM $7F00-7FFF - then execute a transfer to copy
these to the video hardware (Showing the sprite)
The Clear Screen Routine needs to turn off all the hardware
sprites, we do this by zeroing the sprite buffer
Until we set the 'Hardware Sprite Numbers' of the objects
they will still use the old tile code...
We do this in the 'StartLevel' routine... setting the player
object to Hsprite 1... the player bullets to 2-9... and so
on...
Getting NES sprites to work is a bit of
a pain, but once we have the buffer code set up, it's all
pretty easy!
The Nes has 64 sprites, and each can use one of the 4 color
palettes - giving our game some nice color too!...
especially considering palettes are set for the tilemap only
for every 4 tiles (2x2 blocks)
Lesson
YQuest13 - SNES Hardware sprites.
Like the tilemap, we can't write to the sprites out of Vblank,
so once again we'll use a buffer for the sprites, and use the
DMA to transfer the sprite data to vram
See
YQuest folder folder
Multiplatform Sprite code
We had an unused byte in the object definitions before...
We're going to use it now!
This will be the 'Hardware sprite number'...
A Zero will be the same as before - a tile will be used
1+ will be a numbered hardware sprite - relating to one of the
hardware sprites the system is capable of.
This flexibility to use tiles or hardware sprites helps with
systems with small numbers of hardware sprites
We'll need to set banks of objects to consecutive sprite
numbers (EG bullets)...
We use SetHardwareSprites to do this.
We need to alter the DrawAndMove function and check the
'hardware sprite number'
Values 1-128 are hardware sprites... other values (0 / 255
etc) are soft sprites
We're
going to use the same kind of DMA as we did to transfer the
tiles, however unlike tile data, we don't write pairs of
bytes to two different addresses, we write all the data to a
single address.
SNES Sprite code
We need to copy our sprite buffer to the 'OAM' duirng our
NMI Vblank handler... we use the DMA to do this
The sprite buffer uses a total of $220 bytes - we need to
write these bytes to $2104
To select an 'address' in the OAM to write to we use ports
$2102/3 - we write a zero to these to select the start of the
OAM.
We want the bytes of data to all write to $2104... first we
select 'single address' with $4300.... we select port $2104
with port $4301
We select the source address (our SnesSpriteBuffer) with port
$4302/3/4
We select the number of bytes $220 with ports $4305/6/7
we start the transfer with bit 0 of port $420B
The sprites use palette entries from 128 onwards... we
define them in the same way as our regular tile palette
We define the Sprite patterns at address $4000 - and
transfer the sprite patterns to the VRAM... they don't use the
same pattern definitions as the tilemap
Finally, we clear the buffer, and turn on the
Tilemap+Sprites with port $212C
DoGetHSpriteObj will draw a hardware sprite to the screen
from object z_IX... like before, the XY pos will be read from
object z_IX, the sprite frame will also be calculated
We select the pattern with z_h - the first sprite is blank (So
we can hide the sprites) so we add 1 to skip the blank sprite
We need to calculate the pattern number for the current
sprite, and the XY pos...
We define the palette, and how the sprites overlay the tiles
with z_l
To line up the sprites with our Tilemap, we add 16 to z_iy
We need to set the two bytes in the buffer...
In the first part, there are 4 bytes per sprite - Xpos, Ypos,
Tile number and attribs
in the second part, there are 2 bits per sprite in a byte -
so 4 sprites per byte... these are the 8th bit of the Xpos,
and the scaling option
We have to bit shift these into the correct position and OR
them into the current value at that memory position!... what a
pain!
The Clear Screen Routine needs to turn off all the hardware
sprites, we do this by zeroing the sprite buffer
Until we set the 'Hardware Sprite Numbers' of the objects
they will still use the old tile code...
We do this in the 'StartLevel' routine... setting the player
object to Hsprite 1... the player bullets to 2-9... and so
on...
Lesson
YQuest14 - C64 Hardware Sprites
We were using bitmap graphics before for our game - making the
objects move in 4x8 blocks
The C64 has 8 hardware sprites - we'll use one to draw the
player, which will allow for smooth movement.
See
YQuest folder folder
The C64 can only do 8
hardware sprites - and we would need 40 to do all the enemy
objects!
Many games get around this limitation by altering and moving the
sprites as the screen redraws, so one hardware sprite draws
multiple objects in multiple different horizontal lines of the
screen, but this is complex, and outside of the scope of this
tutorial.
Multiplatform Sprite code
We had an unused byte in the object definitions before... We're
going to use it now!
This will be the 'Hardware sprite number'...
A Zero will be the same as before - a tile will be used
1+ will be a numbered hardware sprite - relating to one of the
hardware sprites the system is capable of.
This flexibility to use tiles or hardware sprites helps with
systems with small numbers of hardware sprites
We'll need to set banks of objects to consecutive sprite numbers
(EG bullets)...
We use SetHardwareSprites to do this.
We need to alter the DrawAndMove function and check the
'hardware sprite number'
Values 1-128 are hardware sprites... other values (0 / 255 etc)
are soft sprites
C64 Sprites
The Sprite pointers for the bitmap data are a single byte...
multiplying the sprite pointer by 64 will give the address of the sprite
*within the 16k bank of Vram* (so must be in the range $0000-$3FFF)...
$1000-$2000 and $9000-$A000 are seen by the VIC as character ROM, so
sprites cannot be in this area!
We can move our screen base to something more convenient... so for
example with a screen base of $4000 (Screen ram at $6000)- our sprites
can be at $5000
Sprites are 21 vertical lines and 63 bytes each...
In 1bpp (2 color) mode this makes sprites 24x63...
In 2bpp (4 color) mode they are 12x63...
In both modes, Color 0 is Transparent
In 2bpp mode color 1,2 are read from $D025/6... and color 3 is the
sprite color.
Address
Purpose
Bits
Meaning
$07F8-$07FF
Sprite
pointers (default - will change if screen moved)
SSSSSSSS
s*64=memory
address
$D000
Sprite #0
X-coordinate
XXXXXXXX
(only bits
#0-#7).
$D001
Sprite #0
Y-coordinate
YYYYYYYY
$D002
Sprite #1
X-coordinate
XXXXXXXX
(only bits
#0-#7).
$D003
Sprite #1
Y-coordinate
YYYYYYYY
$D004
Sprite #2
X-coordinate
XXXXXXXX
(only bits
#0-#7).
$D005
Sprite #2
Y-coordinate
YYYYYYYY
$D006
Sprite #3
X-coordinate
XXXXXXXX
(only bits
#0-#7).
$D007
Sprite #3
Y-coordinate
YYYYYYYY
$D008
Sprite #4
X-coordinate
XXXXXXXX
(only bits
#0-#7).
$D009
Sprite #4
Y-coordinate
YYYYYYYY
$D00A
Sprite #5
X-coordinate
XXXXXXXX
(only bits
#0-#7).
$D00B
Sprite #5
Y-coordinate
YYYYYYYY
$D00C
Sprite #6
X-coordinate
XXXXXXXX
(only bits
#0-#7).
$D00D
Sprite #6
Y-coordinate
YYYYYYYY
$D00E
Sprite #7
X-coordinate
XXXXXXXX
(only bits
#0-#7).
$D00F
Sprite #7
Y-coordinate
YYYYYYYY
$D010
Sprite
#0-#7 X-coordinates
76543210
(bit #8)
$D015
Sprite
enable register
76543210
1=on
$D017
Sprite
double height register
76543210
$D01B
Sprite
priority register
76543210
$D01C
Sprite
multicolor mode register
76543210
0=2 color
1=4color
$D01D
Sprite
double width register
76543210
$D01E
Sprite-sprite
collision register
76543210
$D01F
Sprite-background
collision
reg
76543210
$D025
Sprite
extra color #1
----CCCC
$D026
Sprite
extra color #2
----CCCC
$D027
Sprite #0
color
----CCCC
$D028
Sprite #1
color
----CCCC
$D029
Sprite #2
color
----CCCC
$D02A
Sprite #3
color
----CCCC
$D02B
Sprite #4
color
----CCCC
$D02C
Sprite #5
color
----CCCC
$D02D
Sprite #6
color
----CCCC
$D02E
Sprite #7
color
----CCCC
C64 Sprite code
We need to copy our bitmap sprites to the sprite ram at $5000+
DoGetHSpriteObj will draw a hardware sprite to the screen from
object z_IX... like before, the XY pos will be read from object
z_IX, the sprite frame will also be calculated
We select the pattern with z_h - we only have two sprites in our
sprite bitmaps...
4x sprites for the player during the game
4x sprites for the player exploding when dead
We need to calculate the pattern number for the current sprite,
and the XY pos...
We define the sprite color, and other sprite settings in z_l
To line up the sprites with our Tilemap, we add 16 to z_iy
settings for the 8 sprites use 1 bit each for $D015 (bit 0-7 for
the 8 sprites)
We need to set to 1 or leave to 0 a bit depending on some
parameters
we use C64SpriteConvertToMask to calculate the mask to clear a
bit, and set it if required (depending on flag/parameter passed)
We need to set the relevant bits and bytes of the sprite within
the hardware registers.