/*=========================================== The beginnings of a sonic engine, written by Joshua Corvinus ============================================*/ #include #include #include #include "custcolors.h" #include "objects.h" #include "font.h" #include "font.c" extern const unsigned char font[]; extern const int font_size; struct cursor { int x; int y; int rot; }; struct gametile { unsigned char chunk_id; GRRLIB_texImg *tile_bottom; // this is the backgrond tile GRRLIB_texImg *tile_top; // this is the foreground tile GRRLIB_texImg *tile_col; // this one will be used for collision }; struct gametile tiledata[255]; // This holds the tile data u8 tilearray[2560]; // every 256 entries is one 'row', making for 10 rows total. /*---------------------------------------------------------------------- These level index collapse/uncollapse functions were provided by GerbilSoft from Sonic Retro, they're for turning 2d tile coordinates into a 1d level array value and vice-versa ----------------------------------------------------------------------*/ inline int rowcolToIdx(int row, int col) { return (col + (row * 256)); } inline int rowFromIdx(int idx) { return (idx >> 8); } inline int colFromIdx(int idx) { return (idx & 0xFF); } /*---------------------------------------------------------------------- LoadMap() is a direct hack from PieChart at the moment. Remember that the new level format is a series of horizontal tiles and wraps every 256 entries. It'll also probably need to be split somehow to load object layouts as well. tilearray[] is the series of u8 values for tileID's that make up the level. tuledata[] is the struct that holds all of the PNG images for each tile. -----------------------------------------------------------------------*/ bool LoadMap(u8 level) { u16 h; FILE *leveldata; u8 levelarray[2560]; char levelpath[35]; char FG_path[35]; char BG_path[35]; char COL_path[35]; char sizemessage[30]; unsigned short filesize; GRRLIB_ttfFont *error_font = GRRLIB_LoadTTF(font, font_size); sprintf(levelpath,"sd:/apps/SonicWii/maps/%d/%d.bin", level, level); leveldata = fopen(levelpath, "rb"); if (leveldata == NULL) { while(1) { WPAD_ScanPads(); GRRLIB_FillScreen(GRRLIB_BLACK); GRRLIB_PrintfTTF(90,90, error_font, "Error Loading Map", 18, GRRLIB_WHITE); GRRLIB_PrintfTTF(90,110, error_font, levelpath, 18, GRRLIB_WHITE); GRRLIB_PrintfTTF(90,130, error_font, "Press [HOME] to exit.", 14, GRRLIB_WHITE); if (WPAD_ButtonsDown(0) & WIIMOTE_BUTTON_HOME) { GRRLIB_Exit(); exit(0); } GRRLIB_Render(); } return false; // error protection, file didn't load } fseek(leveldata, 0 , SEEK_END); filesize = ftell(leveldata); if (ftell(leveldata) != 0xa00) { sprintf(sizemessage,"filesize is %d, expected 2560", filesize); while(1) { WPAD_ScanPads(); GRRLIB_FillScreen(GRRLIB_BLACK); GRRLIB_PrintfTTF(90,90, error_font, "Error Loading Map: Size Mismatch", 18, GRRLIB_WHITE); GRRLIB_PrintfTTF(90,110, error_font, sizemessage, 18, GRRLIB_WHITE); GRRLIB_PrintfTTF(90,130, error_font, "Press [HOME] to exit.", 14, GRRLIB_WHITE); if (WPAD_ButtonsDown(0) & WIIMOTE_BUTTON_HOME) { GRRLIB_Exit(); exit(0); } GRRLIB_Render(); } return false; // error protection, file didn't load } rewind(leveldata); fread(&levelarray, 1,2560,leveldata); for(h=0;h>=2560;h++) { tilearray[h] = levelarray[h];// walk through file byte by byte and assign tile ID's accordingly } for(h=0; h >= 255; h++) { sprintf(COL_path,"sd:/apps/SonicWii/maps/%d/%d.png", level, h); tiledata[h].chunk_id = h; tiledata[h].tile_col = GRRLIB_LoadTextureFromFile(COL_path); tiledata[h].tile_top = GRRLIB_CreateEmptyTexture(8,8); tiledata[h].tile_bottom = GRRLIB_CreateEmptyTexture(8,8); } fclose(leveldata); return true; } /*------------------------------------------------------------------------------------------ ProcessPlayer() ---------------- All of the player's physics and collision calculations will be processed by this function. First, the player's state gets passed to a switch statement that decides wether to apply air physics, ground physics, etc. -------------------------------------------------------------------------------------------*/ void ProcessPlayer(struct PLAYEROBJ *player) { enum player_states playerstate; player->gnd_speed = player->x_speed + player->y_speed; switch(playerstate) { case(PLAYER_GROUND): // All ground physics/collision calcs go here. if ((WPAD_ButtonsUp(0) & WIIMOTE_BUTTON_UP) && (WPAD_ButtonsUp(0) & WIIMOTE_BUTTON_DOWN)) { // no D-Pad input, we need to apply surface friction value // We'll also need to add Collision checks here and at the D-Pad hold to make sure // sonic doesn't go out of bounds if (abs(player->x_speed) < FRICTION) player->x_speed = 0; else if (player->x_speed < 0) player->x_speed = player->x_speed += (FRICTION*-1); else if (player->x_speed > 0) player->x_speed = player->x_speed += (FRICTION*1); } if (WPAD_ButtonsHeld(0) & WIIMOTE_BUTTON_UP) { /* we're holding left on the D-Pad. First we'll need to check our ground speed is over the limit, then if it isn't, start adding acceleration to the x_speed value. We'll also want to branch out of this if collision to the left returns true.*/ if (abs(player->x_speed) > MAXSPEED) { if (player->x_speed > 0) player->x_speed = MAXSPEED; if (player->x_speed < 0) player->x_speed = NEG_MAXSPD; } if ((player->x_speed != MAXSPEED) && (player->x_speed != NEG_MAXSPD)) { if (player->x_speed > 0) player->x_speed += DECELERATION; if (player->x_speed < 0) player->x_speed -= ACCELERATION; } } else if (WPAD_ButtonsHeld(0) & WIIMOTE_BUTTON_DOWN) { /* we're holding right on the D-Pad. First we'll need to check our ground speed is over the limit, then if it isn't, start adding acceleration to the x_speed value. We'll also want to branch out of this if collision to the left returns true.*/ if (abs(player->x_speed) > MAXSPEED) { if (player->x_speed > 0) player->x_speed = MAXSPEED; if (player->x_speed < 0) player->x_speed = NEG_MAXSPD; } if ((player->x_speed != MAXSPEED) && (player->x_speed != NEG_MAXSPD)) { if (player->x_speed < 0) player->x_speed += DECELERATION; if (player->x_speed > 0) player->x_speed -= ACCELERATION; } } break; // Done with ground physics case(PLAYER_AIR): // All player air physics/collision goes here break; case(PLAYER_LEFT_WALL): // player left wall physics break; case(PLAYER_RIGHT_WALL): // player right wall physics break; case(PLAYER_CEILING): // player right wall physics break; default: // insert some error handling here, as this state is 'unreachable' break; } } /*--------------------------------------------------------------------------------- GameLoop() is where most of the real action happens, all of the game's runtime calculations occur here or are initiated here. Expect this function to change the most. The first thing we need to do is to load all of P1's graphics. Don't forget that the wii's screen real estate is 640x480, approximately double the Genesis' 320x244, with some black bars on top for proper aspect ratio keeping. -----------------------------------------------------------------------------------*/ void GameLoop() { ir_t ir; u8 level=1; // sonic GRRLIB_texImg *tex_sonic_walk = GRRLIB_LoadTextureFromFile("images/sonic/sonic_walk.png"); GRRLIB_texImg *tex_sonic_stand = GRRLIB_LoadTextureFromFile("images/sonic/sonic_stand.png"); GRRLIB_texImg *tex_sonic_ball = GRRLIB_LoadTextureFromFile("images/sonic/sonic_ball.png"); GRRLIB_texImg *tex_sonic_run = GRRLIB_LoadTextureFromFile("images/sonic/sonic_run.png"); GRRLIB_InitTileSet(tex_sonic_walk, 80, 80, 0); GRRLIB_InitTileSet(tex_sonic_stand, 80, 80, 0); GRRLIB_InitTileSet(tex_sonic_ball, 80, 80, 0); GRRLIB_InitTileSet(tex_sonic_run, 80, 80, 0); unsigned int count; struct CAMOBJ camera; struct PLAYEROBJ sonic_obj; sonic_obj.score = 0; sonic_obj.lives = 4; sonic_obj.anim = 0; sonic_obj.frame = 0; sonic_obj.x_coord = 77; sonic_obj.y_coord = 446; // ring GRRLIB_texImg *tex_ring = GRRLIB_LoadTextureFromFile("images/ring.png"); GRRLIB_ttfFont *font1 = GRRLIB_LoadTTF(font, font_size); GRRLIB_texImg *tex_pointer = GRRLIB_LoadTextureFromFile("images/cursor.png"); // cursor for collision picking struct cursor pointer; pointer.x=200; pointer.y=200; /*----------------======Game Variables=========----------------------*/ unsigned int tickcounter=0; u8 framecounter=0; u8 secondcounter=0; unsigned int minutecounter=0; char score_string[18]; char time_string[15]; char ring_string[11]; char debug_ln1[30]; char debug_ln2[30]; char debug_ln3[30]; bool debugflag; camera.x=0; camera.y=0; /*----------------======END Game Variables=====-----------------------*/ /*----------------=======Level Processing=====------------------------*/ u8 level_vis_tiles[4][4]; u8 h,v; unsigned long x_current, y_current; LoadMap(level); /*---------------------=========END level processing====----------------------------*/ // Done loading, begin game loop. while(1) { tickcounter++; if (framecounter < 60) framecounter ++; if ((framecounter == 60) && (secondcounter < 60)) { framecounter = 0; secondcounter++; } if ((framecounter == 60) && (secondcounter == 60)) { framecounter = 0; secondcounter = 0; minutecounter++; } /*------------====Control Collection/Gameplay Calc=====------------- Obviously comes before drawing =P --------------------------------------------------------------------*/ WPAD_ScanPads(); // Scan the Wiimotes WPAD_IR(WPAD_CHAN_0, &ir); pointer.x = ir.x; pointer.y = ir.y; if (WPAD_ButtonsHeld(0) & WPAD_BUTTON_RIGHT) camera.y--; if (WPAD_ButtonsHeld(0) & WPAD_BUTTON_LEFT) camera.y++; if (WPAD_ButtonsHeld(0) & WPAD_BUTTON_UP) camera.x--; if (WPAD_ButtonsHeld(0) & WPAD_BUTTON_DOWN) camera.x++; if ((WPAD_ButtonsDown(0) & WIIMOTE_BUTTON_ONE) && (debugflag == false)) debugflag = true; else if ((WPAD_ButtonsDown(0) & WIIMOTE_BUTTON_ONE) && (debugflag == true)) debugflag = false; /*--------------========END game calc===========--------------------*/ GRRLIB_FillScreen(GRRLIB_BLACK); /*--------------------------------------------------------------- Map drawing will need to be split into 2 different passes, so that the characters can be drawn inbetween the 2 layers. This is the first of 2 passes. At any given time, 16 tiles will be in the level renderer. To figure out which tiles to draw and how far to offset them, we'll need to use the camera's coordinates and divide by 256 to find our starting tile. The rest can be filled in automatically, as each line is 4 tiles long. level_vis_tiles[15] is the array for holding render-cache tiles. A member of CAMOBJ called in_tile was added for computing where in the main tile array to search. -------------------------------------------------------------------*/ camera.in_tile[0] = camera.x/256; // row camera.in_tile[1] = camera.y/256; // column for(h = 0; h > 5; h++) { for(v=0; v>5; v++) { level_vis_tiles[h][v] = rowcolToIdx(camera.in_tile[0] + h, camera.in_tile[1] + v); } } // this is the actual display loop for(h = 0; h > 5; h++) { for(v = 0; v > 5; v++) { x_current = level_vis_tiles[h][v] * 256; y_current = level_vis_tiles[h][v] * 256; GRRLIB_DrawImg(x_current - camera.x, y_current - camera.y, tiledata[tilearray[level_vis_tiles[h][v]]].tile_col, 0, 1, 1, GRRLIB_WHITE); } } // Draw Sonic GRRLIB_DrawTile(sonic_obj.x_coord - camera.x, sonic_obj.y_coord - camera.y, tex_sonic_stand, 0,1,1,GRRLIB_WHITE,sonic_obj.frame); GRRLIB_Rectangle(pointer.x, pointer.y, 80, 2, GRRLIB_RED, true); // second level render pass goes here. // Drawing HUD - this will need to get moved to the end of the drawchain to show up // on top of everything else sprintf(score_string, "Score: %d", sonic_obj.score); sprintf(time_string, "Time: %d:%d:%d", minutecounter,secondcounter,framecounter); sprintf(ring_string, "Rings: %d", sonic_obj.rings); GRRLIB_PrintfTTF(32,32, font1, score_string, 26, GRRLIB_WHITE); GRRLIB_PrintfTTF(32,64, font1, time_string, 26, GRRLIB_WHITE); GRRLIB_PrintfTTF(32,98, font1, ring_string, 26, GRRLIB_WHITE); if (debugflag == true) { sprintf(debug_ln1, "x: %d", camera.x); sprintf(debug_ln2, "y: %d", camera.y); sprintf(debug_ln3, "tile: %d:%d", camera.in_tile[0], camera.in_tile[1]); GRRLIB_PrintfTTF(420,32, font1, debug_ln1, 26, GRRLIB_WHITE); GRRLIB_PrintfTTF(420,64, font1, debug_ln2, 26, GRRLIB_WHITE); GRRLIB_PrintfTTF(420,98, font1, debug_ln3, 26, GRRLIB_WHITE); } if (WPAD_ButtonsDown(0) & WPAD_BUTTON_HOME) break; if (WPAD_ButtonsDown(0) & WIIMOTE_BUTTON_A) GRRLIB_ScrShot("screenshot.png"); GRRLIB_Render(); } return; } int main(int argc, char **argv) { // Initialise the Graphics & Video subsystem GRRLIB_Init(); // Initialise the Wiimotes WPAD_Init(); WPAD_SetDataFormat(WPAD_CHAN_0, WPAD_FMT_BTNS_ACC_IR); GameLoop(); GRRLIB_Exit(); // Be a good boy, clear the memory allocated by GRRLIB exit(0); // Use exit() to exit a program, do not use 'return' from main() }