Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add physics component #29

Merged
merged 3 commits into from
Jan 30, 2024
Merged

add physics component #29

merged 3 commits into from
Jan 30, 2024

Conversation

swagween
Copy link
Contributor

add a component namespace and create a Physics class. the player gets a physics component, and its position is updated based on the physics state.

@swagween swagween requested a review from karnkaul January 30, 2024 14:51
m_acceleration.y += m_gravity * dt;
m_velocity = (m_velocity + (m_acceleration / m_mass) * dt) * m_friction;

m_velocity.x = m_velocity.x > max_velocity.x ? max_velocity.x : m_velocity.x;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there must be a better way to constrain the velocity

Copy link
Member

@karnkaul karnkaul left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor changes like stronger typing, using glm::clamp, constructor vs no constructor, etc.

for (bave::Seconds t{}; t <= dt; t += ft) {
integrate(t.count());
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a subtle issue here: some residue of dt that's less than ft remains un-integrated on every tick. It would be better to accumulate that in a member and add it to the next dt.

Copy link
Contributor Author

@swagween swagween Jan 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would this fix work?

for (bave::Seconds t{ m_accumulator }; t <= dt; t += ft) {
	integrate(t);
	if (t + ft >= dt) { m_accumulator = dt - t; }
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, something like this would be better:

void Physics::tick(bave::Seconds dt) {
	// ...
	for (dt += m_residue; dt > ft; dt -= ft) {
		integrate(ft);
	}
	m_residue = dt;
}

}
}

void Physics::integrate(float dt) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should use bave::Seconds here too, it's ambiguous what unit dt is (seconds or milliseconds etc).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment on lines 21 to 24
m_velocity.x = m_velocity.x > max_velocity.x ? max_velocity.x : m_velocity.x;
m_velocity.y = m_velocity.y > max_velocity.y ? max_velocity.y : m_velocity.y;
m_velocity.x = m_velocity.x < -max_velocity.x ? -max_velocity.x : m_velocity.x;
m_velocity.y = m_velocity.y < -max_velocity.y ? -max_velocity.y : m_velocity.y;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
m_velocity.x = m_velocity.x > max_velocity.x ? max_velocity.x : m_velocity.x;
m_velocity.y = m_velocity.y > max_velocity.y ? max_velocity.y : m_velocity.y;
m_velocity.x = m_velocity.x < -max_velocity.x ? -max_velocity.x : m_velocity.x;
m_velocity.y = m_velocity.y < -max_velocity.y ? -max_velocity.y : m_velocity.y;
m_velocity = glm::clamp(m_velocity, -max_velocity, max_velocity);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment on lines 7 to 8
static constexpr glm::vec2 max_velocity{1000.f, 1000.f};
static constexpr float tick_limit{0.03f}; //this should possibly go somewhere else and be globally accessible
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Globals in headers should not be static: that will introduce new storage for the global in each translation unit (cpp file that includes the header). Just constexpr is fine here, though if you want to be pedantic, inline constexpr.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment on lines 7 to 8
static constexpr glm::vec2 max_velocity{1000.f, 1000.f};
static constexpr float tick_limit{0.03f}; //this should possibly go somewhere else and be globally accessible
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This represents time and should use chrono units (bave::Seconds).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think 30ms is too little, 100ms is ok as the death-spiral-bail-out value.

Suggested change
static constexpr glm::vec2 max_velocity{1000.f, 1000.f};
static constexpr float tick_limit{0.03f}; //this should possibly go somewhere else and be globally accessible
constexpr glm::vec2 max_velocity_v{1000.f};
constexpr bave::Seconds tick_limit_v{100ms};

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed!

Comment on lines 14 to 20
glm::vec2 m_position{};
glm::vec2 m_velocity{};
glm::vec2 m_acceleration{};

glm::vec2 m_friction{};
float m_gravity{};
float m_mass{};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If all this is public, a constructor isn't needed, users can just do:

auto physics = Physics{
  .m_friction = /* ... */,
  .m_gravity = /* ... */,
  .m_mass = /* ... */,
};

As a style thing, this is one reason why I don't prefix m_ for public members.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, shouldn't gravity be shared amongst all physics components, as opposed to being an individual member on each instance?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you think friction, position, velocity etc. should be public? or should I write getters for them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree intuitively about gravity, but being able to change it might make it easier to simulate light-weightedness (for certain enemies or drops, etc).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you think friction, position, velocity etc. should be public? or should I write getters for them?

It depends on whether you want them to be "set once and then it's read-only" or "set at your discretion". The former will take the values in the constructor and provide getters (but not setters). Latter would get rid of the constructor and keep the members public.

Copy link
Member

@karnkaul karnkaul Jan 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this isn't working because it says a designator cannot be used with a non-aggregate type (even though I deleted the constructor). is my syntax wrong here?

component::Physics m_physics = {.friction{0.999f, 0.999f}, .gravity{-1000.f}, .mass{1.f}};

Is this a member variable? Try removing the = sign.
Alternatively, try component::Physics m_physics = component::Physics{...}.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, it's a member variable of Player. neither of those solutions worked either. here's the new physics.hpp if the issue lies there:

namespace component {

constexpr glm::vec2 max_velocity_v{1000.f};
constexpr bave::Seconds tick_limit_v{100ms};

class Physics {

	bave::Seconds m_residue{};

  public:

	glm::vec2 position{};
	glm::vec2 velocity{};
	glm::vec2 acceleration{};

	glm::vec2 friction{};
	float gravity{};
	float mass{};

	void tick(bave::Seconds const dt);
	void integrate(bave::Seconds dt);
};

}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok I realized it's the m_residue being private

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so, I guess I need to define a constructor then, because m_residue being public feels pretty wrong

Copy link
Member

@karnkaul karnkaul Jan 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, all the fields of aggregate types must be public.
A constructor that uses default values will be best then, that way both of these would work:

auto custom = Physics{/* ... */};
auto default_ = Physics{};

@swagween swagween requested a review from karnkaul January 30, 2024 17:03
Copy link
Member

@karnkaul karnkaul left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also recommend removing Player::m_vel and related logic since that's no longer used.

Comment on lines 7 to 12
constexpr glm::vec2 default_friction{0.999f};
constexpr float default_gravity{-1000.f};
constexpr float default_mass{1.f};

constexpr glm::vec2 max_velocity_v{1000.f};
constexpr bave::Seconds tick_limit_v{100ms};
Copy link
Member

@karnkaul karnkaul Jan 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd move these constants into class Physics (as static constexpr) since they are specific to it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment on lines +8 to 10
m_sprite.transform.position = m_physics.position;

handle_wall_collision();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since handle_wall_collision clamps the player sprite's position to the bottom of the screen, after that point m_physics.position and m_sprite.transform.position keep diverging indefinitely, effectively pinning the player to the ground no matter what (jumping from -1000 to -990 will still get clamped to -360 or whatever).

For time being I'd suggest setting m_physics.position = m_sprite.transform.position after the clamping, to synchronize them again.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed!

glm::vec2 m_friction{};
float m_gravity{};
float m_mass{};
Physics(glm::vec2 fric = default_friction, float grav = default_gravity, float m = default_mass) : friction(fric), gravity(grav), mass(m) {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use longer names, doesn't matter if the init goes into the next line.

Suggested change
Physics(glm::vec2 fric = default_friction, float grav = default_gravity, float m = default_mass) : friction(fric), gravity(grav), mass(m) {}
Physics(glm::vec2 friction = default_friction, float gravity = default_gravity, float mass = default_mass) : friction(friction), gravity(gravity), mass(mass) {}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, I always thought the names had to be different otherwise the compiler would get confused. good to know, and fixed!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha yeah, somehow in the initializer list it can disambiguate just fine. Once you enter the constructor body you'll need explicit this->foo = foo.

@karnkaul karnkaul linked an issue Jan 30, 2024 that may be closed by this pull request
@swagween swagween requested a review from karnkaul January 30, 2024 17:33
Comment on lines 9 to 14
static constexpr glm::vec2 default_friction{0.99f};
static constexpr float default_gravity{-1000.f};
static constexpr float default_mass{1.f};

class Physics {
static constexpr glm::vec2 max_velocity_v{1000.f};
static constexpr bave::Seconds tick_limit_v{100ms};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: some constants use a _v suffix (for "value", standard library convention), some don't.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it!

Comment on lines +24 to +25
if (position.x < bounce_rect.top_left().x || position.x > bounce_rect.bottom_right().x) { m_physics.velocity.x *= -0.9f; }
if (position.y > bounce_rect.top_left().y || position.y < bounce_rect.bottom_right().y) { m_physics.velocity.y *= -0.9f; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious: what's with the 0.9... instead of 1.0? 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when it's 1.0, it bounces against the ground for a really long time. I think of the 0.9 as the object transferring 10% of its energy into the ground/wall. it's just a needless temporary cosmetic fix, since player collision will probably be completely static anyway :)

@swagween swagween merged commit d975f25 into main Jan 30, 2024
2 checks passed
@swagween swagween deleted the lana_dev branch January 30, 2024 18:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

implement gravity to the player (and possible other mobs?)
2 participants