Jump to content

  • Log In with Google      Sign In   
  • Create Account

New Old Things

A C64 game - Step 13

Posted by , 08 July 2011 - - - - - - · 2,277 views

And yet again a bigger step.


Of course we need lots of goodies from the killed enemies. We add items. Items are displayed as 2x2 block of characters. There's a new list of possible items with location added (ITEM_ACTIVE, etc.). The list also stores the original background behind the items.
To get an item spawned just walk beside one of the enemies (look in their direction) and keep fire pressed until it dies.

Note that the player cannot collect the items yet (that's up for the next step).

Attached Image


Inside the player subroutine FireShot we add another subroutine call after killing an enemy:
.EnemyKilled          
          jsr RemoveObject
          
          jsr SpawnItem


SpawnItem itself resembles the AddObject routine. First we loop over the active item table (ITEM_ACTIVE) to find a free slot.

Once found we randomly chose the item type (for now there are two types). The ugly part below that stores the original character and color at the item position and puts the items char and color in its place. Remember the item is sized 2x2 chars, so we need to store 8 bytes overall. However to keep the code comfortable, we actually use 8 tables. This allows use to only work with the item index instead of manually accessing a second index. There's only two index registers after all.

;------------------------------------------------------------
;spawns an item at char position from object x
;X = object index
;------------------------------------------------------------
!zone SpawnItem
SpawnItem
          ;find free item slot
          ldy #0
          
.CheckNextItemSlot          
          lda ITEM_ACTIVE,y
          cmp #ITEM_NONE
          beq .FreeSlotFound
          iny
          cpy #ITEM_COUNT
          bne .CheckNextItemSlot
          rts
          
.FreeSlotFound          
          jsr GenerateRandomNumber
          and #$1
          
          sta ITEM_ACTIVE,y
          lda SPRITE_CHAR_POS_X,x
          sta ITEM_POS_X,y
          lda SPRITE_CHAR_POS_Y,x
          sta ITEM_POS_Y,y

          sty PARAM1
          
          ;find address in screen buffer...
          tay
          lda SCREEN_LINE_OFFSET_TABLE_LO,y
          sta ZEROPAGE_POINTER_1
          sta ZEROPAGE_POINTER_2
          lda SCREEN_LINE_OFFSET_TABLE_HI,y
          sta ZEROPAGE_POINTER_1 + 1
          ;...and for the color buffer
          clc
          adc #( ( SCREEN_COLOR - SCREEN_CHAR ) & 0xff00 ) >> 8
          sta ZEROPAGE_POINTER_2 + 1
          
          ldy SPRITE_CHAR_POS_X,x
          ldx PARAM1
          
          ;store old background and put item 
          ;we do not take overlapping items in account yet!
          lda (ZEROPAGE_POINTER_1),y
          sta ITEM_BACK_CHAR_UL,x
          lda (ZEROPAGE_POINTER_2),y
          sta ITEM_BACK_COLOR_UL,x
          
          lda ITEM_CHAR_UL,x
          sta (ZEROPAGE_POINTER_1),y
          lda ITEM_COLOR_UL,x
          sta (ZEROPAGE_POINTER_2),y
          
          iny
          lda (ZEROPAGE_POINTER_1),y
          sta ITEM_BACK_CHAR_UR,x
          lda (ZEROPAGE_POINTER_2),y
          sta ITEM_BACK_COLOR_UR,x

          lda ITEM_CHAR_UR,x
          sta (ZEROPAGE_POINTER_1),y
          lda ITEM_COLOR_UR,x
          sta (ZEROPAGE_POINTER_2),y
          
          tya
          clc
          adc #39
          tay
          lda (ZEROPAGE_POINTER_1),y
          sta ITEM_BACK_CHAR_LL,x
          lda (ZEROPAGE_POINTER_2),y
          sta ITEM_BACK_COLOR_LL,x
          
          lda ITEM_CHAR_LL,x
          sta (ZEROPAGE_POINTER_1),y
          lda ITEM_COLOR_LL,x
          sta (ZEROPAGE_POINTER_2),y
          
          iny
          lda (ZEROPAGE_POINTER_1),y
          sta ITEM_BACK_CHAR_LR,x
          lda (ZEROPAGE_POINTER_2),y
          sta ITEM_BACK_COLOR_LR,x
          lda ITEM_CHAR_LR,x
          sta (ZEROPAGE_POINTER_1),y
          lda ITEM_COLOR_LR,x
          sta (ZEROPAGE_POINTER_2),y

          rts


Previous Step Next Step

Attached Files




A C64 game - Step 12

Posted by , 03 July 2011 - - - - - - · 1,669 views

One of the more complex steps. And also one I someday need to heavily optimize. The player can now shoot an enemy.

The central function for this is FireShot. We don't use a bullet but insta-shot. However walls should block the shot as well. This means, we need to take the current player direction and position, and work our way to the left/right until we hit an enemy or a wall.
Since there's no direct collision involved we take the character pos of the player, add or decrease the x pos and compare against all alive enemies pos. Rinse and repeat until done.

I've given the enemies 5 HP, and the player has a shot delay of 10 frames. Therefore it takes a while for the enemy to disappear (best tested with the box on top). If the player is purple the shot delay is active.


Attached Image


We start with adding a fire delay to PlayerControl:

lda PLAYER_SHOT_PAUSE
          bne .FirePauseActive
          
          lda #1
          sta VIC_SPRITE_COLOR
          
          lda #$10
          bit $dc00
          bne .NotFirePushed
          
          jsr FireShot
          jmp .FireDone

.FirePauseActive
          dec PLAYER_SHOT_PAUSE

.FireDone
.NotFirePushed


This simply checks for PLAYER_SHOT_PAUSE. If it is higher than 0 the player is still pausing. If so the counter is decreased and the fire function skipped. If the counter is zero, we check the fire button and if pressed call the FireShot routine.



The FireShot routine is not that complicated, however it's taking its processing time. First set the fire pause to 10 frames. Mark the player as shooting by changing his color.



Now the hard part. There is no visible bullet. So we take the current player position, increase/decrease X and check for a blocking char or a hittable enemy. If the bullet is blocked, done. If an enemy is hit, decrease its health by one point. Once the health is down to zero the enemy is removed.


!zone FireShot
FireShot
          ;frame delay until next shot
          lda #10
          sta PLAYER_SHOT_PAUSE
          
          ;mark player as shooting
          lda #4
          sta VIC_SPRITE_COLOR
          
          ldy SPRITE_CHAR_POS_Y
          dey
          lda SCREEN_LINE_OFFSET_TABLE_LO,y
          sta ZEROPAGE_POINTER_1
          lda SCREEN_LINE_OFFSET_TABLE_HI,y
          sta ZEROPAGE_POINTER_1 + 1
          
          ldy SPRITE_CHAR_POS_X
          
.ShotContinue
          lda SPRITE_DIRECTION
          beq .ShootRight

          ;shooting left          
          dey
          
          lda (ZEROPAGE_POINTER_1),y
          jsr IsCharBlocking
          bne .ShotDone
          jmp .CheckHitEnemy
          
.ShootRight
          iny
          
          lda (ZEROPAGE_POINTER_1),y
          jsr IsCharBlocking
          bne .ShotDone
          
.CheckHitEnemy          
          ;hit an enemy?
          ldx #1
          
.CheckEnemy          
          stx PARAM2
          lda SPRITE_ACTIVE,x
          beq .CheckNextEnemy
          tax
          lda IS_TYPE_ENEMY,x
          beq .CheckNextEnemy
          
          ;sprite pos matches on x?
          ldx PARAM2
          sty PARAM1
          lda SPRITE_CHAR_POS_X,x
          cmp PARAM1
          bne .CheckNextEnemy
          
          ;sprite pos matches on y?
          lda SPRITE_CHAR_POS_Y,x
          cmp SPRITE_CHAR_POS_Y
          beq .EnemyHit

          ;sprite pos matches on y + 1?
          clc
          adc #1
          cmp SPRITE_CHAR_POS_Y
          beq .EnemyHit

          ;sprite pos matches on y - 1?
          sec
          sbc #2
          cmp SPRITE_CHAR_POS_Y
          bne .CheckNextEnemy
          
.EnemyHit          
          ;enemy hit!
          dec SPRITE_HP,x
          lda SPRITE_HP,x
          beq .EnemyKilled
          jmp .ShotDone
          
          
.EnemyKilled          
          jsr RemoveObject
          jmp .ShotDone
          
.CheckNextEnemy     
          ldx PARAM2
          inx
          cpx #8
          bne .CheckEnemy
          jmp  .ShotContinue
          
.ShotDone          
          rts

Previous Step Next Step

Attached Files




A C64 game - Step 11

Posted by , in Uncategorized, C64 24 June 2011 - - - - - - · 1,762 views

From colliding to dying is a small step. Once the player collides with an enemy we kill him by removing the player object. A "Press Fire to Restart" message is displayed and a press on the button will revive the player object.

We add the function RemoveObject which simply removes the object from the SPRITE_ACTIVE table and disables its sprite. While we wait for the player to press the button all the rest of the game moves on.


First of all we add the getting killed part in our old routine "CheckCollisions". Nothing ground breaking, a call to the text display function follows by removing the object and resetting the button released flag.

.PlayerCollidedWithEnemy          
          ;display text
          lda #<TEXT_PRESS_FIRE
          sta ZEROPAGE_POINTER_1
          lda #>TEXT_PRESS_FIRE
          sta ZEROPAGE_POINTER_1 + 1
          lda #10
          sta PARAM1
          lda #23
          sta PARAM2
          jsr DisplayText

          ldx #0
          stx BUTTON_PRESSED
          stx BUTTON_RELEASED
          jsr RemoveObject

          rts

A new call is added to the main game loop which controls behaviour when the player is dead:

GameLoop  
          jsr WaitFrame
          
          jsr DeadControl

          jsr ObjectControl
          jsr CheckCollisions
          
          jmp GameLoop          




Surprisingly easy. We check if the player is really dead, if he isn't, bail out. Then we check for the joystick button being pressed, but only allow to go on, if the button has been released before. If all that happened, we simply force the player object back into life (for now with hard coded values).


!zone DeadControl
DeadControl
          lda SPRITE_ACTIVE
          beq .PlayerIsDead
          rts
          
.PlayerIsDead
          lda #$10
          bit $dc00
          bne .ButtonNotPressed
          
          ;button pushed
          lda BUTTON_RELEASED
          bne .Restart
          rts
          

.ButtonNotPressed
          lda #1
          sta BUTTON_RELEASED
          rts
          
.Restart
          lda #5
          sta PARAM1 
          
          lda #4
          sta PARAM2

          ;type
          lda #TYPE_PLAYER
          sta PARAM3

          ldx #0
          lda PARAM3
          sta SPRITE_ACTIVE,x
          
          ;PARAM1 and PARAM2 hold x,y already
          jsr CalcSpritePosFromCharPos
          
          ;enable sprite
          lda BIT_TABLE,x
          ora VIC_SPRITE_ENABLE
          sta VIC_SPRITE_ENABLE
          
          ;initialise enemy values
          lda #SPRITE_PLAYER
          sta SPRITE_POINTER_BASE,x
          
          ;look right per default
          lda #0
          sta SPRITE_DIRECTION,x
          
          rts



Attached Image

Previous Step 10Next Step

Attached Files




A C64 game - Step 10

Posted by , in Uncategorized, C64 17 June 2011 - - - - - - · 1,870 views

So you found out the enemies couldn't hurt you? Well, we're working towards that goal in this step. We add collision checks. Since I'm not completely sure about later changes we are NOT relying on the VICs collision checks but roll our own. Remember the object size contraints from step #6? We apply those to the object collision checks as well.

We add a new subroutine CheckCollisions which in turn uses IsEnemyCollidingWithPlayer. We do not check for collisions between enemies. The check is not completely exact (behaves more like 9 pixel * 16 pixel), but that's good enough. To test the function a collision is signalled by setting the border color to white.


Attached Image


The routine CheckCollisions is simply added to the main game loop:


GameLoop  
          jsr WaitFrame

          jsr ObjectControl
          jsr CheckCollisions
          
          jmp GameLoop          



The function CheckCollision just loops through the active object list and calls IsEnemyCollidingWithPlayer for every active entry:


;------------------------------------------------------------
;check object collisions (enemy vs. player etc.)
;x 
;------------------------------------------------------------

CheckCollisions
          ldx #1
          
.CollisionLoop          
          lda SPRITE_ACTIVE,x
          bne .CheckObject
          
.NextObject          
          inx
          cpx #8
          bne .CollisionLoop          

          lda #0
          sta VIC_BORDER_COLOR
          rts
          
.CheckObject
          stx PARAM2
          jsr IsEnemyCollidingWithPlayer
          bne .PlayerCollidedWithEnemy
          ldx PARAM2
          jmp .NextObject
          
.PlayerCollidedWithEnemy          
          lda #1
          sta VIC_BORDER_COLOR
          ;ldx #0
          ;jsr RemoveObject
          rts

IsEnemyCollidingWithPlayer employs a few tricks to ease the calculation.
First we do the Y coordinate check to weed out. For the X coordinate: Since the actual X position is 9 bits we half the value (half the X coordinate and add 128 if the extended X bit is set). Now the comparation is easy.
The routine then returns 1 if a collision occurs and 0 if not.




;------------------------------------------------------------
;check object collision with player (object 0)
;x = enemy index
;return a = 1 when colliding, a = 0 when not
;------------------------------------------------------------

!zone IsEnemyCollidingWithPlayer


.CalculateSimpleXPos
          ;Returns a with simple x pos (x halved + 128 if > 256)
          ;modifies y
          lda BIT_TABLE,x
          and SPRITE_POS_X_EXTEND
          beq .NoXBit
          
          lda SPRITE_POS_X,x
          lsr
          clc
          adc #128
          rts
          
.NoXBit          
          lda SPRITE_POS_X,x
          lsr
          rts

IsEnemyCollidingWithPlayer
          ;modifies X
          ;check y pos
          lda SPRITE_POS_Y,x
          sec
          sbc #( OBJECT_HEIGHT )      ;offset to bottom
          cmp SPRITE_POS_Y
          bcs .NotTouching
          clc
          adc #( OBJECT_HEIGHT + OBJECT_HEIGHT - 1 )
          cmp SPRITE_POS_Y
          bcc .NotTouching
          
          ;X = Index in enemy-table
          jsr .CalculateSimpleXPos
          sta PARAM1
          ldx #0
          jsr .CalculateSimpleXPos
          
          sec
          sbc #4
          ;position X-Anfang Player - 12 Pixel
          cmp PARAM1
          bcs .NotTouching
          adc #8
          cmp PARAM1
          bcc .NotTouching
          
          lda #1
          rts
          
.NotTouching
          lda #0
          rts



Previous Step 9 Next Step 11

Attached Files




A C64 game - Step 9

Posted by , 11 June 2011 - - - - - - · 1,934 views

What are enemies if they just sit put and don't move at all? Therefore we now add the sub routine ObjectControl. ObjectControl loops through all objects (even the player) and jumps to the behaviour function depending on the object type. This incurs that the behaviour is tied to the object type. We provide a table with function pointers to every object's behaviour code (including the player).

ObjectControl takes the object type as index into the table and jumps to the target address. For now we have two enemy types, dumb moving up/down or left/right. For moving we reuse the previously created functions we already use for the player, namely ObjectMoveLeft/ObjectMoveRight etc.




Loop over all active objects and jump at their behaviour code. Note that we apply a nasty trick. Since jsr doesn't allow for indirect jumps we manually push the return address on the stack and then call indirect jmp. This allows for the behaviour code to return with rts.

;------------------------------------------------------------
;Enemy Behaviour
;------------------------------------------------------------
!zone ObjectControl
ObjectControl
          ldx #0
          
.ObjectLoop          
          ldy SPRITE_ACTIVE,x
          beq .NextObject
          
          ;enemy is active
          dey
          lda ENEMY_BEHAVIOUR_TABLE_LO,y
          sta ZEROPAGE_POINTER_2
          lda ENEMY_BEHAVIOUR_TABLE_HI,y
          sta ZEROPAGE_POINTER_2 + 1
          
          ;set up return address for rts
          lda #>( .NextObject - 1 )
          pha 
          lda #<( .NextObject - 1 )
          pha
          
          jmp (ZEROPAGE_POINTER_2)
          
.NextObject          
          inx
          cpx #8
          bne .ObjectLoop
          rts
          



The main game loop is now changed; removed the call of PlayerControl and added the call to ObjectControl:

;------------------------------------------------------------
;the main game loop
;------------------------------------------------------------

GameLoop  
          jsr WaitFrame

          jsr ObjectControl
          
          jmp GameLoop          



The behaviour table is built from the behaviour code addresses. Actually we use two tables for high and low byte, this way we don't have to mess with the index. The < and > operators return the low and high byte of a 16 bit value.

ENEMY_BEHAVIOUR_TABLE_LO          
          !byte <PlayerControl
          !byte <BehaviourDumbEnemyLR
          !byte <BehaviourDumbEnemyUD
          
ENEMY_BEHAVIOUR_TABLE_HI
          !byte >PlayerControl
          !byte >BehaviourDumbEnemyLR
          !byte >BehaviourDumbEnemyUD

Attached Image


Previous Step 8 Next Step 10

Attached Files




A C64 game - Step 8

Posted by , 03 June 2011 - - - - - - · 1,986 views

Of course a game isn't a game without some challenge. Therefore we need enemies. Since we have some neat little level build code why not use it for enemies as well?

We add a new level primitive type LD_OBJECT which adds objects (= sprites). We use it for both player and enemies. A new table SPRITE_ACTIVE is added to see if a sprite is used (and which type).


One central function to this is FindEmptySpriteSlot. It iterates over the sprite active table and looks for a free slot to use. If there is a free slot we set the object active, apply the object startup values and use the previously created CalcSpritePosFromCharPos to place the sprite.


Note that we don't plan to use more than 8 objects so we can directly map object to sprites.


Find an empty slot:

;------------------------------------------------------------
;Looks for an empty sprite slot, returns in X
;#1 in A when empty slot found, #0 when full
;------------------------------------------------------------

!zone FindEmptySpriteSlot
FindEmptySpriteSlot
          ldx #0
.CheckSlot          
          lda SPRITE_ACTIVE,x
          beq .FoundSlot
          
          inx
          cpx #8
          bne .CheckSlot
          
          lda #0
          rts
          
.FoundSlot
          lda #1
          rts


How we add an object during level buildup:


.Object
          ;X pos
          iny
          lda (ZEROPAGE_POINTER_1),y
          sta PARAM1 
          
          ;Y pos
          iny
          lda (ZEROPAGE_POINTER_1),y
          sta PARAM2

          ;type
          iny
          lda (ZEROPAGE_POINTER_1),y
          sta PARAM3

          ;store y for later
          tya
          pha
          
          ;add object to sprite array
          jsr FindEmptySpriteSlot
          beq .NoFreeSlot
          
          lda PARAM3
          sta SPRITE_ACTIVE,x
          
          ;PARAM1 and PARAM2 hold x,y already
          jsr CalcSpritePosFromCharPos
          
          ;enable sprite
          lda BIT_TABLE,x
          ora VIC_SPRITE_ENABLE
          sta VIC_SPRITE_ENABLE
          
          lda #SPRITE_PLAYER
          sta SPRITE_POINTER_BASE,x
          
.NoFreeSlot                    
          jmp .NextLevelData


Attached Image

Previous Step 7 Next Step 9

Attached Files




A C64 game - Step 7

Posted by , in Uncategorized, C64 29 May 2011 - - - - - - · 2,385 views

Now it's starting to resemble a game. Loosely.

In this step we add gravity and jumping. The player will fall if there is no blocking char below. On joystick up the player jumps in a curve.
Both fall speed and jump speed are non linear and based on tables.

This step shows:
-gravity (accelerating)
-jumping (following a delta y curve)

Most prominent addition are the jump and fall table. These hold the deltas we use to make the movement not look linear but somewhat naturalistic:

PLAYER_JUMP_POS
          !byte 0
PLAYER_JUMP_TABLE
          !byte 8,7,5,3,2,1,1,1,0,0
          
PLAYER_FALL_POS
          !byte 0
FALL_SPEED_TABLE
          !byte 1,1,2,2,3,3,3,3,3,3

The jump is only possible if the player is not falling. Once the player jumped the PLAYER_JUMP_POS is increased on every frame and the player moved upwards for entry-of-jump-table pixel. If the player is blocked moving upwards the jump is aborted:

.PlayerIsJumping
          inc PLAYER_JUMP_POS
          lda PLAYER_JUMP_POS
          cmp #JUMP_TABLE_SIZE
          bne .JumpOn
          
          lda #0
          sta PLAYER_JUMP_POS
          jmp .JumpComplete
          
.JumpOn                    
          ldx PLAYER_JUMP_POS
          
          lda PLAYER_JUMP_TABLE,x
          beq .JumpComplete
          sta PARAM5
          
.JumpContinue          
          jsr PlayerMoveUp
          beq .JumpBlocked
          
          dec PARAM5
          bne .JumpContinue
          jmp .JumpComplete
          
          
.JumpBlocked
          lda #0
          sta PLAYER_JUMP_POS
          jmp .JumpStopped

To check for falling an attempt is made to move the player down one pixel. If he is blocked he is standing on solid ground. If he can fall the fall counter is increased. The fall counter is increased in every frame up to the max number of entries in the fall table.

If the player is falling the player is moved down entry-of-fall-table pixel.

.PlayerFell
          ldx PLAYER_FALL_POS
          lda FALL_SPEED_TABLE,x
          beq .FallComplete
          sta PARAM5

.FallLoop          
          dec PARAM5
          beq .FallComplete
          
          jsr PlayerMoveDown
          jmp .FallLoop
          
.FallComplete
          lda PLAYER_FALL_POS
          cmp #( FALL_TABLE_SIZE - 1 )
          beq .FallSpeedAtMax
          
          inc PLAYER_FALL_POS

.FallSpeedAtMax

Attached Image

Previous Step 6 Next Step 8

Attached Files




A C64 Game - Step 6

Posted by , 20 May 2011 - - - - - - · 2,754 views

And onwards we go: Obviously we don't want the player to move through walls. In this step we check the chars in the players way to see if they are blocking.

To make this easier we store the character pos and the character delta pos (0 to 7) for x and y for every sprite (SPRITE_CHAR_POS_X, SPRITE_CHAR_POS_X_DELTA). If the sprite is not at a character brink the move is allowed, if it hits the brink, check the characters at the target.

For this step any character equal or above index 128 is considered blocking, any below is free to move.

The collision code assumes that the collision box of a sprite is one char wide and two chars high.

This step shows:
-calculating character positions while moving about
-checking the position for blocking chars
-calculating the required sprite position from character pos (for starting a sprite at a specific place)

Since the code is basically the same for all four directions I'll only go into details on one of them:

;------------------------------------------------------------
;PlayerMoveLeft
;------------------------------------------------------------
!zone PlayerMoveLeft
PlayerMoveLeft
          ldx #0


          ;check if we are on the brink of a character          
          lda SPRITE_CHAR_POS_X_DELTA
          beq .CheckCanMoveLeft
          

          ;no, we are not
.CanMoveLeft
          dec SPRITE_CHAR_POS_X_DELTA
          
          jsr MoveSpriteLeft
          rts
          
.CheckCanMoveLeft
          lda SPRITE_CHAR_POS_Y_DELTA
          beq .NoThirdCharCheckNeeded


          ;find the character in the screen buffer
          ldy SPRITE_CHAR_POS_Y
          lda SCREEN_LINE_OFFSET_TABLE_LO,y
          sta ZEROPAGE_POINTER_1
          lda SCREEN_LINE_OFFSET_TABLE_HI,y
          sta ZEROPAGE_POINTER_1 + 1

          lda SPRITE_CHAR_POS_X
          clc
          adc #39       ;39 equals one line down (40 chars) and one to the left (-1)
          tay
          
          lda (ZEROPAGE_POINTER_1),y
          
          jsr IsCharBlocking
          bne .BlockedLeft
          
.NoThirdCharCheckNeeded          
          ldy SPRITE_CHAR_POS_Y
          dey
          lda SCREEN_LINE_OFFSET_TABLE_LO,y
          sta ZEROPAGE_POINTER_1
          lda SCREEN_LINE_OFFSET_TABLE_HI,y
          sta ZEROPAGE_POINTER_1 + 1
          
          ldy SPRITE_CHAR_POS_X
          dey
          
          lda (ZEROPAGE_POINTER_1),y
          
          jsr IsCharBlocking
          bne .BlockedLeft
          
          tya
          clc
          adc #40
          tay
          lda (ZEROPAGE_POINTER_1),y
          jsr IsCharBlocking
          bne .BlockedLeft
          
          
          lda #8
          sta SPRITE_CHAR_POS_X_DELTA
          dec SPRITE_CHAR_POS_X
          jmp .CanMoveLeft
          
.BlockedLeft
          rts


The subroutine IsCharBlocking is rather primitive, as described it only checks if the character is smaller than 128:

;------------------------------------------------------------
;IsCharBlocking
;checks if a char is blocking
;A contains the character
;returns 1 for blocking, 0 for not blocking
;------------------------------------------------------------
!zone IsCharBlocking
IsCharBlocking
          cmp #128
          bpl .Blocking
          
          lda #0
          rts
          
.Blocking
          lda #1
          rts


Attached Image

Previous Step 5 Next Step 7

Attached Files




A C64 game - Step 5

Posted by , 14 May 2011 - - - - - - · 2,830 views

Our next big step:

Obviously we want some play screen, and more obviously, we are not going to store full screens (we've got only 64Kb Ram after all). Therefore there's a level build routine that allows to build a screen with various building elements. For now we'll start out with vertical and horizontal lines. Since we always have a level border at the screen edges we'll have the border as a second screen data block (LEVEL_BORDER_DATA).
To allow faster screen building we also have a table with the precalculated char offsets of a vertical line (SCREEN_LINE_OFFSET_TABLE_LO and SCREEN_LINE_OFFSET_TABLE_HI). Note that with the C64s mnemonics the best way to get stuff done is tables, tables, tables.

The BuildScreen sub routine clears the play screen area, builds the level data and then the border. The level data is a collection of primitives which are then worked through until LD_END is hit.



The first addition is the call to the buildroutine:
          ;setup level
          lda #0
          sta LEVEL_NR
          jsr BuildScreen


The BuildScreen routine sets up a level completely. It starts out with clearing the current play area (not the full screen). Then LEVEL_NR is used to look up the location of the level data in table SCREEN_DATA_TABLE.
.BuildLevel is jumped to to actually work through the level primitives and put them on screen. Then we use LEVEL_BORDER_DATA as second level to display a border on the screens edges.


.BuildLevel uses Y as index through the data. Depending on the first byte different routines are called (.LineH, .LineV, .LevelComplete). Since I think levels may be complex and use more than 256 bytes we add Y to the level data pointers so we can start out with Y=0 for the next primitive.


;------------------------------------------------------------
;BuildScreen
;creates a screen from level data
;------------------------------------------------------------
!zone BuildScreen
BuildScreen
          lda #0
          ldy #6
          jsr ClearPlayScreen

          ;get pointer to real level data from table
          ldx LEVEL_NR
          lda SCREEN_DATA_TABLE,x
          sta ZEROPAGE_POINTER_1
          lda SCREEN_DATA_TABLE + 1,x
          sta ZEROPAGE_POINTER_1 + 1
          
          jsr .BuildLevel

          ;get pointer to real level data from table
          lda #<LEVEL_BORDER_DATA
          sta ZEROPAGE_POINTER_1
          lda #>LEVEL_BORDER_DATA
          sta ZEROPAGE_POINTER_1 + 1
          
          jsr .BuildLevel
          rts
          
.BuildLevel
          ;work through data
          ldy #255
          
.LevelDataLoop
          iny
          
          lda (ZEROPAGE_POINTER_1),y
          cmp #LD_END
          beq .LevelComplete
          cmp #LD_LINE_H
          beq .LineH
          cmp #LD_LINE_V
          beq .LineV
          
.LevelComplete          
          rts
          
.NextLevelData
          pla
          
          ;adjust pointers so we are able to access more 
          ;than 256 bytes of level data
          clc
          adc #1
          adc ZEROPAGE_POINTER_1
          sta ZEROPAGE_POINTER_1
          lda ZEROPAGE_POINTER_1 + 1
          adc #0
          sta ZEROPAGE_POINTER_1 + 1
          ldy #255
          
          jmp .LevelDataLoop




The primitive display routines .LineH and .LineV are rather straight forward. Read the parameters, and put the character and color values in place.

.LineH
          ;X pos
          iny
          lda (ZEROPAGE_POINTER_1),y
          sta PARAM1 
          
          ;Y pos
          iny
          lda (ZEROPAGE_POINTER_1),y
          sta PARAM2

          ;width
          iny
          lda (ZEROPAGE_POINTER_1),y
          sta PARAM3
          
          ;char
          iny
          lda (ZEROPAGE_POINTER_1),y
          sta PARAM4
          
          ;color
          iny
          lda (ZEROPAGE_POINTER_1),y
          sta PARAM5
          
          ;store target pointers to screen and color ram
          ldx PARAM2
          lda SCREEN_LINE_OFFSET_TABLE_LO,x
          sta ZEROPAGE_POINTER_2
          sta ZEROPAGE_POINTER_3
          lda SCREEN_LINE_OFFSET_TABLE_HI,x
          sta ZEROPAGE_POINTER_2 + 1
          clc
          adc #( ( SCREEN_COLOR - SCREEN_CHAR ) & 0xff00 ) >> 8
          sta ZEROPAGE_POINTER_3 + 1
          
          tya
          pha
          
          ldy PARAM1
.NextChar          
          lda PARAM4
          sta (ZEROPAGE_POINTER_2),y
          lda PARAM5
          sta (ZEROPAGE_POINTER_3),y
          iny
          dec PARAM3
          bne .NextChar
          
          jmp .NextLevelData
          
.LineV
          ;X pos
          iny
          lda (ZEROPAGE_POINTER_1),y
          sta PARAM1 
          
          ;Y pos
          iny
          lda (ZEROPAGE_POINTER_1),y
          sta PARAM2

          ;height
          iny
          lda (ZEROPAGE_POINTER_1),y
          sta PARAM3
          
          ;char
          iny
          lda (ZEROPAGE_POINTER_1),y
          sta PARAM4
          
          ;color
          iny
          lda (ZEROPAGE_POINTER_1),y
          sta PARAM5
          
          ;store target pointers to screen and color ram
          ldx PARAM2
          lda SCREEN_LINE_OFFSET_TABLE_LO,x
          sta ZEROPAGE_POINTER_2
          sta ZEROPAGE_POINTER_3
          lda SCREEN_LINE_OFFSET_TABLE_HI,x
          sta ZEROPAGE_POINTER_2 + 1
          clc
          adc #( ( SCREEN_COLOR - SCREEN_CHAR ) & 0xff00 ) >> 8
          sta ZEROPAGE_POINTER_3 + 1
          
          tya
          pha
          
          ldy PARAM1
.NextCharV         
          lda PARAM4
          sta (ZEROPAGE_POINTER_2),y
          lda PARAM5
          sta (ZEROPAGE_POINTER_3),y
          
          ;adjust pointer
          lda ZEROPAGE_POINTER_2
          clc
          adc #40
          sta ZEROPAGE_POINTER_2
          sta ZEROPAGE_POINTER_3
          lda ZEROPAGE_POINTER_2 + 1
          adc #0
          sta ZEROPAGE_POINTER_2 + 1
          clc
          adc #( ( SCREEN_COLOR - SCREEN_CHAR ) & 0xff00 ) >> 8
          sta ZEROPAGE_POINTER_3 + 1
          
          dec PARAM3
          bne .NextCharV
          
          jmp .NextLevelData




Attached Image

Previous Step 4 Next Step 6

Attached Files




A C64 game - Step 4

Posted by , 07 May 2011 - - - - - - · 3,431 views

Now we take a bigger step: Moving the sprite with the joystick. Since we want to make a real game we also allow to move the sprite over to the right side (in other words we'll take care of the extended x bit).

For clarification: The C64 has 8 hardware sprites. That's 8 objects of the size 24 x 21 pixels. They can be placed anywhere. The coordinates are stored in memory addresses. However since the X resolution is higher than 256 all the sprites 9th bit is stored in another memory location (which makes it highly annoying to work with).
Sprite coordinates are set in X, Y pairs via the memory locations 53248 (=X sprite 0), 53249 (=Y sprite 0), 53250 (=X sprite 1), etc. The extended sprite bits are stored in 53248 + 16.

Since I don't plan to allow sprites to go off screen in the game later there is no defined behaviour if you move the sprite off screen too far. It'll simply bounce back in once the coordinate wraps around.

The joystick ports can be checked via the memory locations 56320 (Port 2) or 56321 (Port 1). The lower 5 bits are cleared(!) if either up, down, left, right for fire is pressed.


This step shows:
-Joystick control
-Sprite extended x bit


Attached Image


Inside the GameLoop we add a call to the players control function:

jsr PlayerControl

PlayerControl itself checks the joystick port (II) and calls the proper direction move routines. Note that the move routines themselves simply set the object index to 0 (for the player) and call a generic sprite move routine.

;------------------------------------------------------------
;check joystick (player control)
;------------------------------------------------------------
!zone PlayerControl
PlayerControl
          lda #$2
          bit $dc00
          bne .NotDownPressed
          jsr PlayerMoveDown
          
.NotDownPressed          
          lda #$1
          bit $dc00
          bne .NotUpPressed
          jsr PlayerMoveUp
          
.NotUpPressed          
          lda #$4
          bit $dc00
          bne .NotLeftPressed
          jsr PlayerMoveLeft
          
.NotLeftPressed
          lda #$8
          bit $dc00
          bne .NotRightPressed
          jsr PlayerMoveRight

.NotRightPressed
          rts

PlayerMoveLeft
          ldx #0
          jsr MoveSpriteLeft
          rts
          
PlayerMoveRight
          ldx #0
          jsr MoveSpriteRight
          rts

PlayerMoveUp
          ldx #0
          jsr MoveSpriteUp
          rts
          
PlayerMoveDown
          ldx #0
          jsr MoveSpriteDown
          rts

The sprite move routines are rather simple, update the position counter variables and set the actual sprite registers. A bit more complicated are the X move functions. If X reaches the wraparound, the extended x bit (the 9th bit) is looked up in a table and then added/removed.

;------------------------------------------------------------
;Move Sprite Left
;expect x as sprite index (0 to 7)
;------------------------------------------------------------
!zone MoveSpriteLeft
MoveSpriteLeft
          dec SPRITE_POS_X,x
          bpl .NoChangeInExtendedFlag
          
          lda BIT_TABLE,x
          eor #$ff
          and SPRITE_POS_X_EXTEND
          sta SPRITE_POS_X_EXTEND
          sta VIC_SPRITE_X_EXTEND
          
.NoChangeInExtendedFlag     
          txa
          asl
          tay
          
          lda SPRITE_POS_X,x
          sta VIC_SPRITE_X_POS,y
          rts  

;------------------------------------------------------------
;Move Sprite Right
;expect x as sprite index (0 to 7)
;------------------------------------------------------------
!zone MoveSpriteRight
MoveSpriteRight
          inc SPRITE_POS_X,x
          lda SPRITE_POS_X,x
          bne .NoChangeInExtendedFlag
          
          lda BIT_TABLE,x
          ora SPRITE_POS_X_EXTEND
          sta SPRITE_POS_X_EXTEND
          sta VIC_SPRITE_X_EXTEND
          
.NoChangeInExtendedFlag     
          txa
          asl
          tay
          
          lda SPRITE_POS_X,x
          sta VIC_SPRITE_X_POS,y
          rts  

;------------------------------------------------------------
;Move Sprite Up
;expect x as sprite index (0 to 7)
;------------------------------------------------------------
!zone MoveSpriteUp
MoveSpriteUp
          dec SPRITE_POS_Y,x
          
          txa
          asl
          tay
          
          lda SPRITE_POS_Y,x
          sta 53249,y
          rts  

;------------------------------------------------------------
;Move Sprite Down
;expect x as sprite index (0 to 7)
;------------------------------------------------------------
!zone MoveSpriteDown
MoveSpriteDown
          inc SPRITE_POS_Y,x
          
          txa
          asl
          tay
          
          lda SPRITE_POS_Y,x
          sta 53249,y
          rts  


Previous Step 3 Next Step 5

Attached Files








December 2016 »

S M T W T F S
    12 3
45678910
11121314151617
18192021222324
25262728293031