Skip to content

渲染与相机

你能看到游戏世界,全靠这哥俩。

逻辑分辨率

ShitEngine 默认用 1280x720 作为逻辑坐标空间。你写代码时永远在这个"理想屏幕"上思考,渲染器会自动适配到真实窗口,多出的部分用黑边填满(Letterbox)。

逻辑分辨率可以通过 settings.json 修改:

json
{
  "window": {
    "title": "My Game",
    "width": 1920,
    "height": 1080,
    "targetFPS": 60
  }
}
cpp
// 也可以在代码中查询当前逻辑分辨率
int w = Renderer::GetLogicalWidth();    // 返回 int
int h = Renderer::GetLogicalHeight();   // 返回 int

精灵渲染

SpriteRenderer — 最简单的画图方式

SpriteRenderer 挂到 GameObject 上,告诉它纹理路径,它帮你画出来:

cpp
auto* sprite = go->addComponent<Shit::SpriteRenderer>();
sprite->setTexturePath("textures/player.png");

纹理由 ResourceManager 自动缓存。同一个 png 加载一百次也只占一份内存。

源矩形裁剪

精灵图集(sprite-sheet)的用法——不画整张图,只画其中一小块:

cpp
// 只裁剪纹理中 (0, 0, 32, 32) 的区域
sprite->setSourceRect({0.0f, 0.0f, 32.0f, 32.0f});

// 清空恢复整图
sprite->setSourceRect(std::nullopt);

这个接口是 AnimationComponent 在背后调用的——你不需要手动管理它。

Sprite 数据类

Sprite 是一个纯数据容器,描述"画什么":

cpp
Shit::Sprite sprite("player.png", SDL_FRect{0, 0, 32, 32});
sprite.setFlipped(true);

// 从 SpriteSheet 快速取帧
Shit::SpriteSheet sheet(4, 8, 32, 32);
sprite.setFrame(sheet, 5);    // 等价于 setSourceRect(sheet.getFrameRect(5))

显示与隐藏

RendererComponent 支持运行时隐藏,不渲染但保留组件状态:

cpp
sprite->setVisible(false);
sprite->isVisible();       // 返回 bool

相机

相机决定你能看到什么。没有相机等于闭着眼睛玩游戏。

基本用法

cpp
auto* camera = scene->createGameObject("camera");
camera->addComponent<Shit::TransformComponent>();
camera->addComponent<Shit::CameraComponent>();

相机的位置由 TransformComponent 决定,缩放由 CameraComponent 控制:

cpp
auto* cam = camera->getComponent<Shit::CameraComponent>();
cam->setZoom(5.0f);           // 放大,像素风游戏必备
cam->setSize({ 320, 180 });   // 世界大小(逻辑分辨率单位)

坐标转换

cpp
// 世界坐标 → 屏幕像素
Vector2 screenPos = camera->worldToScreen(worldPos);

// 屏幕像素 → 世界坐标
Vector2 worldPos = camera->screenToWorld(screenPos);

做点击检测、拖拽交互的时候特别好用。

像素单位比(PixelPerUnit)

CameraComponent 暴露了 getPixelPerUnit(),表示一个逻辑单位对应多少像素。setZoom() 改变的是视口大小,getPixelPerUnit() 会随之变化,从而自动缩放所有精灵。

多相机分屏

多个相机可以共享同一个场景,各画各的:

cpp
// 玩家 1 视口:左半边
auto* p1cam = p1->getComponent<Shit::CameraComponent>();
p1cam->setViewportRatio({0.0f, 0.0f, 0.5f, 1.0f});

// 玩家 2 视口:右半边
auto* p2cam = p2->getComponent<Shit::CameraComponent>();
p2cam->setViewportRatio({0.5f, 0.0f, 0.5f, 1.0f});

真实用途

要干嘛怎么搞
分屏合作两台相机各占一半
小地图右下角一个小视口
画中画两相机嵌套
子弹镜相机跟子弹走

优先级

多个相机渲染时,优先级低的先画(在底层)。

cpp
cam->setPriority(0);   // 背景层
cam->setPriority(10);  // UI 层,盖在上面

裁剪

超出相机视野范围的精灵不会被渲染。

渲染流程

每帧的铁打流程:

1. ClearScreen()                        — 清屏
2. BehaviorSystem::update()             — 游戏逻辑(优先级 0)
3. PhysicsSystem2D::update()            — 物理步进(优先级 50,可选)
4. RenderSystem::update()               — 游戏世界渲染(优先级 100)
   a. 排序相机(按优先级)
   b. 排序精灵(按 Z-Index)
   c. 对每个相机:设置视口 → 裁剪 → 渲染可见精灵
5. UIRenderSystem::update()             — UI 系统叠在最上层(优先级 200)
   a. 输入 Raycasting
   b. 按钮状态更新
   c. 按 zIndex 从下到上绘制所有 UI 控件
6. Renderer::Present()                  — 统一显示结果(在 Game::run() 末尾)

引擎的 Present 统一在 Game::run() 的末尾执行——游戏世界和 UI 同框只交换一次缓冲区。

Z-Index 控制谁盖在谁上面:

cpp
sprite->setZIndex(10);  // 值越大越靠上,游戏世界内有效

UI 系统

引擎内置了一套完整的 Retained-Mode UI 系统(Canvas / UIImage / UIText / UIButton / UITextInput),走独立的 UIRenderSystem,不受相机/视口影响,自动在游戏世界之上叠加绘制。

详见 UI 系统

直接绘制(DrawSprite)

除了 UI 系统和挂载 SpriteRenderer,也可以用静态方法直接在屏幕上画精灵:

cpp
// 在屏幕坐标 (100, 50) 画一个精灵,自动使用纹理原始尺寸
Shit::Sprite uiSprite("ui/healthbar.png");
Renderer::DrawSprite(uiSprite, {100.0f, 50.0f});

// 指定目标尺寸
Renderer::DrawSprite(uiSprite, {100.0f, 50.0f}, Vector2{200.0f, 40.0f});

DrawSprite 不依赖 GameObject/Camera 体系,直接在屏幕像素坐标上绘制。但需要你自己管理每帧的绘制调用,推荐优先使用 UI 系统

💬 评论