重构SpriteAssertTool.cpp,减少中间文件产生;重构TomGame文件夹,删去不必要的文件;修改CMakeLists,PC重构Game自动重新构建资源头文件;修改文档

This commit is contained in:
HP 2026-06-08 10:27:31 +08:00
parent 08088c8413
commit 1e34e15e04
26 changed files with 139256 additions and 82274 deletions

View File

@ -127,15 +127,6 @@ function(imx6u_configure_app_target target_name)
"${SDL2_DLL}" "${SDL2_DLL}"
"$<TARGET_FILE_DIR:${target_name}>" "$<TARGET_FILE_DIR:${target_name}>"
) )
elseif(SDL2_image_FOUND)
add_executable(SpriteAssetTool ${SPRITE_ASSET_TOOL_SOURCES})
target_include_directories(SpriteAssetTool PRIVATE
src/Core/Asset
src/Core/RenderData
)
target_link_libraries(SpriteAssetTool PRIVATE SDL2::SDL2 SDL2_image::SDL2_image)
else()
message(STATUS "SpriteAssetTool disabled: SDL2_image was not found")
endif() endif()
if(MSVC) if(MSVC)

View File

@ -67,7 +67,7 @@ cmake --build build-win --config Release --target IMX6U-Game
cmake --build build-win --config Release --target IMX6U-Demo cmake --build build-win --config Release --target IMX6U-Demo
``` ```
如果修改了 Tom 的原始 PNG 资源,先重新生成 atlas 头文件 构建 `IMX6U-Game` 时会自动重新生成 Tom 的 atlas 头文件;也可以单独执行
```bash ```bash
cmake --build build-win --config Release --target GenerateTomAtlasHeader cmake --build build-win --config Release --target GenerateTomAtlasHeader
``` ```
@ -106,7 +106,7 @@ cmake --build build-linux --target IMX6U-Game
cmake --build build-linux --target IMX6U-Demo cmake --build build-linux --target IMX6U-Demo
``` ```
如果修改了 Tom 的原始 PNG 资源,先重新生成 atlas 头文件 构建 `IMX6U-Game` 时会自动重新生成 Tom 的 atlas 头文件;也可以单独执行
```bash ```bash
cmake --build build-linux --target GenerateTomAtlasHeader cmake --build build-linux --target GenerateTomAtlasHeader
``` ```
@ -139,6 +139,8 @@ cmake --build build-arm-fb
说明单配置生成器Makefile/Ninja默认使用 `Release` 构建ARM / framebuffer 性能测试必须确认 `CMAKE_BUILD_TYPE=Release`,否则逐像素绘制和 `/dev/fb0` 提交会因未优化构建出现数量级偏差。 说明单配置生成器Makefile/Ninja默认使用 `Release` 构建ARM / framebuffer 性能测试必须确认 `CMAKE_BUILD_TYPE=Release`,否则逐像素绘制和 `/dev/fb0` 提交会因未优化构建出现数量级偏差。
注意Tom 的 atlas 头文件生成工具是主机侧离线工具,依赖 PC/Linux 主机上的 SDL2_image主机构建 `IMX6U-Game` 时会自动执行ARM 交叉编译过程不会执行它。如果修改了 `src/Apps/Game/assets/raw/` 里的 PNG必须先在 Windows 或 Linux x86 构建目录执行 `GenerateTomAtlasHeader` 或构建一次 `IMX6U-Game`,再进行 ARM 交叉编译。ARM 构建只消费已经生成好的 `src/Apps/Game/generated/tom_atlas.h`
构建SDL2 后端,要求工具链/sysroot 可找到目标板 SDL2 开发库): 构建SDL2 后端,要求工具链/sysroot 可找到目标板 SDL2 开发库):
```bash ```bash
cmake -B build-arm-sdl \ cmake -B build-arm-sdl \
@ -193,7 +195,7 @@ test_sprite_pixels
透明像素仍保留 alpha当前 demo 通过 `RenderData::Image(..., 0x00000000)` 把全透明像素作为 color key 跳过。 透明像素仍保留 alpha当前 demo 通过 `RenderData::Image(..., 0x00000000)` 把全透明像素作为 color key 跳过。
Tom 游戏资源使用 `SpriteAssetTool --atlas-header` 从原始 PNG 一步生成 atlas 头文件 Tom 游戏资源使用 `SpriteAssetTool` 从固定目录 `src/Apps/Game/assets/raw/` 读取原始 PNG一步生成 atlas 头文件。主机侧构建 `IMX6U-Game` 时 CMake 会自动执行 `GenerateTomAtlasHeader`;也可以单独执行
```bash ```bash
cmake --build build-win --config Release --target GenerateTomAtlasHeader cmake --build build-win --config Release --target GenerateTomAtlasHeader
@ -205,7 +207,9 @@ cmake --build build-win --config Release --target GenerateTomAtlasHeader
src/Apps/Game/generated/tom_atlas.h src/Apps/Game/generated/tom_atlas.h
``` ```
该头文件包含 `tom_atlas_pixels` 和每张图的 `RenderData::SpriteRegion`,因此板端运行 Tom 游戏时不需要额外部署 `.sprite` 文件。 该头文件包含 `tom_atlas_pixels` 和每张图的 `RenderData::SpriteRegion`,因此板端运行 Tom 游戏时不需要额外部署图片资源文件。尺寸规则、region 名称和 PNG 文件名统一记录在 `src/Apps/Game/tools/asset_pipeline/SpriteAssetTool.cpp` 顶部的 `Sources` 表里CMake 不再重复维护每张 PNG 的路径。
`src/Apps/Game/assets/sprites/` 属于旧的 `.sprite` 文件部署流程Tom 主游戏现在不再依赖它。只有旧的手动测试或未迁移工具还可能引用该目录;清理这些旧入口后可以删除这个目录。
### Bitmap Font 转换 ### Bitmap Font 转换

View File

@ -1,8 +1,10 @@
set(TOM_GAME_TARGET IMX6U-Game) set(TOM_GAME_TARGET IMX6U-Game)
set(TOM_ATLAS_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/generated/tom_atlas.h")
set_source_files_properties(${TOM_ATLAS_HEADER} PROPERTIES GENERATED TRUE)
add_executable(${TOM_GAME_TARGET} add_executable(${TOM_GAME_TARGET}
Main.cpp Main.cpp
generated/tom_atlas.h ${TOM_ATLAS_HEADER}
) )
target_include_directories(${TOM_GAME_TARGET} PRIVATE target_include_directories(${TOM_GAME_TARGET} PRIVATE
@ -11,91 +13,78 @@ target_include_directories(${TOM_GAME_TARGET} PRIVATE
imx6u_configure_app_target(${TOM_GAME_TARGET}) imx6u_configure_app_target(${TOM_GAME_TARGET})
if(CMAKE_CROSSCOMPILING AND NOT EXISTS "${TOM_ATLAS_HEADER}")
message(FATAL_ERROR
"Tom atlas header is missing. Run GenerateTomAtlasHeader in a host build before cross compiling."
)
endif()
if(NOT USE_FRAMEBUFFER) if(NOT USE_FRAMEBUFFER)
set(SPRITE_ASSET_TOOL_SOURCES set(TOM_ATLAS_TOOL_SOURCES
tools/asset_pipeline/SpriteAssetTool.cpp tools/asset_pipeline/SpriteAssetTool.cpp
${PROJECT_SOURCE_DIR}/src/Core/Asset/SpriteAssetLoader.cpp
) )
if(WIN32) if(WIN32)
add_executable(TomSpriteAssetTool EXCLUDE_FROM_ALL ${SPRITE_ASSET_TOOL_SOURCES}) add_executable(TomAtlasTool EXCLUDE_FROM_ALL ${TOM_ATLAS_TOOL_SOURCES})
set_target_properties(TomSpriteAssetTool PROPERTIES OUTPUT_NAME SpriteAssetTool) target_include_directories(TomAtlasTool PRIVATE
target_include_directories(TomSpriteAssetTool PRIVATE
${PROJECT_SOURCE_DIR}/src/Core/Asset
${PROJECT_SOURCE_DIR}/src/Core/RenderData
${PROJECT_SOURCE_DIR}/libs/Win/SDL2/include ${PROJECT_SOURCE_DIR}/libs/Win/SDL2/include
${PROJECT_SOURCE_DIR}/libs/Win/SDL_image/include ${PROJECT_SOURCE_DIR}/libs/Win/SDL_image/include
) )
target_link_directories(TomSpriteAssetTool PRIVATE target_link_directories(TomAtlasTool PRIVATE
${SDL2_LIB_DIR} ${SDL2_LIB_DIR}
${SDL2_IMAGE_LIB_DIR} ${SDL2_IMAGE_LIB_DIR}
) )
target_link_libraries(TomSpriteAssetTool PRIVATE SDL2main SDL2 SDL2_image) target_link_libraries(TomAtlasTool PRIVATE SDL2main SDL2 SDL2_image)
add_custom_command(TARGET TomSpriteAssetTool POST_BUILD add_custom_command(TARGET TomAtlasTool POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${SDL2_DLL}" "${SDL2_DLL}"
"$<TARGET_FILE_DIR:TomSpriteAssetTool>" "$<TARGET_FILE_DIR:TomAtlasTool>"
) )
add_custom_command(TARGET TomSpriteAssetTool POST_BUILD add_custom_command(TARGET TomAtlasTool POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${SDL2_IMAGE_DLL}" "${SDL2_IMAGE_DLL}"
"$<TARGET_FILE_DIR:TomSpriteAssetTool>" "$<TARGET_FILE_DIR:TomAtlasTool>"
) )
elseif(SDL2_image_FOUND) elseif(SDL2_image_FOUND)
add_executable(TomSpriteAssetTool EXCLUDE_FROM_ALL ${SPRITE_ASSET_TOOL_SOURCES}) add_executable(TomAtlasTool EXCLUDE_FROM_ALL ${TOM_ATLAS_TOOL_SOURCES})
set_target_properties(TomSpriteAssetTool PROPERTIES OUTPUT_NAME SpriteAssetTool) target_include_directories(TomAtlasTool PRIVATE
target_include_directories(TomSpriteAssetTool PRIVATE
${PROJECT_SOURCE_DIR}/src/Core/Asset
${PROJECT_SOURCE_DIR}/src/Core/RenderData
${SDL2_image_INCLUDE_DIRS} ${SDL2_image_INCLUDE_DIRS}
${SDL2_IMAGE_INCLUDE_DIRS} ${SDL2_IMAGE_INCLUDE_DIRS}
) )
target_link_libraries(TomSpriteAssetTool PRIVATE SDL2::SDL2) target_link_libraries(TomAtlasTool PRIVATE SDL2::SDL2)
if(TARGET SDL2_image::SDL2_image) if(TARGET SDL2_image::SDL2_image)
target_link_libraries(TomSpriteAssetTool PRIVATE SDL2_image::SDL2_image) target_link_libraries(TomAtlasTool PRIVATE SDL2_image::SDL2_image)
else() else()
target_link_libraries(TomSpriteAssetTool PRIVATE target_link_libraries(TomAtlasTool PRIVATE
${SDL2_image_LIBRARIES} ${SDL2_image_LIBRARIES}
${SDL2_IMAGE_LIBRARIES} ${SDL2_IMAGE_LIBRARIES}
) )
endif() endif()
else() else()
message(STATUS "SpriteAssetTool disabled: SDL2_image was not found") message(STATUS "TomAtlasTool disabled: SDL2_image was not found")
endif() endif()
if(TARGET TomSpriteAssetTool) if(TARGET TomAtlasTool)
add_custom_target(ConvertTomSprites if(NOT CMAKE_CROSSCOMPILING)
COMMAND $<TARGET_FILE:TomSpriteAssetTool>
--batch
"src/Apps/Game/assets/raw"
"src/Apps/Game/assets/sprites"
--preset tom-800x480
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
DEPENDS TomSpriteAssetTool
COMMENT "Converting Tom PNG assets to board-ready .sprite files"
VERBATIM
)
add_custom_target(GenerateTomAtlasHeader add_custom_target(GenerateTomAtlasHeader
COMMAND $<TARGET_FILE:TomSpriteAssetTool> COMMAND ${CMAKE_COMMAND} -E make_directory
--atlas-header "src/Apps/Game/generated"
"src/Apps/Game/assets/raw" COMMAND $<TARGET_FILE:TomAtlasTool>
"src/Apps/Game/generated/tom_atlas.h"
tom_atlas
--atlas-width
1024
--preset
tom-800x480
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
DEPENDS TomSpriteAssetTool DEPENDS TomAtlasTool
COMMENT "Generating Tom atlas header from PNG assets" COMMENT "Generating Tom atlas header from PNG assets"
VERBATIM VERBATIM
) )
if(NOT CMAKE_CROSSCOMPILING)
add_dependencies(${TOM_GAME_TARGET} GenerateTomAtlasHeader) add_dependencies(${TOM_GAME_TARGET} GenerateTomAtlasHeader)
else()
add_custom_target(GenerateTomAtlasHeader
COMMAND ${CMAKE_COMMAND} -E echo
"GenerateTomAtlasHeader is host-only. Run it in build-win or build-linux before cross compiling."
VERBATIM
)
endif() endif()
endif() endif()
endif() endif()

0
src/Apps/Game/README.md Normal file
View File

View File

@ -1 +0,0 @@

View File

@ -1 +0,0 @@

View File

@ -1 +0,0 @@

File diff suppressed because it is too large Load Diff

View File

@ -1 +0,0 @@

View File

@ -1,234 +0,0 @@
#include <SDL.h>
#include <algorithm>
#include <cstdint>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include "FrameBuffer.h"
#include "SDLDisplay.h"
#include "SpriteAssetLoader.h"
#include "SpriteRasterizer.h"
#include "Image.h"
#include "Sprite.h"
#include "SpriteAnimator.h"
#include "AnimationSystem.h"
static std::string FindAssetPath(const std::string& fileName)
{
const char* roots[] = {
"src/Apps/Game/assets/sprites/",
"../src/Apps/Game/assets/sprites/",
"../../src/Apps/Game/assets/sprites/",
"../../../src/Apps/Game/assets/sprites/"
};
for (size_t i = 0; i < sizeof(roots) / sizeof(roots[0]); ++i)
{
const std::string path = std::string(roots[i]) + fileName;
std::ifstream file(path.c_str(), std::ios::binary);
if (file.good())
{
return path;
}
}
return std::string("src/Apps/Game/assets/sprites/") + fileName;
}
static bool LoadSpriteImage(const std::string& fileName, RenderData::Image& image)
{
const std::string path = FindAssetPath(fileName);
if (!Asset::SpriteAssetLoader::Load(path, image))
{
std::cerr << "Load sprite failed: " << path << std::endl;
return false;
}
return true;
}
static RenderData::Image ResizeImageNearest(const RenderData::Image& source, int32_t width, int32_t height)
{
if (!source.is_valid() || width <= 0 || height <= 0)
{
return RenderData::Image();
}
RenderData::Image result(width, height);
for (int32_t y = 0; y < height; ++y)
{
const int32_t sourceY = y * source.get_height() / height;
for (int32_t x = 0; x < width; ++x)
{
const int32_t sourceX = x * source.get_width() / width;
result.set_pixel(x, y, source.get_pixel_fast(sourceX, sourceY));
}
}
return result;
}
static RenderData::Image ResizeImageToFit(const RenderData::Image& source, int32_t maxWidth, int32_t maxHeight)
{
if (!source.is_valid() || maxWidth <= 0 || maxHeight <= 0)
{
return RenderData::Image();
}
const float scaleX = static_cast<float>(maxWidth) / source.get_width();
const float scaleY = static_cast<float>(maxHeight) / source.get_height();
const float scale = std::min(scaleX, scaleY);
const int32_t width = std::max(1, static_cast<int32_t>(source.get_width() * scale));
const int32_t height = std::max(1, static_cast<int32_t>(source.get_height() * scale));
return ResizeImageNearest(source, width, height);
}
static bool LoadFrames(
const std::vector<std::string>& fileNames,
std::vector<RenderData::Image>& images,
std::vector<RenderData::Sprite>& frames,
int32_t maxFrameWidth,
int32_t maxFrameHeight)
{
images.clear();
frames.clear();
images.reserve(fileNames.size());
for (size_t i = 0; i < fileNames.size(); ++i)
{
RenderData::Image image;
if (!LoadSpriteImage(fileNames[i], image))
{
return false;
}
images.push_back(ResizeImageToFit(image, maxFrameWidth, maxFrameHeight));
}
frames.reserve(images.size());
for (size_t i = 0; i < images.size(); ++i)
{
frames.push_back(RenderData::Sprite(&images[i]));
}
return !frames.empty();
}
int main(int argc, char* argv[])
{
(void)argc;
(void)argv;
const int32_t width = 800;
const int32_t height = 480;
Platform::SDLDisplay display;
if (!display.init(width, height))
{
return -1;
}
RenderData::Image originalBackground;
if (!LoadSpriteImage("background.sprite", originalBackground))
{
display.shutdown();
return -1;
}
RenderData::Image background = ResizeImageNearest(originalBackground, width, height);
std::vector<std::string> frameFiles;
frameFiles.push_back("Tom-stand.sprite");
frameFiles.push_back("Tom-listhen.sprite");
frameFiles.push_back("Tom-openmouse1.sprite");
frameFiles.push_back("Tom-openmouse2.sprite");
frameFiles.push_back("Tom-say1.sprite");
frameFiles.push_back("Tom-say2.sprite");
frameFiles.push_back("Tom-say3.sprite");
frameFiles.push_back("Tom-say4.sprite");
std::vector<RenderData::Image> frameImages;
std::vector<RenderData::Sprite> frames;
if (!LoadFrames(frameFiles, frameImages, frames, width * 7 / 10, height * 9 / 10))
{
display.shutdown();
return -1;
}
Core::FrameBuffer frameBuffer(width, height);
Rasterizer::SpriteRasterizer spriteRasterizer(&frameBuffer);
Game::SpriteAnimator tomAnimator(frames, 0.12f, true);
Game::AnimationSystem animationSystem;
const RenderData::Sprite* firstFrame = tomAnimator.get_current_sprite();
const int32_t tomX = firstFrame ? (width - firstFrame->width) / 2 : 0;
const int32_t tomY = firstFrame ? height - firstFrame->height - 20 : 0;
animationSystem.add(&tomAnimator, tomX, tomY);
std::cout << "Sprite animation test" << std::endl;
std::cout << "Space: play/pause, R: reset, Esc: quit" << std::endl;
std::cout << "Background: " << originalBackground.get_width() << "x" << originalBackground.get_height()
<< " -> " << background.get_width() << "x" << background.get_height() << std::endl;
std::cout << "Tom frame 0: " << frameImages[0].get_width() << "x" << frameImages[0].get_height() << std::endl;
bool shouldQuit = false;
uint32_t lastTime = display.get_time_ms();
while (!shouldQuit)
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
if (event.type == SDL_QUIT)
{
shouldQuit = true;
}
else if (event.type == SDL_KEYDOWN)
{
if (event.key.repeat != 0)
{
continue;
}
if (event.key.keysym.sym == SDLK_ESCAPE)
{
shouldQuit = true;
}
else if (event.key.keysym.sym == SDLK_SPACE)
{
if (tomAnimator.is_playing())
{
tomAnimator.stop();
}
else
{
tomAnimator.play();
}
}
else if (event.key.keysym.sym == SDLK_r)
{
tomAnimator.reset();
tomAnimator.play();
}
}
}
const uint32_t currentTime = display.get_time_ms();
const float deltaTime = static_cast<float>(currentTime - lastTime) * 0.001f;
lastTime = currentTime;
animationSystem.update(deltaTime);
frameBuffer.clear(RenderData::Color(18, 18, 24, 255));
spriteRasterizer.DrawImage(background, 0, 0);
animationSystem.draw(spriteRasterizer);
display.present(&frameBuffer);
SDL_Delay(16);
}
display.shutdown();
return 0;
}

View File

@ -1 +0,0 @@

File diff suppressed because it is too large Load Diff