Photon...
battle of the Chibi Photonic hunters!
Introduction to the Photon
Series...
Photon is a 'Tron' Light cycle type game... I
used to make these games in Basic on the CPC... I thought it would be fun
to try to do the same in assembly...
Originally the game was written for Z80
systems, but I have ported it to the 6502 and 68000
Lesson
Photon1
- Introduction and Data Structures
Photon is a tron style game written for many different systems
Lets start by having a quick look at the game, and the structure
of the game RAM, and the settings which define objects and
movements. |
 |
See
Photon folder
|
|
 |
Photon
Photon does not use bitmap graphic,
all the graphics are drawn at the pixel level... to port to a
new system you just need a PSET function to set a pixel and a
POINT function to read a pixel back...
All other graphics are drawn using
a LINE function, which uses the PSET function - PHOTON is
essentially a simple game, a tutorial on LINE and VECTOR image
drawing (With Scaling) AND a tutorial in Pixel plotting on each
system.
photon uses VECTREX packet format
for it's title graphics (Yes it uses the same format as the old
home console!), and a slightly tweaked Cpacket format (2 bytes
per line rather than 3) I designed to save space for it's font
and other bits... Akusprite Editor now has crude export
functions for these formats!
In this series, we'll look at the basic codebase, and the platform
specific modifications for each platform.
Photon is available on the CPC, Spectrum, MSX1, MSX2, Sam Coupe,
Elan Enterprise, Master System, Game Gear... and now it's being
ported to the 6502 and 68000 systems! |

|
Photon is a one player (Vs CPU) Light Cycle game - you have to
stay alive longer than your opponent to win a level.
Death occurs when you hit a wall or light trail.
Each win progresses the level (Shown bottom right), and increases
CPU difficulty (Shown top right), and adds objects to the screen.
The player has a 'boost' power (count in bottom left) to
accelerate... you have four lives per game (shown top left)
Photon's CPU AI is hardly amazing - the game is really a 'tech
demo' for pixel plotting and multiplatform vector drawing. |
 |
The drawing engine has it's own vector font, and is capable of
scaling all vector objects (only powers of 2... eg 1/4x 1/2x 1x 2x
4x)
This allows the same title screen data to be drawn smaller on the
gamegear... and fonts to be shown at different sizes |
 |
Photon supports many different 8 bit systems - porting it to a
new system really just consists of substituting the PSET and POINT
routines |
 |
Ram Definitions
The Player and CPU have various parameters for their position
Direction is the current direction of
movement - the game is controlled with Left and Right to turn -
this is the current direction.
X and Y are the current
position in pixels (2 byte word)
Xacc and Yacc are the
current 'acceleration' - these are added/subtracted from the
position each tick.
CpuTurn is used by the AI to decide which
direction the CPU will turn next time.
|
 |
The Game Engine needs some settings for the Level logic.
KeyTimeout is for 'ignoring' player keys
after a press
BestLevel is the highest level the player has
got to - effectively a highscore.
Level is the current level (duh!)
CpuAI is the CPU intelligence... lower means
tighter corner turning making the CPU harder to trap.
Lives is player lives
Tick is used to handle player boost - normal
speed is an update every other tick... boost is an update every
tick
Boost is used to mark player boost as enabled
(Cpu doesn't use boost)
BoostPower is the remaining 'Boost' - 99
units per level
ShownBoostPower is the visible boost - used
when redrawing the boost in the bottom left corner
RandomSeed is the random seed for random
number generation |
 |
The Line and vector routines need some bytes of data
XposDir and YposDir define
if the movement are Up/Left or Down/Right
Xpos24 and Ypos24 are
the current pixel position (24 bit)
Scale is the scaling factor... 1 2 4 = 1x 2x
4x ... -1 -2 -4 = 1/2 1/4 1/8
LineColor is the line color (obviously!) |
 |
 |
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
Directions defines the 4 directions in the
form of 2 words... these can be added to the position to affect a
move |
 |
Next we have some Random Number Lookup tables
|
 |
We need to reset the player and Cpu location and settings each
level... we have the defaults here.
|
 |
Next we have the text messages used in the
game.
|
 |
Finally we have the Obstruction objects...
These are put in random places in the level to get in the way!
These are in 'Cpacket format' (Compressed Vectrex Packet)... a
format I made based on the 'Packet' format used by the Vectrex
console.
It uses a 7 bit X movement, a 7 bit Y movement, and a 2 bit
Command
There are 2 possible command -
1.Move drawing cursor to position (Byte 2 bit 7 = 0)
2. Draw from drawing cursor to position (Byte 2 bit 7 = 1)
A Byte 2 bit 7 = 1 is the end of the list. |
 |
 |
The
Multiplatform code handles most of the game logic and vector
drawing routines - As they are converted from the Z80 version
we're not going to cover them here - all the function and
variable names are the same as is the structure - so please
watch Z80 lessons Photon 2-5 if you want to know more about how
the multiplaform code works.
|
Much of the code of
Photon is the same as the code in Yquest and the Simple series...
we're not going to cover things like setting up the screen or
reading the Joypad...
We've covered it so many times before it's getting boring!
|
 |
Data Definitions & Starting the game
Each system needs some platform specific settings.
The first is an address of RAM for system vars - the game needs less
than 256 bytes - this is the entirety of the RAM needed - the rest
of the game can run from ROM
We define some screen size vars - these set the scale for objects
and font., and the boundaries of the screen for drawing the level.
We also define some 'colors' ... the game uses up to 5 colors
(Background 0 and 4 more)
BUT some systems have fewer... on the BBC we have only 0-3... so we
use color 1 for Colors 1+4 |
 |
When the game starts we clear the game ram... We then show the
Main menu. |
 |
Main Game Loop
At the start of the loop, we update the 'Tick'
The Tick is 1 or 0 - this is used by the multiplatform code for
boost (during which player moves at 2x speed)
If Boost is ON we lower the delay - we need to speed the game up
because redrawing the 'Boost counter' is too slow. |
 |
During the pause loop we check the keypreses... if a key is not
held down we release the keytimeout
The timeout means if we hold left we don't keep rotating around...
we have to press left multiple times
We store any keypresses in z_D - and wait until z_BC reaches Zero |
 |
If the Keytimeout is not cleared we will ignore Left / Right
keypresses.
First we turn off Boost (it will be turned back on if FIRE is held)
When we process Left or Right we INC/DEC the 'Player Direction' then
run the SetPlayerDirection routine which handles the rest of setting
the movement for the player.
If Fire is pressed then we check if any 'Boost power' is left..
If there is, then we turn boost power on.
Once the keypresses are handled, we update the player with 'Handle
Player'
Then we update the CPU with 'Handle CPU'
That's it... We jump back to the start of the loop for the next
tick. |
 |
PSET - Plot Pixel / POINT - Read pixel color
The PSET command will set a pixel of the screen...
The 16 bit X co-ordinate is defined by registers A z_B (A= High byte
z_B= Low byte)... this is so we can support a 320 pixel wide screen
The 8 Bit Y co ordinate is defined by z_C... only 8 bits, as our
maximum screen is 200 pixels tall
z_D is the new color for the pixel (0-3)
GetPixelMask will calculate the screen byte we want to change (in
z_HL)... the mask to keep the screen pixels we don't want to change
will be in z_D... the mask for the pixel we want to change is z_E
We next need to work out the bytes for the pixel color... we use
GetColorMaskNum ANDing the result with E - this gives us the new
value for the one pixel
Finally we get the current screen byte... AND it with z_D - removing
any pixel data for the pixel we want to change... then OR in the new
pixel color from z_E |
 |
Groups of 4 pixels are held in a single byte - the top and bottom
nibbles are effectively 'bitplanes' where one bit of each make up
the color number.
depending on which of the 4 pixels in the byte we want to change,
we'll return E as a mask for the pixel we want to change... and D as
a mask we can AND with the background to clear the pixel we want to
change.
We first get the screen address with GetScreenPos to calculate the
VRAM address of the byte containing the pixel....
Next we read the pixel mask for z_E from a lookup table... and flip
the bits with EOR to get the background mask.
The GetScreenPos routine is the same as Yquest. |
 |
GetColorMask number will get a byte where all 4 pixels are the
selected color in A
We AND this with E from Get Pixel Mask to get the bits for the pixel
we want, in the color we want. |
 |
POINT works in reverse to PSET... returning the color in A for
pixel AB,C
Once again, we get the pixel mask, and read in the screen byte,
ANDing it with E to get one pixel.
We're going to convert this to a byte where all the pixels are the
same color (so we can search for it in the Color Lookup) with Byte
to Color Mask |
 |
Byte to color mask will take a single colored pixel, and fill all
the pixels in that byte with the same color.
This is done by bit shifts and ORs to copy 1 pixel to all 4 |
 |
 |
The POINT routine here is excessive - as
PHOTON doesn't care what color a pixel is, just if it's black or
not.
ALSO, it would be quicker to calculate the color number via
bitshifts, rather than filling a byte and looking it up... but
POINT is hardly used in this game, so speed isn't a problem, and
the example here is easily ported to other systems, or converted
for other screen modes
|
Much of the code of
Photon is the same as the code in Yquest and the Simple series...
we're not going to cover things like setting up the screen or
reading the Joypad...
We've covered it so many times before it's getting boring!
|
 |
Data Definitions & Starting the game
Each system needs some platform specific settings.
The first is an address of RAM for system vars - the game needs less
than 256 bytes - this is the entirety of the RAM needed - the rest
of the game can run from ROM
We define some screen size vars - these set the scale for objects
and font., and the boundaries of the screen for drawing the level.
We also define some 'colors' ... the game uses up to 5 colors
(Background 0 and 4 more)
BUT some systems have fewer... on the Apple we're just using Black
and White |
 |
When the game starts we clear the game ram... We then show the
Main menu. |
 |
Main Game Loop
At the start of the loop, we update the 'Tick'
The Tick is 1 or 0 - this is used by the multiplatform code for
boost (during which player moves at 2x speed)
If Boost is ON we lower the delay - we need to speed the game up
because redrawing the 'Boost counter' is too slow. |
 |
During the pause loop we check the keypreses... if a key is not
held down we release the keytimeout
The timeout means if we hold left we don't keep rotating around...
we have to press left multiple times
We store any keypresses in z_D - and wait until z_BC reaches Zero |
 |
If the Keytimeout is not cleared we will ignore Left / Right
keypresses.
First we turn off Boost (it will be turned back on if FIRE is held)
When we process Left or Right we INC/DEC the 'Player Direction' then
run the SetPlayerDirection routine which handles the rest of setting
the movement for the player.
If Fire is pressed then we check if any 'Boost power' is left..
If there is, then we turn boost power on.
Once the keypresses are handled, we update the player with 'Handle
Player'
Then we update the CPU with 'Handle CPU'
That's it... We jump back to the start of the loop for the next
tick. |
 |
PSET - Plot Pixel / POINT - Read pixel color
The Apple II has a 280 pixel wide screen... this odd width comes
from the fact there are 7 pixels per byte (The top bit is a color
attribute bit)
This poses a problem, we'll need to divide the Xpos by 7 to get the
horizontal byte, but we can't do this by bitshifting.
We could use a generic 16 bit divide command (Div16) - it's quite
fast, but it's much slower than on other systems with 4 or 8 pixels
per byte.
The alternative is to create a 280x2 lookup table with pairs of
bytes - each pair will be a 'result' of division of a number 0-279
by 7
The first byte is the 'whole number part' (0-39) - this is the
horizontal byte.
The second byte is the 'remainder part' (0-7) - this is the pixel
within the byte. |
 |
The PSET command will set a pixel of the screen...
The 16 bit X co-ordinate is defined by registers A z_B (A= High byte
z_B= Low byte)... this is so we can support a 280 pixel wide screen
The 8 Bit Y co ordinate is defined by z_C... only 8 bits, as our
screen is 192 pixels tall
z_D is the new color for the pixel (0-3)
GetPixelpos wiill calculate the screen byte we want to change (in
z_DE)... the mask to keep the screen pixels we don't want to change
will be in z_L... the mask for the pixel we want to change is z_H
We get the current screen byte from (z_DE)... AND it with z_H-
removing any pixel data for the pixel we want to change... then OR
in the new pixel color from z_L - if we want to set the pixel
(z_D=1) |
 |
GetPixelPos will calculate the memory address of the byte we want
to change in DE, and a mask for the background in H and a a
mask for the pixel in L
We can calculate the horizontal byte in one of two ways... using a
function 'Div16' - which is slow
or with a lookuptable 'DivTable' - which takes a lot of memory... we
have to make a choice!
We use 'PixelBitLookup' to get the bitmask for the pixel within the
byte, and flip it with EOR to get a mask for the remaining pixels. |
 |
We now need to calculate the memory address via the same X byte Y
line calculation we used in Yquest
We also have the "pixelbitlookup" which has the masks for each pixel
within a byte, and the 280x2 byte lookup table for our divide
routine |
 |
POINT works in reverse to PSET... returning the color in A for
pixel AB,C
Once again, we get the pixel mask, and read in the screen byte,
ANDing it with H to get one pixel.
If it's non-zero... 1 is returned to denote the pixel is on. |
 |
This example
just works in black and white... you could use halve the
horizontal resolution, and the color attributes to make colored
graphics if you prefer.
|
 |
Much of the code of
Photon is the same as the code in Yquest and the Simple series...
we're not going to cover things like setting up the screen or
reading the Joypad...
We've covered it so many times before it's getting boring!
|
 |
Data Definitions & Starting the game
Each system needs some platform specific settings.
The first is an address of RAM for system vars - the game needs less
than 256 bytes - this is the entirety of the RAM needed - the rest
of the game can run from ROM
We define some screen size vars - these set the scale for objects
and font., and the boundaries of the screen for drawing the level.
We also define some 'colors' ... the game uses up to 5 colors
(Background 0 and 4 more)
BUT some systems have fewer... on the Atari we have only 0-3... so
we use color 1 for Colors 1+4 |
 |
When the game starts we clear the game ram... We then show the
Main menu. |
 |
Main Game Loop
At the start of the loop, we update the 'Tick'
The Tick is 1 or 0 - this is used by the multiplatform code for
boost (during which player moves at 2x speed)
If Boost is ON we lower the delay - we need to speed the game up
because redrawing the 'Boost counter' is too slow. |
 |
During the pause loop we check the keypreses... if a key is not
held down we release the keytimeout
The timeout means if we hold left we don't keep rotating around...
we have to press left multiple times
We store any keypresses in z_D - and wait until z_BC reaches Zero |
 |
If the Keytimeout is not cleared we will ignore Left / Right
keypresses.
First we turn off Boost (it will be turned back on if FIRE is held)
When we process Left or Right we INC/DEC the 'Player Direction' then
run the SetPlayerDirection routine which handles the rest of setting
the movement for the player.
If Fire is pressed then we check if any 'Boost power' is left..
If there is, then we turn boost power on.
Once the keypresses are handled, we update the player with 'Handle
Player'
Then we update the CPU with 'Handle CPU'
That's it... We jump back to the start of the loop for the next
tick. |
 |
PSET - Plot Pixel / POINT - Read pixel color
The PSET command will set a pixel of the screen...
The 16 bit X co-ordinate is defined by registers A z_B (A= High byte
z_B= Low byte)... this is so we can support a 320 pixel wide screen
(On systems that have one)
The 8 Bit Y co ordinate is defined by z_C... only 8 bits, as our
maximum screen is 200 pixels tall
z_D is the new color for the pixel (0-3)
GetPixelMask will calculate the screen byte we want to change (in
z_HL)... the mask to keep the screen pixels we don't want to change
will be in z_D... the mask for the pixel we want to change is z_E
We next need to work out the bytes for the pixel color... we use
GetColorMaskNum ANDing the result with E - this gives us the new
value for the one pixel
Finally we get the current screen byte... AND it with z_D - removing
any pixel data for the pixel we want to change... then OR in the new
pixel color from z_E |
 |
Groups of 4 pixels are held in a single byte - 2 neighboring bits
for each pixel
depending on which of the 4 pixels in the byte we want to change,
we'll return E as a mask for the pixel we want to change... and D as
a mask we can AND with the background to clear the pixel we want to
change.
We first get the screen address with GetScreenPos to calculate the
VRAM address of the byte containing the pixel....
Next we read the pixel mask for z_E from a lookup table... and flip
the bits with EOR to get the background mask.
The GetScreenPos routine is the same as Yquest. |
 |
GetColorMask number will get a byte where all 4 pixels are the
selected color in A
We AND this with E from Get Pixel Mask to get the bits for the pixel
we want, in the color we want. |
 |
POINT works in reverse to PSET... returning the color in A for
pixel AB,C
Once again, we get the pixel mask, and read in the screen byte,
ANDing it with E to get one pixel.
We're going to convert this to a byte where all the pixels are the
same color (so we can search for it in the Color Lookup) with Byte
to Color Mask |
 |
Byte to color mask will take a single colored pixel, and fill all
the pixels in that byte with the same color.
This is done by bit shifts and ORs to copy 1 pixel to all 4 |
 |
 |
The POINT routine here is excessive - as
PHOTON doesn't care what color a pixel is, just if it's black or
not.
ALSO, it would be quicker to calculate the color number via
bitshifts, rather than filling a byte and looking it up... but
POINT is hardly used in this game, so speed isn't a problem, and
the example here is easily ported to other systems, or converted
for other screen modes
|
Much of the code of
Photon is the same as the code in Yquest and the Simple series...
we're not going to cover things like setting up the screen or
reading the Joypad...
We've covered it so many times before it's getting boring!
|
 |
Data Definitions & Starting the game
Each system needs some platform specific settings.
The first is an address of RAM for system vars - the game needs less
than 256 bytes - this is the entirety of the RAM needed - the rest
of the game can run from ROM
We define some screen size vars - these set the scale for objects
and font., and the boundaries of the screen for drawing the level.
We also define some 'colors' ... the game uses up to 5 colors
(Background 0 and 4 more)
BUT some systems have fewer... on the C64 we have only 0-3... so we
use color 1 for Colors 1+4 |
 |
When the game starts we clear the game ram... We then show the
Main menu. |
 |
Main Game Loop
At the start of the loop, we update the 'Tick'
The Tick is 1 or 0 - this is used by the multiplatform code for
boost (during which player moves at 2x speed)
If Boost is ON we lower the delay - we need to speed the game up
because redrawing the 'Boost counter' is too slow. |
 |
During the pause loop we check the keypreses... if a key is not
held down we release the keytimeout
The timeout means if we hold left we don't keep rotating around...
we have to press left multiple times
We store any keypresses in z_D - and wait until z_BC reaches Zero |
 |
If the Keytimeout is not cleared we will ignore Left / Right
keypresses.
First we turn off Boost (it will be turned back on if FIRE is held)
When we process Left or Right we INC/DEC the 'Player Direction' then
run the SetPlayerDirection routine which handles the rest of setting
the movement for the player.
If Fire is pressed then we check if any 'Boost power' is left..
If there is, then we turn boost power on.
Once the keypresses are handled, we update the player with 'Handle
Player'
Then we update the CPU with 'Handle CPU'
That's it... We jump back to the start of the loop for the next
tick. |
 |
PSET - Plot Pixel / POINT - Read pixel color
The PSET command will set a pixel of the screen...
The 16 bit X co-ordinate is defined by registers A z_B (A= High byte
z_B= Low byte)... this is so we can support a 320 pixel wide screen
The 8 Bit Y co ordinate is defined by z_C... only 8 bits, as our
maximum screen is 200 pixels tall
z_D is the new color for the pixel (0-3)
GetPixelMask will calculate the screen byte we want to change (in
z_HL)... the mask to keep the screen pixels we don't want to change
will be in z_D... the mask for the pixel we want to change is z_E
We next need to work out the bytes for the pixel color... we use
GetColorMaskNum ANDing the result with E - this gives us the new
value for the one pixel
Finally we get the current screen byte... AND it with z_D - removing
any pixel data for the pixel we want to change... then OR in the new
pixel color from z_E |
 |
Groups of 4 pixels are held in a single byte... pairs of bits
define the color for each pixel
depending on which of the 4 pixels in the byte we want to change,
we'll return E as a mask for the pixel we want to change... and D as
a mask we can AND with the background to clear the pixel we want to
change.
We first get the screen address with GetScreenPos to calculate the
VRAM address of the byte containing the pixel....
Next we read the pixel mask for z_E from a lookup table... and flip
the bits with EOR to get the background mask for z_D.
The GetScreenPos routine is the same as Yquest. |
 |
GetColorMask number will get a byte where all 4 pixels are the
selected color in A
We AND this with E from Get Pixel Mask to get the bits for the pixel
we want, in the color we want. |
 |
POINT works in reverse to PSET... returning the color in A for
pixel AB,C
Once again, we get the pixel mask, and read in the screen byte,
ANDing it with E to get one pixel.
We're going to convert this to a byte where all the pixels are the
same color (so we can search for it in the Color Lookup) with Byte
to Color Mask |
 |
Byte to color mask will take a single colored pixel, and fill all
the pixels in that byte with the same color.
This is done by bit shifts and ORs to copy 1 pixel to all 4 |
 |
While the PSET command sets colors 0-3, it does not set the color
attributes for the blocks.
This is done during the CLS routine |
 |
 |
The POINT routine here is excessive - as
PHOTON doesn't care what color a pixel is, just if it's black or
not.
ALSO, it would be quicker to calculate the color number via
bitshifts, rather than filling a byte and looking it up... but
POINT is hardly used in this game, so speed isn't a problem, and
the example here is easily ported to other systems, or converted
for other screen modes
|
Much of the code of
Photon is the same as the code in Yquest and the Simple series...
we're not going to cover things like setting up the screen or
reading the Joypad...
We've covered it so many times before it's getting boring!
|
 |
Data Definitions & Starting the game
Each system needs some platform specific settings.
The first is an address of RAM for system vars - the game needs less
than 256 bytes - this is the entirety of the RAM needed - the rest
of the game can run from ROM
We define some screen size vars - these set the scale for objects
and font., and the boundaries of the screen for drawing the level...
this configures the game engine for a smaller screen.
We also define some 'colors' ... the game uses up to 5 colors
(Background 0 and 4 more) and the lynx can use all of these |
 |
When the game starts we clear the game ram... We then show the
Main menu. |
 |
Main Game Loop
At the start of the loop, we update the 'Tick'
The Tick is 1 or 0 - this is used by the multiplatform code for
boost (during which player moves at 2x speed)
If Boost is ON we lower the delay - we need to speed the game up
because redrawing the 'Boost counter' is too slow. |
 |
During the pause loop we check the keypreses... if a key is not
held down we release the keytimeout
The timeout means if we hold left we don't keep rotating around...
we have to press left multiple times
We store any keypresses in z_D - and wait until z_BC reaches Zero |
 |
If the Keytimeout is not cleared we will ignore Left / Right
keypresses.
First we turn off Boost (it will be turned back on if FIRE is held)
When we process Left or Right we INC/DEC the 'Player Direction' then
run the SetPlayerDirection routine which handles the rest of setting
the movement for the player.
If Fire is pressed then we check if any 'Boost power' is left..
If there is, then we turn boost power on.
Once the keypresses are handled, we update the player with 'Handle
Player'
Then we update the CPU with 'Handle CPU'
That's it... We jump back to the start of the loop for the next
tick. |
 |
PSET - Plot Pixel / POINT - Read pixel color
The PSET command will set a pixel of the screen...
The 16 bit X co-ordinate is defined by registers A z_B (A= High byte
z_B= Low byte)... this is so we can support a 320 pixel wide screen
on systems that use it
The 8 Bit Y co ordinate is defined by z_C... only 8 bits, as our
maximum screen is 200 pixels tall
z_D is the new color for the pixel (0-15)
GetPixelMask will calculate the screen byte we want to change (in
z_HL)... the mask to keep the screen pixels we don't want to change
will be in z_D... the mask for the pixel we want to change is z_E
We next need to work out the bytes for the pixel color... we use
GetColorMaskNum ANDing the result with E - this gives us the new
value for the one pixel
Finally we get the current screen byte... AND it with z_D - removing
any pixel data for the pixel we want to change... then OR in the new
pixel color from z_E |
 |
Pairs of pixels are held in a single byte - the top and bottom
nibbles are separate pixels
depending on which of the 2 pixels in the byte we want to change,
we'll return E as a mask for the pixel we want to change... and D as
a mask we can AND with the background to clear the pixel we want to
change.
We first get the screen address with GetScreenPos to calculate the
VRAM address of the byte containing the pixel....
Next we read the pixel mask for z_E from a lookup table... and flip
the bits with EOR to get the background mask.
The GetScreenPos routine is the same as Yquest. |
 |
GetColorMask number will get a byte where both pixels are the
selected color in A
We AND this with E from Get Pixel Mask to get the bits for the pixel
we want, in the color we want. |
 |
POINT works in reverse to PSET... returning the color in A for
pixel AB,C
Once again, we get the pixel mask, and read in the screen byte,
ANDing it with E to get one pixel.
We're going to convert this to a number from 0-15, we shift if the
pixel we've loaded was the left pixel, it doesn't need shifting if
it's the right one. |
 |
 |
The POINT routine here is excessive - as
PHOTON doesn't care what color a pixel is, just if it's black or
not.
ALSO, it would be quicker to calculate the color number via
bitshifts, rather than filling a byte and looking it up... but
POINT is hardly used in this game, so speed isn't a problem, and
the example here is easily ported to other systems, or converted
for other screen modes
|
Data Definitions & Starting the game
We're going to 8x16 chars, then configure the bits of those to set
pixels... because each character has a single color our pixels will
be pretty low res.
We'll be using a ROM cartridge so we can have all the memory for our
use. |
 |
Each system needs some platform specific settings.
The first is an address of RAM for system vars - the game needs less
than 256 bytes - this is the entirety of the RAM needed - the rest
of the game can run from ROM
We define some screen size vars - these set the scale for objects
and font., and the boundaries of the screen for drawing the level...
this configures the game engine for a smaller screen.
We also define some 'colors' - on the VIC these are the color
attributes we'll transfer to the character when we set a pixel |
 |
We're going to set up our screen with some different settings this
time
We set Bit 0 of reg $9003 to 1 to enable 16 pixel tall mode
We set $9005 to CC - this defines both Screen and Char base as $1000
The First Char we use is 16 at $1100 - out the way of the screen
ram.
Color attributes are at $9400 as usual, though they will cover an
8*16 area now.
Once we've set up the screen we use 'Fill Area With Tiles' to set
each character of the screen to a consecutive character, we then
alter those pixels that make up the character. |
 |
Here are the screen settings we're using |
 |
When the game starts we clear the game ram... We then show the
Main menu. |
 |
 |
The FillAreaWithTiles
function is the same one we used in the SImple Series bitmap
example.
You can find out more about it Here! |
Main Game Loop
At the start of the loop, we update the 'Tick'
The Tick is 1 or 0 - this is used by the multiplatform code for
boost (during which player moves at 2x speed)
If Boost is ON we lower the delay - we need to speed the game up
because redrawing the 'Boost counter' is too slow. |
 |
During the pause loop we check the keypreses... if a key is not
held down we release the keytimeout
The timeout means if we hold left we don't keep rotating around...
we have to press left multiple times
We store any keypresses in z_D - and wait until z_BC reaches Zero |
 |
If the Keytimeout is not cleared we will ignore Left / Right
keypresses.
First we turn off Boost (it will be turned back on if FIRE is held)
When we process Left or Right we INC/DEC the 'Player Direction' then
run the SetPlayerDirection routine which handles the rest of setting
the movement for the player.
If Fire is pressed then we check if any 'Boost power' is left..
If there is, then we turn boost power on.
Once the keypresses are handled, we update the player with 'Handle
Player'
Then we update the CPU with 'Handle CPU'
That's it... We jump back to the start of the loop for the next
tick. |
 |
PSET - Plot Pixel / POINT - Read pixel color
The PSET command will set a pixel of the screen...
The 16 bit X co-ordinate is defined by registers A z_B (A= High byte
z_B= Low byte)... this is so we can support a 320 pixel wide screen
on systems that use it
The 8 Bit Y co ordinate is defined by z_C... only 8 bits, as our
maximum screen is 200 pixels tall
z_D is the new color for the pixel (0-15)
First we check if we're trying to draw offscreen (Minus Xpos) we
return if we are.
Next we need to calculate a the pixel pos within the byte - each
byte contains 8 pixels.
We use CalcVramAddr to get the pixel address within the
characters definition (in z_HL)
We have he mask to set a pixel in A, we store this in z_b, and flip
the bits with EOR, This is now the background mask. - we store this
in Z_C
We now read in the current screen byte, and remove the pixel we're
changing by ANDing with Z_c
Next we check the color we're working with (Z_D).. if it's zero we
need to skip setting the pixel, if we need to set the pixel with OR
Z_B and store back to the screen.
We've set the pixel, but we need to set the color - there's one
color per 8x16 block, so we bitshift the B,C screenpos to a
char number, and use GetVDPScreenpos to get the character base...
We then add $8400 to get the address of the character color
attribute. |
 |
We need to calculate the byte which contains the pixel we want to
change.
There are 8 horizontal pixels per byte...
16 consecutive Y lines are contained in each single character (the
bottom 4 bits)
There are 20 characters per row, so we multiply the top 4 bits of
the Y line by 16 * 20 - we do this by multiple bishifts and
addition. |
 |
POINT works in reverse to PSET... returning the color in A for
pixel AB,C
Once again, we get the pixel mask, and read in the screen byte,
ANDing it with E to get the pixel.
We return 1 or 0 for if the pixel is colored or not.
|
 |
Data Definitions & Starting the game
We're going to fill the screen with tiles, and change the pixels
of the tile patterns to define our bitmap graphics.
This gives us a 256x224 16 color screen! |
 |
Each system needs some platform specific settings.
The first is an address of RAM for system vars - the game needs less
than 256 bytes - this is the entirety of the RAM needed - the rest
of the game can run from ROM
We define some screen size vars - these set the scale for objects
and font., and the boundaries of the screen for drawing the level...
this configures the game engine for a smaller screen.
We also define some 'colors' - Some systems only support 4,
but the PC Engine supports 16 |
 |
We set up our screen in the same way as previous examples, turning
the tilemap on, and setting the palette
We fill the entire tilemap with consecutive tiles, starting at tile
256 (VRAM address $1000+)
We use the FillAreaWithTiles code from the simple
series to do this. |
 |
When the game starts we clear the game ram... We then show the
Main menu. |
 |
We start with tile
pattern 256, as the VRAM for tiles 0-255 are using the memory
the tilemap uses.
You'll see VRAM address $1000 in the code as the pattern base
for the first pixel of the screen.
|
 |
Main Game Loop
At the start of the loop, we update the 'Tick'
The Tick is 1 or 0 - this is used by the multiplatform code for
boost (during which player moves at 2x speed)
If Boost is ON we lower the delay - we need to speed the game up
because redrawing the 'Boost counter' is too slow. |
 |
During the pause loop we check the keypreses... if a key is not
held down we release the keytimeout
The timeout means if we hold left we don't keep rotating around...
we have to press left multiple times
We store any keypresses in z_D - and wait until z_BC reaches Zero |
 |
If the Keytimeout is not cleared we will ignore Left / Right
keypresses.
First we turn off Boost (it will be turned back on if FIRE is held)
When we process Left or Right we INC/DEC the 'Player Direction' then
run the SetPlayerDirection routine which handles the rest of setting
the movement for the player.
If Fire is pressed then we check if any 'Boost power' is left..
If there is, then we turn boost power on.
Once the keypresses are handled, we update the player with 'Handle
Player'
Then we update the CPU with 'Handle CPU'
That's it... We jump back to the start of the loop for the next
tick. |
 |
PSET - Plot Pixel / POINT - Read pixel color
The PSET command will set a pixel of the screen...
The 16 bit X co-ordinate is defined by registers A z_B (A= High byte
z_B= Low byte)... this is so we can support a 320 pixel wide screen
on systems that use it
The 8 Bit Y co ordinate is defined by z_C... only 8 bits, as our
maximum screen is 200 pixels tall
z_D is the new color for the pixel (0-15)
First we need to calculate a the pixel pos within the byte of the
bitplane plane - each byte contains 8 pixels, we use a lookup table
to get the pixel mask.
Next we use CalcVramAddr to get the pixel address within the vram
pattern definitions (in z_HL)
We get Bitplanes 0 and 1 into X and Y
We have he mask to set a pixel in A, we store this in z_b, and flip
the bits with EOR, This is now the background mask. - we store this
in Z_C |
 |
We now process the current screen bitplane, and remove the pixel
we're changing by ANDing with Z_c
Next we check the bit of color we're working with (Z_D).. if it's
zero we need to skip setting the pixel, if we need to set the pixel
with OR Z_B and store back to Vram
Once we've done bitplanes 0 and 1, we add 8 to the VRAM address (8
in words - 16 bytes) and read in bit planes 2 and 3
We repeat again, checking the remaining 2 bits of z_D , and set the
bitplanes 2 and 3. |
 |
We need to calculate the VRAM address... Each address in VRAM
holds 2 bytes.
A pattern is 8 lines, and four bitplanes... As each VRAM address
holds 2 bytes this means each tile is 16 addresses.
Note the Pattern definition is split in two parts, The first 8
addresses are the bytes for Bitplanes 0,1
The next 8 addresses are the bytes for Bitplanes 2,3
We take the top 5 bits of the Xpos, and multiply this by 16 (the
bottom 3 bits are within each tile)
We add the bottom 3 bits of the Ypos - this gets the line within a
pattern
Next we take the top 5 bits and multiply by 512 (8*2*32 - 8 lines
per tile, two words per line, 32 tiles per line) |
 |
POINT works in reverse to PSET... returning the color in A for
pixel AB,C
Once again, we get the pixel mask, get the bytes for VRAM, and take
each bitplane for the pixel.
We return a valid pixel color for z_d |
 |
We also need a clear screen routine, we just repeatedly write Zero
bytes to the pattern ram.
|
 |
 |
Todays example
uses WRAM Read/Write ports 2180h-2183h to access more memory.
This wouldn't be necessary if the author of these tutorials wasn't
too lazy to use 65816 mode! He claims he "Doesn't have time" to
rewrite the tutorials for the 65816.
If you agree that's not good enough, Please feel free to donate to
the patreon account,so we can collect money to pelt him with rocks
until he learns the error of his ways!
|
Data Definitions & Starting the game
We're going to fill the screen with tiles, giving a 256x224
screen.
The SNES can support 16 colors, but we're going to have to transfer
the buffer during the VBlank NMI - and it's not long enough.
If we use 4 colors (2 bitplanes) it will take 4 NMI's to redraw the
whole screen - if we used the full screen it would take 8. so we'll
make do with 4 colors.
We need $4000 of ram for our screen buffer, there isn't really space
in the 6502 memory map for us to do this, but there is a workaround!
There are 4 ports we can use to access the full 24 bit range, and
gain access to the extra 128k ram (WRAM). This "WRAM" is 128k at
address $7E0000-$7FFFFF
We use 3 ports to select the address:
$2181 - WMADDL - WRAM Address (lower 8bit) (W)
$2182 - WMADDM - WRAM Address (middle 8bit) (W)
$2183 - WMADDH - WRAM Address (upper 1bit) (W)
Then read or write with port:
$2180 - WMDATA - WRAM Data Read/Write (R/W)
This port Autoincrements after each write. |
 |
The first is an address of RAM for system vars - the game needs
less than 256 bytes - this is the entirety of the RAM needed - the
rest of the game can run from ROM
We define some screen size vars - these set the scale for objects
and font., and the boundaries of the screen for drawing the level...
this configures the game engine for a smaller screen.
Due to time limitations We can only write 1/4 of our buffer during
each VBlank NMI, so we need to keep track of what section is next
'BufferBank' does this - working in VRAM Words (*2 for ram address)
|
 |
We set up our screen in the same way as previous examples, but
this time using a 4 color Tilemap.
turning the tilemap on, and setting the palette
We fill the entire tilemap with consecutive tiles, starting at tile
0 (VRAM address $1000+)
We then turn on the screen and enable the NMI |
 |
When the game starts we clear the game ram... We then show the
Main menu. |
 |
Main Game Loop
At the start of the loop, we update the 'Tick'
The Tick is 1 or 0 - this is used by the multiplatform code for
boost (during which player moves at 2x speed)
If Boost is ON we lower the delay - we need to speed the game up
because redrawing the 'Boost counter' is too slow. |
 |
During the pause loop we check the keypreses... if a key is not
held down we release the keytimeout
The timeout means if we hold left we don't keep rotating around...
we have to press left multiple times
We store any keypresses in z_D - and wait until z_BC reaches Zero |
 |
If the Keytimeout is not cleared we will ignore Left / Right
keypresses.
First we turn off Boost (it will be turned back on if FIRE is held)
When we process Left or Right we INC/DEC the 'Player Direction' then
run the SetPlayerDirection routine which handles the rest of setting
the movement for the player.
If Fire is pressed then we check if any 'Boost power' is left..
If there is, then we turn boost power on.
Once the keypresses are handled, we update the player with 'Handle
Player'
Then we update the CPU with 'Handle CPU'
That's it... We jump back to the start of the loop for the next
tick. |
 |
The VBlank interrupt handler
During the VBLANK we're going to use a DMA memory transfer to
transfer 1/4 of the buffer ($800 in VRAM addresses $1000 in RAM)
We set up the DMA for the transfer, Copying part of the buffer from
$7E2000+ to VRAM address $1000+
After the transfer we update BufferBank so we're pointing to the
next section to transfer during the next Vblank. |
 |
PSET - Plot Pixel / POINT - Read pixel color
The PSET command will set a pixel of the screen...
The 16 bit X co-ordinate is defined by registers A z_B (A= High byte
z_B= Low byte)... this is so we can support a 320 pixel wide screen
on systems that use it
The 8 Bit Y co ordinate is defined by z_C... only 8 bits, as our
maximum screen is 200 pixels tall
z_D is the new color for the pixel (0-15)
First we need to calculate a the pixel pos within the byte of the
bitplane plane - each byte contains 8 pixels, we use a lookup table
to get the pixel mask.
Next we use CalcVramAddr to get the pixel address within the cache
(in z_HL)
We get Bitplanes 0 and 1 into X and Y
We have he mask to set a pixel in A, we store this in z_b, and flip
the bits with EOR, This is now the background mask. - we store this
in Z_C
We now process the current screen bitplane, and remove the pixel
we're changing by ANDing with Z_c
Next we check the bit of color we're working with (Z_D).. if it's
zero we need to skip setting the pixel, if we need to set the pixel
with OR Z_B and store back into the buffer.
|
 |
We need to calculate the Buffer address...
A pattern is 8 lines, and two bitplanes in 4 color mode.
We take the top 5 bits of the Xpos, and multiply this by 16 (the
bottom 3 bits are within each tile)
We add the bottom 3 bits of the Ypos - this gets the line within a
pattern - we multiply this by 2 (2 bitplanes per line)
Next we take the top 5 bits and multiply by 512 (8*2*32 - 8 lines
per tile, two bitplanes per line, 32 tiles per line) |
 |
POINT works in reverse to PSET... returning the color in A for
pixel AB,C
Once again, we get the pixel mask, get the bytes from the buffer,
and take each bitplane for the pixel.
We return a valid pixel color for z_d |
 |
We also need a clear screen routine, we just repeatedly write Zero
bytes to the buffer
|
 |
This example transferred the entire
screen in 4 chunks, this is most suitable for a game with a
lot of pixels changing each frame.
Actually Photon didn't really do that, so a 'list' of pixel
updates would probably have been better. though it didn't
really matter in this case.
|
 |
 |
The NES uses 1 byte per
tiledef for 256 tiles, BUT it supports 2 pattern maps. We'll switch
to the second pattern map half way down the screen with a line
interrupt (IRQ).
The VBLANK isn't very long, so we can only update 11 tiles per
vblank, we'll update 8 sequentially, and 3 every frame - these 3 can
be used to focus on 'important' things (Our players!)
|
Data Definitions & Starting the game
We're going to fill the screen with tiles, as we only have 512,
our screen limit is 200x160, we'll create a small centralized 'pixel
screen' of 25x20 tiles
We'll fill all the unused tiles with tile 255.
Because the tilemap only supports 255 tiles, we'll switch to the
second pattern table address at line 80 of our pixel screen. this
means the first half of the screen will be defined by the patterns
at vram address $0000, and the second at $1000
We'll need $2000 bytes of data for the pattern cache, so we'll use
some cartridge ram (an upgrade in the cartridge) - this will appear
at addresses $6000-7FFF
We can only transfer patterns to VRAM during VBLANK, this isn't very
long, so we'll only be able to update 11 tiles each VBLANK. |
 |
The first is an address of RAM for system vars - the game needs
less than 256 bytes - this is the entirety of the RAM needed - the
rest of the game can run from ROM
We define some screen size vars - these set the scale for objects
and font., and the boundaries of the screen for drawing the level...
this configures the game engine for a smaller screen.
We also need some zero page entries to handle the buffer copy.
RamAddrH/L will point to the next set of tile patterns to copy - 8
consecutive ones will be transferred each NMI
The "FastRefresh" pairs are updated each frame and can point to
important objects... The Vblank is long enough for 3, though we only
use 2
|
 |
We need to set up our screen, We also turn on the 'Cartridge ram'
with a write to $A001
Finally we initialize our zero page variable. |
 |
We need to initialize the tilemap.
First we fill the entire screen with an unused tile (255)
Next we fill the two halves of our 'pixel screen' with tiles 0-250.
We use the 'Fill Area With Tiles' Function from the simple series to
do this. |
 |
Our screen is ready, so we can start the interrupt handler, which
will transfer the pattern data from the cache to actual VRAM |
 |
When the game starts we clear the game ram... We then show the
Main menu. |
 |
Main Game Loop
At the first run, we need to wait for the level screen to redraw.
this is because only 8 tiles transfer each VBLANK. |
 |
At the start of the loop, we update the 'Tick'
The Tick is 1 or 0 - this is used by the multiplatform code for
boost (during which player moves at 2x speed)
If Boost is ON we lower the delay - we need to speed the game up
because redrawing the 'Boost counter' is too slow. |
 |
During the pause loop we check the keypreses... if a key is not
held down we release the keytimeout
The timeout means if we hold left we don't keep rotating around...
we have to press left multiple times
We store any keypresses in z_D - and wait until z_BC reaches Zero |
 |
If the Keytimeout is not cleared we will ignore Left / Right
keypresses.
First we turn off Boost (it will be turned back on if FIRE is held)
When we process Left or Right we INC/DEC the 'Player Direction' then
run the SetPlayerDirection routine which handles the rest of setting
the movement for the player.
If Fire is pressed then we check if any 'Boost power' is left..
If there is, then we turn boost power on.
Once the keypresses are handled, we update the player with 'Handle
Player'
Then we update the CPU with 'Handle CPU'
|
 |
We set two of the fast update tiles to point to the CPU and Player
position - this means they'll be updated quickly so the player can
see what's going on!
That's it... We jump back to the start of the loop for the next
tick. |
 |
The VBlank & IRQ interrupt handler
During the VBLANK we're need to update the actual patterns in VRAM
from the cache.
We only have enough time to do about 11! we can update one tile with
"SetAndSendOneTile".. The address is defined by the HL Register pair
AX
First we update 3 tiles with unique memory addresses |
 |
We now transfer 8 tiles from the address 'RamADDR',
Once these have been transferred, we update the address so we
continue from the same position next time. |
 |
Every VRAM write will mess up the scroll position, so we need to
reset it now.
When the VBLANK ends, we need to set the first tile pattern position
$0000
We also need to set the IRQ line interrupt to occur at line 116, we
do this by writing this linenumber to $C000
We also need to write to $C001 and $E001 to start the interrupt...
Note: This interrupt function is part of Cartridge Mapper 3 |
 |
During the interrupt, we set the second pattern address $1000, and
turn off the interrupt agian. |
 |
The addresses of the interrupt handers are defined by the
cartridge footer. |
 |
For speed, The transfer routine is an unwrapped loop.
If needed we select an address with "Set and Send One Tile", this
sends the destination VRAM address to $2006
we send the new data for that address to $2007
|
 |
PSET - Plot Pixel / POINT - Read pixel color
The PSET command will set a pixel of the screen...
The 16 bit X co-ordinate is defined by registers A z_B (A= High byte
z_B= Low byte)... this is so we can support a 320 pixel wide screen
on systems that use it
The 8 Bit Y co ordinate is defined by z_C... only 8 bits, as our
maximum screen is 200 pixels tall
z_D is the new color for the pixel (0-15)
First we check the Xposition, and see if we're being asked to draw
offscreen.
Next we calculate the pixel position within the byte - as each byte
contains data for 8 pixels.
Next we use CalcVramAddr to get the pixel address within the cache
(in z_HL)
We have the mask to set a pixel in A, we store this in z_b, and flip
the bits with EOR, This is now the background mask. - we store this
in Z_C
We now process the current screen bitplane, and remove the pixel
we're changing by ANDing with Z_c
Next we check the bit of color we're working with (Z_D).. if it's
zero we need to skip setting the pixel, if we need to set the pixel
with OR Z_B and store back into the buffer.
|
 |
We need to calculate the Buffer address...
A pattern is 8 lines, and two bitplanes
We take the top 5 bits of the Xpos, and multiply this by 16 (the
bottom 3 bits are within each tile)
We add the bottom 3 bits of the Ypos - this gets the line within a
pattern
Next we take the top 5 bits and multiply it up, each strip is 25
tiles wide, and each tile is 16 bytes.
We achieve this multiplication y repeated bitshifts. |
 |
POINT works in reverse to PSET... returning the color in A for
pixel AB,C
Once again, we get the pixel mask, get the bytes from the buffer,
and take each bitplane for the pixel.
We return a valid pixel color for z_d |
 |
We also need a clear screen routine, we just repeatedly write Zero
bytes to the buffer
|
 |
The way we're
using the screen is pretty crude, and it's done the job in this
case, but it would probably be faster to have a 'pixel update
list' and alter the pixels in VRAM during the VBLANK, or have a
'tile update list' with the addresses of the tiles that should be
updated next Vblank.
This is kind of a crude 'cover all' example solution to show
what's possible.
|
 |