Compare commits
2 Commits
e00fc1799d
...
acf162d1b9
| Author | SHA1 | Date |
|---|---|---|
|
|
acf162d1b9 | |
|
|
fda8b120d3 |
|
|
@ -12,5 +12,5 @@ set(LIGHTGAME_SOURCES
|
|||
)
|
||||
|
||||
add_executable(IMX6U-LightGame ${LIGHTGAME_SOURCES})
|
||||
target_include_directories(IMX6U-LightGame PRIVATE src/engine src/systems src/levels)
|
||||
target_include_directories(IMX6U-LightGame PRIVATE src/engine src/systems src/levels generated)
|
||||
imx6u_configure_app_target(IMX6U-LightGame)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -2,6 +2,7 @@
|
|||
#include "DrawContext.h"
|
||||
#include "BitmapFont.h"
|
||||
#include "ButtonInput.h"
|
||||
#include "IKeyboardState.h"
|
||||
#include <cstdio>
|
||||
|
||||
namespace LightGame
|
||||
|
|
@ -10,14 +11,19 @@ namespace LightGame
|
|||
: state_(GameState::Title),
|
||||
hud_(),
|
||||
title_blink_ms_(0),
|
||||
title_show_prompt_(true)
|
||||
title_show_prompt_(true),
|
||||
esc_was_down_(false)
|
||||
{
|
||||
}
|
||||
|
||||
void GameStateManager::update(GameState current_state, const Platform::IButtonInput* button, uint32_t dt_ms)
|
||||
void GameStateManager::update(GameState current_state, const Platform::IButtonInput* button, const Platform::IKeyboardState* keyboard, uint32_t dt_ms)
|
||||
{
|
||||
state_ = current_state;
|
||||
|
||||
const bool esc_down = keyboard && keyboard->is_key_down(Platform::KEY_ESC);
|
||||
const bool esc_pressed = esc_down && !esc_was_down_;
|
||||
esc_was_down_ = esc_down;
|
||||
|
||||
switch (state_)
|
||||
{
|
||||
case GameState::Title:
|
||||
|
|
@ -30,14 +36,14 @@ namespace LightGame
|
|||
break;
|
||||
|
||||
case GameState::Playing:
|
||||
if (button && button->was_pressed())
|
||||
if (esc_pressed || (button && button->was_pressed()))
|
||||
{
|
||||
state_ = GameState::Paused;
|
||||
}
|
||||
break;
|
||||
|
||||
case GameState::Paused:
|
||||
if (button && button->was_pressed())
|
||||
if (esc_pressed || (button && button->was_pressed()))
|
||||
{
|
||||
state_ = GameState::Playing;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ namespace RenderData
|
|||
namespace Platform
|
||||
{
|
||||
class IButtonInput;
|
||||
class IKeyboardState;
|
||||
}
|
||||
|
||||
namespace LightGame
|
||||
|
|
@ -53,11 +54,12 @@ namespace LightGame
|
|||
HudData hud_;
|
||||
uint32_t title_blink_ms_;
|
||||
bool title_show_prompt_;
|
||||
bool esc_was_down_;
|
||||
|
||||
public:
|
||||
GameStateManager();
|
||||
|
||||
void update(GameState current_state, const Platform::IButtonInput* button, uint32_t dt_ms);
|
||||
void update(GameState current_state, const Platform::IButtonInput* button, const Platform::IKeyboardState* keyboard, uint32_t dt_ms);
|
||||
void draw(Core::DrawContext& ctx, const RenderData::BitmapFont& font, int32_t screen_w, int32_t screen_h);
|
||||
|
||||
void set_state(GameState state) { state_ = state; }
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include "GameObject.h"
|
||||
#include "Tilemap.h"
|
||||
#include "Image.h"
|
||||
#include "Sprite.h"
|
||||
|
||||
namespace LightGame
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,19 @@
|
|||
#include "LevelLoader.h"
|
||||
#include "Sprite.h"
|
||||
|
||||
namespace LightGame
|
||||
{
|
||||
namespace
|
||||
{
|
||||
static RenderData::Sprite MakeTileSprite(const RenderData::Image* atlas, int32_t tile_id,
|
||||
int32_t tile_size, int32_t atlas_columns)
|
||||
{
|
||||
const int32_t col = tile_id % atlas_columns;
|
||||
const int32_t row = tile_id / atlas_columns;
|
||||
return RenderData::Sprite(atlas, col * tile_size, row * tile_size, tile_size, tile_size);
|
||||
}
|
||||
}
|
||||
|
||||
void LevelLoader::load(Level& level, const LevelData& data)
|
||||
{
|
||||
RenderData::Tilemap tilemap;
|
||||
|
|
@ -22,59 +34,65 @@ namespace LightGame
|
|||
|
||||
level.set_spawn_point(data.spawn_point);
|
||||
|
||||
const RenderData::Image* atlas = data.atlas;
|
||||
const int32_t ts = data.tile_size;
|
||||
const int32_t ac = data.atlas_columns;
|
||||
|
||||
for (int32_t i = 0; i < data.spawn_count; ++i)
|
||||
{
|
||||
const ObjectSpawn& spawn = data.spawns[i];
|
||||
GameObject obj;
|
||||
obj.type = spawn.type;
|
||||
obj.position = Math::Vector2Int(
|
||||
spawn.tile_x * data.tile_size,
|
||||
spawn.tile_y * data.tile_size);
|
||||
spawn.tile_x * ts,
|
||||
spawn.tile_y * ts);
|
||||
|
||||
switch (spawn.type)
|
||||
{
|
||||
case GameObjectType::StaticPlatform:
|
||||
obj.collider = RenderData::BoundingBox2D(
|
||||
Math::Vector2Int(0, 0),
|
||||
Math::Vector2Int(data.tile_size, data.tile_size));
|
||||
Math::Vector2Int(0, 0), Math::Vector2Int(ts, ts));
|
||||
obj.solid = true;
|
||||
break;
|
||||
|
||||
case GameObjectType::LightPlatform:
|
||||
obj.collider = RenderData::BoundingBox2D(
|
||||
Math::Vector2Int(0, 0), Math::Vector2Int(ts, ts));
|
||||
obj.solid = false;
|
||||
obj.light_threshold = LightThreshold(spawn.light_min, spawn.light_max);
|
||||
break;
|
||||
|
||||
case GameObjectType::ShadowPlatform:
|
||||
obj.collider = RenderData::BoundingBox2D(
|
||||
Math::Vector2Int(0, 0),
|
||||
Math::Vector2Int(data.tile_size, data.tile_size));
|
||||
obj.solid = (spawn.type == GameObjectType::ShadowPlatform);
|
||||
Math::Vector2Int(0, 0), Math::Vector2Int(ts, ts));
|
||||
obj.solid = true;
|
||||
obj.light_threshold = LightThreshold(spawn.light_min, spawn.light_max);
|
||||
break;
|
||||
|
||||
case GameObjectType::Door:
|
||||
obj.collider = RenderData::BoundingBox2D(
|
||||
Math::Vector2Int(0, 0),
|
||||
Math::Vector2Int(data.tile_size, data.tile_size * 2));
|
||||
Math::Vector2Int(0, 0), Math::Vector2Int(ts, ts * 2));
|
||||
obj.solid = true;
|
||||
obj.light_threshold = LightThreshold(spawn.light_min, spawn.light_max);
|
||||
break;
|
||||
|
||||
case GameObjectType::Hazard:
|
||||
obj.collider = RenderData::BoundingBox2D(
|
||||
Math::Vector2Int(0, 0),
|
||||
Math::Vector2Int(data.tile_size, data.tile_size));
|
||||
Math::Vector2Int(0, ts / 2),
|
||||
Math::Vector2Int(ts, ts));
|
||||
obj.solid = false;
|
||||
break;
|
||||
|
||||
case GameObjectType::Collectible:
|
||||
obj.collider = RenderData::BoundingBox2D(
|
||||
Math::Vector2Int(4, 4),
|
||||
Math::Vector2Int(data.tile_size - 4, data.tile_size - 4));
|
||||
Math::Vector2Int(ts / 4, ts / 4),
|
||||
Math::Vector2Int(ts * 3 / 4, ts * 3 / 4));
|
||||
obj.solid = false;
|
||||
break;
|
||||
|
||||
case GameObjectType::Trigger:
|
||||
obj.collider = RenderData::BoundingBox2D(
|
||||
Math::Vector2Int(0, 0),
|
||||
Math::Vector2Int(data.tile_size, data.tile_size));
|
||||
Math::Vector2Int(0, 0), Math::Vector2Int(ts, ts));
|
||||
obj.solid = false;
|
||||
obj.trigger_action = spawn.trigger_action;
|
||||
obj.trigger_target_id = spawn.trigger_target;
|
||||
|
|
@ -82,8 +100,7 @@ namespace LightGame
|
|||
|
||||
case GameObjectType::Player:
|
||||
obj.collider = RenderData::BoundingBox2D(
|
||||
Math::Vector2Int(4, 0),
|
||||
Math::Vector2Int(12, 16));
|
||||
Math::Vector2Int(8, 0), Math::Vector2Int(24, 32));
|
||||
obj.solid = false;
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,13 @@ namespace LightGame
|
|||
for (size_t i = 0; i < objects.size(); ++i)
|
||||
{
|
||||
const GameObject& obj = objects[i];
|
||||
if (!obj.active || obj.sprite == nullptr)
|
||||
if (!obj.active)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const RenderData::Sprite* sprite = obj.sprite;
|
||||
if (!sprite)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
|
@ -28,8 +34,8 @@ namespace LightGame
|
|||
const int32_t screen_x = obj.position.x - camera.get_x();
|
||||
const int32_t screen_y = obj.position.y - camera.get_y();
|
||||
|
||||
if (screen_x + obj.sprite->width < 0 || screen_x > camera.get_screen_width() ||
|
||||
screen_y + obj.sprite->height < 0 || screen_y > camera.get_screen_height())
|
||||
if (screen_x + sprite->width < 0 || screen_x > camera.get_screen_width() ||
|
||||
screen_y + sprite->height < 0 || screen_y > camera.get_screen_height())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
|
@ -43,7 +49,7 @@ namespace LightGame
|
|||
continue;
|
||||
}
|
||||
|
||||
ctx.draw_sprite_ex(screen_x, screen_y, *obj.sprite, 1, obj.flip_h, false);
|
||||
ctx.draw_sprite_ex(screen_x, screen_y, *sprite, 1, obj.flip_h, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include "Level1Data.h"
|
||||
#include "Level2Data.h"
|
||||
#include "Level3Data.h"
|
||||
#include "sprite_atlas.h"
|
||||
|
||||
namespace LightGame
|
||||
{
|
||||
|
|
@ -44,21 +45,58 @@ namespace LightGame
|
|||
renderer_(),
|
||||
current_level_index_(0),
|
||||
player_id_(0),
|
||||
manual_light_level_(2048),
|
||||
light_step_(256),
|
||||
has_manual_override_(false),
|
||||
debug_mode_(false)
|
||||
{
|
||||
camera_.configure(screen_width, screen_height);
|
||||
camera_.set_dead_zone(60, 40);
|
||||
|
||||
static const RenderData::Sprite player_idle[] = {
|
||||
sprite_atlas::spr_player_idle_0,
|
||||
sprite_atlas::spr_player_idle_1
|
||||
};
|
||||
static const RenderData::Sprite player_run[] = {
|
||||
sprite_atlas::spr_player_run_0,
|
||||
sprite_atlas::spr_player_run_1,
|
||||
sprite_atlas::spr_player_run_2,
|
||||
sprite_atlas::spr_player_run_3
|
||||
};
|
||||
player_controller_.set_sprites(
|
||||
player_idle, 2,
|
||||
player_run, 4,
|
||||
&sprite_atlas::spr_player_jump,
|
||||
&sprite_atlas::spr_player_fall);
|
||||
}
|
||||
|
||||
void LightGameApp::update(uint32_t dt_ms)
|
||||
{
|
||||
if (photo_sensor_ && photo_sensor_->is_open())
|
||||
if (keyboard_input_)
|
||||
{
|
||||
if (keyboard_input_->is_key_down(Platform::KEY_W))
|
||||
{
|
||||
const uint32_t next = static_cast<uint32_t>(manual_light_level_) + light_step_;
|
||||
manual_light_level_ = static_cast<uint16_t>(next > 4095 ? 4095 : next);
|
||||
has_manual_override_ = true;
|
||||
}
|
||||
if (keyboard_input_->is_key_down(Platform::KEY_S))
|
||||
{
|
||||
manual_light_level_ = manual_light_level_ >= light_step_
|
||||
? manual_light_level_ - light_step_
|
||||
: 0;
|
||||
has_manual_override_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (photo_sensor_ && photo_sensor_->is_open() && !has_manual_override_)
|
||||
{
|
||||
photo_sensor_->update();
|
||||
manual_light_level_ = photo_sensor_->read_level();
|
||||
}
|
||||
|
||||
const GameState prev_state = state_manager_.get_state();
|
||||
state_manager_.update(prev_state, button_input_, dt_ms);
|
||||
state_manager_.update(prev_state, button_input_, keyboard_input_, dt_ms);
|
||||
|
||||
switch (state_manager_.get_state())
|
||||
{
|
||||
|
|
@ -86,7 +124,7 @@ namespace LightGame
|
|||
if (state_manager_.get_state() == GameState::Playing ||
|
||||
state_manager_.get_state() == GameState::Paused)
|
||||
{
|
||||
const uint16_t light = photo_sensor_ ? photo_sensor_->read_level() : 2048;
|
||||
const uint16_t light = manual_light_level_;
|
||||
renderer_.draw(ctx, level_, camera_, light_system_, light);
|
||||
|
||||
if (debug_mode_)
|
||||
|
|
@ -99,7 +137,7 @@ namespace LightGame
|
|||
|
||||
if (debug_mode_ && state_manager_.get_state() == GameState::Playing)
|
||||
{
|
||||
const uint16_t light = photo_sensor_ ? photo_sensor_->read_level() : 2048;
|
||||
const uint16_t light = manual_light_level_;
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "L:%d", light);
|
||||
ctx.draw_text(font, screen_width_ - 64, screen_height_ - 16,
|
||||
|
|
@ -130,10 +168,15 @@ namespace LightGame
|
|||
state_manager_.get_hud().current_level = level_index + 1;
|
||||
}
|
||||
|
||||
bool LightGameApp::is_action_pressed()
|
||||
{
|
||||
return button_input_ && button_input_->was_pressed();
|
||||
}
|
||||
|
||||
void LightGameApp::update_title(uint32_t dt_ms)
|
||||
{
|
||||
(void)dt_ms;
|
||||
if (button_input_ && button_input_->was_pressed())
|
||||
if (is_action_pressed())
|
||||
{
|
||||
load_level(0);
|
||||
state_manager_.set_state(GameState::Playing);
|
||||
|
|
@ -142,14 +185,14 @@ namespace LightGame
|
|||
|
||||
void LightGameApp::update_playing(uint32_t dt_ms)
|
||||
{
|
||||
const uint16_t light = photo_sensor_ ? photo_sensor_->read_level() : 2048;
|
||||
const uint16_t light = manual_light_level_;
|
||||
light_system_.update(level_.get_all_objects(), light);
|
||||
state_manager_.get_hud().light_level = light;
|
||||
|
||||
GameObject* player = level_.get_object(player_id_);
|
||||
if (player)
|
||||
{
|
||||
player_controller_.update(*player, level_, physics_, button_input_, keyboard_input_, pointer_input_, dt_ms);
|
||||
player_controller_.update(*player, level_, physics_, keyboard_input_, pointer_input_, screen_width_, screen_height_, dt_ms);
|
||||
camera_.follow(player->position);
|
||||
|
||||
if (player_controller_.is_dead())
|
||||
|
|
@ -168,7 +211,7 @@ namespace LightGame
|
|||
void LightGameApp::update_game_over(uint32_t dt_ms)
|
||||
{
|
||||
(void)dt_ms;
|
||||
if (button_input_ && button_input_->was_pressed())
|
||||
if (is_action_pressed())
|
||||
{
|
||||
state_manager_.get_hud().lives = 3;
|
||||
state_manager_.get_hud().collectibles = 0;
|
||||
|
|
@ -180,7 +223,7 @@ namespace LightGame
|
|||
void LightGameApp::update_level_complete(uint32_t dt_ms)
|
||||
{
|
||||
(void)dt_ms;
|
||||
if (button_input_ && button_input_->was_pressed())
|
||||
if (is_action_pressed())
|
||||
{
|
||||
const int32_t next = current_level_index_ + 1;
|
||||
if (next < kLevelCount)
|
||||
|
|
|
|||
|
|
@ -52,6 +52,10 @@ namespace LightGame
|
|||
int32_t current_level_index_;
|
||||
uint32_t player_id_;
|
||||
|
||||
uint16_t manual_light_level_;
|
||||
uint16_t light_step_;
|
||||
|
||||
bool has_manual_override_;
|
||||
bool debug_mode_;
|
||||
|
||||
public:
|
||||
|
|
@ -76,5 +80,6 @@ namespace LightGame
|
|||
void update_game_over(uint32_t dt_ms);
|
||||
void update_level_complete(uint32_t dt_ms);
|
||||
void check_triggers();
|
||||
bool is_action_pressed();
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,82 @@ namespace LightGame
|
|||
}
|
||||
}
|
||||
|
||||
bool Physics2D::is_tile_solid(const RenderData::Tilemap& tilemap, int32_t tile_x, int32_t tile_y) const
|
||||
{
|
||||
if (tile_x < 0 || tile_x >= tilemap.width || tile_y < 0 || tile_y >= tilemap.height)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint16_t tile_id = tilemap.get_tile(tile_x, tile_y);
|
||||
return tile_id != 0 && tile_id != RenderData::Tilemap::EmptyTile;
|
||||
}
|
||||
|
||||
void Physics2D::collide_tilemap(GameObject& obj, const RenderData::Tilemap& tilemap,
|
||||
const RenderData::BoundingBox2D& world_collider)
|
||||
{
|
||||
if (tilemap.tile_w <= 0 || tilemap.tile_h <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int32_t ts = tilemap.tile_w;
|
||||
|
||||
const int32_t tile_left = world_collider.min.x / ts;
|
||||
const int32_t tile_right = (world_collider.max.x - 1) / ts;
|
||||
const int32_t tile_top = world_collider.min.y / ts;
|
||||
const int32_t tile_bottom = (world_collider.max.y - 1) / ts;
|
||||
|
||||
for (int32_t ty = tile_top; ty <= tile_bottom; ++ty)
|
||||
{
|
||||
for (int32_t tx = tile_left; tx <= tile_right; ++tx)
|
||||
{
|
||||
if (!is_tile_solid(tilemap, tx, ty))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const RenderData::BoundingBox2D tile_box(
|
||||
Math::Vector2Int(tx * ts, ty * ts),
|
||||
Math::Vector2Int((tx + 1) * ts, (ty + 1) * ts));
|
||||
|
||||
const int32_t overlap_left = world_collider.max.x - tile_box.min.x;
|
||||
const int32_t overlap_right = tile_box.max.x - world_collider.min.x;
|
||||
const int32_t overlap_top = world_collider.max.y - tile_box.min.y;
|
||||
const int32_t overlap_bottom = tile_box.max.y - world_collider.min.y;
|
||||
|
||||
const int32_t min_x = std::min(overlap_left, overlap_right);
|
||||
const int32_t min_y = std::min(overlap_top, overlap_bottom);
|
||||
|
||||
if (min_x < min_y)
|
||||
{
|
||||
if (overlap_left < overlap_right)
|
||||
{
|
||||
obj.position.x -= overlap_left;
|
||||
}
|
||||
else
|
||||
{
|
||||
obj.position.x += overlap_right;
|
||||
}
|
||||
obj.velocity.x = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (overlap_top < overlap_bottom)
|
||||
{
|
||||
obj.position.y -= overlap_top;
|
||||
obj.velocity.y = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
obj.position.y += overlap_bottom;
|
||||
obj.velocity.y = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Physics2D::move_and_collide(GameObject& obj, Level& level, uint32_t dt_ms)
|
||||
{
|
||||
const int32_t dx = obj.velocity.x * static_cast<int32_t>(dt_ms);
|
||||
|
|
@ -48,9 +124,12 @@ namespace LightGame
|
|||
obj.position.x += move_x;
|
||||
obj.position.y += move_y;
|
||||
|
||||
const RenderData::BoundingBox2D world_collider = obj.get_world_collider();
|
||||
const auto& all_objects = level.get_all_objects();
|
||||
RenderData::BoundingBox2D world_collider = obj.get_world_collider();
|
||||
|
||||
const RenderData::Tilemap& tilemap = level.get_tilemap();
|
||||
collide_tilemap(obj, tilemap, world_collider);
|
||||
|
||||
const auto& all_objects = level.get_all_objects();
|
||||
for (size_t i = 0; i < all_objects.size(); ++i)
|
||||
{
|
||||
const GameObject& other = all_objects[i];
|
||||
|
|
@ -68,6 +147,7 @@ namespace LightGame
|
|||
other.type == GameObjectType::ShadowPlatform);
|
||||
|
||||
const RenderData::BoundingBox2D other_world = other.get_world_collider();
|
||||
world_collider = obj.get_world_collider();
|
||||
|
||||
if (!aabb_overlap(world_collider, other_world))
|
||||
{
|
||||
|
|
@ -90,11 +170,10 @@ namespace LightGame
|
|||
}
|
||||
else
|
||||
{
|
||||
const RenderData::BoundingBox2D updated_collider = obj.get_world_collider();
|
||||
const int32_t overlap_left = updated_collider.max.x - other_world.min.x;
|
||||
const int32_t overlap_right = other_world.max.x - updated_collider.min.x;
|
||||
const int32_t overlap_top = updated_collider.max.y - other_world.min.y;
|
||||
const int32_t overlap_bottom = other_world.max.y - updated_collider.min.y;
|
||||
const int32_t overlap_left = world_collider.max.x - other_world.min.x;
|
||||
const int32_t overlap_right = other_world.max.x - world_collider.min.x;
|
||||
const int32_t overlap_top = world_collider.max.y - other_world.min.y;
|
||||
const int32_t overlap_bottom = other_world.max.y - world_collider.min.y;
|
||||
|
||||
const int32_t min_x = std::min(overlap_left, overlap_right);
|
||||
const int32_t min_y = std::min(overlap_top, overlap_bottom);
|
||||
|
|
@ -140,6 +219,23 @@ namespace LightGame
|
|||
feet.min.y = feet.max.y;
|
||||
feet.max.y = feet.max.y + 2;
|
||||
|
||||
const RenderData::Tilemap& tilemap = level.get_tilemap();
|
||||
if (tilemap.tile_w > 0)
|
||||
{
|
||||
const int32_t ts = tilemap.tile_w;
|
||||
const int32_t tile_left = feet.min.x / ts;
|
||||
const int32_t tile_right = (feet.max.x - 1) / ts;
|
||||
const int32_t tile_y = feet.min.y / ts;
|
||||
|
||||
for (int32_t tx = tile_left; tx <= tile_right; ++tx)
|
||||
{
|
||||
if (is_tile_solid(tilemap, tx, tile_y))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto& all_objects = level.get_all_objects();
|
||||
for (size_t i = 0; i < all_objects.size(); ++i)
|
||||
{
|
||||
|
|
@ -158,27 +254,4 @@ namespace LightGame
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
CollisionResult Physics2D::check_collision(
|
||||
const RenderData::BoundingBox2D& moving,
|
||||
const RenderData::BoundingBox2D& stationary,
|
||||
bool one_way)
|
||||
{
|
||||
CollisionResult result;
|
||||
|
||||
if (!aabb_overlap(moving, stationary))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result.hit = true;
|
||||
result.penetration_x = std::min(
|
||||
moving.max.x - stationary.min.x,
|
||||
stationary.max.x - moving.min.x);
|
||||
result.penetration_y = std::min(
|
||||
moving.max.y - stationary.min.y,
|
||||
stationary.max.y - moving.min.y);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,11 +51,7 @@ namespace LightGame
|
|||
bool is_grounded(const GameObject& obj, const Level& level);
|
||||
|
||||
private:
|
||||
CollisionResult check_collision(
|
||||
const RenderData::BoundingBox2D& moving,
|
||||
const RenderData::BoundingBox2D& stationary,
|
||||
bool one_way);
|
||||
|
||||
void resolve_collision(GameObject& obj, const CollisionResult& result, bool from_above);
|
||||
bool is_tile_solid(const RenderData::Tilemap& tilemap, int32_t tile_x, int32_t tile_y) const;
|
||||
void collide_tilemap(GameObject& obj, const RenderData::Tilemap& tilemap, const RenderData::BoundingBox2D& world_collider);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
#include "PlayerController.h"
|
||||
#include "ButtonInput.h"
|
||||
#include "IKeyboardState.h"
|
||||
#include "PointerInput.h"
|
||||
#include <algorithm>
|
||||
|
|
@ -44,9 +43,9 @@ namespace LightGame
|
|||
}
|
||||
|
||||
void PlayerController::update(GameObject& obj, Level& level, Physics2D& physics,
|
||||
const Platform::IButtonInput* button,
|
||||
const Platform::IKeyboardState* keyboard,
|
||||
const Platform::IPointerInput* pointer,
|
||||
int32_t screen_width, int32_t screen_height,
|
||||
uint32_t dt_ms)
|
||||
{
|
||||
if (state_ == PlayerState::Dead)
|
||||
|
|
@ -59,7 +58,7 @@ namespace LightGame
|
|||
return;
|
||||
}
|
||||
|
||||
update_input(button, keyboard, pointer);
|
||||
update_input(keyboard, pointer, screen_width, screen_height);
|
||||
update_movement(obj, dt_ms);
|
||||
|
||||
physics.apply_gravity(obj, dt_ms);
|
||||
|
|
@ -111,48 +110,45 @@ namespace LightGame
|
|||
check_death(obj, level);
|
||||
}
|
||||
|
||||
void PlayerController::update_input(const Platform::IButtonInput* button,
|
||||
const Platform::IKeyboardState* keyboard,
|
||||
const Platform::IPointerInput* pointer)
|
||||
void PlayerController::update_input(const Platform::IKeyboardState* keyboard,
|
||||
const Platform::IPointerInput* pointer,
|
||||
int32_t screen_width, int32_t screen_height)
|
||||
{
|
||||
move_dir_ = 0;
|
||||
jump_held_ = false;
|
||||
|
||||
if (button)
|
||||
{
|
||||
if (button->is_down())
|
||||
{
|
||||
jump_held_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (pointer)
|
||||
{
|
||||
if (pointer->is_down())
|
||||
{
|
||||
const int32_t px = pointer->get_x();
|
||||
if (px < 400)
|
||||
const int32_t py = pointer->get_y();
|
||||
|
||||
// Top half of screen = jump (works across the full width)
|
||||
if (py < screen_height / 2)
|
||||
{
|
||||
jump_held_ = true;
|
||||
}
|
||||
|
||||
// Left 1/3 = move left, right 1/3 = move right
|
||||
if (px < screen_width / 3)
|
||||
{
|
||||
move_dir_ = -1;
|
||||
}
|
||||
else if (px > 600)
|
||||
else if (px > screen_width * 2 / 3)
|
||||
{
|
||||
move_dir_ = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
jump_held_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (keyboard)
|
||||
{
|
||||
if (keyboard->is_key_down(Platform::KEY_LEFT) || keyboard->is_key_down(Platform::KEY_A))
|
||||
if (keyboard->is_key_down(Platform::KEY_LEFT))
|
||||
move_dir_ = -1;
|
||||
if (keyboard->is_key_down(Platform::KEY_RIGHT) || keyboard->is_key_down(Platform::KEY_D))
|
||||
if (keyboard->is_key_down(Platform::KEY_RIGHT))
|
||||
move_dir_ = 1;
|
||||
if (keyboard->is_key_down(Platform::KEY_SPACE) || keyboard->is_key_down(Platform::KEY_UP) || keyboard->is_key_down(Platform::KEY_W))
|
||||
if (keyboard->is_key_down(Platform::KEY_UP))
|
||||
jump_held_ = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
namespace Platform
|
||||
{
|
||||
class IButtonInput;
|
||||
class IKeyboardState;
|
||||
class IPointerInput;
|
||||
}
|
||||
|
|
@ -72,16 +71,16 @@ namespace LightGame
|
|||
const RenderData::Sprite* fall);
|
||||
|
||||
void update(GameObject& obj, Level& level, Physics2D& physics,
|
||||
const Platform::IButtonInput* button,
|
||||
const Platform::IKeyboardState* keyboard,
|
||||
const Platform::IPointerInput* pointer,
|
||||
int32_t screen_width, int32_t screen_height,
|
||||
uint32_t dt_ms);
|
||||
|
||||
PlayerState get_state() const { return state_; }
|
||||
bool is_dead() const { return state_ == PlayerState::Dead; }
|
||||
|
||||
private:
|
||||
void update_input(const Platform::IButtonInput* button, const Platform::IKeyboardState* keyboard, const Platform::IPointerInput* pointer);
|
||||
void update_input(const Platform::IKeyboardState* keyboard, const Platform::IPointerInput* pointer, int32_t screen_width, int32_t screen_height);
|
||||
void update_movement(GameObject& obj, uint32_t dt_ms);
|
||||
void update_animation(GameObject& obj, uint32_t dt_ms);
|
||||
void check_death(GameObject& obj, Level& level);
|
||||
|
|
|
|||
|
|
@ -1,54 +1,51 @@
|
|||
#pragma once
|
||||
|
||||
#include "LevelData.h"
|
||||
#include "tile_atlas.h"
|
||||
|
||||
namespace LightGame
|
||||
{
|
||||
namespace Level1
|
||||
{
|
||||
static const int32_t kMapWidth = 50;
|
||||
static const int32_t kMapHeight = 15;
|
||||
static const int32_t kTileSize = 16;
|
||||
static const int32_t kMapWidth = 25;
|
||||
static const int32_t kMapHeight = 8;
|
||||
static const int32_t kTileSize = 32;
|
||||
|
||||
// Tile IDs: 0=empty 1=ground_top 2=ground_fill 3=platform 4=spike
|
||||
static const uint16_t tiles[] = {
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,
|
||||
1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
1,1,1,1,0,0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,1,1,
|
||||
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
|
||||
};
|
||||
|
||||
static const ObjectSpawn spawns[] = {
|
||||
{ GameObjectType::Player, 2, 11, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Collectible, 5, 11, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Collectible, 10, 10, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Collectible, 18, 11, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Hazard, 12, 14, 0, 0, TriggerAction::Death, 0 },
|
||||
{ GameObjectType::Hazard, 13, 14, 0, 0, TriggerAction::Death, 0 },
|
||||
{ GameObjectType::Hazard, 14, 14, 0, 0, TriggerAction::Death, 0 },
|
||||
{ GameObjectType::Trigger, 47, 12, 0, 0, TriggerAction::LevelComplete, 0 },
|
||||
{ GameObjectType::Player, 2, 5, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Collectible, 5, 5, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Collectible, 10, 4, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Collectible, 18, 5, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Hazard, 12, 7, 0, 0, TriggerAction::Death, 0 },
|
||||
{ GameObjectType::Hazard, 13, 7, 0, 0, TriggerAction::Death, 0 },
|
||||
{ GameObjectType::Hazard, 14, 7, 0, 0, TriggerAction::Death, 0 },
|
||||
{ GameObjectType::Trigger, 23, 5, 0, 0, TriggerAction::LevelComplete, 0 },
|
||||
};
|
||||
|
||||
static const RenderData::Image kAtlas(tile_atlas_pixels, tile_atlas_width, tile_atlas_height);
|
||||
|
||||
static const LevelData data = {
|
||||
tiles,
|
||||
kMapWidth,
|
||||
kMapHeight,
|
||||
kTileSize,
|
||||
nullptr,
|
||||
0,
|
||||
&kAtlas,
|
||||
tile_atlas_columns,
|
||||
spawns,
|
||||
8,
|
||||
Math::Vector2Int(2 * kTileSize, 11 * kTileSize),
|
||||
Math::Vector2Int(2 * kTileSize, 5 * kTileSize),
|
||||
0,
|
||||
0,
|
||||
kMapWidth * kTileSize,
|
||||
|
|
|
|||
|
|
@ -1,64 +1,61 @@
|
|||
#pragma once
|
||||
|
||||
#include "LevelData.h"
|
||||
#include "tile_atlas.h"
|
||||
|
||||
namespace LightGame
|
||||
{
|
||||
namespace Level2
|
||||
{
|
||||
static const int32_t kMapWidth = 50;
|
||||
static const int32_t kMapHeight = 15;
|
||||
static const int32_t kTileSize = 16;
|
||||
static const int32_t kMapWidth = 25;
|
||||
static const int32_t kMapHeight = 8;
|
||||
static const int32_t kTileSize = 32;
|
||||
|
||||
// Tile IDs: 0=empty 1=ground_top 2=ground_fill 5=light_platform_off 6=light_platform_on
|
||||
static const uint16_t tiles[] = {
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,
|
||||
1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,
|
||||
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
|
||||
};
|
||||
|
||||
static const ObjectSpawn spawns[] = {
|
||||
{ GameObjectType::Player, 2, 11, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Player, 2, 5, 0, 0, TriggerAction::None, 0 },
|
||||
|
||||
{ GameObjectType::LightPlatform, 8, 10, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 12, 9, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 16, 8, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 20, 9, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 24, 10, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 28, 9, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 32, 8, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 36, 9, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 40, 10, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 44, 11, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 4, 4, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 6, 3, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 8, 2, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 10, 3, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 12, 4, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 14, 3, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 16, 2, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 18, 3, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 20, 4, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 22, 5, 1500, 0, TriggerAction::None, 0 },
|
||||
|
||||
{ GameObjectType::Collectible, 12, 8, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Collectible, 20, 8, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Collectible, 32, 7, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Collectible, 6, 2, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Collectible, 10, 2, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Collectible, 16, 1, 0, 0, TriggerAction::None, 0 },
|
||||
|
||||
{ GameObjectType::Trigger, 48, 12, 0, 0, TriggerAction::LevelComplete, 0 },
|
||||
{ GameObjectType::Trigger, 24, 5, 0, 0, TriggerAction::LevelComplete, 0 },
|
||||
};
|
||||
|
||||
static const RenderData::Image kAtlas(tile_atlas_pixels, tile_atlas_width, tile_atlas_height);
|
||||
|
||||
static const LevelData data = {
|
||||
tiles,
|
||||
kMapWidth,
|
||||
kMapHeight,
|
||||
kTileSize,
|
||||
nullptr,
|
||||
0,
|
||||
&kAtlas,
|
||||
tile_atlas_columns,
|
||||
spawns,
|
||||
15,
|
||||
Math::Vector2Int(2 * kTileSize, 11 * kTileSize),
|
||||
Math::Vector2Int(2 * kTileSize, 5 * kTileSize),
|
||||
0,
|
||||
0,
|
||||
kMapWidth * kTileSize,
|
||||
|
|
|
|||
|
|
@ -1,66 +1,64 @@
|
|||
#pragma once
|
||||
|
||||
#include "LevelData.h"
|
||||
#include "tile_atlas.h"
|
||||
|
||||
namespace LightGame
|
||||
{
|
||||
namespace Level3
|
||||
{
|
||||
static const int32_t kMapWidth = 50;
|
||||
static const int32_t kMapHeight = 15;
|
||||
static const int32_t kTileSize = 16;
|
||||
static const int32_t kMapWidth = 25;
|
||||
static const int32_t kMapHeight = 8;
|
||||
static const int32_t kTileSize = 32;
|
||||
|
||||
// Tile IDs: 0=empty 1=ground_top 2=ground_fill
|
||||
// Objects: LightPlatform, ShadowPlatform, Door via spawns
|
||||
static const uint16_t tiles[] = {
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,
|
||||
1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,
|
||||
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
|
||||
};
|
||||
|
||||
static const ObjectSpawn spawns[] = {
|
||||
{ GameObjectType::Player, 2, 11, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Player, 2, 5, 0, 0, TriggerAction::None, 0 },
|
||||
|
||||
{ GameObjectType::LightPlatform, 8, 10, 2000, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::ShadowPlatform, 12, 8, 0, 1000, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 16, 10, 2000, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::ShadowPlatform, 20, 8, 0, 1000, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 24, 10, 2000, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 4, 4, 2000, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::ShadowPlatform, 6, 3, 0, 1000, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 8, 4, 2000, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::ShadowPlatform, 10, 3, 0, 1000, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 12, 4, 2000, 0, TriggerAction::None, 0 },
|
||||
|
||||
{ GameObjectType::Door, 30, 10, 1500, 2500, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Door, 15, 4, 1500, 2500, TriggerAction::None, 0 },
|
||||
|
||||
{ GameObjectType::LightPlatform, 34, 10, 2000, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::ShadowPlatform, 38, 8, 0, 1000, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 42, 10, 2000, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::ShadowPlatform, 44, 11, 0, 1000, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 17, 4, 2000, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::ShadowPlatform, 19, 3, 0, 1000, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 21, 4, 2000, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::ShadowPlatform, 22, 5, 0, 1000, TriggerAction::None, 0 },
|
||||
|
||||
{ GameObjectType::Collectible, 12, 7, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Collectible, 20, 7, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Collectible, 38, 7, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Collectible, 6, 2, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Collectible, 10, 2, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Collectible, 19, 2, 0, 0, TriggerAction::None, 0 },
|
||||
|
||||
{ GameObjectType::Trigger, 48, 12, 0, 0, TriggerAction::LevelComplete, 0 },
|
||||
{ GameObjectType::Trigger, 24, 5, 0, 0, TriggerAction::LevelComplete, 0 },
|
||||
};
|
||||
|
||||
static const RenderData::Image kAtlas(tile_atlas_pixels, tile_atlas_width, tile_atlas_height);
|
||||
|
||||
static const LevelData data = {
|
||||
tiles,
|
||||
kMapWidth,
|
||||
kMapHeight,
|
||||
kTileSize,
|
||||
nullptr,
|
||||
0,
|
||||
&kAtlas,
|
||||
tile_atlas_columns,
|
||||
spawns,
|
||||
15,
|
||||
Math::Vector2Int(2 * kTileSize, 11 * kTileSize),
|
||||
Math::Vector2Int(2 * kTileSize, 5 * kTileSize),
|
||||
0,
|
||||
0,
|
||||
kMapWidth * kTileSize,
|
||||
|
|
|
|||
|
|
@ -7,11 +7,14 @@ namespace Platform
|
|||
constexpr int KEY_A = 4;
|
||||
constexpr int KEY_D = 7;
|
||||
constexpr int KEY_W = 26;
|
||||
constexpr int KEY_S = 22;
|
||||
constexpr int KEY_SPACE = 44;
|
||||
constexpr int KEY_LEFT = 80;
|
||||
constexpr int KEY_RIGHT = 79;
|
||||
constexpr int KEY_UP = 82;
|
||||
constexpr int KEY_DOWN = 81;
|
||||
constexpr int KEY_ESC = 41;
|
||||
constexpr int KEY_RETURN = 40;
|
||||
|
||||
class IKeyboardState
|
||||
{
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ namespace Platform
|
|||
return false;
|
||||
}
|
||||
|
||||
SDL_StopTextInput();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,45 +50,9 @@ namespace Platform
|
|||
|
||||
void SdlPhotoSensor::update()
|
||||
{
|
||||
if (!opened_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_PumpEvents();
|
||||
const Uint8* keyboard_state = SDL_GetKeyboardState(nullptr);
|
||||
if (!keyboard_state)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (keyboard_state[SDL_SCANCODE_UP])
|
||||
{
|
||||
uint32_t next = static_cast<uint32_t>(level_) + step_;
|
||||
level_ = static_cast<uint16_t>(std::min(next, static_cast<uint32_t>(max_level_)));
|
||||
}
|
||||
|
||||
if (keyboard_state[SDL_SCANCODE_DOWN])
|
||||
{
|
||||
if (level_ >= step_)
|
||||
{
|
||||
level_ -= step_;
|
||||
}
|
||||
else
|
||||
{
|
||||
level_ = min_level_;
|
||||
}
|
||||
}
|
||||
|
||||
if (keyboard_state[SDL_SCANCODE_HOME])
|
||||
{
|
||||
level_ = max_level_;
|
||||
}
|
||||
|
||||
if (keyboard_state[SDL_SCANCODE_END])
|
||||
{
|
||||
level_ = min_level_;
|
||||
}
|
||||
// No-op: keyboard-based brightness control is handled in the game layer
|
||||
// (LightGameApp) through IKeyboardState, not at the platform level.
|
||||
(void)opened_;
|
||||
}
|
||||
|
||||
void SdlPhotoSensor::shutdown()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
"""
|
||||
将多个独立 sprite PNG 按 JSON 配置打包为 sprite atlas,生成 C++ header。
|
||||
|
||||
用法:
|
||||
python tools/pack_sprite_atlas.py config.json output.h
|
||||
|
||||
JSON 格式:
|
||||
{
|
||||
"atlas_width": 512,
|
||||
"atlas_height": 512,
|
||||
"sprites": [
|
||||
{ "name": "player_idle_0", "file": "player_idle_0.png" },
|
||||
{ "name": "player_idle_1", "file": "player_idle_1.png" },
|
||||
...
|
||||
]
|
||||
}
|
||||
|
||||
- atlas_width / atlas_height: 输出 atlas 尺寸
|
||||
- sprites: 按顺序排列的 sprite 列表
|
||||
- 每个 sprite 保持原始尺寸,从左到右、从上到下排列
|
||||
- 生成的 header 包含每个 sprite 的子矩形信息
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def pack(config_path: str, output_path: str):
|
||||
config_dir = os.path.dirname(os.path.abspath(config_path))
|
||||
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
cfg = json.load(f)
|
||||
|
||||
atlas_w = cfg["atlas_width"]
|
||||
atlas_h = cfg["atlas_height"]
|
||||
sprites = cfg["sprites"]
|
||||
|
||||
atlas = Image.new("RGBA", (atlas_w, atlas_h), (0, 0, 0, 0))
|
||||
|
||||
regions = []
|
||||
cursor_x = 0
|
||||
cursor_y = 0
|
||||
row_height = 0
|
||||
|
||||
for s in sprites:
|
||||
png_path = os.path.join(config_dir, s["file"])
|
||||
img = Image.open(png_path).convert("RGBA")
|
||||
w, h = img.size
|
||||
|
||||
if cursor_x + w > atlas_w:
|
||||
cursor_x = 0
|
||||
cursor_y += row_height
|
||||
row_height = 0
|
||||
|
||||
if cursor_y + h > atlas_h:
|
||||
print(f"错误: atlas 空间不足,sprite '{s['name']}' 放不下")
|
||||
sys.exit(1)
|
||||
|
||||
atlas.paste(img, (cursor_x, cursor_y))
|
||||
regions.append({
|
||||
"name": s["name"],
|
||||
"x": cursor_x,
|
||||
"y": cursor_y,
|
||||
"w": w,
|
||||
"h": h,
|
||||
})
|
||||
|
||||
cursor_x += w
|
||||
row_height = max(row_height, h)
|
||||
|
||||
var_name = os.path.splitext(os.path.basename(output_path))[0]
|
||||
|
||||
pixels = list(atlas.getdata())
|
||||
packed = []
|
||||
for r, g, b, a in pixels:
|
||||
packed.append(
|
||||
((r >> 3) << 11)
|
||||
| ((g >> 3) << 6)
|
||||
| ((b >> 3) << 1)
|
||||
| (1 if a else 0)
|
||||
)
|
||||
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
f.write("// Auto-generated by tools/pack_sprite_atlas.py\n")
|
||||
f.write(f"#pragma once\n#include <cstdint>\n#include \"Image.h\"\n#include \"Sprite.h\"\n\n")
|
||||
f.write(f"namespace {var_name} {{\n\n")
|
||||
f.write(f"static const int32_t atlas_width = {atlas_w};\n")
|
||||
f.write(f"static const int32_t atlas_height = {atlas_h};\n\n")
|
||||
f.write(f"static const uint16_t atlas_pixels[] = {{\n")
|
||||
|
||||
for i in range(0, len(packed), 16):
|
||||
chunk = packed[i : i + 16]
|
||||
line = ", ".join(f"0x{p:04X}" for p in chunk)
|
||||
f.write(f" {line},\n")
|
||||
|
||||
f.write("};\n\n")
|
||||
f.write(f"static const RenderData::Image atlas_image(atlas_pixels, atlas_width, atlas_height);\n\n")
|
||||
|
||||
for r in regions:
|
||||
f.write(f"static const RenderData::Sprite spr_{r['name']}("
|
||||
f"&atlas_image, {r['x']}, {r['y']}, {r['w']}, {r['h']});\n")
|
||||
|
||||
f.write(f"\n}} // namespace {var_name}\n")
|
||||
|
||||
print(f"已生成: {output_path}")
|
||||
print(f" atlas: {atlas_w}x{atlas_h}, {len(regions)} sprites")
|
||||
for r in regions:
|
||||
print(f" {r['name']}: ({r['x']},{r['y']}) {r['w']}x{r['h']}")
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print(f"用法: {sys.argv[0]} config.json output.h")
|
||||
sys.exit(1)
|
||||
|
||||
pack(sys.argv[1], sys.argv[2])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
"""
|
||||
将多个独立 tile PNG 按 JSON 配置打包为 tile atlas,生成 C++ header。
|
||||
|
||||
用法:
|
||||
python tools/pack_tile_atlas.py config.json output.h
|
||||
|
||||
JSON 格式:
|
||||
{
|
||||
"tile_size": 32,
|
||||
"columns": 4,
|
||||
"tiles": [
|
||||
{ "id": 0, "file": "empty.png" },
|
||||
{ "id": 1, "file": "ground_top.png" },
|
||||
...
|
||||
]
|
||||
}
|
||||
|
||||
- tile_size: 每个 tile 的像素尺寸(正方形)
|
||||
- columns: atlas 每行放几个 tile
|
||||
- tiles: 按 tile_id 排序的列表,id 从 0 开始连续
|
||||
- file: 相对于 config.json 所在目录的 PNG 路径
|
||||
- 未指定的 tile_id 自动填充透明
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import math
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def pack(config_path: str, output_path: str):
|
||||
config_dir = os.path.dirname(os.path.abspath(config_path))
|
||||
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
cfg = json.load(f)
|
||||
|
||||
tile_size = cfg["tile_size"]
|
||||
columns = cfg["columns"]
|
||||
tiles = cfg["tiles"]
|
||||
|
||||
max_id = max(t["id"] for t in tiles)
|
||||
tile_count = max_id + 1
|
||||
rows = (tile_count + columns - 1) // columns
|
||||
|
||||
atlas_w = columns * tile_size
|
||||
atlas_h = rows * tile_size
|
||||
|
||||
atlas = Image.new("RGBA", (atlas_w, atlas_h), (0, 0, 0, 0))
|
||||
|
||||
tile_map = {t["id"]: t for t in tiles}
|
||||
|
||||
for tid in range(tile_count):
|
||||
col = tid % columns
|
||||
row = tid // columns
|
||||
x = col * tile_size
|
||||
y = row * tile_size
|
||||
|
||||
if tid in tile_map:
|
||||
png_path = os.path.join(config_dir, tile_map[tid]["file"])
|
||||
img = Image.open(png_path).convert("RGBA")
|
||||
if img.size != (tile_size, tile_size):
|
||||
img = img.resize((tile_size, tile_size), Image.NEAREST)
|
||||
atlas.paste(img, (x, y))
|
||||
|
||||
var_name = os.path.splitext(os.path.basename(output_path))[0]
|
||||
|
||||
pixels = list(atlas.getdata())
|
||||
packed = []
|
||||
for r, g, b, a in pixels:
|
||||
packed.append(
|
||||
((r >> 3) << 11)
|
||||
| ((g >> 3) << 6)
|
||||
| ((b >> 3) << 1)
|
||||
| (1 if a else 0)
|
||||
)
|
||||
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
f.write("// Auto-generated by tools/pack_tile_atlas.py\n")
|
||||
f.write(f"#pragma once\n#include <cstdint>\n#include \"Image.h\"\n\n")
|
||||
f.write(f"static const int32_t {var_name}_width = {atlas_w};\n")
|
||||
f.write(f"static const int32_t {var_name}_height = {atlas_h};\n")
|
||||
f.write(f"static const int32_t {var_name}_tile_size = {tile_size};\n")
|
||||
f.write(f"static const int32_t {var_name}_columns = {columns};\n\n")
|
||||
f.write(f"static const uint16_t {var_name}_pixels[] = {{\n")
|
||||
|
||||
for i in range(0, len(packed), 16):
|
||||
chunk = packed[i : i + 16]
|
||||
line = ", ".join(f"0x{p:04X}" for p in chunk)
|
||||
f.write(f" {line},\n")
|
||||
|
||||
f.write("};\n\n")
|
||||
f.write(f"static const RenderData::Image {var_name}_image({var_name}_pixels, {var_name}_width, {var_name}_height);\n")
|
||||
|
||||
print(f"已生成: {output_path}")
|
||||
print(f" atlas: {atlas_w}x{atlas_h}, {tile_count} tiles ({columns}x{rows})")
|
||||
print(f" var: {var_name}")
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print(f"用法: {sys.argv[0]} config.json output.h")
|
||||
sys.exit(1)
|
||||
|
||||
pack(sys.argv[1], sys.argv[2])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue