252 lines
8.9 KiB
C++
252 lines
8.9 KiB
C++
#include "player_states.h"
|
|
#include "core/config/engine.h"
|
|
#include "core/math/math_funcs.h"
|
|
#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 {
|
|
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);
|
|
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) {
|
|
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));
|
|
}
|
|
|
|
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()};
|
|
if(!this->get_body()->is_on_floor()) {
|
|
return FallingState::get_class_static();
|
|
} else if(Vector2{velocity.x, velocity.z}.is_zero_approx() && desired.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_lean(double delta) {
|
|
Vector3 const current{this->get_body()->get_velocity()};
|
|
if(!current.is_zero_approx()) {
|
|
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 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() - forward, 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) + Vector3{0.f, -0.01f, 0.f});
|
|
this->process_lean(delta);
|
|
}
|
|
|
|
void RunningState::state_exited() {
|
|
Vector3 const velocity{this->get_body()->get_velocity()};
|
|
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 {
|
|
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 {
|
|
return self_type::get_class_static();
|
|
}
|
|
}
|
|
|
|
void SplitStepState::state_entered() {
|
|
this->jump = false;
|
|
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));
|
|
this->jump |= Input::get_singleton()->is_action_pressed("jump");
|
|
}
|
|
|
|
void SplitStepState::state_exited() {
|
|
Vector3 desired_direction{0.f, 0.f, 0.f};
|
|
if(this->jump) {
|
|
desired_direction = Vector3{last_velocity.x, 0.f, last_velocity.z}.normalized();
|
|
} else if(this->get_body()->is_on_floor()) {
|
|
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->game_over_timer = 0.0;
|
|
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.015f)) + Vector3{0.f, current.y - float(9.8 * delta), 0.f});
|
|
if(this->can_game_over_falling) {
|
|
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 FallingState::state_exited() {
|
|
RenderingServer::get_singleton()->global_shader_parameter_set(this->game_over_param, 0.0);
|
|
this->can_game_over_falling = true;
|
|
}
|
|
|
|
PlayerState::StateID JumpingState::get_next_state() const {
|
|
if(this->get_body()->get_velocity().y <= 0.f) {
|
|
return FallingState::get_class_static();
|
|
} else {
|
|
return self_type::get_class_static();
|
|
}
|
|
}
|
|
|
|
void JumpingState::state_entered() {
|
|
Vector3 const current{this->get_body()->get_velocity()};
|
|
Vector2 const impulse{this->get_body()->get_jump_impulse()};
|
|
this->get_body()->set_velocity( (
|
|
Vector3{current.x * impulse.x, impulse.y, current.z * impulse.x}
|
|
));
|
|
this->get_body()->get_anim()->play("jump");
|
|
}
|
|
|
|
void JumpingState::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.015f)) + 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<FallingState>();
|
|
this->add_state<StandingState>();
|
|
this->add_state<RunningState>();
|
|
this->add_state<SplitStepState>();
|
|
this->add_state<JumpingState>();
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|