From 19ba4047ca10848649e76be7317138ced55ac018 Mon Sep 17 00:00:00 2001 From: Sara Date: Mon, 18 Mar 2024 23:05:42 +0100 Subject: [PATCH] feat: added simple enemy with chase behaviour --- godot/Enemies/enemy.tscn | 46 +++++++++++++++++++++++++ godot/player.tscn | 2 +- godot/player_character.tscn | 11 +++++- godot/project.godot | 6 ++++ godot/test_level.tscn | 21 ++++++++--- src/enemy.cpp | 69 +++++++++++++++++++++++++++++++++++++ src/enemy.hpp | 40 +++++++++++++++++++++ src/register_types.cpp | 13 ++++--- src/tunnels_player.cpp | 7 ++-- src/tunnels_player.hpp | 2 ++ 10 files changed, 202 insertions(+), 15 deletions(-) create mode 100644 godot/Enemies/enemy.tscn create mode 100644 src/enemy.cpp create mode 100644 src/enemy.hpp diff --git a/godot/Enemies/enemy.tscn b/godot/Enemies/enemy.tscn new file mode 100644 index 0000000..7b218d8 --- /dev/null +++ b/godot/Enemies/enemy.tscn @@ -0,0 +1,46 @@ +[gd_scene load_steps=6 format=3 uid="uid://deb8qiasxsobt"] + +[sub_resource type="SphereShape3D" id="SphereShape3D_tnc8b"] +radius = 7.23 + +[sub_resource type="SphereShape3D" id="SphereShape3D_1inhn"] + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_y3cyo"] +albedo_color = Color(0.0666667, 0.223529, 0.254902, 1) + +[sub_resource type="BoxMesh" id="BoxMesh_y311e"] +material = SubResource("StandardMaterial3D_y3cyo") +size = Vector3(0.77, 0.59, 0.805) + +[sub_resource type="BoxMesh" id="BoxMesh_bc6co"] +material = SubResource("StandardMaterial3D_y3cyo") +size = Vector3(0.475, 0.495, 0.47) + +[node name="Enemy" type="Enemy"] +collision_layer = 7 + +[node name="NavigationAgent3D" type="NavigationAgent3D" parent="."] +path_desired_distance = 2.25 + +[node name="VisionArea" type="Area3D" parent="."] +collision_layer = 0 +collision_mask = 2 + +[node name="CollisionShape3D" type="CollisionShape3D" parent="VisionArea"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.32109, 0) +shape = SubResource("SphereShape3D_tnc8b") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.495191, 0) +shape = SubResource("SphereShape3D_1inhn") + +[node name="Health" type="Health" parent="."] +max_health = 10 + +[node name="MeshInstance3D" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.445159, -0.0811542) +mesh = SubResource("BoxMesh_y311e") + +[node name="MeshInstance3D2" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.430494, 0.458904) +mesh = SubResource("BoxMesh_bc6co") diff --git a/godot/player.tscn b/godot/player.tscn index 7cb7f7d..984ddd3 100644 --- a/godot/player.tscn +++ b/godot/player.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=3 format=3 uid="uid://cqkbxe758jr7p"] [sub_resource type="Curve" id="Curve_bxjan"] -_data = [Vector2(0, 0), 0.0, 0.0628674, 0, 0, Vector2(0.51626, 0.549451), 2.7033, 2.7033, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0] +_data = [Vector2(0, 0), 0.0, 3.33407, 0, 0, Vector2(0.430894, 0.692308), 0.730621, 0.730621, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0] point_count = 3 [sub_resource type="SphereMesh" id="SphereMesh_jkn5p"] diff --git a/godot/player_character.tscn b/godot/player_character.tscn index 0397333..eee6d22 100644 --- a/godot/player_character.tscn +++ b/godot/player_character.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=5 format=3 uid="uid://dpda341t6ipiv"] +[gd_scene load_steps=6 format=3 uid="uid://dpda341t6ipiv"] [sub_resource type="Curve" id="Curve_7rmf4"] min_value = 0.2 @@ -12,11 +12,18 @@ height = 1.59321 [sub_resource type="CapsuleMesh" id="CapsuleMesh_rwcvu"] height = 1.605 +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_scmx3"] +albedo_color = Color(0.94902, 0.909804, 0, 1) + [sub_resource type="BoxMesh" id="BoxMesh_f5yvh"] size = Vector3(0.125, 0.14, 0.94) [node name="PlayerCharacter" type="PlayerCharacter"] rotation_speed_curve = SubResource("Curve_7rmf4") +collision_layer = 7 + +[node name="Health" type="Health" parent="."] +max_health = 5 [node name="CollisionShape3D" type="CollisionShape3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.802835, 0) @@ -27,7 +34,9 @@ shape = SubResource("CapsuleShape3D_3g72p") [node name="MeshInstance3D" type="MeshInstance3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.8121, 0) mesh = SubResource("CapsuleMesh_rwcvu") +surface_material_override/0 = SubResource("StandardMaterial3D_scmx3") [node name="MeshInstance3D2" type="MeshInstance3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.509142, 0.986876, 0.380722) mesh = SubResource("BoxMesh_f5yvh") +surface_material_override/0 = SubResource("StandardMaterial3D_scmx3") diff --git a/godot/project.godot b/godot/project.godot index 82f47d4..98015f0 100644 --- a/godot/project.godot +++ b/godot/project.godot @@ -37,3 +37,9 @@ move_backward={ "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"echo":false,"script":null) ] } + +[layer_names] + +3d_physics/layer_1="Default" +3d_physics/layer_2="Vision" +3d_physics/layer_3="Hitboxes" diff --git a/godot/test_level.tscn b/godot/test_level.tscn index a6e9eab..bd50153 100644 --- a/godot/test_level.tscn +++ b/godot/test_level.tscn @@ -1,6 +1,7 @@ -[gd_scene load_steps=9 format=3 uid="uid://m36guasmi3c1"] +[gd_scene load_steps=11 format=3 uid="uid://m36guasmi3c1"] [ext_resource type="PackedScene" uid="uid://cqkbxe758jr7p" path="res://player.tscn" id="1_hv5rj"] +[ext_resource type="PackedScene" uid="uid://deb8qiasxsobt" path="res://Enemies/enemy.tscn" id="2_10wh5"] [sub_resource type="GameState" id="GameState_k4j3x"] @@ -17,6 +18,10 @@ sky_material = SubResource("ProceduralSkyMaterial_s4k5k") background_mode = 2 sky = SubResource("Sky_oquga") +[sub_resource type="NavigationMesh" id="NavigationMesh_gx4lq"] +vertices = PackedVector3Array(-9.5, 0.625, -9.5, -9.5, 0.625, 9.5, 9.5, 0.625, 9.5, 9.5, 0.625, -9.5) +polygons = [PackedInt32Array(3, 2, 0), PackedInt32Array(0, 2, 1)] + [sub_resource type="BoxMesh" id="BoxMesh_5glbk"] size = Vector3(20, 0.25, 20) @@ -29,14 +34,20 @@ game_mode_prototype = SubResource("TunnelsGameMode_itn7y") [node name="WorldEnvironment" type="WorldEnvironment" parent="."] environment = SubResource("Environment_mt2l0") -[node name="MeshInstance3D" type="MeshInstance3D" parent="WorldEnvironment"] +[node name="NavigationRegion3D" type="NavigationRegion3D" parent="WorldEnvironment"] +navigation_mesh = SubResource("NavigationMesh_gx4lq") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="WorldEnvironment/NavigationRegion3D"] mesh = SubResource("BoxMesh_5glbk") -skeleton = NodePath("../..") +skeleton = NodePath("../../..") -[node name="StaticBody3D" type="StaticBody3D" parent="WorldEnvironment"] +[node name="StaticBody3D" type="StaticBody3D" parent="WorldEnvironment/NavigationRegion3D/MeshInstance3D"] -[node name="CollisionShape3D" type="CollisionShape3D" parent="WorldEnvironment/StaticBody3D"] +[node name="CollisionShape3D" type="CollisionShape3D" parent="WorldEnvironment/NavigationRegion3D/MeshInstance3D/StaticBody3D"] shape = SubResource("BoxShape3D_kacqg") [node name="DirectionalLight3D" type="DirectionalLight3D" parent="WorldEnvironment"] transform = Transform3D(0.581243, 0.357328, -0.731077, 0, 0.898426, 0.439124, 0.81373, -0.255238, 0.522204, 0, 2.44259, 0) + +[node name="Enemy" parent="." instance=ExtResource("2_10wh5")] +transform = Transform3D(-0.925514, 0, -0.378713, 0, 1, 0, 0.378713, 0, -0.925514, -3.91746, 0.125, 5.6328) diff --git a/src/enemy.cpp b/src/enemy.cpp new file mode 100644 index 0000000..c4257e7 --- /dev/null +++ b/src/enemy.cpp @@ -0,0 +1,69 @@ +#include "enemy.hpp" +#include "godot_cpp/classes/time.hpp" +#include "godot_cpp/variant/utility_functions.hpp" +#include "health.hpp" +#include "player_character.hpp" +#include "utils/godot_macros.h" +#include + +namespace godot { +void Enemy::_bind_methods() { +#define CLASSNAME Enemy + GDFUNCTION_ARGS(body_entered_vision_area, "body"); +} + +void Enemy::_ready() { GDGAMEONLY(); + this->health = this->get_node("Health"); + this->nav_agent = this->get_node("NavigationAgent3D"); + this->vision_area = this->get_node("VisionArea"); + this->vision_area->connect("body_entered", Callable(this, "body_entered_vision_area")); + this->update_navigation_target(); +} + +void Enemy::_process(double delta_time) { GDGAMEONLY(); + if(this->renav_time > Time::get_singleton()->get_ticks_msec() / 1000.f) { + this->update_navigation_target(); + } + this->process_navigation(delta_time); + this->move_and_slide(); +} + +void Enemy::process_navigation(double delta_time) { + if(this->nav_agent->is_navigation_finished()) { + return; + } + Vector3 const desired_direction = (this->nav_agent->get_next_path_position() - this->get_global_position()).normalized(); + this->set_velocity(desired_direction); + Transform3D trans = this->get_global_transform(); + Vector3 const forward = desired_direction; + trans.set_basis(Basis{desired_direction.cross(Vector3{0.f, 1.f, 0.f}), Vector3{0.f, 1.f, 0.f}, forward}); + this->set_global_transform(trans); +} + +void Enemy::body_entered_vision_area(Node3D *body) { + PlayerCharacter *player = Object::cast_to(body); + if(player == nullptr) + return; + // TODO: replace this with some condition deciding wether to attack the new character or the current target + this->target_player = player; + this->update_navigation_target(); +} + +void Enemy::update_navigation_target() { + this->renav_time = float(Time::get_singleton()->get_ticks_msec()) / 1000.f + Enemy::RENAV_INTERVAL; + if(this->target_player == nullptr) + this->nav_agent->set_target_position(this->get_global_position()); + else + this->nav_agent->set_target_position(this->target_player->get_global_position()); +} + +Health *Enemy::get_health() { + return this->health; +} + +Health const *Enemy::get_health() const { + return this->health; +} + +float const Enemy::RENAV_INTERVAL{0.25f}; +} diff --git a/src/enemy.hpp b/src/enemy.hpp new file mode 100644 index 0000000..1c644f3 --- /dev/null +++ b/src/enemy.hpp @@ -0,0 +1,40 @@ +#ifndef ENEMY_HPP +#define ENEMY_HPP + +#include "godot_cpp/classes/area3d.hpp" +#include "health.hpp" +#include + +namespace godot { +class PlayerCharacter; +class NavigationAgent3D; + +class Enemy : public CharacterBody3D, + public IHealthEntity { + GDCLASS(Enemy, CharacterBody3D); + static void _bind_methods(); +public: + virtual void _ready() override; + virtual void _process(double delta_time) override; + void process_navigation(double delta_time); + + void body_entered_vision_area(Node3D *body); + + void update_navigation_target(); + + virtual Health *get_health() override; + virtual Health const *get_health() const override; + +private: + float renav_time{0.f}; + + PlayerCharacter *target_player{nullptr}; + Health *health{nullptr}; + NavigationAgent3D *nav_agent{nullptr}; + Area3D *vision_area{nullptr}; + + static float const RENAV_INTERVAL; +}; +} + +#endif // !ENEMY_HPP diff --git a/src/register_types.cpp b/src/register_types.cpp index 289aca8..ff0b261 100644 --- a/src/register_types.cpp +++ b/src/register_types.cpp @@ -9,6 +9,7 @@ #include "utils/level.hpp" #include "utils/player_input.hpp" #include "utils/spawn_point.hpp" +#include "enemy.hpp" #include "health.hpp" #include "player_character.hpp" #include "tunnels_game_mode.hpp" @@ -22,16 +23,18 @@ void initialize_gdextension_types(ModuleInitializationLevel p_level) return; } ClassDB::register_abstract_class(); - ClassDB::register_class(); - ClassDB::register_class(); - ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); - ClassDB::register_class(); - ClassDB::register_class(); } extern "C" diff --git a/src/tunnels_player.cpp b/src/tunnels_player.cpp index 6381494..444b711 100644 --- a/src/tunnels_player.cpp +++ b/src/tunnels_player.cpp @@ -68,7 +68,7 @@ void TunnelsPlayer::process_mouse_location(double delta_time) { void TunnelsPlayer::process_camera_rotation(double delta_time) { Vector3 rotation = this->get_global_rotation(); - float const y_multiplier = std::max(0.4f, this->mouse_location.y); // the influence of the mouse's y position on the rotation speed + float const y_multiplier = std::max(TunnelsPlayer::ROTATION_Y_MIN_INFLUENCE, this->mouse_location.y); // the influence of the mouse's y position on the rotation speed // rotate the camera when the mouse is close to the edge of the screen if(this->mouse_location.x < TunnelsPlayer::ROTATION_MARGIN) { // normalized measurement of how far into the rotation margin the mouse is @@ -156,6 +156,7 @@ Ref TunnelsPlayer::get_camera_rotation_ramp() const { return this->camera_rotation_ramp; } -float const TunnelsPlayer::ROTATION_SPEED{4.f}; -float const TunnelsPlayer::ROTATION_MARGIN{0.35f}; +float const TunnelsPlayer::ROTATION_SPEED{6.f}; +float const TunnelsPlayer::ROTATION_Y_MIN_INFLUENCE{7.f}; +float const TunnelsPlayer::ROTATION_MARGIN{0.4f}; } diff --git a/src/tunnels_player.hpp b/src/tunnels_player.hpp index 1749c25..955f5aa 100644 --- a/src/tunnels_player.hpp +++ b/src/tunnels_player.hpp @@ -53,7 +53,9 @@ private: Camera3D *camera{nullptr}; Ref camera_rotation_ramp{}; + static float const ROTATION_SPEED; + static float const ROTATION_Y_MIN_INFLUENCE; static float const ROTATION_MARGIN; }; }