2600 101

Happy Face

So we've come a long way. But I think there's a small chance you'll want to make a game with better graphics than Pong. It's just a theory. So today, we get our "player" graphics mojo working.

Doing player graphics isn't that much more difficult than using the missile we've been using for the last few demos. The trick is that instead of a single on/off boolean value that says if the Atari is drawing the missile, we set a full byte, 8 bits, that make up the graphics for the player for that line. So not only do we have to count the lines and turn the player on or off, but we have to load GRP0 (or GRP1 for the second player) with the correct eight bit value: 1 means the pixel in that location (read left to right, like you'd expect) is on, 0 means it's off. So #%10001101 would have one pixel on, three off, two on, one off, and one on.

You change the color of the player the same you do for a missile, by setting COLUMP0 or COLUMP1 (the same register sets the color for a player and its associated missile.) Some of the fancier programs change the color of a player graphic beteen each scanline, given that distinctive horizontal-band colorization effect that the Atari is known for. (At least for people who pay attention to that kind of thing.) Also like Missiles, there are tricks to duplicate the player or make it wider...I'll let you look that up in the Stella guide.

I guess we might as well talk about color, 'cause I've been faking it up to now. (Unlike the rest of this fine tutorial, of course, which is completely unfaked.) B.Watson provides us with this handy chart:



Colors are most easily thought of as a two digit hex number. The left digit is the color, the right bit is the luminosity, or brightness. So to use this chart, find the hex digit for the color column you want, that's the first digit, then pick the luminosity going down, that's the right digit. So $76 is a medium blue. $0E is white (the rightmost bit of the luminosity doesn't matter, so $0E is the same as $0F) $D2 is a dark green. (This is all aproximate of course; different emulators and different TVs will display things differently, and this chart is only for NTSC TVs, not that crazy European PAL stuff)

One somewhat confusing thing is that (usually) Atari graphics are stored upsidedown in the program listing. This is because usually you have a positive number keeping track of how many lines are left to go as you're drawing the player, and this number is decreasing as you go through the scanlines. Combine that with the fact that the memory offset operation adds a number to the base memory location for the graphics, and it usually ends up making more sense to store the things bottom to top. You'll see in today's example.

One member of that tiny group "things the Atari does to make your life easier" is that if you set bit D3 of REFP0 (or REFP1) to 1, it gives you the mirror image of the player, so you don't have to have a seperate copy of the graphics if you want to have a thing moving the other way.

Another even more important thing "to make your life easier" is that the Atari can tell if any 2 of its 6 items (players (P), missiles (M), ball (BL), and playfield (PF)) have collided. There are 15 1-bit latches. (Do the math...the playfield could've hit any of the other 5 object, the ball could've hit any of the other 4 objects (we've already counted the playfield/ball collision) etc.... 5+4+3+2+1 = 15.)
Here is the relevant extra from the Stella guide:

6-bit AddressAddress Name 76543210 FunctionD7D6
0CXM0P 11...... read collision M0 P1 M0 P0
1CXM1P 11...... read collision M1 P0 M1 P1
2CXP0FB 11...... read collision P0 PF P0 BL
3CXP1FB 11...... read collision P1 PF P1 BL
4CXM0FB 11...... read collision M0 PF M0 BL
5CXM1FB 11...... read collision M1 PF M1 BL
6CXBLPF 1....... read collision BL PF unused
7CXPPMM 11...... read collision P0 P1 M0 M1

What this says is if, say, Missile 1 has hit Player 0, then D7 of CXM1P will be 1. If the two missiles collide, then D6 of CXPPMM will be 1. There's one final trick which seems confusing at first but is secretly useful: when two things collide, that latch stays set with a 1 even if they then move apart. Latches stay set until you hit the register CXCLR, which resets all the latches to zero. So that way, you don't have to check for collisions every time if you don't want to. In practice, most games will read all the collisions they care about and then hit CXCLR every time screen frame, but it's nice to have the option.

Animation is fairly simple...every time you want to show the next position of the guy's animation, just load the graphic from a different memory location that has the next "frame" of animation.

So, I think we're ready for todays example. We'll move a Happy Face player around the screen with the joystick. The code will be based on last lesson's "moving dot", changes in red. To liven things up, I'll throw the sweeping thin red line back in (but using missile 1 instead of missile 0) Every time it hits the player, the screen background will change to a value reflecting the vertical position of the happy face. We'll use a non-symmetrical happy face, giving a slight 3D effect, as well as letting us tell which way the player is facing.
; move a happy face with the joystick by Kirk Israel
; (with a can't'dodge'em line sweeping across the screen)

	processor 6502
	include vcs.h
	org $F000

YPosFromBot = $80;
VisiblePlayerLine = $81;

;generic start up stuff...
Start
	SEI	
	CLD  	
	LDX #$FF	
	TXS	
	LDA #0		
ClearMem 
	STA 0,X		
	DEX		
	BNE ClearMem	
	
	LDA #$00   ;start with a black background
	STA COLUBK	
	LDA #$1C   ;lets go for bright yellow, the traditional color for happyfaces
	STA COLUP0
;Setting some variables...
	LDA #80
	STA YPosFromBot	;Initial Y Position

;; Let's set up the sweeping line. as Missile 1

	
	LDA #2
	STA ENAM1  ;enable it
	LDA #33
	STA COLUP1 ;color it

	LDA #$20	
	STA NUSIZ1	;make it quadwidth (not so thin, that)

	
	LDA #$F0	; -1 in the left nibble
	STA HMM1	; of HMM1 sets it to moving


;VSYNC time
MainLoop
	LDA #2
	STA VSYNC	
	STA WSYNC	
	STA WSYNC 	
	STA WSYNC	
	LDA #43	
	STA TIM64T	
	LDA #0
	STA VSYNC 	


;Main Computations; check down, up, left, right
;general idea is to do a BIT compare to see if 
;a certain direction is pressed, and skip the value
;change if so

;
;Not the most effecient code, but gets the job done,
;including diagonal movement
;

; for up and down, we INC or DEC
; the Y Position

	LDA #%00010000	;Down?
	BIT SWCHA 
	BNE SkipMoveDown
	INC YPosFromBot
SkipMoveDown

	LDA #%00100000	;Up?
	BIT SWCHA 
	BNE SkipMoveUp
	DEC YPosFromBot
SkipMoveUp

; for left and right, we're gonna 
; set the horizontal speed, and then do
; a single HMOVE.  We'll use X to hold the
; horizontal speed, then store it in the 
; appropriate register


;assum horiz speed will be zero
	LDX #0	

	LDA #%01000000	;Left?
	BIT SWCHA 
	BNE SkipMoveLeft
	LDX #$10	;a 1 in the left nibble means go left

;; moving left, so we need the mirror image
	LDA #%00001000   ;a 1 in D3 of REFP0 says make it mirror
	STA REFP0

SkipMoveLeft
	LDA #%10000000	;Right?
	BIT SWCHA 
	BNE SkipMoveRight
	LDX #$F0	;a -1 in the left nibble means go right...

;; moving right, cancel any mirrorimage
	LDA #%00000000
	STA REFP0

SkipMoveRight


	STX HMP0	;set the move for player 0, not the missile like last time...



; see if player and missile collide, and change the background color if so

	;just a review...comparisons of numbers always seem a little backwards to me,
	;since it's easier to load up the accumulator with the test value, and then
	;compare that value to what's in the register we're interested.
	;in this case, we want to see if D7 of CXM1P (meaning Player 0 hit
	; missile 1) is on. So we put 10000000 into the Accumulator,
	;then use BIT to compare it to the value in CXM1P

	LDA #%10000000
	BIT CXM1P		
	BEQ NoCollision	;skip if not hitting...
	LDA YPosFromBot	;must be a hit! load in the YPos...
	STA COLUBK	;and store as the bgcolor
NoCollision
	STA CXCLR	;reset the collision detection for next time




WaitForVblankEnd
	LDA INTIM	
	BNE WaitForVblankEnd	
	LDY #191 	


	STA WSYNC	
	STA HMOVE 	
	
	STA VBLANK  	


;main scanline loop...


ScanLoop 
	STA WSYNC 	

; here the idea is that VisiblePlayerLine
; is zero if the line isn't being drawn now,
; otherwise it's however many lines we have to go

CheckActivatePlayer
	CPY YPosFromBot
	BNE SkipActivatePlayer
	LDA #8
	STA VisiblePlayerLine 
SkipActivatePlayer



;set player graphic to all zeros for this line, and then see if 
;we need to load it with graphic data
	LDA #0		
	STA GRP0  

;
;if the VisiblePlayerLine is non zero,
;we're drawing it now!
;
	LDX VisiblePlayerLine	;check the visible player line...
	BEQ FinishPlayer		;skip the drawing if its zero...
IsPlayerOn	
	LDA BigHeadGraphic-1,X	;otherwise, load the correct line from BigHeadGraphic
				;section below... it's off by 1 though, since at zero
				;we stop drawing
	STA GRP0		;put that line as player graphic
	DEC VisiblePlayerLine 	;and decrement the line count
FinishPlayer


	DEY		
	BNE ScanLoop	

	LDA #2		
	STA WSYNC  	
	STA VBLANK 	
	LDX #30		
OverScanWait
	STA WSYNC
	DEX
	BNE OverScanWait
	JMP  MainLoop      


; here's the actual graphic! If you squint you can see its
; upsidedown smiling self
BigHeadGraphic
	.byte #%00111100
	.byte #%01111110
	.byte #%11000001
	.byte #%10111111
	.byte #%11111111
	.byte #%11101011
	.byte #%01111110
	.byte #%00111100

	org $FFFC
	.word Start
	.word Start

Yikes, with all that joystick testing and graphics crap, our code listings are getting a bit long! Hopefully you're still able to follow along. And next lesson, I'll tell you why this kernal sucks...

Next:PlayerBufferStuffer

Introduction - The Development Environment - Into The Breach - My First Program -
Kernal Clink - The Joy of Sticks - Happy Face - PlayerBufferStuffer