#include #include #include #include #include #include #include #include #include #include #include "Color.h" #include "Display.h" #include "DrawContext.h" #include "Image.h" #include "SpriteAssetLoader.h" #include "TimeSource.h" #include "Timer.h" #ifdef USE_FRAMEBUFFER #include "FBDisplay.h" #else #include "SDLDisplay.h" #endif namespace { const int32_t ScreenWidth = 800; const int32_t ScreenHeight = 480; struct ProgramOptions { uint32_t target_fps; bool show_help; ProgramOptions() : target_fps(Core::Timer::DefaultFps), show_help(false) { } }; struct SpriteImage { std::vector pixels; RenderData::Image image; bool is_valid() const { return image.pixels != nullptr && image.width > 0 && image.height > 0; } }; static Platform::IDisplay* CreateDisplay() { #ifdef USE_FRAMEBUFFER return new Platform::FBDisplay(); #else return new Platform::SDLDisplay(); #endif } static void PrintUsage(const char* program_name) { std::cout << "Usage: " << program_name << " [--fps 30|45|60]\n" << " " << program_name << " [--fps=30|45|60]\n"; } static ProgramOptions ParseProgramOptions(int argc, char* argv[]) { ProgramOptions options; for (int i = 1; i < argc; ++i) { const char* arg = argv[i]; const char* fps_value = nullptr; if (std::strcmp(arg, "--help") == 0 || std::strcmp(arg, "-h") == 0) { options.show_help = true; return options; } else if (std::strcmp(arg, "--fps") == 0) { if (i + 1 < argc) { fps_value = argv[++i]; } else { std::cerr << "Missing value for --fps, falling back to 30. Supported: 30, 45, 60.\n"; } } else if (std::strncmp(arg, "--fps=", 6) == 0) { fps_value = arg + 6; } if (fps_value != nullptr) { const uint32_t parsed_fps = static_cast(std::strtoul(fps_value, nullptr, 10)); if (Core::Timer::is_supported_fps(parsed_fps)) { options.target_fps = parsed_fps; } else { std::cerr << "Unsupported FPS '" << fps_value << "', falling back to 30. Supported: 30, 45, 60.\n"; options.target_fps = Core::Timer::DefaultFps; } } } return options; } static void SleepRemainingFrameTime(const Core::Timer& timer, const Platform::ITimeSource& time_source) { const uint32_t sleep_ms = timer.remaining_frame_ms(time_source.get_time_ms()); if (sleep_ms > 0u) { std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms)); } } static std::string FindSpriteAssetPath(const std::string& file_name) { const char* roots[] = { "src/Apps/Game/assets/sprites/", "../src/Apps/Game/assets/sprites/", "../../src/Apps/Game/assets/sprites/", "../../../src/Apps/Game/assets/sprites/", "/usr/local/share/imx6u-game/sprites/" }; for (size_t i = 0; i < sizeof(roots) / sizeof(roots[0]); ++i) { const std::string path = std::string(roots[i]) + file_name; std::ifstream file(path.c_str(), std::ios::binary); if (file.good()) { return path; } } return std::string("src/Apps/Game/assets/sprites/") + file_name; } static void EnableAlphaColorKey(SpriteImage& sprite) { for (size_t i = 0; i < sprite.pixels.size(); ++i) { if ((sprite.pixels[i] & 0xFFu) == 0u) { sprite.pixels[i] = 0x00000000u; } } sprite.image = RenderData::Image( sprite.pixels.data(), sprite.image.width, sprite.image.height, 0x00000000u); } static bool LoadSpriteAsset(const std::string& file_name, SpriteImage& sprite, bool use_alpha_key) { const std::string path = FindSpriteAssetPath(file_name); if (!Asset::SpriteAssetLoader::Load(path, sprite.pixels, sprite.image)) { std::cerr << "Load sprite asset failed: " << path << std::endl; return false; } if (use_alpha_key) { EnableAlphaColorKey(sprite); } return sprite.is_valid(); } static const RenderData::Image& SelectTomFrame( uint32_t animation_time_ms, const SpriteImage& stand, const SpriteImage& listen, const SpriteImage* const* speaking_frames, size_t speaking_frame_count) { const uint32_t phase = (animation_time_ms / 1000u) % 6u; if (phase < 2u) { return stand.image; } if (phase < 3u) { return listen.image; } const size_t frame_index = (animation_time_ms / 120u) % speaking_frame_count; return speaking_frames[frame_index]->image; } } int main(int argc, char* argv[]) { const ProgramOptions options = ParseProgramOptions(argc, argv); if (options.show_help) { PrintUsage(argv[0]); return 0; } Platform::IDisplay* display = CreateDisplay(); if (!display->init(ScreenWidth, ScreenHeight)) { delete display; return -1; } SpriteImage background; SpriteImage tom_stand; SpriteImage tom_listen; SpriteImage tom_say1; SpriteImage tom_say2; SpriteImage tom_say3; SpriteImage tom_say4; SpriteImage record_button; if (!LoadSpriteAsset("background.sprite", background, false) || !LoadSpriteAsset("Tom-stand.sprite", tom_stand, true) || !LoadSpriteAsset("Tom-listhen.sprite", tom_listen, true) || !LoadSpriteAsset("Tom-say1.sprite", tom_say1, true) || !LoadSpriteAsset("Tom-say2.sprite", tom_say2, true) || !LoadSpriteAsset("Tom-say3.sprite", tom_say3, true) || !LoadSpriteAsset("Tom-say4.sprite", tom_say4, true) || !LoadSpriteAsset("ui-record.sprite", record_button, true)) { std::cerr << "Missing Tom sprite assets. Run the ConvertTomSprites target first.\n"; display->shutdown(); delete display; return -1; } const SpriteImage* speaking_frames[] = { &tom_say1, &tom_say2, &tom_say3, &tom_say4, &tom_say3, &tom_say2 }; const size_t speaking_frame_count = sizeof(speaking_frames) / sizeof(speaking_frames[0]); Core::DrawContext ctx(ScreenWidth, ScreenHeight); Core::Timer timer(options.target_fps); Platform::SteadyTimeSource time_source; std::cout << "Tom game started. Target FPS: " << timer.target_fps() << std::endl; bool is_running = true; uint32_t animation_time_ms = 0; while (is_running) { timer.begin_frame(time_source.get_time_ms()); animation_time_ms += timer.fixed_delta_ms(); bool should_quit = false; display->poll_events(should_quit); if (should_quit) { is_running = false; } ctx.clear(RenderData::Color(18, 18, 24, 255)); ctx.draw_sprite(0, 0, background.image); const RenderData::Image& tom = SelectTomFrame( animation_time_ms, tom_stand, tom_listen, speaking_frames, speaking_frame_count); const int32_t tom_x = (ScreenWidth - tom.width) / 2; const int32_t tom_y = std::max(0, ScreenHeight - tom.height - 72); ctx.draw_sprite(tom_x, tom_y, tom); const int32_t button_x = (ScreenWidth - record_button.image.width) / 2; const int32_t button_y = ScreenHeight - record_button.image.height - 16; ctx.draw_sprite(button_x, button_y, record_button.image); ctx.present(display); SleepRemainingFrameTime(timer, time_source); } display->shutdown(); delete display; return 0; }