# HG changeset patch # User Mike Becker # Date 1755898056 -7200 # Node ID ba7f043f9fdf2573af48e0ecc7807e4d09f5baa2 # Parent 966bfca56b9d5848f86acaa3e3c2172ad7a3f12d move the player's life "controller" to a behavior diff -r 966bfca56b9d -r ba7f043f9fdf demo/snake/snake.c --- a/demo/snake/snake.c Thu Aug 21 22:13:51 2025 +0200 +++ b/demo/snake/snake.c Fri Aug 22 23:27:36 2025 +0200 @@ -72,8 +72,9 @@ /** * The new position of the player when @c reset_position is @c true. */ - asc_vec2i new_position; + asc_vec2u new_position; unsigned health; + bool alive; bool reset_position; uint8_t number; } Player; @@ -272,25 +273,46 @@ asc_mesh_draw_triangle_strip(&sprite->mesh); } -static void player_set_health(Player *player, unsigned health) { - player->health = health; - // TODO: probably we want to add more effects when the health changes +static void player_position(Player *pl, unsigned x, unsigned y) { + pl->new_position.x = x; + pl->new_position.y = y; + pl->reset_position = true; +} + +static void player_position_random(Player *pl) { + // TODO: check if the spawn location is viable when there is more action on the board + player_position( + pl, + 4u + asc_util_rand(GAME_FIELD_SIZE - 8u), + 4u + asc_util_rand(GAME_FIELD_SIZE - 8u) + ); } -static unsigned player_get_health(Player *player) { - return player->health; +static void player_main_behavior(AscBehavior *behavior) { + AscSceneNode *node = behavior->node; + Player *player = node->user_data; + + if (player->alive && player->health == 0) { + player->alive = false; + asc_scene_node_hide(node); + // TODO: probably we don't want the entire trace to disappear instantly + cxListClear(player->trace); + // TODO: this should be controlled by another behavior that watches all players + game.state = GAME_STATE_GAME_OVER; + } + + // TODO: replace with respawn event + if (!player->alive && player->health > 0) { + player_position_random(player); + player->alive = true; + asc_scene_node_show(node); + } } static void player_move(AscBehavior *behavior) { AscSceneNode *node = behavior->node; Player *player = node->user_data; - // TODO: instead of skipping this behavior, it should be disabled when health is zero - if (player_get_health(player) == 0) return; - - // TODO: move this to a different behavior - asc_scene_node_show(node); - const float ts = (float) GAME_FIELD_TILE_SIZE; // check if the position is set programmatically @@ -354,12 +376,8 @@ if (node->position.x < 0 || node->position.y < 0 || node->position.x > GAME_FIELD_SIZE*GAME_FIELD_TILE_SIZE || node->position.y > GAME_FIELD_SIZE*GAME_FIELD_TILE_SIZE) { - // TODO: add fancy death animation - asc_scene_node_hide(node); - player_set_health(player, 0); - // TODO: remove the trace gradually (dequeuing the trace should be a different behavior) - cxListClear(player->trace); - game.state = GAME_STATE_GAME_OVER; + // TODO: replace setting health to zero with a "kill" event + player->health = 0; return; } @@ -383,9 +401,6 @@ } static void player_controls(AscBehavior *behavior) { - // TODO: instead of checking the game state, disable the behavior when the player is dead - if (game.state != GAME_STATE_PLAYING) return; - Player *player = behavior->node->user_data; // TODO: different key bindings for different player number in hot seat mode if (asc_key_pressed(ASC_KEY(LEFT))) { @@ -410,21 +425,6 @@ } } -static void player_position(Player *pl, int x, int y) { - pl->new_position.x = x; - pl->new_position.y = y; - pl->reset_position = true; -} - -static void player_position_random(Player *pl) { - // TODO: check if the spawn location is viable when there is more action on the board - player_position( - pl, - 4 + asc_util_rand(GAME_FIELD_SIZE - 8), - 4 + asc_util_rand(GAME_FIELD_SIZE - 8) - ); -} - static void player_destroy(CxAllocator *allocator, Player *player) { cxListFree(player->trace); cxFree(allocator, player); @@ -444,7 +444,6 @@ ); asc_scene_add_node(MAIN_SCENE, node); Player *player = asc_scene_node_allocate_data(node, sizeof(Player)); - player_position_random(player); player->speed = 3.f; // start with 3 tiles/sec player->number = 1; player->health = 100; @@ -453,9 +452,13 @@ cxDefineDestructor(player->trace, player_trace_release_tile); node->draw_func = player_draw; node->user_data_free_func = (cx_destructor_func2)player_destroy; - // add behaviors (the order is important!) + + // add behaviors + asc_behavior_add(node, player_main_behavior, .always_enabled = true); asc_behavior_add(node, player_controls); asc_behavior_add(node, player_move); + asc_behavior_pause_all_while_hidden(node); + return player; } @@ -580,9 +583,9 @@ if (game.state == GAME_STATE_GAME_OVER) { asc_scene_node_show(text_game_over); if (asc_key_pressed(ASC_KEY(R))) { + // TODO: instead of setting the health, send a "respawn" event to the behavior + game.players[0]->health = 100; // TODO: re-load the "level" - player_position_random(game.players[0]); - player_set_health(game.players[0], 100); game.state = GAME_STATE_PLAYING; asc_scene_node_hide(text_game_over); } diff -r 966bfca56b9d -r ba7f043f9fdf src/ascension/behavior.h --- a/src/ascension/behavior.h Thu Aug 21 22:13:51 2025 +0200 +++ b/src/ascension/behavior.h Fri Aug 22 23:27:36 2025 +0200 @@ -45,6 +45,7 @@ uint64_t interval; cxmutstr name; bool pause_while_hidden; + bool always_enabled; bool enabled; bool killed; }; @@ -75,6 +76,17 @@ * @see asc_scene_node_hide() */ bool pause_while_hidden; + /** + * Set to true if the behavior shall start disabled. + */ + bool start_disabled; + /** + * Behavior that cannot be disabled or paused (even by asc_behavior_disable_all()). + * + * Useful if you want to create a behavior that is always running (for example, a watchdog), + * even when the node is hidden and/or all (other) behaviors are disabled. + */ + bool always_enabled; }; AscBehavior *asc_behavior_add_(AscSceneNode *node, struct asc_behavior_create_args args); @@ -122,7 +134,7 @@ * @param behavior the behavior to disable */ static inline void asc_behavior_disable(AscBehavior *behavior) { - behavior->enabled = false; + behavior->enabled = behavior->always_enabled; } /** diff -r 966bfca56b9d -r ba7f043f9fdf src/behavior.c --- a/src/behavior.c Thu Aug 21 22:13:51 2025 +0200 +++ b/src/behavior.c Fri Aug 22 23:27:36 2025 +0200 @@ -58,9 +58,11 @@ cxmutstr name = args.name == NULL ? asc_util_gen_name("behavior") : cx_mutstr(strdup(args.name)); + // FIXME: we need the ordered map here, because the execution order of behaviors is important AscBehavior *behavior = cxMapEmplace(node->behaviors, name); assert(behavior != NULL); - behavior->enabled = true; + behavior->enabled = !args.start_disabled; + behavior->always_enabled = args.always_enabled; behavior->killed = false; behavior->node = node; behavior->func = args.func; @@ -83,7 +85,7 @@ void asc_behavior_trigger(AscBehavior *behavior) { if (!behavior->enabled) return; if (behavior->last_execution + behavior->interval > asc_context.total_nanos) return; - if (behavior->pause_while_hidden && asc_scene_node_is_hidden(behavior->node)) return; + if (!behavior->always_enabled && behavior->pause_while_hidden && asc_scene_node_is_hidden(behavior->node)) return; behavior->func(behavior); behavior->last_execution = asc_context.total_nanos; @@ -99,27 +101,27 @@ void asc_behavior_enable_all(AscSceneNode *node) { CxMapIterator iter = cxMapIteratorValues(node->behaviors); cx_foreach(AscBehavior*, behavior, iter) { - behavior->enabled = true; + asc_behavior_enable(behavior); } } void asc_behavior_disable_all(AscSceneNode *node) { CxMapIterator iter = cxMapIteratorValues(node->behaviors); cx_foreach(AscBehavior*, behavior, iter) { - behavior->enabled = false; + asc_behavior_disable(behavior); } } void asc_behavior_pause_all_while_hidden(AscSceneNode *node) { CxMapIterator iter = cxMapIteratorValues(node->behaviors); cx_foreach(AscBehavior*, behavior, iter) { - behavior->pause_while_hidden = true; + asc_behavior_pause_while_hidden(behavior); } } void asc_behavior_continue_all_while_hidden(AscSceneNode *node) { CxMapIterator iter = cxMapIteratorValues(node->behaviors); cx_foreach(AscBehavior*, behavior, iter) { - behavior->pause_while_hidden= false; + asc_behavior_continue_while_hidden(behavior); } }