Compare commits
	
		
			20 commits
		
	
	
		
			main
			...
			basic-enem
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 9172b98b9b | ||
|   | 2e4591c3df | ||
|   | 3863b622f5 | ||
|   | 5badb8a68f | ||
|   | d3e8c75d1a | ||
|   | 31b5312cf5 | ||
|   | 491e375b6e | ||
|   | 2c3b3f2724 | ||
|   | a86b912d01 | ||
|   | d5a6aec9d1 | ||
|   | 8366d5b5df | ||
|   | 71596133a5 | ||
|   | f8ffa67a11 | ||
|   | 85b0c66cb3 | ||
|   | f82365b08b | ||
|   | c55d6b67ef | ||
|   | c689d16dfd | ||
|   | ca53fb4ae1 | ||
|   | 5967bbb95b | ||
|   | 8fca1d154f | 
|  | @ -1,46 +0,0 @@ | |||
| [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") | ||||
							
								
								
									
										26
									
								
								godot/Enemies/flower.tscn
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								godot/Enemies/flower.tscn
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,26 @@ | |||
| [gd_scene load_steps=3 format=3 uid="uid://dopygosr0hxb5"] | ||||
| 
 | ||||
| [sub_resource type="SphereShape3D" id="SphereShape3D_ag2i6"] | ||||
| radius = 2.63833 | ||||
| 
 | ||||
| [sub_resource type="CylinderMesh" id="CylinderMesh_vcf5j"] | ||||
| height = 0.2 | ||||
| radial_segments = 16 | ||||
| rings = 1 | ||||
| cap_bottom = false | ||||
| 
 | ||||
| [node name="CharacterActor" type="CharacterActor"] | ||||
| 
 | ||||
| [node name="Health" type="Health" parent="."] | ||||
| 
 | ||||
| [node name="Planner" type="Planner" parent="."] | ||||
| 
 | ||||
| [node name="CharacterAwareness" type="CharacterAwareness" parent="."] | ||||
| collision_mask = 2 | ||||
| 
 | ||||
| [node name="CollisionShape3D" type="CollisionShape3D" parent="CharacterAwareness"] | ||||
| shape = SubResource("SphereShape3D_ag2i6") | ||||
| 
 | ||||
| [node name="MeshInstance3D" type="MeshInstance3D" parent="."] | ||||
| transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.364549, 0) | ||||
| mesh = SubResource("CylinderMesh_vcf5j") | ||||
|  | @ -2,14 +2,19 @@ | |||
| 
 | ||||
| [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_l2qcn"] | ||||
| albedo_color = Color(1, 0.345098, 0, 1) | ||||
| emission_enabled = true | ||||
| emission = Color(1, 0.683333, 0, 1) | ||||
| 
 | ||||
| [sub_resource type="CapsuleMesh" id="CapsuleMesh_0fa8x"] | ||||
| material = SubResource("StandardMaterial3D_l2qcn") | ||||
| radius = 0.02 | ||||
| height = 0.3 | ||||
| height = 0.12 | ||||
| radial_segments = 8 | ||||
| rings = 2 | ||||
| 
 | ||||
| [node name="PelletProjectile" type="PelletProjectile"] | ||||
| 
 | ||||
| [node name="MeshInstance3D" type="MeshInstance3D" parent="."] | ||||
| transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 0, 0) | ||||
| cast_shadow = 0 | ||||
| mesh = SubResource("CapsuleMesh_0fa8x") | ||||
|  |  | |||
|  | @ -3,7 +3,8 @@ | |||
| [ext_resource type="PackedScene" uid="uid://c3fyth1hvgy2m" path="res://Projectiles/default_pellet.tscn" id="1_h12ld"] | ||||
| 
 | ||||
| [sub_resource type="Curve" id="Curve_tdh3d"] | ||||
| _data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0] | ||||
| max_value = 2.0 | ||||
| _data = [Vector2(0, 2), 0.0, 0.0, 0, 0, Vector2(1, 2), 0.0, 0.0, 0, 0] | ||||
| point_count = 2 | ||||
| 
 | ||||
| [resource] | ||||
|  |  | |||
|  | @ -36,7 +36,7 @@ prerequisites = { | |||
| 
 | ||||
| [sub_resource type="CapsuleMesh" id="CapsuleMesh_rwcvu"] | ||||
| radial_segments = 12 | ||||
| rings = 1 | ||||
| rings = 2 | ||||
| 
 | ||||
| [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_scmx3"] | ||||
| albedo_color = Color(0.94902, 0.909804, 0, 1) | ||||
|  | @ -52,6 +52,9 @@ collision_layer = 7 | |||
| actions = [SubResource("Action_gtisq"), SubResource("Action_cwmvs")] | ||||
| goals = [SubResource("Goal_sqtwb")] | ||||
| 
 | ||||
| [node name="CharacterAwareness" type="CharacterAwareness" parent="."] | ||||
| collision_mask = 2 | ||||
| 
 | ||||
| [node name="Health" type="Health" parent="."] | ||||
| max_health = 5 | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,7 +6,6 @@ | |||
| #include "tunnels_game_mode.hpp" | ||||
| #include "utils/game_root.hpp" | ||||
| #include "utils/godot_macros.h" | ||||
| #include <cmath> | ||||
| #include <godot_cpp/classes/navigation_agent3d.hpp> | ||||
| #include <godot_cpp/classes/time.hpp> | ||||
| #include <godot_cpp/variant/callable.hpp> | ||||
|  | @ -16,11 +15,12 @@ namespace godot { | |||
| void CharacterActor::_bind_methods() { | ||||
| #define CLASSNAME CharacterActor | ||||
|     GDPROPERTY_HINTED(rotation_speed_curve, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Curve"); | ||||
|     GDPROPERTY_HINTED(target, Variant::OBJECT, PROPERTY_HINT_NODE_TYPE, "Node"); | ||||
|     GDPROPERTY_HINTED(character_type, Variant::INT, PROPERTY_HINT_ENUM, "Neutral,Player,Enemy"); | ||||
|     GDPROPERTY(acceleration, Variant::FLOAT); | ||||
|     GDPROPERTY(walk_speed, Variant::FLOAT); | ||||
|     GDPROPERTY(sprint_speed, Variant::FLOAT); | ||||
|     GDPROPERTY(rotation_speed, Variant::FLOAT); | ||||
|     GDFUNCTION(get_target); | ||||
|     GDFUNCTION(get_is_near_player); | ||||
| } | ||||
| 
 | ||||
|  | @ -31,8 +31,7 @@ void CharacterActor::_enter_tree() { GDGAMEONLY(); | |||
|     this->health = this->get_node<Health>("Health"); | ||||
|     this->primary_weapon_pool = this->get_node<ProjectilePool>("ProjectilePool"); | ||||
|     this->planner = this->get_node<goap::Planner>("Planner"); | ||||
|     Ref<TunnelsGameMode> game_mode = GameRoot::get_singleton()->get_game_mode(); | ||||
|     game_mode->register_player_character(this); | ||||
|     Ref<TunnelsGameMode>(GameRoot3D::get_singleton()->get_game_mode())->register_player_character(this); | ||||
| } | ||||
| 
 | ||||
| void CharacterActor::_process(double delta_time) { GDGAMEONLY(); | ||||
|  | @ -63,9 +62,13 @@ void CharacterActor::move(Vector3 world_vector) { | |||
| } | ||||
| 
 | ||||
| void CharacterActor::aim(Vector3 at) { | ||||
|     // calculate the forward vector by normalized difference between player character and the target on the XZ plane
 | ||||
|     Vector3 const position{this->weapon_muzzle->get_global_position()}; | ||||
|     Vector3 const forward{Vector3{at.x, 0.f, at.z} - Vector3{position.x, 0.f, position.z}}; | ||||
|     // the forward vector by normalized difference between player character and the target on the XZ plane
 | ||||
|     Vector3 const pos_flat{this->get_global_position().x, 0.f, this->get_global_position().z}; | ||||
|     Vector3 const target_flat{at.x, 0.f, at.z}; | ||||
|     Vector3 const muzzle_flat{this->weapon_muzzle->get_global_position().x, 0.f, this->weapon_muzzle->get_global_position().z}; | ||||
|     if(pos_flat.distance_squared_to(target_flat) < pos_flat.distance_squared_to(muzzle_flat)) | ||||
|         return; | ||||
|     Vector3 const forward{target_flat - muzzle_flat}; | ||||
|     this->aim_direction(forward.normalized()); | ||||
| } | ||||
| 
 | ||||
|  | @ -101,7 +104,6 @@ void CharacterActor::set_firing(bool firing) { | |||
| void CharacterActor::set_manual_mode(bool value) { | ||||
|     this->mode_manual = value; | ||||
|     ProcessMode const mode = value ?  ProcessMode::PROCESS_MODE_DISABLED : ProcessMode::PROCESS_MODE_PAUSABLE; | ||||
|     //this->nav_agent->set_process_mode(mode);
 | ||||
|     this->nav_agent->set_avoidance_priority(value ? 1.f : 0.9f); | ||||
|     this->set_state(goap::State::new_invalid()); | ||||
| } | ||||
|  | @ -141,7 +143,7 @@ Vector3 CharacterActor::get_velocity_target() const { | |||
| } | ||||
| 
 | ||||
| bool CharacterActor::get_is_near_player() const { | ||||
|     return Ref<TunnelsGameMode>(GameRoot::get_singleton()->get_game_mode()) | ||||
|     return Ref<TunnelsGameMode>(GameRoot3D::get_singleton()->get_game_mode()) | ||||
|         ->get_player_instance() | ||||
|         ->get_character() | ||||
|         ->get_global_position().distance_to(this->get_global_position()) < 5.f; | ||||
|  | @ -152,7 +154,8 @@ bool CharacterActor::get_is_near_target() const { | |||
|     Node3D *target_node3d = Object::cast_to<Node3D>(this->target); | ||||
|     return target_marker | ||||
|         ? target_marker->is_point_on(this->get_global_position()) | ||||
|         : (target_node3d && target_node3d->get_global_position().distance_to(this->get_global_position()) < 5.f); | ||||
|         : (target_node3d | ||||
|                 && target_node3d->get_global_position().distance_to(this->get_global_position()) < 5.f); | ||||
| } | ||||
| 
 | ||||
| Vector3 CharacterActor::get_move_target() const { | ||||
|  | @ -195,64 +198,13 @@ void CharacterActor::set_state(goap::State state) { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void CharacterActor::process_behaviour(double delta_time) { | ||||
|     if(this->current_state.is_complete(this) || this->planner->is_action_complete()) | ||||
|         this->set_state(this->planner->get_next_state()); | ||||
|     switch(this->current_state.type) { | ||||
|     default: | ||||
|         break; | ||||
|     case goap::State::STATE_MOVE_TO: | ||||
|         if(this->nav_agent->get_target_position().distance_to(this->get_move_target()) > 2.f) | ||||
|             this->nav_agent->set_target_position(this->get_move_target()); | ||||
|         break; | ||||
|     case goap::State::STATE_ACTIVATE: | ||||
|         break; | ||||
|     case goap::State::STATE_ANIMATE: | ||||
|         break; | ||||
|     } | ||||
| void CharacterActor::set_character_type(int character_type) { | ||||
|     this->character_type = static_cast<CharacterType>(character_type); | ||||
|     UtilityFunctions::print("set_character_type ", character_type); | ||||
| } | ||||
| 
 | ||||
| void CharacterActor::process_navigation(double delta_time) { | ||||
|     float const distance_sqr = this->nav_agent->get_target_position().distance_squared_to(this->get_global_position()); | ||||
|     float const distance_target_sqr = std::pow(this->nav_agent->get_target_desired_distance(), 2.f); | ||||
|     if(!this->nav_agent->is_navigation_finished() && distance_sqr >= distance_target_sqr) { | ||||
|         Vector3 const target_position = this->nav_agent->get_next_path_position(); | ||||
|         Vector3 const direction = (target_position - this->get_global_position()).normalized(); | ||||
|         if(this->nav_agent->get_avoidance_enabled()) | ||||
|             this->nav_agent->set_velocity(direction * CharacterActor::walk_speed); | ||||
|         else | ||||
|             this->move(direction); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void CharacterActor::process_rotation(double delta_time) { | ||||
|     // copy the current transform and basis matrix
 | ||||
|     Transform3D trans{this->get_global_transform()}; | ||||
|     Basis basis = trans.get_basis(); | ||||
|     // construct the current rotation ..
 | ||||
|     Quaternion const current_quaternion = basis.get_rotation_quaternion(); | ||||
|     // .. and the target rotation from their respective bases
 | ||||
|     Quaternion const target_quaternion = this->target_rotation.get_rotation_quaternion(); | ||||
|     // calculate the angle that still needs to be traveled
 | ||||
|     float const angle = current_quaternion.angle_to(target_quaternion); | ||||
|     // calculate the angle amount that can be moved this frame
 | ||||
|     float const angle_step{float(this->rotation_speed_curve->sample(angle) * CharacterActor::rotation_speed * delta_time)}; | ||||
|     // update this object's global transform with the new rotation
 | ||||
|     basis.set_quaternion(angle < angle_step ? target_quaternion // to avoid overshooting, check if the max step is smaller than the angle distance
 | ||||
|                          : current_quaternion.slerp(target_quaternion, angle_step / angle)); // convert the angle step to a lerp t value between current and target rotations
 | ||||
|     trans.set_basis(basis); | ||||
|     this->set_global_transform(trans); | ||||
| } | ||||
| 
 | ||||
| void CharacterActor::try_fire_weapon() { | ||||
|     if(float(Time::get_singleton()->get_ticks_msec()) / 1000.f < this->fire_timer) | ||||
|         return; | ||||
|     if(!this->data->get_weapon()->get_allow_automatic()) | ||||
|         this->set_firing(false); | ||||
|     this->fire_timer = float(Time::get_singleton()->get_ticks_msec()) / 1000.f + this->fire_interval; | ||||
|     Node3D *node = this->primary_weapon_pool->claim_projectile(); | ||||
|     if(node != nullptr) | ||||
|         node->set_global_transform(this->weapon_muzzle->get_global_transform()); | ||||
| int CharacterActor::get_character_type() const { | ||||
|     return static_cast<int>(this->character_type); | ||||
| } | ||||
| 
 | ||||
| void CharacterActor::set_acceleration(float acceleration) { | ||||
|  | @ -286,4 +238,59 @@ void CharacterActor::set_rotation_speed(float rotation_speed) { | |||
| float CharacterActor::get_rotation_speed() const { | ||||
|     return this->rotation_speed; | ||||
| } | ||||
| 
 | ||||
| void CharacterActor::process_behaviour(double delta_time) { | ||||
|     if(this->current_state.is_complete(this) || this->planner->is_action_complete()) | ||||
|         this->set_state(this->planner->get_next_state()); | ||||
|     switch(this->current_state.type) { | ||||
|     default: | ||||
|         break; | ||||
|     case goap::State::STATE_MOVE_TO: | ||||
|         if(this->nav_agent->get_target_position().distance_to(this->get_move_target()) > 2.f) | ||||
|             this->nav_agent->set_target_position(this->get_move_target()); | ||||
|         break; | ||||
|     case goap::State::STATE_ACTIVATE: | ||||
|         break; | ||||
|     case goap::State::STATE_ANIMATE: | ||||
|         break; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void CharacterActor::process_navigation(double delta_time) { | ||||
|     float const sqr_dist = this->nav_agent->get_target_position().distance_squared_to(this->get_global_position()); | ||||
|     float const sqr_dist_target = Math::pow(this->nav_agent->get_target_desired_distance(), 2.0); | ||||
|     if(this->nav_agent->is_navigation_finished() || sqr_dist < sqr_dist_target) | ||||
|         return; | ||||
|     Vector3 const target_pos = this->nav_agent->get_next_path_position(); | ||||
|     Vector3 const direction = (target_pos - this->get_global_position()).normalized(); | ||||
|     if(this->nav_agent->get_avoidance_enabled()) | ||||
|         this->nav_agent->set_velocity(direction * CharacterActor::walk_speed); | ||||
|     else | ||||
|         this->move(direction); | ||||
| } | ||||
| 
 | ||||
| void CharacterActor::process_rotation(double delta_time) { | ||||
|     Basis basis = this->get_global_basis(); | ||||
|     Quaternion const current_quaternion = basis.get_rotation_quaternion(); | ||||
|     Quaternion const target_quaternion = this->target_rotation.get_rotation_quaternion(); | ||||
|     // the angle that still needs to be traveled
 | ||||
|     float const angle = current_quaternion.angle_to(target_quaternion); | ||||
|     // calculate the angle amount that can be moved this frame
 | ||||
|     float const step{float(this->rotation_speed_curve->sample(angle) * CharacterActor::rotation_speed * delta_time)}; | ||||
|     // update this object's global transform with the new rotation
 | ||||
|     basis.set_quaternion(angle < step ? target_quaternion // to avoid overshooting, check if the max step is smaller than the angle distance
 | ||||
|                          : current_quaternion.slerp(target_quaternion, step / angle)); // convert the angle step to a lerp t value between current and target rotations
 | ||||
|     this->set_global_basis(basis); | ||||
| } | ||||
| 
 | ||||
| void CharacterActor::try_fire_weapon() { | ||||
|     if(float(Time::get_singleton()->get_ticks_msec()) / 1000.f < this->fire_timer) | ||||
|         return; | ||||
|     if(!this->data->get_weapon()->get_allow_automatic()) | ||||
|         this->set_firing(false); | ||||
|     this->fire_timer = float(Time::get_singleton()->get_ticks_msec()) / 1000.f + this->fire_interval; | ||||
|     Node3D *node = this->primary_weapon_pool->claim_projectile(); | ||||
|     if(node != nullptr) | ||||
|         node->set_global_transform(this->weapon_muzzle->get_global_transform()); | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| #ifndef PLAYER_CHARACTER_HPP | ||||
| #define PLAYER_CHARACTER_HPP | ||||
| #ifndef CHARACTER_ACTOR_HPP | ||||
| #define CHARACTER_ACTOR_HPP | ||||
| 
 | ||||
| #include "character_data.hpp" | ||||
| #include "health.hpp" | ||||
|  | @ -17,10 +17,10 @@ namespace goap { | |||
|     class Planner; | ||||
| }; | ||||
| 
 | ||||
| enum class Team { | ||||
|     CHARACTER_TEAM_WORLD = 0u, | ||||
|     CHARACTER_TEAM_PLAYER = 1u, | ||||
|     CHARACTER_TEAM_ENEMY = 2u, | ||||
| enum class CharacterType : unsigned { | ||||
|     NEUTRAL, | ||||
|     PLAYER, | ||||
|     PLANT | ||||
| }; | ||||
| 
 | ||||
| class CharacterActor : public CharacterBody3D, | ||||
|  | @ -70,7 +70,9 @@ public: | |||
|     Node *get_target() const; | ||||
|     goap::Planner *get_planner() const; | ||||
|     void set_state(goap::State state); | ||||
| 
 | ||||
|     void set_character_type(int character_type); | ||||
|     int get_character_type() const; | ||||
|     // character settings
 | ||||
|     void set_acceleration(float acceleration); | ||||
|     float get_acceleration() const; | ||||
|     void set_walk_speed(float walk_speed); | ||||
|  | @ -113,6 +115,8 @@ private: | |||
|     Ref<CharacterData> data; | ||||
|     float fire_interval{0.f}; // derived from 1 / the current weapon's rps
 | ||||
| 
 | ||||
|     CharacterType character_type{CharacterType::NEUTRAL}; | ||||
| 
 | ||||
|     float acceleration{20.f}; | ||||
|     float walk_speed{3.f}; | ||||
|     float sprint_speed{5.f}; | ||||
|  | @ -120,4 +124,4 @@ private: | |||
| }; | ||||
| } | ||||
| 
 | ||||
| #endif // !PLAYER_CHARACTER_HPP
 | ||||
| #endif // !CHARACTER_ACTOR_HPP
 | ||||
|  |  | |||
							
								
								
									
										43
									
								
								src/character_awareness.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/character_awareness.cpp
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,43 @@ | |||
| #include "character_awareness.hpp" | ||||
| #include "character_actor.hpp" | ||||
| #include "utils/godot_macros.h" | ||||
| #include <godot_cpp/variant/callable_method_pointer.hpp> | ||||
| 
 | ||||
| namespace godot { | ||||
| void CharacterAwareness::_bind_methods() { | ||||
| #define CLASSNAME CharacterAwareness | ||||
|     GDSIGNAL("detect_actor", PropertyInfo(Variant::OBJECT, "character", PROPERTY_HINT_NODE_TYPE, "CharacterActor")); | ||||
|     GDSIGNAL("lose_actor", PropertyInfo(Variant::OBJECT, "character", PROPERTY_HINT_NODE_TYPE, "CharacterActor")); | ||||
|     GDSIGNAL("detect_node", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_NODE_TYPE, "Node")); | ||||
|     GDSIGNAL("lose_node", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_NODE_TYPE, "Node")); | ||||
| } | ||||
| 
 | ||||
| void CharacterAwareness::_enter_tree() { | ||||
|     this->set_collision_mask(0x2); | ||||
| } | ||||
| 
 | ||||
| void CharacterAwareness::_ready() { | ||||
|     this->connect("body_entered", callable_mp(this, &CharacterAwareness::on_body_entered)); | ||||
|     this->connect("body_exited", callable_mp(this, &CharacterAwareness::on_body_exited)); | ||||
| } | ||||
| 
 | ||||
| void CharacterAwareness::on_body_entered(Node *node) { | ||||
|     this->node_awareness.insert(node); | ||||
|     this->emit_signal("detect_node", node); | ||||
|     CharacterActor *actor = Object::cast_to<CharacterActor>(node); | ||||
|     if(actor) { | ||||
|         this->actor_awareness.insert(actor); | ||||
|         this->emit_signal("detect_actor", actor); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void CharacterAwareness::on_body_exited(Node *node) { | ||||
|     this->node_awareness.erase(node); | ||||
|     this->emit_signal("lose_node", node); | ||||
|     CharacterActor *actor = Object::cast_to<CharacterActor>(node); | ||||
|     if(actor) { | ||||
|         this->actor_awareness.erase(actor); | ||||
|         this->emit_signal("lose_actor", actor); | ||||
|     } | ||||
| } | ||||
| } | ||||
							
								
								
									
										25
									
								
								src/character_awareness.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/character_awareness.hpp
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | |||
| #ifndef CHARACTER_AWARENESS_HPP | ||||
| #define CHARACTER_AWARENESS_HPP | ||||
| 
 | ||||
| #include "character_actor.hpp" | ||||
| #include <godot_cpp/classes/area3d.hpp> | ||||
| #include <godot_cpp/templates/hash_set.hpp> | ||||
| 
 | ||||
| namespace godot { | ||||
| class CharacterAwareness : public Area3D { | ||||
|     GDCLASS(CharacterAwareness, Area3D); | ||||
|     static void _bind_methods(); | ||||
| public: | ||||
|     virtual void _enter_tree() override; | ||||
|     virtual void _ready() override; | ||||
|     void on_body_entered(Node *node); | ||||
|     void on_body_exited(Node *node); | ||||
| private: | ||||
|     // awareness of characters specifically
 | ||||
|     HashSet<CharacterActor*> actor_awareness{}; | ||||
|     // not a character :( cringe
 | ||||
|     HashSet<Node*> node_awareness{}; | ||||
| }; | ||||
| } | ||||
| 
 | ||||
| #endif // !CHARACTER_AWARENESS_HPP
 | ||||
|  | @ -1,79 +0,0 @@ | |||
| #include "enemy.hpp" | ||||
| #include "godot_cpp/classes/time.hpp" | ||||
| #include "health.hpp" | ||||
| #include "character_actor.hpp" | ||||
| #include "utils/godot_macros.h" | ||||
| #include <godot_cpp/classes/navigation_agent3d.hpp> | ||||
| 
 | ||||
| namespace godot { | ||||
| void Enemy::_bind_methods() { | ||||
| #define CLASSNAME Enemy | ||||
|     GDFUNCTION_ARGS(body_entered_vision_area, "body"); | ||||
|     GDFUNCTION_ARGS(on_death, "damage"); | ||||
|     GDFUNCTION_ARGS(on_damage, "damage", "remaining"); | ||||
| } | ||||
| 
 | ||||
| void Enemy::_ready() { GDGAMEONLY(); | ||||
|     this->health = this->get_node<Health>("Health"); | ||||
|     this->nav_agent = this->get_node<NavigationAgent3D>("NavigationAgent3D"); | ||||
|     this->vision_area = this->get_node<Area3D>("VisionArea"); | ||||
|     this->vision_area->connect("body_entered", Callable(this, "body_entered_vision_area")); | ||||
|     this->update_navigation_target(); | ||||
|     this->health->connect("death", Callable(this, "on_death")); | ||||
|     this->health->connect("damage", Callable(this, "on_damage")); | ||||
| } | ||||
| 
 | ||||
| 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) { | ||||
|     CharacterActor *player = Object::cast_to<CharacterActor>(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()); | ||||
| } | ||||
| 
 | ||||
| void Enemy::on_damage(int delta, int health_left) { | ||||
| } | ||||
| 
 | ||||
| void Enemy::on_death(int delta) { | ||||
|     this->queue_free(); | ||||
| } | ||||
| 
 | ||||
| Health *Enemy::get_health() { | ||||
|     return this->health; | ||||
| } | ||||
| 
 | ||||
| Health const *Enemy::get_health() const { | ||||
|     return this->health; | ||||
| } | ||||
| 
 | ||||
| float const Enemy::RENAV_INTERVAL{0.25f}; | ||||
| } | ||||
|  | @ -1,43 +0,0 @@ | |||
| #ifndef ENEMY_HPP | ||||
| #define ENEMY_HPP | ||||
| 
 | ||||
| #include "godot_cpp/classes/area3d.hpp" | ||||
| #include "health.hpp" | ||||
| #include <godot_cpp/classes/character_body3d.hpp> | ||||
| 
 | ||||
| namespace godot { | ||||
| class CharacterActor; | ||||
| 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(); | ||||
| 
 | ||||
|     void on_damage(int delta, int health_left); | ||||
|     void on_death(int damage); | ||||
| 
 | ||||
|     virtual Health *get_health() override; | ||||
|     virtual Health const *get_health() const override; | ||||
| 
 | ||||
| private: | ||||
|     float renav_time{0.f}; | ||||
| 
 | ||||
|     CharacterActor *target_player{nullptr}; | ||||
|     Health *health{nullptr}; | ||||
|     NavigationAgent3D *nav_agent{nullptr}; | ||||
|     Area3D *vision_area{nullptr}; | ||||
| 
 | ||||
|     static float const RENAV_INTERVAL; | ||||
| }; | ||||
| } | ||||
| 
 | ||||
| #endif // !ENEMY_HPP
 | ||||
|  | @ -38,7 +38,7 @@ Vector3 GlobalWorldState::get_player_position() const { | |||
| } | ||||
| 
 | ||||
| CharacterActor *GlobalWorldState::get_player_character() const { | ||||
|     return Ref<TunnelsGameMode>(GameRoot::get_singleton()->get_game_mode())->get_player_instance()->get_character(); | ||||
|     return Ref<TunnelsGameMode>(GameRoot3D::get_singleton()->get_game_mode())->get_player_instance()->get_character(); | ||||
| } | ||||
| 
 | ||||
| Variant GlobalWorldState::get_world_property(StringName prop_key) { | ||||
|  |  | |||
|  | @ -88,22 +88,28 @@ struct PlannerNodeHasher { | |||
|     } | ||||
| }; | ||||
| 
 | ||||
| static _FORCE_INLINE_ bool operator==(PlannerNode const &lhs, PlannerNode const &rhs) { | ||||
| static _FORCE_INLINE_ | ||||
| bool operator==(PlannerNode const &lhs, PlannerNode const &rhs) { | ||||
|     return PlannerNodeHasher::hash(lhs) == PlannerNodeHasher::hash(rhs); | ||||
| } | ||||
| static _FORCE_INLINE_ bool operator!=(PlannerNode const &lhs, PlannerNode const &rhs) { | ||||
| static _FORCE_INLINE_ | ||||
| bool operator!=(PlannerNode const &lhs, PlannerNode const &rhs) { | ||||
|     return !(lhs == rhs); | ||||
| } | ||||
| static _FORCE_INLINE_ bool operator<(PlannerNode const &lhs, PlannerNode const &rhs) { | ||||
| static _FORCE_INLINE_ | ||||
| bool operator<(PlannerNode const &lhs, PlannerNode const &rhs) { | ||||
|     return lhs.open_requirements.size() < rhs.open_requirements.size(); | ||||
| } | ||||
| static _FORCE_INLINE_ bool operator>=(PlannerNode const &lhs, PlannerNode const &rhs) { | ||||
| static _FORCE_INLINE_ | ||||
| bool operator>=(PlannerNode const &lhs, PlannerNode const &rhs) { | ||||
|     return !(lhs < rhs); | ||||
| } | ||||
| static _FORCE_INLINE_ bool operator>(PlannerNode const &lhs, PlannerNode const &rhs) { | ||||
| static _FORCE_INLINE_ | ||||
| bool operator>(PlannerNode const &lhs, PlannerNode const &rhs) { | ||||
|     return lhs.open_requirements.size() > rhs.open_requirements.size(); | ||||
| } | ||||
| static _FORCE_INLINE_ bool operator<=(PlannerNode const &lhs, PlannerNode const &rhs) { | ||||
| static _FORCE_INLINE_ | ||||
| bool operator<=(PlannerNode const &lhs, PlannerNode const &rhs) { | ||||
|     return !(lhs > rhs); | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -1,8 +1,8 @@ | |||
| #include "register_types.h" | ||||
| #include "action.hpp" | ||||
| #include "character_actor.hpp" | ||||
| #include "character_awareness.hpp" | ||||
| #include "character_data.hpp" | ||||
| #include "enemy.hpp" | ||||
| #include "global_world_state.hpp" | ||||
| #include "goal_marker.hpp" | ||||
| #include "health.hpp" | ||||
|  | @ -33,7 +33,6 @@ void initialize_gdextension_types(ModuleInitializationLevel p_level) | |||
|     if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { | ||||
|         return; | ||||
|     } | ||||
|     ClassDB::register_abstract_class<GameRoot>(); | ||||
|     ClassDB::register_class<GameMode>(); | ||||
|     ClassDB::register_class<GameRoot3D>(); | ||||
|     ClassDB::register_class<GameState>(); | ||||
|  | @ -45,7 +44,6 @@ void initialize_gdextension_types(ModuleInitializationLevel p_level) | |||
|     ClassDB::register_class<TunnelsGameState>(); | ||||
|     ClassDB::register_class<TunnelsPlayer>(); | ||||
| 
 | ||||
|     ClassDB::register_class<Enemy>(); | ||||
|     ClassDB::register_class<Health>(); | ||||
|     ClassDB::register_class<CharacterActor>(); | ||||
|     ClassDB::register_class<ProjectilePool>(); | ||||
|  | @ -67,6 +65,7 @@ void initialize_gdextension_types(ModuleInitializationLevel p_level) | |||
|     ClassDB::register_class<goap::Planner>(); | ||||
|     ClassDB::register_class<GoalMarker>(); | ||||
|     ClassDB::register_class<LineGoalMarker>(); | ||||
|     ClassDB::register_class<CharacterAwareness>(); | ||||
| } | ||||
| 
 | ||||
| extern "C" | ||||
|  |  | |||
|  | @ -15,7 +15,7 @@ void TunnelsGameMode::_bind_methods() { | |||
| } | ||||
| 
 | ||||
| void TunnelsGameMode::_begin() { | ||||
|     GameRoot::get_singleton()->connect("player_spawned", Callable(this, "on_player_spawned")); | ||||
|     GameRoot3D::get_singleton()->connect("player_spawned", Callable(this, "on_player_spawned")); | ||||
| } | ||||
| 
 | ||||
| void TunnelsGameMode::on_player_spawned(Node *player) { | ||||
|  |  | |||
|  | @ -38,7 +38,7 @@ void TunnelsPlayer::_ready() { | |||
| } | ||||
| 
 | ||||
| void TunnelsPlayer::_exit_tree() { GDGAMEONLY(); | ||||
|     GameRoot::get_singleton()->remove_player(this->get_player_id()); | ||||
|     GameRoot3D::get_singleton()->remove_player(this->get_player_id()); | ||||
| } | ||||
| 
 | ||||
| void TunnelsPlayer::_process(double delta_time) { GDGAMEONLY(); | ||||
|  | @ -54,18 +54,13 @@ void TunnelsPlayer::_process(double delta_time) { GDGAMEONLY(); | |||
|     case State::ManualControl: | ||||
|         // send the current wasd input to the character
 | ||||
|         this->character->move(this->get_world_move_input().normalized()); | ||||
|         // fall through to shared functionality
 | ||||
|     case State::Tactics: | ||||
|         // send the current world cursor position the character
 | ||||
|         this->character->aim(mouse_world_location); | ||||
|         // move the camera along with the character
 | ||||
|         this->set_global_position(this->character->get_global_position()); | ||||
|         break; | ||||
|     case State::Tactics: | ||||
|         // move camera along with the input
 | ||||
|         this->set_global_position(this->get_global_position() + this->get_world_move_input().normalized() * | ||||
|                                   delta_time * TunnelsPlayer::TACTICS_MOVEMENT_SPEED); | ||||
|         break; | ||||
|     case State::Overview: | ||||
|         break; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -142,8 +137,6 @@ void TunnelsPlayer::fire_pressed(Ref<InputEvent> event, float value) { | |||
|         if(value == 1.f) | ||||
|             this->try_select_marker(); | ||||
|         break; | ||||
|     case State::Overview: | ||||
|         break; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -166,8 +159,9 @@ void TunnelsPlayer::try_select_marker() { | |||
|     if(marker == nullptr) | ||||
|         return; | ||||
|     UtilityFunctions::print("Hit: ", marker->get_path()); | ||||
|     // select a character to send orders to
 | ||||
|     CharacterActor *target_character{nullptr}; | ||||
|     for(CharacterActor *loop_character : Ref<TunnelsGameMode>(GameRoot::get_singleton()->get_game_mode())->get_player_characters()) { | ||||
|     for(CharacterActor *loop_character : Ref<TunnelsGameMode>(GameRoot3D::get_singleton()->get_game_mode())->get_player_characters()) { | ||||
|         if(loop_character != this->character) { | ||||
|             target_character = loop_character; | ||||
|             break; | ||||
|  | @ -206,12 +200,12 @@ void TunnelsPlayer::initialize_character() { | |||
|     this->character = Object::cast_to<CharacterActor>(player_scene->instantiate()); | ||||
|     this->get_parent()->add_child(this->character); | ||||
|     this->character->set_global_transform(this->get_global_transform()); | ||||
|     Ref<TunnelsGameState> game_state = GameRoot::get_singleton()->get_game_state(); | ||||
|     Ref<TunnelsGameState> game_state = GameRoot3D::get_singleton()->get_game_state(); | ||||
|     Ref<CharacterData> character = game_state->get_characters()[0]; | ||||
|     this->character->set_character_data(game_state->get_characters()[0]); | ||||
|     // disable navmesh navigation and start using player input
 | ||||
|     this->character->set_manual_mode(true); | ||||
|     Ref<TunnelsGameMode>(GameRoot::get_singleton()->get_game_mode())->set_manual_character(this->character); | ||||
|     Ref<TunnelsGameMode>(GameRoot3D::get_singleton()->get_game_mode())->set_manual_character(this->character); | ||||
| } | ||||
| 
 | ||||
| Vector3 TunnelsPlayer::get_world_move_input() const { | ||||
|  | @ -256,5 +250,4 @@ float const TunnelsPlayer::ROTATION_SPEED{0.5f}; | |||
| float const TunnelsPlayer::ROTATION_Y_MIN_INFLUENCE{7.f}; | ||||
| float const TunnelsPlayer::ROTATION_MARGIN{0.4f}; | ||||
| float const TunnelsPlayer::ROTATION_MARGIN_TACTICS_MUL{0.6f}; | ||||
| float const TunnelsPlayer::TACTICS_MOVEMENT_SPEED{10.f}; | ||||
| } | ||||
|  |  | |||
|  | @ -19,7 +19,6 @@ class TunnelsPlayer : public Node3D, public IPlayer { | |||
|     enum State { | ||||
|         ManualControl = 0x0, | ||||
|         Tactics = 0x1, | ||||
|         Overview = 0x2, | ||||
|     }; | ||||
| 
 | ||||
| public: | ||||
|  | @ -66,7 +65,6 @@ private: | |||
|     static float const ROTATION_Y_MIN_INFLUENCE; | ||||
|     static float const ROTATION_MARGIN_TACTICS_MUL; | ||||
|     static float const ROTATION_MARGIN; | ||||
|     static float const TACTICS_MOVEMENT_SPEED; | ||||
| }; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1 +1 @@ | |||
| Subproject commit d81ad91a885a74338c02edf1d52a2fa5aa8746b6 | ||||
| Subproject commit 2344f3f2b653dbf6a6c57c2e18c43a1f2c813024 | ||||
		Loading…
	
		Reference in a new issue