Jump to content
IGNORED

p0 on same scanline as m0, nudges it up or down slightly.


GradualGames

Recommended Posts

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 by GradualGames
Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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
Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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

Link to comment
Share on other sites

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 by tokumaru
  • Like 1
Link to comment
Share on other sites

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.

  • Like 1
Link to comment
Share on other sites

 

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 by zilog_z80a
Link to comment
Share on other sites

 

 

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 by GradualGames
Link to comment
Share on other sites

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.

Link to comment
Share on other sites

 

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.

Link to comment
Share on other sites

 

(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, :D

Edited by GradualGames
  • Like 2
Link to comment
Share on other sites

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 by GradualGames
  • Like 1
Link to comment
Share on other sites

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! :-D :-D :-D I jest, I jest. I am devouring these tutorials, fear not.

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