feat: first full movement and enemy prototype

This commit is contained in:
Sara 2025-05-25 22:05:08 +02:00
parent d72a037d5a
commit eb8dba058d
42 changed files with 3844 additions and 0 deletions

View file

@ -0,0 +1,182 @@
#include "player_body.h"
#include "core/config/engine.h"
#include "core/input/input.h"
#include "core/math/math_funcs.h"
#include "scene/3d/camera_3d.h"
#include "scene/animation/animation_player.h"
#include "scene/main/viewport.h"
#include "macros.h"
char *const PlayerBody::split_step_action{const_cast<char*>("split_step")};
char *const PlayerBody::move_left_action{const_cast<char*>("move_left")};
char *const PlayerBody::move_right_action{const_cast<char*>("move_right")};
char *const PlayerBody::move_forward_action{const_cast<char*>("move_forward")};
char *const PlayerBody::move_back_action{const_cast<char*>("move_back")};
void PlayerBody::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_desired_velocity"), &self_type::get_desired_velocity);
ClassDB::bind_method(D_METHOD("get_desired_direction"), &self_type::get_desired_direction);
BIND_PROPERTY(Variant::FLOAT, stopping_deceleration);
BIND_PROPERTY(Variant::FLOAT, start_speed);
BIND_PROPERTY(Variant::FLOAT, step_boost);
BIND_PROPERTY(Variant::FLOAT, min_step_speed);
BIND_PROPERTY(Variant::FLOAT, acceleration);
BIND_PROPERTY(Variant::FLOAT, target_speed);
BIND_PROPERTY(Variant::FLOAT, split_step_time);
BIND_PROPERTY(Variant::FLOAT, split_step_stop_time);
BIND_PROPERTY(Variant::FLOAT, model_lean);
BIND_PROPERTY(Variant::FLOAT, model_lean_speed);
}
void PlayerBody::_notification(int what) {
if(Engine::get_singleton()->is_editor_hint()) {
return;
}
switch(what) {
default:
return;
case NOTIFICATION_ENTER_TREE:
this->enter_tree();
return;
case NOTIFICATION_PROCESS:
this->process(this->get_process_delta_time());
return;
case NOTIFICATION_PHYSICS_PROCESS:
this->physics_process(this->get_physics_process_delta_time());
return;
}
}
void PlayerBody::enter_tree() {
this->set_process(true);
this->set_physics_process(true);
this->model = Object::cast_to<Node3D>(this->get_node(NodePath("character")));
this->anim = Object::cast_to<AnimationPlayer>(this->get_node(NodePath("character/AnimationPlayer")));
this->camera = Object::cast_to<Camera3D>(this->get_node(NodePath("Camera3D")));
this->camera->set_fov(this->min_fov);
}
void PlayerBody::process(double delta) {
Input *input{Input::get_singleton()};
this->movement = Vector2{
input->get_axis(self_type::move_right_action, self_type::move_left_action),
input->get_axis(self_type::move_back_action, self_type::move_forward_action)
}.normalized();
float fov_target{Math::lerp(this->min_fov, this->max_speed_fov, Math::sqrt(this->get_velocity().length() / this->target_speed))};
this->camera->set_fov(Math::move_toward(this->camera->get_fov(), fov_target, float(this->max_delta_fov * delta)));
}
void PlayerBody::physics_process(double delta) {
this->move_and_slide();
}
Vector3 PlayerBody::get_desired_direction() const {
Basis const global{this->camera->get_global_basis()};
Vector3 forward{global.get_column(2)};
Vector3 left{global.get_column(0)};
return Vector3{
Vector3{forward.x, 0.f, forward.z}.normalized() * -this->movement.y +
Vector3{left.x, 0.f, left.z}.normalized() * -this->movement.x
}.normalized();
}
Vector3 PlayerBody::get_desired_velocity() const {
return this->get_desired_direction() * this->target_speed;
}
Vector2 PlayerBody::get_movement_input() const {
return this->movement;
}
AnimationPlayer *PlayerBody::get_anim() const {
return this->anim;
}
Camera3D *PlayerBody::get_camera() const {
return this->camera;
}
Node3D *PlayerBody::get_model() const {
return this->model;
}
void PlayerBody::set_stopping_deceleration(float value) {
this->stopping_deceleration = value;
}
float PlayerBody::get_stopping_deceleration() const {
return this->stopping_deceleration;
}
void PlayerBody::set_start_speed(float value) {
this->start_speed = value;
}
float PlayerBody::get_start_speed() const {
return this->start_speed;
}
void PlayerBody::set_step_boost(float value) {
this->step_boost = value;
}
float PlayerBody::get_step_boost() const {
return this->step_boost;
}
void PlayerBody::set_min_step_speed(float value) {
this->min_step_speed = value;
}
float PlayerBody::get_min_step_speed() const {
return this->min_step_speed;
}
void PlayerBody::set_acceleration(float value) {
this->acceleration = value;
}
float PlayerBody::get_acceleration() const {
return this->acceleration;
}
void PlayerBody::set_target_speed(float value) {
this->target_speed = value;
}
float PlayerBody::get_target_speed() const {
return this->target_speed;
}
void PlayerBody::set_split_step_time(double value) {
this->split_step_time = value;
}
double PlayerBody::get_split_step_time() const {
return this->split_step_time;
}
void PlayerBody::set_split_step_stop_time(double value) {
this->split_step_stop_time = value;
}
double PlayerBody::get_split_step_stop_time() const {
return this->split_step_stop_time;
}
void PlayerBody::set_model_lean(float value) {
this->model_lean = value;
}
float PlayerBody::get_model_lean() const {
return this->model_lean;
}
void PlayerBody::set_model_lean_speed(float value) {
this->model_lean_speed = value;
}
float PlayerBody::get_model_lean_speed() const {
return this->model_lean_speed;
}

View file

@ -0,0 +1,73 @@
#ifndef PLAYER_BODY_H
#define PLAYER_BODY_H
#include "scene/3d/camera_3d.h"
#include "scene/3d/physics/character_body_3d.h"
#include "scene/animation/animation_player.h"
class PlayerBody : public CharacterBody3D {
GDCLASS(PlayerBody, CharacterBody3D);
static void _bind_methods();
void _notification(int what);
void enter_tree();
void process(double delta);
void physics_process(double delta);
public:
Vector3 get_desired_direction() const;
Vector3 get_desired_velocity() const;
Vector2 get_movement_input() const;
AnimationPlayer *get_anim() const;
Camera3D *get_camera() const;
Node3D *get_model() const;
void set_stopping_deceleration(float value);
float get_stopping_deceleration() const;
void set_start_speed(float value);
float get_start_speed() const;
void set_step_boost(float value);
float get_step_boost() const;
void set_min_step_speed(float value);
float get_min_step_speed() const;
void set_acceleration(float value);
float get_acceleration() const;
void set_target_speed(float value);
float get_target_speed() const;
void set_split_step_time(double value);
double get_split_step_time() const;
void set_split_step_stop_time(double value);
double get_split_step_stop_time() const;
void set_model_lean(float value);
float get_model_lean() const;
void set_model_lean_speed(float value);
float get_model_lean_speed() const;
private:
Vector2 movement{0.f, 0.f};
AnimationPlayer *anim{nullptr};
Node3D *model{nullptr};
Camera3D *camera{nullptr};
float stopping_deceleration{20.f};
float start_speed{5.f};
float step_boost{7.f};
float min_step_speed{15.f};
float acceleration{8.f};
float target_speed{30.f};
double split_step_time{0.5};
double split_step_stop_time{0.5};
float max_speed_fov{100.f};
float min_fov{80.f};
double max_delta_fov{100.f};
float model_lean{0.25f};
float model_lean_speed{0.25f};
public:
static char *const split_step_action;
static char *const move_left_action;
static char *const move_right_action;
static char *const move_forward_action;
static char *const move_back_action;
};
#endif // !PLAYER_BODY_H

View file

@ -0,0 +1,174 @@
#include "player_states.h"
#include "core/config/engine.h"
#include "core/math/math_funcs.h"
#include "core/typedefs.h"
#include "going/player_body.h"
PlayerBody *PlayerState::get_body() const {
return this->body;
}
PlayerState::StateID StandingState::get_next_state() const {
if(!this->get_body()->is_on_floor()) {
return FallingState::get_class_static();
} else {
return !this->get_body()->get_movement_input().is_zero_approx()
? RunningState::get_class_static()
: self_type::get_class_static();
}
}
void StandingState::state_entered() {
this->get_body()->get_anim()->play("RESET", 0.1);
}
void StandingState::physics_process(double delta) {
Vector3 const current{this->get_body()->get_velocity()};
float const speed_delta{float(this->get_body()->get_stopping_deceleration() * delta)};
this->get_body()->set_velocity(current.move_toward(Vector3(), speed_delta));
}
PlayerState::StateID RunningState::get_next_state() const {
if(!this->get_body()->is_on_floor()) {
return FallingState::get_class_static();
} else if(this->get_body()->get_velocity().is_zero_approx()) {
return StandingState::get_class_static();
} else if(Input::get_singleton()->is_action_just_pressed(PlayerBody::split_step_action) && this->get_body()->get_velocity().length() > this->get_body()->get_min_step_speed()) {
return SplitStepState::get_class_static();
} else {
return self_type::get_class_static();
}
}
void RunningState::state_entered() {
this->get_body()->get_anim()->play("run", 0.1);
}
void RunningState::process(double delta) {
if(!this->get_body()->get_velocity().is_zero_approx()) {
Vector3 const current{this->get_body()->get_velocity()};
Vector3 const cross{Vector3{0.f, 1.f, 0.f}.cross(current).normalized()};
Vector3 const desired_direction{this->get_body()->get_desired_direction()};
this->lean_modifier = Math::move_toward(
this->lean_modifier,
cross.dot(desired_direction) * this->get_body()->get_model_lean(),
float(this->get_body()->get_model_lean_speed() * delta)
);
Vector3 up{Vector3{0.0, 1.0, 0.0} + cross * this->lean_modifier};
this->get_body()->get_model()->look_at(this->get_body()->get_global_position() - current, up);
}
}
void RunningState::physics_process(double delta) {
Vector3 const current{this->get_body()->get_velocity()};
Vector3 const desired{this->get_body()->get_desired_velocity()};
float const dot{current.dot(desired)};
float const speed_delta{desired.is_zero_approx() || dot < 0.f // if we're stopping or making a full turnaround
? float(this->get_body()->get_stopping_deceleration() * delta) // deceleration
: (current.is_zero_approx() // if we're starting from standstill
? this->get_body()->get_start_speed() // startup boost
: float(this->get_body()->get_acceleration() * delta) // in-motion velocity
)
};
this->get_body()->set_velocity(current.move_toward(desired, speed_delta));
}
void RunningState::state_exited() {
this->get_body()->get_model()->look_at(this->get_body()->get_global_position() - this->get_body()->get_velocity());
this->lean_modifier = 0.f;
}
PlayerState::StateID SplitStepState::get_next_state() const {
if(!this->get_body()->is_on_floor()) {
return FallingState::get_class_static();
} else {
return this->timer <= 0.0
? RunningState::get_class_static()
: self_type::get_class_static();
}
}
void SplitStepState::state_entered() {
this->last_velocity = this->get_body()->get_velocity();
this->timer = this->get_body()->get_split_step_time();
this->get_body()->set_velocity(last_velocity.normalized() * this->get_body()->get_target_speed() * 0.75f);
this->get_body()->get_anim()->play("split-step");
}
void SplitStepState::process(double delta) {
this->timer -= delta;
this->get_body()->set_velocity(this->get_body()->get_velocity()
.move_toward(Vector3(), this->get_body()->get_target_speed() / this->get_body()->get_split_step_stop_time() * delta));
}
void SplitStepState::state_exited() {
if(this->get_body()->is_on_floor()) {
Vector3 const desired_direction{this->get_body()->get_desired_direction()};
float const dot{this->last_velocity.normalized().dot(desired_direction)};
this->get_body()->set_velocity(dot > -0.8f
? desired_direction * MAX(last_velocity.length(), this->get_body()->get_step_boost())
: Vector3()
);
}
}
PlayerState::StateID FallingState::get_next_state() const {
if(this->get_body()->is_on_floor()) {
return RunningState::get_class_static();
} else {
return self_type::get_class_static();
}
}
void FallingState::state_entered() {
this->get_body()->get_anim()->play("falling", 0.1);
}
void FallingState::process(double delta) {
Vector3 const current{this->get_body()->get_velocity()};
Vector3 const flattened{current.x, 0.f, current.z};
this->get_body()->set_velocity((flattened - (flattened * 0.025f)) + Vector3{0.f, current.y - float(9.8 * delta), 0.f});
}
void PlayerStateMachine::_bind_methods() {
}
void PlayerStateMachine::_notification(int what) {
if(Engine::get_singleton()->is_editor_hint()) {
return;
}
switch(what) {
default:
return;
case NOTIFICATION_READY:
this->ready();
break;
case NOTIFICATION_PROCESS:
this->current_state->process(this->get_process_delta_time());
this->try_transition();
break;
case NOTIFICATION_PHYSICS_PROCESS:
this->current_state->physics_process(this->get_physics_process_delta_time());
break;
}
}
void PlayerStateMachine::ready() {
this->set_process(true);
this->set_physics_process(true);
this->body = Object::cast_to<PlayerBody>(this->get_parent());
this->add_state<StandingState>();
this->add_state<RunningState>();
this->add_state<SplitStepState>();
this->add_state<FallingState>();
}
void PlayerStateMachine::try_transition() {
PlayerState::StateID next{this->current_state->get_next_state()};
if(next != this->current_state->get_class()) {
this->current_state->state_exited();
this->current_state = this->states[next];
this->current_state->state_entered();
}
}

View file

@ -0,0 +1,93 @@
#ifndef PLAYER_STATES_H
#define PLAYER_STATES_H
#include "core/object/object.h"
#include "core/templates/hash_map.h"
#include "scene/main/node.h"
class PlayerBody;
class PlayerState : public Object {
GDCLASS(PlayerState, Object);
friend class PlayerStateMachine;
public:
typedef StringName StateID;
public:
virtual StateID get_next_state() const {
return this->get_class();
}
virtual void state_entered() {}
virtual void process(double delta) {}
virtual void physics_process(double delta) {}
virtual void state_exited() {}
PlayerBody *get_body() const;
private:
PlayerBody *body{nullptr};
};
class StandingState : public PlayerState {
GDCLASS(StandingState, PlayerState);
public:
virtual StateID get_next_state() const override;
virtual void state_entered() override;
virtual void physics_process(double delta) override;
};
class RunningState : public PlayerState {
GDCLASS(RunningState, PlayerState);
public:
virtual StateID get_next_state() const override;
virtual void state_entered() override;
virtual void process(double delta) override;
virtual void physics_process(double delta) override;
virtual void state_exited() override;
private:
float lean_modifier{0.f};
};
class SplitStepState : public PlayerState {
GDCLASS(SplitStepState, PlayerState);
public:
virtual StateID get_next_state() const override;
virtual void state_entered() override;
virtual void process(double delta) override;
virtual void state_exited() override;
private:
Vector3 last_velocity{0.f, 0.f, 0.f};
double timer{0.0};
};
class FallingState : public PlayerState {
GDCLASS(FallingState, PlayerState);
public:
virtual StateID get_next_state() const override;
virtual void state_entered() override;
virtual void process(double delta) override;
};
class PlayerStateMachine : public Node {
GDCLASS(PlayerStateMachine, Node);
static void _bind_methods();
void _notification(int what);
void ready();
void try_transition();
template <class TState>
void add_state();
PlayerBody *body{nullptr};
PlayerState *current_state{nullptr};
HashMap<PlayerState::StateID, PlayerState*> states;
};
template <class TState>
void PlayerStateMachine::add_state() {
PlayerState *state{new TState()};
state->body = this->body;
this->states.insert(TState::get_class_static(), state);
if(this->current_state == nullptr) {
this->current_state = state;
state->state_entered();
}
}
#endif // !PLAYER_STATES_H

View file

@ -1,11 +1,19 @@
#include "register_types.h"
#include "core/object/class_db.h"
#include "going/player_body.h"
#include "going/player_states.h"
void initialize_going_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
ClassDB::register_class<PlayerBody>();
ClassDB::register_class<PlayerState>();
ClassDB::register_class<StandingState>();
ClassDB::register_class<RunningState>();
ClassDB::register_class<SplitStepState>();
ClassDB::register_class<PlayerStateMachine>();
}
void uninitialize_going_module(ModuleInitializationLevel p_level) {