21. Finished Platformer

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.

26_full_game1

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. >> 3 is divide by 8, so it will change colors every 8th frame. The & 3 keeps the value in the 0-3 range. That would be similar to modulus 4 (% 4), but much much faster code.

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.

26_Tiled

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

26_Tiled2

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_coll_D();

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.

full_game_debug

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. This version has a limit of 16 sprite objects at a time. If you had more, then you would have to use a different shuffling algorithm.

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

.

Update 2023

I rewrote the collision code and made some other small changes. One thing, I made the graphics look a little nicer. This is what it looks like now, with just a little more effort. Oh, and if you die (or win) you can now press START (after a short pause) to restart the game (so you don’t have to press RESET). I just had to add another game mode, for it to wait a few seconds and then read for the START button.

screenshot26

.

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.

2 thoughts on “21. Finished Platformer

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

    Like

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

      Like

Leave a comment