feat: implemented some basic gameplay

main
Sara 2025-01-11 13:28:54 +01:00
parent 70b6dc4ab7
commit 110e72e241
18 changed files with 105 additions and 60 deletions

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 465 B

After

Width:  |  Height:  |  Size: 514 B

View File

@ -29,9 +29,7 @@ void AssetDB::load(std::string asset_name) {
}
void AssetDB::index_assets() {
SDL_Log("Indexing assets");
for(std::filesystem::directory_entry const &itr : std::filesystem::recursive_directory_iterator("resources")) {
SDL_Log("Indexing %s", itr.path().c_str());
if(itr.is_directory())
continue;
else if(!itr.path().has_extension())
@ -43,7 +41,6 @@ void AssetDB::index_assets() {
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to add asset %s, asset by that name is already indexed", name.c_str());
} else {
this->assets.insert({name, std::make_shared<Texture>(itr.path())});
SDL_Log("Indexed resource %s as texture", name.c_str());
}
}
}

View File

@ -39,7 +39,7 @@ void CollisionWorld::check_collisions_for(CollisionShape *shape, std::vector<Col
// (As the shapes *before* have already been checked, guaranteeing that each pair will only be checked once)
for(std::vector<CollisionShape*>::iterator iter{begin}; iter != this->shapes.end(); ++iter) {
CollisionShape *other{*iter};
if(other != shape && CollisionShape::shapes_overlap(shape, other)) {
if(other != shape && other->get_owner() != shape->get_owner() && CollisionShape::shapes_overlap(shape, other)) {
if((shape->get_mask() & other->get_layers()) != 0x0u)
shape->get_owner()->add_overlap(shape, other);
if((shape->get_layers() & other->get_mask()) != 0x0u)

View File

@ -24,13 +24,16 @@ Shape Shape::make_box(float h_extent, float v_extent) {
};
}
CollisionShape::CollisionShape(std::string const &name, Node *owner, Shape shape)
CollisionShape::CollisionShape(std::string const &name, Shape shape)
: Node2D(name)
, world{CanvasEngine::get_singleton()->get_collision_world()} {
this->shape = shape;
}
, world{CanvasEngine::get_singleton()->get_collision_world()}
, shape{shape} {}
void CollisionShape::_added() {
this->ce::Node2D::_added();
ce::Transform transform{this->get_global_transform()};
if(!this->is_inside_tree())
return;
Node *parent{this->get_parent()};
while(parent != nullptr) {
if(CollidableNode *as_collidable{dynamic_cast<CollidableNode*>(parent)}) {
@ -40,17 +43,19 @@ void CollisionShape::_added() {
}
}
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "%s does not have a parent that is a CollidableNode to serve as the owner", this->get_name().c_str());
if(this->is_registered)
this->deregister_with_world();
#ifdef DEBUG
abort();
#endif
if(this->is_registered)
this->deregister_with_world();
}
void CollisionShape::_removed() {
if(this->is_registered)
this->ce::Node2D::_removed();
if(this->is_registered) {
this->deregister_with_world();
}
}
bool CollisionShape::can_collide(CollisionShape const *lhs, CollisionShape const *rhs) {
return lhs->owner != nullptr && rhs->owner != nullptr
@ -71,9 +76,9 @@ bool CollisionShape::shapes_overlap(CollisionShape const *lhs, CollisionShape co
? overlap_circle_circle(lshape.circle, lhst, rshape.circle, rhst)
: overlap_circle_aabb(lshape.circle, lhst, rshape.box, rhst);
} else if(lhs->shape.shape == Shape::AABB) {
return rhs->shape.shape == Shape::CIRCLE
? overlap_circle_aabb(rshape.circle, rhst, lshape.box, lhst)
: overlap_aabb_aabb(lshape.box, lhst, rshape.box, rhst);
return rhs->shape.shape == Shape::AABB
? overlap_aabb_aabb(lshape.box, lhst, rshape.box, rhst)
: overlap_circle_aabb(lshape.circle, lhst, rshape.box, rhst);
} else {
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Attempt to compare shapes with a shape that is not of a valid shape. This should never happen.");
#ifdef DEBUG
@ -97,16 +102,9 @@ bool CollisionShape::overlap_circle_aabb(ShapeCircle lhs, Transform lhst, ShapeA
}
bool CollisionShape::overlap_aabb_aabb(ShapeAABB lhs, Transform lhst, ShapeAABB rhs, Transform rhst) {
// calculate size of bounding box encompassing both. If it's smaller than the size of both summed, the two are overlapping.
float const bound_x{std::max({
std::abs((lhst.position.x - lhs.h_extent) - (rhst.position.x + rhs.h_extent)),
lhs.h_extent, rhs.h_extent
})};
float const bound_y{std::max({
std::abs((lhst.position.y - lhs.v_extent) - (rhst.position.y + rhs.v_extent)),
lhs.v_extent, rhs.v_extent
})};
return bound_x < (lhs.h_extent + rhs.h_extent) && bound_y < (lhs.v_extent + rhs.v_extent);
ce::Vecf const diff{lhst.position - rhst.position};
return std::abs(diff.x) <= (lhs.h_extent + rhs.h_extent)
&& std::abs(diff.y) <= (lhs.v_extent + rhs.v_extent);
}
CollidableNode *CollisionShape::get_owner() const {

View File

@ -29,7 +29,7 @@ class CollisionShape : public Node2D {
CollisionWorld &world;
bool is_registered{false};
public:
CollisionShape(std::string const &name, Node *owner, Shape shape);
CollisionShape(std::string const &name, Shape shape);
virtual void _added() override;
virtual void _removed() override;

View File

@ -17,6 +17,7 @@ void Level::deinstantiate() {
void Level::propagate_tick(double const &delta_time) {
this->root->propagate_tick(delta_time);
this->root->propagate_post_tick();
}
void Level::propagate_draw(SDL_Renderer *render) {

View File

@ -71,6 +71,12 @@ bool Vecf::is_nan() const {
return std::isnan(this->x) || std::isnan(this->y);
}
Vecf Vecf::clamp_magnitude(float max) const {
float const old_magnitude{this->magnitude()};
float const new_magnitude{std::min(max, old_magnitude)};
return (old_magnitude != 0.f ? ((*this) / old_magnitude) : ce::Vecf::ZERO) * new_magnitude;
}
void Vecf::scale(float x, float y) {
this->x *= x;
this->y *= y;

View File

@ -37,6 +37,7 @@ struct Vecf {
//! returns true if either the x or y element is NaN
bool is_nan() const;
Vecf clamp_magnitude(float max) const;
//! scale vector member-wise
void scale(float x, float y);
//! scale vector member-wise
@ -62,20 +63,20 @@ bool operator==(Vecf const &lhs, Vecf const &rhs) {
static inline
Vecf operator+(Vecf const &lhs, Vecf const &rhs) {
return Vecf(lhs.x + rhs.x, lhs.y + rhs.y);
return Vecf{lhs.x + rhs.x, lhs.y + rhs.y};
}
static inline
Vecf operator-(Vecf const &lhs, Vecf const &rhs) {
return Vecf(lhs.x - rhs.x, lhs.y - rhs.y);
return Vecf{lhs.x - rhs.x, lhs.y - rhs.y};
}
static inline
Vecf operator*(Vecf const &v, float f) {
return Vecf(v.x * f, v.y * f);
return Vecf{v.x * f, v.y * f};
}
static inline
Vecf operator/(Vecf const &v, float f) {
return Vecf(v.x / f, v.y / f);
return Vecf{v.x / f, v.y / f};
}
static inline

View File

@ -1,5 +1,6 @@
#include "node.hpp"
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL.h>
#include <algorithm>
#include <cassert>
#include <utility>
@ -52,8 +53,6 @@ void Node::set_name(std::string const &name) {
void Node::flag_for_deletion() {
this->request_deletion = true;
this->tick = false;
this->visible = false;
}
bool Node::requests_deletion() const {
@ -84,6 +83,10 @@ bool Node::is_inside_tree() const {
return this->inside_tree;
}
Node::ChildrenVector &Node::get_children() {
return this->children;
}
void Node::set_level(ce::Level *level) {
// parent level needs to match
assert(this->parent == nullptr || this->parent->get_level() == level);
@ -136,12 +139,12 @@ void Node::propagate_tick(double const &delta_time) {
void Node::propagate_post_tick() {
// clean up children queued for deletion
for(size_t i{0}; i < this->children.size(); ++i) {
for(size_t i{0}; i < this->children.size();) {
ChildrenVector::value_type &value{this->children[i]};
if(value.second->requests_deletion()) {
value.second->propagate_removed();
this->child_removed.invoke(value.second.get());
this->remove_child(value.second.get());
} else {
++i;
value.second->propagate_post_tick();
}
}

View File

@ -34,7 +34,7 @@ public:
public:
Node(std::string name);
virtual ~Node();
protected:
public:
virtual void _added() {} //!< called the moment after the object is added as a child to another node
virtual void _first_tick() {} //!< called the first frame this object is active
virtual void _tick(double const &delta_time [[maybe_unused]]) {} //!< called every frame
@ -57,6 +57,7 @@ public:
bool is_ticking() const;
ce::Level *get_level() const;
bool is_inside_tree() const;
ChildrenVector &get_children();
private:
void set_level(ce::Level *level);
void set_is_inside_tree(bool value);

View File

@ -6,6 +6,7 @@ Node2D::Node2D(std::string name) : Node(name) {}
void Node2D::_added() {
this->parent_node2d = dynamic_cast<Node2D*>(this->get_parent());
this->_update_transform();
}
void Node2D::_update_transform() {
@ -13,6 +14,8 @@ void Node2D::_update_transform() {
this->global_transform = this->transform * this->parent_node2d->get_global_transform();
else
this->global_transform = this->transform;
for(ChildrenVector::value_type &pair : this->get_children())
pair.second->_update_transform();
}
void Node2D::set_transform(Transform const &transform) {
@ -30,10 +33,11 @@ void Node2D::set_global_transform(Transform transform) {
Transform parent = this->parent_node2d->get_global_transform();
transform.position -= parent.position.rotated(-parent.rotation);
transform.scale_by(parent.scale.reciprocal());
assert(transform.scale.x != 0.f || transform.scale.y != 0.f); // !!!
assert(transform.scale.x != 0.f || transform.scale.y != 0.f);
transform.rotation -= parent.rotation;
}
this->transform = transform;
this->_update_transform();
}
Transform const &Node2D::get_global_transform() const {

View File

@ -13,6 +13,6 @@ ce::Node::OwnedPtr Level1::construct() {
.scale = ce::Vecf::ONE
});
root->create_child<Player>();
root->create_child<Truck>(0.f);
root->create_child<Truck>(true);
return std::move(root);
}

View File

@ -1,9 +1,11 @@
#include "core/canvas_engine.hpp"
#include "level_1.hpp"
#include <SDL2/SDL_log.h>
ce::CanvasEngine engine{};
int main(int argc [[maybe_unused]], char* argv [[maybe_unused]][]) {
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE);
std::unique_ptr<ce::Level> level{std::make_unique<Level1>()};
engine.run(level);
}

View File

@ -10,11 +10,15 @@
#include "truck.hpp"
#include <SDL2/SDL_keyboard.h>
#include <SDL2/SDL_keycode.h>
#include <cmath>
Player::Player()
: ce::CollidableNode("player", 0x1u, 0x1u)
, sprite{this->create_child<ce::Sprite>("bike", "bike")}
, shape{this->create_child<ce::CollisionShape>("player_col_shape", this, ce::Shape::make_box(1.f, 2.f))} {
, shape{this->create_child<ce::CollisionShape>("player_col_shape", ce::Shape::make_box(.4f, 0.75f))} {
this->set_global_transform(this->get_global_transform()
.translated({0.f, 2.f})
);
this->sprite->set_global_transform(this->sprite->get_global_transform()
.scaled({1.5f, 1.5f})
);
@ -24,9 +28,7 @@ Player::Player()
new ce::KeyboardScancode(SDL_SCANCODE_A),
new ce::KeyboardScancode(SDL_SCANCODE_D)
)
})
.changed
.connect(ce::Callable<void, ce::InputValue>::make(
}).changed.connect(ce::Callable<void, ce::InputValue>::make(
this, &Player::_input_horizontal_movement)
);
map.bind_input("vertical", {
@ -34,12 +36,12 @@ Player::Player()
new ce::KeyboardScancode(SDL_SCANCODE_W),
new ce::KeyboardScancode(SDL_SCANCODE_S)
)
})
.changed
.connect(ce::Callable<void, ce::InputValue>::make(
}) .changed.connect(ce::Callable<void, ce::InputValue>::make(
this, &Player::_input_vertical_movement)
);
this->overlap_enter.connect(ce::Callable<void, ce::CollisionShape *, ce::CollidableNode *, ce::CollisionShape *>::make(this, &Player::_on_overlap_enter));
this->overlap_enter.connect(ce::Callable<void, ce::CollisionShape *, ce::CollidableNode *, ce::CollisionShape *>::make(
this, &Player::_on_overlap_enter
));
}
void Player::_tick(double const &delta) {
@ -49,9 +51,17 @@ void Player::_tick(double const &delta) {
ACCELERATION * delta
);
ce::Transform trans{this->get_global_transform().translated(this->velocity * delta)};
trans.position.x = std::clamp(trans.position.x, -3.f, 3.f);
trans.position.y = std::clamp(trans.position.y, -2.f, 2.f);
trans.position.x = std::clamp(trans.position.x, -LIMITS.x, LIMITS.x);
trans.position.y = std::clamp(trans.position.y, -LIMITS.y, LIMITS.y);
this->set_global_transform(trans);
if(this->invincibility > 0.f) {
this->invincibility -= delta;
this->sprite->set_visible(this->invincibility <= 0.f
? true
: int(std::floorf(this->invincibility / DAMAGE_FLASH_FREQ)) % 2 != 0
);
}
}
void Player::_input_horizontal_movement(ce::InputValue value) {
@ -63,9 +73,10 @@ void Player::_input_vertical_movement(ce::InputValue value) {
}
void Player::_on_overlap_enter(ce::CollisionShape *, ce::CollidableNode *other, ce::CollisionShape *) {
if(this->invincibility > 0.f)
return;
if(Truck *truck{dynamic_cast<Truck*>(other)}) {
// TODO: Implement damage
this->flag_for_deletion();
this->invincibility = 2.f;
this->velocity = (this->get_global_transform().position - other->get_global_transform().position).normalized() * DAMAGE_FORCE;
}
SDL_Log("overlap");
}

View File

@ -11,12 +11,16 @@ class Sprite;
class Player : public ce::CollidableNode {
ce::Vecf const SPEED{3.f, 2.5f};
float const ACCELERATION{20.f};
float const ACCELERATION{10.f};
ce::Vecf const LIMITS{3.f, 3.f};
float const DAMAGE_FLASH_FREQ{.1f};
float const DAMAGE_FORCE{6.f};
ce::Vecf velocity{0.f, 0.f};
ce::Vecf input{0.f, 0.f};
ce::CollisionShape *shape{nullptr};
ce::Sprite *sprite{nullptr};
float invincibility{0.f};
public:
Player();
virtual void _tick(double const &delta) override;

View File

@ -1,17 +1,29 @@
#include "truck.hpp"
#include "core/callable.hpp"
#include "core/collision_shape.hpp"
#include "core/sprite.hpp"
#include <SDL2/SDL_log.h>
Truck::Truck(float x_pos)
Truck::Truck(bool left)
: ce::CollidableNode("truck", 0x1u, 0x1u)
, sprite{this->create_child<ce::Sprite>("sprite", "truck")}
, shape{this->create_child<ce::CollisionShape>("truck_col_shape", this, ce::Shape::make_box(2.f, 2.f))} {
this->sprite->set_global_transform(this->get_global_transform()
.scaled({2.5f, 2.5f})
.translated({x_pos, -3.f})
);
, shape{this->create_child<ce::CollisionShape>("truck_col_shape", ce::Shape::make_box(.5f, 1.f))}
, spawned_left{left} {
this->sprite->set_global_transform(this->get_global_transform() .scaled({2.5f, 2.5f}));
this->set_global_transform(this->get_global_transform().translated({
.x=this->spawned_left ? -1.4f : 1.4f,
.y=-4.5f
}));
}
void Truck::_tick(double const &delta) {
wave_time += delta * FREQUENCY;
ce::Transform const transform{this->get_global_transform()
.translated(ce::Vecf{
.x=std::sin(wave_time) * AMPLITUDE * (this->spawned_left > 0.f ? 1.f : -1.f),
.y=this->RELATIVE_VERTICAL_SPEED
} * float(delta))
};
this->set_global_transform(transform);
if(transform.position.y > 4.5f)
this->flag_for_deletion();
}

View File

@ -9,10 +9,15 @@ namespace ce {
};
class Truck : public ce::CollidableNode {
float const AMPLITUDE{4.f};
float const FREQUENCY{3.f};
float const RELATIVE_VERTICAL_SPEED{1.5f};
float wave_time{0.f};
bool spawned_left{false};
ce::Sprite *sprite{nullptr};
ce::CollisionShape *shape{nullptr};
public:
Truck(float x_pos);
Truck(bool left);
virtual void _tick(double const &delta) override;
};