Jump to content
IGNORED

Maintaining P0 horizontal position after previous RESP0


johnnystarr

Recommended Posts

I'm working on a shooter style game and I'm a bit stuck on my understanding of relative sprite positioning. The top of the screen has a 48 pixel score, and the bottom has P0 which can move left and right.

 

In order to place the score properly, I have to RESP0 and RESP1. I use HMOVE to fine position as well.

 

At the bottom of the screen, my P0 kernel has a RESP0 as well that only happens the first frame so that I can have the player start in the center. After that, P0 is able to move left and right as needed. This was working fine until I added the 48p score.

 

The problem I'm facing, is that when RESP0 is fired in the score kernel, it wipes out the H position of P0 at the bottom.

 

Is this a scenario where hardware positioning is rendered moot? Will I need to keep track of the players last X coordinate, and then RESP0 on each frame as well as HMOVE to get me back to where P0 would have been (give or take movement) ?

 

I'm hoping there is something in the TIA / Stella manual I missed.

post-36643-0-81247500-1395942818_thumb.png

Link to comment
Share on other sites

Yes keep track X-pos and reposition each frame. One other pitfall is clearing HMxx registers too early after HMOVE.

 

That seems a bit icky. So in my setup scan-line, I'll need to use a delay, trigger RESP0, and then HMOVE to finetune? So far I've only used a general delay that gets me approximately center. I guess I'll need to write a more sophisticated horizontal position routine so that I can get it perfect.

Link to comment
Share on other sites

No, it's really not too bad.

 

 

You can position your sprite after finish drawing the score display. This will do it for you:

;X=0 P0
;X=1 P1
;X=2 M0
;X=3 M1
;X=4 BL

;A = horizontal position, 0 to 159

    sec
    sta    WSYNC
;---------------------------------------
.divideBy15:
    sbc    #15
    bcs    .divideBy15
    eor    #7
    asl
    asl
    asl
    asl
    sta.wx HMP0,X
    sta    RESP0,X
    sta    WSYNC
;---------------------------------------
    sta    HMOVE

.

 

 

 

Link to comment
Share on other sites

That is an elegant solution my friend...

 

It appears that I would need a minimum of 2 scanlines to prep the sprites. The 1st to load the x position into A, the second to perform this routine. That's a pretty decent compromise vertically. I can't help but expound on this in relation to my proof-of-concept for my vertical shooter. I've attached a mock-up that details my overall goal.

 

The part I'm a bit perplexed by is the missiles. In the picture, the red sprites are P0 and yellow are P1. The two blue lines represent the 2 scanlines needed to prep each mini kernel, which is sectioned in the red lines. My POC currently has a missile that fires up, but the main game area is currently just one kernel.

 

Now that I'm going to be adding mini-kernels for each variation of enemies, the missile is a bit tricky. So far, from what I can tell:

 

I will continue to increment M0's Y position once per frame. Since there is a finite amount of mini-kernels or segments, I will need to also keep track of which segment the missile has entered. Once M0_Y has been incremented to the end top line of the segment, it then increments the segment counter as well. So far so good right?

 

Problem:

 

The issue I'm facing is that there won't be time to draw the missile during the 2nd reposition scanline. The 1st reposition scanline should have enough time to load the X postion, as well as the segment position and then draw the missile. I suppose if I mapped it all out perfectly, I could update the missile every 2 scanlines instead. I noticed that "Riddle Of The Sphinx" has a similar layout, it appears to update the missile every 2 scanlines as well.

 

I'm hoping that this made sense to you guys. Perhaps I'm going about this the wrong way.

 

 

 

post-36643-0-04275500-1396022896_thumb.png

Link to comment
Share on other sites

One thing you can do to reduce the overhead, if you have a number of items to reposition at once, is take the HMOVE out of that. You'd then call PosObject for all the objects, then do a single HMOVE to fine-position all objects at once.

This snippet is from Draconian's source:

PosObject:              ; A holds X value
        sec             ; 2  
        sta WSYNC       ; X holds object, 0=P0, 1=P1, 2=M0, 3=M1, 4=Ball
DivideLoop:
        sbc #15         ; 2  
        bcs DivideLoop  ; 2  4
        eor #7          ; 2  6
        asl             ; 2  8
        asl             ; 2 10
        asl             ; 2 12
        asl             ; 2 14
        sta.wx HMP0,X   ; 5 19
        sta RESP0,X     ; 4 23 <- set object position
SLEEP12: rts             ; 6 29
      ; 6 29

...
PositionMMobjects:
        ldx #3   
pmmoLoop:   
        lda MMobjectPosition,x
        jsr PosObject
        dex
        bpl pmmoLoop
        sta WSYNC
        sta HMOVE   ; fine tune all X positions

...

MMobjectPosition:
        .byte $38   ; P0 for menu
        .byte $40   ; P1 for menu
        .byte 17    ; Missile 0 - to hide MM logo garbage
        .byte 105   ; Missile 1 - to hide MM logo garbage            

In that code I'm positioning the players and missiles to display the main menu. Since the objects don't move MMobjectPosition is in ROM, but it could just as easily be stored in RAM.

Edited by SpiceWare
Link to comment
Share on other sites

That SLEEP12: rts line is also used with JSR for a space saving delay.

 

The loop that draws the score/radar display in Draconian uses it to delay the updates for PF1 and PF2 on the right side of the screen. Another way to do a delay of 12 cycles is to use the SLEEP macro, which will use 6 bytes of ROM by inserting 6 NOP commands into your code for a 12 cycle delay. In contrast, jsr SLEEP12 uses 3 bytes of ROM - every saved byte adds up.

ScoreLoop:
        sta WSYNC
        lda #<DS_PF0L           ; 2  2
        sta PF0                 ; 3  5
        sta ENABL               ; 3  8 - on VDEL
        lda #<DS_PF1L           ; 2 10
        sta PF1                 ; 3 13
        lda #<DS_PF2L           ; 2 15
        sta PF2                 ; 3 18
        lda #<DS_GRP0           ; 2 20
        sta GRP0                ; 3 23 - on VDEL
        lda #<DS_GRP1           ; 2 25
        sta GRP1                ; 3 28 - updates GRP0 and BL as well
        lda #<DS_PF0R           ; 2 30
        sta PF0                 ; 3 33 - PF0R, 28-49
        jsr SLEEP12             ;12 45
        lda #<DS_PF1R           ; 2 47
        sta PF1                 ; 3 50 - PF1R, 39-54
        lda #<DS_PF2R           ; 2 52
        sta PF2                 ; 3 55 - PF2R, 50-65
        dec LoopCounter         ; 5 60
        bne ScoreLoop           ; 2 62
Link to comment
Share on other sites

The missiles and/or ball could be positioned beforehand since the score display is only using the 2 player sprites. Another feature that exists for missile sprites is that they can be "hidden and centered" on their corresponding player sprite by writing bit1 to RESMP0/RESMP1 (ram $28-$29) i.e. if your game features "steerable missiles".

Link to comment
Share on other sites

Another feature that exists for missile sprites is that they can be "hidden and centered" on their corresponding player sprite by writing bit1 to RESMP0/RESMP1 (ram $28-$29) i.e. if your game features "steerable missiles".

Very few games ever made use of these registers. Off hand I can only think of Combat using them, but there's probably more.

 

 

I am making use of RESMP1 in CAA. :D Long story short the situation is I found out that early HMOVE's messed up my paddle reads big time. :( I'm not sure if the same is true for a standard HMOVE, but in any case I don't want to have any HMOVE bars so I'm not using them.

 

 

I quickly ran into a situation where I need to position M1 on the right side of the playfield to make up the middle line of the platform, but it doesn't line up quite right using RESM1. I found that if I used RESP1 and then RESMP1 that I could achieve the exact position I wanted. :)

 

 

One of my favorite positioning tricks is positioning and drawing on the same scanline. I have never actually done this in a kernel, but it can be useful in a situation where you need to draw something immediately, and the object you will use to draw is currently more to the right of where you want it. Simply use a two copies mode, and reposition the sprite before it gets drawn. On the screen you see one sprite appear (the copy), and on the same scanline you positioned it on! The downside is you have to repeat the re-positioning on every scanline afterward, but there at least there are no HMOVE bars. Quick demo:

 

OneSprite.zip

 

 

Conceivably this method could be used if the kernel had a constraint of P0 on one side (say leftside) and P1 on the other. You could have three indirect jumps in the kernel (one for each sprite, and one in between them to align cycles between left sprite and right sprite). Your kernel would consist of reloading the sprites pointer, and position pointer after it was drawn (for either side). The kernel itself could rotate the sprites in a circle.

;Start
      P0    P1
      
     P0      P1
     
    P0        P1
    
     P0      P1
     
      P0    P1
      
      
;Try to imagine sprites overlapping here...
          P0   
      P0      
             P1
     P0        
              P1
     P0      
             P1
      P0     
          P1

Anyhow, this is just being creative, and I'm not here to debate this idea's effectiveness or what saves the most bytes. ;)

Edited by Omegamatrix
Link to comment
Share on other sites

PosObject:              ; A holds X value
        sec             ; 2  
        sta WSYNC       ; X holds object, 0=P0, 1=P1, 2=M0, 3=M1, 4=Ball
DivideLoop:
        sbc #15         ; 2  
        bcs DivideLoop  ; 2  4
        eor #7          ; 2  6
        asl             ; 2  8
        asl             ; 2 10
        asl             ; 2 12
        asl             ; 2 14
        sta.wx HMP0,X   ; 5 19
        sta RESP0,X     ; 4 23 <- set object position
SLEEP12: rts             ; 6 29
      ; 6 29         

 

Maybe I'm missing something here... The DivideLoop would be a variable length of cycles. I updated my project to utilize this type of routine and it works great. Or did until I made it a subroutine. I've found that any position number past #140 delays my sprite kernel by 1 scanline. So, as my sprite moves to the right once I get to position #140 it throws everything off.

 

If 140 is divided by 15, that equals 9 iterations. If each iteration of the loop is 5 cycles, this would be 45 cycles. Now, if we add 45 to the cycle counts of this routine (minus 3) it equals 74. That means, when we return to the segment that called this subroutine, we would wouldn't even have enough time to strobe WSYNC. Obviously, I am strobing WSYNC and that is why sprite kernel is being delayed an additional scanline.

 

Is it just that your code doesn't need to reposition that far to the right, or have I overlooked something?

 

Thanks.

 

EDIT:

 

I take it I could mitigate this imbalance by restricting the range of motion of player 0. If I have to, I can get rid of the JSR / RTS paring and save 12 cycles. However I would prefer a solution that accommodates all 160 positions.

Edited by johnnystarr
Link to comment
Share on other sites

That's how it works.

Ah, looking back at your first post you're probably trying to use it mid-screen. I find it works best to put the score at the bottom of the screen because the X positions don't change and thus both can be easily be repositioned on a single scanline.

If you look in my source for Frantic and look for KernelDone: you'll find this:

KernelDone:
...
        ldy #$CF                ; 2 35
        sty HMP0                ; 3 38
        sta RESP0               ; 3 41
        sta RESP1               ; 3 44
        iny                     ; 2 46
        sty HMP1                ; 3 49
        ldy #3                  ; 2 51
        sty NUSIZ0              ; 3 54
        sty NUSIZ1              ; 3 57
        sty VDELP0              ; 3 60
        sty VDELP1              ; 3 63
        ldy #8                  ; 2 65
        sty LoopCounter         ; 3 68
        SLEEP 2                 ; 2 70
        sta HMOVE               ; 3 73

RESP0 and RESP1 are strobed at cycles 41 and 44 on the same scanline. The HMxx registers only use the upper nybble, so HMP0 gets $C0 while HMP1 gets $D0 after the INY. Finally HMOVE is hit at cycle 73 so both sprites are repositioned to display the score.

Link to comment
Share on other sites

That's how it works.

 

Ah, looking back at your first post you're probably trying to use it mid-screen.

 

Not sure what you mean by 'mid-screen'. Here's the entry point of my code:

            STA    WSYNC                ; begin new scanline
            ;-------------------------------------------------------------
            ;  Sprite Setup (Pre-Kernel) - 2 'slave' scanlines 
            ;-------------------------------------------------------------             
SetupP0:
            
            ; prep for Repositioning
            LDA     P0_X                ; load tracked x position
            LDX     #0                  ; load X=0, reposition P0
            SEC                         ; prepare carry for divideBy15
            JSR     RepositionObject
            
            STA     WSYNC
            ;-------------------------------------------------------------
            ; Sprite Setup complete, we just need to strobe HMOVE
            ;-------------------------------------------------------------  
            STA     HMOVE
            LDX     #15                 ; reassign X for sprite height 
P0Kernel:
            LDA     Player0,X
            STA     GRP0
            DEX
            STA     WSYNC
            BNE     P0Kernel


; heres what the subroutine looks like:
            
RepositionObject:   SUBROUTINE            
            STA     WSYNC
.divideBy15:
            SBC    #15                  ; 2 (2)
            BCS    .divideBy15          ; 3 (4)
            EOR    #7                   ; 2 (6)
            ASL                         ; 2 (
            ASL                         ; 2 (10)
            ASL                         ; 2 (12)
            ASL                         ; 2 (14)
            STA    HMP0,X               ; 5 (19)
            STA    RESP0,X              ; 4 (23)
            RTS                         ; 6 (29)

I'm just using this code to reposition my player at the bottom of the screen. I removed the score kernel until I've worked out a few kinks. Here's two screen shots, one where my player starts off in the center (position #80) and one where I start on the far right (position #140)

 

As you can see, position #140 overlaps into a new scanline. However, if I removed the WSYNC right after the 'JSR RepositionObject' my HMOVE wouldn't be exactly at the beginning of a new scanline.

post-36643-0-47047700-1396292822_thumb.png

post-36643-0-99820500-1396292822_thumb.png

Edited by johnnystarr
Link to comment
Share on other sites

Sorry, was overtired that day after the 2 week marathon sessions for Draconian. For sprite reuse you won't be able to use jsr PosObject due to the scanline count not being consistent. You can either do the routine in-line, just make sure the bcs does not cross a page boundary, or use something like this which would let you update other TIA registers while doing a reposition.

Link to comment
Share on other sites

 

Can you elaborate? - it doesn't sound like it was just that you wanted to use those cycles, does it somehow mess with the reading?

It was messing with the reading.

 

 

It was a problem that didn't show up at all in emulation. I'm still not sure what the exact problem was, but I believe it is a TIA problem. I wrote a routine that would position and HMOVE a sprite in 1 scanline with an early HMOVE at cycle 74. Sorry about the length of this but here is the code:

    ldx    jumperHpos            ;3  @44
    lda    PositionTab,X         ;4  @48


; -5 = RESP0
; -4 = RESP1
; -3 = RESM0
; -2 = RESM1
; -1 = RESBL

    ldx    #-4                   ;2  @50
    sta    HMP0+5,X              ;4  @54
    and    #$0F                  ;2  @56
    tay                          ;2  @58
    lda    JumpTab,Y             ;4  @62
    sta    tB_JumpRowDelay       ;3  @65
    lda    #>postion_3           ;2  @67
    sta    tB_JumpRowDelay+1     ;3  @70
    lda    #NO_MO_74             ;2  @72
    nop                          ;2  @74
;---------------------------------------
    sta    HMP0                  ;3  @1
    pla                          ;4  @5
    sta    GRP0                  ;3  @8

waitPos:
    dey                               ;2  @10
    sbpl   waitPos                    ;2³ @12/13
    jmp.ind (tB_JumpRowDelay)         ;5  @17    jump into table below


postion_3:
    sta   RESP0+5,X
    .byte $1C       ;  nop  $1595,X (rom space), skip next two bytes
postion_15:
    sta   RESP0+5,X
    .byte $1C
postion_30:
    sta   RESP0+5,X
    .byte $1C
postion_45:
    sta   RESP0+5,X
    .byte $1C
postion_60:
    sta   RESP0+5,X
    .byte $1C
postion_75:
    sta   RESP0+5,X
    .byte $1C
postion_90:
    sta   RESP0+5,X
    .byte $1C
postion_105:
    sta   RESP0+5,X
    .byte $1C
postion_120:
    sta   RESP0+5,X
    .byte $1C
postion_135:
    sta   RESP0+5,X
    .byte $1C
postion_150:
    sta   RESP0+5,X
    sta   HMOVE                  ;3  @74
    ldx    #0                    ;2  @76
;--------------------------------
    bit    INPT0                 ;3  @3
    stx    GRP0                  ;3  @6    clear
    bmi    .noPaddle1            ;2³ @8/9
    inx                          ;2  @10
.noPaddle1:
    pla                          ;4  @14
    sta    GRP1                  ;3  @17   P1 and P0 now switched
    lda    #BULK_LINES
    sta    tp_tempOne

    lda   #>JumperGraphics
    sta   tempJumperGfx+1
    lda   #<JumperGraphics
    sta   tempJumperGfx

.loopBelowBalloons:
    lda    #JUMPER_HEIGHT        ;2
    dcp    dcpJumper             ;5
    ldy    dcpJumper             ;2
    lda    #0                    ;2
    bcc   .skipDrawP1            ;2³
    lda    (tempJumperGfx),Y     ;5
.skipDrawP1

    ldy    #NO_MO_74
    sty    HMP1
    sta    WSYNC
;---------------------------------------
    bit    INPT0                 ;3  @3
    bmi    .noPaddle             ;2³ @5/6
    inx                          ;2  @7
.noPaddle:
    sta    GRP1                  ;3  @10

    dec    tp_tempOne
    bpl    .loopBelowBalloons

So all I'm doing is repositioning P1, and then sampling the paddles for about 118 lines afterwards. The problem came from the bit INPT0 I'm doing right after the HMOVE. Without it everything was fine. With it I got large wobbles (I'm guessing 2-7 pixels worth), and it did have a relationship with where the player was being positioned in the routine before.

 

 

This leads me to think something going on in the TIA. I didn't even know about the problem because it didn't show up in emulation, and I thought I had dirty paddles. Having one extra scanline in-between the early HMOVE and reading the paddle was enough to clear up the problem. I never tested if a regular HMOVE did the same thing.

Link to comment
Share on other sites

Thanks for this - I was planning something similar (reading paddle with early HMOVE) - I guess I'll have to get the old girl out of the loft and do some hardware testing once I've written the routine....I'm planning on reading the paddle at about cycle 19 so hopefully the extra seperation might mean the issue doesn't show itself......I wonder how on earth HMOVE could interfere with the inputs!

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...