diff --git a/godot/player.tscn b/godot/player.tscn index 0f3c9fa..7cb7f7d 100644 --- a/godot/player.tscn +++ b/godot/player.tscn @@ -1,6 +1,20 @@ -[gd_scene format=3 uid="uid://cqkbxe758jr7p"] +[gd_scene load_steps=3 format=3 uid="uid://cqkbxe758jr7p"] + +[sub_resource type="Curve" id="Curve_bxjan"] +_data = [Vector2(0, 0), 0.0, 0.0628674, 0, 0, Vector2(0.51626, 0.549451), 2.7033, 2.7033, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0] +point_count = 3 + +[sub_resource type="SphereMesh" id="SphereMesh_jkn5p"] +radius = 0.2 +height = 0.4 [node name="TunnelsPlayer" type="TunnelsPlayer"] +camera_rotation_ramp = SubResource("Curve_bxjan") [node name="Camera3D" type="Camera3D" parent="."] -transform = Transform3D(-1, -6.99417e-08, 5.24489e-08, 0, 0.599946, 0.800041, -8.74227e-08, 0.800041, -0.599946, -2.38419e-07, 7.13901, -2.79085) +transform = Transform3D(-1, -6.99417e-08, 5.24489e-08, 1.59825e-08, 0.443572, 0.896239, -8.59493e-08, 0.896239, -0.443572, -2.38419e-07, 9.94949, -3.08239) + +[node name="Reticle" type="Node3D" parent="."] + +[node name="MeshInstance3D" type="MeshInstance3D" parent="Reticle"] +mesh = SubResource("SphereMesh_jkn5p") diff --git a/godot/player_character.tscn b/godot/player_character.tscn index b47acbc..0397333 100644 --- a/godot/player_character.tscn +++ b/godot/player_character.tscn @@ -1,4 +1,10 @@ -[gd_scene load_steps=3 format=3 uid="uid://dpda341t6ipiv"] +[gd_scene load_steps=5 format=3 uid="uid://dpda341t6ipiv"] + +[sub_resource type="Curve" id="Curve_7rmf4"] +min_value = 0.2 +max_value = 2.0 +_data = [Vector2(0.145299, 0.2), 0.0, 0.482143, 0, 0, Vector2(0.594017, 2), 0.0, 0.0, 0, 0] +point_count = 2 [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_3g72p"] height = 1.59321 @@ -6,7 +12,11 @@ height = 1.59321 [sub_resource type="CapsuleMesh" id="CapsuleMesh_rwcvu"] height = 1.605 +[sub_resource type="BoxMesh" id="BoxMesh_f5yvh"] +size = Vector3(0.125, 0.14, 0.94) + [node name="PlayerCharacter" type="PlayerCharacter"] +rotation_speed_curve = SubResource("Curve_7rmf4") [node name="CollisionShape3D" type="CollisionShape3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.802835, 0) @@ -17,3 +27,7 @@ shape = SubResource("CapsuleShape3D_3g72p") [node name="MeshInstance3D" type="MeshInstance3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.8121, 0) mesh = SubResource("CapsuleMesh_rwcvu") + +[node name="MeshInstance3D2" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.509142, 0.986876, 0.380722) +mesh = SubResource("BoxMesh_f5yvh") diff --git a/src/player_character.cpp b/src/player_character.cpp index b1c0798..ff9da94 100644 --- a/src/player_character.cpp +++ b/src/player_character.cpp @@ -1,11 +1,12 @@ #include "player_character.hpp" -#include -#include "godot_cpp/variant/utility_functions.hpp" #include "utils/godot_macros.h" +#include +#include namespace godot { void PlayerCharacter::_bind_methods() { #define CLASSNAME PlayerCharacter + GDPROPERTY_HINTED(rotation_speed_curve, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Curve"); } void PlayerCharacter::_enter_tree() { GDGAMEONLY(); @@ -14,6 +15,7 @@ void PlayerCharacter::_enter_tree() { GDGAMEONLY(); } void PlayerCharacter::_process(double delta_time) { GDGAMEONLY(); + this->process_rotation(delta_time); if(!this->mode_manual) { this->process_ai(delta_time); } @@ -21,26 +23,32 @@ void PlayerCharacter::_process(double delta_time) { GDGAMEONLY(); void PlayerCharacter::_physics_process(double delta_time) { GDGAMEONLY(); Vector3 const new_velocity = this->get_velocity().move_toward(this->velocity_target, delta_time * PlayerCharacter::ACCELERATION); - UtilityFunctions::print("Velocity target: ", this->velocity_target); - UtilityFunctions::print("Velocity current: ", this->get_velocity()); - UtilityFunctions::print("Velocity next: ", new_velocity); this->set_velocity(new_velocity); this->move_and_slide(); } void PlayerCharacter::move(Vector3 world_vector) { - this->velocity_target = world_vector * PlayerCharacter::SPEED; + this->velocity_target = world_vector * PlayerCharacter::WALK_SPEED; } void PlayerCharacter::aim(Vector3 at) { Vector3 const position{this->get_global_position()}; Vector3 const forward{(Vector3{at.x, 0.f, at.z} - Vector3{position.x, 0.f, position.z}).normalized()}; Vector3 const up{0.f, 1.f, 0.f}; - this->target_rotation = {forward.cross(up), up, forward}; + this->target_rotation = Basis{up.cross(forward), up, forward}; } void PlayerCharacter::set_manual_mode(bool value) { this->mode_manual = value; + this->nav_agent->set_process_mode(value ? ProcessMode::PROCESS_MODE_PAUSABLE : ProcessMode::PROCESS_MODE_DISABLED); +} + +void PlayerCharacter::set_rotation_speed_curve(Ref curve) { + this->rotation_speed_curve = curve; +} + +Ref PlayerCharacter::get_rotation_speed_curve() const { + return this->rotation_speed_curve; } void PlayerCharacter::process_ai(double delta_time) { @@ -48,14 +56,20 @@ void PlayerCharacter::process_ai(double delta_time) { } void PlayerCharacter::process_rotation(double delta_time) { - Transform3D current_trans{this->get_global_transform()}; - Basis const current_basis{current_trans.get_basis()}; - real_t const angle{current_basis.get_quaternion().angle_to(this->target_rotation.get_quaternion())}; - current_trans.set_basis(current_basis.lerp(this->target_rotation, PlayerCharacter::ROTATION_SPEED / angle * delta_time)); - this->set_global_transform(current_trans); + Transform3D trans{this->get_global_transform()}; + Basis basis = trans.get_basis(); + Quaternion const current_quaternion = basis.get_rotation_quaternion(); + Quaternion const target_quaternion = this->target_rotation.get_rotation_quaternion(); + float const angle = current_quaternion.angle_to(target_quaternion); + basis.set_quaternion(angle >= delta_time * PlayerCharacter::ROTATION_SPEED + ? current_quaternion.slerp(target_quaternion, delta_time * (this->rotation_speed_curve->sample(angle) * PlayerCharacter::ROTATION_SPEED) / angle) + : target_quaternion); + trans.set_basis(basis); + this->set_global_transform(trans); } float const PlayerCharacter::ACCELERATION{100.f}; -float const PlayerCharacter::SPEED{3.f}; -float const PlayerCharacter::ROTATION_SPEED{1.f}; +float const PlayerCharacter::WALK_SPEED{3.f}; +float const PlayerCharacter::SPRINT_SPEED{5.f}; +float const PlayerCharacter::ROTATION_SPEED{10.f}; } diff --git a/src/player_character.hpp b/src/player_character.hpp index 98507fc..fbb8038 100644 --- a/src/player_character.hpp +++ b/src/player_character.hpp @@ -1,6 +1,7 @@ #ifndef PLAYER_CHARACTER_HPP #define PLAYER_CHARACTER_HPP +#include "godot_cpp/classes/curve.hpp" #include namespace godot { @@ -16,6 +17,9 @@ public: void move(Vector3 world_vector); void aim(Vector3 at); void set_manual_mode(bool value); + + void set_rotation_speed_curve(Ref curve); + Ref get_rotation_speed_curve() const; protected: void process_ai(double delta_time); void process_rotation(double delta_time); @@ -25,8 +29,11 @@ private: NavigationAgent3D *nav_agent{nullptr}; bool mode_manual{false}; + Ref rotation_speed_curve{}; + static float const ACCELERATION; - static float const SPEED; + static float const WALK_SPEED; + static float const SPRINT_SPEED; static float const ROTATION_SPEED; }; } diff --git a/src/tunnels_player.cpp b/src/tunnels_player.cpp index 9f5f70d..bbc7682 100644 --- a/src/tunnels_player.cpp +++ b/src/tunnels_player.cpp @@ -1,10 +1,13 @@ #include "tunnels_player.hpp" -#include "godot_cpp/classes/resource_loader.hpp" +#include "godot_cpp/variant/plane.hpp" +#include "godot_cpp/variant/projection.hpp" #include "player_character.hpp" #include "utils/game_root.hpp" #include "utils/godot_macros.h" #include "utils/player_input.hpp" +#include #include +#include #include #include @@ -13,11 +16,14 @@ void TunnelsPlayer::_bind_methods() { #define CLASSNAME TunnelsPlayer GDFUNCTION_ARGS(horizontal_move_input, "event", "value"); GDFUNCTION_ARGS(vertical_move_input, "event", "value"); + GDPROPERTY_HINTED(camera_rotation_ramp, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Curve"); } void TunnelsPlayer::_ready() { GDGAMEONLY(); this->camera = this->get_viewport()->get_camera_3d(); this->initialize_character(); + this->camera_rotation_ramp->bake(); + this->reticle = this->get_node("Reticle"); } void TunnelsPlayer::_exit_tree() { GDGAMEONLY(); @@ -25,10 +31,15 @@ void TunnelsPlayer::_exit_tree() { GDGAMEONLY(); } void TunnelsPlayer::_process(double delta_time) { GDGAMEONLY(); + this->process_mouse_location(delta_time); + Vector3 const mouse_world_location = this->get_mouse_world_position(Vector3{0.f, 1.f, 0.f}, this->character->get_global_position().y + 1.f); + this->reticle->set_global_position(mouse_world_location); + this->process_camera_rotation(delta_time); switch(this->state) { default: case State::ManualControl: this->character->move(this->get_world_move_input().normalized()); + this->character->aim(mouse_world_location); this->set_global_position(this->character->get_global_position()); break; case State::Tactics: @@ -39,8 +50,35 @@ void TunnelsPlayer::_process(double delta_time) { GDGAMEONLY(); } } +void TunnelsPlayer::process_mouse_location(double delta_time) { + Viewport *view = this->get_viewport(); + Vector2 const pixel_location = view->get_mouse_position(); + this->mouse_location = pixel_location / view->get_visible_rect().get_size(); + this->mouse_world_ray_normal = this->camera->project_ray_normal(pixel_location); +} + +void TunnelsPlayer::process_camera_rotation(double delta_time) { + Vector3 rotation = this->get_global_rotation(); + float const y_multiplier = std::max(0.1f, this->mouse_location.y); + if(this->mouse_location.x < TunnelsPlayer::ROTATION_MARGIN) { + float const normalized{1.f - (this->mouse_location.x / TunnelsPlayer::ROTATION_MARGIN)}; + rotation.y += delta_time * TunnelsPlayer::ROTATION_SPEED * camera_rotation_ramp->sample(normalized) * y_multiplier; + } + if(this->mouse_location.x > 1.f - TunnelsPlayer::ROTATION_MARGIN) { + float const normalized{((this->mouse_location.x - (1.f - TunnelsPlayer::ROTATION_MARGIN)) / TunnelsPlayer::ROTATION_MARGIN)}; + rotation.y -= delta_time * TunnelsPlayer::ROTATION_SPEED * camera_rotation_ramp->sample(normalized) * y_multiplier; + } + + // wrap rotation to avoid going into invalid numbers + while(rotation.y > 6.283185) + rotation.y -= 6.283185; + while(rotation.y < 0.f) + rotation.y += 6.283185; + this->set_global_rotation(rotation); +} + void TunnelsPlayer::setup_player_input(PlayerInput *input) { - input->listen_to(PlayerInput::Listener("move_right", "move_left", this, "horizontal_move_input")); + input->listen_to(PlayerInput::Listener("move_left", "move_right", this, "horizontal_move_input")); input->listen_to(PlayerInput::Listener("move_forward", "move_backward", this, "vertical_move_input")); } @@ -58,24 +96,50 @@ void TunnelsPlayer::vertical_move_input(Ref event, float value) { void TunnelsPlayer::initialize_character() { Ref player_scene = ResourceLoader::get_singleton()->load("res://player_character.tscn"); + // check if the player scene is a valid player character if(player_scene.is_null() || !player_scene.is_valid()) return; if(player_scene->get_state()->get_node_type(0) != StringName("PlayerCharacter")) return; + // instantiate and store as player character this->character = Object::cast_to(player_scene->instantiate()); this->get_parent()->add_child(this->character); this->character->set_global_transform(this->get_global_transform()); + // toggle manual mode, meaning the character's navigation agent is disabled and direct input to move(..) is used instead this->character->set_manual_mode(true); } Vector3 TunnelsPlayer::get_world_move_input() const { - Basis const camera_basis = camera->get_global_transform().get_basis(); + Basis const basis = this->get_global_transform().get_basis(); // get the forward and left vectors, ensuring that they won't produce {0,0,0} when flattened - Vector3 x = camera_basis.get_column(0); - if(x.x == 0.f && x.z == 0.f) x = camera_basis.get_column(1); - Vector3 z = camera_basis.get_column(2); - if(z.x == 0.f && z.z == 0.f) z = camera_basis.get_column(1); + Vector3 x = basis.get_column(0); + if(x.x == 0.f && x.z == 0.f) + x = basis.get_column(1); + Vector3 z = basis.get_column(2); + if(z.x == 0.f && z.z == 0.f) + z = basis.get_column(1); + // convert input vector to world normal by way of flattened forward and left units return this->move_input.x * Vector3{x.x, 0.f, x.z}.normalized() - + this->move_input.y * Vector3{z.x, 0.f, z.y}.normalized(); + + this->move_input.y * Vector3{z.x, 0.f, z.z}.normalized(); } + +Vector3 TunnelsPlayer::get_mouse_world_position(Vector3 axis, float depth) const { + Vector3 const cam_origin = this->camera->get_global_position(); + float const cam_depth = axis.dot(cam_origin); + float const ray_step = axis.dot(this->mouse_world_ray_normal); + float const distance = depth - cam_depth; + float const steps = distance / ray_step; + return cam_origin + this->mouse_world_ray_normal * steps; +} + +void TunnelsPlayer::set_camera_rotation_ramp(Ref curve) { + this->camera_rotation_ramp = curve; +} + +Ref TunnelsPlayer::get_camera_rotation_ramp() const { + return this->camera_rotation_ramp; +} + +float const TunnelsPlayer::ROTATION_SPEED{4.f}; +float const TunnelsPlayer::ROTATION_MARGIN{0.35f}; } diff --git a/src/tunnels_player.hpp b/src/tunnels_player.hpp index cbe6c69..1749c25 100644 --- a/src/tunnels_player.hpp +++ b/src/tunnels_player.hpp @@ -5,6 +5,7 @@ #include "utils/player_input.hpp" #include #include +#include #include namespace godot { @@ -25,6 +26,9 @@ public: virtual void _exit_tree() override; virtual void _process(double delta_time) override; + void process_mouse_location(double delta_time); + void process_camera_rotation(double delta_time); + virtual void setup_player_input(PlayerInput *input) override; virtual Node *to_node() override; @@ -34,13 +38,23 @@ public: void initialize_character(); Vector3 get_world_move_input() const; + Vector3 get_mouse_world_position(Vector3 axis, float depth) const; + + void set_camera_rotation_ramp(Ref curve); + Ref get_camera_rotation_ramp() const; private: Vector2 move_input{0,0}; Vector2 mouse_location{0,0}; - PlayerCharacter *character{nullptr}; + Vector3 mouse_world_ray_normal{0.f,0.f,0.f}; TunnelsPlayer::State state{State::ManualControl}; + PlayerCharacter *character{nullptr}; + Node3D *reticle{nullptr}; Camera3D *camera{nullptr}; + + Ref camera_rotation_ramp{}; + static float const ROTATION_SPEED; + static float const ROTATION_MARGIN; }; }