GradualGames Posted February 4, 2018 Share Posted February 4, 2018 (edited) Whenever player 0 is right around the same scanline as missile 0, it nudges the missile up or down, but I don't know why. Maybe there's no way around this? I store the previous scanline's buffer for both missile 0 and player 0 right as a scanline starts; I would think those few instructions would fit in hblank? Here's my kernal (ca65 syntax): scan_loop: ;Wait for beginning of next scanline. sta WSYNC lda player_buffer sta GRP0 lda missile_buffer sta ENAM0 ;If we reach the bottom of the player, start ;a counter for its height. cpy player_y bne :+ lda #PLAYER_HEIGHT sta player_line : ;If we reach the bottom of the player, start ;a counter for its height. cpy missile_y bne :+ lda #MISSILE_HEIGHT sta missile_line : lda #0 sta player_buffer sta missile_buffer ;If the player scanline counter is nonzero, enable ;player 0 and decrement the counter. ldx player_line beq :+ lda BigHeadGraphic-1,x sta player_buffer dec player_line : ldx missile_line beq :+ lda #2 sta missile_buffer dec missile_line : dey bne scan_loop Edited February 5, 2018 by GradualGames Quote Link to comment Share on other sites More sharing options...
GradualGames Posted February 4, 2018 Author Share Posted February 4, 2018 (edited) Above post edited to reflect my current problem. Edited February 5, 2018 by GradualGames Quote Link to comment Share on other sites More sharing options...
tokumaru Posted February 5, 2018 Share Posted February 5, 2018 The hardware doesn't care about vertical positioning, that's all done in software, so this is definitely a bug in the program and not a limitation of the system. There are only 76 CPU cycles per scanline, and it looks like your code will take more than that when all branches are not taken, which happens when both the player and the missile start on the same scanline. I don't know why only the missile is nudged, I would expect both to be delayed because of the late sta WSYNC, but I'm still betting this being the cause of the problem. Quote Link to comment Share on other sites More sharing options...
vidak Posted February 5, 2018 Share Posted February 5, 2018 Could you include some cycle counting in your code? I think you may be running into timing issues, like tokumaru says... Quote Link to comment Share on other sites More sharing options...
+nanochess Posted February 5, 2018 Share Posted February 5, 2018 It exceeds available cycles. Replace this: ; removing these ; lda #0 ; sta player_buffer ; sta missile_buffer ;If the player scanline counter is nonzero, enable ;player 0 and decrement the counter. lda player_line beq :+ tax lda BigHeadGraphic-1,x dec player_line : sta player_buffer lda missile_line beq :+ lda #2 dec missile_line : sta missile_buffer Quote Link to comment Share on other sites More sharing options...
GradualGames Posted February 5, 2018 Author Share Posted February 5, 2018 The hardware doesn't care about vertical positioning, that's all done in software, so this is definitely a bug in the program and not a limitation of the system. There are only 76 CPU cycles per scanline, and it looks like your code will take more than that when all branches are not taken, which happens when both the player and the missile start on the same scanline. I don't know why only the missile is nudged, I would expect both to be delayed because of the late sta WSYNC, but I'm still betting this being the cause of the problem. Well hello sir! I was wondering when I'd bump into you here! I thought I read somewhere a lot of games use 2 scanlines at a time to be able to position more objects. Perhaps that's what I need to do? Seems rather nutty to run out of cycles after positioning just TWO things, haha. Quote Link to comment Share on other sites More sharing options...
+SpiceWare Posted February 5, 2018 Share Posted February 5, 2018 I thought I read somewhere a lot of games use 2 scanlines at a time to be able to position more objects. That's known as a 2 Line Kernel, or 2LK. I cover that in my Collect tutorial. 1 Quote Link to comment Share on other sites More sharing options...
Nukey Shay Posted February 5, 2018 Share Posted February 5, 2018 Seems rather nutty to run out of cycles after positioning just TWO things, haha. That is why most games don't rely on separate counters or buffering. They'll utilize the existing scanline counter and draw the objects before or after their visible position/use the delay registers for auto buffering. Nanochess showed you an example of a logic result serving double-duty as a bitmap value to cut down time. If a game screen was really complex, a sprite-drawing portion might be coded as several mini-kernel branches (depending on what else needs to be done under specific conditions). Quote Link to comment Share on other sites More sharing options...
tokumaru Posted February 5, 2018 Share Posted February 5, 2018 (edited) Well hello sir! I was wondering when I'd bump into you here! Hey there! I've been away from 2600 stuff for a while, but it's still my second favorite system to code for. Glad to see other NESDev'ers showing interest! I thought I read somewhere a lot of games use 2 scanlines at a time to be able to position more objects. Perhaps that's what I need to do? Yeah, I guess that if you want anything that looks more complex than the earlier games you have to spread the logic over 2 or more scanlines. Seems rather nutty to run out of cycles after positioning just TWO things, haha. You do need a different mentality to code for the 2600. The more you think like a hardware designer, the better. Hardware designers are always cutting corners and packing the most functionality in the smallest amount of time and space. Think of how the NES PPU evaluates sprites: it subtracts the sprite's Y coordinate from the current scanline number, and the result is, if smaller than the sprite height, the line of the sprite that needs to be drawn. That's a very hardware-oriented approach, because it needs less state (no need for individual counters for each sprite) and solves multiple problems in a single operation. The 2600 can draw sprites in a similar way (I believe that the common name of the technique is "SkipDraw", and there are many variations of it). A more radical approach to positioning sprites vertically is to use masks, but that requires a lot of ROM, so you'd normally only do this if you used bankswitching and had ROM banks dedicated exclusively to graphics. The idea is to have separate pointers to the sprite data and to a mask that blanks out the sprite where you don't want it to show (it's an array of $00s with a "hole" of $FFs the same height as the sprite). Then you can do this in your kernel: lda (P0Pattern), y and (P0Mask), y sta GRP0 Then you don't need to do any positioning in real time, so this is pretty fast (no decisions or states to take care of)! you just need to setup the pointers beforehand, so they align properly with Y as the scanline counter. The size occupied by each of these masks is ScreenHeight * 2 + ObjectHeight, if you plan on being able to place the sprite anywhere in the screen, and you'll need different masks for different object heights. Another important detail is that kernels don't typically buffer TIA writes, since that's way too slow. It's much better to time your logic so you can write to the registers directly. The TIA has a built-in buffer for player graphics though (you can delay GRP0 and GRP1 updates until the other register is written), and that helps a lot with making all the updates fit in hblank. EDIT: Why is the forum mangling my code tags? It consistently removes line breaks and screws up the spacing! Edited February 5, 2018 by tokumaru 1 Quote Link to comment Share on other sites More sharing options...
CurtisP Posted February 9, 2018 Share Posted February 9, 2018 I highly advise reading through the Collect tutorial, then looking at some other kernels. It will keep you from reinventing the wheel. 1 Quote Link to comment Share on other sites More sharing options...
vidak Posted February 9, 2018 Share Posted February 9, 2018 The StayFrosty blog posts from SpiceWare are also really useful for teaching you masking. My project codenamed Sierra Maestra copied the masking concept from StayFrosty, and has a much simpler implementation of it, currently. Using ROM as a mask is much much faster, and I highly recommend it. The SkipDraw, FlipDraw, DoDraw and SwitchDraw routines are all many times slower than masking. 1 Quote Link to comment Share on other sites More sharing options...
zilog_z80a Posted February 9, 2018 Share Posted February 9, 2018 (edited) The StayFrosty blog posts from SpiceWare are also really useful for teaching you masking. My project codenamed Sierra Maestra copied the masking concept from StayFrosty, and has a much simpler implementation of it. Hi Vidak, ty for your blog and comenting about how are you doing your game, is a little difficult to me being a newbie, to understand a simple masking when all the other code is not easy to understand. is here in the forum a simple example of masking like you did but with a simple kernell without playfield? ty. Edited February 9, 2018 by zilog_z80a Quote Link to comment Share on other sites More sharing options...
vidak Posted February 10, 2018 Share Posted February 10, 2018 Yeah! My latest source code of my Sierra Maestra game! I have all the pointers set up properly, and you can read through my debugging. I'm always here to help! 1 Quote Link to comment Share on other sites More sharing options...
zilog_z80a Posted February 10, 2018 Share Posted February 10, 2018 Yeah! My latest source code of my Sierra Maestra game! I have all the pointers set up properly, and you can read through my debugging. I'm always here to help! ty Vidak will take a look this week. cheers. Quote Link to comment Share on other sites More sharing options...
GradualGames Posted February 13, 2018 Author Share Posted February 13, 2018 (edited) That's known as a 2 Line Kernel, or 2LK. I cover that in my Collect tutorial. This is a great tutorial, thanks for putting these together. I think I understand the gist of the 2LK technique now. However, I am slightly thrown off by something. Why is it that the top of the loop is a "continuation of line 2" yet precalculates line 1? Is it possible to reorganize the 2LK to be less optimized for the sake of demonstration? I made a stab at a 2LK today (note: without directly copying the flow of your tutorial, for learning's sake. I wanted to know: can I organize a 2LK in a less optimal but easier to understand way or not?), but it really doesn't behave the way I'd expect. I do see that my missile and player are offset by one scanline (which I further understand is why the VDEL registers are there, to enable this 2LK technique), but I'm getting rapid flickering. It's so rapid I can barely tell its there, but it changes the color from red to a darker red, almost brown. ;Initialize kernel. ldy #191 ;Kernel loop scan_loop: ;Wait for beginning of next scanline. sta WSYNC ;3 lda player_buffer ;3 sta GRP0 ;3 lda #0 ;2 sta player_buffer ;3 ;If we reach the bottom of the missile, start ;a counter for its height. cpy missile_y ;3 bne :+ ;2/3/4 lda #MISSILE_HEIGHT ;2 sta missile_line ;3 ;10 : ldx missile_line ;3 beq :+ ;2/3/4 lda #2 ;2 sta missile_buffer ;3 dec missile_line ;6 ;16 : sta WSYNC lda missile_buffer ;3 sta ENAM0 ;3 ;15 lda #0 sta missile_buffer ;3 ;8 ;If we reach the bottom of the player, start ;a counter for its height. cpy player_y ;3 bne :+ ;2/3/4 lda #PLAYER_HEIGHT ;2 sta player_line ;3 ;10 : ;If the player scanline counter is nonzero, enable ;player 0 and decrement the counter. ldx player_line ;3 beq :+ ;2/3/4 lda BigHeadGraphic-1,x ;4/5 sta player_buffer ;3 dec player_line ;6 ;18 : dey ;2 bne scan_loop ;2/3/4 ;4 Edited February 13, 2018 by GradualGames Quote Link to comment Share on other sites More sharing options...
+SpiceWare Posted February 13, 2018 Share Posted February 13, 2018 This is a great tutorial, thanks for putting these together. I think I understand the gist of the 2LK technique now. However, I am slightly thrown off by something. Why is it that the top of the loop is a "continuation of line 2" yet precalculates line 1? Is it possible to reorganize the 2LK to be less optimized for the sake of demonstration? Thanks! Using the kernel shown in Step 4 - 2 Line Kernel PrepForArenaLoop: ldy #ARENA_HEIGHT ; 2 7 - the arena will be 180 scanlines (from 0-89)*2 lda #HUMAN_HEIGHT-1 ; 2 9 - height of the humanoid graphics, subtract 1 due to starting with 0 dcp HumanDraw ; 5 14 - Decrement HumanDraw and compare with height bcs DoDrawGrp0a ; 2 16 - (3 17) if Carry is Set, then humanoid is on current scanline lda #0 ; 2 18 - otherwise use 0 to turn off player0 .byte $2C ; 4 22 - $2C = BIT with absolute addressing, trick that ; causes the lda (HumanPtr),y to be skipped DoDrawGrp0a: ; 17 - from bcs DoDrawGrp0 lda (HumanPtr),y ; 5 22 - load the shape for player0 ArenaLoop: ; 35 - from bpl ArenaLoop sta WSYNC ; 3 38 ;--------------------------------------- ; start of line 1 of the 2LK sta GRP0 ; 3 3 - @ 0-22, update player0 to draw Human ldx #%11111111 ; 2 5 - playfield pattern for vertical alignment testing stx PF0 ; 3 8 - @ 0-22 ; precalculate data that is needed for line 2 of the 2LK lda #HUMAN_HEIGHT-1 ; 2 10 - height of the humanoid graphics, dcp BoxDraw ; 5 15 - Decrement BoxDraw and compare with height bcs DoDrawGrp1 ; 2 17 - (3 18) if Carry is Set, then box is on current scanline lda #0 ; 2 19 - otherwise use 0 to turn off player1 .byte $2C ; 4 23 - $2C = BIT with absolute addressing, trick that ; causes the lda (BoxPtr),y to be skipped DoDrawGrp1: ; 18 - from bcs DoDrawGRP1 lda (BoxPtr),y ; 5 23 - load the shape for the box sta WSYNC ; 3 26 ;--------------------------------------- ; start of line 2 of the 2LK sta GRP1 ; 3 3 - @0-22, update player1 to draw box ldx #0 ; 2 5 - PF pattern for alignment testing stx PF0 ; 3 8 - @0-22 dey ; 2 10 - decrease the 2LK loop counter php ; 3 13 - save result of DEY onto stack lda #HUMAN_HEIGHT-1 ; 2 15 - height of the humanoid graphics, subtract 1 due to starting with 0 dcp HumanDraw ; 5 20 - Decrement HumanDraw and compare with height bcs DoDrawGrp0b ; 2 22 - (3 23) if Carry is Set, then humanoid is on current scanline lda #0 ; 2 24 - otherwise use 0 to turn off player0 .byte $2C ; 4 28 - $2C = BIT with absolute addressing, trick that ; causes the lda (HumanPtr),y to be skipped DoDrawGrp0b: ; 23 - from bcs DoDrawGrp0 lda (HumanPtr),y ; 5 28 - load the shape for player0 plp ; 4 32 - recover result of DEY from stack bpl ArenaLoop ; 2 34 - (3 35) branch if there is more Arena to draw The lda #HUMAN_HEIGHT-1 thru lda (HumanPtr),y that preps for GRP0 must be repeated at the bottom. Due to when the DEY needs to occur we have to stash/fetch the processor status onto the stack so the bpl ArenaLoop will work. Quote Link to comment Share on other sites More sharing options...
gauauu Posted February 13, 2018 Share Posted February 13, 2018 This is a great tutorial, thanks for putting these together. I think I understand the gist of the 2LK technique now. However, I am slightly thrown off by something. Why is it that the top of the loop is a "continuation of line 2" yet precalculates line 1? Is it possible to reorganize the 2LK to be less optimized for the sake of demonstration? I made a stab at a 2LK today (note: without directly copying the flow of your tutorial, for learning's sake. I wanted to know: can I organize a 2LK in a less optimal but easier to understand way or not?), but it really doesn't behave the way I'd expect. I do see that my missile and player are offset by one scanline (which I further understand is why the VDEL registers are there, to enable this 2LK technique), but I'm getting rapid flickering. It's so rapid I can barely tell its there, but it changes the color from red to a darker red, almost brown. (We discussed this elsewhere, but for the sake of anyone else reading along) The one thing I notice is that you're only doing a single dey, which means that with 2 lines per decrement, your actual kernel is trying to be 382 lines high or so. I'm not sure if that's the main culprit of what you're seeing, but it would cause all sorts of issues. Quote Link to comment Share on other sites More sharing options...
GradualGames Posted February 14, 2018 Author Share Posted February 14, 2018 (edited) (We discussed this elsewhere, but for the sake of anyone else reading along) The one thing I notice is that you're only doing a single dey, which means that with 2 lines per decrement, your actual kernel is trying to be 382 lines high or so. I'm not sure if that's the main culprit of what you're seeing, but it would cause all sorts of issues. Yeah one problem I discovered was I was starting my scanline counter at 191, and so when I added the second dey in there, it skipped from 1 to -1, never reaching zero so it stayed in an infinite loop, haha! I now at least have a working 2LK with ONE object on the screen, Edited February 14, 2018 by GradualGames 2 Quote Link to comment Share on other sites More sharing options...
GradualGames Posted February 14, 2018 Author Share Posted February 14, 2018 (edited) Thanks all, very encouraging to have your thoughts and help.Okie dokie, now I have my own 2LK working with two objects. Likely nowhere near optimal if I wanted make any sort of game yet perhaps, no idea, but I'm happy I think I have the principle basically down. Not using VDEL yet to line them up. ;Initialize kernel. ldy #96 ;Kernel loop scan_loop: lda player_buffer ;3 ;Wait for beginning of next scanline. sta WSYNC ;3 sta GRP0 ;3 lda #0 ;2 sta player_buffer ;3 ;If we reach the bottom of the player, start ;a counter for its height. cpy player_y ;3 bne :+ ;2/3/4 lda #PLAYER_HEIGHT ;2 sta player_line ;3 ;10 : ;If the player scanline counter is nonzero, enable ;player 0 and decrement the counter. ldx player_line ;3 beq :+ ;2/3/4 lda BigHeadGraphic-1,x ;4/5 sta player_buffer ;3 dec player_line ;6 ;18 : lda missile_buffer ;3 sta WSYNC sta ENAM0 ;3 ;15 lda #0 sta missile_buffer ;3 ;8 ;If we reach the bottom of the missile, start ;a counter for its height. cpy missile_y ;3 bne :+ ;2/3/4 lda #MISSILE_HEIGHT ;2 sta missile_line ;3 ;10 : ldx missile_line ;3 beq :+ ;2/3/4 lda #2 ;2 sta missile_buffer ;3 dec missile_line ;6 ;16 : dey ;2 bne scan_loop ;2/3/4 ;4 Edited February 14, 2018 by GradualGames 1 Quote Link to comment Share on other sites More sharing options...
GradualGames Posted February 14, 2018 Author Share Posted February 14, 2018 I highly advise reading through the Collect tutorial, then looking at some other kernels. It will keep you from reinventing the wheel. Oh my goodness no, I don't want to reinvent the wheel. If I did, I'd join some retro computing forum and learn to code games in assembly language or something! I jest, I jest. I am devouring these tutorials, fear not. 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.