IMX6U-Game/game/tests/manual/SpriteAnimationTest.cpp

235 lines
5.9 KiB
C++

#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[] = {
"game/assets/sprites/",
"../game/assets/sprites/",
"../../game/assets/sprites/",
"../../../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("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;
}