Jump to content
IGNORED

What efficient ways can you set player speed based direction?


Recommended Posts

ProcessJoystick:

    lda SWCHA
    sta Temp     ; store Joysticks for later,
           ; going go be trashing the accumulator

    ldx #0    ; joystick counter

PJLoop:
    cpx #1
    beq LowNibble ; J1 already has bits in correct position
    lsr           ; J0 needs to shift bits right 4 times
    lsr
    lsr
    lsr 

LowNibble:
    and #%00001111 ; we only want the low nibble
    xor #%00001111 ; flip bits so 1=direction held
    
    scb            ; set carry bit? necessary?
    sbc #3
    bmi MedSpeed   ; must be going vertically
    sbc #2         ; all directions less than 5 aren't diag
    bmi FastSpeed  ; probably going horizontally
    sbc #3         ; subtract 8 total
    beq FastSpeed  ; going Right
    ; otherwise ...
    ; going diagonally
    lda Temp
    pha     ; push it onto the stack
    lda Frame      ; get the current frame
    and #7         ; if 8th frame,
    beq SlowMovement   ; branch to rest of handler, like Collect Tutorial
    pla   ; pull off stack
    lsr
    lsr
    lsr
    lsr
    jmp NextJoystick
MedSpeed:
    lda Temp       ; restore Joystick
    pha
    lda Frame
    and #3
    beq SlowMovement
    pla
    lsr
    lsr
    lsr
    lsr
    jmp NextJoystick

SlowMovement:
    pla   ; pull back from stack

FastSpeed:
NormalJP:
CheckRight
    asl
    bcs CheckLeft
    ldy ObjectX,x
    iny 
    <compare with Arena size>

CheckLeft:
     asl
     bcs CheckDown
      ...
CheckDown:
      ...
CheckUp:
    ...
NextJoystick:
    inx
    cpx #2
    bne PJLoop
    
    rts


If I wanted to set the player speed based upon which direction the player is moving, how best to do that?

 

Horizontally (fastest speed)

Vertically (medium speed)

Diagonally (slowest speed)

 

I know of the Fractional/Sub-pixel movement. This doubles the RAM requirements for X,Y positions for each object. Essentially, you do 16-bit math on the positionX (onscreen) and the fractionalX (sub-pixel), and when the carry overflows into the onscreen Y position (P0_YPosFromBottom+1), the actual pixels get updated.  I am still not sure In the example code, how I would modify it to identify the difference between moving vertical and moving vertically+horizontally simultaneously (diagonally).

 

https://alienbill.com/2600/cookbook/subpixel.html

 

; for up and down, we INC or DEC
; the Y Position
;joystick down?
lda #%00010000
bit SWCHA
bne DoneMoveDown

;16 bit math, add both bytes
;of the ghost speed constant to
;the 2 bytes of the position
clc
lda P0_YPosFromBot
adc #<C_GHOST_SPEED
sta P0_YPosFromBot
lda P0_YPosFromBot+1
adc #>C_GHOST_SPEED
sta P0_YPosFromBot+1
DoneMoveDown

lda #%00100000 ;Up?
bit SWCHA
bne DoneMoveUp

;16 bit math, subtract both bytes
;of the ghost speed constant to
;the 2 bytes of the position
sec
lda P0_YPosFromBot
sbc #<C_GHOST_SPEED
sta P0_YPosFromBot
lda P0_YPosFromBot+1
sbc #>C_GHOST_SPEED
sta P0_YPosFromBot+1
DoneMoveUp

 

In the Collect Tutorial, the speed is modified by reading the joystick button. It uses a similar approach to handle directional movement as the example above. It left-shifts the SWCHA bits into the carry flag, and then processes each individual direction. Instead of setting the speed via a fractional X/Y position, it checks the current frame and only processes movement if the frame is divisible by 8 (about every 133 millisecond )

 

 

 

 

 

My guess is that before I start processing the individual directions RLUDxxxx, I need to determine if the player is moving Diagonally, Vertically, or Horizontally.

 

Since the Atari sets bits as 0 when that direction is held (which does seem backwards), I would need to read SWCHA, store it (so it can be restored when processing the other joystick), set a counter for JoystickNo (X reg), shift-right if processing J0, then AND it with the P1 bitmask (P1_MASK = #%00001111, then XOR with the same mask to get bit=1 (true) for holding that direction.

 

LowerNibble possible Joystick Values, (after AND + XOR)

#%1010 - 10, RU-diag, slow speed

#%1001 - 9, RD-diag, slow speed

#%0110 - 6, LU-diag, slow speed 

#%0101 - 5, LD-dish, slow speed

 

#%1000 - 8, R, fast speed

#%0100 - 4, L, fast speed

#%0010 - 2, U, med speed

#%0001 - 1, D, med speed

#%0000 - 0, not moving

(Other impossible combinations, without special controllers)

 

At this point I see two possibilities

 

Poss1 - I should be able to use the lower nibble as a pointer offset to a speed table of possible speeds. (Not sure if I need a table that is 16 bytes or 11 bytes. 11 is the maximum when dealing with normal controllers since some joystick combinations aren’t possible). The table could have fractional speeds (so once again 2-bytes for each speed (20 total ROM bytes), and 4-bytes for each object. (X,Y and fractional for each).

 

Poss2 - I can do what was done in the Collect Tutorial. Instead of saving the speed values, check the current frame and only process joysticks on frames divisible by some number (2^x-1 for best results).  I can have conditional branches that first check if we are moving Up/Down (conveniently, this means low-nibble < 3) {only process every 4th frame}, then check if low-nibble is >4 (all low-nibbles 5 or greater are diagonal with the exception of Right = 0x08) and not exactly 8 {only process every 8th frame}, all other conditions {process every 2nd frame}. This should conveniently handle impossible joystick combinations. 

 

Code at TOP (seems to be full of errors. I must not be pushing/popping from the stack properly. I also seem to be controlling P0/P1 with the same controller)

 

 

Edited by CapitanClassic
Link to comment
Share on other sites

I would use the 4 bit joystick value as an index into a direction/speed table.  Something like this:

 

    lda SWCHA
    lsr
    lsr
    lsr
    lsr ; now have a value from 0-15 for the left player    
    
    asl ; times 2 to look up data in a Word table
    tax
    
    lda DirSpeedX,x
    ; do 16 bit add to current X position here
    lda DirSpeedX+1,x
    ; do 16 bit add to current X position here

    
    lda DirSpeedY,x
    ; do 16 bit add to current Y position here
    lda DirSpeedY+1,x
    ; do 16 bit add to current Y position here

    
DirSpeedX:            ;  RLDU
    .word 0           ; %0000 = invalid, all directions held
    .word 0           ; %0001 = invalid, Right & Left held at same time
    .word 0           ; %0010 = invalid, Right & Left held at same time
    .word 0           ; %0011 = invalid, Right & Left held at same time
    .word 0           ; %0100 = invalid, Down & Up held at same time
    .word SLOW        ; %0101 = Right + Down
    .word SLOW        ; %0110 = Right + Up
    .word FAST        ; %0111 = Right
    .word 0           ; %1000 = invalid, Down & Up held at same time
    .word -SLOW       ; %1001 = Left + Down
    .word -SLOW       ; %1010 = Left + Up
    .word -FAST       ; %1011 = Left
    .word 0           ; %1100 = invalid, Down & Up held at same time
    .word MEDIUM      ; %1101 = Down
    .word -MEDIUM     ; %1110 = Up
    .word 0           ; %1111 = no direction held
    
DirSpeedY:
;    Similar to DirSpeedX
    .word             ; %0111

 

If you're not using direction, such as for an endless runner game, then drop the - sign on the Left and Up entries.

 

To use that for the right player you'd do this to get the index:

    lda SWCHA
    and #$0F
    asl
    tax
    ...

 

  • Thanks 1
Link to comment
Share on other sites

Thanks! @SpiceWare, I really appreciate the Collect tutorials, and I am looking forward to the newest Arena blog post for CDFJ Collect 3. I am going to try a small game first in 4K/8K, and probably hit some limitations with regard to available time in the kernel. 

 

Speaking of which, Collect is a great example of DoDraw and a 2LK.

I haven't been able to find a good tutorial for other Kernel strategies such as SwitchDraw, SkipDraw, (others?)

 

Your post here leads me to believe that SkipDraw uses two pointers, one to a ROM mask (which would have to be the size of the Arena) that blanks out the player sprite by  setting the pointer such that the mask is 0x00 when the player Sprite isnt being displayed. The kernel is always drawing the player in the Arena, it is just being blanked out when the Y-value of the Arena doesn't match the Y-value of the player (plus player_height).

 

SwitchDraw on the other hand can only be used for short Arenas, because the max Y value is 0×7F (127), but doesn't require the large bit mask. 

 

I am trying to draw a dynamic asymmetric playfield (similar to Cosmic Swarm, where playfield bits can be turned on/off dynamically during gameplay), which requires writes to both left and right sides of the playfield (minimum cycles, 6 PFx writes (18) + LDA (18)  = 36 cycles). I also want to update both players, both missiles, and ball but only need 2LK accuracy. Is this even possible within 8k/128bytes, or am I going to have to either go with a reflected playfield or CDFJ?

 

 

Link to comment
Share on other sites

Haven't looked into the other methods, so haven't tracked down tutorials. The Tricks page at MiniDig links to @Thomas Jentzsch's skip draw routine from the Stella Mailing List days: https://www.biglist.com/lists/stella/archives/200102/msg00282.html

 

And here's @DEBRO explaining how it works: https://www.biglist.com/lists/stella/archives/200309/msg00056.html

 

Might be able to - the castle kernels in Medieval Mayhem do this on every scanline:

  • updates left PF0, left PF1, right PF1, right PF0
  • updates ball, and both missiles
  • updates both players

 

Every-other scanline it reads 2 paddles (23 cycles), and on some scanlines it applies a texture to the playfield (8-14 cycles).

 

The players are within zones, so updates in 8 cycles using lda (shieldptr),y and sta grp0 with zero padded graphics.

 

I also unrolled the kernel, which eliminated the loop overhead at the expense of ROM.  I do wish I had a handle on using macros for the unrolling, it would have saved a lot of typing. I did the same unrolling in Stay Frosty, and for that I did use macros so the game kernel looks like this for the 5 zones:

 

KernelCode

        SECTION 4
        SECTION 3
        SECTION 2
        SECTION 1
        SECTION 0

 

A brief snippet of the SECTION macro, the ... represents about 250 more lines of code:

 

; usage SECTION #
; # = 0 thru 4 for 5 sections
; level 0 is lowest section and has special condition
; of being solid in the platform area.
        MAC SECTION
.SEC    SET {1}
        ldy Heights+.SEC                ;    load Y with height of current section
        lda #ICE_TOP_COLOR
        sta COLUPF
        lda (FrostyImagePtr+.SEC*2),y
        and (FrostyMaskPtr+.SEC*2),y
        tax
        lda (FrostyColorPtr+.SEC*2),y
        tay
 if .SEC = 4
        sta HMCLR
 endif        
        lda FireBallX+.SEC*2
        bne .repositionFireball
        sta WSYNC
        stx GRP0
        sty COLUP0
        sta WSYNC

...

        ENDM

 

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...