|
@@ -1,93 +1,291 @@
|
|
|
#include "Game.h"
|
|
|
-#include "rendering/Renderer.h"
|
|
|
-#include "utils/StringBuffer.h"
|
|
|
+#include "Engine.h"
|
|
|
+#include "input/Controller.h"
|
|
|
+#include "utils/List.h"
|
|
|
#include "utils/Utils.h"
|
|
|
|
|
|
-Game::Game(Controller& c, const Clock& fps, const Clock& tps, const Size& size)
|
|
|
- : controller(c), fps(fps), tps(tps), size(size), physicsToggle(true),
|
|
|
- player(Vector2(50.0f, 50.0f), 5.0f) {
|
|
|
+struct Line final {
|
|
|
+ Vector2 a;
|
|
|
+ Vector2 b;
|
|
|
+};
|
|
|
+
|
|
|
+static List<Line> lines;
|
|
|
+
|
|
|
+static constexpr float SPEED = 2.5f;
|
|
|
+static constexpr float GRAVITY = 0.5f;
|
|
|
+static constexpr float COLLISION_STEP = 0.005f;
|
|
|
+
|
|
|
+static Vector2 windowSize;
|
|
|
+static Vector2 lastPosition;
|
|
|
+static Vector2 position{150.0f, 50.0f};
|
|
|
+static Vector2 size{50.0f, 50.0f};
|
|
|
+static Vector2 velocity;
|
|
|
+static Vector2 acceleration;
|
|
|
+static Vector2 drag;
|
|
|
+static float mass = 5.0f;
|
|
|
+static bool onGround = false;
|
|
|
+static float jumpPower = 0.0f;
|
|
|
+static float steepness = 0.0f;
|
|
|
+
|
|
|
+static bool physicsToggle = true;
|
|
|
+
|
|
|
+static Vector2 waterPosition;
|
|
|
+static Vector2 waterSize;
|
|
|
+
|
|
|
+static void addForce(const Vector2& force) {
|
|
|
+ acceleration += force / mass;
|
|
|
}
|
|
|
|
|
|
-void Game::tick() {
|
|
|
- if(controller.start.wasReleased()) {
|
|
|
- physicsToggle = !physicsToggle;
|
|
|
- }
|
|
|
- player.preTick();
|
|
|
+static void addMovement() {
|
|
|
+ physicsToggle = physicsToggle ^ Controller::start.wasReleased();
|
|
|
+ float movement = Controller::right.isDown() - Controller::left.isDown();
|
|
|
+ float actualSpeed = SPEED * (1.0f - steepness * onGround);
|
|
|
if(physicsToggle) {
|
|
|
-
|
|
|
- if(controller.a.isDown() && player.onGround) {
|
|
|
- player.jumpTicks = 10;
|
|
|
- player.onGround = false;
|
|
|
- }
|
|
|
- if(controller.right.isDown()) {
|
|
|
- player.addForce(Vector2(2.5f, 0.0f));
|
|
|
- }
|
|
|
- if(controller.left.isDown()) {
|
|
|
- player.addForce(Vector2(-2.5f, 0.0f));
|
|
|
- }
|
|
|
- if(player.jumpTicks > 0) {
|
|
|
- player.jumpTicks--;
|
|
|
- player.addForce(Vector2(0.0f, 10.0f));
|
|
|
+ addForce(Vector2(actualSpeed, 0.0f) * movement);
|
|
|
+ } else {
|
|
|
+
|
|
|
+
|
|
|
+ float terminalFactor = 1.0f / ((1.0 - drag[0]) * drag[0]);
|
|
|
+ velocity[0] = (actualSpeed / mass) * terminalFactor * movement;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static bool isIn(const Vector2& posA, const Vector2& sizeA, const Vector2& posB,
|
|
|
+ const Vector2& sizeB) {
|
|
|
+ Vector2 maxA = posA + sizeA;
|
|
|
+ Vector2 maxB = posB + sizeB;
|
|
|
+ return posA[0] < maxB[0] && maxA[0] > posB[0] && posA[1] < maxB[1] &&
|
|
|
+ maxA[1] > posB[1];
|
|
|
+}
|
|
|
+
|
|
|
+float ERROR = 1.0f / 512.0f;
|
|
|
+
|
|
|
+static bool isBetween(float y, float y1, float y2) {
|
|
|
+ return y >= std::min(y1, y2) && y <= std::max(y1, y2);
|
|
|
+}
|
|
|
+
|
|
|
+static bool compareFloats(float a, float b) {
|
|
|
+ return std::abs(a - b) < ERROR;
|
|
|
+}
|
|
|
+
|
|
|
+static bool intersect(float x11, float y11, float x12, float y12, float x21,
|
|
|
+ float y21, float x22, float y22) {
|
|
|
+ if(compareFloats(x11, x12)) {
|
|
|
+ if(compareFloats(x21, x22)) {
|
|
|
+ return false;
|
|
|
+ } else {
|
|
|
+ if(!isBetween(x11, x21, x22)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ float k = (y21 - y22) / (x21 - x22);
|
|
|
+ float d = y22 - k * x22;
|
|
|
+ float y = d + x11 * k;
|
|
|
+ return isBetween(y, y11, y12) && isBetween(y, y21, y22);
|
|
|
}
|
|
|
- player.jumpTicks *= controller.a.isDown();
|
|
|
-
|
|
|
- player.addForce(Vector2(-player.velocity[0] * 0.5f, 0.0f));
|
|
|
} else {
|
|
|
- player.velocity[0] = 0.0f;
|
|
|
- if(controller.a.isDown() && player.onGround) {
|
|
|
- player.velocity[1] = 15.0f;
|
|
|
- player.onGround = false;
|
|
|
+ if(compareFloats(x21, x22)) {
|
|
|
+ if(!isBetween(x21, x11, x12)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ float k = (y11 - y12) / (x11 - x12);
|
|
|
+ float d = y12 - k * x12;
|
|
|
+ float y = d + x21 * k;
|
|
|
+ return isBetween(y, y11, y12) && isBetween(y, y21, y22);
|
|
|
+ } else {
|
|
|
+ float k1 = (y11 - y12) / (x11 - x12);
|
|
|
+ float k2 = (y21 - y22) / (x21 - x22);
|
|
|
+ if(compareFloats(k1, k2)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ float d1 = y12 - k1 * x12;
|
|
|
+ float d2 = y22 - k2 * x22;
|
|
|
+
|
|
|
+ float x = (d1 - d2) / (k2 - k1);
|
|
|
+ if(!isBetween(x, x11, x12) || !isBetween(x, x21, x22)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ float y = k1 * x + d1;
|
|
|
+ return isBetween(y, y11, y12) && isBetween(y, y21, y22);
|
|
|
}
|
|
|
- if(controller.right.isDown()) {
|
|
|
- player.velocity[0] = 5.0f;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static bool areColliding(const Line& a, const Line& b) {
|
|
|
+ return intersect(a.a[0], a.a[1], a.b[0], a.b[1], b.a[0], b.a[1], b.b[0],
|
|
|
+ b.b[1]);
|
|
|
+}
|
|
|
+
|
|
|
+static bool doesPlayerCollide() {
|
|
|
+ Vector2 posX = position + Vector2(size[0], 0.0f);
|
|
|
+ Vector2 posY = position + Vector2(0.0f, size[1]);
|
|
|
+ Vector2 posXY = position + size;
|
|
|
+ for(const Line& line : lines) {
|
|
|
+ if(areColliding(line, {position, posX}) ||
|
|
|
+ areColliding(line, {position, posY}) ||
|
|
|
+ areColliding(line, {posX, posXY}) ||
|
|
|
+ areColliding(line, {posY, posXY})) {
|
|
|
+ return true;
|
|
|
}
|
|
|
- if(controller.left.isDown()) {
|
|
|
- player.velocity[0] = -5.0f;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+static float slopeSteepness() {
|
|
|
+ Line left{position - Vector2(0.0f, 10.0f * COLLISION_STEP),
|
|
|
+ position + Vector2(0.0f, size[1])};
|
|
|
+ Line right{position + Vector2(size[0], -10.0f * COLLISION_STEP),
|
|
|
+ position + size};
|
|
|
+ float min = 100.0f;
|
|
|
+ float value = 100.0f;
|
|
|
+ for(const Line& line : lines) {
|
|
|
+ if(areColliding(line, left) || areColliding(line, right)) {
|
|
|
+ float f = (line.a[1] - line.b[1]) / (line.a[0] - line.b[0]);
|
|
|
+ if(std::abs(f) < min) {
|
|
|
+ min = std::abs(f);
|
|
|
+ value = f;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
- player.tick();
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- if(player.position[1] < 0.0f) {
|
|
|
- player.position[1] = 0.0f;
|
|
|
- player.velocity[1] = 0.0f;
|
|
|
- player.onGround = true;
|
|
|
+ return value;
|
|
|
+}
|
|
|
+
|
|
|
+static void applyDrag() {
|
|
|
+ waterPosition = windowSize * Vector2(0.5f, 0.0f);
|
|
|
+ waterSize = windowSize * Vector2(0.5f, 0.125f);
|
|
|
+ drag = Vector2(0.1f, 0.1f);
|
|
|
+ if(isIn(position, size, waterPosition, waterSize)) {
|
|
|
+ drag *= 4.0f;
|
|
|
}
|
|
|
-
|
|
|
- if(player.position[0] + player.size[0] > size.width) {
|
|
|
- player.position[0] = size.width - player.size[0];
|
|
|
-
|
|
|
+ acceleration -= velocity * drag;
|
|
|
+}
|
|
|
+
|
|
|
+static void applyGravity() {
|
|
|
+ acceleration -= Vector2(0.0f, GRAVITY);
|
|
|
+}
|
|
|
+
|
|
|
+static void handleJump() {
|
|
|
+ bool justJumped = false;
|
|
|
+ if(Controller::a.isDown() && onGround) {
|
|
|
+ jumpPower = 15.0f;
|
|
|
+ onGround = false;
|
|
|
+ justJumped = true;
|
|
|
}
|
|
|
-
|
|
|
- if(player.position[0] < 0.0f) {
|
|
|
- player.position[0] = 0.0f;
|
|
|
-
|
|
|
+ addForce(Vector2(0.0f, jumpPower));
|
|
|
+ jumpPower *= 0.9f;
|
|
|
+ jumpPower *= Controller::a.isDown() && (velocity[1] > 0.0f || justJumped);
|
|
|
+}
|
|
|
+
|
|
|
+void Game::tick() {
|
|
|
+ windowSize = Vector2(static_cast<float>(Engine::getSize().width),
|
|
|
+ static_cast<float>(Engine::getSize().height));
|
|
|
+ lastPosition = position;
|
|
|
+ acceleration = Vector2();
|
|
|
+
|
|
|
+ applyGravity();
|
|
|
+ addMovement();
|
|
|
+ handleJump();
|
|
|
+ applyDrag();
|
|
|
+
|
|
|
+ if(steepness != 100.0f) {
|
|
|
+ if(steepness < -1.0f) {
|
|
|
+ acceleration[0] += 0.1f;
|
|
|
+ } else if(steepness > 1.0f) {
|
|
|
+ acceleration[0] -= 0.1f;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ lines.clear();
|
|
|
+ lines.add({{0.0f, 0.0f}, windowSize * Vector2(1.0f, 0.0f)});
|
|
|
+ lines.add({{0.0f, 0.0f}, windowSize * Vector2(0.0f, 1.0f)});
|
|
|
+ lines.add({windowSize, windowSize * Vector2(1.0f, 0.0f)});
|
|
|
+ lines.add(
|
|
|
+ {windowSize * Vector2(0.75f, 0.0f), windowSize * Vector2(1.0f, 0.5f)});
|
|
|
+ lines.add({{0.0f, 30.0f}, {400.0f, 0.0f}});
|
|
|
+ lines.add({{0.0f, 60.0f}, {300.0f, 0.0f}});
|
|
|
+ lines.add({{0.0f, 90.0f}, {200.0f, 0.0f}});
|
|
|
+ lines.add({{0.0f, 120.0f}, {100.0f, 0.0f}});
|
|
|
+ lines.add({{100.0f, 180.0f}, {200.0f, 180.0f}});
|
|
|
+ lines.add({{200.0f, 180.0f}, {600.0f, 380.0f}});
|
|
|
+
|
|
|
+ velocity += acceleration;
|
|
|
+
|
|
|
+ onGround = false;
|
|
|
+ Vector2 energy = velocity;
|
|
|
+ while(energy[0] != 0.0f || energy[1] != 0.0f) {
|
|
|
+ for(int i = 0; i < 2; i++) {
|
|
|
+ if(energy[i] != 0.0f) {
|
|
|
+ float old = position[i];
|
|
|
+ if(energy[i] > COLLISION_STEP) {
|
|
|
+ position[i] += COLLISION_STEP;
|
|
|
+ energy[i] -= COLLISION_STEP;
|
|
|
+ } else if(energy[i] < -COLLISION_STEP) {
|
|
|
+ position[i] -= COLLISION_STEP;
|
|
|
+ energy[i] += COLLISION_STEP;
|
|
|
+ } else {
|
|
|
+ position[i] += energy[i];
|
|
|
+ energy[i] = 0.0f;
|
|
|
+ }
|
|
|
+ if(doesPlayerCollide()) {
|
|
|
+ if(i == 0) {
|
|
|
+ float oldY = position[1];
|
|
|
+ position[1] += COLLISION_STEP;
|
|
|
+ if(!doesPlayerCollide()) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ position[1] = oldY;
|
|
|
+ }
|
|
|
+ energy[i] = 0.0f;
|
|
|
+ velocity[i] = 0.0f;
|
|
|
+ position[i] = old;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ steepness = slopeSteepness();
|
|
|
+ onGround = std::abs(steepness) < 1.0f;
|
|
|
+ if(onGround) {
|
|
|
+ velocity[1] = 0.0f;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-void Game::render(float lag, Renderer& r) const {
|
|
|
+void Game::render(float lag, Renderer& r) {
|
|
|
r.translateTo(0.0f, 0.0f)
|
|
|
.scale(1.0f, -1.0f)
|
|
|
- .translateY(size.height)
|
|
|
+ .translateY(Engine::getSize().height)
|
|
|
.update();
|
|
|
- Vector2 pos = Utils::interpolate(player.lastPosition, player.position, lag);
|
|
|
- r.drawRectangle(pos, player.size, Color4(0xFF, 0x00, 0x00, 0xFF));
|
|
|
+ r.drawRectangle(waterPosition, waterSize, Color4(0x00, 0x00, 0xFF, 0xFF));
|
|
|
+
|
|
|
+ Vector2 pos = Utils::interpolate(lastPosition, position, lag);
|
|
|
+ r.drawRectangle(pos, size, Color4(0xFF, 0x00, 0x00, 0xFF));
|
|
|
+
|
|
|
+ for(const Line& line : lines) {
|
|
|
+ Vector2 dir = line.b - line.a;
|
|
|
+ Vector2 normal(dir[1], -dir[0]);
|
|
|
+ normal.normalize();
|
|
|
+ normal *= 2.0f;
|
|
|
+ Vector2 ap = line.a + normal;
|
|
|
+ Vector2 bp = line.b + normal;
|
|
|
+ Vector2 an = line.a - normal;
|
|
|
+ Vector2 bn = line.b - normal;
|
|
|
+ r.drawTriangle(ap, bp, an, Color4(0xFF, 0xFF, 0xFF, 0xFF));
|
|
|
+ r.drawTriangle(bp, an, bn, Color4(0xFF, 0xFF, 0xFF, 0xFF));
|
|
|
+ }
|
|
|
|
|
|
r.translateTo(0.0f, 0.0f).update();
|
|
|
r.setStringSize(2);
|
|
|
- StringBuffer<100> s("FPS: ");
|
|
|
- s.append(fps.getUpdatesPerSecond()).append(" ");
|
|
|
- s.append("%6.2f", tps.getUpdatesPerSecond()).append(" ");
|
|
|
- s.append(physicsToggle ? "Force Physics" : "Velocity Physics");
|
|
|
+
|
|
|
float y = 10.0f;
|
|
|
+ y = r.drawString(10.0f, y,
|
|
|
+ physicsToggle ? "Force Physics" : "Velocity Physics");
|
|
|
+ StringBuffer<100> s("a = ");
|
|
|
+ s.append(acceleration).append(" before collision");
|
|
|
y = r.drawString(10.0f, y, s);
|
|
|
- s.clear().append("a = ").append(player.acceleration);
|
|
|
+ s.clear().append("v = ").append(velocity).append(" after collision");
|
|
|
y = r.drawString(10.0f, y, s);
|
|
|
- s.clear().append("v = ").append(player.velocity);
|
|
|
+ s.clear().append("Ground = ").append(onGround);
|
|
|
y = r.drawString(10.0f, y, s);
|
|
|
}
|
|
|
|
|
|
-bool Game::isRunning() const {
|
|
|
- return !controller.select.isDown();
|
|
|
-}
|
|
|
+bool Game::isRunning() {
|
|
|
+ return !Controller::select.isDown();
|
|
|
+}
|