86 |
87 |
87 typedef struct { |
88 typedef struct { |
88 AscSceneNode *nodes[GAME_FIELD_SIZE][GAME_FIELD_SIZE]; |
89 AscSceneNode *nodes[GAME_FIELD_SIZE][GAME_FIELD_SIZE]; |
89 int8_t tile_data[GAME_FIELD_SIZE][GAME_FIELD_SIZE]; |
90 int8_t tile_data[GAME_FIELD_SIZE][GAME_FIELD_SIZE]; |
90 } GameField; |
91 } GameField; |
|
92 |
|
93 |
|
94 #define GAME_STATE_MENU 0 |
|
95 #define GAME_STATE_PLAYING 1 |
|
96 #define GAME_STATE_GAME_OVER 2 |
|
97 |
|
98 typedef struct { |
|
99 int state; |
|
100 GameField *field; |
|
101 Player *players[4]; |
|
102 } GameState; |
|
103 |
|
104 GameState game = {0}; |
91 |
105 |
92 static void globals_init(void) { |
106 static void globals_init(void) { |
93 asc_transform_identity(rotations[MOVE_UP]); |
107 asc_transform_identity(rotations[MOVE_UP]); |
94 asc_transform_roll(rotations[MOVE_LEFT], asc_rad(-90)); |
108 asc_transform_roll(rotations[MOVE_LEFT], asc_rad(-90)); |
95 asc_transform_roll(rotations[MOVE_RIGHT], asc_rad(90)); |
109 asc_transform_roll(rotations[MOVE_RIGHT], asc_rad(90)); |
172 asc_behavior_add(node, fps_counter_tie_to_corner); |
186 asc_behavior_add(node, fps_counter_tie_to_corner); |
173 asc_ui_add_node(node); |
187 asc_ui_add_node(node); |
174 return node; |
188 return node; |
175 } |
189 } |
176 |
190 |
177 |
|
178 static GameField *game_field; |
|
179 |
|
180 static bool game_field_tile_chown(asc_vec2u coords, Player *player) { |
191 static bool game_field_tile_chown(asc_vec2u coords, Player *player) { |
181 unsigned x = coords.x, y = coords.y; |
192 unsigned x = coords.x, y = coords.y; |
182 asc_ptr_cast(AscRectangle, tile, game_field->nodes[x][y]); |
193 asc_ptr_cast(AscRectangle, tile, game.field->nodes[x][y]); |
183 int old_owner = game_field->tile_data[x][y] & GAME_FIELD_TILE_OWNER_MASK; |
194 int old_owner = game.field->tile_data[x][y] & GAME_FIELD_TILE_OWNER_MASK; |
184 if (player == NULL) { |
195 if (player == NULL) { |
185 asc_clear_flag(game_field->tile_data[x][y], GAME_FIELD_TILE_OWNER_MASK); |
196 asc_clear_flag(game.field->tile_data[x][y], GAME_FIELD_TILE_OWNER_MASK); |
186 tile->color = ASC_RGBi(16, 50, 160); |
197 tile->color = ASC_RGBi(16, 50, 160); |
187 } else { |
198 } else { |
188 asc_set_flag_masked(game_field->tile_data[x][y], GAME_FIELD_TILE_OWNER_MASK, player->number); |
199 asc_set_flag_masked(game.field->tile_data[x][y], GAME_FIELD_TILE_OWNER_MASK, player->number); |
189 tile->color = player->color; |
200 tile->color = player->color; |
190 } |
201 } |
191 int new_owner = game_field->tile_data[x][y] & GAME_FIELD_TILE_OWNER_MASK; |
202 int new_owner = game.field->tile_data[x][y] & GAME_FIELD_TILE_OWNER_MASK; |
192 return old_owner != new_owner; |
203 return old_owner != new_owner; |
193 } |
204 } |
194 |
205 |
195 static GameField *game_field_create() { |
206 static void game_field_create() { |
196 // TODO: create a more interesting map than just a basic grid |
207 // TODO: create a more interesting map than just a basic grid |
197 AscSceneNode *node = asc_scene_node_empty(); |
208 AscSceneNode *node = asc_scene_node_empty(); |
198 game_field = asc_scene_node_allocate_data(node, sizeof(GameField)); |
209 game.field = asc_scene_node_allocate_data(node, sizeof(GameField)); |
199 for (unsigned x = 0; x < GAME_FIELD_SIZE; x++) { |
210 for (unsigned x = 0; x < GAME_FIELD_SIZE; x++) { |
200 for (unsigned y = 0; y < GAME_FIELD_SIZE; y++) { |
211 for (unsigned y = 0; y < GAME_FIELD_SIZE; y++) { |
201 AscSceneNode *tile = asc_rectangle( |
212 AscSceneNode *tile = asc_rectangle( |
202 .x = x*GAME_FIELD_TILE_SIZE, .y = y*GAME_FIELD_TILE_SIZE, .filled = true, .thickness = 2, |
213 .x = x*GAME_FIELD_TILE_SIZE, .y = y*GAME_FIELD_TILE_SIZE, .filled = true, .thickness = 2, |
203 .width = GAME_FIELD_TILE_SIZE, .height = GAME_FIELD_TILE_SIZE, |
214 .width = GAME_FIELD_TILE_SIZE, .height = GAME_FIELD_TILE_SIZE, |
204 .color = ASC_RGBi(16, 50, 160), |
215 .color = ASC_RGBi(16, 50, 160), |
205 .border_color = ASC_RGBi(20, 84, 128), |
216 .border_color = ASC_RGBi(20, 84, 128), |
206 ); |
217 ); |
207 |
218 |
208 game_field->tile_data[x][y] = GAME_FIELD_TILE_EXISTS_FLAG; |
219 game.field->tile_data[x][y] = GAME_FIELD_TILE_EXISTS_FLAG; |
209 game_field->nodes[x][y] = tile; |
220 game.field->nodes[x][y] = tile; |
210 |
221 |
211 asc_scene_node_link(node, tile); |
222 asc_scene_node_link(node, tile); |
212 } |
223 } |
213 } |
224 } |
214 asc_scene_node_set_zindex(node, -2); |
225 asc_scene_node_set_zindex(node, -2); |
215 asc_scene_add_node(MAIN_SCENE, node); |
226 asc_scene_add_node(MAIN_SCENE, node); |
216 return game_field; |
|
217 } |
227 } |
218 |
228 |
219 static asc_vec2u game_field_tile_at_position(asc_vec3f position) { |
229 static asc_vec2u game_field_tile_at_position(asc_vec3f position) { |
220 return ASC_VEC2U((int)position.x / GAME_FIELD_TILE_SIZE, (int)position.y / GAME_FIELD_TILE_SIZE); |
230 return ASC_VEC2U((int)position.x / GAME_FIELD_TILE_SIZE, (int)position.y / GAME_FIELD_TILE_SIZE); |
221 } |
231 } |
258 asc_texture_bind(TEXTURE_PLAYER_COLOR_MAP, shader->map_color, 1); |
268 asc_texture_bind(TEXTURE_PLAYER_COLOR_MAP, shader->map_color, 1); |
259 asc_shader_upload_color(shader->color, player->color); |
269 asc_shader_upload_color(shader->color, player->color); |
260 asc_mesh_draw_triangle_strip(&sprite->mesh); |
270 asc_mesh_draw_triangle_strip(&sprite->mesh); |
261 } |
271 } |
262 |
272 |
|
273 static void player_set_health(Player *player, unsigned health) { |
|
274 player->health = health; |
|
275 // TODO: probably we want to add more effects when the health changes |
|
276 } |
|
277 |
|
278 static unsigned player_get_health(Player *player) { |
|
279 return player->health; |
|
280 } |
|
281 |
263 static void player_move(AscBehavior *behavior) { |
282 static void player_move(AscBehavior *behavior) { |
264 AscSceneNode *node = behavior->node; |
283 AscSceneNode *node = behavior->node; |
265 Player *player = node->user_data; |
284 Player *player = node->user_data; |
|
285 |
|
286 // TODO: instead of skipping this behavior, it should be disabled when health is zero |
|
287 if (player_get_health(player) == 0) return; |
|
288 |
|
289 // TODO: move this to a different behavior |
|
290 asc_scene_node_show(node); |
|
291 |
266 const float ts = (float) GAME_FIELD_TILE_SIZE; |
292 const float ts = (float) GAME_FIELD_TILE_SIZE; |
267 |
293 |
268 // check if the position is set programmatically |
294 // check if the position is set programmatically |
269 if (player->reset_position) { |
295 if (player->reset_position) { |
270 asc_scene_node_set_position2f(node, |
296 asc_scene_node_set_position2f(node, |
320 // changing the direction not permitted, yet, continue movement |
346 // changing the direction not permitted, yet, continue movement |
321 asc_scene_node_move2f(node, movement); |
347 asc_scene_node_move2f(node, movement); |
322 } |
348 } |
323 } |
349 } |
324 |
350 |
|
351 // die when leaving the game field |
|
352 if (node->position.x < 0 || node->position.y < 0 || |
|
353 node->position.x > GAME_FIELD_SIZE*GAME_FIELD_TILE_SIZE || |
|
354 node->position.y > GAME_FIELD_SIZE*GAME_FIELD_TILE_SIZE) { |
|
355 // TODO: add fancy death animation |
|
356 asc_scene_node_hide(node); |
|
357 player_set_health(player, 0); |
|
358 // TODO: remove the trace gradually (dequeuing the trace should be a different behavior) |
|
359 cxListClear(player->trace); |
|
360 game.state = GAME_STATE_GAME_OVER; |
|
361 return; |
|
362 } |
|
363 |
325 // TODO: collision detection |
364 // TODO: collision detection |
326 |
365 |
327 // update the trace, if necessary. |
366 // update the trace, if necessary. |
328 // remark: some calculations are repeated here, but they are cheap enough |
367 // remark: some calculations are repeated here, but they are cheap enough |
329 { |
368 { |
388 asc_scene_add_node(MAIN_SCENE, node); |
430 asc_scene_add_node(MAIN_SCENE, node); |
389 Player *player = asc_scene_node_allocate_data(node, sizeof(Player)); |
431 Player *player = asc_scene_node_allocate_data(node, sizeof(Player)); |
390 player_position(player, 12, 8); |
432 player_position(player, 12, 8); |
391 player->speed = 3.f; // start with 3 tiles/sec |
433 player->speed = 3.f; // start with 3 tiles/sec |
392 player->number = 1; |
434 player->number = 1; |
|
435 player->health = 100; |
393 player->color = ASC_RGB(1, 0, 0); |
436 player->color = ASC_RGB(1, 0, 0); |
394 player->trace = cxLinkedListCreateSimple(sizeof(asc_vec2i)); |
437 player->trace = cxLinkedListCreateSimple(sizeof(asc_vec2u)); |
|
438 cxDefineDestructor(player->trace, player_trace_release_tile); |
395 node->draw_func = player_draw; |
439 node->draw_func = player_draw; |
396 node->user_data_free_func = (cx_destructor_func2)player_destroy; |
440 node->user_data_free_func = (cx_destructor_func2)player_destroy; |
397 asc_behavior_add(node, player_move); |
441 asc_behavior_add(node, player_move); |
398 return player; |
442 return player; |
399 } |
443 } |
489 |
523 |
490 // create the fps counter |
524 // create the fps counter |
491 AscSceneNode *fps_counter = fps_counter_create(); |
525 AscSceneNode *fps_counter = fps_counter_create(); |
492 asc_scene_node_hide(fps_counter); |
526 asc_scene_node_hide(fps_counter); |
493 |
527 |
494 // create the game state |
528 // create game over text |
495 GameState game = {0}; |
529 AscSceneNode *text_game_over = asc_text( |
|
530 .name = "game_over_text", |
|
531 .text = "Game Over\nPress R to Restart", |
|
532 .color = ASC_RGB(1, 1, 1), |
|
533 .font = asc_font(ASC_FONT_REGULAR, 36), |
|
534 .alignment = ASC_TEXT_ALIGN_CENTER, |
|
535 .centered = true, |
|
536 .x = (GAME_FIELD_SIZE+2)*GAME_FIELD_TILE_SIZE/2, |
|
537 .y = (GAME_FIELD_SIZE+2)*GAME_FIELD_TILE_SIZE/2 - 60, |
|
538 ); |
|
539 asc_scene_node_hide(text_game_over); |
|
540 // TODO: add as a UI node and add a behavior which centers the node in the main scenes viewport |
|
541 // otherwise we won't be able to implement a moving camera in the future |
|
542 asc_scene_add_node(MAIN_SCENE, text_game_over); |
|
543 |
|
544 // initialize the game state |
496 // TODO: add a main menu and start with the menu |
545 // TODO: add a main menu and start with the menu |
497 game.state = GAME_STATE_PLAYING; |
546 game.state = GAME_STATE_PLAYING; |
498 game.field = game_field_create(); |
|
499 game.players[0] = player_create(); |
547 game.players[0] = player_create(); |
|
548 game_field_create(); |
500 |
549 |
501 // Main Loop |
550 // Main Loop |
502 do { |
551 do { |
503 // quit application on any error |
552 // quit application on any error |
504 if (asc_has_error()) { |
553 if (asc_has_error()) { |
508 asc_clear_error(); |
557 asc_clear_error(); |
509 asc_context_quit(); |
558 asc_context_quit(); |
510 } |
559 } |
511 |
560 |
512 // game states |
561 // game states |
|
562 // TODO: move all this into behaviors |
513 if (game.state == GAME_STATE_PLAYING) { |
563 if (game.state == GAME_STATE_PLAYING) { |
514 // TODO: implement hot seat 1on1 multiplayer |
564 // TODO: implement hot seat 1on1 multiplayer |
515 player_controls(game.players[0]); |
565 player_controls(game.players[0]); |
|
566 } else if (game.state == GAME_STATE_GAME_OVER) { |
|
567 asc_scene_node_show(text_game_over); |
|
568 if (asc_key_pressed(ASC_KEY(R))) { |
|
569 // TODO: re-load the "level" |
|
570 player_position(game.players[0], 12, 8); |
|
571 player_set_health(game.players[0], 100); |
|
572 game.state = GAME_STATE_PLAYING; |
|
573 asc_scene_node_hide(text_game_over); |
|
574 } |
516 } |
575 } |
517 |
576 |
518 // debug-key for clearing the shader registry |
577 // debug-key for clearing the shader registry |
519 if (asc_key_pressed(ASC_KEY(S))) { |
578 if (asc_key_pressed(ASC_KEY(S))) { |
520 asc_shader_clear_registry(); |
579 asc_shader_clear_registry(); |