From b8d62ce8d2f7c8f38aa9c52ab8d7dd32dc3aba64 Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Fri, 13 Mar 2015 11:14:01 -0400 Subject: Fixed my timestep! http://gafferongames.com/game-physics/fix-your-timestep/ --- src/components.cpp | 62 ++++++++++++++++++++++++++++++------------------------ src/components.h | 10 ++++----- src/entity.cpp | 4 ++-- src/entity.h | 4 ++-- src/game.cpp | 35 ++++++++++++++++-------------- src/game.h | 4 ++-- 6 files changed, 63 insertions(+), 56 deletions(-) diff --git a/src/components.cpp b/src/components.cpp index ad0f501..cf82979 100644 --- a/src/components.cpp +++ b/src/components.cpp @@ -73,10 +73,10 @@ void PhysicsBodyComponent::receive(Game&, Entity&, const Message& msg) { if (msg.type == Message::Type::walkLeft) { - velocity.first = -1.5; + velocity.first = -90; } else if (msg.type == Message::Type::walkRight) { - velocity.first = 1.5; + velocity.first = 90; } else if (msg.type == Message::Type::stopWalking) { velocity.first = 0.0; @@ -89,13 +89,30 @@ void PhysicsBodyComponent::receive(Game&, Entity&, const Message& msg) } } -void PhysicsBodyComponent::tick(Game&, Entity& entity) +void PhysicsBodyComponent::tick(Game&, Entity& entity, double dt) { - velocity.first += accel.first; - velocity.second += accel.second; + // RK4 integration + auto a = std::make_pair(velocity.first, velocity.second); + auto b = std::make_pair(velocity.first + a.first*dt*0.5, velocity.second + a.second*dt*0.5); + auto c = std::make_pair(velocity.first + b.first*dt*0.5, velocity.second + b.second*dt*0.5); + auto d = std::make_pair(velocity.first + c.first*dt, velocity.second + c.second*dt); - entity.position.first += velocity.first; - entity.position.second += velocity.second; + double dxxdt = 1.0 / 6.0 * (a.first + 2.0*(b.first + c.first) + d.first); + double dxydt = 1.0 / 6.0 * (a.second + 2.0*(b.second + c.second) + d.second); + + entity.position.first += dxxdt * dt; + entity.position.second += dxydt * dt; + + velocity.first += accel.first * dt; + velocity.second += accel.second * dt; + + // Terminal velocity +#define TERMINAL_VELOCITY_X (2 * TILE_WIDTH * FRAMES_PER_SECOND) +#define TERMINAL_VELOCITY_Y (2 * TILE_HEIGHT * FRAMES_PER_SECOND) + if (velocity.first < -TERMINAL_VELOCITY_X) velocity.first = -TERMINAL_VELOCITY_X; + if (velocity.first > TERMINAL_VELOCITY_X) velocity.first = TERMINAL_VELOCITY_X; + if (velocity.second < -TERMINAL_VELOCITY_Y) velocity.second = -TERMINAL_VELOCITY_Y; + if (velocity.second > TERMINAL_VELOCITY_Y) velocity.second = TERMINAL_VELOCITY_Y; } void PhysicsBodyComponent::detectCollision(Game& game, Entity& entity, Entity& collider, std::pair old_position) @@ -192,9 +209,9 @@ void PlayerSpriteComponent::receive(Game&, Entity&, const Message& msg) PlayerPhysicsComponent::PlayerPhysicsComponent() { - jump_velocity = JUMP_VELOCITY(TILE_HEIGHT*4.5, 0.3*FRAMES_PER_SECOND); - jump_gravity = JUMP_GRAVITY(TILE_HEIGHT*4.5, 0.3*FRAMES_PER_SECOND); - jump_gravity_short = JUMP_GRAVITY(TILE_HEIGHT*3.5, 0.233*FRAMES_PER_SECOND); + jump_velocity = JUMP_VELOCITY(TILE_HEIGHT*4.5, 0.3); + jump_gravity = JUMP_GRAVITY(TILE_HEIGHT*4.5, 0.3); + jump_gravity_short = JUMP_GRAVITY(TILE_HEIGHT*3.5, 0.233); accel.second = jump_gravity_short; } @@ -203,11 +220,11 @@ void PlayerPhysicsComponent::receive(Game&, Entity& entity, const Message& msg) { if (msg.type == Message::Type::walkLeft) { - velocity.first = -1.5; + velocity.first = -90; direction = -1; } else if (msg.type == Message::Type::walkRight) { - velocity.first = 1.5; + velocity.first = 90; direction = 1; } else if (msg.type == Message::Type::stopWalking) { @@ -255,7 +272,7 @@ void PlayerPhysicsComponent::receive(Game&, Entity& entity, const Message& msg) } } -void PlayerPhysicsComponent::tick(Game& game, Entity& entity) +void PlayerPhysicsComponent::tick(Game& game, Entity& entity, double dt) { // If frozen, do nothing if (frozen) @@ -268,10 +285,10 @@ void PlayerPhysicsComponent::tick(Game& game, Entity& entity) { if (direction < 0) { - velocity.first = -1.5; + velocity.first = -90; } else if (direction > 0) { - velocity.first = 1.5; + velocity.first = 90; } } @@ -281,21 +298,10 @@ void PlayerPhysicsComponent::tick(Game& game, Entity& entity) accel.second = jump_gravity_short; } - // Apply acceleration - velocity.first += accel.first; - velocity.second += accel.second; - - // Terminal velocity - if (velocity.first < -16) velocity.first = -16; - if (velocity.first > 16) velocity.first = 16; - if (velocity.second < -16) velocity.second = -16; - if (velocity.second > 16) velocity.second = 16; - // Do the movement std::pair old_position = entity.position; - entity.position.first += velocity.first; - entity.position.second += velocity.second; - + PhysicsBodyComponent::tick(game, entity, dt); + // Check for collisions game.detectCollision(entity, old_position); } diff --git a/src/components.h b/src/components.h index f421529..c66db47 100644 --- a/src/components.h +++ b/src/components.h @@ -19,10 +19,10 @@ class UserMovementComponent : public Component { class PhysicsBodyComponent : public Component { public: void receive(Game& game, Entity& entity, const Message& msg); - void tick(Game& game, Entity& entity); + void tick(Game& game, Entity& entity, double dt); void detectCollision(Game& game, Entity& entity, Entity& collider, std::pair old_position); - private: + protected: std::pair velocity; std::pair accel; }; @@ -40,15 +40,13 @@ class PlayerSpriteComponent : public Component { bool dying = false; }; -class PlayerPhysicsComponent : public Component { +class PlayerPhysicsComponent : public PhysicsBodyComponent { public: PlayerPhysicsComponent(); - void tick(Game& game, Entity& entity); + void tick(Game& game, Entity& entity, double dt); void receive(Game& game, Entity& entity, const Message& msg); private: - std::pair velocity; - std::pair accel; double jump_velocity; double jump_gravity; double jump_gravity_short; diff --git a/src/entity.cpp b/src/entity.cpp index 38ddffe..2b6cd7f 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -13,11 +13,11 @@ void Entity::send(Game& game, const Message& msg) } } -void Entity::tick(Game& game) +void Entity::tick(Game& game, double dt) { for (auto component : components) { - component->tick(game, *this); + component->tick(game, *this, dt); } } diff --git a/src/entity.h b/src/entity.h index 04772e9..e22fd10 100644 --- a/src/entity.h +++ b/src/entity.h @@ -37,7 +37,7 @@ class Entity { public: void addComponent(std::shared_ptr c); void send(Game& game, const Message& msg); - void tick(Game& game); + void tick(Game& game, double dt); void input(Game& game, int key, int action); void render(Game& game, Texture& buffer); void detectCollision(Game& game, Entity& collider, std::pair old_position); @@ -53,7 +53,7 @@ class Component { public: virtual void receive(Game&, Entity&, const Message&) {} virtual void render(Game&, Entity&, Texture&) {} - virtual void tick(Game&, Entity&) {} + virtual void tick(Game&, Entity&, double) {} virtual void input(Game&, Entity&, int, int) {} virtual void detectCollision(Game&, Entity&, Entity&, std::pair) {} }; diff --git a/src/game.cpp b/src/game.cpp index b2de5e9..a494c5f 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -50,17 +50,14 @@ void Game::execute(GLFWwindow* window) Texture buffer(GAME_WIDTH, GAME_HEIGHT); double lastTime = glfwGetTime(); - int nbFrames = 0; + const double dt = 0.01; + double accumulator = 0.0; + while (!(shouldQuit || glfwWindowShouldClose(window))) { double currentTime = glfwGetTime(); - nbFrames++; - if (currentTime - lastTime >= 1.0) - { - printf("%f ms/frame\n", 1000.0/double(nbFrames)); - nbFrames = 0; - lastTime += 1.0; - } + double frameTime = currentTime - lastTime; + lastTime = currentTime; // Should we load a new world? if (newWorld) @@ -74,23 +71,29 @@ void Game::execute(GLFWwindow* window) glfwPollEvents(); // Tick! - for (auto entity : entities) + accumulator += frameTime; + while (accumulator >= dt) { - entity->tick(*this); + for (auto entity : entities) + { + entity->tick(*this, dt); + } + + accumulator -= dt; } // Do any scheduled tasks for (auto& task : scheduled) { - task.first--; + task.first -= frameTime; - if (task.first == 0) + if (task.first <= 0) { task.second(); } } - scheduled.remove_if([] (std::pair> value) { return value.first == 0; }); + scheduled.remove_if([] (std::pair> value) { return value.first <= 0; }); // Do rendering buffer.fill(buffer.entirety(), 0, 0, 0); @@ -133,9 +136,9 @@ void Game::saveGame(const Map& map, std::pair position) save = {&map, position}; } -void Game::schedule(int frames, std::function&& callback) +void Game::schedule(double time, std::function&& callback) { - scheduled.emplace_front(frames, callback); + scheduled.emplace_front(time, callback); } void Game::playerDie(Entity& player, const Map& curMap) @@ -145,7 +148,7 @@ void Game::playerDie(Entity& player, const Map& curMap) playSound("../res/Hit_Hurt5.wav", 0.25); - schedule(FRAMES_PER_SECOND * 0.75, [&] () { + schedule(0.75, [&] () { if (&curMap != save.map) { loadMap(*(save.map)); diff --git a/src/game.h b/src/game.h index 4f2f3df..c7c2625 100644 --- a/src/game.h +++ b/src/game.h @@ -30,7 +30,7 @@ class Game { void loadMap(const Map& map); void detectCollision(Entity& collider, std::pair old_position); void saveGame(const Map& map, std::pair position); - void schedule(int frames, std::function&& callback); + void schedule(double time, std::function&& callback); void playerDie(Entity& player, const Map& curMap); private: @@ -43,7 +43,7 @@ class Game { Map m{"../maps/embarass.txt"}; Map m2{"../maps/second.txt"}; Savefile save; - std::list>> scheduled; + std::list>> scheduled; bool shouldQuit = false; }; -- cgit 1.4.1