johncl Posted December 23, 2008 Share Posted December 23, 2008 Hi, I have a general interest in all retro computing, and although my main focus (and collection) is Commodore 64 I read about the Atari and considering that it was out as early as 1977 with full color graphics I found the whole machine intriguing. Especially the rather complex connection between the TIA and the 6507. I have followed the excellent tutorials in this forum and thought I'd try to code something. The first plan was just to create a nice raster sky/ground and then add a sprite. This is where things get really challenging, and I see that the amount of cycles you have to do stuff is severly limited. I found a pattern though and that was to compute the data needed for the next scanline during the scanline draw so that it was simply a fetch and save into the TIA registers when its retracing for the next scanline. However even this becomes challenging simply because I only got 3 registers (A,X,Y) and these has to be used at its most optimal way so you dont end up shuffling data in and out of memory variables, as there is really no time for this. I see the outlines of serious tweaks here and there just to make things flow nicely. Fortunately the X position of sprites could be done e.g. during the vertical blank so the real challenge is to put non-zero data into the sprite at the right place and zero it out at the end. I've never seen such complexity just to move a sprite around, its truly challenging. I cannot imagine how I could ever multiplex sprites in y direction in all of this! And this even before I have thought about playfield data. Quote Link to comment Share on other sites More sharing options...
+Random Terrain Posted December 23, 2008 Share Posted December 23, 2008 The Atari 2600 is too challenging for me, so I use batari Basic and Visual batari Basic (the bB IDE). Quote Link to comment Share on other sites More sharing options...
Omegamatrix Posted December 23, 2008 Share Posted December 23, 2008 Hi, I have a general interest in all retro computing, and although my main focus (and collection) is Commodore 64 I read about the Atari and considering that it was out as early as 1977 with full color graphics I found the whole machine intriguing. Especially the rather complex connection between the TIA and the 6507. I have followed the excellent tutorials in this forum and thought I'd try to code something. The first plan was just to create a nice raster sky/ground and then add a sprite. This is where things get really challenging, and I see that the amount of cycles you have to do stuff is severly limited. I found a pattern though and that was to compute the data needed for the next scanline during the scanline draw so that it was simply a fetch and save into the TIA registers when its retracing for the next scanline. However even this becomes challenging simply because I only got 3 registers (A,X,Y) and these has to be used at its most optimal way so you dont end up shuffling data in and out of memory variables, as there is really no time for this. I see the outlines of serious tweaks here and there just to make things flow nicely. Fortunately the X position of sprites could be done e.g. during the vertical blank so the real challenge is to put non-zero data into the sprite at the right place and zero it out at the end. I've never seen such complexity just to move a sprite around, its truly challenging. I cannot imagine how I could ever multiplex sprites in y direction in all of this! And this even before I have thought about playfield data. There are many different learned tricks employed to get around Stella's limitations. After all the thing was meant to be a pong machine! Alot of times people draw sprites with the last byte fetched being zero. This is a simple way to clear the sprite when it is done regardless of what scanline it is on. Sometimes tbe kernal is split into several different vertical zones. Like scanlines 160 to 178 do this subroutine, then 179 to 200 do this subroutine. Then it becomes how clever you design the game, by what type of game it is. Anyhow there are a lot of brilliant subroutines written out there. I'm sure there is a thread or two with them all put together. Things like being able to accurately reposition your sprite with a simple divide by 15 routine, etc... Quote Link to comment Share on other sites More sharing options...
johncl Posted December 29, 2008 Author Share Posted December 29, 2008 Yes I know about splitting up the kernel in several "passes" and thats what I do now. One pass to change background color, one to check if sprites should be drawn, one for setting playfield data and last one for drawing alternate line of sprite. I realised that you could simplify the algorithm a bit by adding some zeros to the top and bottom of the sprite so that you could "slide" its starting position with how it aligns with the rasters that way I can advance the sprite fetch index without checking stuff on all the other raster lines since the actual start/stop of drawing would be issued at every 4th raster. However, I see that multiplexing the second sprite several times down the screen in all of this will be tough. The sprite positioning I use now (found the HMP0, HMOVE routine here) uses two WSYNC's so that ruins the sprite draw routine. One the other hand it seems the only operation it does after the second WSYNC is a sta HMOVE (and then rts) so there should be enough time to store the next sprite data immediately in GRP0. The y register is not used in the sprite positioning subroutine also so I could prefetch that data as well. Except that I also need to get the color information... hmm its going to be challenging... just as my thread title says. Its fun to tweak the code and try to find the most optimal way of doing stuff. Sometimes I see that using tables and indirect indexed fetches just takes too much time compared to actually doing some arithmetic work in the code also (if its simple stuff). In other cases you can often shave one or two cycles by precalculating stuff. And like with the sprites you can do things that allow your draw routine to assume it can always fetch some new data although it is fetching zeroes until its ready, so that the actual logic is only done once in a while. Many cool algorithms to be found here. And Jay Miner sure didnt make it easy for us with the playfield thing. One thing is the repeated/mirrored playfield where you have to change the data at the correct spots to get new data on the right hand, another is the rather "backwards" drawing of PF1. Its really no problem if your want to add some graphics to the screen, but if you want a sprite to interact with it, its clear your best option is to have two "versions" of the playfield data, one where the bits are correct from left to right, and another that is used for drawing the screen. Naturally you could create the other on the fly and store that in memory (whats left of it). Simply because in order to figure out where in the playfield the player is you need to rotate a masking test bit based on the X position. Another nice challenge. Btw I am making a platformer and my initial idea was to have the screen scroll, but I have chosen not do that but rather let the player move left and right to reach other screens. Quote Link to comment Share on other sites More sharing options...
ginnidog Posted December 29, 2008 Share Posted December 29, 2008 My brain hurts Quote Link to comment Share on other sites More sharing options...
johncl Posted December 29, 2008 Author Share Posted December 29, 2008 Hmm, seems I have met a wall here. I just cant seem to find enough cycles to do it all with a full playfield that is updated during the scanline to get different data on both sides. The problem is naturally that since the PF0,PF1,PF2 needs to be updated during the scanline you have to update them twice on every scanline. This severely limits the amount of time for anything else, and I probably have to think differently about how I iterate over the sprite data (consuming too many cycles now). Quote Link to comment Share on other sites More sharing options...
eshu Posted December 29, 2008 Share Posted December 29, 2008 Hmm, seems I have met a wall here. I just cant seem to find enough cycles to do it all with a full playfield that is updated during the scanline to get different data on both sides. The problem is naturally that since the PF0,PF1,PF2 needs to be updated during the scanline you have to update them twice on every scanline. This severely limits the amount of time for anything else, and I probably have to think differently about how I iterate over the sprite data (consuming too many cycles now). Have you considered losing PF0, part of it will be obscured if you're using HMOVE's anyway. You can get 4/5ths of the screen just updating PF1 & 2 twice each... P.S. This is my first post, so hello to everyone, excelent forum here.... Quote Link to comment Share on other sites More sharing options...
johncl Posted December 29, 2008 Author Share Posted December 29, 2008 (edited) Just to ask others before I try it. Is it possible to do asymetrical mirrored playfield (as in they are updated during the drawing) using PF1, PF2 and at the same time draw one sprite, and cycle background, sprite and playfield colors? Seems pretty tight... And yes Eshu, I have limited myself to PF1, PF2 only. Should be enough with a 32 bits wide area for the platforms. Edited December 29, 2008 by johncl Quote Link to comment Share on other sites More sharing options...
eshu Posted December 29, 2008 Share Posted December 29, 2008 Just to ask others before I try it. Is it possible to do asymetrical mirrored playfield (as in they are updated during the drawing) using PF1, PF2 and at the same time draw one sprite, and cycle background, sprite and playfield colors? Seems pretty tight... It really depends on how you're doing these things for example if the sprite is limited to 128 scanlines vertically it can be drawn in 7 cycles if it is stored with a lot of wasted memory: LDA Sprite,y STA GRP0 11 cycles with less wasted memory, but the sprite data must be placed in the centre of a page. LDA Sprite,y AND Mask,y STA GRP0 Or more cycles with less restrictions with SkipDraw. Also there are a few tricks with colours. If you need complete freedom it should take 7 cycles to do a colour change (No masking required as random colour doesn't matter when theres nothing there): LDA Colour,y STA COLUP0 However, sometimes you can relate the colours to the graphics - something I am working on at the moment uses only 3 extra cycles for the colours: LDA Sprite,y AND Mask,y STA GRP0 SAX COLUP0 (SAX is an illegal opcode which stores A & X) I have X permenantly set to 0x0F in this kernel. Quote Link to comment Share on other sites More sharing options...
johncl Posted December 29, 2008 Author Share Posted December 29, 2008 (edited) Thanks for the tips. That mask looks interesting but how many cycles keeping it up to date ($00 or $ff I assume) during the draw? This is my sprite draw routine now: ldy SPRYCNT beq nosprite; skip when sprite has ended dec SPRYPOS bne nosprite; until position is at sprite y pos lda #1 sta SPRYPOS dey sty SPRYCNT nosprite ldx $f900,y lda (SPRPTRLO),y ; --- scanline 1 - Player Sprite draw (even line) sta WSYNC sta GRP0 stx COLUP0 The color info is at $f900 and the sprite is at $f800 and I calculate the SPRPTRLO based on which sprite to show. The whole problem is figuring when the sprite is supposed to be drawn and figure a way to know when it is finished. In my case it just fetches a the first entry (zero value) in the sprite data over and over again when the sprite counter has ended. To make sure it iterates over all bytes I need to set a #1 in the counter again so it passes through every time (all which is naturally skipped when it has reached the end). Still a lot of cycles wasted for these checks and iterations. Edit: Reading your code snippets again I now understand that the mask is a table of preceding zeroes and $ff depending on the height of the sprite and then a lot of zeroes after. And your MASK pointer is then adjusted to when it should start drawing, and your sprite pointer is also offset by the same amount. A really smart trick there yes. I guess one could sacrifice some bytes in rom for this table. Edited December 29, 2008 by johncl Quote Link to comment Share on other sites More sharing options...
+SpiceWare Posted December 29, 2008 Share Posted December 29, 2008 Just to ask others before I try it. Is it possible to do asymetrical mirrored playfield (as in they are updated during the drawing) using PF1, PF2 and at the same time draw one sprite, and cycle background, sprite and playfield colors? Seems pretty tight... And yes Eshu, I have limited myself to PF1, PF2 only. Should be enough with a 32 bits wide area for the platforms. Should be possible - in Stay Frosty I draw a asymmetrical mirrored playfield using just PF1 & PF2 while drawing 2 sprites (and reusing/repositioning one of the sprites), and changing the colors of 1 sprite as well as the playfield. Quote Link to comment Share on other sites More sharing options...
eshu Posted December 29, 2008 Share Posted December 29, 2008 Thanks for the tips. That mask looks interesting but how many cycles keeping it up to date ($00 or $ff I assume) during the draw? This is my sprite draw routine now: ldy SPRYCNT beq nosprite; skip when sprite has ended dec SPRYPOS bne nosprite; until position is at sprite y pos lda #1 sta SPRYPOS dey sty SPRYCNT nosprite ldx $f900,y lda (SPRPTRLO),y ; --- scanline 1 - Player Sprite draw (even line) sta WSYNC sta GRP0 stx COLUP0 The color info is at $f900 and the sprite is at $f800 and I calculate the SPRPTRLO based on which sprite to show. The whole problem is figuring when the sprite is supposed to be drawn and figure a way to know when it is finished. In my case it just fetches a the first entry (zero value) in the sprite data over and over again when the sprite counter has ended. To make sure it iterates over all bytes I need to set a #1 in the counter again so it passes through every time (all which is naturally skipped when it has reached the end). Still a lot of cycles wasted for these checks and iterations. Edit: Reading your code snippets again I now understand that the mask is a table of preceding zeroes and $ff depending on the height of the sprite and then a lot of zeroes after. And your MASK pointer is then adjusted to when it should start drawing, and your sprite pointer is also offset by the same amount. A really smart trick there yes. I guess one could sacrifice some bytes in rom for this table. Have you had a look at the SkipDraw code - skipDraw on MiniDig, think that's the most similar method to yours and is as efficient as possible... Quote Link to comment Share on other sites More sharing options...
+grafixbmp Posted December 29, 2008 Share Posted December 29, 2008 Well really, the basic idea is to compromise between having enough cycles to work with vs. as many things as you can get on the screen in a single scanline. You could do a selective asymetrical playfield. Remember that if one PF register is loaded you could always leave it as is and not reload it again for the right hand side. I am working on a game platform myself where the game images are done in zones and each zone loads the register is diffrent ways and also the playfield also consists of what appears to be 2 scanline images. You could do like First pass: PF0 PF1 PF2 PF1 second pass: PF1 ~~~~~PF1 And the next line would then be new data. You can actualy do several diffrent arangements of register load and stores that can give more detail with out the losses of a full asymentrical playfield. Granted this isn't a ton of savings on cycles but with proper tweaking and some fancy coding along with a compromise here and there, you can get plenty of detail on the screen. I also toyed with the thought that if some registers needed nothing there, then I could do a immediate load and store thereby saving a few cycles. likewise if the register was all on or 11111111. Quote Link to comment Share on other sites More sharing options...
Thomas Jentzsch Posted December 29, 2008 Share Posted December 29, 2008 For effecitve sprite drawing search for "mask(ed)draw", "switchdraw" and "skipdraw". For repositioning think about blank PF lines (or a striped PF like in Thrust). Also consider special code for positioning the sprite left or right as this leaves some CPU time during those lines. Quote Link to comment Share on other sites More sharing options...
johncl Posted December 29, 2008 Author Share Posted December 29, 2008 (edited) Thank you all for the replies. Will definitely look into other ways of placing sprite. I did try that skipdraw method and it uses 22 cycles at most which is the same that my method already uses. Rewriting my code to the skipdraw gives me: inc SPRYCNT ; 5 lda SPRYCNT ; 3 sec ; 2 sbc PlayerY ; 3 adc #SPR_HEIGHT+1 ; 2 bcc skipDrawSprite; 2-3 tay ; 2 jmp drawSprite ; 3 = 22 cycles (pass through bcc) skipDrawSprite ldy #0 ; 2 drawSprite ldx $f900,y lda (SPRPTRLO),y Putting the skipDraw outside the kernal and branching back doesnt give any benefits as that would just move the jmp outside and would use 23 cycles in whenever it is being skipped. Naturally the benefit with this method is that you need to have a scanline counter which is of great use to other kernel code. I didnt have that and sorely needed one so I will probably use the skipsprite method for now. About the tip of empty scanlines thats how I intend to reuse the other sprite several times down the screen. Just afraid my kernel will grow very big. Edit: I see now that the cycles savings for doing the skipDraw outside the kernel is that I dont need to read the color and indirect adressing of data. Thats naturally a big saving in that case! So it seems the skipdraw will max out at 19 cycles which 3 cycles lower than what I had. Good good. Edited December 29, 2008 by johncl Quote Link to comment Share on other sites More sharing options...
johncl Posted December 30, 2008 Author Share Posted December 30, 2008 Btw I tried out Stay Frosty, and its a brilliant platformer with some nice challenges as you get into the game. The only thing missing from it is a level number indicator and perhaps a bonus life there now and then (perhaps there is). I love the detail and expression you have gotten into that snowman, I guess you have used the missile and ball a lot on him. I was hoping to give my hero some eyes using the ball and perhaps some other tiny details. But I dont think I have enough cycles to do that. The screen I have now looks fairly nice but with a mirrored playfield. My next challenge is to update it during draw so its asymmetrical and I need to draw one sprite as well in the same scanline but I guess I will try to use some assumptions that the next data is available always so I just pad the end of the sprite with zeros to avoid any sort of checks. At least I have seen that I need to make my kernel quite a bit larger and split the drawing up into sections since there will be whole lines where there is no playfield (but only background color and the main player sprite). These are perfect places to multiplex the other sprite. I guess I should design the game for NTSC and 192 visible lines and just have more border on the PAL versions (or extra decorations). Quote Link to comment Share on other sites More sharing options...
johncl Posted December 30, 2008 Author Share Posted December 30, 2008 Just a quick question. I felt like reinventing the wheel and doing a 6 digit score. I assumed it was done by using the two sprites next to each other, set them to repeat 3 times and then change the GRP0 and GRP1 as it draws over the screen. And I guess the idea is to use the stack register as extra storage since that only uses 2 cycles to transfer the value which is just enough to sqeeze in that last sta GRP1 to set the 6th number. But is this safe? And it works fine for me now in Stella, can I assume its working on real hardware as well? Or are there things you can do in Stella that allows TIA trickery beyond what the real hardware can do? Also one last question. Is it possible to reuse the sprites on the same raster line in any way all? (Even if I cant move it but always relative to previous location?) Quote Link to comment Share on other sites More sharing options...
Omegamatrix Posted December 31, 2008 Share Posted December 31, 2008 The stack should be safe, as long as you don't have a bunch of return addresses stacked there, and are not close to your used ram. Maybe take a peek with Stella's debugger while your routine is loading. The 6 digit score has been done many times. I don't know what one is the most efficient. I took this one out of Kaboom! .loopDraw STY tempOne ;3 LDA ($FE),Y ;5 STA tempTwo ;+3 = 70 cycles STA WSYNC ;3 ;---------------------------- LDA ($F4),Y ;5 STA GRP0 ;3 LDA ($F6),Y ;5 STA GRP1 ;3 LDA ($F8),Y ;5 STA GRP0 ;3 LDA ($FC),Y ;5 TAX ;2 LDA ($FA),Y ;5 LDY tempTwo ;3 STA GRP1 ;3 STX GRP0 ;3 STY GRP1 ;3 STA GRP0 ;3 LDY tempOne ;3 DEY ;+2 = 56 cycles BPL .loopDraw;2/3 Quote Link to comment Share on other sites More sharing options...
+SpiceWare Posted December 31, 2008 Share Posted December 31, 2008 Btw I tried out Stay Frosty, and its a brilliant platformer with some nice challenges as you get into the game. The only thing missing from it is a level number indicator and perhaps a bonus life there now and then (perhaps there is). I love the detail and expression you have gotten into that snowman, I guess you have used the missile and ball a lot on him. Thanks, Nathan Strum came up with the game idea and did all the graphics. The snowman is only drawn with a single sprite, the fireballs with the other. The ice blocks and platforms are both drawn with the playfield. The snowman's melt trail is drawn with 1 missile, the other missile and ball aren't used. The original design called for a carrot nose to be drawn using the other missile, but there wasn't enough CPU time to do so. I wish I could have put a level number in, I ran out of ROM space due to all the snowman and fireball graphics. There is a bonus life when you complete 32 levels - the game starts over with the temperature increased so everything melts faster. 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.