完成渲染器相机与精灵
This commit is contained in:
@@ -37,6 +37,8 @@ add_executable(${TARGET}
|
||||
src/engine/resource/texture_manager.cpp
|
||||
src/engine/resource/audio_manager.cpp
|
||||
src/engine/resource/font_manager.cpp
|
||||
src/engine/render/renderer.cpp
|
||||
src/engine/render/camera.cpp
|
||||
)
|
||||
|
||||
# 链接库
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "game_app.h"
|
||||
#include "time.h"
|
||||
#include "../resource/resource_manager.h"
|
||||
#include "../render/renderer.h"
|
||||
#include "../render/camera.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
@@ -40,6 +42,8 @@ bool GameApp::init() {
|
||||
if (!initSDL()) return false;
|
||||
if (!initTime()) return false;
|
||||
if (!initResourceManager()) return false;
|
||||
if (!initRenderer()) return false;
|
||||
if (!initCamera()) return false;
|
||||
|
||||
// 测试资源管理器
|
||||
testResourceManager();
|
||||
@@ -59,11 +63,19 @@ void GameApp::handleEvents() {
|
||||
}
|
||||
|
||||
void GameApp::update(float /* delta_time */) {
|
||||
// 游戏逻辑更新,暂时为空
|
||||
// 游戏逻辑更新
|
||||
testCamera();
|
||||
}
|
||||
|
||||
void GameApp::render() {
|
||||
// 渲染代码,暂时为空
|
||||
// 1. 清除屏幕
|
||||
renderer_->clearScreen();
|
||||
|
||||
// 2. 具体渲染代码
|
||||
testRenderer();
|
||||
|
||||
// 3. 更新屏幕显示
|
||||
renderer_->present();
|
||||
}
|
||||
|
||||
void GameApp::close() {
|
||||
@@ -101,6 +113,9 @@ bool GameApp::initSDL() {
|
||||
spdlog::error("无法创建渲染器! SDL错误: {}", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置逻辑分辨率
|
||||
SDL_SetRenderLogicalPresentation(sdl_renderer_, 640, 360, SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||
spdlog::trace("SDL 初始化成功。");
|
||||
return true;
|
||||
}
|
||||
@@ -127,6 +142,31 @@ bool GameApp::initResourceManager() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GameApp::initRenderer() {
|
||||
try {
|
||||
renderer_ = std::make_unique<engine::render::Renderer>(sdl_renderer_, resource_manager_.get());
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("初始化渲染器失败: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
spdlog::trace("渲染器初始化成功。");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GameApp::initCamera() {
|
||||
try {
|
||||
camera_ = std::make_unique<engine::render::Camera>(glm::vec2(640, 360));
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("初始化相机失败: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
spdlog::trace("相机初始化成功。");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// --- 测试用函数 ---
|
||||
|
||||
void GameApp::testResourceManager()
|
||||
{
|
||||
resource_manager_->getTexture("assets/textures/Actors/eagle-attack.png");
|
||||
@@ -138,4 +178,29 @@ void GameApp::testResourceManager()
|
||||
resource_manager_->unloadSound("assets/audio/button_click.wav");
|
||||
}
|
||||
|
||||
void GameApp::testRenderer()
|
||||
{
|
||||
engine::render::Sprite sprite_world("assets/textures/Actors/frog.png");
|
||||
engine::render::Sprite sprite_ui("assets/textures/UI/buttons/Start1.png");
|
||||
engine::render::Sprite sprite_parallax("assets/textures/Layers/back.png");
|
||||
|
||||
static float rotation = 0.0f;
|
||||
rotation += 0.1f;
|
||||
|
||||
// 注意渲染顺序
|
||||
renderer_->drawParallax(*camera_, sprite_parallax, glm::vec2(100, 100), glm::vec2(0.5f, 0.5f), glm::bvec2(true, false));
|
||||
renderer_->drawSprite(*camera_, sprite_world, glm::vec2(200, 200), glm::vec2(1.0f, 1.0f), rotation);
|
||||
renderer_->drawUISprite(sprite_ui, glm::vec2(100, 100));
|
||||
|
||||
}
|
||||
|
||||
void GameApp::testCamera()
|
||||
{
|
||||
auto key_state = SDL_GetKeyboardState(nullptr);
|
||||
if (key_state[SDL_SCANCODE_UP]) camera_->move(glm::vec2(0, -1));
|
||||
if (key_state[SDL_SCANCODE_DOWN]) camera_->move(glm::vec2(0, 1));
|
||||
if (key_state[SDL_SCANCODE_LEFT]) camera_->move(glm::vec2(-1, 0));
|
||||
if (key_state[SDL_SCANCODE_RIGHT]) camera_->move(glm::vec2(1, 0));
|
||||
}
|
||||
|
||||
} // namespace engine::core
|
||||
@@ -9,6 +9,11 @@ namespace engine::resource {
|
||||
class ResourceManager;
|
||||
}
|
||||
|
||||
namespace engine::render {
|
||||
class Renderer;
|
||||
class Camera;
|
||||
}
|
||||
|
||||
namespace engine::core { // 命名空间的最佳实践:与文件路径一致
|
||||
class Time;
|
||||
|
||||
@@ -24,6 +29,8 @@ private:
|
||||
// 引擎组件
|
||||
std::unique_ptr<engine::core::Time> time_;
|
||||
std::unique_ptr<engine::resource::ResourceManager> resource_manager_;
|
||||
std::unique_ptr<engine::render::Renderer> renderer_;
|
||||
std::unique_ptr<engine::render::Camera> camera_;
|
||||
|
||||
public:
|
||||
GameApp();
|
||||
@@ -48,12 +55,16 @@ private:
|
||||
void close();
|
||||
|
||||
// 各模块的初始化/创建函数,在init()中调用
|
||||
bool initSDL();
|
||||
bool initTime();
|
||||
bool initResourceManager();
|
||||
[[nodiscard]] bool initSDL();
|
||||
[[nodiscard]] bool initTime();
|
||||
[[nodiscard]] bool initResourceManager();
|
||||
[[nodiscard]] bool initRenderer();
|
||||
[[nodiscard]] bool initCamera();
|
||||
|
||||
// 测试用函数
|
||||
void testResourceManager();
|
||||
void testRenderer();
|
||||
void testCamera();
|
||||
};
|
||||
|
||||
} // namespace engine::core
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
#include "camera.h"
|
||||
#include "../utils/math.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace engine::render {
|
||||
|
||||
Camera::Camera(const glm::vec2& viewport_size, const glm::vec2& position, const std::optional<engine::utils::Rect> limit_bounds)
|
||||
: viewport_size_(viewport_size), position_(position), limit_bounds_(limit_bounds) {
|
||||
spdlog::trace("Camera 初始化成功,位置: {},{}", position_.x, position_.y);
|
||||
}
|
||||
|
||||
void Camera::setPosition(const glm::vec2& position) {
|
||||
position_ = position;
|
||||
clampPosition();
|
||||
}
|
||||
|
||||
void Camera::update(float /* delta_time */)
|
||||
{
|
||||
// TODO 自动跟随目标
|
||||
}
|
||||
|
||||
void Camera::move(const glm::vec2 &offset)
|
||||
{
|
||||
position_ += offset;
|
||||
clampPosition();
|
||||
}
|
||||
|
||||
void Camera::setLimitBounds(const engine::utils::Rect& bounds)
|
||||
{
|
||||
limit_bounds_ = bounds;
|
||||
clampPosition(); // 设置边界后,立即应用限制
|
||||
}
|
||||
|
||||
const glm::vec2& Camera::getPosition() const {
|
||||
return position_;
|
||||
}
|
||||
|
||||
void Camera::clampPosition()
|
||||
{
|
||||
// 边界检查需要确保相机视图(position 到 position + viewport_size)在 limit_bounds 内
|
||||
if (limit_bounds_.has_value() && limit_bounds_->size.x > 0 && limit_bounds_->size.y > 0) {
|
||||
// 计算允许的相机位置范围
|
||||
glm::vec2 min_cam_pos = limit_bounds_->position;
|
||||
glm::vec2 max_cam_pos = limit_bounds_->position + limit_bounds_->size - viewport_size_;
|
||||
|
||||
// 确保 max_cam_pos 不小于 min_cam_pos (视口可能比世界还大)
|
||||
max_cam_pos.x = std::max(min_cam_pos.x, max_cam_pos.x);
|
||||
max_cam_pos.y = std::max(min_cam_pos.y, max_cam_pos.y);
|
||||
|
||||
position_ = glm::clamp(position_, min_cam_pos, max_cam_pos);
|
||||
}
|
||||
// 如果 limit_bounds 无效则不进行限制
|
||||
}
|
||||
|
||||
glm::vec2 Camera::worldToScreen(const glm::vec2& world_pos) const {
|
||||
// 将世界坐标减去相机左上角位置
|
||||
return world_pos - position_;
|
||||
}
|
||||
|
||||
glm::vec2 Camera::worldToScreenWithParallax(const glm::vec2 &world_pos, const glm::vec2 &scroll_factor) const
|
||||
{
|
||||
// 相机位置应用滚动因子
|
||||
return world_pos - position_ * scroll_factor;
|
||||
}
|
||||
|
||||
glm::vec2 Camera::screenToWorld(const glm::vec2 &screen_pos) const
|
||||
{
|
||||
// 将屏幕坐标加上相机左上角位置
|
||||
return screen_pos + position_;
|
||||
}
|
||||
|
||||
glm::vec2 Camera::getViewportSize() const {
|
||||
return viewport_size_;
|
||||
}
|
||||
|
||||
std::optional<engine::utils::Rect> Camera::getLimitBounds() const {
|
||||
return limit_bounds_;
|
||||
}
|
||||
|
||||
} // namespace engine::render
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
#include "../utils/math.h"
|
||||
#include <optional>
|
||||
|
||||
namespace engine::render {
|
||||
|
||||
/**
|
||||
* @brief 相机类负责管理相机位置和视口大小,并提供坐标转换功能。
|
||||
* 它还包含限制相机移动范围的边界。
|
||||
*/
|
||||
class Camera final {
|
||||
private:
|
||||
glm::vec2 viewport_size_; ///< @brief 视口大小(屏幕大小)
|
||||
glm::vec2 position_; ///< @brief 相机左上角的世界坐标
|
||||
std::optional<engine::utils::Rect> limit_bounds_; ///< @brief 限制相机的移动范围,空值表示不限制
|
||||
|
||||
public:
|
||||
|
||||
Camera(const glm::vec2& viewport_size, const glm::vec2& position = glm::vec2(0.0f, 0.0f), const std::optional<engine::utils::Rect> limit_bounds = std::nullopt);
|
||||
|
||||
void update(float delta_time); ///< @brief 更新相机位置
|
||||
void move(const glm::vec2& offset); ///< @brief 移动相机
|
||||
|
||||
glm::vec2 worldToScreen(const glm::vec2& world_pos) const; ///< @brief 世界坐标转屏幕坐标
|
||||
glm::vec2 worldToScreenWithParallax(const glm::vec2& world_pos, const glm::vec2& scroll_factor) const; ///< @brief 世界坐标转屏幕坐标,考虑视差滚动
|
||||
glm::vec2 screenToWorld(const glm::vec2& screen_pos) const; ///< @brief 屏幕坐标转世界坐标
|
||||
|
||||
void setPosition(const glm::vec2& position); ///< @brief 设置相机位置
|
||||
void setLimitBounds(const engine::utils::Rect& bounds); ///< @brief 设置限制相机的移动范围
|
||||
|
||||
const glm::vec2& getPosition() const; ///< @brief 获取相机位置
|
||||
std::optional<engine::utils::Rect> getLimitBounds() const; ///< @brief 获取限制相机的移动范围
|
||||
glm::vec2 getViewportSize() const; ///< @brief 获取视口大小
|
||||
|
||||
// 禁用拷贝和移动语义
|
||||
Camera(const Camera&) = delete;
|
||||
Camera& operator=(const Camera&) = delete;
|
||||
Camera(Camera&&) = delete;
|
||||
Camera& operator=(Camera&&) = delete;
|
||||
|
||||
private:
|
||||
void clampPosition(); ///< @brief 限制相机位置在边界内
|
||||
};
|
||||
|
||||
} // namespace engine::render
|
||||
@@ -0,0 +1,199 @@
|
||||
#include "renderer.h"
|
||||
#include "../resource/resource_manager.h"
|
||||
#include "camera.h"
|
||||
#include "sprite.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <stdexcept> // For std::runtime_error
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace engine::render {
|
||||
|
||||
// 构造函数: 执行初始化,增加 ResourceManager
|
||||
Renderer::Renderer(SDL_Renderer* sdl_renderer, engine::resource::ResourceManager* resource_manager)
|
||||
: renderer_(sdl_renderer), resource_manager_(resource_manager)
|
||||
{
|
||||
spdlog::trace("构造 Renderer...");
|
||||
if (!renderer_) {
|
||||
throw std::runtime_error("Renderer 构造失败: 提供的 SDL_Renderer 指针为空。");
|
||||
}
|
||||
if (!resource_manager_) {
|
||||
// ResourceManager 是 drawSprite 所必需的
|
||||
throw std::runtime_error("Renderer 构造失败: 提供的 ResourceManager 指针为空。");
|
||||
}
|
||||
setDrawColor(0, 0, 0, 255);
|
||||
spdlog::trace("Renderer 构造成功。");
|
||||
}
|
||||
|
||||
void Renderer::drawSprite(const Camera& camera, const Sprite& sprite, const glm::vec2& position, const glm::vec2& scale, double angle) {
|
||||
auto texture = resource_manager_->getTexture(sprite.getTextureId());
|
||||
if (!texture) {
|
||||
spdlog::error("无法为 ID {} 获取纹理。", sprite.getTextureId());
|
||||
return;
|
||||
}
|
||||
|
||||
auto src_rect = getSpriteSrcRect(sprite);
|
||||
if (!src_rect.has_value()) {
|
||||
spdlog::error("无法获取精灵的源矩形,ID: {}", sprite.getTextureId());
|
||||
return;
|
||||
}
|
||||
|
||||
// 应用相机变换
|
||||
glm::vec2 position_screen = camera.worldToScreen(position);
|
||||
|
||||
// 计算目标矩形,注意 position 是精灵的左上角坐标
|
||||
float scaled_w = src_rect.value().w * scale.x;
|
||||
float scaled_h = src_rect.value().h * scale.y;
|
||||
SDL_FRect dest_rect = {
|
||||
position_screen.x,
|
||||
position_screen.y,
|
||||
scaled_w,
|
||||
scaled_h
|
||||
};
|
||||
|
||||
if (!isRectInViewport(camera, dest_rect)) { // 视口裁剪:如果精灵超出视口,则不绘制
|
||||
// spdlog::info("精灵超出视口范围,ID: {}", sprite.getTextureId());
|
||||
return;
|
||||
}
|
||||
|
||||
// 执行绘制(默认旋转中心为精灵的中心点)
|
||||
if (!SDL_RenderTextureRotated(renderer_, texture, &src_rect.value(), &dest_rect, angle, NULL, sprite.isFlipped() ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE)) {
|
||||
spdlog::error("渲染旋转纹理失败(ID: {}):{}", sprite.getTextureId(), SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::drawParallax(const Camera &camera, const Sprite &sprite, const glm::vec2 &position, const glm::vec2 &scroll_factor, const glm::bvec2 &repeat, const glm::vec2 &scale)
|
||||
{
|
||||
auto texture = resource_manager_->getTexture(sprite.getTextureId());
|
||||
if (!texture) {
|
||||
spdlog::error("无法为 ID {} 获取纹理。", sprite.getTextureId());
|
||||
return;
|
||||
}
|
||||
|
||||
auto src_rect = getSpriteSrcRect(sprite);
|
||||
if (!src_rect.has_value()) {
|
||||
spdlog::error("无法获取精灵的源矩形,ID: {}", sprite.getTextureId());
|
||||
return;
|
||||
}
|
||||
|
||||
// 应用相机变换
|
||||
glm::vec2 position_screen = camera.worldToScreenWithParallax(position, scroll_factor);
|
||||
|
||||
// 计算缩放后的纹理尺寸
|
||||
float scaled_tex_w = src_rect.value().w * scale.x;
|
||||
float scaled_tex_h = src_rect.value().h * scale.y;
|
||||
|
||||
glm::vec2 start, stop;
|
||||
glm::vec2 viewport_size = camera.getViewportSize();
|
||||
|
||||
if (repeat.x) {
|
||||
// 使用 glm::mod 进行浮点数取模
|
||||
start.x = glm::mod(position_screen.x, scaled_tex_w) - scaled_tex_w;
|
||||
stop.x = viewport_size.x;
|
||||
} else {
|
||||
start.x = position_screen.x;
|
||||
stop.x = glm::min(position_screen.x + scaled_tex_w, viewport_size.x); // 结束点是一个纹理宽度之后,但不超过视口宽度
|
||||
}
|
||||
if (repeat.y) {
|
||||
start.y = glm::mod(position_screen.y, scaled_tex_h) - scaled_tex_h;
|
||||
stop.y = viewport_size.y;
|
||||
} else {
|
||||
start.y = position_screen.y;
|
||||
stop.y = glm::min(position_screen.y + scaled_tex_h, viewport_size.y); // 结束点是一个纹理高度之后,但不超过视口高度
|
||||
}
|
||||
|
||||
for (float y = start.y; y < stop.y; y += scaled_tex_h) {
|
||||
for (float x = start.x; x < stop.x; x += scaled_tex_w) {
|
||||
SDL_FRect dest_rect = {x, y, scaled_tex_w, scaled_tex_h};
|
||||
if (!SDL_RenderTexture(renderer_, texture, nullptr, &dest_rect)) {
|
||||
spdlog::error("渲染视差纹理失败(ID: {}):{}", sprite.getTextureId(), SDL_GetError());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::drawUISprite(const Sprite& sprite, const glm::vec2& position, const std::optional<glm::vec2>& size) {
|
||||
auto texture = resource_manager_->getTexture(sprite.getTextureId());
|
||||
if (!texture) {
|
||||
spdlog::error("无法为 ID {} 获取纹理。", sprite.getTextureId());
|
||||
return;
|
||||
}
|
||||
|
||||
auto src_rect = getSpriteSrcRect(sprite);
|
||||
if (!src_rect.has_value()) {
|
||||
spdlog::error("无法获取精灵的源矩形,ID: {}", sprite.getTextureId());
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_FRect dest_rect = {position.x, position.y, 0, 0}; // 首先确定目标矩形的左上角坐标
|
||||
if (size.has_value()) { // 如果提供了尺寸,则使用提供的尺寸
|
||||
dest_rect.w = size.value().x;
|
||||
dest_rect.h = size.value().y;
|
||||
} else { // 如果未提供尺寸,则使用纹理的原始尺寸
|
||||
dest_rect.w = src_rect.value().w;
|
||||
dest_rect.h = src_rect.value().h;
|
||||
}
|
||||
|
||||
// 执行绘制(未考虑UI旋转)
|
||||
if (!SDL_RenderTextureRotated(renderer_, texture, &src_rect.value(), &dest_rect, 0.0, nullptr, sprite.isFlipped() ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE)) {
|
||||
spdlog::error("渲染 UI Sprite 失败 (ID: {}): {}", sprite.getTextureId(), SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::setDrawColor(Uint8 r, Uint8 g, Uint8 b, Uint8 a) {
|
||||
if (!SDL_SetRenderDrawColor(renderer_, r, g, b, a)) {
|
||||
spdlog::error("设置渲染绘制颜色失败:{}", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::setDrawColorFloat(float r, float g, float b, float a)
|
||||
{
|
||||
if (!SDL_SetRenderDrawColorFloat(renderer_, r, g, b, a)) {
|
||||
spdlog::error("设置渲染绘制颜色失败:{}", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::clearScreen() {
|
||||
if (!SDL_RenderClear(renderer_)) {
|
||||
spdlog::error("清除渲染器失败:{}", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::present()
|
||||
{
|
||||
SDL_RenderPresent(renderer_);
|
||||
}
|
||||
|
||||
std::optional<SDL_FRect> Renderer::getSpriteSrcRect(const Sprite &sprite)
|
||||
{
|
||||
SDL_Texture* texture = resource_manager_->getTexture(sprite.getTextureId());
|
||||
if (!texture) {
|
||||
spdlog::error("无法为 ID {} 获取纹理。", sprite.getTextureId());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto src_rect = sprite.getSourceRect();
|
||||
if (src_rect.has_value()) { // 如果Sprite中存在指定rect,则判断尺寸是否有效
|
||||
if (src_rect.value().w <= 0 || src_rect.value().h <= 0) {
|
||||
spdlog::error("源矩形尺寸无效,ID: {}", sprite.getTextureId());
|
||||
return std::nullopt;
|
||||
}
|
||||
return src_rect;
|
||||
} else { // 否则获取纹理尺寸并返回整个纹理大小
|
||||
SDL_FRect result = {0, 0, 0, 0};
|
||||
if (!SDL_GetTextureSize(texture, &result.w, &result.h)) {
|
||||
spdlog::error("无法获取纹理尺寸,ID: {}", sprite.getTextureId());
|
||||
return std::nullopt;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
bool Renderer::isRectInViewport(const Camera& camera, const SDL_FRect &rect)
|
||||
{
|
||||
glm::vec2 viewport_size = camera.getViewportSize();
|
||||
return rect.x + rect.w >= 0 && rect.x <= viewport_size.x && // 相当于 AABB碰撞检测
|
||||
rect.y + rect.h >= 0 && rect.y <= viewport_size.y;
|
||||
}
|
||||
|
||||
} // namespace engine::render
|
||||
@@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
#include "sprite.h"
|
||||
#include <string>
|
||||
#include <optional> // For std::optional
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
struct SDL_Renderer;
|
||||
struct SDL_FRect;
|
||||
|
||||
namespace engine::resource {
|
||||
class ResourceManager;
|
||||
}
|
||||
|
||||
namespace engine::render {
|
||||
class Camera;
|
||||
|
||||
/**
|
||||
* @brief 封装 SDL3 渲染操作
|
||||
*
|
||||
* 包装 SDL_Renderer 并提供清除屏幕、绘制精灵和呈现最终图像的方法。
|
||||
* 在构造时初始化。依赖于一个有效的 SDL_Renderer 和 ResourceManager。
|
||||
* 构造失败会抛出异常。
|
||||
*/
|
||||
class Renderer final{
|
||||
private:
|
||||
SDL_Renderer* renderer_ = nullptr; ///< @brief 指向 SDL_Renderer 的非拥有指针
|
||||
engine::resource::ResourceManager* resource_manager_ = nullptr; ///< @brief 指向 ResourceManager 的非拥有指针
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
*
|
||||
* @param sdl_renderer 指向有效的 SDL_Renderer 的指针。不能为空。
|
||||
* @param resource_manager 指向有效的 ResourceManager 的指针。不能为空。
|
||||
* @throws std::runtime_error 如果任一指针为 nullptr。
|
||||
*/
|
||||
Renderer(SDL_Renderer* sdl_renderer, engine::resource::ResourceManager* resource_manager);
|
||||
|
||||
/**
|
||||
* @brief 绘制一个精灵
|
||||
*
|
||||
* @param sprite 包含纹理ID、源矩形和翻转状态的 Sprite 对象。
|
||||
* @param position 世界坐标中的左上角位置。
|
||||
* @param scale 缩放因子。
|
||||
* @param angle 旋转角度(度)。
|
||||
*/
|
||||
void drawSprite(const Camera& camera, const Sprite& sprite, const glm::vec2& position,
|
||||
const glm::vec2& scale = {1.0f, 1.0f}, double angle = 0.0f);
|
||||
|
||||
/**
|
||||
* @brief 绘制视差滚动背景
|
||||
*
|
||||
* @param sprite 包含纹理ID、源矩形和翻转状态的 Sprite 对象。
|
||||
* @param position 世界坐标。
|
||||
* @param scroll_factor 滚动因子。
|
||||
* @param scale 缩放因子。
|
||||
*/
|
||||
void drawParallax(const Camera& camera, const Sprite& sprite, const glm::vec2& position,
|
||||
const glm::vec2& scroll_factor, const glm::bvec2& repeat = {true, true}, const glm::vec2& scale = {1.0f, 1.0f});
|
||||
|
||||
/**
|
||||
* @brief 在屏幕坐标中直接渲染一个用于UI的Sprite对象。
|
||||
*
|
||||
* @param sprite 包含纹理ID、源矩形和翻转状态的Sprite对象。
|
||||
* @param position 屏幕坐标中的左上角位置。
|
||||
* @param size 可选:目标矩形的大小。如果为 std::nullopt,则使用Sprite的原始大小。
|
||||
*/
|
||||
void drawUISprite(const Sprite& sprite, const glm::vec2& position, const std::optional<glm::vec2>& size = std::nullopt);
|
||||
|
||||
|
||||
void present(); ///< @brief 更新屏幕,包装 SDL_RenderPresent 函数
|
||||
void clearScreen(); ///< @brief 清屏,包装 SDL_RenderClear 函数
|
||||
|
||||
void setDrawColor(Uint8 r, Uint8 g, Uint8 b, Uint8 a = 255); ///< @brief 设置绘制颜色,包装 SDL_SetRenderDrawColor 函数,使用 Uint8 类型
|
||||
void setDrawColorFloat(float r, float g, float b, float a = 1.0f); ///< @brief 设置绘制颜色,包装 SDL_SetRenderDrawColorFloat 函数,使用 float 类型
|
||||
|
||||
SDL_Renderer* getSDLRenderer() const { return renderer_; } ///< @brief 获取底层的 SDL_Renderer 指针
|
||||
|
||||
// 禁用拷贝和移动语义
|
||||
Renderer(const Renderer&) = delete;
|
||||
Renderer& operator=(const Renderer&) = delete;
|
||||
Renderer(Renderer&&) = delete;
|
||||
Renderer& operator=(Renderer&&) = delete;
|
||||
|
||||
private:
|
||||
std::optional<SDL_FRect> getSpriteSrcRect(const Sprite& sprite); ///< @brief 获取精灵的源矩形,用于具体绘制。出现错误则返回std::nullopt并跳过绘制
|
||||
bool isRectInViewport(const Camera& camera, const SDL_FRect& rect); ///< @brief 判断矩形是否在视口中,用于视口裁剪
|
||||
|
||||
};
|
||||
|
||||
} // namespace engine::render
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
#include <SDL3/SDL_rect.h> // 用于 SDL_FRect
|
||||
#include <optional> // 用于 std::optional 表示可选的源矩形
|
||||
#include <string>
|
||||
|
||||
namespace engine::render {
|
||||
|
||||
/**
|
||||
* @brief 表示要绘制的视觉精灵的数据。
|
||||
*
|
||||
* 包含纹理标识符、要绘制的纹理部分(源矩形)以及翻转状态。
|
||||
* 位置、缩放和旋转由外部(例如 SpriteComponent)标识。
|
||||
* 渲染工作由 Renderer 类完成。(传入Sprite作为参数)
|
||||
*/
|
||||
class Sprite final{
|
||||
private:
|
||||
std::string texture_id_; ///< @brief 纹理资源的标识符
|
||||
std::optional<SDL_FRect> source_rect_; ///< @brief 可选:要绘制的纹理部分
|
||||
bool is_flipped_ = false; ///< @brief 是否水平翻转
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief 构造一个精灵
|
||||
*
|
||||
* @param texture_id 纹理资源的标识符。不应为空。
|
||||
* @param source_rect 可选的源矩形(SDL_FRect),定义要使用的纹理部分。如果为 std::nullopt,则使用整个纹理。
|
||||
* @param is_flipped 是否水平翻转
|
||||
*/
|
||||
Sprite(const std::string& texture_id, const std::optional<SDL_FRect>& source_rect = std::nullopt, bool is_flipped = false)
|
||||
: texture_id_(texture_id),
|
||||
source_rect_(source_rect),
|
||||
is_flipped_(is_flipped)
|
||||
{}
|
||||
|
||||
// --- getters and setters ---
|
||||
const std::string& getTextureId() const { return texture_id_; } ///< @brief 获取纹理 ID
|
||||
const std::optional<SDL_FRect>& getSourceRect() const { return source_rect_; } ///< @brief 获取源矩形 (如果使用整个纹理则为 std::nullopt)
|
||||
bool isFlipped() const { return is_flipped_; } ///< @brief 获取是否水平翻转
|
||||
|
||||
void setTextureId(const std::string& texture_id) { texture_id_ = texture_id; } ///< @brief 设置纹理 ID
|
||||
void setSourceRect(const std::optional<SDL_FRect>& source_rect) { source_rect_ = source_rect; } ///< @brief 设置源矩形 (如果使用整个纹理则为 std::nullopt)
|
||||
void setFlipped(bool flipped) { is_flipped_ = flipped; } ///< @brief 设置是否水平翻转
|
||||
|
||||
};
|
||||
|
||||
} // namespace engine::render
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace engine::utils {
|
||||
|
||||
struct Rect
|
||||
{
|
||||
glm::vec2 position;
|
||||
glm::vec2 size;
|
||||
};
|
||||
|
||||
} // namespace engine::utils
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
int main(int /* argc */, char* /* argv */[]) {
|
||||
spdlog::set_level(spdlog::level::debug);
|
||||
// spdlog::set_level(spdlog::level::debug);
|
||||
|
||||
engine::core::GameApp app;
|
||||
app.run();
|
||||
|
||||
Reference in New Issue
Block a user