Compare commits

...

3 commits

Author SHA1 Message Date
f0aba0caf7 feat: added stairs to dock 2026-02-01 16:27:42 +01:00
3340ee4922 fix: demopack pickup is interactables layer 2026-02-01 16:26:30 +01:00
710e9c7bde feat: simplifying enemy state logic 2026-02-01 16:26:12 +01:00
11 changed files with 152 additions and 155 deletions

View file

@ -53,24 +53,8 @@ AnimationPlayer *EnemyRifleman::get_anim() const {
return this->anim; return this->anim;
} }
void RiflemanState::set_target(Node *node) {
this->target = cast_to<EnemyRifleman>(node);
}
EnemyRifleman *RiflemanState::get_target() const { EnemyRifleman *RiflemanState::get_target() const {
return this->target; return cast_to<EnemyRifleman>(get_body());
}
NpcUnit *RiflemanState::get_unit() const {
return this->target->get_unit();
}
NavigationAgent3D *RiflemanState::get_nav() const {
return this->target->get_nav();
}
AnimationPlayer *RiflemanState::get_anim() const {
return this->target->get_anim();
} }
void RiflemanPatrolState::enter_state() { void RiflemanPatrolState::enter_state() {

View file

@ -29,19 +29,12 @@ private:
/* ============================== STATES ============================== */ /* ============================== STATES ============================== */
class RiflemanState : public State { class RiflemanState : public EnemyState {
GDCLASS(RiflemanState, State); GDCLASS(RiflemanState, State);
static void _bind_methods() {} static void _bind_methods() {}
public: public:
virtual void set_target(Node *target) override;
EnemyRifleman *get_target() const; EnemyRifleman *get_target() const;
NpcUnit *get_unit() const;
NavigationAgent3D *get_nav() const;
AnimationPlayer *get_anim() const;
private:
EnemyRifleman *target{ nullptr };
}; };
class RiflemanPatrolState : public RiflemanState { class RiflemanPatrolState : public RiflemanState {

View file

@ -1,27 +1,15 @@
#include "enemy_wretched.h" #include "enemy_wretched.h"
#include "wave_survival/macros.h" #include "scene/animation/animation_player.h"
#include "wave_survival/npc_unit.h" #include "wave_survival/npc_unit.h"
#include "wave_survival/patrol_path.h"
#include "wave_survival/player_body.h" #include "wave_survival/player_body.h"
#include "wave_survival/state_machine.h" #include "wave_survival/state_machine.h"
void EnemyWretched::_bind_methods() { void EnemyWretched::_bind_methods() {}
BIND_PROPERTY(Variant::FLOAT, chase_speed);
}
void EnemyWretched::on_child_entered(Node *node) {
if (StateMachine * fsm{ cast_to<StateMachine>(node) }) {
this->fsm = fsm;
}
if (node->has_node(NodePath("AnimationPlayer"))) {
this->anim = cast_to<AnimationPlayer>(node->get_node(NodePath("AnimationPlayer")));
}
}
void EnemyWretched::ready() { void EnemyWretched::ready() {
this->fsm->add_state(memnew(WretchedPatrolState)); get_fsm()->add_state(memnew(WretchedPatrolState));
this->fsm->add_state(memnew(WretchedChaseState)); get_fsm()->add_state(memnew(WretchedChaseState));
this->fsm->add_state(memnew(WretchedAttackState)); get_fsm()->add_state(memnew(WretchedAttackState));
} }
void EnemyWretched::_notification(int what) { void EnemyWretched::_notification(int what) {
@ -31,99 +19,26 @@ void EnemyWretched::_notification(int what) {
switch (what) { switch (what) {
default: default:
return; return;
case NOTIFICATION_ENTER_TREE:
if (!is_ready()) {
connect("child_entered_tree", callable_mp(this, &self_type::on_child_entered));
}
return;
case NOTIFICATION_READY: case NOTIFICATION_READY:
ready(); ready();
return; return;
} }
} }
void EnemyWretched::set_chase_speed(float speed) {
this->chase_speed = speed;
}
float EnemyWretched::get_chase_speed() const {
return this->chase_speed;
}
AnimationPlayer *EnemyWretched::get_anim() const {
return this->anim;
}
void WretchedState::set_target(Node *node) {
this->target = cast_to<EnemyWretched>(node);
}
EnemyWretched *WretchedState::get_target() const { EnemyWretched *WretchedState::get_target() const {
return this->target; return cast_to<EnemyWretched>(get_body());
}
NpcUnit *WretchedState::get_unit() const {
return this->target->get_unit();
}
NavigationAgent3D *WretchedState::get_nav() const {
return this->target->get_nav();
}
AnimationPlayer *WretchedState::get_anim() const {
return this->target->get_anim();
}
void WretchedPatrolState::set_patrol_target(Vector3 path_point) {
get_nav()->set_target_position(path_point + get_target()->get_unit_offset_3d());
}
void WretchedPatrolState::on_velocity_calculated(Vector3 direction) {
get_target()->set_movement_direction(Vector2{ direction.x, direction.z } / get_target()->get_movement_speed());
}
void WretchedPatrolState::enter_state() {
this->path = get_target()->get_unit()->get_patrol_path();
float const max_speed{ get_unit()->get_patrol_speed() };
get_target()->set_movement_speed(max_speed);
get_nav()->set_max_speed(max_speed);
if (this->path) {
Vector3 const nav_target{ this->path->get_closest_point(get_target()->get_global_position(), &this->path_point) };
set_patrol_target(nav_target);
}
get_nav()->connect("velocity_computed", this->mp_on_velocity_calculated);
}
void WretchedPatrolState::process(double delta) {
if (this->path) {
if (get_nav()->is_navigation_finished()) {
this->path_point += 1;
set_patrol_target(this->path->point_at(this->path_point));
}
Vector3 const direction{ get_target()->get_global_position().direction_to(get_nav()->get_next_path_position()).normalized() };
if (get_nav()->get_avoidance_enabled()) {
get_nav()->set_velocity(direction);
} else {
on_velocity_calculated(direction);
}
}
}
void WretchedPatrolState::exit_state() {
get_nav()->disconnect("velocity_computed", this->mp_on_velocity_calculated);
} }
String WretchedPatrolState::get_next_state() const { String WretchedPatrolState::get_next_state() const {
if (get_target()->get_unit()->is_aware_of_player()) { if (get_body()->get_unit()->is_aware_of_player()) {
return WretchedChaseState::get_class_static(); return WretchedChaseState::get_class_static();
} }
return get_class(); return get_class();
} }
void WretchedChaseState::enter_state() { void WretchedChaseState::enter_state() {
get_target()->set_movement_speed(get_target()->get_chase_speed()); get_target()->set_movement_speed(get_target()->get_max_speed());
get_nav()->set_max_speed(get_target()->get_chase_speed()); get_nav()->set_max_speed(get_target()->get_max_speed());
get_nav()->set_target_position(PlayerBody::get_singleton()->get_global_position()); get_nav()->set_target_position(PlayerBody::get_singleton()->get_global_position());
get_anim()->play("ready"); // TODO: replace this with "run" get_anim()->play("ready"); // TODO: replace this with "run"
} }

View file

@ -1,7 +1,6 @@
#ifndef ENEMY_WRETCHED_H #ifndef ENEMY_WRETCHED_H
#define ENEMY_WRETCHED_H #define ENEMY_WRETCHED_H
#include "scene/animation/animation_player.h"
#include "wave_survival/enemy_body.h" #include "wave_survival/enemy_body.h"
#include "wave_survival/state.h" #include "wave_survival/state.h"
class PatrolPath; class PatrolPath;
@ -11,56 +10,28 @@ class StateMachine;
class EnemyWretched : public EnemyBody { class EnemyWretched : public EnemyBody {
GDCLASS(EnemyWretched, EnemyBody); GDCLASS(EnemyWretched, EnemyBody);
static void _bind_methods(); static void _bind_methods();
void on_child_entered(Node *node);
void ready(); void ready();
protected: protected:
void _notification(int what); void _notification(int what);
public:
void set_chase_speed(float speed);
float get_chase_speed() const;
AnimationPlayer *get_anim() const;
private:
float chase_speed{ 5.f };
StateMachine *fsm{ nullptr };
AnimationPlayer *anim{ nullptr };
}; };
/* ============================== STATES ============================== */ /* ============================== STATES ============================== */
class WretchedState : public State { class WretchedState : public EnemyState {
GDCLASS(WretchedState, State); GDCLASS(WretchedState, State);
static void _bind_methods() {} static void _bind_methods() {}
public: public:
virtual void set_target(Node *node) override;
EnemyWretched *get_target() const; EnemyWretched *get_target() const;
NpcUnit *get_unit() const;
NavigationAgent3D *get_nav() const;
AnimationPlayer *get_anim() const;
private:
EnemyWretched *target{ nullptr };
}; };
class WretchedPatrolState : public WretchedState { class WretchedPatrolState : public PatrolState {
GDCLASS(WretchedPatrolState, WretchedState); GDCLASS(WretchedPatrolState, PatrolState);
static void _bind_methods() {} static void _bind_methods() {}
void set_patrol_target(Vector3 path_point);
void on_velocity_calculated(Vector3 direction);
public: public:
virtual void enter_state() override; String get_next_state() const override;
virtual void process(double delta) override;
virtual String get_next_state() const override;
virtual void exit_state() override;
private:
int path_point{ 0 };
PatrolPath *path{ nullptr };
Callable mp_on_velocity_calculated{ callable_mp(this, &self_type::on_velocity_calculated) };
}; };
class WretchedChaseState : public WretchedState { class WretchedChaseState : public WretchedState {

View file

@ -1,10 +1,13 @@
#include "enemy_body.h" #include "enemy_body.h"
#include "macros.h" #include "macros.h"
#include "npc_unit.h" #include "scene/animation/animation_player.h"
#include "wave_survival/npc_unit.h"
#include "wave_survival/patrol_path.h"
void EnemyBody::_bind_methods() { void EnemyBody::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_movement_direction", "direction"), &self_type::set_movement_direction); ClassDB::bind_method(D_METHOD("set_movement_direction", "direction"), &self_type::set_movement_direction);
ClassDB::bind_method(D_METHOD("get_unit"), &self_type::get_unit); ClassDB::bind_method(D_METHOD("get_unit"), &self_type::get_unit);
BIND_PROPERTY(Variant::FLOAT, max_speed);
} }
void EnemyBody::on_child_added(Node *node) { void EnemyBody::on_child_added(Node *node) {
@ -14,6 +17,9 @@ void EnemyBody::on_child_added(Node *node) {
if (NavigationAgent3D * nav{ cast_to<NavigationAgent3D>(node) }) { if (NavigationAgent3D * nav{ cast_to<NavigationAgent3D>(node) }) {
this->nav = nav; this->nav = nav;
} }
if (node->has_node(NodePath("AnimationPlayer"))) {
this->anim = cast_to<AnimationPlayer>(node->get_node(NodePath("AnimationPlayer")));
}
} }
void EnemyBody::ready() { void EnemyBody::ready() {
@ -42,6 +48,9 @@ void EnemyBody::_notification(int what) {
switch (what) { switch (what) {
default: default:
return; return;
case NOTIFICATION_ENTER_TREE:
connect("child_entered_tree", callable_mp(this, &self_type::on_child_added));
return;
case NOTIFICATION_READY: case NOTIFICATION_READY:
set_physics_process(true); set_physics_process(true);
ready(); ready();
@ -52,6 +61,14 @@ void EnemyBody::_notification(int what) {
} }
} }
void EnemyBody::set_max_speed(float speed) {
this->max_speed = speed;
}
float EnemyBody::get_max_speed() const {
return this->max_speed;
}
void EnemyBody::set_movement_direction(Vector2 direction) { void EnemyBody::set_movement_direction(Vector2 direction) {
this->movement_direction = direction; this->movement_direction = direction;
} }
@ -72,6 +89,14 @@ NavigationAgent3D *EnemyBody::get_nav() const {
return this->nav; return this->nav;
} }
AnimationPlayer *EnemyBody::get_anim() const {
return this->anim;
}
StateMachine *EnemyBody::get_fsm() const {
return this->fsm;
}
void EnemyBody::set_movement_speed(float value) { void EnemyBody::set_movement_speed(float value) {
this->movement_speed = value; this->movement_speed = value;
} }
@ -91,3 +116,64 @@ Vector2 EnemyBody::get_unit_offset() const {
Vector3 EnemyBody::get_unit_offset_3d() const { Vector3 EnemyBody::get_unit_offset_3d() const {
return { this->unit_offset.x, 0, this->unit_offset.y }; return { this->unit_offset.x, 0, this->unit_offset.y };
} }
void EnemyState::set_target(Node *node) {
this->body = cast_to<EnemyBody>(node);
ERR_FAIL_COND_EDMSG(this->body == nullptr, "EnemyState initialised invalid target");
}
NpcUnit *EnemyState::get_unit() const {
return this->body->get_unit();
}
NavigationAgent3D *EnemyState::get_nav() const {
return this->body->get_nav();
}
AnimationPlayer *EnemyState::get_anim() const {
return this->body->get_anim();
}
EnemyBody *EnemyState::get_body() const {
return this->body;
}
void PatrolState::set_patrol_target(Vector3 path_point) {
get_nav()->set_target_position(path_point + get_body()->get_unit_offset_3d());
}
void PatrolState::on_velocity_calculated(Vector3 direction) {
get_body()->set_movement_direction(Vector2{ direction.x, direction.z } / get_body()->get_movement_speed());
}
void PatrolState::enter_state() {
this->path = get_body()->get_unit()->get_patrol_path();
float const max_speed{ get_unit()->get_patrol_speed() };
get_body()->set_movement_speed(max_speed);
get_nav()->set_max_speed(max_speed);
if (this->path) {
Vector3 const nav_target{ this->path->get_closest_point(get_body()->get_global_position(), &this->path_point) };
set_patrol_target(nav_target);
}
get_nav()->connect("velocity_computed", this->mp_on_velocity_calculated);
}
void PatrolState::process(double delta) {
if (this->path) {
if (get_nav()->is_navigation_finished()) {
this->path_point += 1;
set_patrol_target(this->path->point_at(this->path_point));
}
Vector3 const direction{ get_body()->get_global_position().direction_to(get_nav()->get_next_path_position()).normalized() };
if (get_nav()->get_avoidance_enabled()) {
get_nav()->set_velocity(direction);
} else {
on_velocity_calculated(direction);
}
}
}
void PatrolState::exit_state() {
get_nav()->disconnect("velocity_computed", this->mp_on_velocity_calculated);
}

View file

@ -4,8 +4,11 @@
#include "scene/3d/navigation/navigation_agent_3d.h" #include "scene/3d/navigation/navigation_agent_3d.h"
#include "scene/3d/physics/character_body_3d.h" #include "scene/3d/physics/character_body_3d.h"
#include "wave_survival/health_status.h" #include "wave_survival/health_status.h"
#include "wave_survival/state.h"
#include "wave_survival/state_machine.h" #include "wave_survival/state_machine.h"
class NpcUnit; class NpcUnit;
class AnimationPlayer;
class PatrolPath;
class EnemyBody : public CharacterBody3D { class EnemyBody : public CharacterBody3D {
GDCLASS(EnemyBody, CharacterBody3D); GDCLASS(EnemyBody, CharacterBody3D);
@ -14,15 +17,23 @@ class EnemyBody : public CharacterBody3D {
void ready(); void ready();
void physics_process(double delta); void physics_process(double delta);
public: protected:
void _notification(int what); void _notification(int what);
public:
void set_max_speed(float speed);
float get_max_speed() const;
void set_movement_direction(Vector2 direction); void set_movement_direction(Vector2 direction);
void set_unit(NpcUnit *unit); void set_unit(NpcUnit *unit);
NpcUnit *get_unit() const; NpcUnit *get_unit() const;
HealthStatus *get_health() const; HealthStatus *get_health() const;
NavigationAgent3D *get_nav() const; NavigationAgent3D *get_nav() const;
AnimationPlayer *get_anim() const;
StateMachine *get_fsm() const;
void set_movement_speed(float value); void set_movement_speed(float value);
float get_movement_speed() const; float get_movement_speed() const;
void set_unit_offset(Vector2 offset); void set_unit_offset(Vector2 offset);
@ -30,6 +41,7 @@ public:
Vector3 get_unit_offset_3d() const; Vector3 get_unit_offset_3d() const;
private: private:
float max_speed{ 4.f };
float movement_speed{ 1.f }; float movement_speed{ 1.f };
Vector2 movement_direction{ 0, 0 }; Vector2 movement_direction{ 0, 0 };
@ -37,7 +49,40 @@ private:
NpcUnit *unit{ nullptr }; NpcUnit *unit{ nullptr };
HealthStatus *health{ nullptr }; HealthStatus *health{ nullptr };
NavigationAgent3D *nav{ nullptr }; NavigationAgent3D *nav{ nullptr };
AnimationPlayer *anim{ nullptr };
Vector2 unit_offset{ 0, 0 }; Vector2 unit_offset{ 0, 0 };
}; };
class EnemyState : public State {
GDCLASS(EnemyState, State);
static void _bind_methods() {}
public:
void set_target(Node *node) override;
NpcUnit *get_unit() const;
NavigationAgent3D *get_nav() const;
AnimationPlayer *get_anim() const;
EnemyBody *get_body() const;
private:
EnemyBody *body{ nullptr };
};
class PatrolState : public EnemyState {
GDCLASS(PatrolState, EnemyState);
static void _bind_methods() {}
void set_patrol_target(Vector3 path_point);
void on_velocity_calculated(Vector3 direction);
public:
virtual void enter_state() override;
virtual void process(double delta) override;
virtual void exit_state() override;
private:
int path_point{ 0 };
PatrolPath *path{ nullptr };
Callable mp_on_velocity_calculated{ callable_mp(this, &self_type::on_velocity_calculated) };
};
#endif // !ENEMY_BODY_H #endif // !ENEMY_BODY_H

Binary file not shown.

View file

@ -14,13 +14,16 @@ func _highlight_changed(_interactor: PlayerInteractor, value: bool) -> void:
func _activated(interactor: PlayerInteractor) -> void: func _activated(interactor: PlayerInteractor) -> void:
interactor.pickup_demo_pack() interactor.pickup_demo_pack()
get_owner().queue_free() get_owner().queue_free()
func _process(delta : float):
$\"../MeshInstance3D\".global_rotate(Vector3(0, 1, 0), delta)
" "
[sub_resource type="BoxMesh" id="BoxMesh_kl827"] [sub_resource type="BoxMesh" id="BoxMesh_kl827"]
size = Vector3(0.19555664, 0.13061523, 0.37719727) size = Vector3(0.19555664, 0.13061523, 0.37719727)
[node name="DemoPackPickup" type="StaticBody3D" unique_id=1833876644] [node name="DemoPackPickup" type="StaticBody3D" unique_id=1833876644]
collision_layer = 18 collision_layer = 16
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1902989490] [node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1902989490]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.248144, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.248144, 0)