First thing I added was a title screen. To be honest, I made this as quickly as possible, just to show the proof of concept. I made it in NES Screen Tool, and exported the background as a compressed RLE .h file “title.h”. So the title mode waits for the user to press start, and cycles the color a bit to be a little less boring.
temp1 = get_frame_count();
temp1 = (temp1 >> 3) & 3;
pal_col(3,title_color_rotate[temp1]);
title_color_rotate is an array of 4 possible colors.
Now, I didn’t like making 1 room at a time, so I made it so you could have 1 long Tiled file, and export it to csv, and convert it to arrays with CSV2C_BIG.py.
(I had it auto generate an array of pointers at the bottom, but I didn’t end up using those, so I delete them, and instead made 1 larger array of pointers, with all levels in it).
const unsigned char * const Levels_list[]={
Level1_0,Level1_1,Level1_2,Level1_3,Level1_4,Level1_5,Level1_6,Level1_7,
Level2_0,Level2_1,Level2_2,Level2_3,Level2_4,Level2_5,Level2_6,Level2_7,
Level3_0,Level3_1,Level3_2,Level3_3,Level3_4,Level3_5,Level3_6,Level3_7
};
I am using 2 kinds of enemies and 2 kinds of coins.
And then, I made a picture with all the Sprite objects on it (with transparency for background), and imported that as a separate tileset in Tiled, and added another layer, where the Sprite objects are placed. I placed the enemies on that 2nd layer (as tiles), and exported another csv file. Then I wrote another .py file “CSV2C_SP.py” to convert those into arrays.
Well, I didn’t end up using it exactly like this. It mixes the coins and the enemies, and I want them in separate arrays. So, I cut and pasted the different kinds of objects into 2 different arrays. But the .py file is helpful, and definitely sped this up.
These arrays might need to be edited slightly, like if we need coins at a different X offset from the tile grid.
Again, I made 2 kinds of enemies. The chasers now collide with walls. I was going to use the same code that the hero used, but decided it was too slow to check many objects this way, so I wrote a much simpler one.
bg_collision_fast(). This only checks 2 points instead of 4.
The chaser code isn’t very smart, they only move X and never change Y. If you put them on a platform, they would float right off it like a ghost. Maybe in the future I will edit this with improved enemy move logic, so he won’t float off a platform, but rather change directions, or fall, or something.
The other enemy is the bouncer. He just bounces up and down. He checks the floor below when falling, to stop exactly at the floor point, reusing the same code from the hero checking the floor.
bg_check_low();
The second kind of coin is just an end-of-level marker. I suppose we could have added some cool sound fx for reaching the end of the level, or an animation. That would have been nice. Currently, it just fades to black in the switch mode.
Oh, yeah. I added more modes. More game states. title, game, pause, end, switch (transition from one level to another). These are fairly obvious as how they work.
Debugging wasn’t too bad. Mostly I was worried about running past the frame and getting slowdown. I was testing code by placing gray_line() around functions. This helped me speed up things. I combined some of the enemy steps to speed them up. And I would put a gray_line() at the end of the game logic to see how far down on the screen we were. Here’s one of the tests, back when I thought I was going to use sprite zero hit and a HUD above.
We don’t want to make our enemy logic too much more complex, nor put too many objects on the same screen, or we might get slow down, so we need to test as we go, and see how many enemies we can fit on a screen before it crawls to a halt. I think we can handle 7 or 8. That’s more than I need, so we’re still ok.
And finally, I put the # of coins as sprites in the top left. I didn’t put it too high up, where it might get cut off on some old TVs. 16 pixels down is fine.
Oh, almost forgot. The sprite shuffing code. Remember from the Sprite page, I mentioned that you can only have 8 sprites per horizontal line? Well, since that is a possibility, we must add some kind of shuffling to the position of each object inside the OAM buffer, so that we don’t have an enemy disappear entirely.
The simplest way to do this would be to change the starting position each frame. oam_set(rand8() * 4); or something like that. It wouldn’t be very good, though.
I decided to go through the list of enemies in a different order each frame.
const unsigned char shuffle_array[]={
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,
0,2,4,6,8,10,12,14,1,3,5,7,9,11,13,15,
15,13,11,9,7,5,3,1,14,12,10,8,6,4,2,0
};
So, the first pass, it goes forward 0-15. The second pass it goes in reverse. The 3rd pass it does even then odds. The 4th pass, reverse that. This would break immediately if we changed the # of enemies, so it could use some improvement. Also, I’m not shuffling coins, so I have to make sure there aren’t too many coins on the same horizontal line.
And here’s our working game, with 3 levels, 8 rooms each. This could have been expanded a bit. It takes about 2000 bytes per level. We have about 16000 bytes left, so we could have added 7-8 more levels… so maybe 10 levels total. If we needed more than that, we would need to think about a different mapper, or maybe some kind of compression.
https://github.com/nesdoug/26_Full_Game/blob/master/full_game.c
https://github.com/nesdoug/26_Full_Game
And, that’s all. Go make some games.
Hey, Doug, in the reset code of the “hello world” program, why is there a colon sandwiched between stx $4010 and lda $2002? And what does the “bpl :-” sandwiched between lda $2002 and lda #$00 mean? This is all before the Blankram routine.
LikeLike
You’re using an older version, which I’m not maintaining any more. But the colon is an “unnamed label” and the :- means jump backwards to the nearest unnamed label.
LikeLike
Hello, it’s me again. This time, I’m using 6502 Assembly alone to program. So far, I have been majorly unsuccessful at trying to even draw a simple sprite on the screen. NESDEV forums couldn’t help me, nor were they probably aware I was using the cc65 compiler. This is the code I wrote based on another code that blinks the screen from gray to grayish blue:
.segment “HEADER”
.byte “NES”
.byte $1a
.byte $02
.byte $01
.byte %00000000
.byte $00
.byte $00
.byte $00
.byte $00
.byte $00,$00,$00,$00,$00
.segment “STARTUP”
.segment “ZEROPAGE”
flag: .res 1
counter: .res 1
.segment “CODE”
WAITVBLANK:
:
BIT $2002
BPL :-
RTS
RESET:
SEI ; disable IRQs
CLD ; disable decimal mode
LDX #$40
STX $4017 ; disable APU frame IRQ
LDX #$FF
TXS ; Set up stack
INX ; now X = 0
STX $2000 ; disable NMI
STX $2001 ; disable rendering
STX $4010 ; disable DMC IRQs
JSR WAITVBLANK
clrmem:
LDA #$00
STA $0000, x
STA $0100, x
STA $0200, x
STA $0400, x
STA $0500, x
STA $0600, x
STA $0700, x
LDA #$FE
STA $0300, x
INX
BNE clrmem
LDA #%10000000
STA flag
JSR WAITVBLANK
LDA #$3F
STA $2006
LDA #$01
STA $2006
LDA #$07
STA $2007
LDA #$17
STA $2007
LDA #$37
STA $2007
LDA #$80
STA $0200
LDA #$10
STA $0201
LDA #$00
STA $0202
LDA #$70
STA $0203
LDA #%00000000
STA counter
LDA #%00010000
STA $2001
LDA #%10001000
STA $2000
LDA #$3F
STA $2006
LDA #$00
STA $2006
STA $2007
Forever:
JMP Forever
VBLANK:
LDA #$00
STA $2003
LDA #$02
STA $4014
INC counter
LDA counter
CMP #$3C
BNE SkipColorChange
LDA flag
EOR #%10000000
STA flag
STA $2001
LDA #$00
STA counter
SkipColorChange:
RTI
.segment “VECTORS”
.word VBLANK
.word RESET
.word 0
.segment “CHARS”
.incbin “ball.chr”
And whenever I compile, I use the following commands;
cc65 blinktest.asm -o blinktest.o -t nes
ld65 blinktest.o -o blinktest.nes -t nes
What am I doing wrong? I need some honest help because this is driving me insane being that what should be a simple task is legendarily difficult for me. If you’re unable to help me with this, can you refer me to someone you know who might know all this? Thanks!
LikeLike