feat: fully implemented click-to-move
This commit is contained in:
parent
1ddfa17659
commit
8cfbbcce27
6
godot/AI/new_goal.tres
Normal file
6
godot/AI/new_goal.tres
Normal file
|
@ -0,0 +1,6 @@
|
|||
[gd_resource type="Goal" format=3 uid="uid://b4i4e34046n44"]
|
||||
|
||||
[resource]
|
||||
desired_state_dict = {
|
||||
"goal1": true
|
||||
}
|
24
godot/GameObjects/ground_goal_marker.tscn
Normal file
24
godot/GameObjects/ground_goal_marker.tscn
Normal file
|
@ -0,0 +1,24 @@
|
|||
[gd_scene load_steps=4 format=3 uid="uid://btnlq0xajbfit"]
|
||||
|
||||
[sub_resource type="Goal" id="Goal_nmxy4"]
|
||||
desired_state_dict = {
|
||||
"is_at_target": true
|
||||
}
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_t1mgt"]
|
||||
albedo_color = Color(0.596078, 0.745098, 0.635294, 1)
|
||||
|
||||
[sub_resource type="CylinderMesh" id="CylinderMesh_4i5c2"]
|
||||
material = SubResource("StandardMaterial3D_t1mgt")
|
||||
height = 0.1
|
||||
|
||||
[node name="GroundGoalMarker" type="GoalMarker"]
|
||||
goal = SubResource("Goal_nmxy4")
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -2.38494, 0, -2.58246)
|
||||
disable_mode = 1
|
||||
collision_layer = 0
|
||||
collision_mask = 0
|
||||
input_ray_pickable = false
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
|
||||
mesh = SubResource("CylinderMesh_4i5c2")
|
|
@ -1,9 +1,12 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://bmm2quy62exfr"]
|
||||
[gd_scene load_steps=3 format=3 uid="uid://bmm2quy62exfr"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://btnlq0xajbfit" path="res://GameObjects/ground_goal_marker.tscn" id="1_t46fa"]
|
||||
|
||||
[sub_resource type="CylinderMesh" id="CylinderMesh_hd6t5"]
|
||||
height = 0.1
|
||||
|
||||
[node name="PlayerCamera" type="RTSPlayer"]
|
||||
ground_marker_scene = ExtResource("1_t46fa")
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="."]
|
||||
transform = Transform3D(-1, 7.57103e-08, -4.37114e-08, 0, 0.5, 0.866025, 8.74228e-08, 0.866025, -0.5, 0, 5.86004, -2.59394)
|
||||
|
|
32
godot/GameObjects/unit.tscn
Normal file
32
godot/GameObjects/unit.tscn
Normal file
|
@ -0,0 +1,32 @@
|
|||
[gd_scene load_steps=4 format=3 uid="uid://pme230qx1377"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_n4q15"]
|
||||
albedo_color = Color(0.407843, 1, 0.447059, 1)
|
||||
|
||||
[sub_resource type="CapsuleMesh" id="CapsuleMesh_5r0b3"]
|
||||
material = SubResource("StandardMaterial3D_n4q15")
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_drlm2"]
|
||||
radius = 1.0
|
||||
|
||||
[node name="Unit" type="Unit"]
|
||||
collision_layer = 2
|
||||
collision_mask = 0
|
||||
|
||||
[node name="ActorWorldState" type="UnitWorldState" parent="."]
|
||||
|
||||
[node name="Planner" type="Planner" parent="."]
|
||||
actions = [0, 1, 2]
|
||||
|
||||
[node name="NavigationAgent3D" type="NavigationAgent3D" parent="."]
|
||||
target_desired_distance = 0.25
|
||||
|
||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
||||
mesh = SubResource("CapsuleMesh_5r0b3")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
||||
shape = SubResource("SphereShape3D_drlm2")
|
|
@ -1,7 +1,17 @@
|
|||
[gd_scene load_steps=4 format=3 uid="uid://c62s1jmtgajjk"]
|
||||
[gd_scene load_steps=7 format=3 uid="uid://c62s1jmtgajjk"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://dsalxxq3xs842" path="res://rts_game_mode.tscn" id="1_4nchg"]
|
||||
[ext_resource type="Environment" uid="uid://cnfk8yrvklysq" path="res://Environments/default_environment.tres" id="2_jq6bw"]
|
||||
[ext_resource type="PackedScene" uid="uid://pme230qx1377" path="res://GameObjects/unit.tscn" id="3_wl7wm"]
|
||||
|
||||
[sub_resource type="NavigationMesh" id="NavigationMesh_8a2j6"]
|
||||
vertices = PackedVector3Array(-4.5, 0.4, -4.5, -4.5, 0.4, 4.5, 4.5, 0.4, 4.5, 4.5, 0.4, -4.5)
|
||||
polygons = [PackedInt32Array(3, 2, 0), PackedInt32Array(0, 2, 1)]
|
||||
geometry_parsed_geometry_type = 1
|
||||
geometry_collision_mask = 4294967289
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_ubt1u"]
|
||||
size = Vector3(10, 0.1, 10)
|
||||
|
||||
[sub_resource type="PlaneMesh" id="PlaneMesh_hohcb"]
|
||||
size = Vector2(10, 10)
|
||||
|
@ -12,10 +22,23 @@ game_mode_prototype = ExtResource("1_4nchg")
|
|||
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
|
||||
environment = ExtResource("2_jq6bw")
|
||||
|
||||
[node name="NavigationRegion3D" type="NavigationRegion3D" parent="WorldEnvironment"]
|
||||
navigation_mesh = SubResource("NavigationMesh_8a2j6")
|
||||
|
||||
[node name="StaticBody3D" type="StaticBody3D" parent="WorldEnvironment/NavigationRegion3D"]
|
||||
collision_layer = 5
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="WorldEnvironment/NavigationRegion3D/StaticBody3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.05, 0)
|
||||
shape = SubResource("BoxShape3D_ubt1u")
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="WorldEnvironment/NavigationRegion3D/StaticBody3D"]
|
||||
mesh = SubResource("PlaneMesh_hohcb")
|
||||
skeleton = NodePath("../../../..")
|
||||
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="WorldEnvironment"]
|
||||
transform = Transform3D(-0.030678, -0.932282, 0.36043, 0, 0.360599, 0.932721, -0.999529, 0.028614, -0.0110625, 0, 1.28927, 0)
|
||||
light_color = Color(1, 0.926667, 0.78, 1)
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="WorldEnvironment"]
|
||||
mesh = SubResource("PlaneMesh_hohcb")
|
||||
skeleton = NodePath("../..")
|
||||
[node name="Unit" parent="." instance=ExtResource("3_wl7wm")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.7165, 1.8999e-07, 3.29106)
|
||||
|
|
|
@ -62,3 +62,9 @@ rotate_right={
|
|||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
[layer_names]
|
||||
|
||||
3d_physics/layer_1="Default"
|
||||
3d_physics/layer_2="Units"
|
||||
3d_physics/layer_3="Objects"
|
||||
|
|
21
src/goal_marker.cpp
Normal file
21
src/goal_marker.cpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
#include "goal_marker.hpp"
|
||||
#include "utils/godot_macros.hpp"
|
||||
#include <godot_cpp/classes/global_constants.hpp>
|
||||
#include <godot_cpp/classes/node.hpp>
|
||||
|
||||
void GoalMarker::_bind_methods() {
|
||||
#define CLASSNAME GoalMarker
|
||||
GDPROPERTY_HINTED(goal, gd::Variant::OBJECT, gd::PROPERTY_HINT_RESOURCE_TYPE, "Goal");
|
||||
}
|
||||
|
||||
void GoalMarker::destroy_on_forgotten() {
|
||||
callable_mp(gd::Object::cast_to<gd::Node>(this), &gd::Node::queue_free).call_deferred();
|
||||
}
|
||||
|
||||
void GoalMarker::set_goal(gd::Ref<goap::Goal> goal) {
|
||||
this->goal = goal;
|
||||
}
|
||||
|
||||
gd::Ref<goap::Goal> GoalMarker::get_goal() const {
|
||||
return this->goal;
|
||||
}
|
21
src/goal_marker.hpp
Normal file
21
src/goal_marker.hpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
#ifndef GOAL_MARKER_HPP
|
||||
#define GOAL_MARKER_HPP
|
||||
|
||||
#include "goap/goal.hpp"
|
||||
#include "godot_cpp/classes/static_body3d.hpp"
|
||||
#include <godot_cpp/classes/node3d.hpp>
|
||||
|
||||
namespace gd = godot;
|
||||
|
||||
class GoalMarker : public gd::StaticBody3D {
|
||||
GDCLASS(GoalMarker, gd::StaticBody3D);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
void destroy_on_forgotten();
|
||||
void set_goal(gd::Ref<goap::Goal> goal);
|
||||
gd::Ref<goap::Goal> get_goal() const;
|
||||
private:
|
||||
gd::Ref<goap::Goal> goal{};
|
||||
};
|
||||
|
||||
#endif // !GOAL_MARKER_HPP
|
|
@ -9,8 +9,8 @@
|
|||
|
||||
#define GOAP_ACTION(Name_) \
|
||||
public: \
|
||||
_FORCE_INLINE_ static gd::String get_static_class() { return #Name_; }\
|
||||
_FORCE_INLINE_ virtual gd::String get_class() const override { return #Name_; }\
|
||||
_FORCE_INLINE_ static gd::StringName get_static_class() { return #Name_; }\
|
||||
_FORCE_INLINE_ virtual gd::StringName get_class() const override { return #Name_; }\
|
||||
private:
|
||||
|
||||
namespace goap {
|
||||
|
@ -23,11 +23,11 @@ typedef int ActionID;
|
|||
class Action {
|
||||
friend class ActionDB;
|
||||
public:
|
||||
static gd::String get_static_class() { return "Action"; }
|
||||
virtual gd::String get_class() const { return "Action"; }
|
||||
static gd::StringName get_static_class() { return "Action"; }
|
||||
virtual gd::StringName get_class() const { return "Action"; }
|
||||
|
||||
virtual ~Action();
|
||||
virtual State *get_apply_state() const = 0;
|
||||
virtual State *get_apply_state(ActorWorldState *context) const = 0;
|
||||
|
||||
bool is_completed(ActorWorldState *context) const;
|
||||
bool is_possible(ActorWorldState *context) const;
|
||||
|
@ -38,7 +38,8 @@ public:
|
|||
ActionID get_id() const;
|
||||
protected:
|
||||
Action() = default;
|
||||
|
||||
template<class TState>
|
||||
TState *create_state() const;
|
||||
virtual bool procedural_is_possible(ActorWorldState *context) const;
|
||||
virtual bool procedural_is_completed(ActorWorldState *context) const;
|
||||
protected:
|
||||
|
@ -49,6 +50,13 @@ protected:
|
|||
private:
|
||||
ActionID id{-1};
|
||||
};
|
||||
|
||||
template <class TState>
|
||||
TState *Action::create_state() const {
|
||||
TState *state = memnew(TState);
|
||||
state->action = this;
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // !GOAP_ACTION_HPP
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "planner.hpp"
|
||||
#include "action_db.hpp"
|
||||
#include "godot_cpp/classes/engine.hpp"
|
||||
#include "godot_cpp/classes/global_constants.hpp"
|
||||
#include "godot_cpp/templates/hashfuncs.hpp"
|
||||
#include "utils/godot_macros.hpp"
|
||||
|
@ -106,49 +107,58 @@ Plan Planner::plan_for_goal(gd::Ref<Goal> goal) {
|
|||
return {};
|
||||
}
|
||||
gd::Vector<WorldStateNode> open{goal_node};
|
||||
NodeMap from{};
|
||||
NodeScoreMap best_path_cost{};
|
||||
NodeScoreMap heuristic_cost{};
|
||||
NodeMap from{}; // mapping nodes to the next node in the plan
|
||||
NodeScoreMap best_path_cost{}; // map nodes to the cost of the best path found so far
|
||||
NodeScoreMap heuristic_cost{}; // map nodes to their approximate cost to get to their goal
|
||||
|
||||
// insert with the goal node into the cost maps,
|
||||
// path scored 0 as it's the goal
|
||||
best_path_cost.insert(goal_node, 0.f);
|
||||
heuristic_cost.insert(goal_node, goal_node.requirements_unmet());
|
||||
|
||||
WorldStateNode current{goal_node};
|
||||
// repeat until current has no traversible edges
|
||||
while(!open.is_empty()) {
|
||||
current = open.get(0);
|
||||
// return found path when all requirements are met
|
||||
if(current.requirements_unmet() == 0)
|
||||
return this->unroll_plan(current, from);
|
||||
open.remove_at(0);
|
||||
open.remove_at(0); // pop current off the top of the open set
|
||||
// get all actions that contribute to the open requirements of `current`
|
||||
gd::Vector<Action const *> edges{this->get_neighbours(current)};
|
||||
gd::UtilityFunctions::print("found ", edges.size(), " possible actions");
|
||||
for(Action const * action : edges) {
|
||||
// construct the node produced by traversing edge
|
||||
WorldStateNode node_through{current, action};
|
||||
// calculate the cost of getting here from current through action.
|
||||
float const path_cost{best_path_cost.get(current) + 1.f};
|
||||
gd::UtilityFunctions::print("hashes ", WorldStateNodeHasher::hash(node_through), " ; ", WorldStateNodeHasher::hash(current));
|
||||
gd::UtilityFunctions::print("equiv: ", current == node_through);
|
||||
// store only if path through `current` represents a new fastest way to get to the new node
|
||||
if(!best_path_cost.has(node_through) || best_path_cost.get(node_through) >= path_cost) {
|
||||
gd::UtilityFunctions::print("node through action ", action->get_class(), " added to open set");
|
||||
// update cheapest route found
|
||||
best_path_cost[node_through] = path_cost;
|
||||
// approximate cost to goal
|
||||
heuristic_cost[node_through] = path_cost + node_through.requirements_unmet();
|
||||
// store path to get to this point
|
||||
from[node_through] = current;
|
||||
// (re-) insert node according to priority based on unmet requirements
|
||||
if(open.has(node_through))
|
||||
open.erase(node_through);
|
||||
open.ordered_insert(node_through);
|
||||
} else {
|
||||
gd::UtilityFunctions::print("node through action ", action->get_class(), " scores worse than previous at same state. ", path_cost, " vs ", best_path_cost[node_through]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gd::UtilityFunctions::print("failed to find plan for goal ", goal->get_path());
|
||||
gd::UtilityFunctions::push_warning("failed to find plan for goal ", goal->get_path());
|
||||
return {};
|
||||
}
|
||||
|
||||
void Planner::set_actions(gd::Array array) {
|
||||
this->actions.clear();
|
||||
for(size_t i = 0; i < array.size(); ++i) {
|
||||
Action const *action = ActionDB::get_action(array[i]);
|
||||
Action const *action = int(array[i]) < 0 ? nullptr : ActionDB::get_action(array[i]);
|
||||
if(action != nullptr && !this->actions.has(action)) {
|
||||
this->actions.push_back(action);
|
||||
} else if(gd::Engine::get_singleton()->is_editor_hint() && !this->actions.has(nullptr)) {
|
||||
this->actions.push_back(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -156,7 +166,7 @@ void Planner::set_actions(gd::Array array) {
|
|||
gd::Array Planner::get_actions() const {
|
||||
gd::Array array{};
|
||||
for(Action const *action : this->actions)
|
||||
array.push_back(action->get_id());
|
||||
array.push_back(action == nullptr ? -1 : action->get_id());
|
||||
return array;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
#include <cstdint>
|
||||
#include <godot_cpp/classes/node.hpp>
|
||||
#include <godot_cpp/templates/vector.hpp>
|
||||
#include <godot_cpp/variant/packed_int32_array.hpp>
|
||||
|
||||
namespace gd = godot;
|
||||
|
||||
|
@ -25,7 +24,7 @@ struct WorldStateNode {
|
|||
WorldStateNode(WorldStateNode const &last_state, Action const *last_action);
|
||||
~WorldStateNode() = default;
|
||||
int requirements_unmet() const;
|
||||
WorldState state{};
|
||||
WorldState state{}; // the only part considered in hashing
|
||||
WorldState open_requirements{};
|
||||
Action const *last_action{nullptr};
|
||||
ActorWorldState *context;
|
||||
|
|
|
@ -1,32 +1,33 @@
|
|||
#include "state.hpp"
|
||||
#include "goap/action.hpp"
|
||||
#include "goap/actor_world_state.hpp"
|
||||
#include "utils/godot_macros.hpp"
|
||||
|
||||
namespace goap {
|
||||
void State::_bind_methods() {}
|
||||
|
||||
void MoveTo::_bind_methods() {}
|
||||
|
||||
void MoveTo::_enter_tree() {
|
||||
this->agent = this->get_node<gd::NavigationAgent3D>("../NavigationAgent3D");
|
||||
this->agent->set_target_position(this->target_node->get_global_position());
|
||||
this->parent_node3d = Object::cast_to<gd::Node3D>(this->get_parent());
|
||||
void State::_bind_methods() {
|
||||
#define CLASSNAME State
|
||||
GDSIGNAL("end_state");
|
||||
}
|
||||
|
||||
void MoveTo::_exit_tree() {
|
||||
this->agent->set_target_position(this->parent_node3d->get_global_position());
|
||||
void State::_enter_tree() {
|
||||
this->world_state = this->get_node<ActorWorldState>("../ActorWorldState");
|
||||
}
|
||||
|
||||
void MoveTo::_process(double delta_time) {
|
||||
gd::Vector3 const pos = this->parent_node3d->get_global_position();
|
||||
gd::Vector3 const target = this->agent->get_next_path_position();
|
||||
gd::Vector3 const direction = (target - pos).normalized();
|
||||
this->parent_node3d->set_global_position(direction * delta_time);
|
||||
this->parent_node3d->look_at(pos - gd::Vector3{direction.x, 0.f, direction.z});
|
||||
void State::_process(double delta_time) {
|
||||
if(this->action->is_completed(this->world_state)) {
|
||||
this->end_state();
|
||||
}
|
||||
}
|
||||
|
||||
if(this->agent->is_navigation_finished())
|
||||
Action const *State::get_action() const {
|
||||
return this->action;
|
||||
}
|
||||
|
||||
void State::_end_state() {}
|
||||
|
||||
void State::end_state() {
|
||||
this->_end_state();
|
||||
this->queue_free();
|
||||
this->emit_signal("end_state");
|
||||
}
|
||||
|
||||
void Activate::_bind_methods() {}
|
||||
|
||||
void Animate::_bind_methods() {}
|
||||
}
|
||||
|
|
|
@ -1,47 +1,29 @@
|
|||
#ifndef GOAP_STATE_HPP
|
||||
#define GOAP_STATE_HPP
|
||||
|
||||
#include "godot_cpp/classes/navigation_agent3d.hpp"
|
||||
#include "goap/actor_world_state.hpp"
|
||||
#include <godot_cpp/classes/node.hpp>
|
||||
#include <godot_cpp/classes/node3d.hpp>
|
||||
|
||||
namespace gd = godot;
|
||||
|
||||
namespace goap {
|
||||
class State : public gd::Node {
|
||||
GDCLASS(State, gd::Node);
|
||||
static void _bind_methods();
|
||||
};
|
||||
class Action;
|
||||
|
||||
/*! Uses navigation to chase the desired target node.
|
||||
*/
|
||||
class MoveTo : public State {
|
||||
GDCLASS(MoveTo, State);
|
||||
class State : public gd::Node {
|
||||
friend class Action;
|
||||
GDCLASS(State, gd::Node);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
virtual void _enter_tree() override;
|
||||
virtual void _exit_tree() override;
|
||||
virtual void _process(double delta_time) override;
|
||||
public:
|
||||
gd::Node3D *target_node{nullptr};
|
||||
protected:
|
||||
Action const *get_action() const;
|
||||
virtual void _end_state();
|
||||
void end_state();
|
||||
private:
|
||||
gd::Node3D *parent_node3d{nullptr};
|
||||
gd::NavigationAgent3D *agent{nullptr};
|
||||
};
|
||||
|
||||
class Activate : public State {
|
||||
GDCLASS(Activate, State);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
gd::Node3D *target_node{nullptr};
|
||||
gd::String animation{};
|
||||
};
|
||||
|
||||
class Animate : public State {
|
||||
GDCLASS(Animate, State);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
gd::String animation{};
|
||||
ActorWorldState *world_state{nullptr};
|
||||
Action const *action{nullptr};
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
#include "register_types.h"
|
||||
#include "goap/state.hpp"
|
||||
#include "rts_actions.hpp"
|
||||
#include "rts_game_mode.hpp"
|
||||
#include "rts_player.hpp"
|
||||
#include "rts_states.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "unit_world_state.hpp"
|
||||
#include "goap/action.hpp"
|
||||
#include "goap/action_db.hpp"
|
||||
#include "goap/actor_world_state.hpp"
|
||||
#include "goap/goal.hpp"
|
||||
#include "goap/planner.hpp"
|
||||
#include "rts_game_mode.hpp"
|
||||
#include "rts_player.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "utils/register_types.hpp"
|
||||
#include <gdextension_interface.h>
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
|
@ -24,14 +28,24 @@ void initialize_gdextension_types(ModuleInitializationLevel p_level)
|
|||
|
||||
// always register actions before classes,
|
||||
// so that ActionDB::get_enum_hint is complete before _bind_methods
|
||||
|
||||
ClassDB::register_class<RTSPlayer>();
|
||||
ClassDB::register_class<Unit>();
|
||||
ClassDB::register_class<RTSGameMode>();
|
||||
goap::ActionDB::register_action<MoveToTarget>();
|
||||
goap::ActionDB::register_action<FireAtTarget>();
|
||||
goap::ActionDB::register_action<FindTarget>();
|
||||
|
||||
ClassDB::register_class<goap::ActorWorldState>();
|
||||
ClassDB::register_class<goap::Goal>();
|
||||
ClassDB::register_class<goap::Planner>();
|
||||
ClassDB::register_class<goap::State>();
|
||||
|
||||
ClassDB::register_class<MoveTo>();
|
||||
ClassDB::register_class<Animate>();
|
||||
ClassDB::register_class<Activate>();
|
||||
|
||||
ClassDB::register_class<UnitWorldState>();
|
||||
ClassDB::register_class<GoalMarker>();
|
||||
ClassDB::register_class<Unit>();
|
||||
ClassDB::register_class<RTSGameMode>();
|
||||
ClassDB::register_class<RTSPlayer>();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
|
|
47
src/rts_actions.cpp
Normal file
47
src/rts_actions.cpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
#include "rts_actions.hpp"
|
||||
#include "rts_states.hpp"
|
||||
#include "goap/actor_world_state.hpp"
|
||||
#include "goap/state.hpp"
|
||||
#include <godot_cpp/core/memory.hpp>
|
||||
#include <godot_cpp/variant/utility_functions.hpp>
|
||||
|
||||
MoveToTarget::MoveToTarget()
|
||||
: Action() {
|
||||
effects.insert("is_at_target", true);
|
||||
}
|
||||
|
||||
goap::State *MoveToTarget::get_apply_state(goap::ActorWorldState *context) const {
|
||||
gd::Node3D *target = gd::Object::cast_to<gd::Node3D>(context->get_world_property("target_node"));
|
||||
if(target == nullptr) {
|
||||
gd::UtilityFunctions::push_warning("Failed to get target node of action ", get_static_class());
|
||||
return nullptr;
|
||||
} else {
|
||||
MoveTo *state = this->create_state<MoveTo>();
|
||||
state->target_node = target;
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
FireAtTarget::FireAtTarget()
|
||||
: Action() {
|
||||
effects.insert("target_dead", true);
|
||||
required.insert("can_see_target", true);
|
||||
}
|
||||
|
||||
goap::State *FireAtTarget::get_apply_state(goap::ActorWorldState *context) const {
|
||||
Animate *state = memnew(Animate);
|
||||
state->animation = "fire_weapon";
|
||||
return state;
|
||||
}
|
||||
|
||||
FindTarget::FindTarget()
|
||||
: Action() {
|
||||
effects.insert("can_see_target", true);
|
||||
}
|
||||
|
||||
goap::State *FindTarget::get_apply_state(goap::ActorWorldState *context) const {
|
||||
gd::Node3D *target = gd::Object::cast_to<gd::Node3D>(context->get_world_property("target_node"));
|
||||
MoveTo *state = memnew(MoveTo);
|
||||
state->target_node = target;
|
||||
return state;
|
||||
}
|
28
src/rts_actions.hpp
Normal file
28
src/rts_actions.hpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
#ifndef RTS_ACTIONS_HPP
|
||||
#define RTS_ACTIONS_HPP
|
||||
|
||||
#include "goap/action.hpp"
|
||||
#include "goap/actor_world_state.hpp"
|
||||
|
||||
class MoveToTarget : public goap::Action {
|
||||
GOAP_ACTION(MoveToTarget);
|
||||
public:
|
||||
MoveToTarget();
|
||||
virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override;
|
||||
};
|
||||
|
||||
class FireAtTarget : public goap::Action {
|
||||
GOAP_ACTION(FireAtTarget);
|
||||
public:
|
||||
FireAtTarget();
|
||||
virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override;
|
||||
};
|
||||
|
||||
class FindTarget : public goap::Action {
|
||||
GOAP_ACTION(FindTarget);
|
||||
public:
|
||||
FindTarget();
|
||||
virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override;
|
||||
};
|
||||
|
||||
#endif // !RTS_ACTIONS_HPP
|
|
@ -1,8 +1,25 @@
|
|||
#include "rts_player.hpp"
|
||||
#include "goal_marker.hpp"
|
||||
#include "godot_cpp/variant/utility_functions.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "utils/godot_macros.hpp"
|
||||
#include "utils/level.hpp"
|
||||
#include "utils/util_functions.hpp"
|
||||
#include <godot_cpp/classes/packed_scene.hpp>
|
||||
#include <godot_cpp/classes/physics_direct_space_state3d.hpp>
|
||||
#include <godot_cpp/classes/physics_direct_space_state3d.hpp>
|
||||
#include <godot_cpp/classes/physics_ray_query_parameters3d.hpp>
|
||||
#include <godot_cpp/classes/viewport.hpp>
|
||||
#include <godot_cpp/classes/world3d.hpp>
|
||||
#include <godot_cpp/variant/callable_method_pointer.hpp>
|
||||
|
||||
void RTSPlayer::_bind_methods() {
|
||||
#define CLASSNAME RTSPlayer
|
||||
GDPROPERTY_HINTED(ground_marker_scene, gd::Variant::OBJECT, gd::PROPERTY_HINT_RESOURCE_TYPE, "PackedScene");
|
||||
}
|
||||
|
||||
void RTSPlayer::_ready() {
|
||||
this->camera = this->get_node<gd::Camera3D>("Camera3D");
|
||||
}
|
||||
|
||||
void RTSPlayer::_process(double delta_time) {
|
||||
|
@ -13,6 +30,7 @@ void RTSPlayer::_process(double delta_time) {
|
|||
this->camera_mouse_motion = {0.f, 0.f};
|
||||
this->rotate(gd::Vector3{0.f, 1.f, 0.f}, camera_mouse_rotation * this->camera_mouse_rotation_speed);
|
||||
this->camera_mouse_rotation = 0.f;
|
||||
this->process_mouse();
|
||||
}
|
||||
|
||||
void RTSPlayer::setup_player_input(utils::PlayerInput *input) {
|
||||
|
@ -34,11 +52,74 @@ void RTSPlayer::spawn_at_position(gd::Transform3D const &at) {
|
|||
this->set_global_transform(at);
|
||||
}
|
||||
|
||||
void RTSPlayer::process_mouse() {
|
||||
gd::Viewport *view = this->get_viewport();
|
||||
gd::Vector2 const pix_coords = view->get_mouse_position();
|
||||
this->cursor_camera_normal = this->camera->project_ray_normal(pix_coords);
|
||||
}
|
||||
|
||||
void RTSPlayer::move(gd::Vector2 motion) {
|
||||
gd::Vector3 pos = this->get_global_position();
|
||||
this->set_global_position(pos + this->get_forward_direction() * motion.y + this->get_left_direction() * motion.x);
|
||||
}
|
||||
|
||||
gd::Dictionary RTSPlayer::raycast_from_cursor(uint32_t layers) const {
|
||||
gd::Vector3 const origin = this->camera->get_global_position();
|
||||
gd::Vector3 const destination = origin + this->cursor_camera_normal * 1000.f;
|
||||
gd::Ref<gd::PhysicsRayQueryParameters3D> const query{gd::PhysicsRayQueryParameters3D::create(origin, destination, layers)};
|
||||
gd::PhysicsDirectSpaceState3D *state = this->get_world_3d()->get_direct_space_state();
|
||||
return state->intersect_ray(query);
|
||||
}
|
||||
|
||||
gd::Node3D *RTSPlayer::node_under_cursor(uint32_t layers) const {
|
||||
gd::Dictionary result = this->raycast_from_cursor(layers);
|
||||
if(!result.is_empty())
|
||||
gd::UtilityFunctions::print("Found object ", Object::cast_to<gd::Node>(result["collider"])->get_path());
|
||||
else
|
||||
gd::UtilityFunctions::print("No object found");
|
||||
return result.is_empty() ? nullptr : Object::cast_to<gd::Node3D>(result["collider"]);
|
||||
}
|
||||
|
||||
void RTSPlayer::select_unit_under_cursor() {
|
||||
Unit *unit = Object::cast_to<Unit>(this->node_under_cursor(0x2));
|
||||
if(unit != nullptr)
|
||||
this->select_unit(unit);
|
||||
}
|
||||
|
||||
void RTSPlayer::order_activate_object_under_cursor() {
|
||||
if(this->selected_unit == nullptr)
|
||||
return;
|
||||
gd::Dictionary const dict = this->raycast_from_cursor(0x4);
|
||||
if(dict.is_empty())
|
||||
return;
|
||||
gd::Node3D *hit = gd::Object::cast_to<gd::Node3D>(dict["collider"]);
|
||||
GoalMarker *marker = gd::Object::cast_to<GoalMarker>(hit);
|
||||
bool const is_temporary_marker = marker == nullptr;
|
||||
if(is_temporary_marker) {
|
||||
marker = gd::Object::cast_to<GoalMarker>(this->ground_marker_scene->instantiate());
|
||||
this->add_child(marker);
|
||||
marker->set_as_top_level(true);
|
||||
marker->set_global_position(dict["position"]);
|
||||
}
|
||||
this->selected_unit->plan_for_marker(marker);
|
||||
if(is_temporary_marker) {
|
||||
this->selected_unit->get_world_state()->connect("attention_changed", callable_mp(marker, &GoalMarker::destroy_on_forgotten));
|
||||
}
|
||||
}
|
||||
|
||||
void RTSPlayer::clear_selected_unit() {
|
||||
if(this->selected_unit != nullptr) {
|
||||
this->selected_unit->disconnect("tree_exited", this->unit_destroyed_callable());
|
||||
this->selected_unit = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void RTSPlayer::select_unit(Unit *unit) {
|
||||
this->clear_selected_unit();
|
||||
this->selected_unit = unit;
|
||||
unit->connect("tree_exited", this->unit_destroyed_callable());
|
||||
}
|
||||
|
||||
void RTSPlayer::on_mouse_horizontal(gd::Ref<gd::InputEvent> event, float value) {
|
||||
if(this->mmb_down)
|
||||
this->camera_mouse_motion.x = value;
|
||||
|
@ -64,10 +145,18 @@ void RTSPlayer::on_rotate_horizontal(gd::Ref<gd::InputEvent> event, float value)
|
|||
}
|
||||
|
||||
void RTSPlayer::on_rclick(gd::Ref<gd::InputEvent> event, float value) {
|
||||
gd::UtilityFunctions::print("on_rclick on ", this->get_path(), " ---------");
|
||||
if(value == 0.f && !this->rmb_is_held())
|
||||
this->order_activate_object_under_cursor();
|
||||
this->rmb_down = value != 0.f;
|
||||
this->rmb_last_change = utils::time_seconds();
|
||||
}
|
||||
|
||||
void RTSPlayer::on_lclick(gd::Ref<gd::InputEvent> event, float value) {
|
||||
if(value == 0.f && !this->lmb_is_held())
|
||||
this->select_unit_under_cursor();
|
||||
this->lmb_down = value != 0.f;
|
||||
this->lmb_last_change = utils::time_seconds();
|
||||
}
|
||||
|
||||
void RTSPlayer::on_mclick(gd::Ref<gd::InputEvent> event, float value) {
|
||||
|
@ -83,3 +172,23 @@ gd::Vector3 RTSPlayer::get_left_direction() const {
|
|||
gd::Vector3 left = this->get_global_basis().get_column(0);
|
||||
return gd::Vector3{left.x, 0.f, left.z}.normalized();
|
||||
}
|
||||
|
||||
bool RTSPlayer::lmb_is_held() {
|
||||
return this->lmb_down && utils::time_seconds() >= (this->lmb_last_change + this->time_to_held);
|
||||
}
|
||||
|
||||
bool RTSPlayer::rmb_is_held() {
|
||||
return this->rmb_down && utils::time_seconds() >= (this->rmb_last_change + this->time_to_held);
|
||||
}
|
||||
|
||||
gd::Callable RTSPlayer::unit_destroyed_callable() {
|
||||
return callable_mp(this, &RTSPlayer::clear_selected_unit);
|
||||
}
|
||||
|
||||
void RTSPlayer::set_ground_marker_scene(gd::Ref<gd::PackedScene> scene) {
|
||||
this->ground_marker_scene = scene;
|
||||
}
|
||||
|
||||
gd::Ref<gd::PackedScene> RTSPlayer::get_ground_marker_scene() const {
|
||||
return this->ground_marker_scene;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
#ifndef RTS_PLAYER_HPP
|
||||
#define RTS_PLAYER_HPP
|
||||
|
||||
#include "godot_cpp/classes/camera3d.hpp"
|
||||
#include "godot_cpp/classes/input_event.hpp"
|
||||
#include "godot_cpp/classes/packed_scene.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "utils/player.hpp"
|
||||
#include "utils/player_input.hpp"
|
||||
#include <cstdint>
|
||||
#include <godot_cpp/classes/node3d.hpp>
|
||||
|
||||
namespace gd = godot;
|
||||
|
@ -13,13 +17,25 @@ class RTSPlayer : public gd::Node3D,
|
|||
GDCLASS(RTSPlayer, godot::Node3D);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
virtual void _ready() override;
|
||||
virtual void _process(double delta_time) override;
|
||||
|
||||
virtual void setup_player_input(utils::PlayerInput *input) override;
|
||||
virtual Node *to_node() override;
|
||||
virtual void spawn_at_position(gd::Transform3D const &at) override;
|
||||
private:
|
||||
//! process any changes to mouse position
|
||||
void process_mouse();
|
||||
//! move the camera along the ground
|
||||
void move(gd::Vector2 motion);
|
||||
gd::Dictionary raycast_from_cursor(uint32_t layers = UINT32_MAX) const;
|
||||
//! \return the first node from the camera under the cursor
|
||||
gd::Node3D *node_under_cursor(uint32_t layers = UINT32_MAX) const;
|
||||
//! add units under the cursor to this->selected_units
|
||||
void select_unit_under_cursor();
|
||||
void order_activate_object_under_cursor();
|
||||
void clear_selected_unit();
|
||||
void select_unit(Unit *unit);
|
||||
|
||||
// input functions
|
||||
void on_mouse_horizontal(gd::Ref<gd::InputEvent> event, float value);
|
||||
|
@ -31,22 +47,39 @@ private:
|
|||
void on_rclick(gd::Ref<gd::InputEvent> event, float value);
|
||||
void on_mclick(gd::Ref<gd::InputEvent> event, float value);
|
||||
|
||||
// getters
|
||||
// setters & getters
|
||||
gd::Vector3 get_forward_direction() const;
|
||||
gd::Vector3 get_left_direction() const;
|
||||
bool lmb_is_held();
|
||||
bool rmb_is_held();
|
||||
gd::Callable unit_destroyed_callable();
|
||||
void set_ground_marker_scene(gd::Ref<gd::PackedScene> scene);
|
||||
gd::Ref<gd::PackedScene> get_ground_marker_scene() const;
|
||||
private:
|
||||
Unit* selected_unit{nullptr};
|
||||
|
||||
bool mmb_down{false};
|
||||
bool lmb_down{false};
|
||||
float lmb_last_change{0.f};
|
||||
bool rmb_down{false};
|
||||
float rmb_last_change{0.f};
|
||||
|
||||
gd::Vector2 camera_mouse_motion{0.f, 0.f};
|
||||
float camera_mouse_rotation{0.f};
|
||||
|
||||
gd::Vector2 camera_keys_motion{0.f, 0.f};
|
||||
float camera_keys_rotation{0.f};
|
||||
|
||||
gd::Vector3 cursor_camera_normal{0.f, 0.f, 0.f};
|
||||
|
||||
gd::Camera3D *camera{nullptr};
|
||||
|
||||
gd::Ref<gd::PackedScene> ground_marker_scene{nullptr};
|
||||
|
||||
float const camera_keys_speed{10.f};
|
||||
float const camera_keys_rotation_speed{1.f};
|
||||
float const camera_mouse_speed{0.01f};
|
||||
float const camera_mouse_rotation_speed{-0.003f};
|
||||
double const time_to_held{0.1};
|
||||
};
|
||||
|
||||
#endif // !RTS_PLAYER_HPP
|
||||
|
|
31
src/rts_states.cpp
Normal file
31
src/rts_states.cpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
#include "rts_states.hpp"
|
||||
#include "godot_cpp/variant/utility_functions.hpp"
|
||||
|
||||
void MoveTo::_bind_methods() {}
|
||||
|
||||
void MoveTo::_ready() {
|
||||
gd::UtilityFunctions::print("_ready", this->get_path());
|
||||
this->agent = this->get_node<gd::NavigationAgent3D>("../NavigationAgent3D");
|
||||
this->agent->set_target_position(this->target_node->get_global_position());
|
||||
this->parent_node3d = Object::cast_to<gd::Node3D>(this->get_parent());
|
||||
}
|
||||
|
||||
void MoveTo::_end_state() {
|
||||
gd::UtilityFunctions::print("_end_state on ", this->get_path());
|
||||
this->agent->set_target_position(this->parent_node3d->get_global_position());
|
||||
}
|
||||
|
||||
void MoveTo::_process(double delta_time) {
|
||||
gd::Vector3 const pos = this->parent_node3d->get_global_position();
|
||||
gd::Vector3 const target = this->agent->get_next_path_position();
|
||||
gd::Vector3 const direction = (target - pos).normalized();
|
||||
this->parent_node3d->set_global_position(pos + direction * delta_time);
|
||||
this->parent_node3d->look_at(pos - gd::Vector3{direction.x, 0.f, direction.z});
|
||||
if(this->agent->is_navigation_finished())
|
||||
this->end_state();
|
||||
State::_process(delta_time);
|
||||
}
|
||||
|
||||
void Activate::_bind_methods() {}
|
||||
|
||||
void Animate::_bind_methods() {}
|
38
src/rts_states.hpp
Normal file
38
src/rts_states.hpp
Normal file
|
@ -0,0 +1,38 @@
|
|||
#ifndef RTS_STATES_HPP
|
||||
#define RTS_STATES_HPP
|
||||
|
||||
#include "goap/state.hpp"
|
||||
#include <godot_cpp/classes/navigation_agent3d.hpp>
|
||||
|
||||
/*! Uses navigation to chase the desired target node.
|
||||
*/
|
||||
class MoveTo : public goap::State {
|
||||
GDCLASS(MoveTo, goap::State);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
virtual void _ready() override;
|
||||
virtual void _end_state() override;
|
||||
virtual void _process(double delta_time) override;
|
||||
public:
|
||||
gd::Node3D *target_node{nullptr};
|
||||
private:
|
||||
gd::Node3D *parent_node3d{nullptr};
|
||||
gd::NavigationAgent3D *agent{nullptr};
|
||||
};
|
||||
|
||||
class Activate : public goap::State {
|
||||
GDCLASS(Activate, goap::State);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
gd::Node3D *target_node{nullptr};
|
||||
gd::String animation{};
|
||||
};
|
||||
|
||||
class Animate : public goap::State {
|
||||
GDCLASS(Animate, goap::State);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
gd::String animation{};
|
||||
};
|
||||
|
||||
#endif // !RTS_STATES_HPP
|
64
src/unit.cpp
64
src/unit.cpp
|
@ -1,8 +1,70 @@
|
|||
#include "unit.hpp"
|
||||
#include "goap/goal.hpp"
|
||||
#include "godot_cpp/variant/callable_method_pointer.hpp"
|
||||
#include "godot_cpp/variant/utility_functions.hpp"
|
||||
#include "utils/godot_macros.hpp"
|
||||
#include <godot_cpp/classes/navigation_agent3d.hpp>
|
||||
|
||||
void Unit::_bind_methods() {
|
||||
#define CLASSNAME Unit
|
||||
}
|
||||
|
||||
void Unit::_enter_tree() {
|
||||
void Unit::_enter_tree() { GDGAMEONLY();
|
||||
this->agent = this->get_node<gd::NavigationAgent3D>("NavigationAgent3D");
|
||||
this->planner = this->get_node<goap::Planner>("Planner");
|
||||
this->world_state = this->get_node<UnitWorldState>("ActorWorldState");
|
||||
this->world_state->connect("attention_changed", callable_mp(this, &Unit::stop_plan));
|
||||
}
|
||||
|
||||
void Unit::_process(double delta_time) {
|
||||
}
|
||||
|
||||
void Unit::stop_plan() {
|
||||
if(this->is_queued_for_deletion())
|
||||
return;
|
||||
this->current_plan.clear();
|
||||
if(this->state && !this->state->is_queued_for_deletion())
|
||||
this->destroy_state();
|
||||
this->state = nullptr;
|
||||
}
|
||||
|
||||
void Unit::plan_for_marker(GoalMarker *marker) {
|
||||
gd::UtilityFunctions::print("plan_for_marker on ", this->get_path());
|
||||
this->destroy_state();
|
||||
this->world_state->set_target_node(marker);
|
||||
this->plan_for_goal(marker->get_goal());
|
||||
}
|
||||
|
||||
void Unit::plan_for_goal(gd::Ref<goap::Goal> goal) {
|
||||
gd::UtilityFunctions::print("plan_for_goal on ", this->get_path());
|
||||
this->current_plan = this->planner->plan_for_goal(goal);
|
||||
this->next_action();
|
||||
}
|
||||
|
||||
UnitWorldState *Unit::get_world_state() const {
|
||||
return this->world_state;
|
||||
}
|
||||
|
||||
void Unit::destroy_state() {
|
||||
if(this->state == nullptr || this->state->is_queued_for_deletion() || !this->state->is_inside_tree())
|
||||
return;
|
||||
this->state->queue_free();
|
||||
this->remove_child(this->state);
|
||||
this->state = nullptr;
|
||||
}
|
||||
|
||||
void Unit::next_action() {
|
||||
if(this->state != nullptr && !this->state->is_queued_for_deletion())
|
||||
this->destroy_state();
|
||||
this->state = nullptr;
|
||||
if(this->current_plan.is_empty()) return;
|
||||
this->state = this->current_plan.get(0)->get_apply_state(this->world_state);
|
||||
if(state == nullptr) {
|
||||
this->stop_plan();
|
||||
gd::UtilityFunctions::push_error("Plan failed to be executed, abandoning");
|
||||
return;
|
||||
}
|
||||
this->current_plan.remove_at(0);
|
||||
this->add_child(this->state);
|
||||
this->state->connect("end_state", this->on_end_state);
|
||||
}
|
||||
|
|
28
src/unit.hpp
28
src/unit.hpp
|
@ -1,19 +1,43 @@
|
|||
#ifndef RTS_UNIT_HPP
|
||||
#define RTS_UNIT_HPP
|
||||
|
||||
#include "goal_marker.hpp"
|
||||
#include "unit_world_state.hpp"
|
||||
#include "goap/goal.hpp"
|
||||
#include "goap/planner.hpp"
|
||||
#include <godot_cpp/classes/character_body3d.hpp>
|
||||
#include <godot_cpp/classes/navigation_agent3d.hpp>
|
||||
#include <godot_cpp/classes/node3d.hpp>
|
||||
#include <godot_cpp/variant/callable.hpp>
|
||||
|
||||
namespace gd = godot;
|
||||
|
||||
|
||||
class Unit : public gd::Node3D {
|
||||
GDCLASS(Unit, gd::Node3D);
|
||||
class Unit : public gd::CharacterBody3D {
|
||||
GDCLASS(Unit, gd::CharacterBody3D);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
virtual void _enter_tree() override;
|
||||
virtual void _process(double delta_time) override;
|
||||
|
||||
void stop_plan();
|
||||
void plan_for_marker(GoalMarker *marker);
|
||||
void plan_for_goal(gd::Ref<goap::Goal> goal);
|
||||
UnitWorldState *get_world_state() const;
|
||||
private:
|
||||
void destroy_state();
|
||||
void next_action();
|
||||
private:
|
||||
goap::Plan current_plan{};
|
||||
goap::State *state{nullptr};
|
||||
|
||||
gd::Node3D *eyes{nullptr};
|
||||
|
||||
gd::Callable on_end_state{callable_mp(this, &Unit::next_action)};
|
||||
|
||||
gd::NavigationAgent3D *agent{nullptr};
|
||||
UnitWorldState *world_state{nullptr};
|
||||
goap::Planner *planner{nullptr};
|
||||
};
|
||||
|
||||
#endif // !RTS_UNIT_HPP
|
||||
|
|
45
src/unit_world_state.cpp
Normal file
45
src/unit_world_state.cpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
#include "unit_world_state.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "utils/godot_macros.hpp"
|
||||
#include <godot_cpp/variant/utility_functions.hpp>
|
||||
|
||||
void UnitWorldState::_bind_methods() {
|
||||
#define CLASSNAME UnitWorldState
|
||||
GDSIGNAL("attention_changed");
|
||||
GDFUNCTION(get_can_see_target);
|
||||
GDFUNCTION(get_target_dead);
|
||||
GDFUNCTION(get_is_at_target);
|
||||
GDFUNCTION(get_target_node);
|
||||
}
|
||||
|
||||
void UnitWorldState::_enter_tree() { GDGAMEONLY();
|
||||
this->parent_unit = gd::Object::cast_to<Unit>(this->get_parent());
|
||||
this->agent = this->get_node<gd::NavigationAgent3D>("../NavigationAgent3D");
|
||||
if(this->parent_unit == nullptr)
|
||||
gd::UtilityFunctions::push_warning("UnitWorldState needs to be a child node of a Unit");
|
||||
}
|
||||
|
||||
bool UnitWorldState::get_can_see_target() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitWorldState::get_is_at_target() const {
|
||||
if(this->target_node == nullptr) return true;
|
||||
gd::Vector3 const target = this->target_node->get_global_position();
|
||||
gd::Vector3 const current = this->parent_unit->get_global_position();
|
||||
float const min_dist = this->agent->get_target_desired_distance();
|
||||
return (target - current).length_squared() < min_dist * min_dist;
|
||||
}
|
||||
|
||||
bool UnitWorldState::get_target_dead() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void UnitWorldState::set_target_node(gd::Node3D *node) {
|
||||
this->target_node = node;
|
||||
this->emit_signal("attention_changed");
|
||||
}
|
||||
|
||||
gd::Node3D *UnitWorldState::get_target_node() const {
|
||||
return this->target_node;
|
||||
}
|
27
src/unit_world_state.hpp
Normal file
27
src/unit_world_state.hpp
Normal file
|
@ -0,0 +1,27 @@
|
|||
#ifndef UNIT_WORLD_STATE_HPP
|
||||
#define UNIT_WORLD_STATE_HPP
|
||||
|
||||
#include "goap/actor_world_state.hpp"
|
||||
#include "godot_cpp/classes/navigation_agent3d.hpp"
|
||||
#include <godot_cpp/classes/node3d.hpp>
|
||||
|
||||
class Unit;
|
||||
|
||||
class UnitWorldState : public goap::ActorWorldState {
|
||||
GDCLASS(UnitWorldState, goap::ActorWorldState);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
virtual void _enter_tree() override;
|
||||
bool get_can_see_target() const;
|
||||
bool get_is_at_target() const;
|
||||
bool get_target_dead() const;
|
||||
|
||||
void set_target_node(gd::Node3D *node);
|
||||
gd::Node3D *get_target_node() const;
|
||||
private:
|
||||
Unit *parent_unit{nullptr};
|
||||
gd::NavigationAgent3D *agent{nullptr};
|
||||
gd::Node3D *target_node{nullptr};
|
||||
};
|
||||
|
||||
#endif // !UNIT_WORLD_STATE_HPP
|
Loading…
Reference in a new issue