LightGame 完成 Windows

This commit is contained in:
SepComet 2026-06-14 09:42:27 +08:00
parent 254a8ce5c8
commit 786170f40a
50 changed files with 85787 additions and 1392 deletions

1
.gitignore vendored
View File

@ -62,3 +62,4 @@ build-*
assets/test
gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz
/tools/__pycache__

View File

@ -223,6 +223,16 @@ src/Apps/Game/generated/tom_atlas.h
`assets/sprite/` 用于存放测试用 PNG sprite 源文件及转换后的头文件Tom 主游戏不依赖它。Tom 游戏的 atlas 资源由 `src/Apps/Game/tools/asset_pipeline/SpriteAssetTool.cpp``src/Apps/Game/assets/raw/` 读取原始 PNG 生成。
### LightGame Tile Atlas
LightGame 的 tile atlas 配置位于 `assets/tile/tile_atlas.json`,生成结果位于 `src/Apps/LightGame/generated/tile_atlas.h`
```bash
python tools/pack_tile_atlas.py assets/tile/tile_atlas.json src/Apps/LightGame/generated/tile_atlas.h
```
当前 tile id 约定集中在 `src/Apps/LightGame/src/engine/TileIds.h``0` 是空透明 tile`1..4` 是背景装饰;`5..11` 是可碰撞地形;`12`/`22` 是伤害尖刺;`13..18` 是光照平台和门的状态贴图;`19..21` 是金币、旗帜和 checkpoint`23` 是藤蔓装饰。LightGame 关卡支持可选 `background_tiles` 视觉层,该层先于主 tilemap 渲染,不参与碰撞、死亡、触发器或光照 tile 规则。
### Bitmap Font 转换
像素字体图集使用 `tools/gen_font_atlas.py` 生成:

83069
SampleScene.unity Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 174 B

After

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 301 B

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,22 +1,30 @@
{
"tile_size": 32,
"columns": 4,
"columns": 6,
"tiles": [
{ "id": 0, "file": "empty.png" },
{ "id": 1, "file": "ground_top.png" },
{ "id": 2, "file": "ground_fill.png" },
{ "id": 3, "file": "platform.png" },
{ "id": 4, "file": "spike.png" },
{ "id": 5, "file": "light_platform_off.png" },
{ "id": 6, "file": "light_platform_on.png" },
{ "id": 7, "file": "shadow_platform_off.png" },
{ "id": 8, "file": "shadow_platform_on.png" },
{ "id": 9, "file": "door_closed.png" },
{ "id": 10, "file": "door_open.png" },
{ "id": 11, "file": "coin.png" },
{ "id": 12, "file": "flag.png" },
{ "id": 13, "file": "checkpoint.png" },
{ "id": 14, "file": "spike_ceiling.png" },
{ "id": 15, "file": "decor_vine.png" }
{ "id": 1, "file": "background_1.png" },
{ "id": 2, "file": "background_2.png" },
{ "id": 3, "file": "background_3.png" },
{ "id": 4, "file": "background_4.png" },
{ "id": 5, "file": "ground_top_green.png" },
{ "id": 6, "file": "ground_fill_green.png" },
{ "id": 7, "file": "ground_top_gray.png" },
{ "id": 8, "file": "ground_fill_gray.png" },
{ "id": 9, "file": "ground_top_purple.png" },
{ "id": 10, "file": "ground_fill_purple.png" },
{ "id": 11, "file": "platform.png" },
{ "id": 12, "file": "spike.png" },
{ "id": 13, "file": "light_platform_off.png" },
{ "id": 14, "file": "light_platform_on.png" },
{ "id": 15, "file": "shadow_platform_off.png" },
{ "id": 16, "file": "shadow_platform_on.png" },
{ "id": 17, "file": "door_closed.png" },
{ "id": 18, "file": "door_open.png" },
{ "id": 19, "file": "coin.png" },
{ "id": 20, "file": "flag.png" },
{ "id": 21, "file": "checkpoint.png" },
{ "id": 22, "file": "spike_ceiling.png" },
{ "id": 23, "file": "decor_vine.png" }
]
}

View File

@ -7,6 +7,7 @@ set(LIGHTGAME_SOURCES
src/engine/LevelRenderer.cpp
src/engine/GameStateManager.cpp
src/engine/LightGameApp.cpp
src/engine/RoomLayout.cpp
src/systems/LightEffectSystem.cpp
src/editor/LevelEditor.cpp
src/main.cpp

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@
#include "DrawContext.h"
#include "BitmapFont.h"
#include "LevelLoader.h"
#include "TileIds.h"
#include "imgui.h"
#include "tile_atlas.h"
#include <algorithm>
@ -14,6 +15,11 @@ namespace LightGame
{
namespace
{
static const int32_t kRoomTilesW = 32;
static const int32_t kRoomTilesH = 18;
static const int32_t kWorldRoomsW = 3;
static const int32_t kWorldRoomsH = 3;
static RenderData::Sprite MakeTileSprite(const RenderData::Image* atlas, int32_t tile_id,
int32_t tile_size, int32_t atlas_columns)
{
@ -22,13 +28,13 @@ namespace LightGame
return RenderData::Sprite(atlas, col * tile_size, row * tile_size, tile_size, tile_size);
}
static const RenderData::Sprite spr_platform = MakeTileSprite(&tile_atlas_image, 3, 32, 4);
static const RenderData::Sprite spr_spike = MakeTileSprite(&tile_atlas_image, 4, 32, 4);
static const RenderData::Sprite spr_light_platform = MakeTileSprite(&tile_atlas_image, 6, 32, 4);
static const RenderData::Sprite spr_shadow_platform = MakeTileSprite(&tile_atlas_image, 8, 32, 4);
static const RenderData::Sprite spr_door = MakeTileSprite(&tile_atlas_image, 9, 32, 4);
static const RenderData::Sprite spr_coin = MakeTileSprite(&tile_atlas_image, 11, 32, 4);
static const RenderData::Sprite spr_checkpoint = MakeTileSprite(&tile_atlas_image, 13, 32, 4);
static const RenderData::Sprite spr_platform = MakeTileSprite(&tile_atlas_image, TileId::Platform, 32, tile_atlas_columns);
static const RenderData::Sprite spr_spike = MakeTileSprite(&tile_atlas_image, TileId::Spike, 32, tile_atlas_columns);
static const RenderData::Sprite spr_light_platform = MakeTileSprite(&tile_atlas_image, TileId::LightPlatformOn, 32, tile_atlas_columns);
static const RenderData::Sprite spr_shadow_platform = MakeTileSprite(&tile_atlas_image, TileId::ShadowPlatformOn, 32, tile_atlas_columns);
static const RenderData::Sprite spr_door = MakeTileSprite(&tile_atlas_image, TileId::DoorClosed, 32, tile_atlas_columns);
static const RenderData::Sprite spr_coin = MakeTileSprite(&tile_atlas_image, TileId::Coin, 32, tile_atlas_columns);
static const RenderData::Sprite spr_checkpoint = MakeTileSprite(&tile_atlas_image, TileId::Checkpoint, 32, tile_atlas_columns);
static const char* ObjTypeToString(GameObjectType type)
{
@ -63,12 +69,15 @@ namespace LightGame
active_(false),
should_quit_(false),
current_tool_(Tool::TileBrush),
selected_tile_id_(1),
selected_layer_(TileLayer::Foreground),
selected_tile_id_(TileId::GroundTopGreen),
selected_object_type_(GameObjectType::Player),
selected_object_id_(0),
preview_light_level_(2048),
show_grid_(true),
show_room_guides_(true),
show_colliders_(false),
show_background_layer_(true),
map_width_(0),
map_height_(0),
tile_size_(32),
@ -77,7 +86,7 @@ namespace LightGame
last_tile_y_(-1),
pending_load_level_(-1)
{
camera_.configure(screen_width_, screen_height_);
camera_.configure(screen_width_, screen_height_ - LevelRenderer::kTopBlackBorder);
std::memset(export_name_, 0, sizeof(export_name_));
export_open_ = false;
}
@ -87,7 +96,7 @@ namespace LightGame
active_ = active;
if (active)
{
camera_.configure(screen_width_, screen_height_);
camera_.configure(screen_width_, screen_height_ - LevelRenderer::kTopBlackBorder);
}
}
@ -98,6 +107,15 @@ namespace LightGame
map_width_ = data.map_width;
map_height_ = data.map_height;
tile_size_ = data.tile_size;
if (!level_.get_background_tiles())
{
const int32_t total = map_width_ * map_height_;
std::vector<uint16_t> empty_tiles(static_cast<size_t>(total), 0);
RenderData::Tilemap background_tilemap = level_.get_tilemap();
background_tilemap.tiles = empty_tiles.data();
level_.set_background_tilemap(background_tilemap);
level_.init_background_buffer(empty_tiles.data(), total);
}
camera_.set_level_bounds(LevelBounds(0, 0, map_width_ * tile_size_, map_height_ * tile_size_));
camera_.snap_to(level_.get_spawn_point());
selected_object_id_ = 0;
@ -124,6 +142,11 @@ namespace LightGame
level_.set_tilemap(tilemap);
level_.init_tile_buffer(empty_tiles.data(), total);
RenderData::Tilemap background_tilemap = tilemap;
background_tilemap.tiles = empty_tiles.data();
level_.set_background_tilemap(background_tilemap);
level_.init_background_buffer(empty_tiles.data(), total);
level_.set_bounds(LevelBounds(0, 0, width * tile_size, height * tile_size));
level_.set_spawn_point(Math::Vector2Int(tile_size * 2, tile_size * 2));
@ -161,6 +184,29 @@ namespace LightGame
// 中键拖拽可后续扩展
(void)pointer;
}
// UI 面板会遮挡视图边缘,允许相机超出地图边界以便编辑被遮挡处的瓦片。
const int32_t kUiLeftMargin = 160; // Tools 工具栏宽 140多留余量
const int32_t kUiRightMargin = 240; // Properties 面板宽 220
const int32_t kUiTopMargin = 32; // 主菜单栏高度
const int32_t kUiBottomMargin = 32; // 状态栏高度
const int32_t world_w = map_width_ * tile_size_;
const int32_t world_h = map_height_ * tile_size_;
int32_t min_x = -kUiLeftMargin;
int32_t min_y = -kUiTopMargin;
int32_t max_x = world_w - camera_.get_screen_width() + kUiRightMargin;
int32_t max_y = world_h - camera_.get_screen_height() + kUiBottomMargin;
if (max_x < min_x) max_x = min_x;
if (max_y < min_y) max_y = min_y;
int32_t clamped_x = camera_.get_x();
int32_t clamped_y = camera_.get_y();
if (clamped_x < min_x) clamped_x = min_x;
if (clamped_y < min_y) clamped_y = min_y;
if (clamped_x > max_x) clamped_x = max_x;
if (clamped_y > max_y) clamped_y = max_y;
camera_.set_position(clamped_x, clamped_y);
}
void LevelEditor::handle_mouse(Platform::IPointerInput* pointer)
@ -205,13 +251,18 @@ namespace LightGame
void LevelEditor::paint_tile(int32_t tile_x, int32_t tile_y, uint16_t tile_id)
{
uint16_t* tiles = level_.get_mutable_tiles();
uint16_t* tiles = (selected_layer_ == TileLayer::Foreground)
? level_.get_mutable_tiles()
: level_.get_mutable_background_tiles();
if (!tiles) return;
const int32_t idx = tile_y * map_width_ + tile_x;
tiles[idx] = tile_id;
if (selected_layer_ == TileLayer::Foreground)
{
uint16_t* orig = const_cast<uint16_t*>(level_.get_original_tiles());
if (orig) orig[idx] = tile_id;
}
}
void LevelEditor::place_object(int32_t tile_x, int32_t tile_y)
{
@ -224,10 +275,27 @@ namespace LightGame
switch (selected_object_type_)
{
case GameObjectType::Player:
{
std::vector<uint32_t> old_player_ids;
const auto& objects = level_.get_all_objects();
for (size_t i = 0; i < objects.size(); ++i)
{
if (objects[i].type == GameObjectType::Player)
{
old_player_ids.push_back(objects[i].id);
}
}
for (size_t i = 0; i < old_player_ids.size(); ++i)
{
level_.remove_object(old_player_ids[i]);
}
selected_object_id_ = 0;
obj.collider = RenderData::BoundingBox2D(Math::Vector2Int(8, 0), Math::Vector2Int(24, 32));
obj.solid = false;
level_.set_spawn_point(obj.position);
break;
}
case GameObjectType::LightPlatform:
obj.collider = RenderData::BoundingBox2D(Math::Vector2Int(0, 0), Math::Vector2Int(ts, ts));
obj.solid = false;
@ -292,9 +360,9 @@ namespace LightGame
light_system_.update(level_.get_all_objects(), preview_light_level_);
light_system_.update_tilemap(level_, preview_light_level_);
renderer_.draw(ctx, level_, camera_, light_system_, preview_light_level_);
renderer_.draw(ctx, level_, camera_, light_system_, preview_light_level_, show_background_layer_);
if (show_grid_) draw_grid(ctx);
if (show_grid_ || show_room_guides_) draw_grid(ctx);
draw_spawn_point(ctx);
if (selected_object_id_ != 0) draw_selection_highlight(ctx);
if (show_colliders_) renderer_.draw_debug(ctx, level_, camera_, font);
@ -317,9 +385,11 @@ namespace LightGame
const int32_t cam_y = camera_.get_y();
const int32_t start_x = (cam_x / ts) * ts;
const int32_t start_y = (cam_y / ts) * ts;
const int32_t end_x = cam_x + screen_width_ + ts;
const int32_t end_y = cam_y + screen_height_ + ts;
const int32_t end_x = cam_x + camera_.get_screen_width() + ts;
const int32_t end_y = cam_y + camera_.get_screen_height() + ts;
if (show_grid_)
{
const RenderData::Color grid_color(80, 80, 80, 128);
for (int32_t x = start_x; x < end_x; x += ts)
@ -334,16 +404,48 @@ namespace LightGame
}
}
if (show_room_guides_)
{
const RenderData::Color room_color(255, 196, 64, 255);
const int32_t room_w = kRoomTilesW * ts;
const int32_t room_h = kRoomTilesH * ts;
for (int32_t x = room_w; x < map_width_ * ts; x += room_w)
{
const int32_t sx = x - cam_x;
ctx.draw_line(Math::Vector2Int(sx, LightGame::LevelRenderer::kTopBlackBorder),
Math::Vector2Int(sx, screen_height_), room_color);
ctx.draw_line(Math::Vector2Int(sx + 1, LightGame::LevelRenderer::kTopBlackBorder),
Math::Vector2Int(sx + 1, screen_height_), room_color);
}
for (int32_t y = room_h; y < map_height_ * ts; y += room_h)
{
const int32_t sy = y - cam_y + LightGame::LevelRenderer::kTopBlackBorder;
ctx.draw_line(Math::Vector2Int(0, sy), Math::Vector2Int(screen_width_, sy), room_color);
ctx.draw_line(Math::Vector2Int(0, sy + 1), Math::Vector2Int(screen_width_, sy + 1), room_color);
}
}
}
void LevelEditor::draw_spawn_point(Core::DrawContext& ctx)
{
const Math::Vector2Int sp = level_.get_spawn_point();
const int32_t sx = sp.x - camera_.get_x();
const int32_t sy = sp.y - camera_.get_y() + LightGame::LevelRenderer::kTopBlackBorder;
if (sx < -16 || sx > screen_width_ + 16 || sy < -16 || sy > screen_height_ + 16) return;
const int32_t ts = tile_size_;
if (sx + ts < 0 || sx > screen_width_ || sy + ts < 0 || sy > screen_height_) return;
const RenderData::Color color(0, 255, 0, 255);
ctx.draw_line(Math::Vector2Int(sx - 8, sy - 8), Math::Vector2Int(sx + 8, sy + 8), color);
ctx.draw_line(Math::Vector2Int(sx + 8, sy - 8), Math::Vector2Int(sx - 8, sy + 8), color);
ctx.draw_line(Math::Vector2Int(sx, sy), Math::Vector2Int(sx + ts, sy), color);
ctx.draw_line(Math::Vector2Int(sx + ts, sy), Math::Vector2Int(sx + ts, sy + ts), color);
ctx.draw_line(Math::Vector2Int(sx + ts, sy + ts), Math::Vector2Int(sx, sy + ts), color);
ctx.draw_line(Math::Vector2Int(sx, sy + ts), Math::Vector2Int(sx, sy), color);
const int32_t cx = sx + ts / 2;
const int32_t cy = sy + ts / 2;
ctx.draw_line(Math::Vector2Int(cx - 8, cy - 8), Math::Vector2Int(cx + 8, cy + 8), color);
ctx.draw_line(Math::Vector2Int(cx + 8, cy - 8), Math::Vector2Int(cx - 8, cy + 8), color);
}
void LevelEditor::draw_selection_highlight(Core::DrawContext& ctx)
@ -372,15 +474,17 @@ namespace LightGame
{
if (ImGui::BeginMenu("File"))
{
if (ImGui::MenuItem("New Level", "Ctrl+N"))
if (ImGui::MenuItem("New 3x3 World", "Ctrl+N"))
{
new_level(32, 18, 32);
new_level(kRoomTilesW * kWorldRoomsW, kRoomTilesH * kWorldRoomsH, 32);
}
if (ImGui::MenuItem("New Single Room"))
{
new_level(kRoomTilesW, kRoomTilesH, 32);
}
if (ImGui::BeginMenu("Load Level"))
{
if (ImGui::MenuItem("Level 1")) pending_load_level_ = 0;
if (ImGui::MenuItem("Level 2")) pending_load_level_ = 1;
if (ImGui::MenuItem("Level 3")) pending_load_level_ = 2;
if (ImGui::MenuItem("Level Total 3x3")) pending_load_level_ = 0;
ImGui::EndMenu();
}
if (ImGui::MenuItem("Export to Header..."))
@ -396,7 +500,9 @@ namespace LightGame
if (ImGui::BeginMenu("View"))
{
ImGui::MenuItem("Show Grid", nullptr, &show_grid_);
ImGui::MenuItem("Show Room Guides", nullptr, &show_room_guides_);
ImGui::MenuItem("Show Colliders", nullptr, &show_colliders_);
ImGui::MenuItem("Show Background", nullptr, &show_background_layer_);
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
@ -423,17 +529,89 @@ namespace LightGame
ImGui::Separator();
if (current_tool_ == Tool::TileBrush || current_tool_ == Tool::Eraser)
{
ImGui::Text("Layer");
if (ImGui::RadioButton("Foreground", selected_layer_ == TileLayer::Foreground))
{
selected_layer_ = TileLayer::Foreground;
const uint16_t selected_tile = static_cast<uint16_t>(selected_tile_id_);
if (current_tool_ == Tool::TileBrush &&
selected_tile != TileId::Empty &&
!is_solid_tile(selected_tile) &&
!is_deadly_tile(selected_tile))
{
selected_tile_id_ = TileId::GroundTopGreen;
}
}
if (ImGui::RadioButton("Background", selected_layer_ == TileLayer::Background))
{
selected_layer_ = TileLayer::Background;
if (current_tool_ == Tool::TileBrush && is_solid_tile(static_cast<uint16_t>(selected_tile_id_)))
{
selected_tile_id_ = TileId::Background1;
}
}
ImGui::Separator();
}
if (current_tool_ == Tool::TileBrush)
{
ImGui::Text("Tile Type");
const char* tile_names[] = {
"0 Empty", "1 Ground Top", "2 Ground Fill", "3 Platform", "4 Spike"
struct TileEntry { uint16_t id; const char* name; };
static const TileEntry foreground_tile_entries[] = {
{ TileId::Empty, "0 Empty" },
{ TileId::GroundTopGreen, "5 Top Green" },
{ TileId::GroundFillGreen, "6 Fill Green" },
{ TileId::GroundTopGray, "7 Top Gray" },
{ TileId::GroundFillGray, "8 Fill Gray" },
{ TileId::GroundTopPurple, "9 Top Purple" },
{ TileId::GroundFillPurple, "10 Fill Purple" },
{ TileId::Platform, "11 Platform" },
{ TileId::Spike, "12 Spike Floor" },
{ TileId::SpikeCeiling, "22 Spike Ceiling" }
};
for (int i = 0; i < 5; ++i)
static const TileEntry background_tile_entries[] = {
{ TileId::Empty, "0 Empty" },
{ TileId::Background1, "1 Background 1" },
{ TileId::Background2, "2 Background 2" },
{ TileId::Background3, "3 Background 3" },
{ TileId::Background4, "4 Background 4" },
{ TileId::DecorVine, "23 Decor Vine" },
{ TileId::GroundTopGreen, "5 Ground Top Green" },
{ TileId::GroundFillGreen, "6 Ground Fill Green" },
{ TileId::GroundTopGray, "7 Ground Top Gray" },
{ TileId::GroundFillGray, "8 Ground Fill Gray" },
{ TileId::GroundTopPurple, "9 Ground Top Purple" },
{ TileId::GroundFillPurple, "10 Ground Fill Purple" },
{ TileId::Platform, "11 Platform" },
{ TileId::Spike, "12 Spike" },
{ TileId::SpikeCeiling, "22 Spike Ceiling" },
{ TileId::LightPlatformOff, "13 Light Off Visual" },
{ TileId::LightPlatformOn, "14 Light On Visual" },
{ TileId::ShadowPlatformOff, "15 Shadow Off Visual" },
{ TileId::ShadowPlatformOn, "16 Shadow On Visual" },
{ TileId::DoorClosed, "17 Door Closed Visual" },
{ TileId::DoorOpen, "18 Door Open Visual" },
{ TileId::Coin, "19 Coin Visual" },
{ TileId::Flag, "20 Flag Visual" },
{ TileId::Checkpoint, "21 Checkpoint Visual" }
};
const TileEntry* entries = (selected_layer_ == TileLayer::Foreground)
? foreground_tile_entries
: background_tile_entries;
const size_t entry_count = (selected_layer_ == TileLayer::Foreground)
? sizeof(foreground_tile_entries) / sizeof(foreground_tile_entries[0])
: sizeof(background_tile_entries) / sizeof(background_tile_entries[0]);
ImGui::BeginChild("TilePalette", ImVec2(0, 250), true);
for (size_t i = 0; i < entry_count; ++i)
{
if (ImGui::RadioButton(tile_names[i], selected_tile_id_ == i))
selected_tile_id_ = i;
if (ImGui::RadioButton(entries[i].name, selected_tile_id_ == entries[i].id))
selected_tile_id_ = entries[i].id;
}
ImGui::EndChild();
}
else if (current_tool_ == Tool::ObjectPlace)
{
@ -452,6 +630,10 @@ namespace LightGame
if (ImGui::RadioButton(entries[i].name, selected_object_type_ == entries[i].type))
selected_object_type_ = entries[i].type;
}
if (selected_object_type_ == GameObjectType::Player)
{
ImGui::TextWrapped("Player marks the spawn tile. Only one Player spawn is kept.");
}
}
else if (current_tool_ == Tool::Select)
{
@ -464,7 +646,7 @@ namespace LightGame
}
else if (current_tool_ == Tool::Eraser)
{
ImGui::Text("Click to erase tiles");
ImGui::Text("Erase %s layer", selected_layer_name());
}
ImGui::Separator();
@ -535,9 +717,18 @@ namespace LightGame
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoScrollbar);
ImGui::Text("Map: %dx%d | Tile: %d | Objects: %zu | Light: %d",
ImGui::Text("Map: %dx%d | Tile: %d | Layer: %s | Objects: %zu | Light: %d",
map_width_, map_height_, tile_size_,
selected_layer_name(),
level_.object_count(), preview_light_level_);
ImGui::SameLine();
ImGui::Text("| Bg: %s/%s",
level_.get_background_tiles() ? "data" : "none",
show_background_layer_ ? "visible" : "hidden");
ImGui::SameLine();
ImGui::Text("| Room: %d,%d",
camera_.get_x() / (kRoomTilesW * tile_size_),
camera_.get_y() / (kRoomTilesH * tile_size_));
ImGui::End();
}
@ -641,6 +832,23 @@ namespace LightGame
}
oss << " };\n\n";
const uint16_t* background_tiles = level_.get_background_tiles();
if (background_tiles)
{
oss << " static const uint16_t background_tiles[] = {\n";
for (int32_t y = 0; y < map_height_; ++y)
{
oss << " ";
for (int32_t x = 0; x < map_width_; ++x)
{
oss << background_tiles[y * map_width_ + x];
if (x < map_width_ - 1 || y < map_height_ - 1) oss << ",";
}
oss << "\n";
}
oss << " };\n\n";
}
const auto& objects = level_.get_all_objects();
int32_t spawn_count = 0;
for (size_t i = 0; i < objects.size(); ++i)
@ -687,11 +895,25 @@ namespace LightGame
oss << " " << b.min_x << ",\n";
oss << " " << b.min_y << ",\n";
oss << " " << b.max_x << ",\n";
oss << " " << b.max_y << "\n";
oss << " " << b.max_y << ",\n";
if (background_tiles)
{
oss << " background_tiles\n";
}
else
{
oss << " nullptr\n";
}
oss << " };\n";
oss << " }\n";
oss << "}\n";
return oss.str();
}
const char* LevelEditor::selected_layer_name() const
{
return selected_layer_ == TileLayer::Foreground ? "Foreground" : "Background";
}
}

View File

@ -20,6 +20,7 @@ namespace LightGame
{
public:
enum class Tool { TileBrush, ObjectPlace, Select, Eraser };
enum class TileLayer { Foreground, Background };
LevelEditor(int32_t screen_w, int32_t screen_h);
@ -57,6 +58,7 @@ namespace LightGame
void draw_ui_properties();
void draw_ui_status_bar();
void draw_ui_export_dialog();
const char* selected_layer_name() const;
int32_t world_to_tile_x(int32_t world_x) const;
int32_t world_to_tile_y(int32_t world_y) const;
@ -76,13 +78,16 @@ namespace LightGame
bool should_quit_;
Tool current_tool_;
TileLayer selected_layer_;
int32_t selected_tile_id_;
GameObjectType selected_object_type_;
uint32_t selected_object_id_;
uint16_t preview_light_level_;
bool show_grid_;
bool show_room_guides_;
bool show_colliders_;
bool show_background_layer_;
int32_t map_width_;
int32_t map_height_;

View File

@ -4,7 +4,8 @@
namespace LightGame
{
Level::Level()
: tilemap_(),
: background_tilemap_(),
tilemap_(),
bounds_(),
spawn_point_(),
next_id_(1)
@ -151,4 +152,18 @@ namespace LightGame
}
tilemap_.tiles = tile_buffer_.data();
}
void Level::init_background_buffer(const uint16_t* source, int32_t count)
{
if (count <= 0 || source == nullptr)
{
return;
}
background_tiles_.resize(count);
for (int32_t i = 0; i < count; ++i)
{
background_tiles_[i] = source[i];
}
background_tilemap_.tiles = background_tiles_.data();
}
}

View File

@ -36,7 +36,9 @@ namespace LightGame
std::vector<Checkpoint> checkpoints_;
std::vector<uint16_t> original_tiles_;
std::vector<uint16_t> tile_buffer_;
std::vector<uint16_t> background_tiles_;
std::vector<TileLightRule> tile_light_rules_;
RenderData::Tilemap background_tilemap_;
RenderData::Tilemap tilemap_;
LevelBounds bounds_;
Math::Vector2Int spawn_point_;
@ -63,6 +65,12 @@ namespace LightGame
uint16_t* get_mutable_tiles() { return tile_buffer_.empty() ? nullptr : tile_buffer_.data(); }
const uint16_t* get_original_tiles() const { return original_tiles_.empty() ? nullptr : original_tiles_.data(); }
void set_background_tilemap(const RenderData::Tilemap& tilemap) { background_tilemap_ = tilemap; }
const RenderData::Tilemap& get_background_tilemap() const { return background_tilemap_; }
void init_background_buffer(const uint16_t* source, int32_t count);
uint16_t* get_mutable_background_tiles() { return background_tiles_.empty() ? nullptr : background_tiles_.data(); }
const uint16_t* get_background_tiles() const { return background_tiles_.empty() ? nullptr : background_tiles_.data(); }
void add_tile_light_rule(const TileLightRule& rule) { tile_light_rules_.push_back(rule); }
const std::vector<TileLightRule>& get_tile_light_rules() const { return tile_light_rules_; }

View File

@ -48,5 +48,11 @@ namespace LightGame
int32_t bounds_min_y;
int32_t bounds_max_x;
int32_t bounds_max_y;
// Optional visual-only layer. Uses the same map size, tile size, atlas,
// and atlas_columns as tiles. It is rendered behind tiles and ignored
// by physics, triggers, and light tile rules. Omitted legacy LevelData
// aggregate initializers value-initialize this to nullptr.
const uint16_t* background_tiles;
};
}

View File

@ -1,5 +1,6 @@
#include "LevelLoader.h"
#include "Sprite.h"
#include "TileIds.h"
#include "tile_atlas.h"
namespace LightGame
@ -14,14 +15,14 @@ namespace LightGame
return RenderData::Sprite(atlas, col * tile_size, row * tile_size, tile_size, tile_size);
}
static const RenderData::Sprite spr_platform = MakeTileSprite(&tile_atlas_image, 3, 32, 4);
static const RenderData::Sprite spr_spike = MakeTileSprite(&tile_atlas_image, 4, 32, 4);
static const RenderData::Sprite spr_light_platform_on = MakeTileSprite(&tile_atlas_image, 6, 32, 4);
static const RenderData::Sprite spr_shadow_platform_on = MakeTileSprite(&tile_atlas_image, 8, 32, 4);
static const RenderData::Sprite spr_door_closed = MakeTileSprite(&tile_atlas_image, 9, 32, 4);
static const RenderData::Sprite spr_coin = MakeTileSprite(&tile_atlas_image, 11, 32, 4);
static const RenderData::Sprite spr_flag = MakeTileSprite(&tile_atlas_image, 12, 32, 4);
static const RenderData::Sprite spr_checkpoint = MakeTileSprite(&tile_atlas_image, 13, 32, 4);
static const RenderData::Sprite spr_platform = MakeTileSprite(&tile_atlas_image, TileId::Platform, 32, tile_atlas_columns);
static const RenderData::Sprite spr_spike = MakeTileSprite(&tile_atlas_image, TileId::Spike, 32, tile_atlas_columns);
static const RenderData::Sprite spr_light_platform_on = MakeTileSprite(&tile_atlas_image, TileId::LightPlatformOn, 32, tile_atlas_columns);
static const RenderData::Sprite spr_shadow_platform_on = MakeTileSprite(&tile_atlas_image, TileId::ShadowPlatformOn, 32, tile_atlas_columns);
static const RenderData::Sprite spr_door_closed = MakeTileSprite(&tile_atlas_image, TileId::DoorClosed, 32, tile_atlas_columns);
static const RenderData::Sprite spr_coin = MakeTileSprite(&tile_atlas_image, TileId::Coin, 32, tile_atlas_columns);
static const RenderData::Sprite spr_flag = MakeTileSprite(&tile_atlas_image, TileId::Flag, 32, tile_atlas_columns);
static const RenderData::Sprite spr_checkpoint = MakeTileSprite(&tile_atlas_image, TileId::Checkpoint, 32, tile_atlas_columns);
}
void LevelLoader::load(Level& level, const LevelData& data)
@ -37,6 +38,14 @@ namespace LightGame
level.set_tilemap(tilemap);
level.init_tile_buffer(data.tiles, data.map_width * data.map_height);
if (data.background_tiles != nullptr)
{
RenderData::Tilemap background_tilemap = tilemap;
background_tilemap.tiles = data.background_tiles;
level.set_background_tilemap(background_tilemap);
level.init_background_buffer(data.background_tiles, data.map_width * data.map_height);
}
level.set_bounds(LevelBounds(
data.bounds_min_x,
data.bounds_min_y,

View File

@ -4,15 +4,35 @@
namespace LightGame
{
void LevelRenderer::draw(Core::DrawContext& ctx, const Level& level, const Camera2D& camera,
const LightEffectSystem& light_system, uint16_t light_level)
namespace
{
static const int32_t kBackgroundShadeNumerator = 80;
static const int32_t kBackgroundShadeShift = 7;
}
void LevelRenderer::draw(Core::DrawContext& ctx, const Level& level, const Camera2D& camera,
const LightEffectSystem& light_system, uint16_t light_level,
bool draw_background)
{
if (draw_background)
{
const RenderData::Tilemap& background_tilemap = level.get_background_tilemap();
if (background_tilemap.tiles != nullptr && background_tilemap.atlas != nullptr)
{
ctx.draw_tilemap_shaded(background_tilemap,
0, kTopBlackBorder,
camera.get_screen_width(), camera.get_screen_height(),
camera.get_x(), camera.get_y(),
kBackgroundShadeNumerator, kBackgroundShadeShift);
}
}
const RenderData::Tilemap& tilemap = level.get_tilemap();
if (tilemap.tiles != nullptr && tilemap.atlas != nullptr)
{
ctx.draw_tilemap(tilemap,
0, kTopBlackBorder,
camera.get_screen_width(), camera.get_screen_height() - kTopBlackBorder,
camera.get_screen_width(), camera.get_screen_height(),
camera.get_x(), camera.get_y());
}
@ -35,7 +55,8 @@ namespace LightGame
const int32_t screen_y = obj.position.y - camera.get_y() + kTopBlackBorder;
if (screen_x + sprite->width < 0 || screen_x > camera.get_screen_width() ||
screen_y + sprite->height < 0 || screen_y > camera.get_screen_height())
screen_y + sprite->height < kTopBlackBorder ||
screen_y > kTopBlackBorder + camera.get_screen_height())
{
return;
}

View File

@ -22,7 +22,8 @@ namespace LightGame
static const int32_t kTopBlackBorder = 24;
void draw(Core::DrawContext& ctx, const Level& level, const Camera2D& camera,
const LightEffectSystem& light_system, uint16_t light_level);
const LightEffectSystem& light_system, uint16_t light_level,
bool draw_background = true);
void draw_debug(Core::DrawContext& ctx, const Level& level, const Camera2D& camera,
const RenderData::BitmapFont& font);

View File

@ -6,39 +6,15 @@
#include "PointerInput.h"
#include "LevelData.h"
#include "LevelLoader.h"
#include "Level1Data.h"
#include "Level2Data.h"
#include "Level3Data.h"
#include "Level4Data.h"
#include "Level5Data.h"
#include "Level6Data.h"
#include "Level7Data.h"
#include "Level8Data.h"
#include "Level9Data.h"
#include "Level10Data.h"
#include "LevelTotalData.h"
#include "LevelTotalRooms.h"
#include "sprite_atlas.h"
#include "TileIds.h"
#include "Tilemap.h"
#include <cassert>
namespace LightGame
{
namespace
{
const LevelData* kLevels[] = {
&Level1::data,
&Level2::data,
&Level3::data,
&Level4::data,
&Level5::data,
&Level6::data,
&Level7::data,
&Level8::data,
&Level9::data,
&Level10::data
};
const int32_t kLevelCount = 10;
}
LightGameApp::LightGameApp(
int32_t screen_width,
int32_t screen_height,
@ -59,15 +35,17 @@ namespace LightGame
light_system_(),
state_manager_(),
renderer_(),
current_level_index_(0),
player_id_(0),
room_grid_(&LevelTotal::room_grid),
current_room_index_(-1),
level_total_loaded_(false),
manual_light_level_(2048),
light_step_(256),
has_manual_override_(false),
debug_mode_(false),
death_processed_(false)
{
camera_.configure(screen_width, screen_height);
camera_.configure(screen_width, screen_height - LevelRenderer::kTopBlackBorder);
camera_.set_dead_zone(60, 40);
static const RenderData::Sprite player_idle[] = {
@ -162,63 +140,6 @@ namespace LightGame
}
}
void LightGameApp::load_level(int32_t level_index)
{
if (level_index < 0 || level_index >= kLevelCount)
{
return;
}
current_level_index_ = level_index;
level_ = Level();
LevelLoader::load(level_, *kLevels[level_index]);
// Validate spawn point: must be on solid ground and not inside solid tiles.
{
GameObject temp_player;
temp_player.position = level_.get_spawn_point();
temp_player.collider = RenderData::BoundingBox2D(
Math::Vector2Int(8, 0), Math::Vector2Int(24, 32));
assert(physics_.is_grounded(temp_player, level_) &&
"Player spawn point is not on solid ground");
const RenderData::Tilemap& tilemap = level_.get_tilemap();
if (tilemap.tile_w > 0)
{
const RenderData::BoundingBox2D world_box = temp_player.get_world_collider();
const int32_t ts = tilemap.tile_w;
const int32_t tile_left = world_box.min.x / ts;
const int32_t tile_right = (world_box.max.x - 1) / ts;
const int32_t tile_top = world_box.min.y / ts;
const int32_t tile_bottom = (world_box.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 (tx < 0 || tx >= tilemap.width || ty < 0 || ty >= tilemap.height)
continue;
const uint16_t tile_id = tilemap.get_tile(tx, ty);
assert((tile_id == 0 || tile_id == RenderData::Tilemap::EmptyTile) &&
"Player spawn point overlaps solid tiles");
}
}
}
}
const auto players = level_.get_objects_by_type(GameObjectType::Player);
if (!players.empty())
{
player_id_ = players[0]->id;
}
camera_.set_level_bounds(level_.get_bounds());
camera_.snap_to(level_.get_spawn_point());
state_manager_.get_hud().current_level = level_index + 1;
}
bool LightGameApp::is_action_pressed()
{
return button_input_ && button_input_->was_pressed();
@ -229,7 +150,7 @@ namespace LightGame
(void)dt_ms;
if (is_action_pressed())
{
load_level(0);
load_room(LevelTotal::kStartRoomIndex);
state_manager_.set_state(GameState::Playing);
}
}
@ -244,6 +165,9 @@ namespace LightGame
GameObject* player = level_.get_object(player_id_);
if (player)
{
check_room_transition();
update_room_respawn_anchor();
player_controller_.update(*player, level_, physics_, keyboard_input_, pointer_input_, screen_width_, screen_height_, dt_ms);
camera_.follow(player->position);
@ -275,7 +199,8 @@ namespace LightGame
{
state_manager_.get_hud().lives = 3;
state_manager_.get_hud().collectibles = 0;
load_level(0);
level_total_loaded_ = false;
load_room(LevelTotal::kStartRoomIndex);
state_manager_.set_state(GameState::Playing);
}
}
@ -284,18 +209,124 @@ namespace LightGame
{
(void)dt_ms;
if (is_action_pressed())
{
const int32_t next = current_level_index_ + 1;
if (next < kLevelCount)
{
load_level(next);
state_manager_.set_state(GameState::Playing);
}
else
{
state_manager_.set_state(GameState::Title);
}
}
void LightGameApp::load_room(int32_t room_index)
{
if (!room_grid_)
{
return;
}
const RoomDef* room = room_by_index(*room_grid_, room_index);
if (!room)
{
return;
}
if (!level_total_loaded_)
{
level_ = Level();
LevelLoader::load(level_, LevelTotal::data);
level_total_loaded_ = true;
const auto players = level_.get_objects_by_type(GameObjectType::Player);
if (!players.empty())
{
player_id_ = players[0]->id;
}
}
GameObject* player = level_.get_object(player_id_);
if (player)
{
player->position = room->default_spawn;
player->velocity = Math::Vector2Int(0, 0);
player->active = true;
}
current_room_index_ = room_index;
level_.set_spawn_point(room->default_spawn);
// Deactivate checkpoints outside the active room so respawn falls back to room spawn.
auto& checkpoints = level_.get_checkpoints();
for (size_t i = 0; i < checkpoints.size(); ++i)
{
Math::Vector2Int p = checkpoints[i].position;
if (room_index_of(*room_grid_, p) != room_index)
{
checkpoints[i].activated = false;
}
}
LevelBounds rb(room->bounds.min_x, room->bounds.min_y,
room->bounds.max_x, room->bounds.max_y);
camera_.set_level_bounds(rb);
camera_.snap_to(room->default_spawn);
state_manager_.get_hud().current_level = room_index + 1;
}
void LightGameApp::check_room_transition()
{
if (!room_grid_)
{
return;
}
const GameObject* player = level_.get_object(player_id_);
if (!player)
{
return;
}
const int32_t new_idx = room_index_of(*room_grid_, player->position);
if (new_idx == current_room_index_ || new_idx < 0)
{
return;
}
const RoomDef* room = room_by_index(*room_grid_, new_idx);
if (!room)
{
return;
}
current_room_index_ = new_idx;
LevelBounds rb(room->bounds.min_x, room->bounds.min_y,
room->bounds.max_x, room->bounds.max_y);
camera_.set_level_bounds(rb);
camera_.snap_to(player->position);
state_manager_.get_hud().current_level = new_idx + 1;
}
void LightGameApp::update_room_respawn_anchor()
{
if (!room_grid_ || current_room_index_ < 0)
{
return;
}
const RoomDef* room = room_by_index(*room_grid_, current_room_index_);
if (!room)
{
return;
}
// Drop activations belonging to other rooms; keep only current-room ones.
auto& checkpoints = level_.get_checkpoints();
for (size_t i = 0; i < checkpoints.size(); ++i)
{
if (!checkpoints[i].activated)
{
continue;
}
if (room_index_of(*room_grid_, checkpoints[i].position) != current_room_index_)
{
checkpoints[i].activated = false;
}
}
level_.set_spawn_point(room->default_spawn);
}
void LightGameApp::check_triggers()

View File

@ -8,6 +8,7 @@
#include "LightEffectSystem.h"
#include "GameStateManager.h"
#include "LevelRenderer.h"
#include "RoomLayout.h"
#include "IPhotoSensor.h"
namespace Core
@ -49,9 +50,12 @@ namespace LightGame
GameStateManager state_manager_;
LevelRenderer renderer_;
int32_t current_level_index_;
uint32_t player_id_;
const RoomGrid* room_grid_;
int32_t current_room_index_;
bool level_total_loaded_;
uint16_t manual_light_level_;
uint16_t light_step_;
@ -75,12 +79,14 @@ namespace LightGame
void set_debug_mode(bool enabled) { debug_mode_ = enabled; }
private:
void load_level(int32_t level_index);
void load_room(int32_t room_index);
void update_playing(uint32_t dt_ms);
void update_title(uint32_t dt_ms);
void update_game_over(uint32_t dt_ms);
void update_level_complete(uint32_t dt_ms);
void check_triggers();
void check_room_transition();
void update_room_respawn_anchor();
bool is_action_pressed();
};
}

View File

@ -1,4 +1,5 @@
#include "Physics2D.h"
#include "TileIds.h"
#include <algorithm>
#include <cstdlib>
@ -40,16 +41,7 @@ namespace LightGame
}
const uint16_t tile_id = tilemap.get_tile(tile_x, tile_y);
if (tile_id == 0 || tile_id == RenderData::Tilemap::EmptyTile)
{
return false;
}
// spike(4) 是杀伤物,不参与碰撞 — 玩家须能踩进去触发 check_death
if (tile_id == 4)
{
return false;
}
return true;
return is_solid_tile(tile_id);
}
void Physics2D::collide_tilemap(GameObject& obj, const RenderData::Tilemap& tilemap,

View File

@ -1,6 +1,7 @@
#include "PlayerController.h"
#include "IKeyboardState.h"
#include "PointerInput.h"
#include "TileIds.h"
#include <algorithm>
namespace LightGame
@ -257,7 +258,7 @@ void PlayerController::check_death(GameObject& obj, Level& level)
continue;
}
const uint16_t tile_id = tilemap.get_tile(tx, ty);
if (tile_id == 4)
if (is_deadly_tile(tile_id))
{
const int32_t spike_top = (ty + 1) * ts - kSpikeHitHeight;
const int32_t spike_bottom = (ty + 1) * ts;

View File

@ -0,0 +1,40 @@
#include "RoomLayout.h"
namespace LightGame
{
int32_t room_index_of(const RoomGrid& grid, const Math::Vector2Int& world_pos)
{
if (grid.room_pixel_w <= 0 || grid.room_pixel_h <= 0)
{
return -1;
}
int32_t col = world_pos.x / grid.room_pixel_w;
int32_t row = world_pos.y / grid.room_pixel_h;
if (col < 0) col = 0;
if (row < 0) row = 0;
if (col >= grid.cols) col = grid.cols - 1;
if (row >= grid.rows) row = grid.rows - 1;
return row * grid.cols + col;
}
const RoomDef* room_at(const RoomGrid& grid, int32_t col, int32_t row)
{
if (col < 0 || col >= grid.cols || row < 0 || row >= grid.rows)
{
return nullptr;
}
return &grid.rooms[row * grid.cols + col];
}
const RoomDef* room_by_index(const RoomGrid& grid, int32_t index)
{
if (index < 0 || index >= grid.cols * grid.rows)
{
return nullptr;
}
return &grid.rooms[index];
}
}

View File

@ -0,0 +1,34 @@
#pragma once
#include <cstdint>
#include "Vector2.h"
namespace LightGame
{
struct RoomBounds
{
int32_t min_x;
int32_t min_y;
int32_t max_x;
int32_t max_y;
};
struct RoomDef
{
RoomBounds bounds;
Math::Vector2Int default_spawn;
};
struct RoomGrid
{
const RoomDef* rooms;
int32_t cols;
int32_t rows;
int32_t room_pixel_w;
int32_t room_pixel_h;
};
int32_t room_index_of(const RoomGrid& grid, const Math::Vector2Int& world_pos);
const RoomDef* room_at(const RoomGrid& grid, int32_t col, int32_t row);
const RoomDef* room_by_index(const RoomGrid& grid, int32_t index);
}

View File

@ -0,0 +1,61 @@
#pragma once
#include <cstdint>
#include "Tilemap.h"
namespace LightGame
{
namespace TileId
{
static const uint16_t Empty = 0;
static const uint16_t Hidden = RenderData::Tilemap::EmptyTile;
static const uint16_t Background1 = 1;
static const uint16_t Background2 = 2;
static const uint16_t Background3 = 3;
static const uint16_t Background4 = 4;
static const uint16_t GroundTopGreen = 5;
static const uint16_t GroundFillGreen = 6;
static const uint16_t GroundTopGray = 7;
static const uint16_t GroundFillGray = 8;
static const uint16_t GroundTopPurple = 9;
static const uint16_t GroundFillPurple = 10;
static const uint16_t Platform = 11;
static const uint16_t Spike = 12;
static const uint16_t LightPlatformOff = 13;
static const uint16_t LightPlatformOn = 14;
static const uint16_t ShadowPlatformOff = 15;
static const uint16_t ShadowPlatformOn = 16;
static const uint16_t DoorClosed = 17;
static const uint16_t DoorOpen = 18;
static const uint16_t Coin = 19;
static const uint16_t Flag = 20;
static const uint16_t Checkpoint = 21;
static const uint16_t SpikeCeiling = 22;
static const uint16_t DecorVine = 23;
}
inline bool is_deadly_tile(uint16_t tile_id)
{
return tile_id == TileId::Spike || tile_id == TileId::SpikeCeiling;
}
inline bool is_solid_tile(uint16_t tile_id)
{
switch (tile_id)
{
case TileId::GroundTopGreen:
case TileId::GroundFillGreen:
case TileId::GroundTopGray:
case TileId::GroundFillGray:
case TileId::GroundTopPurple:
case TileId::GroundFillPurple:
case TileId::Platform:
return true;
default:
return false;
}
}
}

View File

@ -1,104 +0,0 @@
#pragma once
#include "LevelData.h"
#include "tile_atlas.h"
namespace LightGame
{
namespace Level10
{
// 终极之塔:综合最终关。三段、两个 Checkpoint。
// 段 1地面尖刺 + 光暗台错位 + 暗门(须切到 0-1023
// 段 2阶梯爬升错位光暗台。
// 段 3顶板走廊中间光门1500-2500+ 高速节奏跳到出口。
static const int32_t kMapWidth = 50;
static const int32_t kMapHeight = 12;
static const int32_t kTileSize = 32;
// Tile IDs: 0=empty 1=ground_top 2=ground_fill 4=spike
// 顶天墙 1列 18 row 0..8 (仅 row 9 通道给暗门)
// 顶天墙 2列 34 row 0..2 (仅 row 3 通道给中间光门row 4 是门下挡板)
// 阶梯块:(32,4)(33,4) 单格 ground_fill 让玩家从段 2 跳到顶板高度
// 顶板row 4=ground_top, row 5=ground_fill (列 35..49),玩家走在 row 3
static const uint16_t tiles[] = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,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,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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,4,4,4,4,4,4,4,4,4,4,4,4,4,1,2,1,1,4,4,4,4,4,4,4,4,4,1,1,1,1,1,4,4,4,4,4,4,4,4,4,4,4,4,4,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,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, 9, 0, 0, TriggerAction::None, 0 },
// === 段 14-16地面尖刺 + 光暗错位 ===
{ GameObjectType::ShadowPlatform, 5, 8, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 7, 7, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 9, 8, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 11, 7, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 13, 6, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 15, 7, 3072, 4095, TriggerAction::None, 0 },
// 暗门入口(须切到 0-1023
{ GameObjectType::Door, 18, 9, 0, 1023, TriggerAction::None, 0 },
// Checkpoint 1
{ GameObjectType::Trigger, 19, 9, 0, 0, TriggerAction::Checkpoint, 0 },
// === 段 220-31阶梯爬升错位切换 ===
{ GameObjectType::LightPlatform, 22, 7, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 24, 6, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 26, 6, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 28, 5, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 30, 5, 3072, 4095, TriggerAction::None, 0 },
// 段 2 顶端阶梯:(32,4)(33,4) 是 tilemap 中的 ground_fill
// 玩家立足 row 3 在阶梯顶部停留并切换光强。
// 中间光门:玩家走在 row 3门挡在 (34,3)
{ GameObjectType::Door, 34, 3, 1500, 2500, TriggerAction::None, 0 },
// Checkpoint 2顶板入口
{ GameObjectType::Trigger, 36, 3, 0, 0, TriggerAction::Checkpoint, 0 },
// === 段 335-49顶板走廊浮空台节奏跳 ===
{ GameObjectType::ShadowPlatform, 39, 2, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 42, 1, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 45, 2, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 7, 5, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 13, 4, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 26, 4, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 42, 0, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 45, 1, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Trigger, 49, 3, 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,
&kAtlas,
tile_atlas_columns,
spawns,
25,
nullptr,
0,
Math::Vector2Int(2 * kTileSize, 9 * kTileSize),
0,
0,
kMapWidth * kTileSize,
kMapHeight * kTileSize
};
}
}

View File

@ -1,53 +0,0 @@
#pragma once
#include "LevelData.h"
#include "tile_atlas.h"
namespace LightGame
{
namespace Level1
{
static const int32_t kMapWidth = 25;
static const int32_t kMapHeight = 8;
static const int32_t kTileSize = 32;
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,
1,1,1,1,4,4,4,4,1,1,4,4,4,4,4,4,1,1,4,4,4,4,4,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, 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::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,
&kAtlas,
tile_atlas_columns,
spawns,
5,
nullptr,
0,
Math::Vector2Int(64, 160),
0,
0,
800,
256
};
}
}

View File

@ -1,67 +0,0 @@
#pragma once
#include "LevelData.h"
#include "tile_atlas.h"
namespace LightGame
{
namespace Level2
{
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,
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, 5, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 4, 4, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 6, 3, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 8, 2, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 10, 3, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 12, 4, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 14, 3, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 16, 2, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 18, 3, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 20, 4, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 22, 5, 3192, 4095, 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, 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,
&kAtlas,
tile_atlas_columns,
spawns,
15,
nullptr,
0,
Math::Vector2Int(2 * kTileSize, 5 * kTileSize),
0,
0,
kMapWidth * kTileSize,
kMapHeight * kTileSize
};
}
}

View File

@ -1,70 +0,0 @@
#pragma once
#include "LevelData.h"
#include "tile_atlas.h"
namespace LightGame
{
namespace Level3
{
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,
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, 5, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 4, 4, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 6, 3, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 8, 4, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 10, 3, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 12, 4, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::Door, 15, 4, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 17, 4, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 19, 3, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 21, 4, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 22, 5, 0, 1023, 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, 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,
&kAtlas,
tile_atlas_columns,
spawns,
15,
nullptr,
0,
Math::Vector2Int(2 * kTileSize, 5 * kTileSize),
0,
0,
kMapWidth * kTileSize,
kMapHeight * kTileSize
};
}
}

View File

@ -1,71 +0,0 @@
#pragma once
#include "LevelData.h"
#include "tile_atlas.h"
namespace LightGame
{
namespace Level4
{
// 影中漫步前半段须降到暗0-1023踩 ShadowPlatform
// 中央安全岛切换光强后半段须升到亮3072-4095踩 LightPlatform。
// 地面间隙下方是 spike掉下即死。
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 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,
1,1,1,4,4,4,4,4,4,4,1,1,1,4,4,4,4,4,4,4,4,4,4,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, 5, 0, 0, TriggerAction::None, 0 },
// 前半段:暗时实
{ GameObjectType::ShadowPlatform, 4, 4, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 6, 3, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 8, 4, 0, 1023, TriggerAction::None, 0 },
// 后半段:亮时实
{ GameObjectType::LightPlatform, 13, 4, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 15, 3, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 17, 2, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 19, 3, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 21, 4, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 6, 2, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 11, 5, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 17, 1, 0, 0, TriggerAction::None, 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,
&kAtlas,
tile_atlas_columns,
spawns,
13,
nullptr,
0,
Math::Vector2Int(2 * kTileSize, 5 * kTileSize),
0,
0,
kMapWidth * kTileSize,
kMapHeight * kTileSize
};
}
}

View File

@ -1,73 +0,0 @@
#pragma once
#include "LevelData.h"
#include "tile_atlas.h"
namespace LightGame
{
namespace Level5
{
// 三色之门:必须依次通过 3 道光阈值不同的门。
// 实心墙到顶,门是唯一通路。
// 门 1暗门(0-1023)
// 门 2中间光门(1500-2500)
// 门 3亮门(3300-4095)
// 每两段间留有空间和提示性平台供玩家手动调光。
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
// 墙体 y=0..4 顶天y=5 留空作为门的通道y=6 是 ground_top 玩家立足
static const uint16_t tiles[] = {
0,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,
0,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,
0,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,
0,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,
0,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,
0,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,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,2,1,1,1,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, 5, 0, 0, TriggerAction::None, 0 },
// 三道门(站立层)
{ GameObjectType::Door, 4, 5, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::Door, 12, 5, 1500, 2500, TriggerAction::None, 0 },
{ GameObjectType::Door, 19, 5, 3300, 4095, TriggerAction::None, 0 },
// 中段提示:每段中央放对应阈值的浮空平台
{ GameObjectType::ShadowPlatform, 7, 3, 0, 1500, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 15, 3, 1500, 3000, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 22, 3, 3000, 4095, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 7, 2, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 15, 2, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 22, 2, 0, 0, TriggerAction::None, 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,
&kAtlas,
tile_atlas_columns,
spawns,
11,
nullptr,
0,
Math::Vector2Int(2 * kTileSize, 5 * kTileSize),
0,
0,
kMapWidth * kTileSize,
kMapHeight * kTileSize
};
}
}

View File

@ -1,79 +0,0 @@
#pragma once
#include "LevelData.h"
#include "tile_atlas.h"
namespace LightGame
{
namespace Level6
{
// 刺影回廊:长尖刺走廊,依靠光感平台跳跃前进。
// 光台与暗台错位排列,强迫玩家在跳跃中切换光强。
// 此关引入 Checkpoint约 1/2 处一个救援点。
static const int32_t kMapWidth = 35;
static const int32_t kMapHeight = 8;
static const int32_t kTileSize = 32;
// Tile IDs: 0=empty 1=ground_top 2=ground_fill 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,
1,1,1,1,4,4,4,4,4,4,4,4,4,4,4,4,1,1,1,1,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,2,2,2,
};
static const ObjectSpawn spawns[] = {
{ GameObjectType::Player, 2, 5, 0, 0, TriggerAction::None, 0 },
// 第一段尖刺走廊:暗台与光台错位(玩家须在跳跃间切换)
{ GameObjectType::ShadowPlatform, 5, 4, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 7, 3, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 9, 4, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 11, 3, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 13, 4, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 15, 3, 3072, 4095, TriggerAction::None, 0 },
// 中段安全岛 + Checkpoint
{ GameObjectType::Trigger, 18, 5, 0, 0, TriggerAction::Checkpoint, 0 },
// 第二段更密:跳跃间隔更短
{ GameObjectType::LightPlatform, 21, 4, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 23, 3, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 25, 4, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 27, 3, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 29, 4, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 31, 4, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 7, 2, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 13, 3, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 25, 3, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 31, 3, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Trigger, 34, 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,
&kAtlas,
tile_atlas_columns,
spawns,
19,
nullptr,
0,
Math::Vector2Int(2 * kTileSize, 5 * kTileSize),
0,
0,
kMapWidth * kTileSize,
kMapHeight * kTileSize
};
}
}

View File

@ -1,80 +0,0 @@
#pragma once
#include "LevelData.h"
#include "tile_atlas.h"
namespace LightGame
{
namespace Level7
{
// 暗井攀登:垂直关卡,从底部 Z 字形爬升到顶部。
// 光台与暗台交替排布,玩家须不断切换光强。
// 中段 Checkpoint 作为安全点,顶部出口落在左侧。
static const int32_t kMapWidth = 25;
static const int32_t kMapHeight = 12;
static const int32_t kTileSize = 32;
// Tile IDs: 0=empty 1=ground_top 2=ground_fill 4=spike
// 顶台在 y=1(ground_top) / y=2(ground_fill),玩家从下方跳到顶台上方 y=0 站立。
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,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,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,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,
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, 9, 0, 0, TriggerAction::None, 0 },
// Z 字形阶梯:右上方向爬升
{ GameObjectType::LightPlatform, 5, 9, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 8, 8, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 11, 7, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 14, 6, 0, 1023, TriggerAction::None, 0 },
// 中段 Checkpoint站在亮光实台上
{ GameObjectType::LightPlatform, 17, 6, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::Trigger, 17, 5, 0, 0, TriggerAction::Checkpoint, 0 },
// 上半段回头向左爬
{ GameObjectType::LightPlatform, 14, 4, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 11, 3, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 8, 3, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 8, 7, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 17, 4, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 11, 2, 0, 0, TriggerAction::None, 0 },
// 顶部出口:站在顶台 ground_top 上方
{ GameObjectType::Trigger, 7, 0, 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,
&kAtlas,
tile_atlas_columns,
spawns,
14,
nullptr,
0,
Math::Vector2Int(2 * kTileSize, 9 * kTileSize),
0,
0,
kMapWidth * kTileSize,
kMapHeight * kTileSize
};
}
}

View File

@ -1,85 +0,0 @@
#pragma once
#include "LevelData.h"
#include "tile_atlas.h"
namespace LightGame
{
namespace Level8
{
// 双相迷宫:节奏跳 + 门锁 + 长尖刺综合关。
// 三段式spike 节奏 → 双门夹道(须切换光强通过)→ 长走廊高速切换。
// Checkpoint 设在第二段后。
static const int32_t kMapWidth = 40;
static const int32_t kMapHeight = 10;
static const int32_t kTileSize = 32;
// Tile IDs: 0=empty 1=ground_top 2=ground_fill 4=spike
// 列 14 与列 24 是顶天实墙(仅留 y=7 一格作为门通道)
static const uint16_t tiles[] = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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,4,4,4,4,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,2,1,1,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
};
static const ObjectSpawn spawns[] = {
{ GameObjectType::Player, 2, 7, 0, 0, TriggerAction::None, 0 },
// 第一段spike 4-7 节奏跳
{ GameObjectType::LightPlatform, 5, 6, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 7, 5, 0, 1023, TriggerAction::None, 0 },
// 第一道暗门(站立层 14,7暗光通过
{ GameObjectType::Door, 14, 7, 0, 1023, TriggerAction::None, 0 },
// 中段安全区 15-23
{ GameObjectType::Collectible, 18, 6, 0, 0, TriggerAction::None, 0 },
// 第二道亮门24,7亮光通过
{ GameObjectType::Door, 24, 7, 3072, 4095, TriggerAction::None, 0 },
// Checkpoint刚过亮门
{ GameObjectType::Trigger, 25, 7, 0, 0, TriggerAction::Checkpoint, 0 },
// 第三段spike 27-37 高速切换
{ GameObjectType::LightPlatform, 28, 6, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 30, 5, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 32, 6, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 34, 5, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 36, 6, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 5, 4, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 30, 3, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 36, 4, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Trigger, 39, 7, 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,
&kAtlas,
tile_atlas_columns,
spawns,
16,
nullptr,
0,
Math::Vector2Int(2 * kTileSize, 7 * kTileSize),
0,
0,
kMapWidth * kTileSize,
kMapHeight * kTileSize
};
}
}

View File

@ -1,93 +0,0 @@
#pragma once
#include "LevelData.h"
#include "tile_atlas.h"
namespace LightGame
{
namespace Level9
{
// 光暗交界:综合挑战。三段,每段一个 Checkpoint。
// 段 1尖刺 + 阶梯,引入"中间光"门(1500-2500)。
// 段 2长尖刺走廊光台暗台双层错位。
// 段 3双门夹道暗 → 亮),失误重置回上一 CP。
static const int32_t kMapWidth = 45;
static const int32_t kMapHeight = 10;
static const int32_t kTileSize = 32;
// Tile IDs: 0=empty 1=ground_top 2=ground_fill 4=spike
// 列 16、列 30 是顶天实墙(仅 y=7 通道)
static const uint16_t tiles[] = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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,4,4,4,4,4,4,4,4,1,1,1,1,2,1,4,4,4,4,4,4,4,4,4,4,4,1,2,1,4,4,4,4,1,1,4,4,4,4,4,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,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, 7, 0, 0, TriggerAction::None, 0 },
// === 段 14-15 ===
{ GameObjectType::LightPlatform, 5, 6, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 7, 5, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 9, 6, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 11, 5, 0, 1023, TriggerAction::None, 0 },
// 中间光门(须把光强调到中间区间)
{ GameObjectType::Door, 16, 7, 1500, 2500, TriggerAction::None, 0 },
// 段 1 末尾 Checkpoint
{ GameObjectType::Trigger, 17, 7, 0, 0, TriggerAction::Checkpoint, 0 },
// === 段 218-29双层错位 ===
{ GameObjectType::LightPlatform, 19, 6, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 21, 5, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 23, 4, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 25, 5, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 27, 6, 3072, 4095, TriggerAction::None, 0 },
// 段 2 末尾 Checkpoint
{ GameObjectType::Trigger, 29, 7, 0, 0, TriggerAction::Checkpoint, 0 },
// === 段 330-43双门夹道 ===
{ GameObjectType::Door, 30, 7, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 33, 6, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 35, 5, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 39, 6, 3072, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 41, 5, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 9, 4, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 23, 2, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 35, 3, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 41, 3, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Trigger, 44, 7, 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,
&kAtlas,
tile_atlas_columns,
spawns,
23,
nullptr,
0,
Math::Vector2Int(2 * kTileSize, 7 * kTileSize),
0,
0,
kMapWidth * kTileSize,
kMapHeight * kTileSize
};
}
}

View File

@ -0,0 +1,380 @@
#pragma once
#include "LevelData.h"
#include "tile_atlas.h"
namespace LightGame
{
namespace LevelTotal
{
static const int32_t kMapWidth = 96;
static const int32_t kMapHeight = 54;
static const int32_t kTileSize = 32;
static const uint16_t tiles[] = {
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,0,0,0,0,0,0,0,0,0,0,0,0,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,0,0,0,0,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,6,6,6,8,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,0,0,0,0,0,0,0,0,0,0,0,
6,6,6,6,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,6,6,6,22,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,8,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
6,6,6,6,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,6,6,6,0,22,22,22,22,22,0,0,0,0,0,0,0,0,22,22,22,22,0,0,6,6,6,6,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
6,6,6,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,6,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,22,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
6,6,6,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,6,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
6,6,6,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,6,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
6,6,6,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,6,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
6,6,6,6,6,0,0,0,0,0,0,5,14,14,14,14,14,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,17,0,0,0,0,0,0,0,0,6,6,5,6,6,5,6,6,5,5,5,5,5,6,6,6,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
6,6,6,6,6,0,0,0,0,0,0,0,0,0,14,14,14,14,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,6,5,6,6,6,6,6,6,5,5,5,5,5,6,6,6,6,6,6,6,8,8,6,6,6,6,6,6,6,6,6,6,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
6,6,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,6,6,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
6,6,6,6,6,0,0,0,0,0,0,0,0,0,0,0,0,6,6,6,6,6,6,5,6,6,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,5,6,6,6,6,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,
6,6,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,5,5,0,0,0,0,0,5,5,5,5,5,
6,6,6,6,0,0,6,6,0,0,0,0,0,0,0,0,0,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,5,0,0,0,0,0,0,0,0,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,0,0,0,0,0,0,0,0,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,0,0,0,0,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,8,8,8,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,8,6,6,6,8,8,8,
6,6,6,6,6,6,6,6,6,6,6,0,0,0,0,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,0,22,22,22,22,0,0,0,0,0,0,8,8,8,8,8,8,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,0,0,0,0,0,0,0,0,0,0,0,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,14,14,8,8,8,8,8,8,8,8,8,8,0,6,6,8,8,8,8,8,8,
6,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,6,6,6,6,6,6,6,6,6,6,6,6,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,6,6,6,6,22,22,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,14,14,14,8,8,8,8,8,8,8,8,22,0,0,0,0,8,8,8,8,8,
6,6,6,0,0,0,0,17,0,0,0,0,0,16,16,16,16,6,6,6,6,6,6,6,6,6,6,8,8,8,8,8,8,8,8,8,8,8,8,8,22,22,22,22,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,14,14,8,0,0,0,0,0,0,0,0,0,0,0,0,0,8,8,8,8,
8,8,8,0,0,17,17,17,0,0,0,0,16,16,16,16,16,6,6,6,6,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,6,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,8,8,8,8,
8,8,8,0,0,8,17,17,17,0,0,0,0,0,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,6,22,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,16,16,16,8,0,0,8,
8,8,8,7,0,0,0,0,0,0,0,0,0,0,0,0,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,8,7,0,0,0,0,0,0,0,0,0,0,0,0,14,17,0,0,0,0,0,0,0,0,0,0,16,16,0,0,0,0,8,8,
8,8,8,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,8,8,8,0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,17,0,0,0,0,0,0,0,22,22,0,0,0,16,8,8,
8,8,8,8,7,7,14,14,14,0,0,0,0,0,0,0,0,0,8,8,8,8,8,8,8,8,8,8,8,8,8,8,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,8,8,8,8,0,0,0,0,0,0,0,0,0,0,0,0,0,17,17,0,0,0,0,0,0,0,0,0,0,0,0,16,8,8,
8,8,8,8,8,8,14,14,14,14,0,0,0,0,0,0,0,0,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,0,0,8,8,8,8,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,8,8,8,
8,8,8,8,8,8,8,0,0,0,0,0,0,0,0,0,0,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,6,8,8,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,8,8,8,8,8,8,8,8,7,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,16,16,8,
8,8,8,8,8,8,8,0,0,0,0,0,0,0,0,0,0,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,0,0,0,0,0,0,0,0,8,8,8,8,8,8,8,8,8,8,8,8,8,8,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,14,16,16,8,
8,8,8,8,0,8,0,0,0,0,0,0,0,0,0,0,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,10,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,8,8,8,8,8,8,8,8,8,8,8,8,14,14,14,8,17,17,8,8,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,16,16,8,
8,8,8,8,0,0,0,0,0,0,0,0,0,0,0,16,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,10,8,7,7,7,7,7,7,12,12,12,12,7,7,7,12,12,12,12,12,8,8,10,10,16,16,16,8,10,8,8,8,8,8,8,8,8,14,8,8,17,17,17,17,8,8,7,7,7,0,0,0,0,0,0,0,0,0,0,16,16,8,8,
8,8,8,0,0,0,0,0,0,0,0,0,0,0,0,16,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,10,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,16,16,8,8,8,8,8,10,8,8,8,8,8,8,8,8,8,8,8,17,17,17,8,8,8,8,8,8,8,7,7,7,0,0,0,0,16,8,8,8,
8,8,8,0,0,0,0,0,0,0,0,0,0,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,10,10,10,8,8,8,8,8,8,8,10,8,8,10,8,8,8,8,8,8,8,8,8,8,8,8,8,17,17,8,8,8,8,8,8,8,8,8,8,7,0,0,0,0,8,8,8,
8,8,8,0,0,0,0,0,14,14,14,14,14,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,8,8,8,8,8,8,8,8,8,10,8,8,8,8,8,10,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,0,0,0,0,8,8,8,
8,8,8,7,7,0,0,0,0,0,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,8,8,8,8,8,8,8,8,10,10,10,10,10,8,8,8,8,10,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,0,0,0,0,0,8,8,8,
8,8,8,8,8,0,0,0,0,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,8,8,8,8,8,10,10,10,10,10,10,10,10,10,10,8,8,10,10,10,10,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,0,0,0,0,0,0,0,8,8,
8,8,8,8,8,8,0,0,0,0,0,0,0,0,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,0,0,0,0,0,0,0,0,8,
8,8,8,8,8,8,0,0,0,0,0,0,0,0,0,0,0,8,8,8,8,8,8,8,8,8,8,8,8,8,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,10,10,8,8,0,0,0,0,0,0,0,0,0,8,
8,8,8,8,8,8,0,0,0,0,0,0,0,0,0,0,0,0,8,8,8,8,8,8,8,8,8,8,8,8,8,8,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,0,0,0,10,10,10,0,0,10,10,10,10,10,10,10,10,8,8,8,8,8,8,8,8,8,8,8,8,10,10,10,10,10,8,8,10,0,0,0,0,0,0,0,0,0,8,
8,8,8,8,8,10,10,10,10,10,10,10,0,0,0,0,0,0,0,0,0,0,8,8,8,8,8,8,8,8,8,8,10,10,10,10,10,10,10,10,10,10,10,0,0,0,10,10,0,0,0,0,0,0,0,0,0,0,0,0,10,0,0,10,10,8,8,8,8,8,8,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,0,0,0,0,0,0,0,0,0,8,
8,8,8,8,8,8,10,10,10,10,10,10,10,10,0,0,0,0,0,0,0,0,0,0,0,8,8,8,8,8,10,10,10,10,10,10,10,10,22,22,10,10,10,0,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,22,0,0,0,0,0,0,0,0,12,10,
10,10,10,8,8,8,8,8,8,10,10,10,10,10,10,9,0,0,0,0,0,0,0,0,0,0,0,8,8,0,0,0,10,10,10,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,10,10,10,10,10,10,0,0,10,10,10,10,10,10,10,10,10,10,10,10,10,0,0,0,0,0,0,0,0,0,10,10,
10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,10,10,0,0,0,0,0,0,0,0,10,10,10,10,0,0,0,0,0,0,0,0,0,0,0,0,0,10,10,10,10,10,10,0,0,0,0,10,10,10,10,10,10,10,10,10,10,10,10,22,0,0,0,0,0,0,0,0,0,10,10,
10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,22,22,22,0,0,0,0,10,10,10,10,10,10,10,10,10,0,0,0,0,0,0,0,0,0,0,10,10,10,10,10,10,10,10,0,0,0,0,10,10,10,10,10,10,10,10,10,10,10,22,0,0,0,0,0,0,0,0,0,0,10,10,
10,10,10,10,16,16,16,16,16,16,10,10,10,10,10,10,10,10,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,10,10,10,10,0,0,0,0,10,10,10,10,0,0,0,0,0,0,10,10,0,0,0,0,10,10,10,10,0,0,0,0,0,10,10,10,10,10,10,10,22,22,0,0,0,0,0,0,0,0,0,0,0,10,10,
10,10,10,10,16,17,10,10,17,16,10,10,10,10,10,10,10,10,10,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,10,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,10,0,0,0,0,14,14,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,10,
10,10,10,10,16,16,16,16,16,16,10,10,10,10,10,10,10,10,10,10,10,10,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,10,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,14,14,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,10,
10,10,10,0,16,16,16,16,16,16,16,10,10,10,10,10,10,10,10,10,10,10,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,9,10,10,
10,10,0,0,14,17,10,10,17,14,14,14,10,10,10,10,10,10,10,10,10,10,10,9,9,9,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,10,10,10,10,
10,10,10,0,14,14,17,17,14,14,14,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,9,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,9,9,10,10,10,10,10,
10,10,10,10,14,14,14,14,14,14,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,9,9,0,0,0,0,0,0,0,0,0,0,0,12,12,12,12,12,9,9,9,9,9,9,9,9,9,0,0,9,9,9,9,9,10,10,10,14,14,14,14,14,14,14,14,14,14,14,14,0,12,0,0,0,9,9,10,10,10,10,10,10,10,10,
10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,0,0,0,0,0,0,12,12,12,9,9,9,9,9,10,10,10,10,10,10,10,10,10,9,10,10,10,10,10,10,10,10,10,10,12,12,12,12,12,12,12,12,12,12,12,9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,
10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,9,9,9,9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,9,9,9,9,9,9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10
};
static const uint16_t background_tiles[] = {
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,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,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,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,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,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,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,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,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,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,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,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,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,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,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,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,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,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,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,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,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,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,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,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,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,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,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,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,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,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,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,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,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,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,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,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,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4
};
static const ObjectSpawn spawns[] = {
{ GameObjectType::LightPlatform, 11, 9, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 12, 9, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 13, 9, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 14, 9, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 14, 10, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 15, 10, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 15, 9, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 16, 9, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 16, 10, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 17, 10, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 17, 9, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::Door, 40, 9, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 41, 9, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 42, 9, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 43, 9, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Trigger, 92, 12, 0, 0, TriggerAction::LevelComplete, 0 },
{ GameObjectType::LightPlatform, 51, 29, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 68, 30, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 69, 30, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 70, 30, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 70, 31, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 58, 27, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 57, 31, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 57, 32, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 56, 32, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 58, 31, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 59, 31, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 88, 24, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 89, 24, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 89, 23, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 90, 23, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 91, 23, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 91, 22, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 93, 25, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 93, 26, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 93, 27, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 92, 27, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 92, 28, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 93, 28, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 93, 29, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 93, 30, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 92, 30, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 92, 31, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 92, 32, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 93, 31, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 94, 30, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 94, 29, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 94, 28, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 92, 29, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 91, 29, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 76, 24, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 76, 23, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 76, 22, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 76, 22, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 75, 21, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 75, 22, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 76, 21, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 77, 21, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 77, 20, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 76, 20, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 76, 19, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 75, 19, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 75, 20, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 74, 20, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 74, 19, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::Door, 72, 30, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 73, 30, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 73, 31, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 74, 31, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 75, 31, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 76, 31, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 76, 32, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 75, 32, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 76, 33, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 77, 33, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 77, 32, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 80, 25, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 79, 26, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 79, 25, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 80, 26, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 78, 25, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 77, 25, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 77, 24, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 69, 51, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 70, 51, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 71, 51, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 78, 51, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 79, 51, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 80, 51, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 72, 51, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 73, 51, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 74, 51, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 75, 51, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 76, 51, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 77, 51, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 72, 48, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 72, 47, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 73, 47, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 73, 48, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 74, 47, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 74, 46, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 73, 46, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 75, 46, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 75, 47, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 57, 48, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 9, 49, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 9, 50, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 8, 50, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 9, 51, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 8, 51, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 10, 50, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 10, 49, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 11, 49, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 7, 51, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 6, 51, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 5, 51, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 5, 50, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 4, 50, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 4, 51, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 4, 49, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 7, 47, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 7, 48, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 6, 48, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 6, 47, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 5, 47, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 5, 48, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 4, 48, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 4, 47, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 4, 46, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 4, 45, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 5, 45, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 6, 45, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 7, 45, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 8, 45, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 9, 45, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 9, 46, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 9, 47, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 8, 47, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 8, 48, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 9, 48, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 10, 48, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::Door, 5, 46, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 8, 46, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 6, 50, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 7, 50, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 8, 49, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 5, 49, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 8, 34, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 9, 34, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 10, 34, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 11, 34, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 12, 34, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 9, 27, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 8, 27, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 8, 26, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 7, 26, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 7, 27, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 6, 27, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 6, 26, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::Door, 5, 22, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 6, 22, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 6, 23, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 6, 23, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 7, 22, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 7, 21, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 7, 23, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 8, 23, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 13, 21, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 13, 22, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 12, 22, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 14, 22, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 14, 21, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 15, 21, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 15, 22, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 16, 22, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 16, 21, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 15, 32, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 15, 31, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 3, 49, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 2, 49, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 3, 50, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 3, 48, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::Trigger, 69, 27, 0, 0, TriggerAction::Checkpoint, 0 },
{ GameObjectType::Trigger, 89, 49, 0, 0, TriggerAction::Checkpoint, 0 },
{ GameObjectType::Trigger, 4, 34, 0, 0, TriggerAction::Checkpoint, 0 },
{ GameObjectType::Trigger, 15, 16, 0, 0, TriggerAction::Checkpoint, 0 },
{ GameObjectType::Trigger, 34, 10, 0, 0, TriggerAction::Checkpoint, 0 },
{ GameObjectType::Trigger, 37, 30, 0, 0, TriggerAction::Checkpoint, 0 },
{ GameObjectType::Collectible, 42, 27, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 58, 26, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 51, 28, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 58, 25, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 87, 24, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 86, 24, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 74, 45, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 74, 43, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 74, 44, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 15, 30, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 15, 29, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 15, 28, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::ShadowPlatform, 14, 20, 0, 1023, TriggerAction::None, 0 },
{ GameObjectType::Trigger, 37, 30, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Player, 37, 30, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Trigger, 37, 30, 0, 0, TriggerAction::Checkpoint, 0 },
{ GameObjectType::Collectible, 94, 23, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 90, 22, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Door, 81, 26, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 68, 43, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::Collectible, 71, 50, 0, 0, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 8, 36, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 8, 35, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::LightPlatform, 9, 35, 3192, 4095, TriggerAction::None, 0 },
{ GameObjectType::Door, 4, 14, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 5, 14, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 5, 13, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 4, 13, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 5, 12, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 6, 12, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 6, 13, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 7, 13, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 7, 12, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 4, 11, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 5, 11, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Door, 6, 11, 1023, 3192, TriggerAction::None, 0 },
{ GameObjectType::Trigger, 71, 9, 0, 0, TriggerAction::Checkpoint, 0 },
{ GameObjectType::Trigger, 58, 50, 0, 0, TriggerAction::Checkpoint, 0 },
{ GameObjectType::Trigger, 25, 48, 0, 0, TriggerAction::Checkpoint, 0 },
{ GameObjectType::Collectible, 52, 44, 0, 0, TriggerAction::None, 0 },
};
static const RenderData::Image kAtlas(tile_atlas_pixels, tile_atlas_width, tile_atlas_height);
static const LevelData data = {
tiles,
kMapWidth,
kMapHeight,
kTileSize,
&kAtlas,
tile_atlas_columns,
spawns,
228,
nullptr,
0,
Math::Vector2Int(1184, 960),
0,
0,
3072,
1728,
background_tiles
};
}
}

View File

@ -0,0 +1,34 @@
#pragma once
#include "RoomLayout.h"
namespace LightGame
{
namespace LevelTotal
{
// 3 columns x 3 rows; each room is 32x18 tiles = 1024x576 pixels.
// default_spawn 为该房间的 Checkpoint Trigger 像素位置tile_x*32, tile_y*32
// 数据源LevelTotalData.h spawns 中 TriggerAction::Checkpoint 条目。
static const RoomDef rooms_array[] = {
{ { 0, 0, 1024, 576 }, Math::Vector2Int( 480, 512) }, // tile(15,16)
{ { 1024, 0, 2048, 576 }, Math::Vector2Int(1088, 320) }, // tile(34,10)
{ { 2048, 0, 3072, 576 }, Math::Vector2Int(2272, 288) }, // tile(71, 9)
{ { 0, 576, 1024, 1152 }, Math::Vector2Int( 128, 1088) }, // tile( 4,34)
{ { 1024, 576, 2048, 1152 }, Math::Vector2Int(1184, 960) }, // tile(37,30)
{ { 2048, 576, 3072, 1152 }, Math::Vector2Int(2208, 864) }, // tile(69,27)
{ { 0, 1152, 1024, 1728 }, Math::Vector2Int( 800, 1536) }, // tile(25,48)
{ { 1024, 1152, 2048, 1728 }, Math::Vector2Int(1856, 1600) }, // tile(58,50)
{ { 2048, 1152, 3072, 1728 }, Math::Vector2Int(2848, 1568) } // tile(89,49)
};
static const RoomGrid room_grid = {
rooms_array,
3,
3,
1024,
576
};
static const int32_t kStartRoomIndex = 4; // center (1,1)
}
}

View File

@ -14,9 +14,7 @@
#include "LightGameApp.h"
#include "LevelEditor.h"
#include "font_atlas.h"
#include "Level1Data.h"
#include "Level2Data.h"
#include "Level3Data.h"
#include "LevelTotalData.h"
#ifdef USE_FRAMEBUFFER
#include "FBDisplay.h"
@ -163,9 +161,7 @@ int main(int argc, char* argv[])
if (editor.get_pending_load() >= 0)
{
int32_t idx = editor.get_pending_load();
if (idx == 0) editor.load_from_data(LightGame::Level1::data);
else if (idx == 1) editor.load_from_data(LightGame::Level2::data);
else if (idx == 2) editor.load_from_data(LightGame::Level3::data);
if (idx == 0) editor.load_from_data(LightGame::LevelTotal::data);
editor.clear_pending_load();
}
editor.update(timer.fixed_delta_ms(), &keyboardState, &pointerInput);

View File

@ -1,4 +1,5 @@
#include "LightEffectSystem.h"
#include "TileIds.h"
#include "tile_atlas.h"
namespace LightGame
@ -13,12 +14,12 @@ namespace LightGame
return RenderData::Sprite(atlas, col * tile_size, row * tile_size, tile_size, tile_size);
}
static const RenderData::Sprite spr_light_platform_on = MakeTileSprite(&tile_atlas_image, 6, 32, 4);
static const RenderData::Sprite spr_light_platform_off = MakeTileSprite(&tile_atlas_image, 5, 32, 4);
static const RenderData::Sprite spr_shadow_platform_on = MakeTileSprite(&tile_atlas_image, 8, 32, 4);
static const RenderData::Sprite spr_shadow_platform_off = MakeTileSprite(&tile_atlas_image, 7, 32, 4);
static const RenderData::Sprite spr_door_closed = MakeTileSprite(&tile_atlas_image, 9, 32, 4);
static const RenderData::Sprite spr_door_open = MakeTileSprite(&tile_atlas_image, 10, 32, 4);
static const RenderData::Sprite spr_light_platform_on = MakeTileSprite(&tile_atlas_image, TileId::LightPlatformOn, 32, tile_atlas_columns);
static const RenderData::Sprite spr_light_platform_off = MakeTileSprite(&tile_atlas_image, TileId::LightPlatformOff, 32, tile_atlas_columns);
static const RenderData::Sprite spr_shadow_platform_on = MakeTileSprite(&tile_atlas_image, TileId::ShadowPlatformOn, 32, tile_atlas_columns);
static const RenderData::Sprite spr_shadow_platform_off = MakeTileSprite(&tile_atlas_image, TileId::ShadowPlatformOff, 32, tile_atlas_columns);
static const RenderData::Sprite spr_door_closed = MakeTileSprite(&tile_atlas_image, TileId::DoorClosed, 32, tile_atlas_columns);
static const RenderData::Sprite spr_door_open = MakeTileSprite(&tile_atlas_image, TileId::DoorOpen, 32, tile_atlas_columns);
}
void LightEffectSystem::update(std::vector<GameObject>& objects, uint16_t light_level)

View File

@ -8,6 +8,34 @@
namespace Core
{
namespace
{
static uint16_t rgba5551_to_rgb565_shaded(uint16_t c, int32_t shade_numerator, int32_t shade_shift)
{
if (shade_numerator <= 0)
{
return 0;
}
const int32_t max_numerator = 1 << shade_shift;
if (shade_numerator >= max_numerator)
{
return RenderData::rgba5551_to_rgb565(c);
}
uint16_t r = static_cast<uint16_t>((c >> 11) & 0x1Fu);
uint16_t g = static_cast<uint16_t>((c >> 6) & 0x1Fu);
uint16_t b = static_cast<uint16_t>((c >> 1) & 0x1Fu);
r = static_cast<uint16_t>((static_cast<int32_t>(r) * shade_numerator) >> shade_shift);
g = static_cast<uint16_t>((static_cast<int32_t>(g) * shade_numerator) >> shade_shift);
b = static_cast<uint16_t>((static_cast<int32_t>(b) * shade_numerator) >> shade_shift);
const uint16_t g6 = static_cast<uint16_t>((g << 1) | (g >> 4));
return static_cast<uint16_t>((r << 11) | (g6 << 5) | b);
}
}
DrawContext::DrawContext(int32_t width, int32_t height)
{
frameBuffer = new Core::FrameBuffer(width, height);
@ -168,11 +196,29 @@ namespace Core
int32_t screen_x, int32_t screen_y,
int32_t viewport_w, int32_t viewport_h,
int32_t camera_x, int32_t camera_y)
{
draw_tilemap_shaded(tilemap,
screen_x,
screen_y,
viewport_w,
viewport_h,
camera_x,
camera_y,
1,
0);
}
void DrawContext::draw_tilemap_shaded(const RenderData::Tilemap& tilemap,
int32_t screen_x, int32_t screen_y,
int32_t viewport_w, int32_t viewport_h,
int32_t camera_x, int32_t camera_y,
int32_t shade_numerator, int32_t shade_shift)
{
if (!tilemap.tiles || !tilemap.atlas || !tilemap.atlas->pixels) return;
if (tilemap.width <= 0 || tilemap.height <= 0) return;
if (tilemap.tile_w <= 0 || tilemap.tile_h <= 0 || tilemap.atlas_columns <= 0) return;
if (viewport_w <= 0 || viewport_h <= 0) return;
if (shade_shift < 0 || shade_shift > 15) return;
const int32_t viewport_left = screen_x;
const int32_t viewport_top = screen_y;
@ -235,10 +281,33 @@ namespace Core
const int32_t clipped_w = clipped_right - clipped_left;
const int32_t clipped_h = clipped_bottom - clipped_top;
if (shade_numerator >= (1 << shade_shift))
{
blit_sprite_pixels(clipped_left, clipped_top, *tilemap.atlas,
clipped_src_x, clipped_src_y, clipped_w, clipped_h,
1, false, false);
}
else
{
const int32_t img_w = tilemap.atlas->width;
const uint16_t* src = static_cast<const uint16_t*>(tilemap.atlas->pixels);
Core::FramePixel* dst = static_cast<Core::FramePixel*>(frameBuffer->get_buffer());
const int32_t fb_w = frameBuffer->get_width();
for (int32_t y = 0; y < clipped_h; ++y)
{
const int32_t read_y = clipped_src_y + y;
Core::FramePixel* dst_row = dst + (clipped_top + y) * fb_w;
const uint16_t* src_row = src + read_y * img_w;
for (int32_t x = 0; x < clipped_w; ++x)
{
const uint16_t pixel = src_row[clipped_src_x + x];
if (!RenderData::rgba5551_is_opaque(pixel)) continue;
dst_row[clipped_left + x] = rgba5551_to_rgb565_shaded(pixel, shade_numerator, shade_shift);
}
}
}
}
}
}

View File

@ -74,6 +74,11 @@ namespace Core
int32_t screen_x, int32_t screen_y,
int32_t viewport_w, int32_t viewport_h,
int32_t camera_x, int32_t camera_y);
void draw_tilemap_shaded(const RenderData::Tilemap& tilemap,
int32_t screen_x, int32_t screen_y,
int32_t viewport_w, int32_t viewport_h,
int32_t camera_x, int32_t camera_y,
int32_t shade_numerator, int32_t shade_shift);
void fill_rect(int32_t x, int32_t y, int32_t w, int32_t h, const RenderData::Color& color);
void draw_text(const RenderData::BitmapFont& font, int32_t x, int32_t y,

103
tools/analyze_foreground.py Normal file
View File

@ -0,0 +1,103 @@
"""Print per-GUID analysis of the foreground Tilemap to help identify TileIds.
For each tile asset GUID, prints:
- count
- bounding box (min/max x, y in Unity cell coords)
- 5 sample positions
- top neighbor distribution: which GUID most often sits directly ABOVE this one
- bottom neighbor distribution: which GUID most often sits directly BELOW
Run:
python tools/analyze_foreground.py
"""
from __future__ import annotations
import os
import sys
from collections import Counter, defaultdict
from typing import Dict, Tuple
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import unity_to_level as u2l
def short(guid: str) -> str:
return guid[:8]
def main() -> int:
with open(u2l.SCENE_PATH, "r", encoding="utf-8") as f:
text = f.read()
docs = u2l.split_documents(text)
go_names = u2l.parse_gameobjects(docs)
tms = u2l.parse_tilemaps(docs, go_names)
fg = tms.get("foreground")
if not fg:
print("no foreground tilemap")
return 1
# Build a (x, y) -> guid map; drop duplicates conservatively.
cell_guid: Dict[Tuple[int, int], str] = {}
for x, y, idx in fg.cells:
if 0 <= idx < len(fg.asset_guids):
cell_guid[(x, y)] = fg.asset_guids[idx]
# Group cells by guid.
by_guid: Dict[str, list] = defaultdict(list)
for (x, y), guid in cell_guid.items():
by_guid[guid].append((x, y))
# Order by count desc.
ordered = sorted(by_guid.items(), key=lambda kv: -len(kv[1]))
print(f"foreground tile assets: {len(fg.asset_guids)}, total cells: {len(cell_guid)}")
print()
for guid, cells in ordered:
xs = [c[0] for c in cells]
ys = [c[1] for c in cells]
# Neighbors: in Unity Y is up, so "above" = y+1, "below" = y-1.
above_counter: Counter = Counter()
below_counter: Counter = Counter()
left_counter: Counter = Counter()
for x, y in cells:
up = cell_guid.get((x, y + 1))
dn = cell_guid.get((x, y - 1))
lf = cell_guid.get((x - 1, y))
above_counter[up] += 1 # None means empty/sky
below_counter[dn] += 1
left_counter[lf] += 1
# Sample positions: a few spread out cells.
sample = sorted(cells)[:: max(1, len(cells) // 5)][:5]
print(f"{short(guid)} ({guid})")
print(f" count : {len(cells)}")
print(f" bbox : x [{min(xs)} .. {max(xs)}] y [{min(ys)} .. {max(ys)}]")
print(f" samples: {sample}")
# top 3 of each direction
def fmt(c: Counter) -> str:
top = c.most_common(3)
return ", ".join(
f"{short(g) if g else '<empty>'}={n}" for g, n in top
)
print(f" above (Unity y+1): {fmt(above_counter)}")
print(f" below (Unity y-1): {fmt(below_counter)}")
print(f" left : {fmt(left_counter)}")
# If almost all rows are the same y, it's probably a horizontal-only tile.
y_hist = Counter(ys)
top_y = y_hist.most_common(3)
print(f" y histogram (top 3): {top_y}")
# Single-row or single-col flag
if len(set(ys)) <= 2:
print(f" >>> appears on only {len(set(ys))} distinct row(s) — likely a 'top' or row-specific tile")
if len(set(xs)) <= 2:
print(f" >>> appears on only {len(set(xs))} distinct column(s)")
print()
return 0
if __name__ == "__main__":
sys.exit(main())

107
tools/find_room_spawns.py Normal file
View File

@ -0,0 +1,107 @@
"""Validate default spawn candidates per 3x3 room of LevelTotal.
Reads tiles[] from src/Apps/LightGame/src/levels/LevelTotalData.h, then for each
candidate (col,row,tile_x,tile_y) checks:
- tile at (tx,ty) and (tx,ty+1)/(tx,ty-1) per collider (8,0)->(24,32) is non-solid
- tile at (tx, ty+1) (the tile directly under feet at world_y=tile_y*32+32) is solid
Player position used by spawn check (LightGameApp.cpp:181):
pos = (tile_x*32, tile_y*32) -- collider min=(8,0), max=(24,32)
world_box = pos + collider = (tx*32+8, ty*32) -> (tx*32+24, ty*32+32)
is_grounded checks tiles below feet; collider rows tile_top..tile_bottom-1 must be non-solid.
Solid ids: 5,6,7,8,9,10,11.
Deadly: 12, 22.
"""
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SRC = ROOT / "src/Apps/LightGame/src/levels/LevelTotalData.h"
text = SRC.read_text(encoding="utf-8")
# Parse tiles array
m = re.search(r"static const uint16_t tiles\[\]\s*=\s*\{([^}]*)\};", text, re.DOTALL)
nums = [int(x) for x in re.findall(r"-?\d+", m.group(1))]
W, H = 96, 54
assert len(nums) == W * H, len(nums)
tiles = [nums[y * W:(y + 1) * W] for y in range(H)]
SOLID = {5, 6, 7, 8, 9, 10, 11}
DEADLY = {12, 22}
def at(tx, ty):
if tx < 0 or tx >= W or ty < 0 or ty >= H:
return -1
return tiles[ty][tx]
def collider_overlaps_solid(tx, ty):
# world_box = (tx*32+8, ty*32) -> (tx*32+24, ty*32+32)
# tile rows: ty .. (ty*32+32-1)//32 = ty (since 32+32-1=63, //32=1, so rows ty..ty+1-1 = ty)
# Actually max.y - 1 = ty*32 + 31, //32 = ty. So only row ty.
# tile cols: (tx*32+8)//32 = tx, (tx*32+24-1)//32 = tx. Only col tx.
return at(tx, ty) in SOLID or at(tx, ty) in DEADLY
def has_solid_below(tx, ty):
# Player feet at world_y = ty*32 + 32. is_grounded probes 1px below = ty*32+33.
# Tile row = (ty*32+32)//32 = ty+1.
return at(tx, ty + 1) in SOLID
def safe_above_head(tx, ty):
# don't have ceiling spike just touching
return at(tx, ty - 1) not in DEADLY
def candidate_score(tx, ty):
if collider_overlaps_solid(tx, ty):
return None, "overlap solid/deadly at body row"
if not has_solid_below(tx, ty):
return None, "not grounded"
if not safe_above_head(tx, ty):
return None, "deadly above head"
return True, "ok"
# Room grid: cols 0/32/64, rows 0/18/36; size 32x18
def find_safe_in_room(rcol, rrow, prefer=None):
tx0, ty0 = rcol * 32, rrow * 18
tx1, ty1 = tx0 + 32, ty0 + 18
candidates = []
if prefer:
ptx, pty = prefer
ok, why = candidate_score(ptx, pty)
if ok:
return (ptx, pty, "preferred")
else:
print(f" preferred {(ptx,pty)} rejected: {why}")
# Scan center outward
cx, cy = (tx0 + tx1) // 2, (ty0 + ty1) // 2
for ty in range(ty0, ty1):
for tx in range(tx0, tx1):
ok, why = candidate_score(tx, ty)
if ok:
d = (tx - cx) ** 2 + (ty - cy) ** 2
candidates.append((d, tx, ty))
if not candidates:
return None
candidates.sort()
_, tx, ty = candidates[0]
return (tx, ty, "auto")
# Center room must reuse current spawn (1184, 928) = tile (37, 29)
preferences = {
(1, 1): (37, 29),
}
print(f"{'Room':<8} {'tile':<10} {'pixel':<14} note")
print("-" * 50)
for rrow in range(3):
for rcol in range(3):
prefer = preferences.get((rcol, rrow))
result = find_safe_in_room(rcol, rrow, prefer=prefer)
if result is None:
print(f"({rcol},{rrow}) NO SAFE SPAWN FOUND")
continue
tx, ty, kind = result
# Spawn pos in pixels = (tx*32, ty*32). Verify under feet:
below = at(tx, ty + 1)
print(f"({rcol},{rrow}) ({tx:3d},{ty:3d}) ({tx*32:5d},{ty*32:4d}) below={below} ({kind})")

422
tools/unity_to_level.py Normal file
View File

@ -0,0 +1,422 @@
"""Convert Unity Tilemap data from SampleScene.unity into LevelTotalData.h.
Workflow:
1. Parse the Unity scene YAML (no PyYAML dep pure text scan,
because the file is huge and uses Unity-specific tags).
2. For each named Tilemap GameObject (e.g. "foreground", "background"),
extract:
- m_TileAssetArray : ordered list of tile asset GUIDs
- m_Tiles : list of (x, y, m_TileIndex)
3. Map each tile asset GUID -> a project TileId (uint16_t).
4. Crop to a fixed window (origin + width + height, in Unity cell coords)
and emit a C++ uint16_t[] array. Unity Y is up; the C++ array is
row-major with row 0 at the TOP, so we flip Y on output.
Usage:
python tools/unity_to_level.py
Edit FOREGROUND_GUID_TO_TILEID below to fill in the foreground mapping.
"""
from __future__ import annotations
import os
import re
import sys
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Tuple
# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SCENE_PATH = os.path.join(REPO_ROOT, "SampleScene.unity")
OUTPUT_PATH = os.path.join(
REPO_ROOT, "src", "Apps", "LightGame", "src", "levels", "LevelTotalData.h"
)
# Crop window in Unity cell coordinates. Aligned to the `background`
# Tilemap's origin (-43, -24). LevelTotal is 96 wide x 54 tall.
CROP_ORIGIN_X = -42
CROP_ORIGIN_Y = -24
CROP_WIDTH = 96
CROP_HEIGHT = 54
# TileId values mirror src/Apps/LightGame/src/engine/TileIds.h
class TileId:
Empty = 0
Background1 = 1
Background2 = 2
Background3 = 3
Background4 = 4
GroundTopGreen = 5
GroundFillGreen = 6
GroundTopGray = 7
GroundFillGray = 8
GroundTopPurple = 9
GroundFillPurple = 10
Platform = 11
Spike = 12
LightPlatformOff = 13
LightPlatformOn = 14
ShadowPlatformOff = 15
ShadowPlatformOn = 16
DoorClosed = 17
DoorOpen = 18
Coin = 19
Flag = 20
Checkpoint = 21
SpikeCeiling = 22
DecorVine = 23
# background Tilemap: m_TileAssetArray order (verified by user).
# Indexes 0..3 in the array correspond to TileId 1..4.
BACKGROUND_GUID_TO_TILEID: Dict[str, int] = {
"7a416b45749852649890b58c40c94382": TileId.Background1,
"3e398b33bfe59e8488e6622639e22680": TileId.Background2,
"9c0d8493a349d2d4c9394cd4db6c144c": TileId.Background3,
"c311fab5d43fea040b20ddf4109be8d4": TileId.Background4,
}
# foreground Tilemap: m_TileAssetArray order (16 entries in scene).
# >>> FILL THIS IN <<< — map each GUID to the matching TileId.* constant.
# Use TileId.Empty (= 0) to drop a tile, or comment a line out to leave
# it as Empty (the default).
FOREGROUND_GUID_TO_TILEID: Dict[str, int] = {
"fc664ce3f6082ba478b8d50ca2dce27e": 12,
"47a0287d160d89e449cfb63ece6bc64d": 10,
"9c787be8025ad3e4ea41faf8641b6542": 19,
"d317ceed21dc2844cbe35d377a237065": 9,
"228f030a961526742ac581cd160291f2": 1,
"5276bbe304866324fbd94b343f5d5716": 1,
"47822c4ae0c151848be71ecddbe3e363": 20,
"9dae7bdac3edf7e4996e186ac7ea8d42": 8,
"f3c8dcd4899bb144795bf74e61529d57": 6,
"430a6d0aec468d94d929e481d5082741": 5,
"dcd1ff73844513940941a7906732f790": 7,
"8caba5ecd24bd3143831d4f04096921f": 17,
"c0ea1f55905049745a90ea7d4772f95f": 22,
"0f7b94bca5b66ea4e8bf37ac8b2c1528": 16,
"67e0c92da848ffa49b3792338de741ac": 14,
"1289993ae67654b48a59c5743728e507": 20,
}
# Map Unity GameObject name -> tile id mapping table.
LAYER_MAPPINGS: Dict[str, Dict[str, int]] = {
"foreground": FOREGROUND_GUID_TO_TILEID,
"background": BACKGROUND_GUID_TO_TILEID,
}
# ---------------------------------------------------------------------------
# Parser
# ---------------------------------------------------------------------------
@dataclass
class TilemapBlock:
name: str
asset_guids: List[str] = field(default_factory=list) # by m_TileIndex
cells: List[Tuple[int, int, int]] = field(default_factory=list) # x, y, idx
# Scene file blocks are separated by lines that start with "--- !u!".
DOC_SEP = re.compile(r"^--- !u!(\d+) &(\d+)", re.MULTILINE)
def split_documents(text: str) -> List[Tuple[int, int, str]]:
"""Return a list of (class_id, file_id, body) for each YAML doc."""
matches = list(DOC_SEP.finditer(text))
docs: List[Tuple[int, int, str]] = []
for i, m in enumerate(matches):
start = m.end()
end = matches[i + 1].start() if i + 1 < len(matches) else len(text)
docs.append((int(m.group(1)), int(m.group(2)), text[start:end]))
return docs
def parse_gameobjects(docs) -> Dict[int, str]:
"""Map GameObject fileID -> name."""
name_re = re.compile(r"^\s*m_Name:\s*(.+?)\s*$", re.MULTILINE)
out: Dict[int, str] = {}
for class_id, file_id, body in docs:
if class_id != 1: # GameObject
continue
m = name_re.search(body)
if m:
out[file_id] = m.group(1)
return out
GAMEOBJECT_REF_RE = re.compile(r"m_GameObject:\s*\{fileID:\s*(\d+)\}")
TILE_FIRST_RE = re.compile(
r"-\s*first:\s*\{x:\s*(-?\d+),\s*y:\s*(-?\d+),\s*z:\s*-?\d+\}\s*"
r"second:\s*\n"
r"\s*serializedVersion:\s*\d+\s*\n"
r"\s*m_TileIndex:\s*(-?\d+)"
)
ASSET_ENTRY_RE = re.compile(
r"-\s*m_RefCount:\s*(-?\d+)\s*\n\s*m_Data:\s*\{fileID:\s*\d+,\s*guid:\s*([0-9a-f]+),"
)
def parse_tilemaps(docs, go_names: Dict[int, str]) -> Dict[str, TilemapBlock]:
"""Find Tilemap docs (class 1839735485) and bucket by parent GO name."""
out: Dict[str, TilemapBlock] = {}
for class_id, _file_id, body in docs:
if class_id != 1839735485: # Tilemap
continue
m = GAMEOBJECT_REF_RE.search(body)
if not m:
continue
go_id = int(m.group(1))
name = go_names.get(go_id)
if not name:
continue
# Slice out the m_Tiles ... m_AnimatedTiles section so the cell regex
# only sees real tile entries.
tiles_start = body.find("m_Tiles:")
tiles_end = body.find("m_AnimatedTiles", tiles_start if tiles_start >= 0 else 0)
tiles_body = body[tiles_start:tiles_end] if tiles_start >= 0 else ""
# Slice the m_TileAssetArray section.
asset_start = body.find("m_TileAssetArray:")
asset_end = body.find("m_TileSpriteArray", asset_start if asset_start >= 0 else 0)
asset_body = body[asset_start:asset_end] if asset_start >= 0 else ""
# Collect (refcount, guid) entries in their YAML order.
asset_entries: List[Tuple[int, str]] = []
for am in ASSET_ENTRY_RE.finditer(asset_body):
asset_entries.append((int(am.group(1)), am.group(2)))
# Collect raw cell entries (x, y, raw_idx).
raw_cells: List[Tuple[int, int, int]] = []
for tm in TILE_FIRST_RE.finditer(tiles_body):
raw_cells.append((int(tm.group(1)), int(tm.group(2)), int(tm.group(3))))
# Unity does NOT renumber m_TileIndex when entries are removed from
# m_TileAssetArray, so the YAML index of an entry is not necessarily
# equal to the m_TileIndex referencing it. Reconstruct the mapping
# by aligning the YAML-order asset entries with the *sorted distinct*
# m_TileIndex values that share the same refcount/usage-count.
from collections import Counter as _Counter
idx_counts = _Counter(c[2] for c in raw_cells)
sorted_idxs = sorted(idx_counts.keys())
block = TilemapBlock(name=name)
if len(sorted_idxs) == len(asset_entries):
# Pair entries in array order with idx values in ascending order.
# Sanity-check that refcount matches usage count for each pair.
for entry, idx in zip(asset_entries, sorted_idxs):
refcount, guid = entry
actual = idx_counts[idx]
if refcount != actual:
print(
f" [warn] '{name}' refcount mismatch for guid {guid[:8]}: "
f"asset refcount={refcount} but idx={idx} appears {actual} times",
file=sys.stderr,
)
# Grow asset_guids so [idx] -> guid works directly.
while len(block.asset_guids) <= idx:
block.asset_guids.append("")
block.asset_guids[idx] = guid
else:
print(
f" [warn] '{name}' has {len(asset_entries)} asset entries but "
f"{len(sorted_idxs)} distinct m_TileIndex values; falling back "
f"to positional mapping (results may be wrong)",
file=sys.stderr,
)
for entry in asset_entries:
block.asset_guids.append(entry[1])
block.cells = raw_cells
out[name] = block
return out
# ---------------------------------------------------------------------------
# Conversion
# ---------------------------------------------------------------------------
def block_to_grid(
block: TilemapBlock,
guid_to_tile: Dict[str, int],
origin_x: int,
origin_y: int,
width: int,
height: int,
) -> List[List[int]]:
"""Convert a TilemapBlock to a row-major grid (row 0 = top).
Cells outside the crop window are dropped. Cells whose tile asset has no
mapping default to TileId.Empty (and a warning is printed once per GUID).
"""
grid = [[0 for _ in range(width)] for _ in range(height)]
unmapped: Dict[str, int] = {}
for x, y, idx in block.cells:
if idx < 0 or idx >= len(block.asset_guids):
continue
guid = block.asset_guids[idx]
tile_id = guid_to_tile.get(guid)
if tile_id is None:
unmapped[guid] = unmapped.get(guid, 0) + 1
continue
if tile_id == 0:
continue
col = x - origin_x
# Unity Y is up; flip so row 0 is the top of the array.
row = (origin_y + height - 1) - y
if 0 <= col < width and 0 <= row < height:
grid[row][col] = tile_id
if unmapped:
print(
f" [warn] {len(unmapped)} unmapped GUID(s) in '{block.name}' "
f"(treated as Empty):",
file=sys.stderr,
)
for guid, count in sorted(unmapped.items(), key=lambda kv: -kv[1]):
print(f" {guid} x{count}", file=sys.stderr)
return grid
# ---------------------------------------------------------------------------
# C++ emitter
# ---------------------------------------------------------------------------
HEADER_TEMPLATE = """#pragma once
#include "LevelData.h"
#include "tile_atlas.h"
namespace LightGame
{{
namespace LevelTotal
{{
static const int32_t kMapWidth = {width};
static const int32_t kMapHeight = {height};
static const int32_t kTileSize = 32;
static const uint16_t tiles[] = {{
{tiles_body}
}};
static const uint16_t background_tiles[] = {{
{background_body}
}};
static const ObjectSpawn spawns[] = {{
{{ GameObjectType::Player, 69, 29, 0, 0, TriggerAction::None, 0 }},
}};
static const RenderData::Image kAtlas(tile_atlas_pixels, tile_atlas_width, tile_atlas_height);
static const LevelData data = {{
tiles,
kMapWidth,
kMapHeight,
kTileSize,
&kAtlas,
tile_atlas_columns,
spawns,
1,
nullptr,
0,
Math::Vector2Int({spawn_px_x}, {spawn_px_y}),
0,
0,
{bounds_max_x},
{bounds_max_y},
background_tiles
}};
}}
}}
"""
def format_grid(grid: List[List[int]], indent: str = " ") -> str:
rows = []
for row in grid:
rows.append(indent + ", ".join(str(v) for v in row) + ",")
if rows:
rows[-1] = rows[-1].rstrip(",")
return "\n".join(rows)
def emit_header(
fg_grid: List[List[int]],
bg_grid: List[List[int]],
width: int,
height: int,
tile_size: int = 32,
) -> str:
spawn_tile_x, spawn_tile_y = 69, 29 # preserved from current LevelTotal
return HEADER_TEMPLATE.format(
width=width,
height=height,
tiles_body=format_grid(fg_grid),
background_body=format_grid(bg_grid),
spawn_px_x=spawn_tile_x * tile_size,
spawn_px_y=spawn_tile_y * tile_size,
bounds_max_x=width * tile_size,
bounds_max_y=height * tile_size,
)
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main() -> int:
if not os.path.isfile(SCENE_PATH):
print(f"scene file not found: {SCENE_PATH}", file=sys.stderr)
return 1
with open(SCENE_PATH, "r", encoding="utf-8") as f:
text = f.read()
print(f"reading {SCENE_PATH} ({len(text)} bytes)")
docs = split_documents(text)
go_names = parse_gameobjects(docs)
tilemaps = parse_tilemaps(docs, go_names)
print(f"found Tilemaps: {sorted(tilemaps.keys())}")
for name, block in tilemaps.items():
print(f" {name}: {len(block.cells)} cells, {len(block.asset_guids)} tile assets")
missing = [n for n in ("foreground", "background") if n not in tilemaps]
if missing:
print(f"missing Tilemap layers: {missing}", file=sys.stderr)
return 1
fg_grid = block_to_grid(
tilemaps["foreground"],
FOREGROUND_GUID_TO_TILEID,
CROP_ORIGIN_X, CROP_ORIGIN_Y, CROP_WIDTH, CROP_HEIGHT,
)
bg_grid = block_to_grid(
tilemaps["background"],
BACKGROUND_GUID_TO_TILEID,
CROP_ORIGIN_X, CROP_ORIGIN_Y, CROP_WIDTH, CROP_HEIGHT,
)
if not FOREGROUND_GUID_TO_TILEID:
print(
"\n[note] FOREGROUND_GUID_TO_TILEID is empty — foreground will be all 0.\n"
" Edit tools/unity_to_level.py and re-run once you have the mapping.",
file=sys.stderr,
)
out = emit_header(fg_grid, bg_grid, CROP_WIDTH, CROP_HEIGHT)
with open(OUTPUT_PATH, "w", encoding="utf-8", newline="\n") as f:
f.write(out)
print(f"wrote {OUTPUT_PATH}")
return 0
if __name__ == "__main__":
sys.exit(main())