Compare commits

...

20 commits

Author SHA1 Message Date
Sara 9172b98b9b chore: added emission to default_pellet 2024-05-26 15:17:34 +02:00
Sara 2e4591c3df feat: cleanup 2024-05-19 15:49:48 +02:00
Sara 3863b622f5 feat: updated utils, all references to GameRoot are now GameRoot3D 2024-05-06 11:03:54 +02:00
Sara 5badb8a68f feat: CharacterActor::target is no longer a property
Instead only a getter is exposed to godotscript
2024-05-02 15:18:25 +02:00
Sara d3e8c75d1a chore: simplified formatting 2024-05-02 15:17:51 +02:00
Sara 31b5312cf5 fix: player no longer rotates endlessly when hovered over 2024-05-02 15:17:10 +02:00
Sara 491e375b6e feat: added character awareness node to player character 2024-05-02 15:16:34 +02:00
Sara 2c3b3f2724 feat: increased pistol projectile speed 2024-05-02 15:15:59 +02:00
Sara a86b912d01 chore: reordered character actor getters/setters 2024-05-02 15:09:47 +02:00
Sara d5a6aec9d1 feat: added character_type to CharacterActor 2024-05-02 15:08:45 +02:00
Sara 8366d5b5df feat: renamed Team -> CharacterType, removed prefixes 2024-05-02 15:07:37 +02:00
Sara 71596133a5 fix: name of character actor include guard 2024-05-02 15:07:07 +02:00
Sara f8ffa67a11 feat: removed references to overview mode and tactics movement 2024-05-02 15:06:36 +02:00
Sara 85b0c66cb3 feat: changed tactics controls 2024-05-02 15:06:20 +02:00
Sara f82365b08b feat: registering of CharacterAwareness 2024-05-02 15:05:45 +02:00
Sara c55d6b67ef chore: updated operator formatting 2024-05-02 15:05:28 +02:00
Sara c689d16dfd feat: created character awareness node 2024-05-02 15:05:08 +02:00
Sara ca53fb4ae1 feat: deleted enemy class 2024-05-02 15:04:48 +02:00
Sara 5967bbb95b chore: cleaned up default pistol pellet projectile 2024-05-02 15:04:23 +02:00
Sara 8fca1d154f feat: created a new enemy entity "flower" 2024-05-02 15:04:08 +02:00
18 changed files with 215 additions and 273 deletions

View file

@ -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
View 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")

View file

@ -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")

View file

@ -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]

View file

@ -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

View file

@ -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());
}
}

View file

@ -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

View 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);
}
}
}

View 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

View file

@ -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};
}

View file

@ -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

View file

@ -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) {

View file

@ -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);
}
}

View file

@ -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"

View file

@ -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) {

View file

@ -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};
}

View file

@ -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