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]] bool ShouldTick() const;
[[nodiscard]] float TickDeltaTime() const; [[nodiscard]] float TickDeltaTime() const;
[[nodiscard]] float FrameDeltaTime() 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. /// @returns the number of microseconds since Jan 1st 1970 that the last tick was finished on.
[[nodiscard]] unsigned long LastTickTime() const; [[nodiscard]] unsigned long LastTickTime() const;
public: public:

View File

@@ -7,28 +7,126 @@ namespace Engine {
} }
// Movable Object. // Movable Object.
// TODO currently assumes no mass.
class Engine::Movable { class Engine::Movable {
protected: private:
// Assuming 0 radians is up. float previous_decay = 0;
float face_angle = 0.0f; 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}; Vector2 position = {0, 0};
public: protected:
// Movements independent of the face_angle. /// Instantly changes the direction of travel.
void MoveX(float speed); /// @note Radians, Zero is interpreted as to the right.
void MoveY(float speed); void SetHeading(float heading_rad);
void Move(float angle_rad, float speed);
// Movements dependent on face angle. /** Instantly changes the heading of a constant force to be applied
void MoveForward(float speed); * to this movable after everything else, Like gravity. */
void MoveBackward(float speed); /// @note Radians, Zero is interpreted as to the right.
void MoveLeft(float speed); void SetConstantForceHeading(float heading_rad);
void MoveRight(float speed);
void Rotate(float speed); /// Instantly changes the amount constant velocity For ex, Gravity.
void SetRotation(float new_rotation); 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: 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; [[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: 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; void Update() final;
public: public:
explicit Box(const Vector2& position, unsigned int layer = 1, float rad_rotation = 0.0f) : Engine::InstancedSprite(position, layer, "assets/sprites/Re3D.png", 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; 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/types/entity/mixins/Movable.h>
#include <Engine/Globals.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; using namespace J3ML;
void Engine::Movable::MoveX(float speed) { using namespace Engine;
position.x = position.x + (speed * Globals::CurrentScene->TickDeltaTime());
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) { void Movable::SetHeading(float new_heading) {
position.y = position.y + (speed * Globals::CurrentScene->TickDeltaTime()); heading = NormalizeRadians(new_heading);
} }
void Engine::Movable::Move(float angle_rad, float speed) { float Movable::GetHeading() const {
position.x = position.x + (speed * Globals::CurrentScene->TickDeltaTime()) * Math::Cos(angle_rad); return heading;
position.y = position.y + (speed * Globals::CurrentScene->TickDeltaTime()) * Math::Sin(angle_rad);
} }
void Engine::Movable::MoveForward(float speed) { Vector2 Movable::GetPosition() const {
Move(face_angle, speed); return position;
} }
void Engine::Movable::MoveBackward(float speed) { float Movable::GetDecay() const {
speed = -speed; return decay;
position.x = position.x + (speed * Globals::CurrentScene->TickDeltaTime()) * Math::Cos(face_angle);
position.y = position.y + (speed * Globals::CurrentScene->TickDeltaTime()) * Math::Sin(face_angle);
} }
void Engine::Movable::MoveLeft(float speed) { float Movable::GetVelocity() const {
position.x = position.x + (speed * Globals::CurrentScene->TickDeltaTime()) * -Math::Sin(face_angle); return velocity;
position.y = position.y + (speed * Globals::CurrentScene->TickDeltaTime()) * Math::Cos(face_angle);
} }
void Engine::Movable::MoveRight(float speed) { float Movable::GetFriction() const {
position.x = position.x + (speed * Globals::CurrentScene->TickDeltaTime()) * Math::Sin(face_angle); return friction;
position.y = position.y + (speed * Globals::CurrentScene->TickDeltaTime()) * -Math::Cos(face_angle);
} }
void Engine::Movable::SetRotation(float new_face_angle) { void Movable::SetDecay(float new_decay) {
face_angle = new_face_angle; decay = std::clamp(new_decay, 0.0f, 1.0f);
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 Engine::Movable::Rotate(float speed) { void Movable::SetFriction(float new_friction) {
SetRotation(face_angle + speed * Globals::CurrentScene->TickDeltaTime()); 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; return face_angle;
} }
Vector2 Engine::Movable::GetPosition() const { void Movable::Move(const u8& tick_rate) {
return position; 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(); auto* a = GetAlphaMask();
if (t && !a) 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) 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 { Texture* Engine::Sprite::GetTexture() const {
@@ -29,7 +29,7 @@ void Engine::Sprite::SetAlphaMask(Texture* new_alpha_mask) {
} }
Vector2 Engine::Sprite::GetRenderPosition() const { Vector2 Engine::Sprite::GetRenderPosition() const {
return position - (origin * GetTexture()->GetDimensions()); return GetPosition() - (origin * GetTexture()->GetDimensions());
} }
void Engine::Sprite::SetOrigin(const Vector2& new_origin) { void Engine::Sprite::SetOrigin(const Vector2& new_origin) {

View File

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

View File

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