IMX6U-Game/src/Apps/Game/Main.cpp

302 lines
6.9 KiB
C++

#include <algorithm>
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#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<uint32_t> 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<uint32_t>(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;
}