Smoother movement

Interpolation & Extrapolation still todo.
This commit is contained in:
2025-01-15 02:11:27 -05:00
parent d26237762e
commit a86ac432fb
8 changed files with 271 additions and 61 deletions

View File

@@ -43,6 +43,7 @@ public:
[[nodiscard]] bool ShouldTick() const;
[[nodiscard]] float TickDeltaTime() const;
[[nodiscard]] float FrameDeltaTime() const;
[[nodiscard]] u8 GetTickRate() const;
/// @returns the number of microseconds since Jan 1st 1970 that the last tick was finished on.
[[nodiscard]] unsigned long LastTickTime() const;
public:

View File

@@ -7,28 +7,126 @@ namespace Engine {
}
// Movable Object.
// TODO currently assumes no mass.
class Engine::Movable {
protected:
// Assuming 0 radians is up.
float face_angle = 0.0f;
private:
float previous_decay = 0;
float previous_velocity = 0;
float previous_heading = 0;
float previous_face_angle = 0;
float previous_constant_force_heading = 0;
float previous_constant_force_velocity = 0;
float previous_friction = 0;
Vector2 previous_position = Vector2::Zero;
[[nodiscard]] inline float NormalizeRadians(float radians) const;
private:
float decay = 0;
float velocity = 0;
float heading = 0;
//float heading_rotational_velocity = 0;
//float heading_rotational_velocity_decay = 0;
float face_angle = 0;
//float face_angle_rotational_velocity = 0;
//float face_angle_rotational_velocity_decay = 0;
float constant_force_heading = 0;
float constant_force_velocity = 0;
float friction = 0;
Vector2 position = {0, 0};
public:
// Movements independent of the face_angle.
void MoveX(float speed);
void MoveY(float speed);
void Move(float angle_rad, float speed);
protected:
/// Instantly changes the direction of travel.
/// @note Radians, Zero is interpreted as to the right.
void SetHeading(float heading_rad);
// Movements dependent on face angle.
void MoveForward(float speed);
void MoveBackward(float speed);
void MoveLeft(float speed);
void MoveRight(float speed);
/** Instantly changes the heading of a constant force to be applied
* to this movable after everything else, Like gravity. */
/// @note Radians, Zero is interpreted as to the right.
void SetConstantForceHeading(float heading_rad);
void Rotate(float speed);
void SetRotation(float new_rotation);
/// Instantly changes the amount constant velocity For ex, Gravity.
void SetConstantForce(float new_velocity);
/// Instantly changes the rate at which this object naturally shows down.
/// @note 0 never loses speed, 1 loses speed at the beginning of the next tick.
void SetDecay(float new_decay);
/// Instantly changes the amount of velocity.
void SetVelocity(float new_velocity);
/// Instantly changes the amount of friction
/// @note Zero is no friction. 1 is full friction.
void SetFriction(float new_friction);
/// Instantly move from the current position to a new position.
/// @param reset Whether the other values should be preserved.
void SetPosition(const Vector2& new_position, bool reset = false);
void SetVisualRotation(float rad_rotation);
public:
[[nodiscard]] float GetRotation() const;
/// @returns The direction of travel in radians.
/// @note Radians, Zero is interpreted as to the right.
[[nodiscard]] float GetHeading() const;
/// @returns The rate at which this object will naturally slow down.
/// @note 0 never loses speed, 1 loses speed on the next update.
[[nodiscard]] float GetDecay() const;
/// @returns The current velocity / speed.
[[nodiscard]] float GetVelocity() const;
/// @returns The current friction.
/// @note Zero is no friction. 1 is full friction.
[[nodiscard]] float GetFriction() const;
/// @returns The current position.
[[nodiscard]] Vector2 GetPosition() const;
/// @returns The direction of travel the constant force will move this object in.
/// @note Radians, Zero is interpreted as to the right.
[[nodiscard]] float GetConstantForceHeading() const;
/// @returns The current velocity / speed.
[[nodiscard]] float GetConstantForceVelocity() const;
/// @returns The direction we're *visually* facing.
/// @note Radians, Zero is interpreted as to the right.
[[nodiscard]] float GetVisualRotation() const;
public:
explicit Movable(const Vector2& position, float rad_rotation = 0.0f) : position(position), face_angle(rad_rotation) {}
/// Advance state.
void Move(const u8& tick_rate);
/// Adds velocity in a given direction.
/// @param heading_rad The heading in which to apply the velocity.
/// @param vel The velocity to apply.
/// @note Velocities will smoothly combine.
void AddVelocity(float heading_rad, float vel);
/// @returns the relative direction in radians for the forward.
[[nodiscard]] float HeadingRelativeForward() const;
[[nodiscard]] float FaceRelativeForward() const;
/// @returns The direction in radians for the back.
[[nodiscard]] float FaceRelativeBackward() const;
[[nodiscard]] float HeadingRelativeBackward() const;
/// @returns The relative direction in radians for left.
[[nodiscard]] float FaceRelativeLeft() const;
[[nodiscard]] float HeadingRelativeLeft() const;
[[nodiscard]] float FaceRelativeRight() const;
[[nodiscard]] float HeadingRelativeRight() const;
//virtual Movable Extrapolate(const unsigned long& last_tick_time, const unsigned long& target_time);
public:
[[nodiscard]] static float WorldUp();
[[nodiscard]] static float WorldDown();
[[nodiscard]] static float WorldLeft();
[[nodiscard]] static float WorldRight();
/// @returns an interpolation of this Movable.
//virtual Movable Interpolate(unsigned long previous_tick_time, u8 tick_rate, unsigned long now);
explicit Movable(const Vector2& position, float face_angle = 0.0f) : position(position), face_angle(face_angle) {}
};

View File

@@ -11,5 +11,6 @@ public:
void Update() final;
public:
explicit Box(const Vector2& position, unsigned int layer = 1, float rad_rotation = 0.0f) : Engine::InstancedSprite(position, layer, "assets/sprites/Re3D.png",
{0.5, 0.5},rad_rotation, Colors::White, nullptr, "assets/sprites/alpha_mask.png") {}
{0.5, 0.5},rad_rotation, Colors::White, nullptr, "assets/sprites/alpha_mask.png")
{ SetDecay(0.10f); SetVisualRotation(WorldRight()); SetPosition({200, 200}, false); }
};

View File

@@ -177,3 +177,7 @@ unsigned long Scene::LastTickTime() const {
return last_tick_time;
}
u8 Scene::GetTickRate() const {
return tick_rate;
}

View File

@@ -1,59 +1,162 @@
#include <Engine/types/entity/mixins/Movable.h>
#include <Engine/Globals.h>
// TODO Improve the design such that in mixins we wouldn't have to get Globals::CurrentScene
// Maybe pass in the delta time as a parameter or something idk.
using namespace J3ML;
void Engine::Movable::MoveX(float speed) {
position.x = position.x + (speed * Globals::CurrentScene->TickDeltaTime());
using namespace Engine;
float Movable::NormalizeRadians(float radians) const {
radians = std::fmod(radians, Math::Pi * 2);
if (radians < 0)
radians += Math::Pi * 2;
return radians;
}
void Engine::Movable::MoveY(float speed) {
position.y = position.y + (speed * Globals::CurrentScene->TickDeltaTime());
void Movable::SetHeading(float new_heading) {
heading = NormalizeRadians(new_heading);
}
void Engine::Movable::Move(float angle_rad, float speed) {
position.x = position.x + (speed * Globals::CurrentScene->TickDeltaTime()) * Math::Cos(angle_rad);
position.y = position.y + (speed * Globals::CurrentScene->TickDeltaTime()) * Math::Sin(angle_rad);
float Movable::GetHeading() const {
return heading;
}
void Engine::Movable::MoveForward(float speed) {
Move(face_angle, speed);
Vector2 Movable::GetPosition() const {
return position;
}
void Engine::Movable::MoveBackward(float speed) {
speed = -speed;
position.x = position.x + (speed * Globals::CurrentScene->TickDeltaTime()) * Math::Cos(face_angle);
position.y = position.y + (speed * Globals::CurrentScene->TickDeltaTime()) * Math::Sin(face_angle);
float Movable::GetDecay() const {
return decay;
}
void Engine::Movable::MoveLeft(float speed) {
position.x = position.x + (speed * Globals::CurrentScene->TickDeltaTime()) * -Math::Sin(face_angle);
position.y = position.y + (speed * Globals::CurrentScene->TickDeltaTime()) * Math::Cos(face_angle);
float Movable::GetVelocity() const {
return velocity;
}
void Engine::Movable::MoveRight(float speed) {
position.x = position.x + (speed * Globals::CurrentScene->TickDeltaTime()) * Math::Sin(face_angle);
position.y = position.y + (speed * Globals::CurrentScene->TickDeltaTime()) * -Math::Cos(face_angle);
float Movable::GetFriction() const {
return friction;
}
void Engine::Movable::SetRotation(float new_face_angle) {
face_angle = new_face_angle;
if (face_angle < 0)
face_angle = fmod(face_angle + Math::Pi * 2, Math::Pi * 2);
else if (face_angle >= 2 * M_PI)
face_angle = fmod(face_angle, Math::Pi * 2);
void Movable::SetDecay(float new_decay) {
decay = std::clamp(new_decay, 0.0f, 1.0f);
}
void Engine::Movable::Rotate(float speed) {
SetRotation(face_angle + speed * Globals::CurrentScene->TickDeltaTime());
void Movable::SetFriction(float new_friction) {
friction = std::clamp(new_friction, 0.0f, 1.0f);
}
float Engine::Movable::GetRotation() const {
void Movable::SetVelocity(float new_velocity) {
velocity = new_velocity;
}
void Movable::SetPosition(const Vector2& new_position, bool reset) {
position = new_position;
if (reset) {
heading = 0;
velocity = 0;
friction = 0;
decay = 0;
}
}
void Movable::SetConstantForceHeading(float heading_rad) {
constant_force_heading = heading_rad;
}
void Movable::SetConstantForce(float new_velocity) {
constant_force_velocity = new_velocity;
}
float Movable::GetConstantForceHeading() const {
return constant_force_heading;
}
float Movable::GetConstantForceVelocity() const {
return constant_force_velocity;
}
void Movable::SetVisualRotation(float rad_rotation) {
face_angle = rad_rotation;
}
float Movable::GetVisualRotation() const {
return face_angle;
}
Vector2 Engine::Movable::GetPosition() const {
return position;
}
void Movable::Move(const u8& tick_rate) {
previous_decay = decay;
previous_velocity = velocity;
previous_heading = heading;
previous_face_angle = face_angle;
previous_constant_force_heading = constant_force_heading;
previous_constant_force_velocity = velocity;
previous_friction = friction;
previous_position = position;
// Advance.
position.x += ((Math::Cos(heading) * velocity) + (Math::Cos(constant_force_heading) * constant_force_velocity)) * (1.f / tick_rate);
position.y += ((Math::Sin(heading) * velocity) + (Math::Sin(constant_force_heading) * constant_force_velocity)) * (1.f / tick_rate);
// Apply decay.
velocity = velocity * (1 - decay);
// Apply friction.
velocity = velocity * (1 - friction);
}
void Movable::AddVelocity(float heading_rad, float vel) {
heading_rad = NormalizeRadians(heading_rad);
Vector2 v = {velocity * Math::Cos(heading), velocity * Math::Sin(heading)};
Vector2 add = {vel * Math::Cos(heading_rad), vel * Math::Sin(heading_rad)};
Vector2 result = {v.x + add.x, v.y + add.y};
velocity = Math::Sqrt(result.x * result.x + result.y * result.y);
heading = NormalizeRadians(Math::Atan2(result.y, result.x));
}
float Movable::HeadingRelativeForward() const {
return heading;
}
float Movable::FaceRelativeForward() const {
return face_angle;
}
float Movable::FaceRelativeBackward() const {
return NormalizeRadians(face_angle + Math::Pi);
}
float Movable::HeadingRelativeBackward() const {
return NormalizeRadians(heading + Math::Pi);
}
float Movable::FaceRelativeLeft() const {
return NormalizeRadians(face_angle - Math::HalfPi);
}
float Movable::HeadingRelativeLeft() const {
return NormalizeRadians(heading - Math::HalfPi);
}
float Movable::FaceRelativeRight() const {
return NormalizeRadians(face_angle + Math::HalfPi);
}
float Movable::HeadingRelativeRight() const {
return NormalizeRadians(heading + Math::HalfPi);
}
float Movable::WorldUp() {
return -Math::Pi / 2;
}
float Movable::WorldDown() {
return Math::Pi / 2;
}
float Movable::WorldLeft() {
return Math::Pi;
}
float Movable::WorldRight() {
return 0;
}

View File

@@ -11,9 +11,9 @@ void Engine::Sprite::Render() {
auto* a = GetAlphaMask();
if (t && !a)
return J2D::DrawSprite(t, GetRenderPosition(), face_angle, origin, scale, base_color);
return J2D::DrawSprite(t, GetRenderPosition(), GetVisualRotation(), origin, scale, base_color);
if (t && a)
return J2D::DrawSprite(t, a, GetRenderPosition(), face_angle, origin, scale, base_color);
return J2D::DrawSprite(t, a, GetRenderPosition(), GetVisualRotation(), origin, scale, base_color);
}
Texture* Engine::Sprite::GetTexture() const {
@@ -29,7 +29,7 @@ void Engine::Sprite::SetAlphaMask(Texture* new_alpha_mask) {
}
Vector2 Engine::Sprite::GetRenderPosition() const {
return position - (origin * GetTexture()->GetDimensions());
return GetPosition() - (origin * GetTexture()->GetDimensions());
}
void Engine::Sprite::SetOrigin(const Vector2& new_origin) {

View File

@@ -6,12 +6,12 @@ using namespace Engine;
void Trigger::Render() {
if (ShouldRender()) {
auto size = GetBounds().maxPoint - GetBounds().minPoint;
J2D::OutlineRect(Colors::Red, position, size);
J2D::OutlineRect(Colors::Red, GetPosition(), size);
}
}
AABB2D Trigger::GetBounds() const {
return { position, position + size };
return { GetPosition(), GetPosition() + size };
}

View File

@@ -3,11 +3,14 @@
void Game::Box::Update() {
if (Globals::Window->IsKeyDown(Keys::W))
MoveY(-500);
AddVelocity(WorldUp(), 100);
if (Globals::Window->IsKeyDown(Keys::S))
MoveY(500);
AddVelocity(WorldDown(), 100);
if (Globals::Window->IsKeyDown(Keys::A))
MoveX(-500);
AddVelocity(WorldLeft(), 100);
if (Globals::Window->IsKeyDown(Keys::D))
MoveX(500);
AddVelocity(WorldRight(), 100);
if (Globals::Window->IsKeyDown(Keys::Space))
AddVelocity(HeadingRelativeBackward(), GetVelocity());
Move(GetScene()->GetTickRate());
}