Scrolling

By request, I made a scrolling game engine, with BG collisions. I was supposed to use neslib, but I made so many changes, and basically did 90% of the meat in ASM, that it’s not really a lesson in C. Sorry.

I’m going to have to come back and explain lots of things. I made it way too complicated…so basically, I created a huge top-down all-direction scrolling game engine.

I think I’m going to have to make a simpler one, for my next page (a little later).

Here’s the mess of code, that probably still has bugs in it. I recommend speeding up the emulation, it’s a bit slow.

http://dl.dropboxusercontent.com/s/wapes71qfp3gvxx/lesson28.zip

lesson28

 

Add Music, Famitracker

I wrote some (terrible) songs and sound effects in Famitracker. So, first the songs. Put all songs in the same Famitracker file (adding songs in the module properties). I wrote another page on the limitations of the famitone music engine. Basically, no effects allowed except looping backwards (the Bxx effect, where xx is the frame number).

Export all the songs as a text export, and then process the .txt with text2data (command line program). Make sure to include the -ca65 tag.

Sound effects were put on another Famitracker file. Again, each sound effect on a unique song, using module properties to add another song. You can use any kind of sound effect (I think). But there is an annoying limitation on the length of sound effects. You can actually get around that limitation, by rewriting the famitone.s source (but I can’t show you that rewrite, because it’s not open source code. Sorry.) I have a habit of ending each sound effect with — cut sound and then C00 end of song (on separate lines). I believe this properly ends the sound effect.

Anyway, save all the sound effects as 1 FTM file. Process it with ftm2data (with the -ca65 tag).

Include the music data, in crt0, right after the label music_data: and include the SFX data in crt0 after sounds_data. The crt0 I have, will automatically initialize (set up some pointers) the data, during startup.

Now…use music_play(0); to start the first song. You pass it the song number, starting with 0, of course. If the song number is too high, music will stop. (That’s not the rigtht way to make music stop, btw).

To pause the music, use music_pause(1). To unpause, use music_pause(0). To stop the music (and lose your place in the song) use music_stop().

To trigger a sound effect, use sfx_play(unsigned char sound,unsigned char channel). I didn’t enable (in crt0) more than 1 sound effect channel, but you can, if you want some sound effects to have priority over the others. Notice, I have Square Channel 2 blank in the songs, and exclusively use Square Channel 2 for sound effects. That’s so they don’t collide. If you put song parts on the same channel as the sound effect, make sure you use a less important song part, so the main melody doesn’t cut out every time a sound effect plays.

I just remembered. I have previously used a modified version of famitone.s…but I think I’m using the original verion here. I may change that in the future. No worries. Just make sure the volume of sound effects stays at least as loud as the music channel…or else it won’t play. In a modified version (not here) sound effects always had priority over music, regardless of volume.

That’s it. Easy. The song’s suck, but I didn’t want to spend much time on this. Here’s the example code. Pong, with sound and music (but still no scoreboard).

http://dl.dropboxusercontent.com/s/m02sq4hfod15dct/lesson27.zip

 

Sprite BG Collision, Pong

So, I wrote the simplest game possible. Pong with walls. The ball bounces off the walls (and the paddles)

The first thing I did was make everything in NES Screen Tool. Saved the CHR, saved the BG as a RLE .h file. Saved the Paddles as Metatiles.

Then I made a collision MAP. I just did this by hand. I could have used Tiled and saved the map as a .csv, but it was quicker just to type the map out as an array of zeroes and ones. 1 = the ball will collide with the BG. 0 = the ball will pass through it. Each entry represents a 16×16 block on the screen.

Now, I wanted a random start point and direction for the ball, but I needed a way to get a random seed. I decided to have it spin in a while loop, and count how many loops before a button is pressed on Controller 1.

while (!pad_poll(0)){
  ++Seedy; 
} // Seedy is an int, 2 bytes

set_rand(Seedy);

Now rand8() will give us some actually random numbers.

 

Each frame, the ball’s speed are added to the ball’s position. First X, then check collisions, then Y, then check collisions. If the change in X caused a collision, then I reverse the X speed. If the change in Y caused a collision, then I reverse the Y speed.

X example…

KeepIt = Ball.X; 
Ball.X += Ball_X_Speed;
index = (Ball.Y & 0xf0) + (Ball.X >> 4); 
if (MAP[index]){ 
  Ball_X_Speed = 0 - Ball_X_Speed; 
  Ball.X = KeepIt; 
}

If the ball goes past the paddle, I have it pause for 2 seconds. It counts down frames, until the pause variable is zero, then it respawns the ball with new random direction and position.

I didn’t add a scoreboard. I just wanted to keep it as simple as possible, to show how sprites can collide with the background. It, of course, isn’t really colliding with the background tiles, but rather an array (MAP) that represents the positions of the wall within the room.

Here’s the example code.

lesson26

http://dl.dropboxusercontent.com/s/1a20e1s1pd00ntg/lesson26.zip

 

Sprite Collision, and Controllers

OK, I kind of copied the idea from Shiru’s example code. Controller 1 controls the yellow sprite. Controller 2 controls the blue sprite. The background color changes when a collision is detected.

CONTROLLER READS. I changed this. Normally, with neslib, you have to pass the controller read to a variable, and do a seperate function to get new button presses (trigger mode). I feel it would save zero-page RAM if you could access those internal variables directly.

So normally you would…

pad1 = pad_poll(0); // reads controller 1

I changed it so you do this…

pad_poll(0); // reads controller 1

…then to access the read value, you use the PAD_STATE variable. And to use the new button pressed value, you use the PAD_STATET variable. Example.

if(PAD_STATE & PAD_LEFT){ }

would do something if LEFT is pressed on controller 1.

if(PAD_STATET & PAD_LEFT){ }

would do something if LEFT is pressed this frame, but not the last frame. A new press.


 

You should read the controller at the beginning of each frame. (Or not, if your game logic takes 2 frames to complete, and you want a consistent controller value across both frames.).

For 2 player, you need to make sure you read the 2nd controller.

pad_poll(1); // reads controller 2


 

For the sprite collision, I wrote some ASM code, that expects 2 pointers to structs who’s first 4 bytes are ordered like this…

struct BoxGuy {

unsigned char X;

unsigned char Y;

unsigned char width;

unsigned char height;};

It returns 0 if no collision, and 1 if collision. It’s slightly buggy at the edges of the screen. This is the function…

unsigned char CheckCollision(struct BoxGuy* one, struct BoxGuy* two);

 

NOTE – it could have been written in C. Something like this…

unsigned char CheckCollision (struct BoxGuy* one, struct BoxGuy* two){ 
  if (((one->X + one->width) > two->X) && 
  ((two->X + two->width) > one->X) && 
  ((one->Y + one->height) > two->Y) && 
  ((two->Y + two->height) > one->Y)){ 
     return 1; 
  } 
  else { 
     return 0; 
  }
}

But, I tested that, and it runs twice as slow as the ASM version (478 cycles vs. 255 cycles). I’m assuming that a much more complex game will need to do lots of sprite collision checks. Hmm. Maybe I should make this even more efficient?

So, anyway, if collision == true, change the background color, else, change it back. You can change 1 color with this function.

pal_col(unsigned char index,unsigned char color);

pal_col(0, 0x30); // 0 is the first color in the palette array, 0x30 is white.

Here’s the code.

lesson25

http://dl.dropboxusercontent.com/s/qdkz26l9n3rpx6y/lesson25.zip

 

Interesting side note. Notice how the yellow box is always in front of the blue box. That is because the yellow box was loaded first into the buffer. It has a higher priority. If the blue box was loaded first, it would be on top.

Sprites, Again

Ok, with neslib, you will be loading the OAM (sprite) buffer. It will automatically send them to the PPU during v-blank. The only thing you need to do, is keep track of the index into the OAM buffer.

unsigned char sprid;

Oh, and I clear the OAM at the top of every frame.

oam_clear();

(Alternatively, you could call oam_hide_rest(); at the end of all sprite drawing code.)

 


 

To send 1 sprite, you use this function.

oam_spr(unsigned char x,unsigned char y,unsigned char chrnum,unsigned char attr,unsigned char sprid); // it returns a char, the current index #

sprid = oam_spr(X_position, Y_position, 0, 0, sprid);

chrnum is the tile #. attr is which palette 0-3 (and H and/or V flipping).

 


 

I made some metasprites with NES Screen Tool 2.3. And used the ‘Metasprite/Put single metasprite to clipboard as C’ option, and pasted it into a .c file. Notice that I only made the left half of the sprite graphics, because the right half is just a mirror image of the left, and you can flip sprite tiles.

SpriteCHR

Then, to put a metasprite into the OAM buffer…

oam_meta_spr(unsigned char x,unsigned char y,unsigned char sprid,const unsigned char *data); // it returns a char, the current index #

sprid = oam_meta_spr(X_position2, Y_position, sprid, metasprite);

…where ‘metasprite’ is an array of chars that represents the sprites used, and their relative positions.

So, here’s the code. I’m shifting the position of each sprite object by adding 1 to it’s Y position each frame. You use ppu_wait_nmi() to wait for the beginning of the next frame.

lesson24

http://dl.dropboxusercontent.com/s/92m5gikr51emf9d/lesson24.zip

 

Update Aug 2017 / CFG files

I made some minor code changes to lesson21, lesson22, and lesson23, to fix potential bugs. Specifically added LDX#0 in the NMI code and on rand8(). Also, touched up the face on lesson23.

And, I made some more changes…to the same .zip files. I changed all the .cfg files.

cc65 CFG Files for the most common mappers

http://dl.dropboxusercontent.com/s/0m4p4xh2ae51axm/CFG.zip

Includes-

AXROM, BNROM, CXROM, MMC1, MMC2, MMC3, MMC5, NROM, and UNROM.