Spacecrash day 2 of 7: core gameplay
Today is more “gameplay day” than anything else. We have to try and squeeze some gameplay out of the game concept we are working with. I’ve spent a few hours and it’s starting to show some promise.
First thing: you want to start playing as soon as possible, to get a feel of the gameplay experience. It’s less important to have great sound & graphics feedback, you can easily imagine that. But you need the core control & game result. In this game, I’ve implemented crashing into obstacles and a life/energy bar as soon as possible. After that, it starts to be a game, since you can die!
One concern I have with this design is that, since content generation will be random, it will be quite difficult to control. I will try to use patterns and pattern sequencing to control this better (for example: define a level by sections, and manually control parameters controlling the random generation of each section). But still, you don’t have all control. To me, this means it’s better to make a game which is somewhat “forgiving”: crashing into an obstacle shouldn’t finish the game, since we won’t even be sure we won’t generate situations where it is impossible not to crash into something.
Here are the changes I have done in the basic support:
1) I added a function to normalize a vector in base.h:
1 2 3 4 |
// add to base.h inline vec2 vunit (vec2 v) { return vscale(v, 1.f/vlen(v)); } |
2) I have added an “rgba” type to hold color, and a few functions to use random number generation more easily. Also, I’ve added extra parameters to the function that draws a rectangle with a given texture. See the changes in core.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// core.h #ifndef _CORE_H_ #define _CORE_H_ //----------------------------------------------------------------------------- inline float CORE_FRand(float from, float to) { return from + (to-from)*(rand()/(float)RAND_MAX); } inline unsigned CORE_URand(unsigned from, unsigned to) { return from + rand()%((unsigned)(to-from+1)); } inline bool CORE_RandChance(float chance) { return CORE_FRand(0.f, 1.f) < chance; } inline float CORE_FSquare(float f) { return f * f; } //----------------------------------------------------------------------------- struct rgba { float r, g, b, a; }; inline rgba vmake(float r, float g, float b, float a) { rgba c; c.r = r; c.g = g; c.b = b; c.a = a; return c; } #define COLOR_WHITE vmake(1.f,1.f,1.f,1.f) //----------------------------------------------------------------------------- // Bitmap/texture functions int CORE_LoadBmp(const char filename[], bool wrap); ivec2 CORE_GetBmpSize(int texix); void CORE_UnloadBmp(int texix); void CORE_RenderCenteredSprite(vec2 pos, vec2 size, int texix, rgba color = COLOR_WHITE, bool additive = false); #endif |
Now, let me show you the new version of CORE_RenderCenteredSprite() and show it to you (the rest of core.cpp doesn’t change from yesterday’s version):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// add to core.cpp //----------------------------------------------------------------------------- void CORE_RenderCenteredSprite(vec2 pos, vec2 size, int texix, rgba color, bool additive) { glColor4f(color.r, color.g, color.b, color.a); if (additive) glBlendFunc(GL_SRC_ALPHA, GL_ONE); else glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); vec2 p0 = vsub(pos, vscale(size, .5f)); vec2 p1 = vadd(pos, vscale(size, .5f)); glBindTexture( GL_TEXTURE_2D, g_textures[texix].tex ); glBegin( GL_QUADS ); glTexCoord2d(0.0, 0.0); glVertex2f(p0.x, p0.y); glTexCoord2d(g_textures[texix].w, 0.0); glVertex2f(p1.x, p0.y); glTexCoord2d(g_textures[texix].w, g_textures[texix].h); glVertex2f(p1.x, p1.y); glTexCoord2d(0.0, g_textures[texix].h); glVertex2f(p0.x, p1.y); glEnd(); } |
This function accepts two new parameters that the one yesterday didn’t. The first one, it accepts a color. We send this color over to OpenGL with the glColor4f() call, and what this does is it multiplies each color component of each pixel by the global color we have set (the color we are setting is 0.0 to 1.0 in each component). If we set color to all 1.0f, then this will not affect anything at all. But if we set all components to 0.5f, it will divide all R,G,B,A by half, and thus draw something darker (half as light), and also with alpha = 0.5, which is probably semi-transparent.
This parameter allows a very nice trick for drawing shadows: if you draw a sprite with color (0,0,0,0.5) (that is, RGB to 0 and alpha to 0.5), you will be drawing it completely black, but half transparent. If you draw this a few pixels below a sprite, it works as a cheap but cool shadow of the sprite, and it can help make the scene look much more integrated and solid.
The other parameter that this function accepts is a boolean saying whether it should be draw additively or not. It sets OpenGL’s blending function to use a different combination. What this does is it adds the pixel that is being drawing to whatever there is on the screen, rather than replacing it. So, wherever the image being painted is black, it won’t change anything (it’s adding 0 for all components), and all other colors will actually only “lighten” what there is behind. This is typically used for efects, overlays, etc… and we will use it for the energy bars and other effects down the road. Have a look at how the UI looks in additive form:
See how the two bars above look (the yellow one on the left is for energy, the blue one on the right is for fuel). They are drawn in “additive” mode, and thus it seems as if they “lighten” the background.
There are no more changes in the “core” code, all the rest are in game.cpp. Here is the list of things I have added today:
- Making rock obstacles float and move around a bit
- Detecting collisions between the rocks and the ship, and reacting a bit (rebound, energy)
- Inertia and controls for the main ship. Even if pixelated, the Tyrian graphics look cool!
- An UI with energy and fuel bars, and a bar of “pearls” to display your advance in the level (ugly yet!)
- A global “game state”, allowing the game to start, continue, and end whether with success or failure
- Entities project shadows on the floor now
- Added a “nice” effect to the succesful completion of the level
You will need to download a new set of graphics and copy them to the “data” subdirectory:
http://jonbho.net/wp-content/uploads/2013/08/sc-gfx-2.zip
And here is the new game.cpp code that implements all the new functionality:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 |
// game3.cpp #include "stdafx.h" #include "base.h" #include "sys.h" #include "core.h" #define SAFESUB(a,b) (a-b > 0 ? a-b : 0) #define SPRITE_SCALE 8.f #define SHADOW_OFFSET 80.f #define SHADOW_SCALE 0.9f #define MAINSHIP_ENTITY 0 #define MAINSHIP_RADIUS 100.f #define ROCK_RADIUS 100.f #define SHIP_W 250.f #define SHIP_H 270.f #define CRASH_VEL 20.f #define CRASH_ENERGY_LOSS 30.f #define MAX_ENERGY 100.f #define MAX_FUEL 100.f #define MAIN_SHIP g_entities[MAINSHIP_ENTITY] #define ENERGY_BAR_W 60.f #define ENERGY_BAR_H 1500.f #define FUEL_BAR_W 60.f #define FUEL_BAR_H 1500.f #define CHUNK_W 40.f #define CHUNK_H 40.f #define MAX_CHUNKS 30 #define START_ROCK_CHANCE_PER_PIXEL 1.f/1000.f #define EXTRA_ROCK_CHANCE_PER_PIXEL 0.f//1.f/2500000.f #define SHIP_CRUISE_SPEED 25.f #define SHIP_START_SPEED 5.f #define SHIP_INC_SPEED .5f #define HORIZONTAL_SHIP_VEL 10.f #define SHIP_TILT_INC .2f; #define SHIP_TILT_FRICTION .1f #define SHIP_MAX_TILT 1.f #define SHIP_HVEL_FRICTION .1f #define TILT_FUEL_COST .03f #define FRAME_FUEL_COST .01f #define RACE_END 100000.f #define FPS 60.f #define FRAMETIME (1.f/FPS) #define STARTING_TIME 2.f #define DYING_TIME 2.f #define VICTORY_TIME 8.f float g_current_race_pos = 0.f; float g_camera_offset = 0.f; float g_rock_chance = START_ROCK_CHANCE_PER_PIXEL; //============================================================================= // Game state enum GameState { GS_PLAYING, GS_DYING, GS_STARTING, GS_VICTORY }; GameState g_gs = GS_STARTING; float g_gs_timer = 0.f; //============================================================================= // Textures int g_ship_LL, g_ship_L, g_ship_C, g_ship_R, g_ship_RR; int g_bkg, g_pearl, g_energy, g_fuel, g_star; int g_rock[5]; //============================================================================= // Entities enum EType { E_NULL, E_MAIN, E_ROCK, E_STAR }; #define MAX_ENTITIES 64 struct Entity { EType type; vec2 pos; vec2 vel; float radius; float energy; float fuel; float tilt; float gfxscale; int gfx; bool gfxadditive; rgba color; bool has_shadow; }; Entity g_entities[MAX_ENTITIES]; //----------------------------------------------------------------------------- void InsertEntity(EType type, vec2 pos, vec2 vel, float radius, int gfx, bool has_shadow, bool additive = false) { for (int i = 0; i < MAX_ENTITIES; i++) { if (g_entities[i].type == E_NULL) { g_entities[i].type = type; g_entities[i].pos = pos; g_entities[i].vel = vel; g_entities[i].radius = radius; g_entities[i].gfx = gfx; g_entities[i].energy = MAX_ENERGY; g_entities[i].fuel = MAX_FUEL; g_entities[i].tilt = 0.f; g_entities[i].gfxscale = 1.f; g_entities[i].gfxadditive = additive; g_entities[i].color = vmake(1.f,1.f,1.f,1.f); g_entities[i].has_shadow = has_shadow; break; } } } //----------------------------------------------------------------------------- void LoadResources() { // Resources g_ship_LL = CORE_LoadBmp("data/ShipLL.bmp", false); g_ship_L = CORE_LoadBmp("data/ShipL.bmp" , false); g_ship_C = CORE_LoadBmp("data/ShipC.bmp" , false); g_ship_R = CORE_LoadBmp("data/ShipR.bmp" , false); g_ship_RR = CORE_LoadBmp("data/ShipRR.bmp", false); g_bkg = CORE_LoadBmp("data/bkg0.bmp" , false); g_rock[0] = CORE_LoadBmp("data/Rock0.bmp" , false); g_rock[1] = CORE_LoadBmp("data/Rock1.bmp" , false); g_rock[2] = CORE_LoadBmp("data/Rock2.bmp" , false); g_rock[3] = CORE_LoadBmp("data/Rock3.bmp" , false); g_rock[4] = CORE_LoadBmp("data/Rock4.bmp" , false); g_pearl = CORE_LoadBmp("data/Pearl.bmp" , false); g_energy = CORE_LoadBmp("data/Energy.bmp", false); g_fuel = CORE_LoadBmp("data/Fuel.bmp" , false); g_star = CORE_LoadBmp("data/Star.bmp" , false); } //----------------------------------------------------------------------------- void UnloadResources() { CORE_UnloadBmp(g_ship_LL); CORE_UnloadBmp(g_ship_L); CORE_UnloadBmp(g_ship_C); CORE_UnloadBmp(g_ship_R); CORE_UnloadBmp(g_ship_RR); CORE_UnloadBmp(g_bkg); CORE_UnloadBmp(g_rock[0]); CORE_UnloadBmp(g_rock[1]); CORE_UnloadBmp(g_rock[2]); CORE_UnloadBmp(g_rock[3]); CORE_UnloadBmp(g_rock[4]); CORE_UnloadBmp(g_pearl); CORE_UnloadBmp(g_energy); CORE_UnloadBmp(g_fuel); CORE_UnloadBmp(g_star); } //----------------------------------------------------------------------------- void Render() { glClear( GL_COLOR_BUFFER_BIT ); // Render background, only tiles within the camera view int first_tile = (int)floorf(g_camera_offset / G_HEIGHT); for (int i = first_tile; i < first_tile + 2; i++) { CORE_RenderCenteredSprite( vsub(vadd(vmake(G_WIDTH/2.0f,G_HEIGHT/2.0f), vmake(0.f, (float)i * G_HEIGHT)), vmake(0.f,g_camera_offset)), vmake(G_WIDTH,G_HEIGHT), g_bkg); } // Draw entities for (int i = MAX_ENTITIES - 1; i >= 0; i--) { if (g_entities[i].type != E_NULL) { ivec2 sz = CORE_GetBmpSize(g_entities[i].gfx); vec2 pos = g_entities[i].pos; pos.x = (float)((int)pos.x); pos.y = (float)((int)pos.y); if (g_entities[i].has_shadow) CORE_RenderCenteredSprite( vadd(vsub(pos, vmake(0.f,g_camera_offset)), vmake(0.f, -SHADOW_OFFSET)), vmake(sz.x * SPRITE_SCALE * g_entities[i].gfxscale * SHADOW_SCALE, sz.y * SPRITE_SCALE * g_entities[i].gfxscale * SHADOW_SCALE), g_entities[i].gfx, vmake(0.f,0.f,0.f,0.4f), g_entities[i].gfxadditive ); CORE_RenderCenteredSprite( vsub(pos, vmake(0.f,g_camera_offset)), vmake(sz.x * SPRITE_SCALE * g_entities[i].gfxscale, sz.y * SPRITE_SCALE * g_entities[i].gfxscale), g_entities[i].gfx, g_entities[i].color, g_entities[i].gfxadditive ); } } if (g_gs != GS_VICTORY) { // Draw UI float energy_ratio = MAIN_SHIP.energy / MAX_ENERGY; CORE_RenderCenteredSprite( vmake(ENERGY_BAR_W/2.f, energy_ratio * ENERGY_BAR_H / 2.f), vmake(ENERGY_BAR_W, ENERGY_BAR_H * energy_ratio), g_energy, COLOR_WHITE, true); float fuel_ratio = MAIN_SHIP.fuel / MAX_FUEL; CORE_RenderCenteredSprite( vmake(G_WIDTH - FUEL_BAR_W/2.f, fuel_ratio * FUEL_BAR_H / 2.f), vmake(FUEL_BAR_W, FUEL_BAR_H * fuel_ratio), g_fuel, COLOR_WHITE, true); // Draw how long you have lasted int num_chunks = (int)((g_current_race_pos/RACE_END) * MAX_CHUNKS); for (int i = 0; i < num_chunks; i++) CORE_RenderCenteredSprite( vmake(G_WIDTH - 100.f, 50.f + i * 50.f), vmake(CHUNK_W, CHUNK_H), g_pearl); } } //----------------------------------------------------------------------------- void ResetNewGame() { // Reset everything for a new game... g_current_race_pos = 0.f; g_camera_offset = 0.f; g_rock_chance = START_ROCK_CHANCE_PER_PIXEL; g_gs = GS_STARTING; g_gs_timer = 0.f; // Start logic for (int i = 0; i < MAX_ENTITIES; i++) g_entities[i].type = E_NULL; InsertEntity(E_MAIN, vmake(G_WIDTH/2.0, G_HEIGHT/8.f), vmake(0.f, SHIP_START_SPEED), MAINSHIP_RADIUS, g_ship_C, true); } //----------------------------------------------------------------------------- void RunGame() { // Control main ship if (g_gs == GS_PLAYING || g_gs == GS_VICTORY) { if (MAIN_SHIP.vel.y < SHIP_CRUISE_SPEED) { MAIN_SHIP.vel.y += SHIP_INC_SPEED; if (MAIN_SHIP.vel.y > SHIP_CRUISE_SPEED) MAIN_SHIP.vel.y = SHIP_CRUISE_SPEED; } MAIN_SHIP.fuel = SAFESUB(MAIN_SHIP.fuel, FRAME_FUEL_COST); } // Move entities for (int i = MAX_ENTITIES - 1; i >= 0; i--) { if (g_entities[i].type != E_NULL) { g_entities[i].pos = vadd(g_entities[i].pos, g_entities[i].vel); // Remove entities that fell off the screen if (g_entities[i].pos.y < g_camera_offset - G_HEIGHT) g_entities[i].type = E_NULL; } } // Advance 'stars' for (int i = 0; i < MAX_ENTITIES; i++) { if (g_entities[i].type == E_STAR) { g_entities[i].gfxscale *= 1.008f; } } // Dont let steering off the screen! if (MAIN_SHIP.pos.x < MAINSHIP_RADIUS) MAIN_SHIP.pos.x = MAINSHIP_RADIUS; if (MAIN_SHIP.pos.x > G_WIDTH - MAINSHIP_RADIUS) MAIN_SHIP.pos.x = G_WIDTH - MAINSHIP_RADIUS; // Check collisions between main ship and rocks if (g_gs == GS_PLAYING) { for (int i = 1; i < MAX_ENTITIES; i++) { if (g_entities[i].type == E_ROCK) { if (vlen2(vsub(g_entities[i].pos, MAIN_SHIP.pos)) < CORE_FSquare(g_entities[i].radius+MAIN_SHIP.radius)) { MAIN_SHIP.energy = SAFESUB(MAIN_SHIP.energy, CRASH_ENERGY_LOSS); MAIN_SHIP.vel.y = SHIP_START_SPEED; g_entities[i].vel = vscale(vunit(vsub(g_entities[i].pos, MAIN_SHIP.pos)), CRASH_VEL); } } } } // Possibly insert new rock if (g_gs == GS_PLAYING) { float trench = MAIN_SHIP.pos.y - g_current_race_pos; // How much advanced from previous frame if (CORE_RandChance(trench * g_rock_chance)) { InsertEntity(E_ROCK, vmake(CORE_FRand(0.f, G_WIDTH), g_camera_offset + G_HEIGHT + 400.f), vmake(CORE_FRand(-1.f, +1.f), CORE_FRand(-1.f, +1.f)), ROCK_RADIUS, g_rock[CORE_URand(0,4)], true); } // Advance difficulty in level g_rock_chance += (trench * EXTRA_ROCK_CHANCE_PER_PIXEL); } // Set camera to follow the main ship g_camera_offset = MAIN_SHIP.pos.y - G_HEIGHT/8.f; if (g_gs == GS_PLAYING) { g_current_race_pos = MAIN_SHIP.pos.y; if (g_current_race_pos >= RACE_END) { g_gs = GS_VICTORY; g_gs_timer = 0.f; MAIN_SHIP.gfxadditive = true; } } // Advance game mode g_gs_timer += FRAMETIME; switch (g_gs) { case GS_STARTING: if (g_gs_timer >= STARTING_TIME) { g_gs = GS_PLAYING; g_gs_timer = 0.f; } break; case GS_DYING: if (g_gs_timer >= DYING_TIME) { ResetNewGame(); } break; case GS_PLAYING: if (MAIN_SHIP.energy <= 0.f || MAIN_SHIP.fuel <= 0.f) { g_gs = GS_DYING; g_gs_timer = 0.f; } break; case GS_VICTORY: if (CORE_RandChance(1.f/10.f)) InsertEntity(E_STAR, MAIN_SHIP.pos, vadd(MAIN_SHIP.vel,vmake(CORE_FRand(-5.f, 5.f), CORE_FRand(-5.f,5.f))), 0, g_star, false, true); if (g_gs_timer >= VICTORY_TIME) ResetNewGame(); break; } } //----------------------------------------------------------------------------- void ProcessInput() { /* OLD if (SYS_KeyPressed(SYS_KEY_LEFT)) MAIN_SHIP.vel.x = -HORIZONTAL_SHIP_VEL; else if (SYS_KeyPressed(SYS_KEY_RIGHT)) MAIN_SHIP.vel.x = +HORIZONTAL_SHIP_VEL; else MAIN_SHIP.vel.x = 0.f; */ bool left = SYS_KeyPressed(SYS_KEY_LEFT); bool right = SYS_KeyPressed(SYS_KEY_RIGHT); if (left && !right) { MAIN_SHIP.fuel = SAFESUB(MAIN_SHIP.fuel, TILT_FUEL_COST); MAIN_SHIP.tilt -= SHIP_TILT_INC; } if (right && !left) { MAIN_SHIP.fuel -= TILT_FUEL_COST; MAIN_SHIP.tilt += SHIP_TILT_INC; } if (!left && !right) MAIN_SHIP.tilt *= (1.f - SHIP_TILT_FRICTION); if (MAIN_SHIP.tilt <= -SHIP_MAX_TILT) MAIN_SHIP.tilt = -SHIP_MAX_TILT; if (MAIN_SHIP.tilt >= SHIP_MAX_TILT) MAIN_SHIP.tilt = SHIP_MAX_TILT; MAIN_SHIP.vel.x += MAIN_SHIP.tilt; MAIN_SHIP.vel.x *= (1.f - SHIP_HVEL_FRICTION); float tilt = MAIN_SHIP.tilt; if (tilt < -.6f * SHIP_MAX_TILT) MAIN_SHIP.gfx = g_ship_LL; else if (tilt < -.2f * SHIP_MAX_TILT) MAIN_SHIP.gfx = g_ship_L; else if (tilt < +.2f * SHIP_MAX_TILT) MAIN_SHIP.gfx = g_ship_C; else if (tilt < +.6f * SHIP_MAX_TILT) MAIN_SHIP.gfx = g_ship_R; else MAIN_SHIP.gfx = g_ship_RR; } //----------------------------------------------------------------------------- // Game state (apart from entities & other stand-alone modules) float g_time = 0.f; //----------------------------------------------------------------------------- // Main int Main(void) { // Start things up & load resources --------------------------------------------------- LoadResources(); ResetNewGame(); // Set up rendering --------------------------------------------------------------------- glViewport(0, 0, SYS_WIDTH, SYS_HEIGHT); glClearColor( 0.0f, 0.1f, 0.3f, 0.0f ); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho( 0.0, G_WIDTH, 0.0, G_HEIGHT, 0.0, 1.0); glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); // Main game loop! ====================================================================== while (!SYS_GottaQuit()) { Render(); SYS_Show(); ProcessInput(); RunGame(); SYS_Pump(); SYS_Sleep(16); g_time += FRAMETIME; } UnloadResources(); return 0; } |
The game is in dire need of several things: adjusting collision boundaries, sound effects, nicer graphics and effects, more variety of game elements (shooting, collecting fuel, other obstacles…), and some gameplay balance so that playing is challenging in a more controlled fashion. Still in its current form, with the above parameters, I haven’t been able to beat the level (but I’m going to keep playing to see if I can get to the end!). Let me know how you fare yourself too! 🙂
The code above is not the cleanest, but it should be straightforward to understand. It needs improvements in organization, sprite management, etc… we’ll do so in the next few days. My main concern today was to get some gameplay out of this, and I’m starting to see some.
And here is a video clip from a gameplay session:
Spacecrash: Day 2 of 7 from Jon Beltran de Heredia on Vimeo.
I’m happy for where we are on day 2, it’s starting to take shape. Hope you like it. Feel free to ask any questions regarding the things above, or anything at all. I’d also be happy to hear your thoughts about how the series is progressing and its instructional value, and how you think it could be improved.
And if you want to be notified when new articles to help you become a games developer are ready, just fill in the form below:
Loving this so far. Hit an error with today’s entry though. I just copied your changes to core.h, core.cpp, and game.cpp just as you have them here, but when I build I get the following error:
error C2664: ‘vunit’ : cannot convert parameter 1 from ‘vec2’ to ‘float’
For me it’s on line 746:
g_entities[i].vel = vscale(vunit(vsub(g_entities[i].pos, MAIN_SHIP.pos)), CRASH_VEL);
I’m trying to figure it out myself, but just wanted to post it here in case you already know what to do, and the answer will be here for the next person who encounters the same issue.
Keep it up! I’m learning a lot 🙂
Oops, you’re right, I added a function in base.h too, going to add it now above! Sorry & thanks!
Ah I see! You overrode vunit. I probably could’ve figured that part out, but wouldn’t have had a clue on how to define it. Thanks for the fix, compiles just fine now!
Er, line 297. No clue how 746 happened. Haha. Unfortunately, that didn’t fix my problem though.
If you add the line above to base.h, it defines a new “vunit” version that does that. It should work! Let me know if it doesn’t.
This is great! Studying this code is very helpful.
This is great. I’ll be going through this when I get a chance.
Do you mind explaining the math behind modifying a rock’s velocity after you hit it?
Sure. I want the rock to fly “away” from the ship. So I first calculate the vector from the ship’s center to the rock’s center, which I do by subtracting their positions:
vsub(g_entities[i].pos, MAIN_SHIP.pos)
Now that I have this vector, I don’t know the length. They’re close since they collided, but don’t know how close. So I could use this for the velocity, but I wouldn’t control it. So, first thing I do, I normalize it (that is, I calculate the vector in the same direction but of lenght one):
vunit(vsub(g_entities[i].pos, MAIN_SHIP.pos))
And now that I have the unit vector, I can scale it to actually control the speed:
vscale(vunit(vsub(g_entities[i].pos, MAIN_SHIP.pos)), CRASH_VEL)
CRASH_VEL is defined to 20 pixels per frame above, it can be changed to obtain the speed you want.
Hope it’s clear now!
Awesome! Makes perfect sense now. Thanks!
I’m behind a couple days and trying to catch up, and I’m stuck here. The gameplay is all functioning as it should, but for some reason my rocks are displaying the ship graphics. Also noticed that if I complete the level, the star graphic that should appear is also displaying the ship graphic.
I’ve checked all the InsertEntity calls, and they’re asking for the right graphics, but for some reason all I get is ships on ships on ships. I did notice that changing one of the boolean parameters changes the rock graphic from Ship_C to Ship_LL. I’m at a loss, if anyone can shed some light it’d be much appreciated.
Thanks again for the tutorial, it’s been unbelievably informative.
[…] Spacecrash day 2 of 7: core gameplay […]
[…] Spacecrash day 2 of 7: core gameplay […]
I’ve learned many important things through your
post. I will also like to express that there will be a
situation in which you will have a loan and don’t need a co-signer such as a U.S.
Student Aid Loan. When you are getting that loan through a classic loan company
then you need to be made ready to have a cosigner ready to assist you.
The lenders are going to base any decision on the few elements but the main one will be
your credit history. There are some loan companies that will likewise look at your
work history and determine based on this but in
many cases it will hinge on your score.
Also visit my webpage :: pet carriers
I can’t believe you let pass such a nice opportunity to use xor!
Check this sh*t out:
bool left = SYS_KeyPressed(SYS_KEY_LEFT);
bool right = SYS_KeyPressed(SYS_KEY_RIGHT);
if (left ^ right) {
MAIN_SHIP.fuel = SAFESUB(MAIN_SHIP.fuel, TILT_FUEL_COST);
}
MAIN_SHIP.tilt += (right – left) * SHIP_TILT_INC;
YEAH!