feat: finishing touches

This commit is contained in:
Sara 2025-06-17 02:52:26 +02:00
parent cebae80b5c
commit 6d4a961423
39 changed files with 4028 additions and 2162 deletions

View file

@ -0,0 +1,67 @@
#include "game_over_effect.h"
#include "core/config/engine.h"
#include "core/typedefs.h"
#include "going/player_body.h"
void GameOverEffect::_bind_methods() {
}
void GameOverEffect::_notification(int what) {
if(Engine::get_singleton()->is_editor_hint()) {
return;
}
switch(what) {
default:
return;
case NOTIFICATION_READY:
this->ready();
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;
case NOTIFICATION_EXIT_TREE:
RenderingServer::get_singleton()->global_shader_parameter_set("game_over_percentage", this->game_over_percentage);
return;
}
}
void GameOverEffect::ready() {
this->set_process(true);
this->set_physics_process(true);
this->body = Object::cast_to<PlayerBody>(this->get_parent());
this->anim = Object::cast_to<AnimationPlayer>(this->get_node(NodePath("AnimationPlayer")));
this->camera = Object::cast_to<Camera3D>(this->get_node(NodePath("../Camera3D")));
this->anim->play("game_over", -1, 0.0);
this->set_visible(false);
}
void GameOverEffect::process(double delta) {
this->anim->seek(MIN(this->game_over_percentage * 2.f, 1.0f), true);
RenderingServer::get_singleton()->global_shader_parameter_set("game_over_percentage", this->game_over_percentage);
this->set_visible(this->game_over_percentage > 0.1);
if(this->game_over_percentage >= 1.0) {
this->body->load_checkpoint();
this->game_over_percentage = 0.0;
}
}
void GameOverEffect::physics_process(double delta) {
Vector3 const delta_position{this->get_global_position() - this->last_frame_point};
if(delta_position.length() / delta > this->game_over_max_delta) { // convert per-frame movement to approximate movement/second
this->game_over_percentage = 0.f;
} else {
this->game_over_percentage += delta * this->game_over_speed;
}
this->last_frame_point = this->get_global_position();
Vector3 const position{this->body->get_global_position()};
Vector3 camera_position{this->camera->get_global_position()};
camera_position.y = this->get_global_position().y;
this->set_global_position(position);
Vector3 const target{position - (camera_position - position)};
if(!target.is_equal_approx(position)) {
this->look_at(target);
}
}

View file

@ -0,0 +1,27 @@
#ifndef GAME_OVER_EFFECT_H
#define GAME_OVER_EFFECT_H
#include "scene/3d/camera_3d.h"
#include "scene/3d/node_3d.h"
#include "scene/animation/animation_player.h"
class PlayerBody;
class GameOverEffect : public Node3D {
GDCLASS(GameOverEffect, Node3D);
static void _bind_methods();
void _notification(int what);
void ready();
void process(double delta);
void physics_process(double delta);
private:
Vector3 last_frame_point{};
float game_over_max_delta{1.0};
float game_over_speed{1.0/2.0};
double game_over_percentage{0.f};
AnimationPlayer *anim{nullptr};
PlayerBody *body{nullptr};
Camera3D *camera{nullptr};
};
#endif // !GAME_OVER_EFFECT_H

View file

@ -16,6 +16,7 @@ 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() {
ADD_SIGNAL(MethodInfo("obstacle_broken"));
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);
@ -31,10 +32,10 @@ void PlayerBody::_bind_methods() {
BIND_PROPERTY(Variant::FLOAT, split_step_stop_time);
BIND_PROPERTY(Variant::FLOAT, bash_speed);
BIND_PROPERTY(Variant::FLOAT, bash_time);
BIND_PROPERTY(Variant::FLOAT, extra_bash_time);
BIND_PROPERTY(Variant::VECTOR2, jump_impulse);
BIND_PROPERTY(Variant::FLOAT, model_lean);
BIND_PROPERTY(Variant::FLOAT, model_lean_speed);
BIND_PROPERTY(Variant::FLOAT, game_over_speed);
BIND_PROPERTY(Variant::BOOL, can_jump);
BIND_PROPERTY(Variant::BOOL, can_bash);
}
@ -97,7 +98,7 @@ void PlayerBody::load_checkpoint() {
this->last_checkpoint->load(this);
this->force_update_transform();
this->set_velocity(Vector3{});
this->state->force_state<FallingState>();
this->state->_force_state<FallingState>();
}
Vector3 PlayerBody::get_desired_direction() const {
@ -210,6 +211,14 @@ double PlayerBody::get_bash_time() const {
return this->bash_time;
}
void PlayerBody::set_extra_bash_time(double value) {
this->extra_bash_time = value;
}
double PlayerBody::get_extra_bash_time() const {
return this->extra_bash_time;
}
void PlayerBody::set_jump_impulse(Vector2 value) {
this->jump_impulse = value;
}
@ -234,14 +243,6 @@ float PlayerBody::get_model_lean_speed() const {
return this->model_lean_speed;
}
void PlayerBody::set_game_over_speed(float value) {
this->game_over_speed = value;
}
float PlayerBody::get_game_over_speed() const {
return this->game_over_speed;
}
void PlayerBody::set_can_jump(bool value) {
this->can_jump = value;
}

View file

@ -47,14 +47,14 @@ public:
float get_bash_speed() const;
void set_bash_time(double value);
double get_bash_time() const;
void set_extra_bash_time(double value);
double get_extra_bash_time() const;
void set_jump_impulse(Vector2 value);
Vector2 get_jump_impulse() 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;
void set_game_over_speed(float value);
float get_game_over_speed() const;
void set_can_jump(bool value);
bool get_can_jump() const;
void set_can_bash(bool value);
@ -76,13 +76,13 @@ private:
double split_step_stop_time{0.5};
float bash_speed{100.f};
double bash_time{0.25};
double extra_bash_time{0.25};
Vector2 jump_impulse{5.f, 5.f};
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};
double game_over_speed{1.0/4.0};
bool can_bash{false};
bool can_jump{false};
Ref<Checkpoint> last_checkpoint{nullptr};

View file

@ -5,7 +5,6 @@
#include "core/typedefs.h"
#include "going/player_body.h"
#include "scene/main/scene_tree.h"
#include "servers/rendering_server.h"
PlayerBody *PlayerState::get_body() const {
@ -24,18 +23,6 @@ PlayerState::StateID StandingState::get_next_state() const {
void StandingState::state_entered() {
this->get_body()->get_anim()->play("RESET", 0.1);
this->game_over_timer = 0.0;
}
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);
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);
}
}
void StandingState::physics_process(double delta) {
@ -44,10 +31,6 @@ void StandingState::physics_process(double delta) {
this->get_body()->set_velocity(current.move_toward(Vector3(), speed_delta));
}
void StandingState::state_exited() {
RenderingServer::get_singleton()->global_shader_parameter_set(this->game_over_param, 0.0f);
}
PlayerState::StateID RunningState::get_next_state() const {
Vector3 const velocity{this->get_body()->get_velocity()};
Vector3 const desired{this->get_body()->get_desired_velocity()};
@ -202,6 +185,8 @@ void JumpingState::state_entered() {
Vector3{current.x * impulse.x, impulse.y, current.z * impulse.x}
));
this->get_body()->get_anim()->play("jump");
this->vfx = cast_to<Node3D>(this->get_body()->get_node(NodePath("character/jump_effect")));
this->vfx->set_visible(true);
}
void JumpingState::physics_process(double delta) {
@ -210,23 +195,33 @@ void JumpingState::physics_process(double delta) {
this->get_body()->set_velocity((flattened - (flattened * 0.015f)) + Vector3{0.f, current.y - float(9.8 * delta), 0.f});
}
void JumpingState::state_exited() {
this->vfx->set_visible(false);
}
PlayerState::StateID BashState::get_next_state() const {
if(!this->get_body()->is_on_floor()) {
return FallingState::get_class_static();
} else if (this->timer < 0.0) {
return StandingState::get_class_static();
} else if(Input::get_singleton()->is_action_just_pressed(PlayerBody::split_step_action)) {
return SplitStepState::get_class_static();
} else if(this->timer < 0.0) {
return RunningState::get_class_static();
}
return this->get_class();
}
void BashState::state_entered() {
this->get_body()->get_anim()->play("bash");
this->get_body()->connect("obstacle_broken", this->on_obstacle_broken_cal);
this->timer = this->get_body()->get_bash_time();
this->speed = this->get_body()->get_bash_speed();
(this->vfx = Object::cast_to<Node3D>(this->get_body()->get_node(this->vfx_path)))->set_visible(true);
}
void BashState::on_obstacle_broken() {
this->timer += this->get_body()->get_extra_bash_time();
}
void BashState::process(double delta) {
this->timer -= delta;
}
@ -245,13 +240,37 @@ void BashState::physics_process(double delta) {
void BashState::state_exited() {
this->vfx->set_visible(false);
if(!this->get_body()->is_on_floor()) {
this->get_body()->set_velocity(this->get_body()->get_velocity() / 2.f);
this->get_body()->set_velocity(this->get_body()->get_model()->get_global_basis().get_column(2) * this->get_body()->get_target_speed());
this->get_body()->disconnect("obstacle_broken", this->on_obstacle_broken_cal);
}
void VictoryState::state_entered() {
this->camera = cast_to<Camera3D>(this->get_body()->get_node(NodePath("Camera3D")));
this->camera->set_process(false);
this->camera->set_physics_process(false);
this->get_body()->get_node(NodePath("GameOverEffect"))->queue_free();
this->get_body()->get_anim()->play("victory");
this->camera_target = this->camera->get_global_position() - this->camera->get_global_basis().get_column(2) * 3.f;
}
void VictoryState::process(double delta) {
if(this->get_body()->get_velocity().length() < 0.05) {
this->get_body()->set_velocity(Vector3{});
}
float fov{this->camera->get_fov()};
this->camera->set_fov(Math::move_toward(fov, 30.f, 20.f * (float)delta));
}
void VictoryState::physics_process(double delta) {
Vector3 const velocity{this->get_body()->get_velocity() * 0.8};
this->get_body()->set_velocity(velocity + Vector3{0.f, -50.f, 0.f});
this->camera->look_at(this->camera_target);
this->camera_target.y = MIN(this->camera_target.y + delta * 0.2, 30.f);
}
void PlayerStateMachine::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_current_state"), &self_type::get_current_state);
ClassDB::bind_method(D_METHOD("force_state"), &self_type::force_state);
}
void PlayerStateMachine::_notification(int what) {
@ -284,6 +303,7 @@ void PlayerStateMachine::ready() {
this->add_state<SplitStepState>();
this->add_state<JumpingState>();
this->add_state<BashState>();
this->add_state<VictoryState>();
}
void PlayerStateMachine::try_transition() {
@ -298,3 +318,13 @@ void PlayerStateMachine::try_transition() {
StringName PlayerStateMachine::get_current_state() const {
return this->current_state->get_class();
}
void PlayerStateMachine::force_state(StringName name) {
if(this->states.has(name)) {
this->current_state->state_exited();
this->current_state = this->states[name];
this->current_state->state_entered();
} else {
ERR_FAIL_EDMSG("Attempt to force switch to state that is not registered");
}
}

View file

@ -3,6 +3,8 @@
#include "core/object/object.h"
#include "core/templates/hash_map.h"
#include "core/variant/callable.h"
#include "scene/3d/camera_3d.h"
#include "scene/main/node.h"
class PlayerBody;
@ -30,12 +32,7 @@ class StandingState : public 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:
double game_over_timer{0.0};
StringName game_over_param{"game_over_percentage"};
};
class RunningState : public PlayerState {
@ -81,6 +78,9 @@ public:
virtual StateID get_next_state() const override;
virtual void state_entered() override;
virtual void physics_process(double delta) override;
virtual void state_exited() override;
private:
Node3D *vfx{nullptr};
};
class BashState : public PlayerState {
@ -88,16 +88,29 @@ class BashState : public PlayerState {
public:
virtual StateID get_next_state() const override;
virtual void state_entered() override;
void on_obstacle_broken();
virtual void process(double delta) override;
virtual void physics_process(double delta) override;
virtual void state_exited() override;
private:
Callable on_obstacle_broken_cal{callable_mp(this, &self_type::on_obstacle_broken)};
NodePath vfx_path{"character/bash_attack"};
Node3D *vfx{nullptr};
double timer{0.0};
float speed{0.f};
};
class VictoryState : public PlayerState {
GDCLASS(VictoryState, PlayerState);
public:
virtual void state_entered() override;
virtual void process(double delta) override;
virtual void physics_process(double delta) override;
private:
Camera3D *camera{nullptr};
Vector3 camera_target{};
};
class PlayerStateMachine : public Node {
GDCLASS(PlayerStateMachine, Node);
static void _bind_methods();
@ -108,8 +121,9 @@ class PlayerStateMachine : public Node {
void add_state();
public:
StringName get_current_state() const;
void force_state(StringName name);
template <class TState>
void force_state();
void _force_state();
private:
PlayerBody *body{nullptr};
PlayerState *current_state{nullptr};
@ -128,12 +142,10 @@ void PlayerStateMachine::add_state() {
}
template <class TState>
void PlayerStateMachine::force_state() {
void PlayerStateMachine::_force_state() {
PlayerState::StateID next{TState::get_class_static()};
if(!next.is_empty()) {
this->current_state->state_exited();
this->current_state = this->states[TState::get_class_static()];
this->current_state->state_entered();
this->force_state(next);
}
}

View file

@ -2,9 +2,11 @@
#include "core/object/class_db.h"
#include "going/checkpoint.h"
#include "going/game_over_effect.h"
#include "going/game_ui.h"
#include "going/player_body.h"
#include "going/player_states.h"
#include "going/valley_root.h"
void initialize_going_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
@ -23,6 +25,8 @@ void initialize_going_module(ModuleInitializationLevel p_level) {
ClassDB::register_class<Checkpoint>();
ClassDB::register_class<CheckpointArea>();
ClassDB::register_class<ReloadArea>();
ClassDB::register_class<GameOverEffect>();
ClassDB::register_class<ValleyRoot>();
}
void uninitialize_going_module(ModuleInitializationLevel p_level) {

View file

@ -0,0 +1,29 @@
#include "valley_root.h"
#include "scene/gui/subviewport_container.h"
double ValleyRoot::render_factor{1.0};
void ValleyRoot::_bind_methods() {
ClassDB::bind_static_method("ValleyRoot", D_METHOD("set_render_factor", "value"), &self_type::set_render_factor);
}
void ValleyRoot::_notification(int what) {
if(Engine::get_singleton()->is_editor_hint()) {
return;
}
switch(what) {
case NOTIFICATION_READY:
this->ready();
break;
}
}
void ValleyRoot::ready() {
if(SubViewportContainer *container{cast_to<SubViewportContainer>(this->get_node(NodePath("SubViewportContainer")))}) {
container->set_stretch_shrink(this->render_factor);
}
}
void ValleyRoot::set_render_factor(double factor) {
self_type::render_factor = factor;
}

View file

@ -0,0 +1,16 @@
#ifndef VALLEY_ROOT_H
#define VALLEY_ROOT_H
#include "scene/main/node.h"
class ValleyRoot : public Node {
GDCLASS(ValleyRoot, Node);
static void _bind_methods();
void _notification(int what);
void ready();
private:
static double render_factor;
public:
static void set_render_factor(double factor);
};
#endif // !VALLEY_ROOT_H