basic components

This commit is contained in:
Noel Berry 2021-01-02 16:20:01 -08:00
parent 8db43ce18e
commit 4e05170023
20 changed files with 636 additions and 9 deletions

View File

@ -11,9 +11,23 @@ add_subdirectory(libs/blah)
# add our source
add_executable(game
src/main.cpp
src/world.h
src/world.cpp
src/game.h
src/game.cpp
src/content.h
src/content.cpp
src/assets/sprite.h
src/assets/sprite.cpp
src/components/animator.h
src/components/animator.cpp
src/components/collider.h
src/components/collider.cpp
src/components/player.h
src/components/player.cpp
src/components/mover.h
src/components/mover.cpp
src/masks.h
)
# Reference blah

BIN
content/sprites/circle.ase Normal file

Binary file not shown.

BIN
content/sprites/player.ase Normal file

Binary file not shown.

0
src/assets/sprite.cpp Normal file
View File

26
src/assets/sprite.h Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include <blah.h>
using namespace Blah;
namespace TL
{
struct Sprite
{
struct Frame
{
Subtexture image;
float duration;
};
struct Animation
{
String name;
Vector<Frame> frames;
};
String name;
Vec2 origin;
Vector<Animation> animations;
};
}

View File

@ -0,0 +1,76 @@
#include "animator.h"
#include "../content.h"
using namespace TL;
Animator::Animator(const String& sprite)
{
m_sprite = Content::find_sprite(sprite);
m_animation_index = 0;
}
void Animator::play(const String& animation)
{
BLAH_ASSERT(m_sprite, "No Sprite Assigned!");
for (int i = 0; i < m_sprite->animations.size(); i++)
{
if (m_sprite->animations[i].name == animation)
{
m_animation_index = i;
m_frame_index = 0;
break;
}
}
}
void Animator::update()
{
// only update if we're in a valid state
if (in_valid_state())
{
// quick references
auto& anim = m_sprite->animations[m_animation_index];
auto& frame = anim.frames[m_frame_index];
// increment frame counter
m_frame_counter += Time::delta;
// move to next frame after duration
while (m_frame_counter >= frame.duration)
{
// reset frame counter
m_frame_counter -= frame.duration;
// increement frame, move back if we're at the end
m_frame_index++;
if (m_frame_index >= anim.frames.size())
m_frame_index = 0;
}
}
}
void Animator::render(Batch& batch)
{
if (in_valid_state())
{
batch.push_matrix(
Mat3x2::create_transform(entity()->position, m_sprite->origin, Vec2::one, 0));
auto& anim = m_sprite->animations[m_animation_index];
auto& frame = anim.frames[m_frame_index];
batch.tex(frame.image, Vec2::zero, Color::white);
batch.pop_matrix();
}
}
bool Animator::in_valid_state() const
{
return
m_sprite &&
m_animation_index >= 0 &&
m_animation_index < m_sprite->animations.size() &&
m_frame_index >= 0 &&
m_frame_index < m_sprite->animations[m_animation_index].frames.size();
}

31
src/components/animator.h Normal file
View File

@ -0,0 +1,31 @@
#pragma once
#include <blah.h>
#include "../assets/sprite.h"
#include "../world.h"
using namespace Blah;
namespace TL
{
class Animator : public Component
{
private:
const Sprite* m_sprite = nullptr;
int m_animation_index = 0;
int m_frame_index = 0;
float m_frame_counter = 0;
public:
Animator() = default;
Animator(const String& sprite);
void play(const String& animation);
void update() override;
void render(Batch& batch) override;
private:
bool in_valid_state() const;
};
}

147
src/components/collider.cpp Normal file
View File

@ -0,0 +1,147 @@
#include "collider.h"
using namespace TL;
Collider::Collider()
{
visible = false;
active = false;
}
Collider Collider::make_rect(const RectI& rect)
{
Collider collider;
collider.m_shape = Shape::Rect;
collider.m_rect = rect;
return collider;
}
Collider Collider::make_grid(int tile_size, int columns, int rows)
{
Collider collider;
collider.m_shape = Shape::Grid;
collider.m_grid.tile_size = tile_size;
collider.m_grid.columns = columns;
collider.m_grid.rows = rows;
collider.m_grid.cells = std::shared_ptr<bool[]>(new bool[columns * rows]);
return collider;
}
Collider::Shape Collider::shape() const
{
return m_shape;
}
RectI Collider::get_rect() const
{
BLAH_ASSERT(m_shape == Shape::Rect, "Collider is not a Rectangle");
return m_rect;
}
void Collider::set_rect(const RectI& value)
{
BLAH_ASSERT(m_shape == Shape::Rect, "Collider is not a Rectangle");
m_rect = value;
}
bool Collider::get_cell(int x, int y) const
{
BLAH_ASSERT(m_shape == Shape::Grid, "Collider is not a Grid");
BLAH_ASSERT(x >= 0 && y >= 0 && x < m_grid.columns && y < m_grid.rows, "Cell is out of bounds");
return m_grid.cells[x + y * m_grid.columns];
}
void Collider::set_cell(int x, int y, bool value)
{
BLAH_ASSERT(m_shape == Shape::Grid, "Collider is not a Grid");
BLAH_ASSERT(x >= 0 && y >= 0 && x < m_grid.columns&& y < m_grid.rows, "Cell is out of bounds");
m_grid.cells[x + y * m_grid.columns] = value;
}
bool Collider::check(uint32_t mask, Point offset) const
{
auto other = world()->first<Collider>();
while (other)
{
if (other != this &&
(other->mask & mask) == mask &&
overlaps(other, offset))
return true;
other = (Collider*)other->next();
}
return false;
}
bool Collider::overlaps(const Collider* other, Point offset) const
{
if (m_shape == Shape::Rect)
{
if (other->m_shape == Shape::Rect)
{
return rect_to_rect(this, other, offset);
}
else if (other->m_shape == Shape::Grid)
{
return rect_to_grid(this, other, offset);
}
}
else if (m_shape == Shape::Grid)
{
if (other->m_shape == Shape::Rect)
{
return rect_to_grid(other, this, -offset);
}
else if (other->m_shape == Shape::Grid)
{
BLAH_ASSERT(false, "Grid->Grid Overlap checks not supported!");
}
}
return false;
}
void Collider::render(Batch& batch)
{
static const Color color = Color::red;
batch.push_matrix(Mat3x2::create_translation(entity()->position));
if (m_shape == Shape::Rect)
{
batch.rect_line(m_rect, 1, color);
}
else if (m_shape == Shape::Grid)
{
for (int x = 0; x < m_grid.columns; x++)
{
for (int y = 0; y < m_grid.rows; y++)
{
if (!m_grid.cells[x + y * m_grid.columns])
continue;
batch.rect_line(
Rect(x * m_grid.tile_size, y * m_grid.tile_size, m_grid.tile_size, m_grid.tile_size),
1, color);
}
}
}
batch.pop_matrix();
}
bool TL::Collider::rect_to_rect(const Collider* a, const Collider* b, Point offset)
{
RectI ar = a->m_rect + a->entity()->position + offset;
RectI br = b->m_rect + b->entity()->position;
return ar.overlaps(br);
}
bool TL::Collider::rect_to_grid(const Collider* a, const Collider* b, Point offset)
{
return false;
}

54
src/components/collider.h Normal file
View File

@ -0,0 +1,54 @@
#pragma once
#include <blah.h>
#include <memory>
#include "../world.h"
using namespace Blah;
namespace TL
{
class Collider : public Component
{
public:
enum class Shape
{
None,
Rect,
Grid
};
uint32_t mask;
Collider();
static Collider make_rect(const RectI& rect);
static Collider make_grid(int tile_size, int columns, int rows);
Shape shape() const;
RectI get_rect() const;
void set_rect(const RectI& value);
bool get_cell(int x, int y) const;
void set_cell(int x, int y, bool value);
bool check(uint32_t mask, Point offset = Point::zero) const;
bool overlaps(const Collider* other, Point offset = Point::zero) const;
void render(Batch& batch) override;
private:
struct Grid
{
int columns;
int rows;
int tile_size;
std::shared_ptr<bool[]> cells;
};
Shape m_shape = Shape::None;
RectI m_rect;
Grid m_grid;
static bool rect_to_rect(const Collider* a, const Collider* b, Point offset);
static bool rect_to_grid(const Collider* a, const Collider* b, Point offset);
};
}

89
src/components/mover.cpp Normal file
View File

@ -0,0 +1,89 @@
#include "mover.h"
#include "../masks.h"
using namespace TL;
bool Mover::move_x(int amount)
{
if (collider)
{
int sign = Calc::sign(amount);
while (amount != 0)
{
if (collider->check(Mask::solid, Point(sign, 0)))
{
stop_x();
return true;
}
amount -= sign;
entity()->position.x += sign;
}
}
else
{
entity()->position.x += amount;
}
}
bool Mover::move_y(int amount)
{
if (collider)
{
int sign = Calc::sign(amount);
while (amount != 0)
{
if (collider->check(Mask::solid, Point(0, sign)))
{
stop_y();
return true;
}
amount -= sign;
entity()->position.y += sign;
}
}
else
{
entity()->position.y += amount;
}
}
void Mover::stop_x()
{
speed.x = 0;
m_remainder.x = 0;
}
void Mover::stop_y()
{
speed.y = 0;
m_remainder.y = 0;
}
void Mover::stop()
{
speed.x = 0;
speed.y = 0;
m_remainder.x = 0;
m_remainder.y = 0;
}
void Mover::update()
{
// get the amount we should move, including remainder from the previous frame
Vec2 total = m_remainder + speed * Time::delta;
// round to integer values since we only move in pixels at a time
Point to_move = Point((int)total.x, (int)total.y);
// store remainder floating values for next frame
m_remainder.x = total.x - to_move.x;
m_remainder.y = total.y - to_move.y;
// move by integer values
move_x(to_move.x);
move_y(to_move.y);
}

28
src/components/mover.h Normal file
View File

@ -0,0 +1,28 @@
#pragma once
#include "../world.h"
#include "collider.h"
#include <blah.h>
using namespace Blah;
namespace TL
{
class Mover : public Component
{
private:
Vec2 m_remainder;
public:
Collider* collider;
Vec2 speed;
bool move_x(int amount);
bool move_y(int amount);
void stop_x();
void stop_y();
void stop();
void update() override;
};
}

View File

14
src/components/player.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include "../world.h"
#include <blah.h>
using namespace Blah;
namespace TL
{
class Player : public Component
{
public:
void update() override;
};
}

View File

@ -1,10 +1,21 @@
#include "content.h"
#include "assets/sprite.h"
using namespace TL;
namespace
{
FilePath root;
Vector<Sprite> sprites;
Vector<Subtexture> subtextures;
TextureRef sprite_atlas;
struct SpriteInfo
{
String name;
Aseprite aseprite;
uint64_t pack_index;
};
}
SpriteFont Content::font;
@ -31,10 +42,94 @@ FilePath Content::path()
void Content::load()
{
Packer packer;
packer.padding = 0;
// load the main font
font = SpriteFont(path() + "fonts/dogica.ttf", 8, SpriteFont::ASCII);
// load sprites
Vector<SpriteInfo> sprite_info;
uint64_t pack_index = 0;
{
// get all the sprites
FilePath sprite_path = path() + "sprites/";
for (auto& it : Directory::enumerate(sprite_path, true))
{
if (!it.ends_with(".ase"))
continue;
SpriteInfo* info = sprite_info.expand();
info->aseprite = Aseprite(it.cstr());
info->name = String(it.cstr() + sprite_path.length(), it.end() - 4);
}
// add to the atlas
for (auto& info : sprite_info)
{
info.pack_index = pack_index;
for (auto& frame : info.aseprite.frames)
{
packer.add(pack_index, frame.image);
pack_index++;
}
}
}
// build the atlas
{
packer.pack();
sprite_atlas = Texture::create(packer.pages[0]);
subtextures.expand(packer.entries.size());
for (auto& entry : packer.entries)
subtextures[entry.id] = Subtexture(sprite_atlas, entry.packed, entry.frame);
}
// add sprites
for (auto& info : sprite_info)
{
Sprite* sprite = sprites.expand();
sprite->name = info.name;
sprite->origin = Vec2::zero;
if (info.aseprite.slices.size() > 0 && info.aseprite.slices[0].has_pivot)
{
sprite->origin = Vec2(
info.aseprite.slices[0].pivot.x,
info.aseprite.slices[0].pivot.y);
}
for (auto& tag : info.aseprite.tags)
{
Sprite::Animation* anim = sprite->animations.expand();
anim->name = tag.name;
for (int i = tag.from; i <= tag.to; i++)
{
Sprite::Frame* frame = anim->frames.expand();
frame->duration = info.aseprite.frames[i].duration / 1000.0f;
frame->image = subtextures[info.pack_index + i];
}
}
}
}
void Content::unload()
{
font.dispose();
}
TextureRef Content::atlas()
{
return sprite_atlas;
}
const Sprite* Content::find_sprite(const char* name)
{
for (auto& it : sprites)
if (it.name == name)
return &it;
return nullptr;
}

View File

@ -5,6 +5,8 @@ using namespace Blah;
namespace TL
{
struct Sprite;
class Content
{
public:
@ -13,5 +15,8 @@ namespace TL
static FilePath path();
static void load();
static void unload();
static TextureRef atlas();
static const Sprite* find_sprite(const char* name);
};
}

View File

@ -1,5 +1,9 @@
#include "game.h"
#include "content.h"
#include "masks.h"
#include "components/animator.h"
#include "components/collider.h"
#include "components/mover.h"
using namespace TL;
@ -13,6 +17,22 @@ void Game::startup()
// set batcher to use Nearest Filter
batch.default_sampler = TextureSampler(TextureFilter::Nearest);
// add a test entity
auto en = world.add_entity(Point(100, 60));
auto an = en->add(Animator("player"));
auto col = en->add(Collider::make_rect(RectI(-4, -8, 8, 8)));
auto mover = en->add(Mover());
mover->collider = col;
mover->speed = Vec2(5, 20);
an->play("idle");
auto floor = world.add_entity(Point(50, 100));
auto c2 = floor->add(Collider::make_rect(RectI(0, 0, 100, 16)));
c2->mask = Mask::solid;
m_draw_colliders = true;
}
void Game::shutdown()
@ -22,16 +42,30 @@ void Game::shutdown()
void Game::update()
{
if (Input::pressed(Key::F1))
m_draw_colliders = !m_draw_colliders;
world.update();
}
void Game::render()
{
// draw gameplay stuff
{
buffer->clear(Color::red);
buffer->clear(0x4488aa);
world.render(batch);
if (m_draw_colliders)
{
auto collider = world.first<Collider>();
while (collider)
{
collider->render(batch);
collider = (Collider*)collider->next();
}
}
batch.str(Content::font, "Hello World", Vec2(32, 32), Color::white);
batch.render(buffer);
batch.clear();
}

View File

@ -17,5 +17,8 @@ namespace TL
void shutdown();
void update();
void render();
private:
bool m_draw_colliders;
};
}

10
src/masks.h Normal file
View File

@ -0,0 +1,10 @@
#pragma once
#include <inttypes.h>
namespace TL
{
struct Mask
{
static constexpr uint32_t solid = 1 << 0;
};
}

View File

@ -130,7 +130,7 @@ World::~World()
}
}
Entity* World::add_entity()
Entity* World::add_entity(Point point)
{
// create entity instance
Entity* instance;
@ -149,6 +149,7 @@ Entity* World::add_entity()
m_alive.insert(instance);
// assign
instance->position = point;
instance->m_world = this;
// return new entity!

View File

@ -73,7 +73,7 @@ namespace TL
public:
bool active = true;
bool visible = true;
Blah::Vec2 position;
Blah::Point position;
World* world();
const World* world() const;
@ -115,7 +115,7 @@ namespace TL
World& operator=(World&&) = delete;
~World();
Entity* add_entity();
Entity* add_entity(Blah::Point position = Blah::Point(0, 0));
Entity* first_entity();
@ -249,28 +249,28 @@ namespace TL
T* World::first()
{
uint8_t type = Component::Types::id<T>();
return m_components_alive[type].first;
return (T*)m_components_alive[type].first;
}
template<class T>
const T* World::first() const
{
uint8_t type = Component::Types::id<T>();
return m_components_alive[type].first;
return (T*)m_components_alive[type].first;
}
template<class T>
T* World::last()
{
uint8_t type = Component::Types::id<T>();
return m_components_alive[type].last;
return (T*)m_components_alive[type].last;
}
template<class T>
const T* World::last() const
{
uint8_t type = Component::Types::id<T>();
return m_components_alive[type].last;
return (T*)m_components_alive[type].last;
}
template<class T>