163 lines
7.1 KiB
C++
163 lines
7.1 KiB
C++
#include "tunnels_player.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>
|
|
|
|
namespace godot {
|
|
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();
|
|
GameRoot::get_singleton()->remove_player(this->get_player_id());
|
|
}
|
|
|
|
void TunnelsPlayer::_process(double delta_time) { GDGAMEONLY();
|
|
this->process_mouse_location(delta_time); // get the current screen location of the cursor
|
|
// convert screen location to world location on the same y plane as the player character
|
|
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);
|
|
// move the reticle
|
|
this->reticle->set_global_position(mouse_world_location);
|
|
// rotate the camera
|
|
this->process_camera_rotation(delta_time);
|
|
switch(this->state) {
|
|
default:
|
|
case State::ManualControl:
|
|
// send the current wasd input to the character
|
|
this->character->move(this->get_world_move_input().normalized());
|
|
// send the current world cursor position the character
|
|
this->character->aim(mouse_world_location);
|
|
// move the camera along with the character
|
|
this->set_global_position(this->character->get_global_position());
|
|
break;
|
|
case State::Tactics:
|
|
break;
|
|
case State::Overview:
|
|
// move camera along with the input
|
|
this->set_global_position(this->get_global_position() + this->get_world_move_input().normalized());
|
|
break;
|
|
}
|
|
}
|
|
|
|
void TunnelsPlayer::process_mouse_location(double delta_time) {
|
|
Viewport *view = this->get_viewport();
|
|
Vector2 const pixel_location = view->get_mouse_position();
|
|
// convert cursor's global pixel position to normalized screen coordinates
|
|
this->mouse_location = pixel_location / view->get_visible_rect().get_size();
|
|
// get the direction the mouse is pointing in the world
|
|
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(TunnelsPlayer::ROTATION_Y_MIN_INFLUENCE, this->mouse_location.y); // the influence of the mouse's y position on the rotation speed
|
|
// rotate the camera when the mouse is close to the edge of the screen
|
|
if(this->mouse_location.x < TunnelsPlayer::ROTATION_MARGIN) {
|
|
// normalized measurement of how far into the rotation margin the mouse is
|
|
float const normalized{1.f - (this->mouse_location.x / TunnelsPlayer::ROTATION_MARGIN)};
|
|
// rotate based on delta time and use a curve to make the rotation zone feel more natural
|
|
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;
|
|
// apply new rotation
|
|
this->set_global_rotation(rotation);
|
|
}
|
|
|
|
void TunnelsPlayer::setup_player_input(PlayerInput *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"));
|
|
}
|
|
|
|
Node *TunnelsPlayer::to_node() {
|
|
return Object::cast_to<Node>(this);
|
|
}
|
|
|
|
void TunnelsPlayer::horizontal_move_input(Ref<InputEvent> event, float value) {
|
|
this->move_input.x = value;
|
|
}
|
|
|
|
void TunnelsPlayer::vertical_move_input(Ref<InputEvent> event, float value) {
|
|
this->move_input.y = 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 the 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());
|
|
// disable navmesh navigation and start using player input
|
|
this->character->set_manual_mode(true);
|
|
}
|
|
|
|
Vector3 TunnelsPlayer::get_world_move_input() const {
|
|
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 = 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.z}.normalized();
|
|
}
|
|
|
|
Vector3 TunnelsPlayer::get_mouse_world_position(Vector3 axis, float depth) const {
|
|
// cache camera location
|
|
Vector3 const cam_origin = this->camera->get_global_position();
|
|
// get the ray and origin depths along the axis
|
|
float const cam_depth = axis.dot(cam_origin);
|
|
float const ray_step = axis.dot(this->mouse_world_ray_normal);
|
|
// calculate the number of "steps" the ray needs to take to get the target depth from the origin along the axis
|
|
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{6.f};
|
|
float const TunnelsPlayer::ROTATION_Y_MIN_INFLUENCE{7.f};
|
|
float const TunnelsPlayer::ROTATION_MARGIN{0.4f};
|
|
}
|