feat: fundamental movement and separation of concerns for camera and character motion
This commit is contained in:
parent
121bb0b7d6
commit
709b972f91
|
@ -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)
|
||||
|
|
19
godot/player_character.tscn
Normal file
19
godot/player_character.tscn
Normal 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")
|
|
@ -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
61
src/player_character.cpp
Normal 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
34
src/player_character.hpp
Normal 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
|
|
@ -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"
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue