2600 Cookbook: Asymmetrical Playfield Graphics

Problem:
You'd like a full screen graphic, like for a title screen or a fancy background.

Solution:
Learn how to set PF0, PF1, and PF2 at just the right times during the kernal to make an "asymmetrical playfield."

Discussion:
Playfield Graphics are most often used for background in atari games. They can also be used for Title Screens and Low-Rez score displays. PF graphics are 40 pixels across the screen, and each row of graphic can use as many scanlines as is convenient. The Atari uses 2 1/2 8-bits bytes -- i.e., 20 bits -- twice to get the full 40 pixels. That means if you want the right side of the screen to not look like the left side (or a mirror image of the right side) you need to update PF0, PF1, and PF2 halfway through the scanline.

Depending on the rightmost bit (D0) of CTRLPF, the right side will be a copy of the left side (D0 = 0) or a mirror image. (D0 = 1) (Note that the Stella Programmer's guide calls this value "CTLPF", but the standard vcs.h calls it "CTRLPF".)

The bits that make up playfield graphics aren't laid out in an intuitive way. The left side of the screen takes the four leftmost bits of PF0 backwards, then the eight bits of PF1 in the order you would expect, then the eight bits of PF2 backwards. Here's a table that shows you which PF registers are used, going from left-to-right, pixel-wise:
PF0 PF1 PF2
D4 D5 D6 D7 D7 D6 D5 D4 D3 D2 D1 D0 D0 D1 D2 D3 D4 D5 D6 D7

Here's another diagram, loaded with some sample data, you can see the 3 memory locations and how they'd show up as pixels:


So, if the playfield is mirrored, the right side will be the reverse of that, otherwise the ordering is the same. (I'll spare you yet another diagram to make that point)

Of course, flipping all those pixels by hand to make your program is very annoying. You might want to consider using a tool like Kirk Israel's PlayfieldPal to draw in your background....it will then do the bit-flipping work for you. (It's a javascript paint program...primitive, but sometimes easier than doing it all yourself.)

One thing to keep in mind, is that while the pixels are going to be wide, they don't have to be big, because they can be as short as one scanline each. These wide pixels can make a very distinctive look. Case in point: I was pretty happy with the way the JoustPong title screen came out. I made that by making the graphic I wanted at a high resolution, switching it to "two colors", shrinking it to 40 pixels across, then using that as a guide in PlayfieldPal.)

References:
Example:
;
;
; A Simple Asymmetrical Title Screen Playfield
;
; this is a simple kernal meant to be usable for a title screen.
; can be adapted to put playfield text at an arbitrary height on the screen
;
; it owes a great debt to Glenn Saunders Thu, 20 Sep 2001 Stella post
; " Asymmetrical Reflected Playfield" (who in turn took from Roger Williams,
; who in turn took from Nick Bensema--yeesh!)
;
; it's meant to be a tightish, welll-commented, flexible kernal,
; that displays a title (or other playfield graphic) once, 
; instead of repeating it - also it's a steady 60 FPS, 262 scanlines,
; unlike some of its predecessors
;
; also, it's non-reflected, so you can easily use a tool like my
; online javascript tool at http://alienbill.com/vgames/playerpal/
; to draw the playfield
;
; It uses no RAM, but all Registers when it's drawing the title 


	processor 6502
	include vcs.h
	include macro.h
	org $F000
Start
	CLEAN_START

	lda #00
	sta COLUBK  ;black background 	
	lda #33    
	sta COLUPF  ;colored playfield

;MainLoop starts with usual VBLANK code,
;and the usual timer seeding
MainLoop
	VERTICAL_SYNC
	lda #43	
	sta TIM64T
;
; lots of logic can go here, obviously,
; and then we'll get to the point where we're waiting
; for the timer to run out

WaitForVblankEnd
	lda INTIM	
	bne WaitForVblankEnd	
	sta VBLANK  	


;so, scanlines. We have three loops; 
;TitlePreLoop , TitleShowLoop, TitlePostLoop
;
; I found that if the 3 add up to 174 WSYNCS,
; you should get the desired 262 lines per frame
;
; The trick is, the middle loop is 
; how many pixels are in the playfield,
; times how many scanlines you want per "big" letter pixel 

pixelHeightOfTitle = #6
scanlinesPerTitlePixel = #6

; ok, that's a weird place to define constants, but whatever


;just burning scanlines....you could do something else
	ldy #20
TitlePreLoop
	sta WSYNC	
	dey
	bne TitlePreLoop



	ldx #pixelHeightOfTitle ; X will hold what letter pixel we're on
	ldy #scanlinesPerTitlePixel ; Y will hold which scan line we're on for each pixel

;
;the next part is careful cycle counting from those 
;who have gone before me....

TitleShowLoop
	sta WSYNC 	
	lda PFData0Left-1,X           ;[0]+4
	sta PF0                 ;[4]+3 = *7*   < 23	;PF0 visible
	lda PFData1Left-1,X           ;[7]+4
	sta PF1                 ;[11]+3 = *14*  < 29	;PF1 visible
	lda PFData2Left-1,X           ;[14]+4
	sta PF2                 ;[18]+3 = *21*  < 40	;PF2 visible
	nop			;[21]+2
	nop			;[23]+2
	nop			;[25]+2
	;six cycles available  Might be able to do something here
	lda PFData0Right-1,X          ;[27]+4
	;PF0 no longer visible, safe to rewrite
	sta PF0                 ;[31]+3 = *34* 
	lda PFData1Right-1,X		;[34]+4
	;PF1 no longer visible, safe to rewrite
	sta PF1			;[38]+3 = *41*  
	lda PFData2Right-1,X		;[41]+4
	;PF2 rewrite must begin at exactly cycle 45!!, no more, no less
	sta PF2			;[45]+2 = *47*  ; >



	dey ;ok, we've drawn one more scaneline for this 'pixel'
	bne NotChangingWhatTitlePixel ;go to not changing if we still have more to do for this pixel
	dex ; we *are* changing what title pixel we're on...

	beq DoneWithTitle ; ...unless we're done, of course
	
	ldy #scanlinesPerTitlePixel ;...so load up Y with the count of how many scanlines for THIS pixel...
NotChangingWhatTitlePixel
	
	jmp TitleShowLoop

DoneWithTitle	

	;clear out the playfield registers for obvious reasons	
	lda #0
	sta PF2 ;clear out PF2 first, I found out through experience
	sta PF0
	sta PF1

;just burning scanlines....you could do something else
	ldy #137
TitlePostLoop
	sta WSYNC
	dey
	bne TitlePostLoop

; usual vblank
	lda #2		
	sta VBLANK 	
	ldx #30		
OverScanWait
	sta WSYNC
	dex
	bne OverScanWait
	jmp  MainLoop      

;
; the graphics!
; I suggest my online javascript tool, 
;PlayfieldPal at http://alienbill.com/vgames/playerpal/
;to draw these things. Just rename 'em left and right

PFData0Left
        .byte #%00000000
        .byte #%00000000
        .byte #%10000000
        .byte #%01000000
        .byte #%00000000
        .byte #%00000000

PFData1Left
        .byte #%00000000
        .byte #%00000000
        .byte #%00010011
        .byte #%10101010
        .byte #%10010010
        .byte #%10000000

PFData2Left
        .byte #%00000000
        .byte #%00000000
        .byte #%10001101
        .byte #%10001001
        .byte #%11011001
        .byte #%10000000



PFData0Right
        .byte #%00000000
        .byte #%00000000
        .byte #%01000000
        .byte #%11000000
        .byte #%01010000
        .byte #%11000000

PFData1Right
        .byte #%00000000
        .byte #%00000000
        .byte #%00010010
        .byte #%00101010
        .byte #%10010011
        .byte #%00000000

PFData2Right
        .byte #%00001100
        .byte #%00010000
        .byte #%00011001
        .byte #%00010101
        .byte #%00011000
        .byte #%00000000

	org $FFFC
	.word Start
	.word Start

Back to 2600 Cookbook