Proper X/Y split in neslib

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

Post Reply
na_th_an
Posts: 558
Joined: Mon May 27, 2013 9:40 am

Proper X/Y split in neslib

Post by na_th_an »

I've modified neslib to allow for proper X/Y split. It is obviously glitchy. For best results, you have to play with blank lines and the placement of your sprite 0 hit, so the last two writes in the modified split rutine go into HBLANK. In the proof of concept attached, this is not correctly timed and a visible glitch can be noticed.

I was thinking on a compo entry for this year which needs to alternate between horizontal and vertical scrolling levels. I thought about using vertical mirroring and hiding the screen updates behind the status bar (hud). The target mapper is CNROM.

Maybe this is useful for somebody - and I'm sure it can be improved, so I share it here.

This is my new split routine:

Code: Select all

;;void __fastcall__ split(unsigned int x,unsigned int y);

_split:

	; Extract SCROLL_Y1, SCROLL_X1, WRITE1 from parameters.

	sta <TEMP

	txa
	bne @1
	lda <TEMP
	cmp #240
	bcs @1
	sta <SCROLL_Y1
	lda #0
	sta <TEMP
	beq @2	;bra

@1:
	sec
	lda <TEMP
	sbc #240
	sta <SCROLL_Y1
	lda #8					;; Bit 3
	sta <TEMP
@2:

	jsr popax
	sta <SCROLL_X1
	txa
	and #$01
	asl a
	asl a 					;; Bit 2
	ora <TEMP               ;; From Y
	sta <WRITE1				;; Store!

	; Calculate WRITE2 = ((Y & $F8) << 2) | (X >> 3)

	lda <SCROLL_Y1
	and #$F8
	asl a
	asl a
	sta <TEMP 				;; TEMP = (Y & $F8) << 2
	lda <SCROLL_X1
	lsr a
	lsr a
	lsr a 					;; A = (X >> 3)
	ora <TEMP 				;; A = (X >> 3) | ((Y & $F8) << 2)
	sta <WRITE2				;; Store!

	; Wait for sprite 0 hit

@3:
	bit PPU_STATUS
	bvs @3
@4:
	bit PPU_STATUS
	bvc @4

	; Set scroll value
	lda PPU_STATUS
	lda <WRITE1
	sta PPU_ADDR
	lda <SCROLL_Y1
	sta PPU_SCROLL
	lda <SCROLL_X1
	ldx <WRITE2
	sta PPU_SCROLL
	stx PPU_ADDR
	
	rts
Which needs a couple of extra variables in ZP not present in unmodified neslib. Add them to crt0.s:

Code: Select all

SCROLL_Y1: 			.res 1
WRITE1:				.res 1	
WRITE2:				.res 2		; For extra X/Y split data.
Anyways, everything is in the zip (plus a .NES file).
Attachments
poc.zip
(63.66 KiB) Downloaded 110 times
User avatar
dougeff
Posts: 3078
Joined: Fri May 08, 2015 7:17 pm

Re: Proper X/Y split in neslib

Post by dougeff »

Thanks for this.

I have 2 issues.

1. I feel like arbitrary / on-the-fly X/Y splits are not a good idea. People should probably precalculate 2005/2006 values and not make the CPU do it.

2. You are using shiru's Y scrolling system, which involves substraction (if Y scroll > 240 pixels). This works fine for 2 screen high games, but you mentioned vertical scrolling. How are you going to calculate Y positions in a map that extends over 2 screens high? Division by 240?
nesdoug.com -- blog/tutorial on programming for the NES
tepples
Posts: 22705
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Proper X/Y split in neslib

Post by tepples »

Probably the best way to handle maps taller than 240 pixels is to store the offset between the top of the map (in world coordinates) and the top of the 240- or 480-pixel plane explicitly, as tokumaru described.
na_th_an
Posts: 558
Joined: Mon May 27, 2013 9:40 am

Re: Proper X/Y split in neslib

Post by na_th_an »

dougeff wrote:Thanks for this.

I have 2 issues.

1. I feel like arbitrary / on-the-fly X/Y splits are not a good idea. People should probably precalculate 2005/2006 values and not make the CPU do it.

2. You are using shiru's Y scrolling system, which involves substraction (if Y scroll > 240 pixels). This works fine for 2 screen high games, but you mentioned vertical scrolling. How are you going to calculate Y positions in a map that extends over 2 screens high? Division by 240?
I can do some simplifications for my game, as Y won't be ever > 240 and the scrolling will always be performed in the first 32 visible scans of nametable B. But for sharing, I chose to take a more general approach.

Anyways, in my engine I don't do division by 240. I just keep one set of "world" variables and one set of "screen" variables, which I keep always synchronized. The "world" variables are used to read the map data and fill a circular 16x16 tiles buffer (256x256 pixels), the "screen" variables are used to keep track on where on screen to write. Each time I advance my world coordinates I do so with my screen coordinates, substracting 240 if I go >= 240.

I think this is what tepples mentions.
Post Reply