Browse Source

new trait system, split Body into Body and Physics

opengl-renderer-broken
Martin Dørum 4 years ago
parent
commit
b2fd4178df

+ 10
- 6
core.mod/src/entities/ItemStackEntity.cc View File

@@ -10,9 +10,9 @@ ItemStackEntity::ItemStackEntity(
static std::uniform_real_distribution vy(-2.3f, -1.2f);

item_ = &ctx.world.getItem(item);
body_.pos_ = pos;
body_.pos_.y += 0.5 - body_.size_.y / 2;
body_.vel_ += Swan::Vec2{ vx(ctx.world.random_), vy(ctx.world.random_) };
body_.pos = pos;
body_.pos.y += 0.5 - body_.size.y / 2;
physics_.vel += Swan::Vec2{ vx(ctx.world.random_), vy(ctx.world.random_) };
}

ItemStackEntity::ItemStackEntity(const Swan::Context &ctx, const PackObject &obj):
@@ -26,10 +26,14 @@ void ItemStackEntity::draw(const Swan::Context &ctx, Swan::Win &win) {
SDL_Texture *tex = item_->image_.texture_.get();
Swan::TexColorMod darken(tex, 220, 220, 220);

win.showTexture(body_.pos_, tex, &rect,
win.showTexture(body_.pos, tex, &rect,
{ .hscale = 0.5, .vscale = 0.5 });
}

void ItemStackEntity::update(const Swan::Context &ctx, float dt) {
physics(ctx, dt, { .mass = MASS, .bounciness = 0.6 });
}

void ItemStackEntity::tick(const Swan::Context &ctx, float dt) {
despawn_timer_ -= dt;
if (despawn_timer_ <= 0)
@@ -37,13 +41,13 @@ void ItemStackEntity::tick(const Swan::Context &ctx, float dt) {
}

void ItemStackEntity::deserialize(const Swan::Context &ctx, const PackObject &obj) {
body_.pos_ = obj.at("pos").as<Swan::Vec2>();
body_.pos = obj.at("pos").as<Swan::Vec2>();
item_ = &ctx.world.getItem(obj.at("item").as<std::string>());
}

Swan::Entity::PackObject ItemStackEntity::serialize(const Swan::Context &ctx, msgpack::zone &zone) {
return {
{ "pos", msgpack::object(body_.pos_, zone) },
{ "pos", msgpack::object(body_.pos, zone) },
{ "tile", msgpack::object(item_->name_, zone) },
};
}

+ 3
- 3
core.mod/src/entities/ItemStackEntity.h View File

@@ -8,6 +8,7 @@ public:
ItemStackEntity(const Swan::Context &ctx, const PackObject &obj);

void draw(const Swan::Context &ctx, Swan::Win &win) override;
void update(const Swan::Context &ctx, float dt) override;
void tick(const Swan::Context &ctx, float dt) override;
void deserialize(const Swan::Context &ctx, const PackObject &obj) override;
PackObject serialize(const Swan::Context &ctx, msgpack::zone &zone) override;
@@ -16,10 +17,9 @@ private:
static constexpr float MASS = 80;
static constexpr Swan::Vec2 SIZE = Swan::Vec2(0.5, 0.5);
static constexpr float DESPAWN_TIME = 5 * 60;
static constexpr float BOUNCINESS = 0.6;

ItemStackEntity(): PhysicsEntity(SIZE, MASS) {
PhysicsEntity::body_.bounciness_ = 0.6;
}
ItemStackEntity(): PhysicsEntity(SIZE) {}

float despawn_timer_ = DESPAWN_TIME;
Swan::Item *item_ = NULL;

+ 10
- 10
core.mod/src/entities/PlayerEntity.cc View File

@@ -6,7 +6,7 @@

PlayerEntity::PlayerEntity(const Swan::Context &ctx, Swan::Vec2 pos):
PlayerEntity(ctx) {
body_.pos_ = pos;
body_.pos = pos;
}

PlayerEntity::PlayerEntity(const Swan::Context &ctx, const PackObject &obj):
@@ -16,7 +16,7 @@ PlayerEntity::PlayerEntity(const Swan::Context &ctx, const PackObject &obj):

void PlayerEntity::draw(const Swan::Context &ctx, Swan::Win &win) {
body_.outline(win);
anims_[(int)state_].draw(body_.pos_ - Swan::Vec2(0.2, 0.1), win);
anims_[(int)state_].draw(body_.pos - Swan::Vec2(0.2, 0.1), win);
}

void PlayerEntity::update(const Swan::Context &ctx, float dt) {
@@ -33,13 +33,13 @@ void PlayerEntity::update(const Swan::Context &ctx, float dt) {

// Move left
if (ctx.game.isKeyPressed(SDL_SCANCODE_A) || ctx.game.isKeyPressed(SDL_SCANCODE_LEFT)) {
body_.force_ += Swan::Vec2(-MOVE_FORCE, 0);
physics_.force += Swan::Vec2(-MOVE_FORCE, 0);
state_ = State::RUNNING_L;
}

// Move right
if (ctx.game.isKeyPressed(SDL_SCANCODE_D) || ctx.game.isKeyPressed(SDL_SCANCODE_RIGHT)) {
body_.force_ += Swan::Vec2(MOVE_FORCE, 0);
physics_.force += Swan::Vec2(MOVE_FORCE, 0);
if (state_ == State::RUNNING_L)
state_ = State::IDLE;
else
@@ -49,25 +49,25 @@ void PlayerEntity::update(const Swan::Context &ctx, float dt) {
bool jump_pressed = ctx.game.isKeyPressed(SDL_SCANCODE_SPACE);

// Jump
if (body_.on_ground_ && jump_pressed && jump_timer_.periodic(0.5)) {
body_.vel_.y = -JUMP_VEL;
if (physics_.on_ground && jump_pressed && jump_timer_.periodic(0.5)) {
physics_.vel.y = -JUMP_VEL;
}

// Fall down faster than we went up
if (!body_.on_ground_ && (!jump_pressed || body_.vel_.y > 0))
body_.force_ += Swan::Vec2(0, DOWN_FORCE);
if (!physics_.on_ground && (!jump_pressed || physics_.vel.y > 0))
physics_.force += Swan::Vec2(0, DOWN_FORCE);

if (state_ != oldState)
anims_[(int)state_].reset();
anims_[(int)state_].tick(dt);

PhysicsEntity::update(ctx, dt);
physics(ctx, dt, { .mass = MASS });
}

void PlayerEntity::tick(const Swan::Context &ctx, float dt) {
for (ItemStackEntity *ent: ctx.plane.getEntsOfType<ItemStackEntity>()) {
float squared_dist =
(getBody().getBounds().bottomMid() - ent->getBody().getBounds().center())
(body_.bottomMid() - ent->get(Swan::BodyTrait::Tag{}).center())
.squareLength();

if (squared_dist < 0.5 * 0.5) {

+ 1
- 1
core.mod/src/entities/PlayerEntity.h View File

@@ -25,7 +25,7 @@ private:
static constexpr Swan::Vec2 SIZE = Swan::Vec2(0.6, 1.9);

PlayerEntity(const Swan::Context &ctx):
PhysicsEntity(SIZE, MASS), inventory_(INVENTORY_SIZE),
PhysicsEntity(SIZE), inventory_(INVENTORY_SIZE),
anims_{
Swan::Animation(ctx.resources.getImage("core/entity/player-still"), 0.8),
Swan::Animation(

+ 1
- 0
libswan/CMakeLists.txt View File

@@ -1,5 +1,6 @@
add_library(libswan SHARED
src/traits/BodyTrait.cc
src/traits/PhysicsTrait.cc
src/Animation.cc
src/Chunk.cc
src/Clock.cc

+ 13
- 8
libswan/include/swan/Entity.h View File

@@ -7,6 +7,7 @@
#include "common.h"
#include "log.h"
#include "traits/BodyTrait.h"
#include "traits/PhysicsTrait.h"

namespace Swan {

@@ -44,20 +45,24 @@ public:
size_t generation_;
};

class PhysicsEntity: public Entity, public BodyTrait::HasBody {
class PhysicsEntity: public Entity, public BodyTrait, public PhysicsTrait {
public:
PhysicsEntity(Vec2 size, float mass):
body_(size, mass) {}
PhysicsEntity(Vec2 size): body_({ .size = size }) {}

virtual BodyTrait::Body &getBody() override { return body_; }
BodyTrait::Body &get(BodyTrait::Tag) override { return body_; }
PhysicsTrait::Physics &get(PhysicsTrait::Tag) override { return physics_; }

virtual void update(const Context &ctx, float dt) override {
body_.standardForces();
body_.update(ctx, dt);
void physics(
const Context &ctx, float dt,
const PhysicsTrait::PhysicsProps &props) {

physics_.standardForces(props.mass);
physics_.update(ctx, dt, body_, props);
}

protected:
BodyTrait::PhysicsBody body_;
BodyTrait::Body body_;
PhysicsTrait::Physics physics_;
};

}

+ 1
- 1
libswan/include/swan/World.h View File

@@ -59,7 +59,7 @@ public:
std::unordered_map<std::string, WorldGen::Factory> worldgen_factories_;
std::vector<EntityCollection::Factory> ent_coll_factories_;

BodyTrait::HasBody *player_;
BodyTrait::Body *player_;
Game *game_;

std::mt19937 random_;

+ 28
- 88
libswan/include/swan/traits/BodyTrait.h View File

@@ -1,99 +1,39 @@
#pragma once

#include "../common.h"
#include "../Vector2.h"

namespace Swan {

class WorldPlane;
class Win;

namespace BodyTrait {

class Body;
class HasBody {
public:
virtual Body &getBody() = 0;
};

struct Bounds {
Vec2 pos;
Vec2 size;

float left() { return pos.x; }
float right() { return pos.x + size.x; }
float midX() { return pos.x + size.x / 2; }
float top() { return pos.y; }
float bottom() { return pos.y + size.y; }
float midY() { return pos.y + size.y / 2; }

Vec2 topLeft() { return { left(), top() }; }
Vec2 midLeft() { return { left(), midY() }; }
Vec2 bottomLeft() { return { left(), bottom() }; }
Vec2 topMid() { return { midX(), top() }; }
Vec2 center() { return { midX(), midY() }; }
Vec2 bottomMid() { return { midX(), bottom() }; }
Vec2 topRight() { return { right(), top() }; }
Vec2 midRight() { return { right(), midY() }; }
Vec2 bottomRight() { return { right(), bottom() }; }
};

class Body {
public:
virtual ~Body() = default;

virtual Bounds getBounds() = 0;
virtual void move(Vec2 rel) = 0;
virtual void moveTo(Vec2 pos) = 0;
};

// PhysicsBody is a BodyTrait::Body which implements
// a bunch of physics stuff.
class PhysicsBody: public Body {
public:
PhysicsBody(Vec2 size, float mass, Vec2 pos = Vec2::ZERO):
size_(size), mass_(mass), pos_(pos) {};

BodyTrait::Bounds getBounds() override { return BodyTrait::Bounds{ pos_, size_ }; }
void move(Vec2 rel) override { pos_ += rel; }
void moveTo(Vec2 pos) override { pos_ = pos; }

void friction(Vec2 coef = Vec2(400, 50));
void gravity(Vec2 g = Vec2(0, 20));
void standardForces() { friction(); gravity(); }

void outline(Win &win);
void update(const Swan::Context &ctx, float dt);
void updateWithoutCollision(float dt);

Vec2 force_ = { 0, 0 };
Vec2 vel_ = { 0, 0 };
bool on_ground_ = false;
Vec2 size_;
float mass_;
Vec2 pos_;
float bounciness_ = 0;
float mushyness_ = 2;

private:
void collideX(WorldPlane &plane);
void collideY(WorldPlane &plane);
struct BodyTrait {
struct Body;
struct Tag {};
virtual Body &get(Tag) = 0;

struct Body {
Vec2 pos{};
Vec2 size{};

float left() { return pos.x; }
float right() { return pos.x + size.x; }
float midX() { return pos.x + size.x / 2; }
float top() { return pos.y; }
float bottom() { return pos.y + size.y; }
float midY() { return pos.y + size.y / 2; }

Vec2 topLeft() { return { left(), top() }; }
Vec2 midLeft() { return { left(), midY() }; }
Vec2 bottomLeft() { return { left(), bottom() }; }
Vec2 topMid() { return { midX(), top() }; }
Vec2 center() { return { midX(), midY() }; }
Vec2 bottomMid() { return { midX(), bottom() }; }
Vec2 topRight() { return { right(), top() }; }
Vec2 midRight() { return { right(), midY() }; }
Vec2 bottomRight() { return { right(), bottom() }; }

void outline(Win &win);
};
};

// StaticBody is a BodyTrait::Body which just has a static
// position and size.
class StaticBody: public Body {
public:
StaticBody(Vec2 size, Vec2 pos):
size_(size), pos_(pos) {}

BodyTrait::Bounds getBounds() override { return BodyTrait::Bounds{ pos_, size_ }; }
void move(Vec2 rel) override { pos_ += rel; }
void moveTo(Vec2 pos) override { pos_ = pos; }

Vec2 size_;
Vec2 pos_;
};

}
}

+ 47
- 0
libswan/include/swan/traits/PhysicsTrait.h View File

@@ -0,0 +1,47 @@
#pragma once

#include "../traits/BodyTrait.h"
#include "../Vector2.h"
#include "../common.h"

namespace Swan {

struct PhysicsTrait {
struct Physics;
struct Tag {};
virtual Physics &get(Tag) = 0;

struct PhysicsProps {
float mass;
float bounciness = 0;
float mushyness = 2;
};

struct Physics {
Vec2 vel{};
Vec2 force{};
bool on_ground = false;

void friction(Vec2 coef = Vec2(400, 50));
void gravity(float mass, Vec2 g = Vec2(0, 20));
void standardForces(float mass) { friction(); gravity(mass); }

void update(
const Swan::Context &ctx, float dt,
BodyTrait::Body &body, const PhysicsProps &props);
};
};

/*
* Physics
*/

inline void PhysicsTrait::Physics::friction(Vec2 coef) {
force += -vel * coef;
}

inline void PhysicsTrait::Physics::gravity(float mass, Vec2 g) {
force += g * mass;
}

}

+ 4
- 6
libswan/src/World.cc View File

@@ -83,8 +83,8 @@ void World::setWorldGen(std::string gen) {
}

void World::spawnPlayer() {
player_ = dynamic_cast<BodyTrait::HasBody *>(
planes_[current_plane_].spawnPlayer().get());
player_ = &((dynamic_cast<BodyTrait *>(
planes_[current_plane_].spawnPlayer().get()))->get(BodyTrait::Tag{}));
}

void World::setCurrentPlane(WorldPlane &plane) {
@@ -142,8 +142,7 @@ SDL_Color World::backgroundColor() {

void World::draw(Win &win) {
ZoneScopedN("World draw");
auto bounds = player_->getBody().getBounds();
win.cam_ = bounds.pos - (win.getSize() / 2) + (bounds.size / 2);
win.cam_ = player_->pos - (win.getSize() / 2) + (player_->size / 2);
planes_[current_plane_].draw(win);
}

@@ -158,10 +157,9 @@ void World::tick(float dt) {
for (auto &plane: planes_)
plane.tick(dt);

auto bounds = player_->getBody().getBounds();
chunk_renderer_.tick(
planes_[current_plane_],
ChunkPos((int)bounds.pos.x / CHUNK_WIDTH, (int)bounds.pos.y / CHUNK_HEIGHT));
ChunkPos((int)player_->pos.x / CHUNK_WIDTH, (int)player_->pos.y / CHUNK_HEIGHT));
}

}

+ 5
- 5
libswan/src/WorldPlane.cc View File

@@ -163,19 +163,19 @@ void WorldPlane::breakTile(TilePos pos) {
}

SDL_Color WorldPlane::backgroundColor() {
return gen_->backgroundColor(world_->player_->getBody().getBounds().pos);
return gen_->backgroundColor(world_->player_->pos);
}

void WorldPlane::draw(Win &win) {
ZoneScopedN("WorldPlane draw");
auto ctx = getContext();
auto pbounds = world_->player_->getBody().getBounds();
auto &pbody = *(world_->player_);

gen_->drawBackground(ctx, win, pbounds.pos);
gen_->drawBackground(ctx, win, pbody.pos);

ChunkPos pcpos = ChunkPos(
(int)floor(pbounds.pos.x / CHUNK_WIDTH),
(int)floor(pbounds.pos.y / CHUNK_HEIGHT));
(int)floor(pbody.pos.x / CHUNK_WIDTH),
(int)floor(pbody.pos.y / CHUNK_HEIGHT));

// Just init one chunk per frame
if (chunk_init_list_.size() > 0) {

+ 2
- 116
libswan/src/traits/BodyTrait.cc View File

@@ -1,125 +1,11 @@
#include "traits/BodyTrait.h"

#include <math.h>
#include <array>
#include <algorithm>

#include "WorldPlane.h"
#include "Win.h"

namespace Swan {
namespace BodyTrait {

static float epsilon = 0.001;

void PhysicsBody::friction(Vec2 coef) {
force_ += -vel_ * coef;
}

void PhysicsBody::gravity(Vec2 g) {
force_ += g * mass_;
}

void PhysicsBody::collideX(WorldPlane &plane) {
auto bounds = getBounds();
bool collided = false;

for (int y = (int)floor(bounds.top() + epsilon); y <= (int)floor(bounds.bottom() - epsilon); ++y) {
int lx = (int)floor(bounds.left() + epsilon);
Tile &left = plane.getTile({ lx, y });
if (left.is_solid_) {
bounds.pos.x = (float)lx + 1.0;
collided = true;
break;
}

int rx = (int)floor(bounds.right() - epsilon);
Tile &right = plane.getTile({ rx, y });
if (right.is_solid_) {
bounds.pos.x = (float)rx - bounds.size.x;
collided = true;
break;
}
}

if (collided) {
pos_.x = bounds.pos.x;

vel_.x *= -bounciness_;
if (abs(vel_.x) < mushyness_)
vel_.x = 0;
}
}

void PhysicsBody::collideY(WorldPlane &plane) {
auto bounds = getBounds();
bool collided = false;
on_ground_ = false;

for (int x = (int)floor(bounds.left() + epsilon); x <= (int)floor(bounds.right() - epsilon); ++x) {
int ty = (int)floor(bounds.top() + epsilon);
Tile &top = plane.getTile({ x, ty });
if (top.is_solid_) {
bounds.pos.y = (float)ty + 1.0;
collided = true;
break;
}

int by = (int)floor(bounds.bottom() - epsilon);
Tile &bottom = plane.getTile({ x, by });
if (bottom.is_solid_) {
bounds.pos.y = (float)by - bounds.size.y;
collided = true;
on_ground_ = true;
break;
}
}

if (collided) {
pos_.y = bounds.pos.y;

vel_.y *= -bounciness_;
if (abs(vel_.y) < mushyness_)
vel_.y = 0;
}
void BodyTrait::Body::outline(Win &win) {
win.drawRect(pos, size);
}

void PhysicsBody::outline(Win &win) {
win.drawRect(pos_, size_);
}

void PhysicsBody::update(const Swan::Context &ctx, float dt) {
vel_ += (force_ / mass_) * dt;
force_ = { 0, 0 };

Vec2 dist = vel_ * dt;
Vec2 dir = dist.sign();
Vec2 step = dir * 0.4;

// Move in increments of at most 'step', on the X axis
while (abs(dist.x) > abs(step.x)) {
pos_.x += step.x;
collideX(ctx.plane);
dist.x -= step.x;
}
pos_.x += dist.x;
collideX(ctx.plane);

// Move in increments of at most 'step', on the Y axis
while (abs(dist.y) > abs(step.y)) {
pos_.y += step.y;
collideY(ctx.plane);
dist.y -= step.y;
}
pos_.y += dist.y;
collideY(ctx.plane);
}

void PhysicsBody::updateWithoutCollision(float dt) {
vel_ += (force_ / mass_) * dt;
pos_ += vel_ * dt;
force_ = { 0, 0 };
}

}
}

+ 101
- 0
libswan/src/traits/PhysicsTrait.cc View File

@@ -0,0 +1,101 @@
#include "traits/PhysicsTrait.h"

#include "WorldPlane.h"
#include "Win.h"

namespace Swan {

static float epsilon = 0.001;

static void collideX(
PhysicsTrait::Physics &phys, BodyTrait::Body &body,
WorldPlane &plane, const PhysicsTrait::PhysicsProps &props) {
bool collided = false;

for (int y = (int)floor(body.top() + epsilon); y <= (int)floor(body.bottom() - epsilon); ++y) {
int lx = (int)floor(body.left() + epsilon);
Tile &left = plane.getTile({ lx, y });
if (left.is_solid_) {
body.pos.x = (float)lx + 1.0;
collided = true;
break;
}

int rx = (int)floor(body.right() - epsilon);
Tile &right = plane.getTile({ rx, y });
if (right.is_solid_) {
body.pos.x = (float)rx - body.size.x;
collided = true;
break;
}
}

if (collided) {
phys.vel.x *= -props.bounciness;
if (abs(phys.vel.x) < props.mushyness)
phys.vel.x = 0;
}
}

static void collideY(
PhysicsTrait::Physics &phys, BodyTrait::Body &body,
WorldPlane &plane, const PhysicsTrait::PhysicsProps &props) {
bool collided = false;
phys.on_ground = false;

for (int x = (int)floor(body.left() + epsilon); x <= (int)floor(body.right() - epsilon); ++x) {
int ty = (int)floor(body.top() + epsilon);
Tile &top = plane.getTile({ x, ty });
if (top.is_solid_) {
body.pos.y = (float)ty + 1.0;
collided = true;
break;
}

int by = (int)floor(body.bottom() - epsilon);
Tile &bottom = plane.getTile({ x, by });
if (bottom.is_solid_) {
body.pos.y = (float)by - body.size.y;
collided = true;
phys.on_ground = true;
break;
}
}

if (collided) {
phys.vel.y *= -props.bounciness;
if (abs(phys.vel.y) < props.mushyness)
phys.vel.y = 0;
}
}

void PhysicsTrait::Physics::update(
const Swan::Context &ctx, float dt,
BodyTrait::Body &body, const PhysicsProps &props) {
vel += (force / props.mass) * dt;
force = { 0, 0 };

Vec2 dist = vel * dt;
Vec2 dir = dist.sign();
Vec2 step = dir * 0.4;

// Move in increments of at most 'step', on the X axis
while (abs(dist.x) > abs(step.x)) {
body.pos.x += step.x;
collideX(*this, body, ctx.plane, props);
dist.x -= step.x;
}
body.pos.x += dist.x;
collideX(*this, body, ctx.plane, props);

// Move in increments of at most 'step', on the Y axis
while (abs(dist.y) > abs(step.y)) {
body.pos.y += step.y;
collideY(*this, body, ctx.plane, props);
dist.y -= step.y;
}
body.pos.y += dist.y;
collideY(*this, body, ctx.plane, props);
}

}

Loading…
Cancel
Save