C64 Machine Language Game

I

n this session Darren had imported his sprites into the working game with a little bit of background to work with for collision detection. He also rearranged the code to make it more “structured”, since he stated he was “getting lost in it”. The sprite can also move left and right using the controller.

Code Structure explained

In the main loop there is a subroutine to update the timers (UpdateTimers), which is the frame counter. Then the joystick is read with ReadJoystick. Darren explained that “sets a variable to tell us which direction the player is going”. The variable is then used to move the player sprite in the appropriate direction based on which whether the left or right controller is being pressed.

Some of other subroutines called UpdatePlayerSpriteDirection and UpdatePlayerAnimationFrames was broken up for clarity. The UpdatePlayerSpriteDirection includes the idle animation, walk left, and walk right calls. “Then separate from there is the index for which frame we are on”, Darren explained.

He mentioned afterward that he noticed a bug that if the sprite changed direction we need to detect that the index of the animation frame can be reset back to zero so it starts over. There are other variables that check what the previous direction was to the current one. This confirms when it changes.

Next the code works with sprite 0’s direction. If there is a negative number that means the player is walking to the left. If it’s zero that is the idle animation. If it’s not negative or zero, then the player is walking to the right.

  • ldx #0

  • clc

  • lda SPRITE_DIRECTION, x

  • bmi @WalkLeft

  • beq @Idle

  • jsr WalkRight

Idle Animation Explained

The idle animation starts at 28. The code below sets the pointers for the sprite graphics to be associated with sprite 0. The overlay is sprite 1. The idle animation has 4 frames. The max frames is set before the player moves to track the total animation frames.

  • @Idleldx #0

  • lda #SPRITE_BASE + 28; idle Animation starts at 28

  • sta ANIM_START, x

  • ldx #1

  • lda #SPRITE_BASE + 32

  • sta ANIM_START, x

  • lda #4

  • sta MAX_FRAME

  • jmp @SetPointers

The WalkLeft subroutine has 7 frames of animation. These animation frames point to the starting section in the graphics file.

  • @WalkLeft

  • ldx #0

  • lda #SPRITE_BASE + 14

  • sta ANIM_START, x

  • ldx #1

  • lda #SPRITE_BASE + 21

  • sta ANIM_START, x

  • lda #7

  • sta MAX_FRAME

  • jmp @SetPointers

Other parts of the code keep track of the timer, and the frame counts. If there is an appropriate frame count then the animation is updated. Check to make sure it hasn’t exceeded the maximum frame, and if it has reset it.

Darren then explained that he had to “dumb down” the code to make it easier to follow since we were getting confused with the logic previously. He stated that if he gets more time he may rewrite it after trying to digest Siggy’s code some more. He admits that maybe he’s using a few extra variables, but its necessary for now to get the job done.

Checking for Ladder Collisions

C64 Machine Language GameSome characters were added to the screen, such as the ladder and a top platform. This was used to check for the ladder contacting the ladder to identify a collision. When the sprite is in front of the character, it then checks for an up and down movement on the ladder once it crosses over that background character. “The tiles are rope or ladder tiles”, Darren stated. It is important to check above and below the sprite to see if he has walked off the ladder.

We decided to work on the gravity to detect when the player sprite has walked completely off the ladder. This means that the sprite bits are no longer in contact with the background character, which is accomplished by doing a pixel check against the ladder.

Checking for blocked areas

I searched the code until I find the TestBlocking area. Darren explained this area. “If it’s blocking, the code is looking to see if the character is behind the sprite somewhere.” It that character is greater than 128, then the position is blocked. Characters above 128 depict that the sprite has encountered a wall, or something similar. It’s important to figure out which characters to use for ropes and ladders.

So we started working on the subroutine for applying the gravity, aptly named ApplyingGravity. The ldx #1 checks for sprite 0. The subroutine CanMoveDown returns 0 if the sprite can’t move down. Otherwise if it is not equal to zero then the sprite is able to move down.  The MoveSpriteDown subroutine is called twice since there are two sprites. ldx #0 calls sprite 0 and ldx #1 calls sprite 1 (front and foreground parts of the sprites).

  • ApplyingGravity

  • ldx #0

  • jsr CanMoveDown

  • bne @Skip

  • jsr MoveSpriteDown

  • ldx #1

  • jsr MoveSpriteDown

  • @Skip

  • rts

Next I ran the game to test the changes. The sprite was locked to the floor and could not move up or down, where previously the sprite could freely move in any direction and was not pinned to anything. Later we changed the Y (vertical) position of the sprite so he could free fall to the floor. This was done by altering the PARAM2 variable below to a higher place on the screen.

  • ; SETUP SPRITE 0 START POSITIION

  • lda #0

  • sta VIC_SPRITE_X_EXTEND ; clear extended bits

  • lda #3

  • sta PARAM1 ; Character column 10 (x coord)

  • lda #5

  • sta PARAM2 ; Character row 10 (Y coord)

  • ldx #0 ; Sprite # in X (0)

  • jsr SpriteToCharPos

  • ; apply to sprite 1 – color fill

  • inx

  • jsr SpriteToCharPos

After this we wanted to test the gravity from the top ledge. These were some simple tweaks, but necessary to ensure that the sprite would stop once they collided with the top platform. This just required relocating the sprite’s X position and the new changes appeared as follows.

  • ; SETUP SPRITE 0 START POSITIION

  • lda #0

  • sta VIC_SPRITE_X_EXTEND ; clear extended bits

  • lda #17

  • sta PARAM1 ; Character column 10 (x coord)

  • lda #5

  • sta PARAM2 ; Character row 10 (Y coord)

  • ldx #0 ; Sprite # in X (0)

  • jsr SpriteToCharPos

  • ; apply to sprite 1 – color fill

  • inx

  • jsr SpriteToCharPos

I wanted to see if we could implement a jump routine for our player sprite. Some investigation was needed. So I discussed the subroutines for JoyButton with Darren to see if this should be altered. He stated “you don’t want to change this. He sets a variable and you want to check that variable.” This subroutine can check if a button has been pressed.

  • JoyButton

  • lda #1 ; checks for a previous button action

  • cmp BUTTON_ACTION

  • bne @buttonTest

  • lda #0

  • sta BUTTON_ACTION

  • @buttonTest

  • lda #$10 ; test bit #4 in JOY_2 Register

  • bit JOY_2

  • bne @buttonNotPressed

  • lda #1 ; if it’s pressed – save the result

  • sta BUTTON_PRESSED ; and return – we wat a single press

  • rts ; so we need to wait for the release

Analyzing a process for jumping

The goal next was trying to figure out where we could possibly insert a jumping subroutine. Darren stated that the player should have an upward and downward trajectory to track where the player hits the arc at. If the button is released early then the player could fall. Ken chimed in, “You’re gonna need something like a look up table that gives you different Y offsets over time. So that it starts and ends in bigger increments. Then there is hang time at the top of the trajectory. So there are a couple of zero’s at the top. Then you have to consider if you can allow for a double jump.”

We then started discussing gravity a little more. Darren clarified that “this is more of a falling gravity constant. There is no acceleration. You fall for each terminal velocity. So technically, he should be moving slower at the beginning of the fall. Then the terminal velocity should be speeded up the closer he gets to the ground.”

Checking for the ladder collision proved to be more difficult. Since I wasn’t skilled with the code, I was throwing questions left and right to Darren. Siggy setup the code to grab the character behind the sprite and call the subroutine TestBlocking. The code was currently checking the direction in the left and right direction, so Darren suggested not making changes to the logic here. Instead he said “In the up/down the test blocking is different because everything is blocked unless it’s a rope or a ladder. In the example below however, everything is blocked unless its’ under 128.

  • TestBlocking

  • cmp #128 ; is the character > 128?

  • bpl @blocking

  • lda #0

  • rts

  • @blocking

  • lda #1

  • rts

CBM Prg Studio Character Editor“So rather than TestBlocking you want a TestLadder or TestRope subroutine embedded,” Darren stated. So it was suggested to make a new file and call it spelunk_collision_routines. So going forward we had to check for the ladder character.

This is contained inside the Character Editor built into CBM Prg Studio. Once I had it open, Darren said to uncheck the selection for Reverse at the top right of the window. The character was then accessible, which was 191.  This is known in the editor as the Screen Code. At this time though, Darren was already falling asleep as the streams go late into the night. He mentioned that he would need to review the code further before moving ahead and having a plan would be idea. So we were eventually going to wrap up for the night. After filtering some code segments out, Darren directed me to comment out TestBlocking and replacing that with TestLadder.  Google Hangouts crashed due to a Windows update before we could see the results. Such is the day in the life of programmers.

  • TestLadder

  • cmp #127

  • bne @NoBlocking

  • ldx #0

  • rts

  • @LadderLookup

  • ldx #1

  • rts