johnnystarr Posted March 27, 2014 Share Posted March 27, 2014 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. Quote Link to comment Share on other sites More sharing options...
Omegamatrix Posted March 27, 2014 Share Posted March 27, 2014 Yes keep track X-pos and reposition each frame. One other pitfall is clearing HMxx registers too early after HMOVE. Quote Link to comment Share on other sites More sharing options...
johnnystarr Posted March 27, 2014 Author Share Posted March 27, 2014 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. Quote Link to comment Share on other sites More sharing options...
Omegamatrix Posted March 28, 2014 Share Posted March 28, 2014 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 . Quote Link to comment Share on other sites More sharing options...
johnnystarr Posted March 28, 2014 Author Share Posted March 28, 2014 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. Quote Link to comment Share on other sites More sharing options...
+SpiceWare Posted March 28, 2014 Share Posted March 28, 2014 (edited) 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 March 28, 2014 by SpiceWare Quote Link to comment Share on other sites More sharing options...
johnnystarr Posted March 28, 2014 Author Share Posted March 28, 2014 @SpiceWare, very cool. I was planning on using JSR in my code, but I hadn't thought of it this way. Quote Link to comment Share on other sites More sharing options...
+SpiceWare Posted March 29, 2014 Share Posted March 29, 2014 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 Quote Link to comment Share on other sites More sharing options...
Nukey Shay Posted March 29, 2014 Share Posted March 29, 2014 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". Quote Link to comment Share on other sites More sharing options...
Omegamatrix Posted March 29, 2014 Share Posted March 29, 2014 (edited) 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. 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 March 29, 2014 by Omegamatrix Quote Link to comment Share on other sites More sharing options...
johnnystarr Posted March 31, 2014 Author Share Posted March 31, 2014 (edited) 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 March 31, 2014 by johnnystarr Quote Link to comment Share on other sites More sharing options...
+SpiceWare Posted March 31, 2014 Share Posted March 31, 2014 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. Quote Link to comment Share on other sites More sharing options...
johnnystarr Posted March 31, 2014 Author Share Posted March 31, 2014 (edited) 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. Edited March 31, 2014 by johnnystarr Quote Link to comment Share on other sites More sharing options...
eshu Posted March 31, 2014 Share Posted March 31, 2014 Long story short the situation is I found out that early HMOVE's messed up my paddle reads big time. 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? Quote Link to comment Share on other sites More sharing options...
+SpiceWare Posted March 31, 2014 Share Posted March 31, 2014 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. Quote Link to comment Share on other sites More sharing options...
Omegamatrix Posted April 1, 2014 Share Posted April 1, 2014 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. Quote Link to comment Share on other sites More sharing options...
eshu Posted April 1, 2014 Share Posted April 1, 2014 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! Quote Link to comment Share on other sites More sharing options...
Recommended Posts
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.