feat: implemented enemy wretched patrol and chase states
This commit is contained in:
		
							parent
							
								
									1b0e4384b9
								
							
						
					
					
						commit
						9517588415
					
				| 
						 | 
				
			
			@ -10,6 +10,8 @@ void EnemyWretched::_bind_methods() {
 | 
			
		|||
void EnemyWretched::ready() {
 | 
			
		||||
	if (StateMachine * fsm{ cast_to<StateMachine>(get_node(NodePath("%StateMachine"))) }) {
 | 
			
		||||
		fsm->add_state(memnew(WretchedPatrolState));
 | 
			
		||||
		fsm->add_state(memnew(WretchedChaseState));
 | 
			
		||||
		fsm->add_state(memnew(WretchedAttackState));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -28,28 +30,69 @@ void EnemyWretched::_notification(int what) {
 | 
			
		|||
 | 
			
		||||
void WretchedState::set_target(Node *node) {
 | 
			
		||||
	this->target = cast_to<EnemyWretched>(node);
 | 
			
		||||
	this->nav = this->target->get_nav();
 | 
			
		||||
	this->unit = this->target->get_unit();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
EnemyWretched *WretchedState::get_target() const {
 | 
			
		||||
	return this->target;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
NpcUnit *WretchedState::get_unit() const {
 | 
			
		||||
	return this->unit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
NavigationAgent3D *WretchedState::get_nav() const {
 | 
			
		||||
	return this->nav;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WretchedPatrolState::enter_state() {
 | 
			
		||||
	this->nav = get_target()->get_nav();
 | 
			
		||||
	this->path = get_target()->get_unit()->get_patrol_path();
 | 
			
		||||
 | 
			
		||||
	float const max_speed{ get_target()->get_unit()->get_patrol_speed() };
 | 
			
		||||
	float const max_speed{ get_unit()->get_patrol_speed() };
 | 
			
		||||
	get_target()->set_movement_speed(max_speed);
 | 
			
		||||
	this->nav->set_max_speed(max_speed);
 | 
			
		||||
	get_nav()->set_max_speed(max_speed);
 | 
			
		||||
	this->path_point = this->path->get_closest_point(get_target()->get_global_position());
 | 
			
		||||
	this->nav->set_target_position(this->path->point_at(this->path_point));
 | 
			
		||||
	get_nav()->set_target_position(this->path->point_at(this->path_point));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WretchedPatrolState::process(double delta) {
 | 
			
		||||
	if (this->nav->is_navigation_finished()) {
 | 
			
		||||
	if (get_nav()->is_navigation_finished()) {
 | 
			
		||||
		this->path_point += 1;
 | 
			
		||||
		this->nav->set_target_position(this->path->point_at(this->path_point));
 | 
			
		||||
		get_nav()->set_target_position(this->path->point_at(this->path_point));
 | 
			
		||||
	}
 | 
			
		||||
	Vector3 const direction{ get_target()->get_global_position().direction_to(this->nav->get_next_path_position()) };
 | 
			
		||||
	Vector3 const direction{ get_target()->get_global_position().direction_to(get_nav()->get_next_path_position()) };
 | 
			
		||||
	get_target()->set_movement_direction(Vector2{ direction.x, direction.z }.normalized());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
String WretchedPatrolState::get_next_state() const {
 | 
			
		||||
	if (get_target()->get_unit()->is_aware_of_player()) {
 | 
			
		||||
		return WretchedChaseState::get_class_static();
 | 
			
		||||
	}
 | 
			
		||||
	return get_class();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WretchedChaseState::enter_state() {
 | 
			
		||||
	// TODO: replace this with a setting somewhere
 | 
			
		||||
	get_target()->set_movement_speed(get_unit()->get_patrol_speed() * 2.f);
 | 
			
		||||
	get_nav()->set_max_speed(get_unit()->get_patrol_speed() * 2.f);
 | 
			
		||||
	get_nav()->set_target_position(PlayerBody::get_singleton()->get_global_position());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WretchedChaseState::process(double delta) {
 | 
			
		||||
	// TODO: optimize this with some checks to avoid re-pathing every frame
 | 
			
		||||
	get_nav()->set_target_position(PlayerBody::get_singleton()->get_global_position());
 | 
			
		||||
	Vector3 const direction{ get_target()->get_global_position().direction_to(get_nav()->get_next_path_position()) };
 | 
			
		||||
	get_target()->set_movement_direction(Vector2{ direction.x, direction.z }.normalized());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
String WretchedChaseState::get_next_state() const {
 | 
			
		||||
	if (get_target()->get_global_position().distance_to(PlayerBody::get_singleton()->get_global_position()) < 2.f) {
 | 
			
		||||
		return WretchedAttackState::get_class_static();
 | 
			
		||||
	}
 | 
			
		||||
	return get_class();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
String WretchedAttackState::get_next_state() const {
 | 
			
		||||
	return WretchedChaseState::get_class_static();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@
 | 
			
		|||
#include "wave_survival/enemy_body.h"
 | 
			
		||||
#include "wave_survival/state.h"
 | 
			
		||||
class PatrolPath;
 | 
			
		||||
class NpcUnit;
 | 
			
		||||
 | 
			
		||||
class EnemyWretched : public EnemyBody {
 | 
			
		||||
	GDCLASS(EnemyWretched, EnemyBody);
 | 
			
		||||
| 
						 | 
				
			
			@ -23,9 +24,13 @@ class WretchedState : public State {
 | 
			
		|||
public:
 | 
			
		||||
	virtual void set_target(Node *node) override;
 | 
			
		||||
	EnemyWretched *get_target() const;
 | 
			
		||||
	NpcUnit *get_unit() const;
 | 
			
		||||
	NavigationAgent3D *get_nav() const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	NpcUnit *unit{ nullptr };
 | 
			
		||||
	EnemyWretched *target{ nullptr };
 | 
			
		||||
	NavigationAgent3D *nav{ nullptr };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class WretchedPatrolState : public WretchedState {
 | 
			
		||||
| 
						 | 
				
			
			@ -35,11 +40,29 @@ class WretchedPatrolState : public WretchedState {
 | 
			
		|||
public:
 | 
			
		||||
	virtual void enter_state() override;
 | 
			
		||||
	virtual void process(double delta) override;
 | 
			
		||||
	virtual String get_next_state() const override;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	NavigationAgent3D *nav{ nullptr };
 | 
			
		||||
	int path_point{ 0 };
 | 
			
		||||
	PatrolPath *path{ nullptr };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class WretchedChaseState : public WretchedState {
 | 
			
		||||
	GDCLASS(WretchedChaseState, WretchedState);
 | 
			
		||||
	static void _bind_methods() {}
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	virtual void enter_state() override;
 | 
			
		||||
	virtual void process(double delta) override;
 | 
			
		||||
	virtual String get_next_state() const override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class WretchedAttackState : public WretchedState {
 | 
			
		||||
	GDCLASS(WretchedAttackState, WretchedState);
 | 
			
		||||
	static void _bind_methods() {}
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	virtual String get_next_state() const override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // !ENEMY_WRETCHED_H
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,15 +2,24 @@
 | 
			
		|||
#include "enemy_body.h"
 | 
			
		||||
#include "macros.h"
 | 
			
		||||
#include "patrol_path.h"
 | 
			
		||||
#include "player_detector.h"
 | 
			
		||||
 | 
			
		||||
void NpcUnit::_bind_methods() {
 | 
			
		||||
	BIND_HPROPERTY(Variant::OBJECT, patrol_path, PROPERTY_HINT_NODE_TYPE, "PatrolPath");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NpcUnit::on_npc_awareness_changed(bool value) {
 | 
			
		||||
	if (value) {
 | 
			
		||||
		this->aware_of_player = true;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NpcUnit::child_added(Node *node) {
 | 
			
		||||
	if (EnemyBody * npc{ cast_to<EnemyBody>(node) }) {
 | 
			
		||||
		this->npcs.push_back(npc);
 | 
			
		||||
		npc->set_unit(this);
 | 
			
		||||
		PlayerDetector *detector{ cast_to<PlayerDetector>(npc->get_node(NodePath("%PlayerDetector"))) };
 | 
			
		||||
		detector->connect(PlayerDetector::sig_awareness_changed, callable_mp(this, &self_type::on_npc_awareness_changed));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -40,7 +49,7 @@ PatrolPath *NpcUnit::get_patrol_path() const {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
bool NpcUnit::is_aware_of_player() const {
 | 
			
		||||
	return false;
 | 
			
		||||
	return this->aware_of_player;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NpcUnit::set_patrol_speed(float speed) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,6 +9,7 @@ class PatrolPath;
 | 
			
		|||
class NpcUnit : public Node {
 | 
			
		||||
	GDCLASS(NpcUnit, Node);
 | 
			
		||||
	static void _bind_methods();
 | 
			
		||||
	void on_npc_awareness_changed(bool seen);
 | 
			
		||||
	void child_added(Node *node);
 | 
			
		||||
	void enter_tree();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -23,6 +24,7 @@ public:
 | 
			
		|||
	float get_patrol_speed() const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	bool aware_of_player{ false };
 | 
			
		||||
	Vector<EnemyBody *> npcs{};
 | 
			
		||||
	PatrolPath *patrol_path{ nullptr };
 | 
			
		||||
	float patrol_speed{ 3.f };
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										74
									
								
								modules/wave_survival/player_detector.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								modules/wave_survival/player_detector.cpp
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,74 @@
 | 
			
		|||
#include "player_detector.h"
 | 
			
		||||
 | 
			
		||||
String PlayerDetector::sig_awareness_changed{ "awareness_changed" };
 | 
			
		||||
 | 
			
		||||
void PlayerDetector::_bind_methods() {
 | 
			
		||||
	ADD_SIGNAL(MethodInfo(sig_awareness_changed, PropertyInfo(Variant::BOOL, "aware")));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Check if the player is in a bounded area in front of the detector and unobscured.
 | 
			
		||||
// As all tests are required to pass, we do them in increasing order of complexity, to minimize unneeded resource use.
 | 
			
		||||
bool PlayerDetector::check() const {
 | 
			
		||||
	Vector3 const forward{ get_global_basis().get_column(2) };
 | 
			
		||||
	Vector3 const position{ get_global_position() };
 | 
			
		||||
	Vector3 const target{ PlayerBody::get_singleton()->get_global_position() + Vector3{ 0.f, 1.5f, 0.f } };
 | 
			
		||||
	// check if the target is in a view cone
 | 
			
		||||
	if (forward.dot(target - position) < this->min_dot) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	// check if the target is in range
 | 
			
		||||
	if (position.distance_squared_to(target) > this->max_distance * this->max_distance) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	// check if the target is obscured
 | 
			
		||||
	PhysicsDirectSpaceState3D::RayParameters params{ this->ray_params };
 | 
			
		||||
	params.from = position;
 | 
			
		||||
	params.to = target;
 | 
			
		||||
	PhysicsDirectSpaceState3D *space{ get_world_3d()->get_direct_space_state() };
 | 
			
		||||
	PhysicsDirectSpaceState3D::RayResult result{};
 | 
			
		||||
	space->intersect_ray(params, result);
 | 
			
		||||
	return result.collider == nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlayerDetector::ready() {
 | 
			
		||||
	this->ray_params.exclude.insert(PlayerBody::get_singleton()->get_rid());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlayerDetector::process(double delta) {
 | 
			
		||||
	if (this->query_timer > 0.0) {
 | 
			
		||||
		this->query_timer -= delta;
 | 
			
		||||
	} else {
 | 
			
		||||
		this->query_timer = this->query_time;
 | 
			
		||||
		bool const new_awareness{ check() };
 | 
			
		||||
		if (new_awareness != this->aware_of_player) {
 | 
			
		||||
			set_aware_of_player(new_awareness);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlayerDetector::set_aware_of_player(bool value) {
 | 
			
		||||
	print_line(vformat("awareness changed to %s", value));
 | 
			
		||||
	emit_signal(sig_awareness_changed, value);
 | 
			
		||||
	this->aware_of_player = value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlayerDetector::_notification(int what) {
 | 
			
		||||
	if (Engine::get_singleton()->is_editor_hint()) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	switch (what) {
 | 
			
		||||
		default:
 | 
			
		||||
			return;
 | 
			
		||||
		case NOTIFICATION_READY:
 | 
			
		||||
			set_process(true);
 | 
			
		||||
			ready();
 | 
			
		||||
			return;
 | 
			
		||||
		case NOTIFICATION_PROCESS:
 | 
			
		||||
			process(get_process_delta_time());
 | 
			
		||||
			return;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool PlayerDetector::is_aware_of_player() const {
 | 
			
		||||
	return this->aware_of_player;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								modules/wave_survival/player_detector.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								modules/wave_survival/player_detector.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
#ifndef PLAYER_DETECTOR_H
 | 
			
		||||
#define PLAYER_DETECTOR_H
 | 
			
		||||
 | 
			
		||||
#include "player_body.h"
 | 
			
		||||
#include "scene/3d/node_3d.h"
 | 
			
		||||
 | 
			
		||||
class PlayerDetector : public Node3D {
 | 
			
		||||
	GDCLASS(PlayerDetector, Node3D);
 | 
			
		||||
	static void _bind_methods();
 | 
			
		||||
	bool check() const;
 | 
			
		||||
	void ready();
 | 
			
		||||
	void process(double delta);
 | 
			
		||||
	void set_aware_of_player(bool value);
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
	void _notification(int what);
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	bool is_aware_of_player() const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	bool aware_of_player{ false };
 | 
			
		||||
	float max_distance{ 100.f };
 | 
			
		||||
	float min_dot{ 0.1f };
 | 
			
		||||
	double query_time{ 0.3 };
 | 
			
		||||
	double query_timer{ 0.0 };
 | 
			
		||||
	PhysicsDirectSpaceState3D::RayParameters ray_params{};
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	static String sig_awareness_changed;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // !PLAYER_DETECTOR_H
 | 
			
		||||
| 
						 | 
				
			
			@ -9,6 +9,7 @@
 | 
			
		|||
#include "wave_survival/patrol_path.h"
 | 
			
		||||
#include "wave_survival/player_body.h"
 | 
			
		||||
#include "wave_survival/player_camera.h"
 | 
			
		||||
#include "wave_survival/player_detector.h"
 | 
			
		||||
#include "wave_survival/player_input.h"
 | 
			
		||||
#include "wave_survival/state.h"
 | 
			
		||||
#include "wave_survival/state_machine.h"
 | 
			
		||||
| 
						 | 
				
			
			@ -34,6 +35,10 @@ void initialize_wave_survival_module(ModuleInitializationLevel p_level) {
 | 
			
		|||
	GDREGISTER_CLASS(PatrolPath);
 | 
			
		||||
	GDREGISTER_CLASS(NpcUnit);
 | 
			
		||||
	GDREGISTER_CLASS(EnemyWretched);
 | 
			
		||||
	GDREGISTER_CLASS(WretchedPatrolState);
 | 
			
		||||
	GDREGISTER_CLASS(WretchedChaseState);
 | 
			
		||||
	GDREGISTER_CLASS(WretchedAttackState);
 | 
			
		||||
	GDREGISTER_CLASS(PlayerDetector);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void uninitialize_wave_survival_module(ModuleInitializationLevel p_level) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,15 +15,12 @@ void StateMachine::switch_to_state(State *state) {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StateMachine::ready() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StateMachine::process(double delta) {
 | 
			
		||||
	if (this->current_state) {
 | 
			
		||||
		this->current_state->process(delta);
 | 
			
		||||
		String new_state{ this->current_state->get_next_state() };
 | 
			
		||||
		if (new_state != this->current_state->get_class()) {
 | 
			
		||||
			this->switch_to_state(this->states[new_state]);
 | 
			
		||||
			this->switch_to_state(this->states.has(new_state) ? this->states[new_state] : nullptr);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -37,7 +34,6 @@ void StateMachine::_notification(int what) {
 | 
			
		|||
			return;
 | 
			
		||||
		case NOTIFICATION_READY:
 | 
			
		||||
			set_process(true);
 | 
			
		||||
			ready();
 | 
			
		||||
			return;
 | 
			
		||||
		case NOTIFICATION_PROCESS:
 | 
			
		||||
			process(get_process_delta_time());
 | 
			
		||||
| 
						 | 
				
			
			@ -47,8 +43,10 @@ void StateMachine::_notification(int what) {
 | 
			
		|||
 | 
			
		||||
StateMachine::~StateMachine() {
 | 
			
		||||
	for (KeyValue<StringName, State *> kvp : this->states) {
 | 
			
		||||
		if (kvp.value != nullptr) {
 | 
			
		||||
			memdelete(kvp.value);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StateMachine::add_state(State *state) {
 | 
			
		||||
| 
						 | 
				
			
			@ -58,4 +56,8 @@ void StateMachine::add_state(State *state) {
 | 
			
		|||
	if (this->current_state == nullptr) {
 | 
			
		||||
		this->switch_to_state(state);
 | 
			
		||||
	}
 | 
			
		||||
	print_line("states:");
 | 
			
		||||
	for (KeyValue<StringName, State *> kv : this->states) {
 | 
			
		||||
		print_line(vformat("\t\t| %s %s", kv.key, kv.value));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,6 @@ class StateMachine : public Node {
 | 
			
		|||
	GDCLASS(StateMachine, Node);
 | 
			
		||||
	static void _bind_methods();
 | 
			
		||||
	void switch_to_state(State *state);
 | 
			
		||||
	void ready();
 | 
			
		||||
	void process(double delta);
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,6 +29,8 @@ navigation_mesh = SubResource("NavigationMesh_7ng1a")
 | 
			
		|||
 | 
			
		||||
[node name="CSGCombiner3D" type="CSGCombiner3D" parent="NavigationRegion3D"]
 | 
			
		||||
use_collision = true
 | 
			
		||||
collision_layer = 3
 | 
			
		||||
collision_mask = 0
 | 
			
		||||
 | 
			
		||||
[node name="CSGBox3D2" type="CSGBox3D" parent="NavigationRegion3D/CSGCombiner3D"]
 | 
			
		||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.424776, 3.1962457, -1.9109192)
 | 
			
		||||
| 
						 | 
				
			
			@ -399,7 +401,16 @@ slide_on_ceiling = false
 | 
			
		|||
patrol_path = NodePath("../PatrolPath")
 | 
			
		||||
 | 
			
		||||
[node name="EnemyWretched" parent="NpcUnit" instance=ExtResource("3_7ng1a")]
 | 
			
		||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.017590523, 0.023196908, 3.073964)
 | 
			
		||||
transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 0.017590523, 0.023196908, 3.073964)
 | 
			
		||||
 | 
			
		||||
[node name="EnemyWretched2" parent="NpcUnit" instance=ExtResource("3_7ng1a")]
 | 
			
		||||
transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, -1.0904664, 0.023196908, 1.6584808)
 | 
			
		||||
 | 
			
		||||
[node name="EnemyWretched3" parent="NpcUnit" instance=ExtResource("3_7ng1a")]
 | 
			
		||||
transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 1.1613213, 0.023196908, 1.513694)
 | 
			
		||||
 | 
			
		||||
[node name="EnemyWretched4" parent="NpcUnit" instance=ExtResource("3_7ng1a")]
 | 
			
		||||
transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, -0.977286, 0.023196908, 4.202239)
 | 
			
		||||
 | 
			
		||||
[node name="PatrolPath" type="PatrolPath" parent="."]
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,3 +19,7 @@ unique_name_in_owner = true
 | 
			
		|||
unique_name_in_owner = true
 | 
			
		||||
path_desired_distance = 0.25
 | 
			
		||||
debug_enabled = true
 | 
			
		||||
 | 
			
		||||
[node name="PlayerDetector" type="PlayerDetector" parent="."]
 | 
			
		||||
unique_name_in_owner = true
 | 
			
		||||
transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 0, 1.4599279, 0)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -72,3 +72,5 @@ switch_weapon={
 | 
			
		|||
 | 
			
		||||
3d_render/layer_1="Default"
 | 
			
		||||
3d_render/layer_2="FirstPerson"
 | 
			
		||||
3d_physics/layer_1="Movement"
 | 
			
		||||
3d_physics/layer_2="Hit"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue