添加 LightGame 关卡编辑器并修复死亡扣命、光感门阈值与渲染问题
- 新增 LevelEditor:ImGui 面板支持 tile 绘制、对象放置、选择、擦除、网格/碰撞体可视化、关卡导出为头文件 - 修复 LevelRenderer::draw 中 draw_tilemap 错误传入负 screen 坐标导致摄像机移动后 tile 消失 - 修复 Preview Light 滑块用 reinterpret_cast 将 uint16_t 强转为 int* 写入 4 字节覆盖相邻 bool 成员 - 修复编辑器放置光感门缺少 light_threshold 默认值导致不响应亮度变化 - 统一 LightPlatform[3192,4095] ShadowPlatform[0,1023] Door[1023,3192] 阈值到关卡数据与编辑器默认值 - 修复 SDL_StopTextInput 导致 ImGui InputText 无法输入及键盘状态异常 - 修复死亡状态每帧扣命导致一次死亡直接 GameOver - 集成 ImGui (SDL2 Renderer 后端) 到构建系统
This commit is contained in:
@@ -8,9 +8,10 @@ set(LIGHTGAME_SOURCES
|
||||
src/engine/GameStateManager.cpp
|
||||
src/engine/LightGameApp.cpp
|
||||
src/systems/LightEffectSystem.cpp
|
||||
src/editor/LevelEditor.cpp
|
||||
src/main.cpp
|
||||
)
|
||||
|
||||
add_executable(IMX6U-LightGame ${LIGHTGAME_SOURCES})
|
||||
target_include_directories(IMX6U-LightGame PRIVATE src/engine src/systems src/levels generated)
|
||||
target_include_directories(IMX6U-LightGame PRIVATE src/engine src/systems src/levels src/editor generated)
|
||||
imx6u_configure_app_target(IMX6U-LightGame)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,709 @@
|
||||
#include "LevelEditor.h"
|
||||
#include "DrawContext.h"
|
||||
#include "BitmapFont.h"
|
||||
#include "LevelLoader.h"
|
||||
#include "imgui.h"
|
||||
#include "tile_atlas.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace LightGame
|
||||
{
|
||||
namespace
|
||||
{
|
||||
static RenderData::Sprite MakeTileSprite(const RenderData::Image* atlas, int32_t tile_id,
|
||||
int32_t tile_size, int32_t atlas_columns)
|
||||
{
|
||||
const int32_t col = tile_id % atlas_columns;
|
||||
const int32_t row = tile_id / atlas_columns;
|
||||
return RenderData::Sprite(atlas, col * tile_size, row * tile_size, tile_size, tile_size);
|
||||
}
|
||||
|
||||
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 char* ObjTypeToString(GameObjectType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case GameObjectType::Player: return "Player";
|
||||
case GameObjectType::StaticPlatform: return "StaticPlatform";
|
||||
case GameObjectType::LightPlatform: return "LightPlatform";
|
||||
case GameObjectType::ShadowPlatform: return "ShadowPlatform";
|
||||
case GameObjectType::Hazard: return "Hazard";
|
||||
case GameObjectType::Collectible: return "Collectible";
|
||||
case GameObjectType::Trigger: return "Trigger";
|
||||
case GameObjectType::Door: return "Door";
|
||||
}
|
||||
return "StaticPlatform";
|
||||
}
|
||||
|
||||
static const char* TriggerActionToString(TriggerAction action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case TriggerAction::None: return "None";
|
||||
case TriggerAction::LevelComplete: return "LevelComplete";
|
||||
case TriggerAction::Checkpoint: return "Checkpoint";
|
||||
case TriggerAction::Death: return "Death";
|
||||
}
|
||||
return "None";
|
||||
}
|
||||
}
|
||||
|
||||
LevelEditor::LevelEditor(int32_t screen_w, int32_t screen_h)
|
||||
: screen_width_(screen_w),
|
||||
screen_height_(screen_h),
|
||||
active_(false),
|
||||
should_quit_(false),
|
||||
current_tool_(Tool::TileBrush),
|
||||
selected_tile_id_(1),
|
||||
selected_object_type_(GameObjectType::StaticPlatform),
|
||||
selected_object_id_(0),
|
||||
preview_light_level_(2048),
|
||||
show_grid_(true),
|
||||
show_colliders_(false),
|
||||
map_width_(0),
|
||||
map_height_(0),
|
||||
tile_size_(32),
|
||||
mouse_was_down_(false),
|
||||
last_tile_x_(-1),
|
||||
last_tile_y_(-1),
|
||||
pending_load_level_(-1)
|
||||
{
|
||||
camera_.configure(screen_width_, screen_height_);
|
||||
std::memset(export_name_, 0, sizeof(export_name_));
|
||||
export_open_ = false;
|
||||
}
|
||||
|
||||
void LevelEditor::set_active(bool active)
|
||||
{
|
||||
active_ = active;
|
||||
if (active)
|
||||
{
|
||||
camera_.configure(screen_width_, screen_height_);
|
||||
}
|
||||
}
|
||||
|
||||
void LevelEditor::load_from_data(const LevelData& data)
|
||||
{
|
||||
level_ = Level();
|
||||
LevelLoader::load(level_, data);
|
||||
map_width_ = data.map_width;
|
||||
map_height_ = data.map_height;
|
||||
tile_size_ = data.tile_size;
|
||||
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;
|
||||
}
|
||||
|
||||
void LevelEditor::new_level(int32_t width, int32_t height, int32_t tile_size)
|
||||
{
|
||||
level_ = Level();
|
||||
map_width_ = width;
|
||||
map_height_ = height;
|
||||
tile_size_ = tile_size;
|
||||
|
||||
const int32_t total = width * height;
|
||||
std::vector<uint16_t> empty_tiles(static_cast<size_t>(total), 0);
|
||||
|
||||
RenderData::Tilemap tilemap;
|
||||
tilemap.tiles = empty_tiles.data();
|
||||
tilemap.width = width;
|
||||
tilemap.height = height;
|
||||
tilemap.tile_w = tile_size;
|
||||
tilemap.tile_h = tile_size;
|
||||
tilemap.atlas = &tile_atlas_image;
|
||||
tilemap.atlas_columns = tile_atlas_columns;
|
||||
level_.set_tilemap(tilemap);
|
||||
level_.init_tile_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));
|
||||
|
||||
camera_.set_level_bounds(LevelBounds(0, 0, width * tile_size, height * tile_size));
|
||||
camera_.snap_to(level_.get_spawn_point());
|
||||
selected_object_id_ = 0;
|
||||
}
|
||||
|
||||
void LevelEditor::update(uint32_t dt_ms, Platform::IKeyboardState* keyboard, Platform::IPointerInput* pointer)
|
||||
{
|
||||
if (!active_) return;
|
||||
update_camera(dt_ms, keyboard, pointer);
|
||||
handle_mouse(pointer);
|
||||
}
|
||||
|
||||
void LevelEditor::update_camera(uint32_t dt_ms, Platform::IKeyboardState* keyboard, Platform::IPointerInput* pointer)
|
||||
{
|
||||
(void)dt_ms;
|
||||
const int32_t speed = 8;
|
||||
|
||||
if (keyboard)
|
||||
{
|
||||
if (keyboard->is_key_down(Platform::KEY_A))
|
||||
camera_.set_position(camera_.get_x() - speed, camera_.get_y());
|
||||
if (keyboard->is_key_down(Platform::KEY_D))
|
||||
camera_.set_position(camera_.get_x() + speed, camera_.get_y());
|
||||
if (keyboard->is_key_down(Platform::KEY_W))
|
||||
camera_.set_position(camera_.get_x(), camera_.get_y() - speed);
|
||||
if (keyboard->is_key_down(Platform::KEY_S))
|
||||
camera_.set_position(camera_.get_x(), camera_.get_y() + speed);
|
||||
}
|
||||
|
||||
if (pointer && pointer->is_open())
|
||||
{
|
||||
// 中键拖拽可后续扩展
|
||||
(void)pointer;
|
||||
}
|
||||
}
|
||||
|
||||
void LevelEditor::handle_mouse(Platform::IPointerInput* pointer)
|
||||
{
|
||||
if (!pointer || !pointer->is_open()) return;
|
||||
|
||||
if (ImGui::GetIO().WantCaptureMouse) return;
|
||||
|
||||
const bool is_down = pointer->is_down();
|
||||
const bool was_pressed = pointer->was_pressed();
|
||||
const int32_t px = pointer->get_x();
|
||||
const int32_t py = pointer->get_y();
|
||||
|
||||
const int32_t world_x = px + camera_.get_x();
|
||||
const int32_t world_y = py + camera_.get_y();
|
||||
const int32_t tile_x = world_to_tile_x(world_x);
|
||||
const int32_t tile_y = world_to_tile_y(world_y);
|
||||
|
||||
if (current_tool_ == Tool::TileBrush || current_tool_ == Tool::Eraser)
|
||||
{
|
||||
if (is_down && is_valid_tile(tile_x, tile_y))
|
||||
{
|
||||
const uint16_t tile_id = (current_tool_ == Tool::Eraser) ? 0 : static_cast<uint16_t>(selected_tile_id_);
|
||||
paint_tile(tile_x, tile_y, tile_id);
|
||||
}
|
||||
}
|
||||
else if (current_tool_ == Tool::ObjectPlace)
|
||||
{
|
||||
if (was_pressed && is_valid_tile(tile_x, tile_y))
|
||||
{
|
||||
place_object(tile_x, tile_y);
|
||||
}
|
||||
}
|
||||
else if (current_tool_ == Tool::Select)
|
||||
{
|
||||
if (was_pressed)
|
||||
{
|
||||
select_at(world_x, world_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LevelEditor::paint_tile(int32_t tile_x, int32_t tile_y, uint16_t tile_id)
|
||||
{
|
||||
uint16_t* tiles = level_.get_mutable_tiles();
|
||||
if (!tiles) return;
|
||||
const int32_t idx = tile_y * map_width_ + tile_x;
|
||||
tiles[idx] = tile_id;
|
||||
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)
|
||||
{
|
||||
const int32_t ts = tile_size_;
|
||||
GameObject obj;
|
||||
obj.type = selected_object_type_;
|
||||
obj.position = Math::Vector2Int(tile_x * ts, tile_y * ts);
|
||||
obj.active = true;
|
||||
|
||||
switch (selected_object_type_)
|
||||
{
|
||||
case GameObjectType::Player:
|
||||
obj.collider = RenderData::BoundingBox2D(Math::Vector2Int(8, 0), Math::Vector2Int(24, 32));
|
||||
obj.solid = false;
|
||||
level_.set_spawn_point(obj.position);
|
||||
break;
|
||||
case GameObjectType::StaticPlatform:
|
||||
obj.collider = RenderData::BoundingBox2D(Math::Vector2Int(0, 0), Math::Vector2Int(ts, ts));
|
||||
obj.solid = true;
|
||||
obj.sprite = &spr_platform;
|
||||
break;
|
||||
case GameObjectType::LightPlatform:
|
||||
obj.collider = RenderData::BoundingBox2D(Math::Vector2Int(0, 0), Math::Vector2Int(ts, ts));
|
||||
obj.solid = false;
|
||||
obj.light_threshold = LightThreshold(3192, 4095);
|
||||
obj.sprite = &spr_light_platform;
|
||||
break;
|
||||
case GameObjectType::ShadowPlatform:
|
||||
obj.collider = RenderData::BoundingBox2D(Math::Vector2Int(0, 0), Math::Vector2Int(ts, ts));
|
||||
obj.solid = true;
|
||||
obj.light_threshold = LightThreshold(0, 1023);
|
||||
obj.sprite = &spr_shadow_platform;
|
||||
break;
|
||||
case GameObjectType::Door:
|
||||
obj.collider = RenderData::BoundingBox2D(Math::Vector2Int(0, 0), Math::Vector2Int(ts, ts));
|
||||
obj.solid = true;
|
||||
obj.light_threshold = LightThreshold(1023, 3192);
|
||||
obj.sprite = &spr_door;
|
||||
break;
|
||||
case GameObjectType::Hazard:
|
||||
obj.collider = RenderData::BoundingBox2D(Math::Vector2Int(0, ts / 2), Math::Vector2Int(ts, ts));
|
||||
obj.solid = false;
|
||||
obj.sprite = &spr_spike;
|
||||
break;
|
||||
case GameObjectType::Collectible:
|
||||
obj.collider = RenderData::BoundingBox2D(Math::Vector2Int(ts / 4, ts / 4), Math::Vector2Int(ts * 3 / 4, ts * 3 / 4));
|
||||
obj.solid = false;
|
||||
obj.sprite = &spr_coin;
|
||||
break;
|
||||
case GameObjectType::Trigger:
|
||||
obj.collider = RenderData::BoundingBox2D(Math::Vector2Int(0, 0), Math::Vector2Int(ts, ts));
|
||||
obj.solid = false;
|
||||
obj.trigger_action = TriggerAction::Checkpoint;
|
||||
obj.sprite = &spr_checkpoint;
|
||||
level_.add_checkpoint(Checkpoint(obj.position.x, obj.position.y));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
level_.add_object(obj);
|
||||
}
|
||||
|
||||
void LevelEditor::select_at(int32_t world_x, int32_t world_y)
|
||||
{
|
||||
selected_object_id_ = 0;
|
||||
const auto& objects = level_.get_all_objects();
|
||||
for (size_t i = 0; i < objects.size(); ++i)
|
||||
{
|
||||
const GameObject& obj = objects[i];
|
||||
if (!obj.active) continue;
|
||||
const RenderData::BoundingBox2D box = obj.get_world_collider();
|
||||
if (world_x >= box.min.x && world_x < box.max.x &&
|
||||
world_y >= box.min.y && world_y < box.max.y)
|
||||
{
|
||||
selected_object_id_ = obj.id;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LevelEditor::draw(Core::DrawContext& ctx, const RenderData::BitmapFont& font)
|
||||
{
|
||||
if (!active_) return;
|
||||
|
||||
ctx.clear_color(RenderData::Color(20, 20, 40, 255));
|
||||
|
||||
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_);
|
||||
|
||||
if (show_grid_) 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);
|
||||
}
|
||||
|
||||
void LevelEditor::draw_ui()
|
||||
{
|
||||
if (!active_) return;
|
||||
draw_ui_main_menu();
|
||||
draw_ui_toolbar();
|
||||
draw_ui_properties();
|
||||
draw_ui_status_bar();
|
||||
draw_ui_export_dialog();
|
||||
}
|
||||
|
||||
void LevelEditor::draw_grid(Core::DrawContext& ctx)
|
||||
{
|
||||
const int32_t ts = tile_size_;
|
||||
const int32_t cam_x = camera_.get_x();
|
||||
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 RenderData::Color grid_color(80, 80, 80, 128);
|
||||
|
||||
for (int32_t x = start_x; x < end_x; x += ts)
|
||||
{
|
||||
const int32_t sx = x - cam_x;
|
||||
ctx.draw_line(Math::Vector2Int(sx, 0), Math::Vector2Int(sx, screen_height_), grid_color);
|
||||
}
|
||||
for (int32_t y = start_y; y < end_y; y += ts)
|
||||
{
|
||||
const int32_t sy = y - cam_y;
|
||||
ctx.draw_line(Math::Vector2Int(0, sy), Math::Vector2Int(screen_width_, sy), grid_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();
|
||||
if (sx < -16 || sx > screen_width_ + 16 || sy < -16 || sy > screen_height_ + 16) 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);
|
||||
}
|
||||
|
||||
void LevelEditor::draw_selection_highlight(Core::DrawContext& ctx)
|
||||
{
|
||||
GameObject* obj = level_.get_object(selected_object_id_);
|
||||
if (!obj || !obj->active) return;
|
||||
|
||||
const RenderData::BoundingBox2D box = obj->get_world_collider();
|
||||
const int32_t sx = box.min.x - camera_.get_x();
|
||||
const int32_t sy = box.min.y - camera_.get_y();
|
||||
const int32_t w = box.max.x - box.min.x;
|
||||
const int32_t h = box.max.y - box.min.y;
|
||||
|
||||
if (sx + w < 0 || sx > screen_width_ || sy + h < 0 || sy > screen_height_) return;
|
||||
|
||||
const RenderData::Color color(255, 255, 0, 255);
|
||||
ctx.draw_line(Math::Vector2Int(sx, sy), Math::Vector2Int(sx + w, sy), color);
|
||||
ctx.draw_line(Math::Vector2Int(sx + w, sy), Math::Vector2Int(sx + w, sy + h), color);
|
||||
ctx.draw_line(Math::Vector2Int(sx + w, sy + h), Math::Vector2Int(sx, sy + h), color);
|
||||
ctx.draw_line(Math::Vector2Int(sx, sy + h), Math::Vector2Int(sx, sy), color);
|
||||
}
|
||||
|
||||
void LevelEditor::draw_ui_main_menu()
|
||||
{
|
||||
if (ImGui::BeginMainMenuBar())
|
||||
{
|
||||
if (ImGui::BeginMenu("File"))
|
||||
{
|
||||
if (ImGui::MenuItem("New Level", "Ctrl+N"))
|
||||
{
|
||||
new_level(25, 8, 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;
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::MenuItem("Export to Header..."))
|
||||
{
|
||||
export_open_ = true;
|
||||
}
|
||||
if (ImGui::MenuItem("Close Editor", "Esc"))
|
||||
{
|
||||
should_quit_ = true;
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("View"))
|
||||
{
|
||||
ImGui::MenuItem("Show Grid", nullptr, &show_grid_);
|
||||
ImGui::MenuItem("Show Colliders", nullptr, &show_colliders_);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMainMenuBar();
|
||||
}
|
||||
}
|
||||
|
||||
void LevelEditor::draw_ui_toolbar()
|
||||
{
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 20));
|
||||
ImGui::SetNextWindowSize(ImVec2(140, static_cast<float>(screen_height_ - 20)));
|
||||
ImGui::Begin("Tools", nullptr,
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
|
||||
|
||||
ImGui::Text("Tool");
|
||||
if (ImGui::RadioButton("Tile Brush", current_tool_ == Tool::TileBrush))
|
||||
current_tool_ = Tool::TileBrush;
|
||||
if (ImGui::RadioButton("Object Place", current_tool_ == Tool::ObjectPlace))
|
||||
current_tool_ = Tool::ObjectPlace;
|
||||
if (ImGui::RadioButton("Select", current_tool_ == Tool::Select))
|
||||
current_tool_ = Tool::Select;
|
||||
if (ImGui::RadioButton("Eraser", current_tool_ == Tool::Eraser))
|
||||
current_tool_ = Tool::Eraser;
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (current_tool_ == Tool::TileBrush)
|
||||
{
|
||||
ImGui::Text("Tile Type");
|
||||
const char* tile_names[] = { "Empty", "Ground Top", "Ground Fill", "Platform", "Spike" };
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
if (ImGui::RadioButton(tile_names[i], selected_tile_id_ == i))
|
||||
selected_tile_id_ = i;
|
||||
}
|
||||
}
|
||||
else if (current_tool_ == Tool::ObjectPlace)
|
||||
{
|
||||
ImGui::Text("Object Type");
|
||||
struct ObjEntry { GameObjectType type; const char* name; };
|
||||
static const ObjEntry entries[] = {
|
||||
{ GameObjectType::Player, "Player" },
|
||||
{ GameObjectType::StaticPlatform, "Platform" },
|
||||
{ GameObjectType::LightPlatform, "Light Platform" },
|
||||
{ GameObjectType::ShadowPlatform, "Shadow Platform" },
|
||||
{ GameObjectType::Door, "Door" },
|
||||
{ GameObjectType::Hazard, "Hazard" },
|
||||
{ GameObjectType::Collectible, "Collectible" },
|
||||
{ GameObjectType::Trigger, "Trigger" },
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(entries) / sizeof(entries[0]); ++i)
|
||||
{
|
||||
if (ImGui::RadioButton(entries[i].name, selected_object_type_ == entries[i].type))
|
||||
selected_object_type_ = entries[i].type;
|
||||
}
|
||||
}
|
||||
else if (current_tool_ == Tool::Select)
|
||||
{
|
||||
ImGui::Text("Click to select");
|
||||
if (selected_object_id_ != 0)
|
||||
{
|
||||
if (ImGui::Button("Deselect"))
|
||||
selected_object_id_ = 0;
|
||||
}
|
||||
}
|
||||
else if (current_tool_ == Tool::Eraser)
|
||||
{
|
||||
ImGui::Text("Click to erase tiles");
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Preview Light");
|
||||
{
|
||||
int light_val = static_cast<int>(preview_light_level_);
|
||||
if (ImGui::SliderInt("##light", &light_val, 0, 4095))
|
||||
preview_light_level_ = static_cast<uint16_t>(light_val);
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void LevelEditor::draw_ui_properties()
|
||||
{
|
||||
if (selected_object_id_ == 0) return;
|
||||
|
||||
GameObject* obj = level_.get_object(selected_object_id_);
|
||||
if (!obj) return;
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(static_cast<float>(screen_width_ - 220), 20));
|
||||
ImGui::SetNextWindowSize(ImVec2(220, 300));
|
||||
ImGui::Begin("Properties", nullptr,
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
|
||||
|
||||
ImGui::Text("Type: %s", ObjTypeToString(obj->type));
|
||||
ImGui::Text("ID: %u", obj->id);
|
||||
ImGui::InputInt("Pos X", &obj->position.x);
|
||||
ImGui::InputInt("Pos Y", &obj->position.y);
|
||||
|
||||
if (obj->type == GameObjectType::LightPlatform ||
|
||||
obj->type == GameObjectType::ShadowPlatform ||
|
||||
obj->type == GameObjectType::Door)
|
||||
{
|
||||
int min_l = obj->light_threshold.min_level;
|
||||
int max_l = obj->light_threshold.max_level;
|
||||
ImGui::SliderInt("Light Min", &min_l, 0, 4095);
|
||||
ImGui::SliderInt("Light Max", &max_l, 0, 4095);
|
||||
obj->light_threshold.min_level = static_cast<uint16_t>(min_l);
|
||||
obj->light_threshold.max_level = static_cast<uint16_t>(max_l);
|
||||
}
|
||||
|
||||
if (obj->type == GameObjectType::Trigger)
|
||||
{
|
||||
const char* actions[] = { "None", "LevelComplete", "Checkpoint", "Death" };
|
||||
int action_idx = static_cast<int>(obj->trigger_action);
|
||||
if (ImGui::Combo("Action", &action_idx, actions, 4))
|
||||
{
|
||||
obj->trigger_action = static_cast<TriggerAction>(action_idx);
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::Button("Delete", ImVec2(-1, 0)))
|
||||
{
|
||||
level_.remove_object(selected_object_id_);
|
||||
selected_object_id_ = 0;
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void LevelEditor::draw_ui_status_bar()
|
||||
{
|
||||
ImGui::SetNextWindowPos(ImVec2(140, static_cast<float>(screen_height_ - 24)));
|
||||
ImGui::SetNextWindowSize(ImVec2(static_cast<float>(screen_width_ - 140), 24));
|
||||
ImGui::Begin("Status", nullptr,
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoScrollbar);
|
||||
|
||||
ImGui::Text("Map: %dx%d | Tile: %d | Objects: %zu | Light: %d",
|
||||
map_width_, map_height_, tile_size_,
|
||||
level_.object_count(), preview_light_level_);
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void LevelEditor::draw_ui_export_dialog()
|
||||
{
|
||||
if (!export_open_) return;
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(static_cast<float>(screen_width_) * 0.5f - 200, static_cast<float>(screen_height_) * 0.5f - 100), ImGuiCond_Appearing);
|
||||
ImGui::SetNextWindowSize(ImVec2(400, 200));
|
||||
if (ImGui::Begin("Export Level", &export_open_))
|
||||
{
|
||||
ImGui::InputText("Level Name", export_name_, sizeof(export_name_));
|
||||
if (ImGui::Button("Export", ImVec2(120, 0)))
|
||||
{
|
||||
if (std::strlen(export_name_) > 0)
|
||||
{
|
||||
const std::string header = export_to_header(export_name_);
|
||||
const std::string filename = std::string(export_name_) + "Data.h";
|
||||
std::cout << "[Editor] Export: name='" << export_name_ << "'" << std::endl;
|
||||
std::cout << "[Editor] Export: filename='" << filename << "'" << std::endl;
|
||||
std::cout << "[Editor] Export: header_size=" << header.size() << std::endl;
|
||||
FILE* f = nullptr;
|
||||
errno_t err = fopen_s(&f, filename.c_str(), "w");
|
||||
if (err == 0 && f)
|
||||
{
|
||||
size_t written = std::fwrite(header.c_str(), 1, header.size(), f);
|
||||
std::fclose(f);
|
||||
std::cout << "[Editor] Export: written=" << written << " bytes, OK" << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "[Editor] Export: fopen_s failed, err=" << err << std::endl;
|
||||
}
|
||||
export_open_ = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "[Editor] Export: name empty, skipped" << std::endl;
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Cancel", ImVec2(120, 0)))
|
||||
{
|
||||
export_open_ = false;
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
int32_t LevelEditor::world_to_tile_x(int32_t world_x) const
|
||||
{
|
||||
return world_x / tile_size_;
|
||||
}
|
||||
|
||||
int32_t LevelEditor::world_to_tile_y(int32_t world_y) const
|
||||
{
|
||||
return world_y / tile_size_;
|
||||
}
|
||||
|
||||
bool LevelEditor::is_valid_tile(int32_t tx, int32_t ty) const
|
||||
{
|
||||
return tx >= 0 && tx < map_width_ && ty >= 0 && ty < map_height_;
|
||||
}
|
||||
|
||||
RenderData::Sprite LevelEditor::MakeObjSprite(int32_t tile_id)
|
||||
{
|
||||
const int32_t col = tile_id % tile_atlas_columns;
|
||||
const int32_t row = tile_id / tile_atlas_columns;
|
||||
return RenderData::Sprite(&tile_atlas_image, col * 32, row * 32, 32, 32);
|
||||
}
|
||||
|
||||
std::string LevelEditor::export_to_header(const std::string& level_name) const
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "#pragma once\n\n";
|
||||
oss << "#include \"LevelData.h\"\n";
|
||||
oss << "#include \"tile_atlas.h\"\n\n";
|
||||
oss << "namespace LightGame\n";
|
||||
oss << "{\n";
|
||||
oss << " namespace " << level_name << "\n";
|
||||
oss << " {\n";
|
||||
oss << " static const int32_t kMapWidth = " << map_width_ << ";\n";
|
||||
oss << " static const int32_t kMapHeight = " << map_height_ << ";\n";
|
||||
oss << " static const int32_t kTileSize = " << tile_size_ << ";\n\n";
|
||||
|
||||
oss << " static const uint16_t tiles[] = {\n";
|
||||
const uint16_t* tiles = level_.get_original_tiles();
|
||||
if (tiles)
|
||||
{
|
||||
for (int32_t y = 0; y < map_height_; ++y)
|
||||
{
|
||||
oss << " ";
|
||||
for (int32_t x = 0; x < map_width_; ++x)
|
||||
{
|
||||
oss << 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)
|
||||
if (objects[i].active) ++spawn_count;
|
||||
|
||||
if (spawn_count > 0)
|
||||
{
|
||||
oss << " static const ObjectSpawn spawns[] = {\n";
|
||||
for (size_t i = 0; i < objects.size(); ++i)
|
||||
{
|
||||
const GameObject& obj = objects[i];
|
||||
if (!obj.active) continue;
|
||||
const int32_t tx = obj.position.x / tile_size_;
|
||||
const int32_t ty = obj.position.y / tile_size_;
|
||||
oss << " { GameObjectType::" << ObjTypeToString(obj.type)
|
||||
<< ", " << tx << ", " << ty << ", "
|
||||
<< obj.light_threshold.min_level << ", "
|
||||
<< obj.light_threshold.max_level << ", ";
|
||||
oss << "TriggerAction::" << TriggerActionToString(obj.trigger_action) << ", "
|
||||
<< obj.trigger_target_id << " },\n";
|
||||
}
|
||||
oss << " };\n\n";
|
||||
}
|
||||
|
||||
oss << " static const RenderData::Image kAtlas(tile_atlas_pixels, tile_atlas_width, tile_atlas_height);\n\n";
|
||||
|
||||
oss << " static const LevelData data = {\n";
|
||||
oss << " tiles,\n";
|
||||
oss << " kMapWidth,\n";
|
||||
oss << " kMapHeight,\n";
|
||||
oss << " kTileSize,\n";
|
||||
oss << " &kAtlas,\n";
|
||||
oss << " tile_atlas_columns,\n";
|
||||
if (spawn_count > 0)
|
||||
oss << " spawns,\n";
|
||||
else
|
||||
oss << " nullptr,\n";
|
||||
oss << " " << spawn_count << ",\n";
|
||||
oss << " nullptr,\n";
|
||||
oss << " 0,\n";
|
||||
const Math::Vector2Int sp = level_.get_spawn_point();
|
||||
oss << " Math::Vector2Int(" << sp.x << ", " << sp.y << "),\n";
|
||||
const LevelBounds b = level_.get_bounds();
|
||||
oss << " " << b.min_x << ",\n";
|
||||
oss << " " << b.min_y << ",\n";
|
||||
oss << " " << b.max_x << ",\n";
|
||||
oss << " " << b.max_y << "\n";
|
||||
oss << " };\n";
|
||||
oss << " }\n";
|
||||
oss << "}\n";
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include "Level.h"
|
||||
#include "Camera2D.h"
|
||||
#include "LevelRenderer.h"
|
||||
#include "LightEffectSystem.h"
|
||||
#include "LevelData.h"
|
||||
#include "IKeyboardState.h"
|
||||
#include "PointerInput.h"
|
||||
#include "Sprite.h"
|
||||
|
||||
namespace Core { class DrawContext; }
|
||||
namespace RenderData { struct BitmapFont; }
|
||||
|
||||
namespace LightGame
|
||||
{
|
||||
class LevelEditor
|
||||
{
|
||||
public:
|
||||
enum class Tool { TileBrush, ObjectPlace, Select, Eraser };
|
||||
|
||||
LevelEditor(int32_t screen_w, int32_t screen_h);
|
||||
|
||||
void load_from_data(const LevelData& data);
|
||||
void new_level(int32_t width, int32_t height, int32_t tile_size);
|
||||
|
||||
void update(uint32_t dt_ms, Platform::IKeyboardState* keyboard, Platform::IPointerInput* pointer);
|
||||
void draw(Core::DrawContext& ctx, const RenderData::BitmapFont& font);
|
||||
void draw_ui();
|
||||
|
||||
bool is_active() const { return active_; }
|
||||
void set_active(bool active);
|
||||
bool should_quit() const { return should_quit_; }
|
||||
|
||||
void set_pending_load(int32_t level_index) { pending_load_level_ = level_index; }
|
||||
int32_t get_pending_load() const { return pending_load_level_; }
|
||||
void clear_pending_load() { pending_load_level_ = -1; }
|
||||
|
||||
std::string export_to_header(const std::string& level_name) const;
|
||||
|
||||
private:
|
||||
void update_camera(uint32_t dt_ms, Platform::IKeyboardState* keyboard, Platform::IPointerInput* pointer);
|
||||
void handle_mouse(Platform::IPointerInput* pointer);
|
||||
void paint_tile(int32_t tile_x, int32_t tile_y, uint16_t tile_id);
|
||||
void place_object(int32_t tile_x, int32_t tile_y);
|
||||
void select_at(int32_t world_x, int32_t world_y);
|
||||
void erase_at(int32_t world_x, int32_t world_y);
|
||||
|
||||
void draw_grid(Core::DrawContext& ctx);
|
||||
void draw_spawn_point(Core::DrawContext& ctx);
|
||||
void draw_selection_highlight(Core::DrawContext& ctx);
|
||||
|
||||
void draw_ui_main_menu();
|
||||
void draw_ui_toolbar();
|
||||
void draw_ui_properties();
|
||||
void draw_ui_status_bar();
|
||||
void draw_ui_export_dialog();
|
||||
|
||||
int32_t world_to_tile_x(int32_t world_x) const;
|
||||
int32_t world_to_tile_y(int32_t world_y) const;
|
||||
bool is_valid_tile(int32_t tx, int32_t ty) const;
|
||||
|
||||
static RenderData::Sprite MakeObjSprite(int32_t tile_id);
|
||||
|
||||
int32_t screen_width_;
|
||||
int32_t screen_height_;
|
||||
|
||||
Level level_;
|
||||
Camera2D camera_;
|
||||
LevelRenderer renderer_;
|
||||
LightEffectSystem light_system_;
|
||||
|
||||
bool active_;
|
||||
bool should_quit_;
|
||||
|
||||
Tool current_tool_;
|
||||
int32_t selected_tile_id_;
|
||||
GameObjectType selected_object_type_;
|
||||
uint32_t selected_object_id_;
|
||||
|
||||
uint16_t preview_light_level_;
|
||||
bool show_grid_;
|
||||
bool show_colliders_;
|
||||
|
||||
int32_t map_width_;
|
||||
int32_t map_height_;
|
||||
int32_t tile_size_;
|
||||
|
||||
bool mouse_was_down_;
|
||||
int32_t last_tile_x_;
|
||||
int32_t last_tile_y_;
|
||||
int32_t pending_load_level_;
|
||||
|
||||
mutable char export_name_[64];
|
||||
mutable bool export_open_;
|
||||
};
|
||||
}
|
||||
@@ -27,6 +27,7 @@ namespace LightGame
|
||||
void follow(const Math::Vector2Int& target);
|
||||
void snap_to(const Math::Vector2Int& target);
|
||||
|
||||
void set_position(int32_t x, int32_t y) { position_.x = x; position_.y = y; }
|
||||
Math::Vector2Int get_position() const { return position_; }
|
||||
int32_t get_x() const { return position_.x; }
|
||||
int32_t get_y() const { return position_.y; }
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace LightGame
|
||||
if (tilemap.tiles != nullptr && tilemap.atlas != nullptr)
|
||||
{
|
||||
ctx.draw_tilemap(tilemap,
|
||||
-camera.get_x(), -camera.get_y(),
|
||||
0, 0,
|
||||
camera.get_screen_width(), camera.get_screen_height(),
|
||||
camera.get_x(), camera.get_y());
|
||||
}
|
||||
|
||||
@@ -50,7 +50,8 @@ namespace LightGame
|
||||
manual_light_level_(2048),
|
||||
light_step_(256),
|
||||
has_manual_override_(false),
|
||||
debug_mode_(false)
|
||||
debug_mode_(false),
|
||||
death_processed_(false)
|
||||
{
|
||||
camera_.configure(screen_width, screen_height);
|
||||
camera_.set_dead_zone(60, 40);
|
||||
@@ -234,12 +235,20 @@ namespace LightGame
|
||||
|
||||
if (player_controller_.is_dead())
|
||||
{
|
||||
state_manager_.get_hud().lives--;
|
||||
if (state_manager_.get_hud().lives <= 0)
|
||||
if (!death_processed_)
|
||||
{
|
||||
state_manager_.set_state(GameState::GameOver);
|
||||
state_manager_.get_hud().lives--;
|
||||
death_processed_ = true;
|
||||
if (state_manager_.get_hud().lives <= 0)
|
||||
{
|
||||
state_manager_.set_state(GameState::GameOver);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
death_processed_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
check_triggers();
|
||||
|
||||
@@ -57,6 +57,7 @@ namespace LightGame
|
||||
|
||||
bool has_manual_override_;
|
||||
bool debug_mode_;
|
||||
bool death_processed_;
|
||||
|
||||
public:
|
||||
LightGameApp(
|
||||
|
||||
@@ -1,57 +1,70 @@
|
||||
#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;
|
||||
|
||||
// Tile IDs: 0=empty 1=ground_top 2=ground_fill 3=platform 4=spike
|
||||
static const uint16_t tiles[] = {
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
1,1,1,1,0,0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,1,1,
|
||||
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
|
||||
};
|
||||
|
||||
static const ObjectSpawn spawns[] = {
|
||||
{ GameObjectType::Player, 2, 5, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Collectible, 5, 5, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Collectible, 10, 4, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Collectible, 18, 5, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Hazard, 12, 7, 0, 0, TriggerAction::Death, 0 },
|
||||
{ GameObjectType::Hazard, 13, 7, 0, 0, TriggerAction::Death, 0 },
|
||||
{ GameObjectType::Hazard, 14, 7, 0, 0, TriggerAction::Death, 0 },
|
||||
{ GameObjectType::Trigger, 23, 5, 0, 0, TriggerAction::LevelComplete, 0 },
|
||||
};
|
||||
|
||||
static const RenderData::Image kAtlas(tile_atlas_pixels, tile_atlas_width, tile_atlas_height);
|
||||
|
||||
static const LevelData data = {
|
||||
tiles,
|
||||
kMapWidth,
|
||||
kMapHeight,
|
||||
kTileSize,
|
||||
&kAtlas,
|
||||
tile_atlas_columns,
|
||||
spawns,
|
||||
8,
|
||||
nullptr,
|
||||
0,
|
||||
Math::Vector2Int(2 * kTileSize, 5 * kTileSize),
|
||||
0,
|
||||
0,
|
||||
kMapWidth * kTileSize,
|
||||
kMapHeight * kTileSize
|
||||
};
|
||||
}
|
||||
}
|
||||
#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,0,0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,1,1,
|
||||
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2
|
||||
};
|
||||
|
||||
static const ObjectSpawn spawns[] = {
|
||||
{ GameObjectType::Player, 2, 5, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Collectible, 5, 5, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Collectible, 10, 4, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Collectible, 18, 5, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Hazard, 12, 6, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Hazard, 13, 6, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Hazard, 14, 6, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Trigger, 23, 5, 0, 0, TriggerAction::LevelComplete, 0 },
|
||||
{ GameObjectType::Hazard, 4, 6, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Hazard, 6, 6, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Hazard, 5, 6, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Hazard, 7, 6, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Hazard, 10, 6, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Hazard, 11, 6, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Hazard, 15, 6, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Hazard, 18, 6, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Hazard, 19, 6, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Hazard, 20, 6, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Hazard, 20, 6, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Hazard, 21, 6, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Hazard, 21, 6, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Hazard, 22, 6, 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,
|
||||
22,
|
||||
nullptr,
|
||||
0,
|
||||
Math::Vector2Int(64, 160),
|
||||
0,
|
||||
0,
|
||||
800,
|
||||
256
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,16 +26,16 @@ namespace LightGame
|
||||
static const ObjectSpawn spawns[] = {
|
||||
{ GameObjectType::Player, 2, 5, 0, 0, TriggerAction::None, 0 },
|
||||
|
||||
{ GameObjectType::LightPlatform, 4, 4, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 6, 3, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 8, 2, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 10, 3, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 12, 4, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 14, 3, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 16, 2, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 18, 3, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 20, 4, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 22, 5, 1500, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::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 },
|
||||
|
||||
@@ -27,18 +27,18 @@ namespace LightGame
|
||||
static const ObjectSpawn spawns[] = {
|
||||
{ GameObjectType::Player, 2, 5, 0, 0, TriggerAction::None, 0 },
|
||||
|
||||
{ GameObjectType::LightPlatform, 4, 4, 2000, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::ShadowPlatform, 6, 3, 0, 1000, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 8, 4, 2000, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::ShadowPlatform, 10, 3, 0, 1000, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 12, 4, 2000, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::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, 1500, 2500, TriggerAction::None, 0 },
|
||||
{ GameObjectType::Door, 15, 4, 1023, 3192, TriggerAction::None, 0 },
|
||||
|
||||
{ GameObjectType::LightPlatform, 17, 4, 2000, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::ShadowPlatform, 19, 3, 0, 1000, TriggerAction::None, 0 },
|
||||
{ GameObjectType::LightPlatform, 21, 4, 2000, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::ShadowPlatform, 22, 5, 0, 1000, TriggerAction::None, 0 },
|
||||
{ GameObjectType::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 },
|
||||
|
||||
@@ -8,15 +8,23 @@
|
||||
#include "DefaultHardware.h"
|
||||
#include "Display.h"
|
||||
#include "DrawContext.h"
|
||||
#include "FrameBuffer.h"
|
||||
#include "TimeSource.h"
|
||||
#include "Timer.h"
|
||||
#include "LightGameApp.h"
|
||||
#include "LevelEditor.h"
|
||||
#include "font_atlas.h"
|
||||
#include "Level1Data.h"
|
||||
#include "Level2Data.h"
|
||||
#include "Level3Data.h"
|
||||
|
||||
#ifdef USE_FRAMEBUFFER
|
||||
#include "FBDisplay.h"
|
||||
#else
|
||||
#include "SDLDisplay.h"
|
||||
#include "imgui.h"
|
||||
#include "backends/imgui_impl_sdl2.h"
|
||||
#include "backends/imgui_impl_sdlrenderer2.h"
|
||||
#endif
|
||||
|
||||
namespace
|
||||
@@ -91,6 +99,17 @@ int main(int argc, char* argv[])
|
||||
&pointerInput,
|
||||
&photoSensor);
|
||||
|
||||
LightGame::LevelEditor editor(ScreenWidth, ScreenHeight);
|
||||
|
||||
#ifndef USE_FRAMEBUFFER
|
||||
Platform::SDLDisplay* sdl_display = static_cast<Platform::SDLDisplay*>(display);
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGui::StyleColorsDark();
|
||||
ImGui_ImplSDL2_InitForSDLRenderer(sdl_display->get_window(), sdl_display->get_renderer());
|
||||
ImGui_ImplSDLRenderer2_Init(sdl_display->get_renderer());
|
||||
#endif
|
||||
|
||||
bool debug_mode = false;
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
@@ -109,7 +128,28 @@ int main(int argc, char* argv[])
|
||||
timer.begin_frame(time_source.get_time_ms());
|
||||
|
||||
bool should_quit = false;
|
||||
|
||||
#ifdef USE_FRAMEBUFFER
|
||||
display->poll_events(should_quit);
|
||||
#else
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event))
|
||||
{
|
||||
ImGui_ImplSDL2_ProcessEvent(&event);
|
||||
if (event.type == SDL_QUIT)
|
||||
{
|
||||
should_quit = true;
|
||||
}
|
||||
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE)
|
||||
{
|
||||
should_quit = true;
|
||||
}
|
||||
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_F1)
|
||||
{
|
||||
editor.set_active(!editor.is_active());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (should_quit)
|
||||
{
|
||||
is_running = false;
|
||||
@@ -118,15 +158,57 @@ int main(int argc, char* argv[])
|
||||
buttonInput.update();
|
||||
pointerInput.update();
|
||||
|
||||
app.update(timer.fixed_delta_ms());
|
||||
app.draw(ctx, font);
|
||||
if (editor.is_active())
|
||||
{
|
||||
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);
|
||||
editor.clear_pending_load();
|
||||
}
|
||||
editor.update(timer.fixed_delta_ms(), &keyboardState, &pointerInput);
|
||||
editor.draw(ctx, font);
|
||||
}
|
||||
else
|
||||
{
|
||||
app.update(timer.fixed_delta_ms());
|
||||
app.draw(ctx, font);
|
||||
}
|
||||
|
||||
#ifdef USE_FRAMEBUFFER
|
||||
ctx.present(display);
|
||||
#else
|
||||
const Core::FrameBuffer* fb = ctx.get_frame_buffer();
|
||||
SDL_UpdateTexture(sdl_display->get_texture(), nullptr, fb->get_buffer(), ScreenWidth * 2);
|
||||
SDL_RenderClear(sdl_display->get_renderer());
|
||||
SDL_RenderCopy(sdl_display->get_renderer(), sdl_display->get_texture(), nullptr, nullptr);
|
||||
|
||||
ImGui_ImplSDLRenderer2_NewFrame();
|
||||
ImGui_ImplSDL2_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
if (editor.is_active())
|
||||
{
|
||||
editor.draw_ui();
|
||||
}
|
||||
ImGui::Render();
|
||||
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), sdl_display->get_renderer());
|
||||
SDL_RenderPresent(sdl_display->get_renderer());
|
||||
#endif
|
||||
SleepRemainingFrameTime(timer, time_source);
|
||||
}
|
||||
|
||||
photoSensor.shutdown();
|
||||
pointerInput.shutdown();
|
||||
buttonInput.shutdown();
|
||||
|
||||
#ifndef USE_FRAMEBUFFER
|
||||
ImGui_ImplSDLRenderer2_Shutdown();
|
||||
ImGui_ImplSDL2_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
#endif
|
||||
|
||||
display->shutdown();
|
||||
delete display;
|
||||
return 0;
|
||||
|
||||
@@ -82,5 +82,7 @@ namespace Core
|
||||
const RenderData::Color& color, const RenderData::Color& bg_color, const char* text);
|
||||
|
||||
void present(Platform::IDisplay* display);
|
||||
|
||||
const Core::FrameBuffer* get_frame_buffer() const { return frameBuffer; }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Platform
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_StopTextInput();
|
||||
SDL_StartTextInput();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,5 +18,9 @@ namespace Platform
|
||||
void present(const Core::FrameBuffer* framebuffer) override;
|
||||
void poll_events(bool& should_quit) override;
|
||||
void shutdown() override;
|
||||
|
||||
SDL_Window* get_window() const { return window; }
|
||||
SDL_Renderer* get_renderer() const { return renderer; }
|
||||
SDL_Texture* get_texture() const { return texture; }
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user