tiny_link/src/game.cpp

410 lines
9.4 KiB
C++
Raw Normal View History

2021-01-03 05:51:50 +08:00
#include "game.h"
#include "content.h"
2021-01-03 08:20:01 +08:00
#include "masks.h"
2021-01-03 09:50:25 +08:00
#include "assets/tileset.h"
2021-01-03 08:20:01 +08:00
#include "components/collider.h"
2021-01-03 09:50:25 +08:00
#include "components/tilemap.h"
2021-01-04 06:08:22 +08:00
#include "components/player.h"
2021-01-04 10:55:56 +08:00
#include "components/mover.h"
#include "assets/sprite.h"
2021-01-03 09:05:12 +08:00
#include "factory.h"
2022-02-13 05:03:02 +08:00
#include <cstdlib>
2021-01-03 05:51:50 +08:00
using namespace TL;
2021-01-04 06:08:22 +08:00
namespace
{
2021-01-04 07:11:49 +08:00
constexpr float transition_duration = 0.4f;
2021-01-04 06:08:22 +08:00
}
2021-01-03 05:51:50 +08:00
void Game::startup()
{
2021-01-04 13:54:18 +08:00
world.game = this;
2021-01-03 05:51:50 +08:00
// load our content
Content::load();
// framebuffer for the game
2022-02-13 05:03:02 +08:00
buffer = Target::create(width, height);
2021-01-03 05:51:50 +08:00
// set batcher to use Nearest Filter
batch.default_sampler = TextureSampler(TextureFilter::Nearest);
2021-01-04 05:04:11 +08:00
m_draw_colliders = false;
2021-01-03 08:20:01 +08:00
2021-01-04 09:03:48 +08:00
// load first room
2021-01-04 13:54:18 +08:00
load_room(Point(0, 0));
2022-02-13 05:03:02 +08:00
camera = Vec2f(room.x * width, room.y * height);
2021-01-04 14:01:22 +08:00
fullscreen = false;
2021-01-03 09:05:12 +08:00
}
2021-01-03 08:20:01 +08:00
2021-01-04 10:55:56 +08:00
void Game::load_room(Point cell, bool is_reload)
2021-01-03 09:05:12 +08:00
{
2021-01-04 05:04:11 +08:00
const Image* grid = Content::find_room(cell);
BLAH_ASSERT(grid, "Room doesn't exist!");
room = cell;
2021-01-03 08:20:01 +08:00
2021-01-04 06:08:22 +08:00
// get room offset
auto offset = Point(cell.x * width, cell.y * height);
2021-01-03 09:05:12 +08:00
2021-01-03 09:50:25 +08:00
// get the castle tileset for now
auto castle = Content::find_tileset("castle");
2021-01-04 06:08:22 +08:00
auto grass = Content::find_tileset("grass");
auto plants = Content::find_tileset("plants");
2021-01-04 09:03:48 +08:00
auto backs = Content::find_tileset("back");
2021-01-04 11:47:16 +08:00
auto jumpthrus = Content::find_tileset("jumpthru");
2021-01-03 09:50:25 +08:00
// make the floor
2021-01-04 06:08:22 +08:00
auto floor = world.add_entity(offset);
2021-01-04 05:04:11 +08:00
auto tilemap = floor->add(Tilemap(8, 8, columns, rows));
auto solids = floor->add(Collider::make_grid(8, 40, 23));
solids->mask = Mask::solid;
// loop over the room grid
for (int x = 0; x < columns; x ++)
for (int y = 0; y < rows; y++)
{
2021-01-04 09:03:48 +08:00
Point world_position = offset + Point(x * tile_width, y * tile_height) + Point(tile_width / 2, tile_height);
2021-01-04 05:04:11 +08:00
Color col = grid->pixels[x + y * columns];
uint32_t rgb =
((uint32_t)col.r << 16) |
((uint32_t)col.g << 8) |
((uint32_t)col.b);
switch (rgb)
{
// black does nothing
case 0x000000:
break;
2021-01-04 06:08:22 +08:00
// castle tiles
2021-01-04 05:04:11 +08:00
case 0xffffff:
tilemap->set_cell(x, y, &castle->random_tile());
solids->set_cell(x, y, true);
break;
2021-01-04 06:08:22 +08:00
// grass tiles
case 0x8f974a:
tilemap->set_cell(x, y, &grass->random_tile());
solids->set_cell(x, y, true);
break;
// plants tiles
case 0x4b692f:
tilemap->set_cell(x, y, &plants->random_tile());
break;
2021-01-04 09:03:48 +08:00
// back tiles
case 0x45283c:
tilemap->set_cell(x, y, &backs->random_tile());
break;
2021-01-04 11:47:16 +08:00
// jumpthru tiles
case 0xdf7126:
{
tilemap->set_cell(x, y, &jumpthrus->random_tile());
auto jumpthru_en = world.add_entity(offset + Point(x * tile_width, y * tile_height));
2022-02-13 05:03:02 +08:00
auto jumpthru_col = jumpthru_en->add(Collider::make_rect(Recti(0, 0, 8, 4)));
2021-01-04 11:47:16 +08:00
jumpthru_col->mask = Mask::jumpthru;
break;
}
2021-01-04 06:08:22 +08:00
// player (only if it doesn't already exist)
2021-01-04 05:04:11 +08:00
case 0x6abe30:
2021-01-04 06:08:22 +08:00
if (!world.first<Player>())
2021-01-04 10:55:56 +08:00
Factory::player(&world, world_position + (is_reload ? Point(0, -16) : Point::zero));
2021-01-04 05:04:11 +08:00
break;
2021-01-04 07:11:49 +08:00
// brambles
case 0xd77bba:
2021-01-04 09:03:48 +08:00
Factory::bramble(&world, world_position);
break;
// spitter plant
case 0xac3232:
Factory::spitter(&world, world_position);
break;
// mosquito
case 0xfbf236:
Factory::mosquito(&world, world_position + Point(0, -8));
2021-01-04 07:11:49 +08:00
break;
2021-01-04 10:55:56 +08:00
// door
case 0x9badb7:
Factory::door(&world, world_position);
break;
2021-01-04 11:47:16 +08:00
// closing door
case 0x847e87:
2021-01-04 13:42:45 +08:00
Factory::door(&world, world_position, !is_reload);
2021-01-04 11:47:16 +08:00
break;
2021-01-04 10:55:56 +08:00
// blob
case 0x3f3f74:
Factory::blob(&world, world_position);
break;
2021-01-04 13:42:45 +08:00
case 0x76428a:
Factory::ghost_frog(&world, world_position + Point(-4, 0));
break;
2021-01-04 05:04:11 +08:00
}
}
2021-01-03 05:51:50 +08:00
}
void Game::shutdown()
{
}
void Game::update()
{
2021-01-04 13:54:18 +08:00
2021-01-04 06:08:22 +08:00
// Toggle Collider Render
2021-01-03 08:20:01 +08:00
if (Input::pressed(Key::F1))
m_draw_colliders = !m_draw_colliders;
2021-01-04 06:08:22 +08:00
// Reload Current Room
2021-01-03 09:05:12 +08:00
if (Input::pressed(Key::F2))
2021-01-04 06:08:22 +08:00
{
m_transition = false;
world.clear();
2021-01-04 05:04:11 +08:00
load_room(room);
2021-01-04 06:08:22 +08:00
}
2021-01-04 14:01:22 +08:00
// Reload First Room
if (Input::pressed(Key::F9))
{
m_transition = false;
world.clear();
load_room(Point(0, 0));
2022-02-13 05:03:02 +08:00
camera = Vec2f(0, 0);
2021-01-04 14:01:22 +08:00
}
// Toggle Fullscreen
if (Input::pressed(Key::F4))
App::fullscreen(fullscreen = !fullscreen);
2021-01-04 06:08:22 +08:00
// Normal Update
if (!m_transition)
{
2021-01-04 13:54:18 +08:00
// Screen Shake
m_shake_timer -= Time::delta;
if (m_shake_timer > 0)
{
if (Time::on_interval(0.05f))
{
2022-02-13 05:03:02 +08:00
m_shake.x = rand_int(0, 2) == 0 ? -1 : 1;
m_shake.y = rand_int(0, 2) == 0 ? -1 : 1;
2021-01-04 13:54:18 +08:00
}
}
else
m_shake = Point::zero;
// Upodate Objects
2021-01-04 06:08:22 +08:00
world.update();
2021-01-04 13:54:18 +08:00
// Check for transition / death
2021-01-04 06:08:22 +08:00
auto player = world.first<Player>();
if (player)
{
auto pos = player->entity()->position;
2022-02-13 05:03:02 +08:00
auto bounds = Recti(room.x * width, room.y * height, width, height);
2021-01-04 06:08:22 +08:00
if (!bounds.contains(pos))
{
// target room
Point next_room = Point(pos.x / width, pos.y / height);
if (pos.x < 0) next_room.x--;
if (pos.y < 0) next_room.y--;
// see if room exists
2021-01-04 11:47:16 +08:00
if (player->health > 0 && Content::find_room(next_room) && next_room.x >= room.x)
2021-01-04 06:08:22 +08:00
{
2021-01-04 07:11:49 +08:00
Time::pause_for(0.1f);
2021-01-04 06:08:22 +08:00
// transiton to it!
m_transition = true;
m_next_ease = 0;
m_next_room = next_room;
m_last_room = room;
// store entities from the previous room
m_last_entities.clear();
Entity* e = world.first_entity();
while (e)
{
m_last_entities.push_back(e);
e = e->next();
}
// load contents of the next room
load_room(next_room);
}
// doesn't exist, clamp player
else
{
player->entity()->position = Point(
2021-03-21 11:44:04 +08:00
Calc::clamp(pos.x, bounds.x, bounds.x + bounds.w),
Calc::clamp(pos.y, bounds.y, bounds.y + bounds.h + 100));
2021-01-04 09:03:48 +08:00
// reload if they fell out the bottom
if (player->entity()->position.y > bounds.y + bounds.h + 64)
{
world.clear();
2021-01-04 10:55:56 +08:00
load_room(room, true);
2021-01-04 09:03:48 +08:00
}
2021-01-04 06:08:22 +08:00
}
}
2021-01-04 10:55:56 +08:00
// death ... delete everything except the player
// then when they fall out of the screen, we reset
if (player->health <= 0)
{
Entity* e = world.first_entity();
while (e)
{
auto next = e->next();
if (!e->get<Player>())
world.destroy_entity(e);
e = next;
}
}
2021-01-04 06:08:22 +08:00
}
}
// Room Transition routine
else
{
2021-01-04 07:11:49 +08:00
// increment ease
m_next_ease = Calc::approach(m_next_ease, 1.0f, Time::delta / transition_duration);
// get last & next camera position
2022-02-13 05:03:02 +08:00
auto last_cam = Vec2f(m_last_room.x * width, m_last_room.y * height);
auto next_cam = Vec2f(m_next_room.x * width, m_next_room.y * height);
2021-01-04 06:08:22 +08:00
2021-01-04 07:11:49 +08:00
// LERP camera position
2021-01-04 06:08:22 +08:00
camera = last_cam + (next_cam - last_cam) * Ease::cube_in_out(m_next_ease);
2021-01-03 05:51:50 +08:00
2021-01-04 07:11:49 +08:00
// Finish Transition
2021-01-04 06:08:22 +08:00
if (m_next_ease >= 1.0f)
{
2021-01-04 10:55:56 +08:00
// boost player on vertical up rooms
if (m_next_room.y < m_last_room.y)
{
auto player = world.first<Player>();
if (player)
2022-02-13 05:03:02 +08:00
player->get<Mover>()->speed = Vec2f(0, -150);
2021-01-04 10:55:56 +08:00
}
2021-01-04 06:08:22 +08:00
// delete old objects (except player!)
for (auto& it : m_last_entities)
{
if (!it->get<Player>())
world.destroy_entity(it);
}
2021-01-04 07:11:49 +08:00
Time::pause_for(0.1f);
2021-01-04 06:08:22 +08:00
m_transition = false;
}
}
2021-01-03 05:51:50 +08:00
}
void Game::render()
{
// draw gameplay stuff
{
2021-01-04 05:04:11 +08:00
buffer->clear(0x150e22);
2021-01-03 08:20:01 +08:00
2021-01-04 10:55:56 +08:00
// push camera offset
2022-02-13 05:03:02 +08:00
batch.push_matrix(Mat3x2f::create_translation(-camera + m_shake));
2021-01-04 10:55:56 +08:00
// draw gameplay objects
2021-01-03 08:20:01 +08:00
world.render(batch);
2021-01-04 10:55:56 +08:00
// draw debug colliders
2021-01-03 08:20:01 +08:00
if (m_draw_colliders)
{
auto collider = world.first<Collider>();
while (collider)
{
collider->render(batch);
collider = (Collider*)collider->next();
}
}
2021-01-03 05:51:50 +08:00
2021-01-04 11:47:16 +08:00
// hacky start / end screen text
if (room == Point(0, 0) || m_last_room == Point(0, 0))
{
auto w = Content::font.width_of(title);
auto pos = Point((width - w) / 2, 20);
batch.str(Content::font, title, pos + Point(0, 1), Color::black);
batch.str(Content::font, title, pos, Color::white);
w = Content::font.width_of(controls);
pos.x = (width - w) / 2;
pos.y += 20;
batch.str(Content::font, controls, pos, Color::white * 0.25f);
}
else if (room == Point(13, 0))
{
auto w = Content::font.width_of(ending);
auto pos = Point(room.x * width + width / 2, room.y * height + 20);
batch.str(Content::font, ending, pos + Point(0, 1), TextAlign::Top, 8, Color::black);
batch.str(Content::font, ending, pos, TextAlign::Top, 8, Color::white);
}
2021-01-04 10:55:56 +08:00
// end camera offset
2021-01-04 06:08:22 +08:00
batch.pop_matrix();
2021-01-04 10:55:56 +08:00
// draw the health
auto player = world.first<Player>();
if (player)
{
auto hearts = Content::find_sprite("heart");
auto full = hearts->get_animation("full");
auto empty = hearts->get_animation("empty");
Point pos = Point(0, height - 16);
batch.rect(Rect(pos.x, pos.y + 7, 40, 4), Color::black);
for (int i = 0; i < Player::max_health; i++)
{
if (player->health >= i + 1)
batch.tex(full->frames[0].image, pos);
else
batch.tex(empty->frames[0].image, pos);
pos.x += 12;
}
}
// draw to the gameplay buffer
2021-01-03 05:51:50 +08:00
batch.render(buffer);
batch.clear();
}
// draw buffer to the screen
{
float scale = Calc::min(
2022-02-13 05:03:02 +08:00
App::backbuffer()->width() / (float)buffer->width(),
App::backbuffer()->height() / (float)buffer->height());
2021-01-03 05:51:50 +08:00
2022-02-13 05:03:02 +08:00
Vec2f screen_center = Vec2f(App::backbuffer()->width(), App::backbuffer()->height()) / 2;
Vec2f buffer_center = Vec2f(buffer->width(), buffer->height()) / 2;
2021-01-03 05:51:50 +08:00
2022-02-13 05:03:02 +08:00
App::backbuffer()->clear(Color::black);
batch.push_matrix(Mat3x2f::create_transform(screen_center, buffer_center, Vec2f::one * scale, 0));
batch.tex(buffer->texture(0), Vec2f::zero, Color::white);
2021-01-03 05:51:50 +08:00
batch.pop_matrix();
2022-02-13 05:03:02 +08:00
batch.render(App::backbuffer());
2021-01-03 05:51:50 +08:00
batch.clear();
}
}
2021-01-04 13:54:18 +08:00
void Game::shake(float time)
{
m_shake_timer = time;
}
2022-02-13 05:03:02 +08:00
int Game::rand_int(int min, int max)
{
return min + (rand() % (max - min));
}