1 /* |
|
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
|
3 * Copyright 2023 Mike Becker. All rights reserved. |
|
4 * |
|
5 * Redistribution and use in source and binary forms, with or without |
|
6 * modification, are permitted provided that the following conditions are met: |
|
7 * |
|
8 * 1. Redistributions of source code must retain the above copyright |
|
9 * notice, this list of conditions and the following disclaimer. |
|
10 * |
|
11 * 2. Redistributions in binary form must reproduce the above copyright |
|
12 * notice, this list of conditions and the following disclaimer in the |
|
13 * documentation and/or other materials provided with the distribution. |
|
14 * |
|
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
|
19 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|
20 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|
21 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|
22 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|
23 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|
24 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
25 * POSSIBILITY OF SUCH DAMAGE. |
|
26 */ |
|
27 |
|
28 #include <ascension/core.h> |
|
29 #include <ascension/ui.h> |
|
30 #include <ascension/sprite.h> |
|
31 #include <ascension/2d.h> |
|
32 #include <ascension/shader.h> |
|
33 |
|
34 #include <cx/printf.h> |
|
35 #include <cx/linked_list.h> |
|
36 |
|
37 #define TEXTURE_2D_COUNT 3 |
|
38 static AscTexture tex2d[TEXTURE_2D_COUNT]; |
|
39 #define TEXTURE_PLAYER &tex2d[0] |
|
40 #define TEXTURE_PLAYER_COLOR_MAP &tex2d[1] |
|
41 #define TEXTURE_BACKDROP &tex2d[2] |
|
42 |
|
43 #define SHADER_ID_PLAYER 1 |
|
44 |
|
45 #define BACKDROP_SCENE asc_window_scene(0) |
|
46 #define MAIN_SCENE asc_window_scene(1) |
|
47 #define HUD_WIDTH 400 |
|
48 |
|
49 enum MoveDirection { |
|
50 MOVE_UP, |
|
51 MOVE_LEFT, |
|
52 MOVE_DOWN, |
|
53 MOVE_RIGHT |
|
54 }; |
|
55 |
|
56 static asc_transform rotations[4]; |
|
57 static asc_vec2i directions[4]; |
|
58 |
|
59 typedef struct { |
|
60 asc_color color; |
|
61 enum MoveDirection direction; |
|
62 enum MoveDirection target_direction; |
|
63 /** |
|
64 * The speed in tiles per second. |
|
65 */ |
|
66 float speed; |
|
67 /** |
|
68 * A linked list of vec2u elements describing the current trace. |
|
69 */ |
|
70 CxList *trace; |
|
71 /** |
|
72 * The new position of the player when @c reset_position is @c true. |
|
73 */ |
|
74 asc_vec2i new_position; |
|
75 unsigned health; |
|
76 bool reset_position; |
|
77 uint8_t number; |
|
78 } Player; |
|
79 |
|
80 #define GAME_FIELD_SIZE 32 |
|
81 #define GAME_FIELD_TILE_SIZE 32 |
|
82 |
|
83 /** The bit in the tile data indicating if the tile exists. */ |
|
84 #define GAME_FIELD_TILE_EXISTS_FLAG 0x80 |
|
85 /** The bits in the tile data that identify the owner. */ |
|
86 #define GAME_FIELD_TILE_OWNER_MASK 0xF |
|
87 |
|
88 typedef struct { |
|
89 AscSceneNode *nodes[GAME_FIELD_SIZE][GAME_FIELD_SIZE]; |
|
90 int8_t tile_data[GAME_FIELD_SIZE][GAME_FIELD_SIZE]; |
|
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}; |
|
105 |
|
106 static void globals_init(void) { |
|
107 asc_transform_identity(rotations[MOVE_UP]); |
|
108 asc_transform_roll(rotations[MOVE_LEFT], asc_rad(-90)); |
|
109 asc_transform_roll(rotations[MOVE_RIGHT], asc_rad(90)); |
|
110 asc_transform_roll(rotations[MOVE_DOWN], asc_rad(180)); |
|
111 directions[MOVE_UP] = ASC_VEC2I(0, -1); |
|
112 directions[MOVE_LEFT] = ASC_VEC2I(-1, 0); |
|
113 directions[MOVE_DOWN] = ASC_VEC2I(0, 1); |
|
114 directions[MOVE_RIGHT] = ASC_VEC2I(1, 0); |
|
115 } |
|
116 |
|
117 static void textures_destroy(void) { |
|
118 asc_texture_destroy(tex2d, TEXTURE_2D_COUNT); |
|
119 } |
|
120 |
|
121 static void textures_init(void) { |
|
122 asc_texture_init_2d(tex2d, TEXTURE_2D_COUNT); |
|
123 asc_texture_from_file(TEXTURE_PLAYER, "player.png"); |
|
124 asc_texture_from_file(TEXTURE_PLAYER_COLOR_MAP, "player-color-map.png"); |
|
125 asc_texture_from_file(TEXTURE_BACKDROP, "backdrop.png"); |
|
126 asc_gl_context_add_cleanup_func(asc_active_glctx, textures_destroy); |
|
127 } |
|
128 |
|
129 static void backdrop_scale(AscBehavior *behavior) { |
|
130 // scale the backdrop to the size of the window |
|
131 if (!asc_active_window->resized) return; |
|
132 asc_ptr_cast(AscSprite, sprite, behavior->node); |
|
133 asc_vec2u window_size = asc_active_window->dimensions; |
|
134 asc_sprite_set_size(sprite, window_size); |
|
135 } |
|
136 |
|
137 static void main_scene_frame_scale(AscBehavior *behavior) { |
|
138 if (!asc_active_window->resized) return; |
|
139 asc_ptr_cast(AscRectangle, frame, behavior->node); |
|
140 asc_rectangle_set_bounds(frame, MAIN_SCENE->camera.viewport); |
|
141 } |
|
142 |
|
143 static void backdrop_create(void) { |
|
144 const float scale = 1.f / asc_active_window->ui_scale; |
|
145 AscSceneNode *node = asc_sprite( |
|
146 .texture = TEXTURE_BACKDROP, |
|
147 .texture_scale_mode = ASC_TEXTURE_SCALE_REPEAT, |
|
148 .texture_scale_x = scale, .texture_scale_y = scale, |
|
149 ); |
|
150 asc_behavior_add(node, .func = backdrop_scale); |
|
151 asc_scene_add_node(BACKDROP_SCENE, node); |
|
152 |
|
153 // also add a frame for the main scene |
|
154 // add this to the UI layer so that the border size does not scale |
|
155 AscSceneNode *frame = asc_rectangle(.thickness = 2, .color = ASC_RGBi(66, 142, 161)); |
|
156 asc_behavior_add(frame, .func = main_scene_frame_scale); |
|
157 asc_ui_add_node(frame); |
|
158 } |
|
159 |
|
160 static void fps_counter_update(AscBehavior *behavior) { |
|
161 asc_ptr_cast(AscText, node, behavior->node); |
|
162 static float last_fps = 0.f; |
|
163 if (fabsf(asc_context.frame_rate - last_fps) > 1) { |
|
164 last_fps = asc_context.frame_rate; |
|
165 asc_text_printf(node, "%.2f FPS", asc_context.frame_rate); |
|
166 } |
|
167 } |
|
168 |
|
169 static void fps_counter_tie_to_corner(AscBehavior *behavior) { |
|
170 // TODO: this should be replaced with some sort of UI layout manager |
|
171 AscSceneNode *node = behavior->node; |
|
172 if (asc_test_flag(node->flags, ASC_SCENE_NODE_GRAPHICS_UPDATED) || asc_active_window->resized) { |
|
173 asc_scene_node_set_position2f(node, ASC_VEC2F(10, |
|
174 asc_active_window->dimensions.y - ((AscText*)node)->dimension.height - 10 |
|
175 )); |
|
176 } |
|
177 } |
|
178 |
|
179 static AscSceneNode *fps_counter_create(void) { |
|
180 AscSceneNode *node = asc_text( |
|
181 .name = "FPS Counter", |
|
182 .color = ASC_RGB(1, 1, 1), |
|
183 .font = asc_font(ASC_FONT_REGULAR, 12), |
|
184 ); |
|
185 asc_behavior_add(node, .func = fps_counter_update, .interval = asc_seconds(1)); |
|
186 asc_behavior_add(node, fps_counter_tie_to_corner); |
|
187 asc_ui_add_node(node); |
|
188 return node; |
|
189 } |
|
190 |
|
191 static bool game_field_tile_chown(asc_vec2u coords, Player *player) { |
|
192 unsigned x = coords.x, y = coords.y; |
|
193 asc_ptr_cast(AscRectangle, tile, game.field->nodes[x][y]); |
|
194 int old_owner = game.field->tile_data[x][y] & GAME_FIELD_TILE_OWNER_MASK; |
|
195 if (player == NULL) { |
|
196 asc_clear_flag(game.field->tile_data[x][y], GAME_FIELD_TILE_OWNER_MASK); |
|
197 tile->color = ASC_RGBi(16, 50, 160); |
|
198 } else { |
|
199 asc_set_flag_masked(game.field->tile_data[x][y], GAME_FIELD_TILE_OWNER_MASK, player->number); |
|
200 tile->color = player->color; |
|
201 } |
|
202 int new_owner = game.field->tile_data[x][y] & GAME_FIELD_TILE_OWNER_MASK; |
|
203 return old_owner != new_owner; |
|
204 } |
|
205 |
|
206 static void game_field_create() { |
|
207 // TODO: create a more interesting map than just a basic grid |
|
208 AscSceneNode *node = asc_scene_node_empty(); |
|
209 game.field = asc_scene_node_allocate_data(node, sizeof(GameField)); |
|
210 for (unsigned x = 0; x < GAME_FIELD_SIZE; x++) { |
|
211 for (unsigned y = 0; y < GAME_FIELD_SIZE; y++) { |
|
212 AscSceneNode *tile = asc_rectangle( |
|
213 .x = x*GAME_FIELD_TILE_SIZE, .y = y*GAME_FIELD_TILE_SIZE, .filled = true, .thickness = 2, |
|
214 .width = GAME_FIELD_TILE_SIZE, .height = GAME_FIELD_TILE_SIZE, |
|
215 .color = ASC_RGBi(16, 50, 160), |
|
216 .border_color = ASC_RGBi(20, 84, 128), |
|
217 ); |
|
218 |
|
219 game.field->tile_data[x][y] = GAME_FIELD_TILE_EXISTS_FLAG; |
|
220 game.field->nodes[x][y] = tile; |
|
221 |
|
222 asc_scene_node_link(node, tile); |
|
223 } |
|
224 } |
|
225 asc_scene_node_set_zindex(node, -2); |
|
226 asc_scene_add_node(MAIN_SCENE, node); |
|
227 } |
|
228 |
|
229 static asc_vec2u game_field_tile_at_position(asc_vec3f position) { |
|
230 return ASC_VEC2U((int)position.x / GAME_FIELD_TILE_SIZE, (int)position.y / GAME_FIELD_TILE_SIZE); |
|
231 } |
|
232 |
|
233 typedef struct { |
|
234 AscShaderProgram program; |
|
235 asc_uniform_loc map_albedo; |
|
236 asc_uniform_loc map_color; |
|
237 asc_uniform_loc color; |
|
238 } PlayerShader; |
|
239 |
|
240 static void player_shader_init(AscShaderProgram *p, cx_attr_unused int flags) { |
|
241 asc_shader_init_uniform_loc_nice(p, PlayerShader, map_albedo); |
|
242 asc_shader_init_uniform_loc_nice(p, PlayerShader, map_color); |
|
243 asc_shader_init_uniform_loc_nice(p, PlayerShader, color); |
|
244 } |
|
245 |
|
246 static AscShaderProgram *player_shader_create(cx_attr_unused int unused) { |
|
247 return asc_shader_create((AscShaderCodes) { |
|
248 .vtx = {.source_file = "sprite_vtx.glsl"}, |
|
249 .frag = {.source_file = "player.glsl",}, |
|
250 }, sizeof(PlayerShader), player_shader_init, 0); |
|
251 } |
|
252 |
|
253 static void player_draw(const AscCamera *camera, const AscSceneNode *node) { |
|
254 asc_cptr_cast(AscSprite, sprite, node); |
|
255 const Player *player = node->user_data; |
|
256 |
|
257 // TODO: we shall finally add the shader information to the node |
|
258 const AscShaderProgram *s = asc_shader_lookup( |
|
259 SHADER_ID_PLAYER, player_shader_create, 0 |
|
260 ); |
|
261 if (asc_shader_use(s, camera)) return; |
|
262 asc_cptr_cast(PlayerShader, shader, s); |
|
263 |
|
264 asc_shader_upload_model_matrix(s, node); |
|
265 |
|
266 // Bind texture |
|
267 asc_texture_bind(TEXTURE_PLAYER, shader->map_albedo, 0); |
|
268 asc_texture_bind(TEXTURE_PLAYER_COLOR_MAP, shader->map_color, 1); |
|
269 asc_shader_upload_color(shader->color, player->color); |
|
270 asc_mesh_draw_triangle_strip(&sprite->mesh); |
|
271 } |
|
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 |
|
282 static void player_move(AscBehavior *behavior) { |
|
283 AscSceneNode *node = behavior->node; |
|
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 |
|
292 const float ts = (float) GAME_FIELD_TILE_SIZE; |
|
293 |
|
294 // check if the position is set programmatically |
|
295 if (player->reset_position) { |
|
296 asc_scene_node_set_position2f(node, |
|
297 ASC_VEC2F( |
|
298 ts * (player->new_position.x + .5f), |
|
299 ts * (player->new_position.y + .5f) |
|
300 )); |
|
301 player->reset_position = false; |
|
302 return; |
|
303 } |
|
304 |
|
305 // normal movement |
|
306 const float speed = ts * player->speed * asc_context.frame_factor; |
|
307 const asc_vec2i dir = directions[player->direction]; |
|
308 const asc_vec2f movement = asc_vec2f_scale(asc_vec2_itof(dir), speed); |
|
309 |
|
310 // check if we are supposed to change the direction |
|
311 if (player->direction == player->target_direction) { |
|
312 // move without changing the direction |
|
313 asc_scene_node_move2f(node, movement); |
|
314 } else { |
|
315 // determine axis |
|
316 // and check if we are about to cross the center |
|
317 // this relies on positive positions! |
|
318 bool rotate = false; |
|
319 if (movement.x == 0) { |
|
320 // vertical movement |
|
321 const float y_0 = floorf(node->position.y / ts); |
|
322 const float y_curr = node->position.y / ts - y_0; |
|
323 const float y_next = (node->position.y+movement.y) / ts - y_0; |
|
324 const bool side_curr = y_curr > 0.5f; |
|
325 const bool side_next = y_next > 0.5f; |
|
326 rotate = side_curr ^ side_next; |
|
327 } else { |
|
328 // horizontal movement |
|
329 const float x0 = floorf(node->position.x / ts); |
|
330 const float x_curr = node->position.x / ts - x0; |
|
331 const float x_next = (node->position.x+movement.x) / ts - x0; |
|
332 const bool side_curr = x_curr > 0.5f; |
|
333 const bool side_next = x_next > 0.5f; |
|
334 rotate = side_curr ^ side_next; |
|
335 } |
|
336 if (rotate) { |
|
337 // snap position to the center of the tile |
|
338 asc_scene_node_set_position2f(node, |
|
339 ASC_VEC2F( |
|
340 (.5f+floorf(node->position.x / ts)) * ts, |
|
341 (.5f+floorf(node->position.y / ts)) * ts |
|
342 )); |
|
343 player->direction = player->target_direction; |
|
344 asc_scene_node_set_rotation(node, rotations[player->direction]); |
|
345 } else { |
|
346 // changing the direction not permitted, yet, continue movement |
|
347 asc_scene_node_move2f(node, movement); |
|
348 } |
|
349 } |
|
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 |
|
364 // TODO: collision detection |
|
365 |
|
366 // update the trace, if necessary. |
|
367 // remark: some calculations are repeated here, but they are cheap enough |
|
368 { |
|
369 const asc_vec2u tile_coords = game_field_tile_at_position(node->position); |
|
370 // TODO: player should have been destroyed before leaving the field |
|
371 if (tile_coords.x > GAME_FIELD_SIZE || tile_coords.y > GAME_FIELD_SIZE) return; |
|
372 if (game_field_tile_chown(tile_coords, player)) { |
|
373 // new owner of the tile! |
|
374 asc_vec2u p = tile_coords; |
|
375 cxListAdd(player->trace, &p); |
|
376 if (cxListSize(player->trace) > 7) { |
|
377 // TODO: implement power-up which makes the trace longer |
|
378 cxListRemove(player->trace, 0); |
|
379 } |
|
380 } |
|
381 } |
|
382 } |
|
383 |
|
384 static void player_controls(Player *player) { |
|
385 if (asc_key_pressed(ASC_KEY(LEFT))) { |
|
386 if (player->direction != MOVE_RIGHT) { |
|
387 player->target_direction = MOVE_LEFT; |
|
388 } |
|
389 } |
|
390 if (asc_key_pressed(ASC_KEY(RIGHT))) { |
|
391 if (player->direction != MOVE_LEFT) { |
|
392 player->target_direction = MOVE_RIGHT; |
|
393 } |
|
394 } |
|
395 if (asc_key_pressed(ASC_KEY(UP))) { |
|
396 if (player->direction != MOVE_DOWN) { |
|
397 player->target_direction = MOVE_UP; |
|
398 } |
|
399 } |
|
400 if (asc_key_pressed(ASC_KEY(DOWN))) { |
|
401 if (player->direction != MOVE_UP) { |
|
402 player->target_direction = MOVE_DOWN; |
|
403 } |
|
404 } |
|
405 } |
|
406 |
|
407 static void player_position(Player *pl, int x, int y) { |
|
408 pl->new_position.x = x; |
|
409 pl->new_position.y = y; |
|
410 pl->reset_position = true; |
|
411 } |
|
412 |
|
413 static void player_destroy(CxAllocator *allocator, Player *player) { |
|
414 cxListFree(player->trace); |
|
415 cxFree(allocator, player); |
|
416 } |
|
417 |
|
418 static void player_trace_release_tile(asc_vec2u *coords) { |
|
419 game_field_tile_chown(*coords, NULL); |
|
420 } |
|
421 |
|
422 static Player *player_create(void) { |
|
423 AscSceneNode *node = asc_sprite( |
|
424 .name = "Player", |
|
425 .width = GAME_FIELD_TILE_SIZE, |
|
426 .height = GAME_FIELD_TILE_SIZE, |
|
427 .origin_x = GAME_FIELD_TILE_SIZE / 2, |
|
428 .origin_y = GAME_FIELD_TILE_SIZE / 2, |
|
429 ); |
|
430 asc_scene_add_node(MAIN_SCENE, node); |
|
431 Player *player = asc_scene_node_allocate_data(node, sizeof(Player)); |
|
432 player_position(player, 12, 8); |
|
433 player->speed = 3.f; // start with 3 tiles/sec |
|
434 player->number = 1; |
|
435 player->health = 100; |
|
436 player->color = ASC_RGB(1, 0, 0); |
|
437 player->trace = cxLinkedListCreateSimple(sizeof(asc_vec2u)); |
|
438 cxDefineDestructor(player->trace, player_trace_release_tile); |
|
439 node->draw_func = player_draw; |
|
440 node->user_data_free_func = (cx_destructor_func2)player_destroy; |
|
441 asc_behavior_add(node, player_move); |
|
442 return player; |
|
443 } |
|
444 |
|
445 static asc_rect main_scene_viewport_update(asc_vec2u window_size) { |
|
446 |
|
447 // margins |
|
448 const unsigned margin = 16; |
|
449 |
|
450 // space for score, power-ups, etc. |
|
451 const unsigned left_area = (unsigned) (asc_active_window->ui_scale*HUD_WIDTH); |
|
452 |
|
453 // calculate how many pixels need to be removed from width and height |
|
454 const unsigned rw = 2*margin + left_area; |
|
455 const unsigned rh = 2*margin; |
|
456 |
|
457 // check if there is still a viewport left and chicken out when not |
|
458 if (window_size.width < rw || window_size.height < rh) { |
|
459 return ASC_RECT(0, 0, 0, 0); |
|
460 } |
|
461 window_size.width -= rw; |
|
462 window_size.height -= rh; |
|
463 |
|
464 // Compute scaling and offsets |
|
465 unsigned viewport_size, offset_x = 0, offset_y = 0; |
|
466 if (window_size.width > window_size.height) { |
|
467 // Wider window: letterbox (black bars on top/bottom) |
|
468 offset_x = (window_size.width - window_size.height) / 2; |
|
469 viewport_size = window_size.height; |
|
470 } else { |
|
471 // Taller window: pillarbox (black bars on sides) |
|
472 offset_y = (window_size.height - window_size.width) / 2; |
|
473 viewport_size = window_size.width; |
|
474 } |
|
475 offset_x += left_area + margin; |
|
476 offset_y += margin; |
|
477 |
|
478 // Set the viewport to the scaled and centered region |
|
479 return ASC_RECT(offset_x, offset_y, viewport_size, viewport_size); |
|
480 } |
|
481 |
|
482 int main(void) { |
|
483 asc_context_initialize(); |
|
484 if (asc_has_error()) { |
|
485 SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, |
|
486 "Fatal Error",asc_get_error(), NULL); |
|
487 return 1; |
|
488 } |
|
489 |
|
490 // initialize globals |
|
491 globals_init(); |
|
492 |
|
493 // create the window |
|
494 asc_window_initialize(0, asc_gl_context_settings_default(4, 0)); |
|
495 asc_window_set_title(0, "Snake"); |
|
496 asc_window_set_size(0, asc_vec2_ftou( |
|
497 asc_vec2f_scale(ASC_VEC2F(1000+HUD_WIDTH, 1000), asc_ui_scale_auto()))); |
|
498 asc_window_center(0); |
|
499 |
|
500 // load textures |
|
501 textures_init(); |
|
502 |
|
503 // initialize backdrop scene |
|
504 asc_scene_init(BACKDROP_SCENE, "backdrop", |
|
505 .type = ASC_CAMERA_ORTHO, |
|
506 .projection_update_func = asc_camera_ortho_update_size |
|
507 ); |
|
508 backdrop_create(); |
|
509 |
|
510 // Initialize main scene |
|
511 asc_scene_init(MAIN_SCENE, "main", |
|
512 .type = ASC_CAMERA_ORTHO, |
|
513 .ortho.rect = ASC_RECT( |
|
514 -GAME_FIELD_TILE_SIZE, |
|
515 -GAME_FIELD_TILE_SIZE, |
|
516 (GAME_FIELD_SIZE+2)*GAME_FIELD_TILE_SIZE, |
|
517 (GAME_FIELD_SIZE+2)*GAME_FIELD_TILE_SIZE |
|
518 ), |
|
519 .viewport_clear = true, |
|
520 .clear_color = ASC_RGBi(0, 32, 16), |
|
521 .viewport_update_func = main_scene_viewport_update |
|
522 ); |
|
523 |
|
524 // create the fps counter |
|
525 AscSceneNode *fps_counter = fps_counter_create(); |
|
526 asc_scene_node_hide(fps_counter); |
|
527 |
|
528 // create game over text |
|
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 |
|
545 // TODO: add a main menu and start with the menu |
|
546 game.state = GAME_STATE_PLAYING; |
|
547 game.players[0] = player_create(); |
|
548 game_field_create(); |
|
549 |
|
550 // Main Loop |
|
551 do { |
|
552 // quit application on any error |
|
553 if (asc_has_error()) { |
|
554 SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, |
|
555 "Fatal Error", asc_get_error(), |
|
556 asc_active_window->window); |
|
557 asc_clear_error(); |
|
558 asc_context_quit(); |
|
559 } |
|
560 |
|
561 // game states |
|
562 // TODO: move all this into behaviors |
|
563 if (game.state == GAME_STATE_PLAYING) { |
|
564 // TODO: implement hot seat 1on1 multiplayer |
|
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 } |
|
575 } |
|
576 |
|
577 // debug-key for clearing the shader registry |
|
578 if (asc_key_pressed(ASC_KEY(S))) { |
|
579 asc_shader_clear_registry(); |
|
580 asc_dprintf("Shader cache cleared."); |
|
581 } |
|
582 |
|
583 // show/hide the FPS counter |
|
584 if (asc_key_pressed(ASC_KEY(F2))) { |
|
585 asc_scene_node_toggle_visibility(fps_counter); |
|
586 } |
|
587 |
|
588 // quit application on ESC key press |
|
589 if (asc_key_pressed(ASC_KEY(ESCAPE))) { |
|
590 asc_context_quit(); |
|
591 } |
|
592 } while (asc_loop_next()); |
|
593 |
|
594 // cleanup |
|
595 asc_context_destroy(); |
|
596 return 0; |
|
597 } |
|
598 |
|