Implemented simple turret state machine ai

dev
Shariq Shah 6 years ago
parent 9b73126b8c
commit 10727fbc33
  1. 2
      assets/entities/turret.symtres
  2. 2
      src/common/version.h
  3. 2
      src/game/editor.c
  4. 182
      src/game/enemy.c
  5. 8
      src/game/enemy.h
  6. 13
      src/game/entity.h
  7. 2
      src/game/event.c
  8. 2
      src/game/player.c
  9. 5
      todo.txt

@ -31,7 +31,7 @@ Entity
bouding_box_min : -0.500 -0.500 -0.500 bouding_box_min : -0.500 -0.500 -0.500
source_filename : sounds/bullet_1.wav source_filename : sounds/bullet_1.wav
sound_type : 1 sound_type : 1
sound_max_distance : 10.0000 sound_max_distance : 30.0000
name : Turret_Weapon_Sound name : Turret_Weapon_Sound
bouding_box_max : 0.500 0.500 0.500 bouding_box_max : 0.500 0.500 0.500
sound_attenuation_type : 2 sound_attenuation_type : 2

@ -4,7 +4,7 @@
/* Auto generated version file. DO NOT MODIFY */ /* Auto generated version file. DO NOT MODIFY */
#define SYMMETRY_VERSION_MAJOR 0 #define SYMMETRY_VERSION_MAJOR 0
#define SYMMETRY_VERSION_MINOR 1 #define SYMMETRY_VERSION_MINOR 1
#define SYMMETRY_VERSION_REVISION 320 #define SYMMETRY_VERSION_REVISION 321
#define SYMMETRY_VERSION_BRANCH "dev" #define SYMMETRY_VERSION_BRANCH "dev"
#endif #endif

@ -1682,6 +1682,8 @@ void editor_window_scene_hierarchy(struct nk_context* context, struct Editor* ed
nk_layout_row_dynamic(context, 380, 1); nk_layout_row_dynamic(context, 380, 1);
if(nk_group_begin(context, "Entity Name", NK_WINDOW_SCROLL_AUTO_HIDE)) 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)) if(nk_tree_push(context, NK_TREE_TAB, "Cameras", NK_MAXIMIZED))
{ {
for(int i = 0; i < MAX_SCENE_CAMERAS; i++) for(int i = 0; i < MAX_SCENE_CAMERAS; i++)

@ -14,10 +14,12 @@
#include <string.h> #include <string.h>
#include <math.h> #include <math.h>
#include <assert.h>
static void enemy_on_scene_loaded(struct Event* event, void* enemy_ptr); 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_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_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) void enemy_init(struct Enemy* enemy, int type)
{ {
@ -35,10 +37,21 @@ void enemy_init(struct Enemy* enemy, int type)
enemy->health = 100; enemy->health = 100;
enemy->damage = 10; enemy->damage = 10;
enemy->Turret.turn_direction_positive = true; enemy->Turret.turn_direction_positive = true;
enemy->Turret.scan = false;
enemy->Turret.turn_speed = 10.f; enemy->Turret.turn_speed = 10.f;
enemy->Turret.max_turn_angle = 60.f; enemy->Turret.max_turn_angle = 60.f;
break; 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: default:
log_error("enemy:init", "Unsupported Enemy Type"); log_error("enemy:init", "Unsupported Enemy Type");
break; 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) 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; static float time_elapsed_since_last_update = 0.f;
time_elapsed_since_last_update += dt; time_elapsed_since_last_update += dt;
@ -169,19 +182,33 @@ void enemy_on_scene_loaded(struct Event* event, void* enemy_ptr)
enemy->weapon_sound = (struct Sound_Source*)child; enemy->weapon_sound = (struct Sound_Source*)child;
} }
if(enemy->mesh) enemy->mesh->base.flags |= EF_TRANSIENT; if(enemy->mesh)
if(enemy->weapon_sound) enemy->weapon_sound->base.flags |= EF_TRANSIENT; 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 // 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 */ /* Turning/Rotation */
static vec3 turn_axis = { 0.f, 1.f, 0.f }; 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 current_yaw = quat_get_yaw(&enemy->base.transform.rotation);
float yaw = enemy->Turret.turn_speed * 1.f * dt; float yaw = enemy->Turret.turn_speed * 1.f * fixed_dt;
if(!enemy->Turret.turn_direction_positive) if(!enemy->Turret.turn_direction_positive)
yaw *= -1.f; yaw *= -1.f;
@ -198,31 +225,150 @@ void enemy_update_physics_turret(struct Enemy* enemy, struct Game_State* game_st
} }
if(yaw != 0.f) if(yaw != 0.f)
transform_rotate(enemy, &turn_axis, yaw, TS_LOCAL); transform_rotate(enemy, &yaw_axis, yaw, TS_LOCAL);
}
else if(!enemy->Turret.scan)
{
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;
transform_rotate(enemy, &yaw_axis, yaw, TS_LOCAL);
}
}
/* Movement */ /* Movement */
if(enemy->Turret.pulsate)
{
float ticks = (float)platform_ticks_get(); float ticks = (float)platform_ticks_get();
float pulsate_speed_scale = 0.1f;
float pulsate_height = 1.5f;
vec3 translation = { 0.f }; vec3 translation = { 0.f };
transform_get_absolute_position(enemy, &translation); //transform_get_absolute_position(enemy->mesh, &translation);
translation.y += sinf(TO_RADIANS(ticks * pulsate_speed_scale)) * dt * pulsate_height; vec3_assign(&translation, &enemy->mesh->base.transform.position);
transform_set_position(enemy, &translation); 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) void enemy_update_ai_turret(struct Enemy* enemy, struct Game_State* game_state, float dt)
{ {
struct Scene* scene = game_state->scene; struct Scene* scene = game_state->scene;
float turret_vision_range = 5.f;
struct Ray turret_ray; 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); transform_get_forward(enemy, &turret_ray.direction);
im_ray(&turret_ray, turret_vision_range, (vec4) { 0.f, 1.f, 1.f, 1.f }, 4); 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:
{
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); struct Entity* player = scene_ray_intersect_closest(scene, &turret_ray, ERM_PLAYER);
if(player) if(player)
{ {
float distance = scene_entity_distance(scene, player, enemy); float distance = scene_entity_distance(scene, player, enemy);
if(distance <= turret_vision_range) if(distance <= enemy->Turret.vision_range)
log_message("Player Intersected, distance: %.3f", distance); {
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;
} }
} }

@ -7,6 +7,14 @@ struct Parser_Object;
struct Entity; struct Entity;
struct Hashmap; 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_init(struct Enemy* enemy, int type);
void enemy_update_physics(struct Enemy* enemy, struct Scene* scene, float dt); void enemy_update_physics(struct Enemy* enemy, struct Scene* scene, float dt);
void enemy_update(struct Enemy* enemy, struct Scene* scene, float dt); void enemy_update(struct Enemy* enemy, struct Scene* scene, float dt);

@ -197,6 +197,7 @@ struct Enemy
int type; int type;
int health; int health;
int damage; int damage;
int current_state;
struct Static_Mesh* mesh; struct Static_Mesh* mesh;
struct Sound_Source* weapon_sound; struct Sound_Source* weapon_sound;
union union
@ -206,6 +207,18 @@ struct Enemy
float turn_speed; float turn_speed;
float max_turn_angle; float max_turn_angle;
bool turn_direction_positive; 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; }Turret;
}; };
}; };

@ -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++) for(int i = 0; i < MAX_EVENT_SUBSCRIPTIONS; i++)
{ {
struct Event_Subscription* subscription = &event_manager->event_subsciptions[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)); log_message("Already subscibed to %s event", event_name_get(event_type));
subscribed = true; subscribed = true;

@ -28,6 +28,8 @@ void player_init(struct Player* player, struct Scene* scene)
player->base.flags |= EF_ACTIVE; player->base.flags |= EF_ACTIVE;
player->base.id = 1; player->base.id = 1;
player->base.type = ET_PLAYER; 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; struct Hashmap* config = game_state->cvars;
player->move_speed = hashmap_float_get(config, "player_move_speed"); player->move_speed = hashmap_float_get(config, "player_move_speed");

@ -1,6 +1,11 @@
Todo: Todo:
- Implement separate property window for player related variables that can be shown in the editor similar to renderer settings etc - Implement separate property window for player related variables that can be shown in the editor similar to renderer settings etc
- Implement turret state machine - 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 - Fix rotate gizmo's origin not being set to the selected entity
- Player shooting - Player shooting
- Player jump cooldown, don't allow jump until a certian time interval has passed, even if we're grounded - Player jump cooldown, don't allow jump until a certian time interval has passed, even if we're grounded

Loading…
Cancel
Save