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