feat: mouse rotation for camera and character

This commit is contained in:
Sara 2024-03-18 15:30:03 +01:00
parent 709b972f91
commit 00a9334653
6 changed files with 154 additions and 27 deletions

View file

@ -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")

View file

@ -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")

View file

@ -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};
}

View file

@ -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;
};
}

View file

@ -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};
}

View file

@ -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;
};
}