feat: mouse rotation for camera and character
This commit is contained in:
parent
709b972f91
commit
00a9334653
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
#include "player_character.hpp"
|
||||
#include <godot_cpp/classes/navigation_agent3d.hpp>
|
||||
#include "godot_cpp/variant/utility_functions.hpp"
|
||||
#include "utils/godot_macros.h"
|
||||
#include <godot_cpp/classes/navigation_agent3d.hpp>
|
||||
#include <godot_cpp/variant/utility_functions.hpp>
|
||||
|
||||
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> curve) {
|
||||
this->rotation_speed_curve = curve;
|
||||
}
|
||||
|
||||
Ref<Curve> 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};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef PLAYER_CHARACTER_HPP
|
||||
#define PLAYER_CHARACTER_HPP
|
||||
|
||||
#include "godot_cpp/classes/curve.hpp"
|
||||
#include <godot_cpp/classes/character_body3d.hpp>
|
||||
|
||||
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> curve);
|
||||
Ref<Curve> 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<Curve> 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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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 <algorithm>
|
||||
#include <godot_cpp/classes/input_event.hpp>
|
||||
#include <godot_cpp/classes/resource_loader.hpp>
|
||||
#include <godot_cpp/classes/scene_state.hpp>
|
||||
#include <godot_cpp/classes/viewport.hpp>
|
||||
|
||||
|
@ -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<Node3D>("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<InputEvent> event, float value) {
|
|||
|
||||
void TunnelsPlayer::initialize_character() {
|
||||
Ref<PackedScene> 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<PlayerCharacter>(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> curve) {
|
||||
this->camera_rotation_ramp = curve;
|
||||
}
|
||||
|
||||
Ref<Curve> 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};
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "utils/player_input.hpp"
|
||||
#include <godot_cpp/classes/camera3d.hpp>
|
||||
#include <godot_cpp/classes/character_body3d.hpp>
|
||||
#include <godot_cpp/classes/curve.hpp>
|
||||
#include <godot_cpp/classes/input_event.hpp>
|
||||
|
||||
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> curve);
|
||||
Ref<Curve> 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<Curve> camera_rotation_ramp{};
|
||||
static float const ROTATION_SPEED;
|
||||
static float const ROTATION_MARGIN;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue