Compare commits

..

No commits in common. "24fe01af4602e6d63e447e2d341b71aaf26057e2" and "c3f12d9013a1d7cb8108a09cc1d77fe6db2da0da" have entirely different histories.

26 changed files with 91843 additions and 148825 deletions

View File

@ -127,6 +127,15 @@ 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
``` ```
构建 `IMX6U-Game` 时会自动重新生成 Tom 的 atlas 头文件;也可以单独执行 如果修改了 Tom 的原始 PNG 资源,先重新生成 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
``` ```
构建 `IMX6U-Game` 时会自动重新生成 Tom 的 atlas 头文件;也可以单独执行 如果修改了 Tom 的原始 PNG 资源,先重新生成 atlas 头文件
```bash ```bash
cmake --build build-linux --target GenerateTomAtlasHeader cmake --build build-linux --target GenerateTomAtlasHeader
``` ```
@ -139,8 +139,6 @@ 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 \
@ -195,7 +193,7 @@ test_sprite_pixels
透明像素仍保留 alpha当前 demo 通过 `RenderData::Image(..., 0x00000000)` 把全透明像素作为 color key 跳过。 透明像素仍保留 alpha当前 demo 通过 `RenderData::Image(..., 0x00000000)` 把全透明像素作为 color key 跳过。
Tom 游戏资源使用 `SpriteAssetTool` 从固定目录 `src/Apps/Game/assets/raw/` 读取原始 PNG一步生成 atlas 头文件。主机侧构建 `IMX6U-Game` 时 CMake 会自动执行 `GenerateTomAtlasHeader`;也可以单独执行 Tom 游戏资源使用 `SpriteAssetTool --atlas-header` 从原始 PNG 一步生成 atlas 头文件
```bash ```bash
cmake --build build-win --config Release --target GenerateTomAtlasHeader cmake --build build-win --config Release --target GenerateTomAtlasHeader
@ -207,9 +205,7 @@ 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 游戏时不需要额外部署图片资源文件。尺寸规则、region 名称和 PNG 文件名统一记录在 `src/Apps/Game/tools/asset_pipeline/SpriteAssetTool.cpp` 顶部的 `Sources` 表里CMake 不再重复维护每张 PNG 的路径。 该头文件包含 `tom_atlas_pixels` 和每张图的 `RenderData::SpriteRegion`,因此板端运行 Tom 游戏时不需要额外部署 `.sprite` 文件。
`src/Apps/Game/assets/sprites/` 属于旧的 `.sprite` 文件部署流程Tom 主游戏现在不再依赖它。只有旧的手动测试或未迁移工具还可能引用该目录;清理这些旧入口后可以删除这个目录。
### Bitmap Font 转换 ### Bitmap Font 转换

View File

@ -1,10 +1,8 @@
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
${TOM_ATLAS_HEADER} generated/tom_atlas.h
) )
target_include_directories(${TOM_GAME_TARGET} PRIVATE target_include_directories(${TOM_GAME_TARGET} PRIVATE
@ -13,78 +11,91 @@ 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(TOM_ATLAS_TOOL_SOURCES set(SPRITE_ASSET_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(TomAtlasTool EXCLUDE_FROM_ALL ${TOM_ATLAS_TOOL_SOURCES}) add_executable(TomSpriteAssetTool EXCLUDE_FROM_ALL ${SPRITE_ASSET_TOOL_SOURCES})
target_include_directories(TomAtlasTool PRIVATE set_target_properties(TomSpriteAssetTool PROPERTIES OUTPUT_NAME SpriteAssetTool)
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(TomAtlasTool PRIVATE target_link_directories(TomSpriteAssetTool PRIVATE
${SDL2_LIB_DIR} ${SDL2_LIB_DIR}
${SDL2_IMAGE_LIB_DIR} ${SDL2_IMAGE_LIB_DIR}
) )
target_link_libraries(TomAtlasTool PRIVATE SDL2main SDL2 SDL2_image) target_link_libraries(TomSpriteAssetTool PRIVATE SDL2main SDL2 SDL2_image)
add_custom_command(TARGET TomAtlasTool POST_BUILD add_custom_command(TARGET TomSpriteAssetTool POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${SDL2_DLL}" "${SDL2_DLL}"
"$<TARGET_FILE_DIR:TomAtlasTool>" "$<TARGET_FILE_DIR:TomSpriteAssetTool>"
) )
add_custom_command(TARGET TomAtlasTool POST_BUILD add_custom_command(TARGET TomSpriteAssetTool 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:TomAtlasTool>" "$<TARGET_FILE_DIR:TomSpriteAssetTool>"
) )
elseif(SDL2_image_FOUND) elseif(SDL2_image_FOUND)
add_executable(TomAtlasTool EXCLUDE_FROM_ALL ${TOM_ATLAS_TOOL_SOURCES}) add_executable(TomSpriteAssetTool EXCLUDE_FROM_ALL ${SPRITE_ASSET_TOOL_SOURCES})
target_include_directories(TomAtlasTool PRIVATE set_target_properties(TomSpriteAssetTool PROPERTIES OUTPUT_NAME SpriteAssetTool)
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(TomAtlasTool PRIVATE SDL2::SDL2) target_link_libraries(TomSpriteAssetTool PRIVATE SDL2::SDL2)
if(TARGET SDL2_image::SDL2_image) if(TARGET SDL2_image::SDL2_image)
target_link_libraries(TomAtlasTool PRIVATE SDL2_image::SDL2_image) target_link_libraries(TomSpriteAssetTool PRIVATE SDL2_image::SDL2_image)
else() else()
target_link_libraries(TomAtlasTool PRIVATE target_link_libraries(TomSpriteAssetTool PRIVATE
${SDL2_image_LIBRARIES} ${SDL2_image_LIBRARIES}
${SDL2_IMAGE_LIBRARIES} ${SDL2_IMAGE_LIBRARIES}
) )
endif() endif()
else() else()
message(STATUS "TomAtlasTool disabled: SDL2_image was not found") message(STATUS "SpriteAssetTool disabled: SDL2_image was not found")
endif() endif()
if(TARGET TomAtlasTool) if(TARGET TomSpriteAssetTool)
if(NOT CMAKE_CROSSCOMPILING) add_custom_target(ConvertTomSprites
add_custom_target(GenerateTomAtlasHeader COMMAND $<TARGET_FILE:TomSpriteAssetTool>
COMMAND ${CMAKE_COMMAND} -E make_directory --batch
"src/Apps/Game/generated" "src/Apps/Game/assets/raw"
COMMAND $<TARGET_FILE:TomAtlasTool> "src/Apps/Game/assets/sprites"
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" --preset tom-800x480
DEPENDS TomAtlasTool WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
COMMENT "Generating Tom atlas header from PNG assets" DEPENDS TomSpriteAssetTool
VERBATIM COMMENT "Converting Tom PNG assets to board-ready .sprite files"
) VERBATIM
)
add_custom_target(GenerateTomAtlasHeader
COMMAND $<TARGET_FILE:TomSpriteAssetTool>
--atlas-header
"src/Apps/Game/assets/raw"
"src/Apps/Game/generated/tom_atlas.h"
tom_atlas
--atlas-width
1024
--preset
tom-800x480
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
DEPENDS TomSpriteAssetTool
COMMENT "Generating Tom atlas header from PNG assets"
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()

View File

@ -0,0 +1 @@

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@

File diff suppressed because it is too large Load Diff

1
src/Apps/Game/tests/fixtures/.gitkeep vendored Normal file
View File

@ -0,0 +1 @@

View File

@ -0,0 +1,234 @@
#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

@ -0,0 +1 @@

File diff suppressed because it is too large Load Diff