diff options
Diffstat (limited to 'src/player.s')
| -rw-r--r-- | src/player.s | 317 |
1 files changed, 317 insertions, 0 deletions
diff --git a/src/player.s b/src/player.s new file mode 100644 index 0000000..cee79cf --- /dev/null +++ b/src/player.s @@ -0,0 +1,317 @@ +.segment "CODE" + +;; Functions and variables that keep up with the player's sprite. That is, +;; movement, heading, animation, collision with the environment, etc. +.scope Player + ;;; + ;; *Movement* is done with subpixel precision. Hence, we need to handle + ;; things via fixed-point arithemetic. In particular, the `zp_screen_*` + ;; variables refer to pure screen coordinates, and they are the only uint8_t + ;; variables. As for the rest: + ;; + ;; - Velocity (actual and target) are 4.4 fixed-point signed values. Hence, + ;; the high nibble represents the signed integer part, and the low nibble + ;; the fractional one. + ;; - Each Position is a 16-bit signed fixed-point value in little-endian + ;; format like so: |llll/ffff| - |0000/hhhh|. That is, the low byte is + ;; split with the fractional part and the low nibble of the integer part, + ;; and the high byte only contains the high nibble of the signed integer + ;; part (and the high nibble of that high byte is discarded). + ;; + ;; This has the following properties: + ;; + ;; - Adding the velocity to a position is just a matter of performing an + ;; `adc` between the low byte of the position and the velocity. + ;; - Translating positions into screen coordinates it's a matter of simply + ;; rolling the low nibble of the high byte into the low byte. + + INIT_Y_POSITION_LO = $80 + INIT_Y_POSITION_HI = $0C + INIT_Y_VELOCITY = $08 + + INIT_X_POSITION_LO = $00 + INIT_X_POSITION_HI = $04 + + THROTTLE = $D8 + BLAST_OFF = $F8 + GRAVITY = $28 + + UPPER_LIMIT = 10 + GROUND_LIMIT = 200 + + zp_screen_y = $40 + zp_position_y = $41 ; NOTE: 16-bit. + zp_target_velocity_y = $43 ; TODO: needed? + zp_velocity_y = $44 + + zp_screen_x = $45 + zp_position_x = $46 ; NOTE: 16-bit. + zp_target_velocity_x = $48 ; TODO: needed? + zp_velocity_x = $49 + + ;; Initialize the player's sprite. Note that for the sprite to look + ;; correctly on screen you still need to call `Player::update` afterwards. + .proc init + ;; Reset velocity + lda #$00 + sta zp_target_velocity_y + sta zp_velocity_y + sta zp_target_velocity_x + sta zp_velocity_x + + ;; Set position, and the screen coordinates will be updated upong + ;; calling `update`, which on initialization will happen right after. + lda #INIT_Y_POSITION_LO + sta zp_position_y + lda #INIT_Y_POSITION_HI + sta zp_position_y + 1 + lda #INIT_X_POSITION_LO + sta zp_position_x + lda #INIT_X_POSITION_HI + sta zp_position_x + 1 + + ;;; + ;; TODO: this should really just go away + + lda #$00 + sta $201 + lda #$00 + sta $202 + + lda #$01 + sta $205 + lda #$00 + sta $206 + + lda #$10 + sta $209 + lda #$00 + sta $20A + + lda #$11 + sta $20D + lda #$00 + sta $20E + + lda #$20 + sta $211 + lda #$00 + sta $212 + + lda #$21 + sta $215 + lda #$00 + sta $216 + + rts + .endproc + + .proc update + jsr update_vertical_position + + ;; TODO: horizontal + + ;; At this point all positions are clear, transform them into screen + ;; coordinates, eject out from boundaries and platforms, and update the + ;; sprite with the new state. + jsr position_to_screen + jsr bound_check + JAL update_sprite + .endproc + + ;; Updates the `zp_velocity_y` and the `zp_position_y` depending on whether + ;; the player is throttling or gravity should just apply. + .proc update_vertical_position + ;; Check if the player is asking to throttle, otherwise apply gravity. + lda #(Joypad::BUTTON_UP | Joypad::BUTTON_A) + and Joypad::zp_buttons1 + beq @set_gravity + lda zp_velocity_y + beq @blast_off + lda #THROTTLE + bne @compute_vertical + @set_gravity: + lda #GRAVITY + + @compute_vertical: + sta Globals::zp_tmp0 + + ;; Check the difference between the given target velocity and what we + ;; have now. If it equals to zero, then we change nothing in regards to + ;; the velocity. + lda zp_velocity_y + sec + sbc Globals::zp_tmp0 + beq @apply_velocity + + ;; Increase or decrease depending on what we have now. + ;; TODO: inc/dec might not quite cut it in NTSC vs PAL + bmi @down + dec zp_velocity_y + jmp @apply_velocity + @down: + inc zp_velocity_y + jmp @apply_velocity + + @blast_off: + lda #BLAST_OFF + sta zp_velocity_y + jmp @going_up + + @apply_velocity: + lda zp_velocity_y + bmi @going_up + + ;; The velocity is positive, so it's just a 16-bit addition. + clc + adc zp_position_y + sta zp_position_y + lda #0 + adc zp_position_y + 1 + sta zp_position_y + 1 + rts + + @going_up: + ;; Negative velocity, we need to go up. This is probably not optimal, + ;; but we just invert the number and subtract with that. + lda #0 + sec + sbc zp_velocity_y + sta Globals::zp_tmp0 + lda zp_position_y + sec + sbc Globals::zp_tmp0 + sta zp_position_y + lda zp_position_y + 1 + sbc #0 + sta zp_position_y + 1 + + rts + .endproc + + ;; Convert the positions with subpixel precision into mere screen + ;; coordinates. That is, update the values on `zp_screen_{x,y}` given the + ;; current values of `zp_position_{x,y}`. + .proc position_to_screen + ;; We save the high byte into a temporary value, and we load the low + ;; byte into the accumulator. + lda zp_position_y + 1 + sta Globals::zp_tmp0 + lda zp_position_y + + ;; And now it's a matter of rotating the high byte into the low one to + ;; match a full byte. + lsr Globals::zp_tmp0 + ror + lsr Globals::zp_tmp0 + ror + lsr Globals::zp_tmp0 + ror + lsr Globals::zp_tmp0 + ror + + ;; Ecce Y coordinates. + sta zp_screen_y + + ;; And the same for the X coordinates. + lda zp_position_x + 1 + sta Globals::zp_tmp0 + lda zp_position_x + + ;; And rolling... + lsr Globals::zp_tmp0 + ror + lsr Globals::zp_tmp0 + ror + lsr Globals::zp_tmp0 + ror + lsr Globals::zp_tmp0 + ror + + ;; Ecce X coordinates. + sta zp_screen_x + + rts + .endproc + + ;; Check on whether the player is out of bounds in any way and provide an + ;; ejection logic for each situation. + .proc bound_check + ;; Are we at the top? + ;; TODO: actually buggy, but nevermind for now + lda zp_screen_y + cmp #UPPER_LIMIT + beq @too_high_icarus + bcs @check_ground + @too_high_icarus: + lda #0 + sta zp_velocity_y + lda #UPPER_LIMIT + sta zp_screen_y + rts + + ;; Nope, are we at the ground? + @check_ground: + cmp #(GROUND_LIMIT - 24) + bcc @above_ground + + ;; We appear to be either at the ground or below it (e.g. we are + ;; standing still but initial gravity is pulling us down). In this case, + ;; just reset the Y velocity and the Y position. + lda #0 + sta zp_velocity_y + lda #(GROUND_LIMIT - 24) + sta zp_screen_y + lda #$F0 + and zp_position_y + sta zp_position_y + rts + + ;; Nope, let's check for the platforms. + @above_ground: + ;; TODO: notice how ground and top are just cases on the general + ;; "collision up/down". Next commits will merge these logics. + + rts + .endproc + + .proc update_sprite + ;; TODO: + ;; - Update heading + ;; - Update motion state + ;; - Update tiles + + jsr update_sprite_coordinates + + rts + .endproc + + ;; Update the coordinate for the six sprites that make up the player. + .proc update_sprite_coordinates + ;; Y axis. + lda zp_screen_y + sta $0200 + sta $0204 + clc + adc #8 + sta $0208 + sta $020C + clc + adc #8 + sta $0210 + sta $0214 + + ;; X axis. + lda zp_screen_x + sta $0203 + sta $020B + sta $0213 + clc + adc #8 + sta $0207 + sta $020F + sta $0217 + + rts + .endproc +.endscope |
