From 10727fbc33fce96070103a008471c14f895fbbfe Mon Sep 17 00:00:00 2001 From: Shariq Shah Date: Fri, 24 Jan 2020 14:15:22 +1100 Subject: [PATCH] Implemented simple turret state machine ai --- assets/entities/turret.symtres | 2 +- src/common/version.h | 2 +- src/game/editor.c | 2 + src/game/enemy.c | 228 +++++++++++++++++++++++++++------ src/game/enemy.h | 8 ++ src/game/entity.h | 29 +++-- src/game/event.c | 2 +- src/game/player.c | 8 +- todo.txt | 5 + 9 files changed, 231 insertions(+), 55 deletions(-) diff --git a/assets/entities/turret.symtres b/assets/entities/turret.symtres index 8e6bdd3..9b477a3 100644 --- a/assets/entities/turret.symtres +++ b/assets/entities/turret.symtres @@ -31,7 +31,7 @@ Entity bouding_box_min : -0.500 -0.500 -0.500 source_filename : sounds/bullet_1.wav sound_type : 1 - sound_max_distance : 10.0000 + sound_max_distance : 30.0000 name : Turret_Weapon_Sound bouding_box_max : 0.500 0.500 0.500 sound_attenuation_type : 2 diff --git a/src/common/version.h b/src/common/version.h index 3dcfc8a..ab84a1d 100755 --- a/src/common/version.h +++ b/src/common/version.h @@ -4,7 +4,7 @@ /* Auto generated version file. DO NOT MODIFY */ #define SYMMETRY_VERSION_MAJOR 0 #define SYMMETRY_VERSION_MINOR 1 -#define SYMMETRY_VERSION_REVISION 320 +#define SYMMETRY_VERSION_REVISION 321 #define SYMMETRY_VERSION_BRANCH "dev" #endif \ No newline at end of file diff --git a/src/game/editor.c b/src/game/editor.c index 43d8dd9..5e2e0e8 100755 --- a/src/game/editor.c +++ b/src/game/editor.c @@ -1682,6 +1682,8 @@ void editor_window_scene_hierarchy(struct nk_context* context, struct Editor* ed nk_layout_row_dynamic(context, 380, 1); if(nk_group_begin(context, "Entity Name", NK_WINDOW_SCROLL_AUTO_HIDE)) { + editor_show_entity_in_list(editor, context, scene, &scene->player); + if(nk_tree_push(context, NK_TREE_TAB, "Cameras", NK_MAXIMIZED)) { for(int i = 0; i < MAX_SCENE_CAMERAS; i++) diff --git a/src/game/enemy.c b/src/game/enemy.c index 24ecb56..a8cb9e1 100644 --- a/src/game/enemy.c +++ b/src/game/enemy.c @@ -14,10 +14,12 @@ #include #include +#include static void enemy_on_scene_loaded(struct Event* event, void* enemy_ptr); static void enemy_update_physics_turret(struct Enemy* enemy, struct Game_State* game_state, float fixed_dt); static void enemy_update_ai_turret(struct Enemy* enemy, struct Game_State* game_state, float dt); +static void enemy_state_set_turret(struct Enemy* enemy, int state); void enemy_init(struct Enemy* enemy, int type) { @@ -32,13 +34,24 @@ void enemy_init(struct Enemy* enemy, int type) { case ENEMY_TURRET: { - enemy->health = 100; - enemy->damage = 10; - enemy->Turret.turn_direction_positive = true; - enemy->Turret.turn_speed = 10.f; - enemy->Turret.max_turn_angle = 60.f; - break; + enemy->health = 100; + enemy->damage = 10; + enemy->Turret.turn_direction_positive = true; + enemy->Turret.scan = false; + enemy->Turret.turn_speed = 10.f; + enemy->Turret.max_turn_angle = 60.f; + enemy->Turret.pulsate_height = 1.5f; + enemy->Turret.pulsate_speed_scale = 0.1f; + enemy->Turret.attack_cooldown = 0.05f; + enemy->Turret.alert_cooldown = 1.f; + enemy->Turret.color_default = (vec4){ 0.f, 1.f, 1.f, 1.f }; + enemy->Turret.color_alert = (vec4){ 1.f, 1.f, 0.f, 1.f }; + enemy->Turret.color_attack = (vec4){ 1.f, 0.f, 0.f, 1.f }; + enemy->Turret.time_elapsed_since_alert = 0.f; + enemy->Turret.time_elapsed_since_attack = 0.f; + enemy->Turret.vision_range = 15.f; } + break; default: log_error("enemy:init", "Unsupported Enemy Type"); break; @@ -70,7 +83,7 @@ void enemy_static_mesh_set(struct Enemy* enemy, const char* geometry_filename, i void enemy_update(struct Enemy* enemy, struct Scene* scene, float dt) { - static float enemy_update_interval = 1.f / 60.f; + static float enemy_update_interval = 1.f / 120.f; static float time_elapsed_since_last_update = 0.f; time_elapsed_since_last_update += dt; @@ -169,60 +182,193 @@ void enemy_on_scene_loaded(struct Event* event, void* enemy_ptr) enemy->weapon_sound = (struct Sound_Source*)child; } - if(enemy->mesh) enemy->mesh->base.flags |= EF_TRANSIENT; - if(enemy->weapon_sound) enemy->weapon_sound->base.flags |= EF_TRANSIENT; + if(enemy->mesh) + enemy->mesh->base.flags |= EF_TRANSIENT; + else + log_error("enemy:on_scene_loaded", "Could not find mesh child entity for enemy %s", enemy->base.name); + if(enemy->weapon_sound) + enemy->weapon_sound->base.flags |= EF_TRANSIENT; + else + log_error("enemy:on_scene_loaded", "Could not find weapon_sound child entity for enemy %s", enemy->base.name); // Do other post-scene-load initialization stuff per enemy type here + switch(enemy->type) + { + case ENEMY_TURRET: + enemy_state_set_turret(enemy, TURRET_DEFAULT); + break; + } } -void enemy_update_physics_turret(struct Enemy* enemy, struct Game_State* game_state, float dt) +void enemy_update_physics_turret(struct Enemy* enemy, struct Game_State* game_state, float fixed_dt) { /* Turning/Rotation */ - static vec3 turn_axis = { 0.f, 1.f, 0.f }; - float current_yaw = quat_get_yaw(&enemy->base.transform.rotation); + static vec3 yaw_axis = { 0.f, 1.f, 0.f }; + if(enemy->Turret.scan) + { + float current_yaw = quat_get_yaw(&enemy->base.transform.rotation); - float yaw = enemy->Turret.turn_speed * 1.f * dt; - if(!enemy->Turret.turn_direction_positive) - yaw *= -1.f; + float yaw = enemy->Turret.turn_speed * 1.f * fixed_dt; + if(!enemy->Turret.turn_direction_positive) + yaw *= -1.f; - current_yaw += yaw; - if(current_yaw >= enemy->Turret.max_turn_angle) - { - yaw = 0.f; - enemy->Turret.turn_direction_positive = false; + current_yaw += yaw; + if(current_yaw >= enemy->Turret.max_turn_angle) + { + yaw = 0.f; + enemy->Turret.turn_direction_positive = false; + } + else if(current_yaw <= -enemy->Turret.max_turn_angle) + { + yaw = 0.f; + enemy->Turret.turn_direction_positive = true; + } + + if(yaw != 0.f) + transform_rotate(enemy, &yaw_axis, yaw, TS_LOCAL); } - else if(current_yaw <= -enemy->Turret.max_turn_angle) + else if(!enemy->Turret.scan) { - yaw = 0.f; - enemy->Turret.turn_direction_positive = true; - } + float current_yaw = quat_get_yaw(&enemy->base.transform.rotation); + if(fabsf(current_yaw) + EPSILON != 0.f) + { + float yaw = enemy->Turret.turn_speed * 1.f * fixed_dt; + if(current_yaw > 0.f) + yaw *= -1.f; - if(yaw != 0.f) - transform_rotate(enemy, &turn_axis, yaw, TS_LOCAL); + transform_rotate(enemy, &yaw_axis, yaw, TS_LOCAL); + } + } /* Movement */ - float ticks = (float)platform_ticks_get(); - float pulsate_speed_scale = 0.1f; - float pulsate_height = 1.5f; - vec3 translation = { 0.f }; - transform_get_absolute_position(enemy, &translation); - translation.y += sinf(TO_RADIANS(ticks * pulsate_speed_scale)) * dt * pulsate_height; - transform_set_position(enemy, &translation); + if(enemy->Turret.pulsate) + { + float ticks = (float)platform_ticks_get(); + vec3 translation = { 0.f }; + //transform_get_absolute_position(enemy->mesh, &translation); + vec3_assign(&translation, &enemy->mesh->base.transform.position); + translation.y += sinf(TO_RADIANS(ticks * enemy->Turret.pulsate_speed_scale)) * fixed_dt * enemy->Turret.pulsate_height; + transform_set_position(enemy->mesh, &translation); + } } void enemy_update_ai_turret(struct Enemy* enemy, struct Game_State* game_state, float dt) { struct Scene* scene = game_state->scene; - float turret_vision_range = 5.f; + struct Ray turret_ray; - transform_get_absolute_position(enemy, &turret_ray.origin); + transform_get_absolute_position(enemy->mesh, &turret_ray.origin); transform_get_forward(enemy, &turret_ray.direction); - im_ray(&turret_ray, turret_vision_range, (vec4) { 0.f, 1.f, 1.f, 1.f }, 4); - struct Entity* player = scene_ray_intersect_closest(scene, &turret_ray, ERM_PLAYER); - if(player) + switch(enemy->current_state) + { + case TURRET_DEFAULT: + { + im_ray(&turret_ray, enemy->Turret.vision_range, enemy->Turret.color_default, 4); + struct Entity* player = scene_ray_intersect_closest(scene, &turret_ray, ERM_PLAYER); + if(player) + { + float distance = scene_entity_distance(scene, player, enemy); + if(distance <= enemy->Turret.vision_range) + { + enemy_state_set_turret(enemy, TURRET_ALERT); + log_message("Player spotted"); + } + } + } + break; + case TURRET_ALERT: { - float distance = scene_entity_distance(scene, player, enemy); - if(distance <= turret_vision_range) - log_message("Player Intersected, distance: %.3f", distance); + enemy->Turret.time_elapsed_since_alert += dt; + if(enemy->Turret.time_elapsed_since_alert >= enemy->Turret.alert_cooldown) + { + enemy_state_set_turret(enemy, TURRET_DEFAULT); + break; + } + im_ray(&turret_ray, enemy->Turret.vision_range, enemy->Turret.color_alert, 4); + struct Entity* player = scene_ray_intersect_closest(scene, &turret_ray, ERM_PLAYER); + if(player) + { + float distance = scene_entity_distance(scene, player, enemy); + if(distance <= enemy->Turret.vision_range) + { + enemy_state_set_turret(enemy, TURRET_ATTACK); + log_message("Player spotted"); + } + } + } + break; + case TURRET_ATTACK: + { + enemy->Turret.time_elapsed_since_attack += dt; + if(enemy->Turret.time_elapsed_since_attack >= enemy->Turret.attack_cooldown) + { + im_ray(&turret_ray, enemy->Turret.vision_range, enemy->Turret.color_attack, 4); + struct Entity* player = scene_ray_intersect_closest(scene, &turret_ray, ERM_PLAYER); + bool target_lost = false; + if(player) + { + float distance = scene_entity_distance(scene, player, enemy); + if(distance <= enemy->Turret.vision_range) + { + enemy->Turret.time_elapsed_since_attack = 0.f; + sound_source_play(game_state->sound, enemy->weapon_sound); + log_message("Player spotted and attacked"); + } + else + { + target_lost = true; + } + } + else + { + target_lost = true; + } + + if(target_lost) + { + enemy_state_set_turret(enemy, TURRET_ALERT); + log_message("Target lost"); + } + } + } + break; + } +} + +void enemy_state_set_turret(struct Enemy* enemy, int state) +{ + assert(state >= 0 && state < TURRET_STATE_MAX); + struct Model* model = &enemy->mesh->model; + enemy->current_state = state; + + switch(enemy->current_state) + { + case TURRET_DEFAULT: + { + vec4_assign(&model->material_params[MMP_DIFFUSE_COL].val_vec4, &enemy->Turret.color_default); + enemy->Turret.time_elapsed_since_alert = 0.f; + enemy->Turret.time_elapsed_since_attack = 0.f; + enemy->Turret.pulsate = true; + enemy->Turret.scan = false; + vec3 default_position = { 0.f }; + transform_set_position(enemy->mesh, &default_position); + } + break; + case TURRET_ALERT: + { + enemy->Turret.pulsate = false; + enemy->Turret.scan = true; + vec4_assign(&model->material_params[MMP_DIFFUSE_COL].val_vec4, &enemy->Turret.color_alert); + enemy->Turret.time_elapsed_since_alert = 0.f; + } + break; + case TURRET_ATTACK: + { + enemy->Turret.pulsate = false; + enemy->Turret.scan = false; + vec4_assign(&model->material_params[MMP_DIFFUSE_COL].val_vec4, &enemy->Turret.color_attack); + enemy->Turret.time_elapsed_since_attack = 0.f; + } + break; } } diff --git a/src/game/enemy.h b/src/game/enemy.h index c8cd081..faaa3ee 100644 --- a/src/game/enemy.h +++ b/src/game/enemy.h @@ -7,6 +7,14 @@ struct Parser_Object; struct Entity; struct Hashmap; +enum Turret_State +{ + TURRET_DEFAULT = 0, + TURRET_ALERT, + TURRET_ATTACK, + TURRET_STATE_MAX +}; + void enemy_init(struct Enemy* enemy, int type); void enemy_update_physics(struct Enemy* enemy, struct Scene* scene, float dt); void enemy_update(struct Enemy* enemy, struct Scene* scene, float dt); diff --git a/src/game/entity.h b/src/game/entity.h index a9b63cd..d18ccd2 100755 --- a/src/game/entity.h +++ b/src/game/entity.h @@ -79,14 +79,14 @@ enum Entity_Ray_Mask struct Transform { - vec3 position; - vec3 scale; - quat rotation; - mat4 trans_mat; - bool is_modified; - bool sync_physics; - struct Entity* parent; - struct Entity** children; + vec3 position; + vec3 scale; + quat rotation; + mat4 trans_mat; + bool is_modified; + bool sync_physics; + struct Entity* parent; + struct Entity** children; }; struct Entity @@ -197,6 +197,7 @@ struct Enemy int type; int health; int damage; + int current_state; struct Static_Mesh* mesh; struct Sound_Source* weapon_sound; union @@ -206,6 +207,18 @@ struct Enemy float turn_speed; float max_turn_angle; bool turn_direction_positive; + bool pulsate; + bool scan; + float pulsate_speed_scale; + float pulsate_height; + float attack_cooldown; + float time_elapsed_since_attack; + float time_elapsed_since_alert; + float alert_cooldown; + float vision_range; + vec4 color_default; + vec4 color_alert; + vec4 color_attack; }Turret; }; }; diff --git a/src/game/event.c b/src/game/event.c index 0262378..615290c 100644 --- a/src/game/event.c +++ b/src/game/event.c @@ -269,7 +269,7 @@ void event_manager_subscribe_with_object(struct Event_Manager* event_manager, in for(int i = 0; i < MAX_EVENT_SUBSCRIPTIONS; i++) { struct Event_Subscription* subscription = &event_manager->event_subsciptions[i]; - if(subscription->type == EST_WITH_OBJECT && subscription->event_type == event_type && subscription->handler_with_object == handler_func && subscription->subscriber != subscriber) + if(subscription->type == EST_WITH_OBJECT && subscription->event_type == event_type && subscription->handler_with_object == handler_func && subscription->subscriber == subscriber) { log_message("Already subscibed to %s event", event_name_get(event_type)); subscribed = true; diff --git a/src/game/player.c b/src/game/player.c index 51c750a..ad70d20 100755 --- a/src/game/player.c +++ b/src/game/player.c @@ -25,9 +25,11 @@ void player_init(struct Player* player, struct Scene* scene) { struct Game_State* game_state = game_state_get(); entity_init(player, "Player", &scene->root_entity); - player->base.flags |= EF_ACTIVE; - player->base.id = 1; - player->base.type = ET_PLAYER; + player->base.flags |= EF_ACTIVE; + player->base.id = 1; + player->base.type = ET_PLAYER; + player->base.bounding_box.min = (vec3){ -1.5f, -1.5f, -1.0f }; + player->base.bounding_box.max = (vec3){ 1.5f, 1.5f, 1.0f }; struct Hashmap* config = game_state->cvars; player->move_speed = hashmap_float_get(config, "player_move_speed"); diff --git a/todo.txt b/todo.txt index eb9db03..0f920fe 100644 --- a/todo.txt +++ b/todo.txt @@ -1,6 +1,11 @@ Todo: - Implement separate property window for player related variables that can be shown in the editor similar to renderer settings etc - Implement turret state machine + - Add all sound source properties to propery inspector + - Add turret properties to property inspector + - Add another ambient sound_source entity as child to enemy entity + - Fix Turret losing target at diagonals + - Track current height of the turret and return it to default once pulsation resumes - Fix rotate gizmo's origin not being set to the selected entity - Player shooting - Player jump cooldown, don't allow jump until a certian time interval has passed, even if we're grounded