//Chase NES game by Shiru (shiru@mail.ru) 01'12 //Feel free to do anything you want with this code, consider it Public Domain //This game is an example for my article Programming NES games in C //include the library #include "neslib.h" #include //#link "famitone2.s" //#link "music.s" //#link "sounds.s" // CHR data //#resource "tileset.chr" //#link "tileset.s" //include nametables for all the screens such as title or game over #include "title_nam.h" #include "level_nam.h" #include "gameover_nam.h" #include "welldone_nam.h" //include nametables for levels #include "level1_nam.h" #include "level2_nam.h" #include "level3_nam.h" #include "level4_nam.h" #include "level5_nam.h" //game uses 12:4 fixed point calculations for enemy movements #define FP_BITS 4 //max size of the game map #define MAP_WDT 16 #define MAP_WDT_BIT 4 #define MAP_HGT 13 //macro for calculating map offset from screen space, as //the map size is smaller than screen to save some memory #define MAP_ADR(x,y) ((((y)-2)<>FP_BITS); if(pad_trigger(0)&PAD_START) break; iy+=dy; if(iy<0) { iy=0; dy=-dy>>1; } if(dy>(-8<>8); if(pad_trigger(0)&PAD_START) break; ++frame_cnt; } } pal_fade_to(0); } //set up a move in the specified direction if there is no wall void player_move(unsigned char id,unsigned char dir) { px=player_x[id]>>(TILE_SIZE_BIT+FP_BITS); py=player_y[id]>>(TILE_SIZE_BIT+FP_BITS); switch(dir) { case DIR_LEFT: --px; break; case DIR_RIGHT: ++px; break; case DIR_UP: --py; break; case DIR_DOWN: ++py; break; } if(map[MAP_ADR(px,py)]==TILE_WALL) return; player_cnt[id]=TILE_SIZE<2) vram_put(0x10+num/100); if(len>1) vram_put(0x10+num/10%10); vram_put(0x10+num%10); } //the main gameplay code void game_loop(void) { oam_clear(); i=game_level<<1; vram_adr(NAMETABLE_A); vram_unrle(levelList[i]); //unpack level nametable vram_adr(NAMETABLE_A+0x0042); vram_write((unsigned char*)statsStr,27); //add game stats string pal_bg(levelList[i+1]); //set up background palette pal_spr(palGameSpr); //set up sprites palette player_all=0; items_count=0; items_collected=0; //this loop reads the level nametable back from VRAM, row by row, //constructs game map, removes spawn points from the nametable, //and writes back to the VRAM i16=NAMETABLE_A+0x0080; ptr=0; wait=0; for(i=2;i>FP_BITS; if(player_wait[i]) { if(player_wait[i]>=16||player_wait[i]&2) py=240; } oam_meta_spr(player_x[i]>>FP_BITS,py,spr,sprListPlayer[i]); spr-=16; } //wait for next frame //it is here and not at beginning of the loop because you need //to update OAM for the very first frame, and you also need to do that //right after object parameters were changed, so either OAM update should //be in a function that called before the loop and at the end of the loop, //or wait for NMI should be placed there //otherwise you would have situation update-wait-display, i.e. //one frame delay between action and display of its result ppu_wait_frame(); ++frame_cnt; //slowly fade virtual brightness to needed value, //which is max for gameplay or half for pause if(!(frame_cnt&3)) { if(!game_paused&&bright<4) ++bright; if( game_paused&&bright>2) --bright; pal_bright(bright); } //poll the gamepad in the trigger mode i=pad_trigger(0); //it start was released and then pressed, toggle pause mode if(i&PAD_START) { game_paused^=TRUE; music_pause(game_paused); } //don't process anything in pause mode, just display latest game state if(game_paused) continue; //CHR bank switching animation with different speed for background and sprites bank_bg((frame_cnt>>4)&1); bank_spr((frame_cnt>>3)&1); //a counter that does not allow objects to move while spawn animation plays if(wait) { --wait; if(!wait) music_play(MUSIC_GAME);//start the music when all the objects spawned } //check for level completion condition if(items_collected==items_count) { music_play(MUSIC_CLEAR); game_done=TRUE; game_clear=TRUE; } //process all the objects //player and enemies are the same type of object in this game, //to make code simpler and shorter, but generally they need to be //different kind of objects for(i=0;i=(player_x[0]+(12<=(player_y[0]+(12<>(TILE_SIZE_BIT+FP_BITS)), (player_y[i]>>(TILE_SIZE_BIT+FP_BITS))); if(map[i16]==TILE_ITEM) { map[i16]=TILE_EMPTY; //mark as collected in the game map sfx_play(SFX_ITEM,2); ++items_collected; //get address of the tile in the nametable i16=NAMETABLE_A+0x0080+(((player_y[i]>>(TILE_SIZE_BIT+FP_BITS))-2)<<6)| ((player_x[i]>>(TILE_SIZE_BIT+FP_BITS))<<1); //replace it with empty tile through the update list update_list[0]=i16>>8; update_list[1]=i16&255; update_list[3]=update_list[0]; update_list[4]=update_list[1]+1; i16+=32; update_list[6]=i16>>8; update_list[7]=i16&255; update_list[9]=update_list[6]; update_list[10]=update_list[7]+1; //update number of collected items in the game stats update_list[14]=0x10+items_collected/100; update_list[17]=0x10+items_collected/10%10; update_list[20]=0x10+items_collected%10; } } } } if(!player_cnt[i]) //movement to the next tile is done, set up new movement { if(!i) //this is the player, process controls { //get gamepad state, it was previously polled with pad_trigger j=pad_state(0); //this is a tricky part to make controls more predictable //when you press two directions at once, sliding by a wall //to take turn into a passage on the side //this piece of code gives current direction lower priority //through testing it first //bits in player_dir var are matching to the buttons bits if(j&player_dir[0]) { j&=~player_dir[0]; //remove the direction from further check player_move(i,player_dir[0]); //change the direction } //now continue control processing as usual if(j&PAD_LEFT) player_move(i,DIR_LEFT); if(j&PAD_RIGHT) player_move(i,DIR_RIGHT); if(j&PAD_UP) player_move(i,DIR_UP); if(j&PAD_DOWN) player_move(i,DIR_DOWN); } else //this is an enemy, run AI { //the AI is very simple //first we create list of all directions that are possible to take //excluding the direction that is opposite to previous one i16=MAP_ADR((player_x[i]>>8),(player_y[i]>>8)); ptr=player_dir[i]; j=0; if(ptr!=DIR_RIGHT&&map[i16-1]!=TILE_WALL) dir[j++]=DIR_LEFT; if(ptr!=DIR_LEFT &&map[i16+1]!=TILE_WALL) dir[j++]=DIR_RIGHT; if(ptr!=DIR_DOWN &&map[i16-MAP_WDT]!=TILE_WALL) dir[j++]=DIR_UP; if(ptr!=DIR_UP &&map[i16+MAP_WDT]!=TILE_WALL) dir[j++]=DIR_DOWN; //randomly select a possible direction player_move(i,dir[rand8()%j]); //if there was more than one possible direction, //i.e. it is a branch and not a corridor, //attempt to move towards the player if(j>1) { if(ptr!=DIR_DOWN &&player_y[0]player_y[i]) player_move(i,DIR_DOWN); if(ptr!=DIR_RIGHT&&player_x[0]player_x[i]) player_move(i,DIR_RIGHT); } } } } } delay(100); pal_fade_to(0); } //this is where the program starts extern const void sound_data[]; extern const void music_data[]; void main(void) { famitone_init(&music_data); sfx_init(&sound_data); nmi_set_callback(famitone_update); while(1)//infinite loop, title-gameplay { title_screen(); game_level=0; game_lives=4; while(game_lives&&game_level