feat: implemented initial checkpoint mechanic

This commit is contained in:
Sara 2025-06-10 23:28:02 +02:00
parent 4ef4212d26
commit 37b7b94566
7 changed files with 147 additions and 12 deletions

View file

@ -0,0 +1,65 @@
#include "checkpoint.h"
#include "core/config/engine.h"
#include "going/player_body.h"
#include "macros.h"
void Checkpoint::_bind_methods() {
BIND_PROPERTY(Variant::VECTOR3, location);
BIND_PROPERTY(Variant::BOOL, can_jump);
BIND_PROPERTY(Variant::BOOL, can_bash);
}
void Checkpoint::set_location(Transform3D location) {
this->location = location;
}
Transform3D Checkpoint::get_location() const {
return this->location;
}
void Checkpoint::set_can_jump(bool can_jump) {
this->can_jump = can_jump;
}
bool Checkpoint::get_can_jump() const {
return this->can_jump;
}
void Checkpoint::set_can_bash(bool can_bash) {
this->can_bash = can_bash;
}
bool Checkpoint::get_can_bash() const {
return this->can_bash;
}
void Checkpoint::load(PlayerBody *body) const {
body->set_global_transform(this->location);
body->set_can_jump(this->can_jump);
// body->set_can_bash(this->can_bash);
}
void Checkpoint::save(PlayerBody *body) {
this->set_location(body->get_global_transform_interpolated());
this->set_can_jump(body->get_can_jump());
// self->set_can_bash(body->get_can_bash());
}
Ref<Checkpoint> Checkpoint::save_new(PlayerBody *body) {
Ref<Checkpoint> self{memnew(Checkpoint)};
self->save(body);
return self;
}
void CheckpointArea::_notification(int what) {
if(!Engine::get_singleton()->is_editor_hint() && what == NOTIFICATION_READY) {
this->connect(this->body_entered, callable_mp(this, &self_type::on_body_entered));
}
}
void CheckpointArea::on_body_entered(Node3D *body) {
if(PlayerBody *player{Object::cast_to<PlayerBody>(body)}) {
player->save_checkpoint();
}
}

View file

@ -0,0 +1,37 @@
#ifndef CHECKPOINT_H
#define CHECKPOINT_H
#include "core/io/resource.h"
#include "core/math/transform_3d.h"
#include "scene/3d/node_3d.h"
#include "scene/3d/physics/area_3d.h"
class PlayerBody;
class Checkpoint : public Resource {
GDCLASS(Checkpoint, Resource);
static void _bind_methods();
public:
void set_location(Transform3D location);
Transform3D get_location() const;
void set_can_jump(bool can_jump);
bool get_can_jump() const;
void set_can_bash(bool can_bash);
bool get_can_bash() const;
void load(PlayerBody *body) const;
void save(PlayerBody *body);
static Ref<Checkpoint> save_new(PlayerBody *body);
private:
Transform3D location{};
bool can_jump{false}, can_bash{false};
};
class CheckpointArea : public Area3D {
GDCLASS(CheckpointArea, Area3D);
static void _bind_methods() {}
void _notification(int what);
void on_body_entered(Node3D *body);
StringName body_entered{"body_entered"};
};
#endif // !CHECKPOINT_H

View file

@ -2,10 +2,12 @@
#include "core/config/engine.h"
#include "core/input/input.h"
#include "core/math/math_funcs.h"
#include "core/object/class_db.h"
#include "macros.h"
#include "player_states.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")};
@ -16,6 +18,8 @@ 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);
ClassDB::bind_method(D_METHOD("save_checkpoint"), &self_type::save_checkpoint);
ClassDB::bind_method(D_METHOD("load_checkpoint"), &self_type::load_checkpoint);
BIND_PROPERTY(Variant::FLOAT, stopping_deceleration);
BIND_PROPERTY(Variant::FLOAT, start_speed);
@ -58,6 +62,7 @@ void PlayerBody::enter_tree() {
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);
this->last_checkpoint = Checkpoint::save_new(this);
}
void PlayerBody::process(double delta) {
@ -74,6 +79,15 @@ void PlayerBody::physics_process(double delta) {
this->move_and_slide();
}
void PlayerBody::save_checkpoint() {
this->last_checkpoint->save(this);
}
void PlayerBody::load_checkpoint() {
this->last_checkpoint->load(this);
this->state->force_state<FallingState>();
}
Vector3 PlayerBody::get_desired_direction() const {
Basis const global{this->camera->get_global_basis()};
Vector3 forward{global.get_column(2)};

View file

@ -1,9 +1,11 @@
#ifndef PLAYER_BODY_H
#define PLAYER_BODY_H
#include "going/checkpoint.h"
#include "scene/3d/camera_3d.h"
#include "scene/3d/physics/character_body_3d.h"
#include "scene/animation/animation_player.h"
class PlayerStateMachine;
class PlayerBody : public CharacterBody3D {
GDCLASS(PlayerBody, CharacterBody3D);
@ -14,6 +16,8 @@ class PlayerBody : public CharacterBody3D {
void physics_process(double delta);
public:
void save_checkpoint();
void load_checkpoint();
Vector3 get_desired_direction() const;
Vector3 get_desired_velocity() const;
Vector2 get_movement_input() const;
@ -71,6 +75,8 @@ private:
float model_lean_speed{0.25f};
double game_over_speed{1.0/4.0};
bool can_jump{false};
Ref<Checkpoint> last_checkpoint{nullptr};
PlayerStateMachine *state{nullptr};
public:
static char *const split_step_action;
static char *const move_left_action;

View file

@ -30,7 +30,7 @@ void StandingState::process(double delta) {
this->game_over_timer += delta * this->get_body()->get_game_over_speed();
if(this->game_over_timer > 1.0) {
RenderingServer::get_singleton()->global_shader_parameter_set(this->game_over_param, 0.0);
SceneTree::get_singleton()->reload_current_scene();
this->get_body()->load_checkpoint();
} else {
RenderingServer::get_singleton()->global_shader_parameter_set(this->game_over_param, float(this->game_over_timer));
this->game_over_timer = MIN(this->game_over_timer, 1.f);
@ -78,7 +78,7 @@ void RunningState::process_lean(double delta) {
Vector3 const up{Vector3{0.0, 1.0, 0.0} + Vector3{cross.x, 0.f, cross.y} * this->lean_modifier};
Vector3 const forward{current.x, 0.f, current.z};
if(!forward.is_zero_approx()) {
this->get_body()->get_model()->look_at(this->get_body()->get_global_position() - current, up);
this->get_body()->get_model()->look_at(this->get_body()->get_global_position() - forward, up);
}
}
}
@ -100,18 +100,20 @@ void RunningState::physics_process(double delta) {
void RunningState::state_exited() {
Vector3 const velocity{this->get_body()->get_velocity()};
Vector3 const velocity_flat{velocity.x, 0.f, velocity.z};
if(!velocity_flat.is_zero_approx()) {
this->get_body()->get_model()->look_at(this->get_body()->get_global_position() - velocity_flat);
Vector3 velocity_flat{velocity.x, 0.f, velocity.z};
if(velocity_flat.is_zero_approx()) {
velocity_flat = this->get_body()->get_model()->get_global_basis().get_column(2);
}
this->get_body()->get_model()->look_at(this->get_body()->get_global_position() - velocity_flat);
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 if(this->get_body()->get_can_jump() && this->jump && this->timer <= 0.0) {
bool const jump_input{this->get_body()->get_can_jump() && this->jump};
if(jump_input && (this->timer <= 0.0 || !this->get_body()->is_on_floor())) {
return JumpingState::get_class_static();
} else if(!this->get_body()->is_on_floor()) {
return FallingState::get_class_static();
} else if(this->timer <= 0.0) {
return RunningState::get_class_static();
} else {
@ -169,7 +171,7 @@ void FallingState::process(double delta) {
this->game_over_timer += delta * this->get_body()->get_game_over_speed();
if(this->game_over_timer > 1.0) {
RenderingServer::get_singleton()->global_shader_parameter_set(this->game_over_param, 0.0);
SceneTree::get_singleton()->reload_current_scene();
this->get_body()->load_checkpoint();
} else {
RenderingServer::get_singleton()->global_shader_parameter_set(this->game_over_param, float(this->game_over_timer));
this->game_over_timer = MIN(this->game_over_timer, 1.f);

View file

@ -92,7 +92,10 @@ class PlayerStateMachine : public Node {
void try_transition();
template <class TState>
void add_state();
public:
template <class TState>
void force_state();
private:
PlayerBody *body{nullptr};
PlayerState *current_state{nullptr};
HashMap<PlayerState::StateID, PlayerState*> states;
@ -100,7 +103,7 @@ class PlayerStateMachine : public Node {
template <class TState>
void PlayerStateMachine::add_state() {
PlayerState *state{new TState()};
PlayerState *state{memnew(TState)};
state->body = this->body;
this->states.insert(TState::get_class_static(), state);
if(this->current_state == nullptr) {
@ -109,4 +112,9 @@ void PlayerStateMachine::add_state() {
}
}
template <class TState>
void PlayerStateMachine::force_state() {
this->states[TState::get_class_static()];
}
#endif // !PLAYER_STATES_H

View file

@ -1,6 +1,7 @@
#include "register_types.h"
#include "core/object/class_db.h"
#include "going/checkpoint.h"
#include "going/game_ui.h"
#include "going/player_body.h"
#include "going/player_states.h"
@ -18,6 +19,8 @@ void initialize_going_module(ModuleInitializationLevel p_level) {
ClassDB::register_class<JumpingState>();
ClassDB::register_class<PlayerStateMachine>();
ClassDB::register_class<GameUI>();
ClassDB::register_class<Checkpoint>();
ClassDB::register_class<CheckpointArea>();
}
void uninitialize_going_module(ModuleInitializationLevel p_level) {