This commit is contained in:
Noel Berry 2021-01-03 21:42:45 -08:00
parent 0d01334e0a
commit f721b86bb9
18 changed files with 499 additions and 21 deletions

View File

@ -39,7 +39,7 @@ add_executable(game
src/components/timer.h src/components/timer.h
src/components/timer.cpp src/components/timer.cpp
src/components/enemy.h src/components/enemy.h
) "src/components/ghost_frog.h" "src/components/ghost_frog.cpp" "src/components/orb.h" "src/components/orb.cpp")
# Reference blah # Reference blah
target_link_libraries(game blah) target_link_libraries(game blah)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 243 B

After

Width:  |  Height:  |  Size: 243 B

Binary file not shown.

View File

@ -72,7 +72,7 @@ void Animator::render(Batch& batch)
if (in_valid_state()) if (in_valid_state())
{ {
batch.push_matrix( batch.push_matrix(
Mat3x2::create_transform(entity()->position, m_sprite->origin, scale, 0)); Mat3x2::create_transform(entity()->position + offset, m_sprite->origin, scale, 0));
auto& anim = m_sprite->animations[m_animation_index]; auto& anim = m_sprite->animations[m_animation_index];
auto& frame = anim.frames[m_frame_index]; auto& frame = anim.frames[m_frame_index];

View File

@ -17,6 +17,7 @@ namespace TL
public: public:
Vec2 scale = Vec2::one; Vec2 scale = Vec2::one;
Point offset = Point::zero;
Animator() = default; Animator() = default;
Animator(const String& sprite); Animator(const String& sprite);

View File

@ -71,6 +71,11 @@ void Collider::set_cells(int x, int y, int w, int h, bool value)
} }
bool Collider::check(uint32_t mask, Point offset) const bool Collider::check(uint32_t mask, Point offset) const
{
return first(mask, offset) != nullptr;
}
Collider* Collider::first(uint32_t mask, Point offset)
{ {
if (world()) if (world())
{ {
@ -80,13 +85,32 @@ bool Collider::check(uint32_t mask, Point offset) const
if (other != this && if (other != this &&
(other->mask & mask) == mask && (other->mask & mask) == mask &&
overlaps(other, offset)) overlaps(other, offset))
return true; return other;
other = (Collider*)other->next(); other = (Collider*)other->next();
} }
} }
return false; return nullptr;
}
const Collider* Collider::first(uint32_t mask, Point offset) const
{
if (world())
{
auto other = world()->first<Collider>();
while (other)
{
if (other != this &&
(other->mask & mask) == mask &&
overlaps(other, offset))
return other;
other = (Collider*)other->next();
}
}
return nullptr;
} }
bool Collider::overlaps(const Collider* other, Point offset) const bool Collider::overlaps(const Collider* other, Point offset) const

View File

@ -31,6 +31,8 @@ namespace TL
void set_cell(int x, int y, bool value); void set_cell(int x, int y, bool value);
void set_cells(int x, int y, int w, int h, bool value); void set_cells(int x, int y, int w, int h, bool value);
Collider* first(uint32_t mask, Point offset = Point::zero);
const Collider* first(uint32_t mask, Point offset = Point::zero) const;
bool check(uint32_t mask, Point offset = Point::zero) const; bool check(uint32_t mask, Point offset = Point::zero) const;
bool overlaps(const Collider* other, Point offset = Point::zero) const; bool overlaps(const Collider* other, Point offset = Point::zero) const;

View File

@ -0,0 +1,268 @@
#include "ghost_frog.h"
#include "mover.h"
#include "hurtable.h"
#include "player.h"
#include "animator.h"
#include "orb.h"
#include "../masks.h"
#include "../factory.h"
using namespace TL;
GhostFrog::GhostFrog()
{
}
void GhostFrog::awake()
{
m_home = entity()->position;
}
void GhostFrog::update()
{
m_timer += Time::delta;
auto player = world()->first<Player>();
auto mover = get<Mover>();
auto anim = get<Animator>();
auto hitbox = get<Collider>();
// no player - turn off AI
if (!player)
return;
auto x = entity()->position.x;
auto y = entity()->position.y;
auto player_x = player->entity()->position.x;
// update sprite
if (mover->on_ground())
{
}
// flip sprite
anim->scale = Vec2(m_facing, 1);
// NORMAL STATE
if (m_state == st_readying_attack)
{
m_facing = Calc::sign(player_x - x);
if (m_facing == 0)
m_facing = 1;
float target_x = player_x + 32 * -m_facing;
mover->speed.x = Calc::approach(mover->speed.x, Calc::sign(target_x - x) * 40, 400 * Time::delta);
mover->friction = 100;
anim->play("run");
if (m_timer > 3.0f ||
(m_timer > 1.0f && hitbox->check(Mask::solid, Point(-m_facing * 8, 0))))
{
mover->speed.x = 0;
set_state(st_perform_slash);
}
}
// SLASH STATE
else if (m_state == st_perform_slash)
{
// start attack anim
anim->play("attack");
mover->friction = 500;
// after 0.8s, do the lunge
if (Time::on_time(m_timer, 0.8f))
{
mover->speed.x = m_facing * 250;
hitbox->set_rect(RectI(-4 + m_facing * 4, -12, 8, 12));
RectI rect(8, -8, 20, 8);
if (m_facing < 0)
rect.x = -(rect.x + rect.w);
if (m_attack_collider)
m_attack_collider->destroy();
m_attack_collider = entity()->add(Collider::make_rect(rect));
m_attack_collider->mask = Mask::enemy;
}
// turn off attack collider
else if (Time::on_time(m_timer, anim->animation()->duration() - 1.0f))
{
if (m_attack_collider)
m_attack_collider->destroy();
m_attack_collider = nullptr;
}
// end attack state
else if (m_timer >= anim->animation()->duration())
{
hitbox->set_rect(RectI(-4, -12, 8, 12));
if (health > 0)
{
set_state(st_readying_attack);
}
else
{
phase = 1;
health = max_health_2;
m_side = Calc::rand_int(0, 2) == 0 ? -1 : 1;
set_state(st_floating);
}
}
}
// FLOATING STATE
else if (m_state == st_floating)
{
anim->play("float");
mover->friction = 0;
mover->collider = nullptr;
float target_y = m_home.y - 50;
float target_x = m_home.x + m_side * 50;
if (Calc::sign(target_y - y) != Calc::sign(target_y - m_last_pos.y))
{
mover->speed.y = 0;
entity()->position.y = target_y;
}
else
mover->speed.y = Calc::approach(mover->speed.y, Calc::sign(target_y - y) * 50, 800 * Time::delta);
if (Calc::abs(y - target_y) < 8)
mover->speed.x = Calc::approach(mover->speed.x, Calc::sign(target_x - x) * 80, 800 * Time::delta);
else
mover->speed.x = 0;
if (m_timer > 5.0f || (Calc::abs(target_x - x) < 8 && Calc::abs(target_y - y) < 8))
set_state(st_shoot);
}
// SHOOTING STATE
else if (m_state == st_shoot)
{
mover->speed = Calc::approach(mover->speed, Vec2::zero, 300 * Time::delta);
m_facing = Calc::sign(player_x - x);
if (m_facing == 0)
m_facing = 1;
if (Time::on_time(m_timer, 1.0f))
{
anim->play("reflect");
}
else if (Time::on_time(m_timer, 1.2f))
{
Factory::orb(world(), entity()->position + Point(m_facing * 12, -8));
m_reflect_count = 0;
}
else if (Time::on_time(m_timer, 1.4f))
{
anim->play("float");
set_state(st_reflect);
}
}
// REFLECT STATE
else if (m_state == st_reflect)
{
if (Time::on_time(m_timer, 0.4f))
anim->play("float");
auto orb = world()->first<Orb>();
if (!orb)
{
if (m_timer > 1.0f)
{
m_side = -m_side;
set_state(st_floating);
}
}
else if (!orb->towards_player)
{
if (m_reflect_count < 2)
{
if (Vec2(orb->entity()->position - orb->target()).length() < 16)
{
anim->play("reflect");
orb->on_hit();
m_reflect_count++;
m_timer = 0;
}
}
else
{
if (Vec2(orb->entity()->position - orb->target()).length() < 8)
{
Factory::pop(world(), entity()->position + Point(0, -8));
orb->entity()->destroy();
on_hurt(nullptr);
m_timer = 0;
}
}
}
}
// DEAD STATE
else if (m_state == st_dead_state)
{
anim->play("dead");
if (Time::on_interval(0.25f))
{
auto offset = Point(Calc::rand_int(-16, 16), Calc::rand_int(-16, 16));
Factory::pop(world(), entity()->position + Point(0, -8) + offset);
}
if (Time::on_time(m_timer, 3.0f))
{
for (int x = -1; x < 2; x ++)
for (int y = -1; y < 2; y ++)
Factory::pop(world(), entity()->position + Point(x * 12, -8 + y * 12));
Time::pause_for(0.3f);
entity()->destroy();
}
}
if (m_state == st_floating || m_state == st_shoot || m_state == st_reflect)
{
anim->offset.y = Calc::sin(Time::elapsed * 2) * 3;
}
m_last_pos = entity()->position;
}
void GhostFrog::on_hurt(Hurtable* hurtable)
{
if (health > 0)
{
health--;
if (health <= 0 && phase > 0)
{
set_state(st_dead_state);
}
if (m_state == st_waiting)
{
Factory::pop(world(), entity()->position + Point(0, -8));
Time::pause_for(0.25f);
set_state(st_readying_attack);
}
}
}
void GhostFrog::on_hit_x(Mover* mover)
{
}
void GhostFrog::on_hit_y(Mover* mover)
{
}
void GhostFrog::set_state(int state)
{
m_state = state;
m_timer = 0;
}

View File

@ -0,0 +1,48 @@
#pragma once
#include "../world.h"
#include <blah.h>
using namespace Blah;
namespace TL
{
class Hurtable;
class Mover;
class Collider;
class GhostFrog : public Component
{
public:
static constexpr int st_waiting = 0;
static constexpr int st_readying_attack = 1;
static constexpr int st_perform_slash = 2;
static constexpr int st_floating = 3;
static constexpr int st_shoot = 4;
static constexpr int st_reflect = 5;
static constexpr int st_dead_state = 6;
static constexpr int max_health_1 = 10;
static constexpr int max_health_2 = 3;
int health = max_health_1;
int phase = 0;
GhostFrog();
void awake() override;
void update() override;
void on_hurt(Hurtable* hurtable);
void on_hit_x(Mover* mover);
void on_hit_y(Mover* mover);
private:
void set_state(int state);
float m_timer = 0;
int m_state = st_waiting;
int m_facing = 1;
int m_side = 1;
int m_reflect_count = 0;
Point m_home;
Point m_last_pos;
Collider* m_attack_collider = nullptr;
};
}

42
src/components/orb.cpp Normal file
View File

@ -0,0 +1,42 @@
#include "orb.h"
#include "mover.h"
#include "hurtable.h"
#include "player.h"
#include "ghost_frog.h"
#include "../masks.h"
#include "../factory.h"
using namespace TL;
Point Orb::target() const
{
auto player = world()->first<Player>();
auto ghost = world()->first<GhostFrog>();
if (player && ghost)
return (towards_player ? player->entity()->position : ghost->entity()->position) + Point(0, -8);
return Point(0, 0);
}
void Orb::update()
{
auto mover = get<Mover>();
auto diff = Vec2(target() - entity()->position).normal();
mover->speed = diff * speed;
}
void Orb::destroyed()
{
Factory::pop(world(), entity()->position);
}
void Orb::on_hit()
{
towards_player = !towards_player;
speed += 40;
auto hurt = get<Hurtable>();
if (towards_player)
hurt->stun_timer = 0;
}

20
src/components/orb.h Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include "../world.h"
#include <blah.h>
using namespace Blah;
namespace TL
{
class Orb : public Component
{
public:
float speed = 40;
bool towards_player = true;
Point target() const;
void update() override;
void destroyed() override;
void on_hit();
};
}

View File

@ -2,6 +2,7 @@
#include "mover.h" #include "mover.h"
#include "animator.h" #include "animator.h"
#include "collider.h" #include "collider.h"
#include "orb.h"
#include "../masks.h" #include "../masks.h"
using namespace TL; using namespace TL;
@ -220,7 +221,10 @@ void Player::update()
} }
// Hurt Check! // Hurt Check!
if (m_invincible_timer <= 0 && hitbox->check(Mask::enemy)) if (m_invincible_timer <= 0)
{
auto hit = hitbox->first(Mask::enemy);
if (hit)
{ {
Time::pause_for(0.1f); Time::pause_for(0.1f);
anim->play("hurt"); anim->play("hurt");
@ -237,5 +241,11 @@ void Player::update()
m_hurt_timer = hurt_duration; m_hurt_timer = hurt_duration;
m_invincible_timer = invincible_duration; m_invincible_timer = invincible_duration;
m_state = st_hurt; m_state = st_hurt;
// hack:
// destroy orb
if (hit->get<Orb>())
hit->entity()->destroy();
}
} }
} }

View File

@ -14,7 +14,7 @@ namespace TL
static constexpr int st_attack = 1; static constexpr int st_attack = 1;
static constexpr int st_hurt = 2; static constexpr int st_hurt = 2;
static constexpr int st_start = 3; static constexpr int st_start = 3;
static constexpr int max_health = 3; static constexpr int max_health = 30;
int health = max_health; int health = max_health;

View File

@ -7,6 +7,8 @@
#include "components/hurtable.h" #include "components/hurtable.h"
#include "components/timer.h" #include "components/timer.h"
#include "components/enemy.h" #include "components/enemy.h"
#include "components/ghost_frog.h"
#include "components/orb.h"
using namespace TL; using namespace TL;
@ -19,6 +21,7 @@ Entity* Factory::player(World* world, Point position)
anim->depth = -10; anim->depth = -10;
auto hitbox = en->add(Collider::make_rect(RectI(-4, -12, 8, 12))); auto hitbox = en->add(Collider::make_rect(RectI(-4, -12, 8, 12)));
hitbox->mask = Mask::player;
auto mover = en->add(Mover()); auto mover = en->add(Mover());
mover->collider = hitbox; mover->collider = hitbox;
@ -334,3 +337,55 @@ Entity* Factory::blob(World* world, Point position)
return en; return en;
} }
Entity* Factory::ghost_frog(World* world, Point position)
{
auto en = world->add_entity(position);
en->add(GhostFrog());
en->add(Enemy());
auto anim = en->add(Animator("ghostfrog"));
anim->play("sword");
anim->depth = -5;
auto hitbox = en->add(Collider::make_rect(RectI(-4, -12, 8, 12)));
hitbox->mask = Mask::enemy;
auto mover = en->add(Mover());
mover->collider = hitbox;
mover->gravity = 0;
mover->friction = 100;
mover->on_hit_x = [](Mover* self) { self->get<GhostFrog>()->on_hit_x(self); };
mover->on_hit_y = [](Mover* self) { self->get<GhostFrog>()->on_hit_y(self); };
auto hurtable = en->add(Hurtable());
hurtable->hurt_by = Mask::player_attack;
hurtable->collider = hitbox;
hurtable->on_hurt = [](Hurtable* self) { self->get<GhostFrog>()->on_hurt(self); };
return en;
}
Entity* Factory::orb(World* world, Point position)
{
auto en = world->add_entity(position);
en->add(Orb());
auto anim = en->add(Animator("bullet"));
anim->play("idle");
anim->depth = -5;
auto hitbox = en->add(Collider::make_rect(RectI(-4, -4, 8, 8)));
hitbox->mask = Mask::enemy;
auto mover = en->add(Mover());
mover->collider = hitbox;
mover->on_hit_x = [](Mover* self) { Factory::pop(self->world(), self->entity()->position); self->entity()->destroy(); };
mover->on_hit_y = [](Mover* self) { Factory::pop(self->world(), self->entity()->position); self->entity()->destroy(); };
auto hurtable = en->add(Hurtable());
hurtable->hurt_by = Mask::player_attack;
hurtable->collider = en->add(Collider::make_rect(RectI(-8, -8, 16, 16)));
hurtable->on_hurt = [](Hurtable* self) { self->get<Orb>()->on_hit(); };
return en;
}

View File

@ -16,5 +16,7 @@ namespace TL
Entity* mosquito(World* world, Point position); Entity* mosquito(World* world, Point position);
Entity* door(World* world, Point position, bool wait_for_player = false); Entity* door(World* world, Point position, bool wait_for_player = false);
Entity* blob(World* world, Point position); Entity* blob(World* world, Point position);
Entity* ghost_frog(World* world, Point position);
Entity* orb(World* world, Point position);
} }
} }

View File

@ -29,7 +29,7 @@ void Game::startup()
m_draw_colliders = false; m_draw_colliders = false;
// load first room // load first room
load_room(Point(11, 0)); load_room(Point(12, 0));
camera = Vec2(room.x * width, room.y * height); camera = Vec2(room.x * width, room.y * height);
} }
@ -132,13 +132,17 @@ void Game::load_room(Point cell, bool is_reload)
// closing door // closing door
case 0x847e87: case 0x847e87:
Factory::door(&world, world_position, true); Factory::door(&world, world_position, !is_reload);
break; break;
// blob // blob
case 0x3f3f74: case 0x3f3f74:
Factory::blob(&world, world_position); Factory::blob(&world, world_position);
break; break;
case 0x76428a:
Factory::ghost_frog(&world, world_position + Point(-4, 0));
break;
} }
} }
} }

View File

@ -9,5 +9,6 @@ namespace TL
static constexpr uint32_t jumpthru = 1 << 1; static constexpr uint32_t jumpthru = 1 << 1;
static constexpr uint32_t player_attack = 1 << 2; static constexpr uint32_t player_attack = 1 << 2;
static constexpr uint32_t enemy = 1 << 3; static constexpr uint32_t enemy = 1 << 3;
static constexpr uint32_t player = 1 << 4;
}; };
} }

View File

@ -245,6 +245,7 @@ namespace TL
entity->m_components.push_back(instance); entity->m_components.push_back(instance);
// and we're done! // and we're done!
instance->awake();
return instance; return instance;
} }