feat: fundamental movement and separation of concerns for camera and character motion

This commit is contained in:
Sara 2024-03-17 21:25:52 +01:00
parent 121bb0b7d6
commit 709b972f91
8 changed files with 198 additions and 20 deletions

View file

@ -3,4 +3,4 @@
[node name="TunnelsPlayer" type="TunnelsPlayer"]
[node name="Camera3D" type="Camera3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 0.971276, 0.237957, 0, -0.237957, 0.971276, 0, 1.20073, 1.85333)
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)

View file

@ -0,0 +1,19 @@
[gd_scene load_steps=3 format=3 uid="uid://dpda341t6ipiv"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_3g72p"]
height = 1.59321
[sub_resource type="CapsuleMesh" id="CapsuleMesh_rwcvu"]
height = 1.605
[node name="PlayerCharacter" type="PlayerCharacter"]
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.802835, 0)
shape = SubResource("CapsuleShape3D_3g72p")
[node name="NavigationAgent3D" type="NavigationAgent3D" parent="."]
[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")

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=6 format=3 uid="uid://m36guasmi3c1"]
[gd_scene load_steps=9 format=3 uid="uid://m36guasmi3c1"]
[ext_resource type="PackedScene" uid="uid://cqkbxe758jr7p" path="res://player.tscn" id="1_hv5rj"]
@ -8,6 +8,15 @@
game_state = SubResource("GameState_k4j3x")
player_scene = ExtResource("1_hv5rj")
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_s4k5k"]
[sub_resource type="Sky" id="Sky_oquga"]
sky_material = SubResource("ProceduralSkyMaterial_s4k5k")
[sub_resource type="Environment" id="Environment_mt2l0"]
background_mode = 2
sky = SubResource("Sky_oquga")
[sub_resource type="BoxMesh" id="BoxMesh_5glbk"]
size = Vector3(20, 0.25, 20)
@ -17,10 +26,17 @@ size = Vector3(20, 0.25, 20)
[node name="Level3D" type="Level3D"]
game_mode_prototype = SubResource("TunnelsGameMode_itn7y")
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_mt2l0")
[node name="MeshInstance3D" type="MeshInstance3D" parent="WorldEnvironment"]
mesh = SubResource("BoxMesh_5glbk")
skeleton = NodePath("../..")
[node name="StaticBody3D" type="StaticBody3D" parent="."]
[node name="StaticBody3D" type="StaticBody3D" parent="WorldEnvironment"]
[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D"]
[node name="CollisionShape3D" type="CollisionShape3D" parent="WorldEnvironment/StaticBody3D"]
shape = SubResource("BoxShape3D_kacqg")
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="WorldEnvironment"]
transform = Transform3D(0.581243, 0.357328, -0.731077, 0, 0.898426, 0.439124, 0.81373, -0.255238, 0.522204, 0, 2.44259, 0)

61
src/player_character.cpp Normal file
View file

@ -0,0 +1,61 @@
#include "player_character.hpp"
#include <godot_cpp/classes/navigation_agent3d.hpp>
#include "godot_cpp/variant/utility_functions.hpp"
#include "utils/godot_macros.h"
namespace godot {
void PlayerCharacter::_bind_methods() {
#define CLASSNAME PlayerCharacter
}
void PlayerCharacter::_enter_tree() { GDGAMEONLY();
this->nav_agent = this->get_node<NavigationAgent3D>("NavigationAgent3D");
this->target_rotation = this->get_global_transform().get_basis().get_quaternion();
}
void PlayerCharacter::_process(double delta_time) { GDGAMEONLY();
if(!this->mode_manual) {
this->process_ai(delta_time);
}
}
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;
}
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};
}
void PlayerCharacter::set_manual_mode(bool value) {
this->mode_manual = value;
}
void PlayerCharacter::process_ai(double delta_time) {
this->velocity_target = this->nav_agent->get_velocity();
}
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);
}
float const PlayerCharacter::ACCELERATION{100.f};
float const PlayerCharacter::SPEED{3.f};
float const PlayerCharacter::ROTATION_SPEED{1.f};
}

34
src/player_character.hpp Normal file
View file

@ -0,0 +1,34 @@
#ifndef PLAYER_CHARACTER_HPP
#define PLAYER_CHARACTER_HPP
#include <godot_cpp/classes/character_body3d.hpp>
namespace godot {
class NavigationAgent3D;
class PlayerCharacter : public CharacterBody3D {
GDCLASS(PlayerCharacter, CharacterBody3D);
static void _bind_methods();
public:
virtual void _enter_tree() override;
virtual void _process(double delta_time) override;
virtual void _physics_process(double delta_time) override;
void move(Vector3 world_vector);
void aim(Vector3 at);
void set_manual_mode(bool value);
protected:
void process_ai(double delta_time);
void process_rotation(double delta_time);
private:
Vector3 velocity_target{0.f,0.f,0.f};
Basis target_rotation{};
NavigationAgent3D *nav_agent{nullptr};
bool mode_manual{false};
static float const ACCELERATION;
static float const SPEED;
static float const ROTATION_SPEED;
};
}
#endif // !PLAYER_CHARACTER_HPP

View file

@ -3,12 +3,13 @@
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/core/defs.hpp>
#include <godot_cpp/godot.hpp>
#include "utils/game_root.hpp"
#include "utils/game_mode.hpp"
#include "utils/game_root.hpp"
#include "utils/game_state.hpp"
#include "utils/level.hpp"
#include "utils/spawn_point.hpp"
#include "utils/player_input.hpp"
#include "utils/spawn_point.hpp"
#include "player_character.hpp"
#include "tunnels_game_mode.hpp"
#include "tunnels_player.hpp"
@ -19,7 +20,7 @@ void initialize_gdextension_types(ModuleInitializationLevel p_level)
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
ClassDB::register_class<GameRoot>();
ClassDB::register_abstract_class<GameRoot>();
ClassDB::register_class<GameRoot3D>();
ClassDB::register_class<SpawnPoint3D>();
ClassDB::register_class<PlayerInput>();
@ -28,6 +29,7 @@ void initialize_gdextension_types(ModuleInitializationLevel p_level)
ClassDB::register_class<Level3D>();
ClassDB::register_class<TunnelsGameMode>();
ClassDB::register_class<TunnelsPlayer>();
ClassDB::register_class<PlayerCharacter>();
}
extern "C"

View file

@ -1,12 +1,14 @@
#include "tunnels_player.hpp"
#include "godot_cpp/classes/resource_loader.hpp"
#include "player_character.hpp"
#include "utils/game_root.hpp"
#include "utils/godot_macros.h"
#include "utils/player_input.hpp"
#include <godot_cpp/classes/input_event.hpp>
#include <godot_cpp/classes/scene_state.hpp>
#include <godot_cpp/classes/viewport.hpp>
namespace godot {
float const TunnelsPlayer::MOVE_SPEED{0.f};
void TunnelsPlayer::_bind_methods() {
#define CLASSNAME TunnelsPlayer
GDFUNCTION_ARGS(horizontal_move_input, "event", "value");
@ -15,11 +17,26 @@ void TunnelsPlayer::_bind_methods() {
void TunnelsPlayer::_ready() { GDGAMEONLY();
this->camera = this->get_viewport()->get_camera_3d();
this->initialize_character();
}
void TunnelsPlayer::_physics_process(double delta_time) { GDGAMEONLY();
this->set_velocity(this->get_world_move_input());
this->move_and_slide();
void TunnelsPlayer::_exit_tree() { GDGAMEONLY();
GameRoot::get_singleton()->remove_player(this->get_player_id());
}
void TunnelsPlayer::_process(double delta_time) { GDGAMEONLY();
switch(this->state) {
default:
case State::ManualControl:
this->character->move(this->get_world_move_input().normalized());
this->set_global_position(this->character->get_global_position());
break;
case State::Tactics:
break;
case State::Overview:
this->set_global_position(this->get_global_position() + this->get_world_move_input().normalized());
break;
}
}
void TunnelsPlayer::setup_player_input(PlayerInput *input) {
@ -39,9 +56,26 @@ 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");
if(player_scene.is_null() || !player_scene.is_valid())
return;
if(player_scene->get_state()->get_node_type(0) != StringName("PlayerCharacter"))
return;
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());
this->character->set_manual_mode(true);
}
Vector3 TunnelsPlayer::get_world_move_input() const {
Basis const camera_basis = camera->get_global_transform().get_basis();
return this->move_input.x * camera_basis.get_column(0)
+ this->move_input.y * camera_basis.get_column(2);
// 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);
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();
}
}

View file

@ -8,13 +8,22 @@
#include <godot_cpp/classes/input_event.hpp>
namespace godot {
class TunnelsPlayer : public CharacterBody3D, public IPlayer {
GDCLASS(TunnelsPlayer, CharacterBody3D);
class PlayerCharacter;
class TunnelsPlayer : public Node3D, public IPlayer {
GDCLASS(TunnelsPlayer, Node3D);
static void _bind_methods();
enum State {
ManualControl = 0x0,
Tactics = 0x1,
Overview = 0x2,
};
public:
virtual void _ready() override;
virtual void _physics_process(double delta_time) override;
virtual void _exit_tree() override;
virtual void _process(double delta_time) override;
virtual void setup_player_input(PlayerInput *input) override;
virtual Node *to_node() override;
@ -22,13 +31,16 @@ public:
void horizontal_move_input(Ref<InputEvent> event, float value);
void vertical_move_input(Ref<InputEvent> event, float value);
void initialize_character();
Vector3 get_world_move_input() const;
private:
Vector2 move_input{0,0};
Vector2 mouse_location{0,0};
PlayerCharacter *character{nullptr};
TunnelsPlayer::State state{State::ManualControl};
Camera3D *camera{nullptr};
public:
static float const MOVE_SPEED;
};
}