static std::uniform_real_distribution vy(-2.3f, -1.2f); | static std::uniform_real_distribution vy(-2.3f, -1.2f); | ||||
item_ = &ctx.world.getItem(item); | 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): | ItemStackEntity::ItemStackEntity(const Swan::Context &ctx, const PackObject &obj): | ||||
SDL_Texture *tex = item_->image_.texture_.get(); | SDL_Texture *tex = item_->image_.texture_.get(); | ||||
Swan::TexColorMod darken(tex, 220, 220, 220); | 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 }); | { .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) { | void ItemStackEntity::tick(const Swan::Context &ctx, float dt) { | ||||
despawn_timer_ -= dt; | despawn_timer_ -= dt; | ||||
if (despawn_timer_ <= 0) | if (despawn_timer_ <= 0) | ||||
} | } | ||||
void ItemStackEntity::deserialize(const Swan::Context &ctx, const PackObject &obj) { | 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>()); | item_ = &ctx.world.getItem(obj.at("item").as<std::string>()); | ||||
} | } | ||||
Swan::Entity::PackObject ItemStackEntity::serialize(const Swan::Context &ctx, msgpack::zone &zone) { | Swan::Entity::PackObject ItemStackEntity::serialize(const Swan::Context &ctx, msgpack::zone &zone) { | ||||
return { | return { | ||||
{ "pos", msgpack::object(body_.pos_, zone) }, | |||||
{ "pos", msgpack::object(body_.pos, zone) }, | |||||
{ "tile", msgpack::object(item_->name_, zone) }, | { "tile", msgpack::object(item_->name_, zone) }, | ||||
}; | }; | ||||
} | } |
ItemStackEntity(const Swan::Context &ctx, const PackObject &obj); | ItemStackEntity(const Swan::Context &ctx, const PackObject &obj); | ||||
void draw(const Swan::Context &ctx, Swan::Win &win) override; | 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 tick(const Swan::Context &ctx, float dt) override; | ||||
void deserialize(const Swan::Context &ctx, const PackObject &obj) override; | void deserialize(const Swan::Context &ctx, const PackObject &obj) override; | ||||
PackObject serialize(const Swan::Context &ctx, msgpack::zone &zone) override; | PackObject serialize(const Swan::Context &ctx, msgpack::zone &zone) override; | ||||
static constexpr float MASS = 80; | static constexpr float MASS = 80; | ||||
static constexpr Swan::Vec2 SIZE = Swan::Vec2(0.5, 0.5); | static constexpr Swan::Vec2 SIZE = Swan::Vec2(0.5, 0.5); | ||||
static constexpr float DESPAWN_TIME = 5 * 60; | 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; | float despawn_timer_ = DESPAWN_TIME; | ||||
Swan::Item *item_ = NULL; | Swan::Item *item_ = NULL; |
PlayerEntity::PlayerEntity(const Swan::Context &ctx, Swan::Vec2 pos): | PlayerEntity::PlayerEntity(const Swan::Context &ctx, Swan::Vec2 pos): | ||||
PlayerEntity(ctx) { | PlayerEntity(ctx) { | ||||
body_.pos_ = pos; | |||||
body_.pos = pos; | |||||
} | } | ||||
PlayerEntity::PlayerEntity(const Swan::Context &ctx, const PackObject &obj): | PlayerEntity::PlayerEntity(const Swan::Context &ctx, const PackObject &obj): | ||||
void PlayerEntity::draw(const Swan::Context &ctx, Swan::Win &win) { | void PlayerEntity::draw(const Swan::Context &ctx, Swan::Win &win) { | ||||
body_.outline(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) { | void PlayerEntity::update(const Swan::Context &ctx, float dt) { | ||||
// Move left | // Move left | ||||
if (ctx.game.isKeyPressed(SDL_SCANCODE_A) || ctx.game.isKeyPressed(SDL_SCANCODE_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; | state_ = State::RUNNING_L; | ||||
} | } | ||||
// Move right | // Move right | ||||
if (ctx.game.isKeyPressed(SDL_SCANCODE_D) || ctx.game.isKeyPressed(SDL_SCANCODE_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) | if (state_ == State::RUNNING_L) | ||||
state_ = State::IDLE; | state_ = State::IDLE; | ||||
else | else | ||||
bool jump_pressed = ctx.game.isKeyPressed(SDL_SCANCODE_SPACE); | bool jump_pressed = ctx.game.isKeyPressed(SDL_SCANCODE_SPACE); | ||||
// Jump | // 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 | // 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) | if (state_ != oldState) | ||||
anims_[(int)state_].reset(); | anims_[(int)state_].reset(); | ||||
anims_[(int)state_].tick(dt); | anims_[(int)state_].tick(dt); | ||||
PhysicsEntity::update(ctx, dt); | |||||
physics(ctx, dt, { .mass = MASS }); | |||||
} | } | ||||
void PlayerEntity::tick(const Swan::Context &ctx, float dt) { | void PlayerEntity::tick(const Swan::Context &ctx, float dt) { | ||||
for (ItemStackEntity *ent: ctx.plane.getEntsOfType<ItemStackEntity>()) { | for (ItemStackEntity *ent: ctx.plane.getEntsOfType<ItemStackEntity>()) { | ||||
float squared_dist = | float squared_dist = | ||||
(getBody().getBounds().bottomMid() - ent->getBody().getBounds().center()) | |||||
(body_.bottomMid() - ent->get(Swan::BodyTrait::Tag{}).center()) | |||||
.squareLength(); | .squareLength(); | ||||
if (squared_dist < 0.5 * 0.5) { | if (squared_dist < 0.5 * 0.5) { |
static constexpr Swan::Vec2 SIZE = Swan::Vec2(0.6, 1.9); | static constexpr Swan::Vec2 SIZE = Swan::Vec2(0.6, 1.9); | ||||
PlayerEntity(const Swan::Context &ctx): | PlayerEntity(const Swan::Context &ctx): | ||||
PhysicsEntity(SIZE, MASS), inventory_(INVENTORY_SIZE), | |||||
PhysicsEntity(SIZE), inventory_(INVENTORY_SIZE), | |||||
anims_{ | anims_{ | ||||
Swan::Animation(ctx.resources.getImage("core/entity/player-still"), 0.8), | Swan::Animation(ctx.resources.getImage("core/entity/player-still"), 0.8), | ||||
Swan::Animation( | Swan::Animation( |
add_library(libswan SHARED | add_library(libswan SHARED | ||||
src/traits/BodyTrait.cc | src/traits/BodyTrait.cc | ||||
src/traits/PhysicsTrait.cc | |||||
src/Animation.cc | src/Animation.cc | ||||
src/Chunk.cc | src/Chunk.cc | ||||
src/Clock.cc | src/Clock.cc |
#include "common.h" | #include "common.h" | ||||
#include "log.h" | #include "log.h" | ||||
#include "traits/BodyTrait.h" | #include "traits/BodyTrait.h" | ||||
#include "traits/PhysicsTrait.h" | |||||
namespace Swan { | namespace Swan { | ||||
size_t generation_; | size_t generation_; | ||||
}; | }; | ||||
class PhysicsEntity: public Entity, public BodyTrait::HasBody { | |||||
class PhysicsEntity: public Entity, public BodyTrait, public PhysicsTrait { | |||||
public: | 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: | protected: | ||||
BodyTrait::PhysicsBody body_; | |||||
BodyTrait::Body body_; | |||||
PhysicsTrait::Physics physics_; | |||||
}; | }; | ||||
} | } |
std::unordered_map<std::string, WorldGen::Factory> worldgen_factories_; | std::unordered_map<std::string, WorldGen::Factory> worldgen_factories_; | ||||
std::vector<EntityCollection::Factory> ent_coll_factories_; | std::vector<EntityCollection::Factory> ent_coll_factories_; | ||||
BodyTrait::HasBody *player_; | |||||
BodyTrait::Body *player_; | |||||
Game *game_; | Game *game_; | ||||
std::mt19937 random_; | std::mt19937 random_; |
#pragma once | #pragma once | ||||
#include "../common.h" | |||||
#include "../Vector2.h" | #include "../Vector2.h" | ||||
namespace Swan { | namespace Swan { | ||||
class WorldPlane; | |||||
class Win; | 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_; | |||||
}; | |||||
} | |||||
} | } |
#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; | |||||
} | |||||
} |
} | } | ||||
void World::spawnPlayer() { | 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) { | void World::setCurrentPlane(WorldPlane &plane) { | ||||
void World::draw(Win &win) { | void World::draw(Win &win) { | ||||
ZoneScopedN("World draw"); | 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); | planes_[current_plane_].draw(win); | ||||
} | } | ||||
for (auto &plane: planes_) | for (auto &plane: planes_) | ||||
plane.tick(dt); | plane.tick(dt); | ||||
auto bounds = player_->getBody().getBounds(); | |||||
chunk_renderer_.tick( | chunk_renderer_.tick( | ||||
planes_[current_plane_], | 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)); | |||||
} | } | ||||
} | } |
} | } | ||||
SDL_Color WorldPlane::backgroundColor() { | SDL_Color WorldPlane::backgroundColor() { | ||||
return gen_->backgroundColor(world_->player_->getBody().getBounds().pos); | |||||
return gen_->backgroundColor(world_->player_->pos); | |||||
} | } | ||||
void WorldPlane::draw(Win &win) { | void WorldPlane::draw(Win &win) { | ||||
ZoneScopedN("WorldPlane draw"); | ZoneScopedN("WorldPlane draw"); | ||||
auto ctx = getContext(); | 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( | 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 | // Just init one chunk per frame | ||||
if (chunk_init_list_.size() > 0) { | if (chunk_init_list_.size() > 0) { |
#include "traits/BodyTrait.h" | #include "traits/BodyTrait.h" | ||||
#include <math.h> | |||||
#include <array> | |||||
#include <algorithm> | |||||
#include "WorldPlane.h" | |||||
#include "Win.h" | #include "Win.h" | ||||
namespace Swan { | 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 }; | |||||
} | |||||
} | |||||
} | } |
#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); | |||||
} | |||||
} |