相机
每个场景都通过一个相机渲染:一个带 Camera 组件的实体。相机控制缩放、投影,以及世界里
哪一片会到达屏幕。本指南涵盖配置相机、跟随目标、在屏幕与世界空间之间转换、震屏与混合视图,
以及同时运行多个相机。
你可以有很多相机实体,但同一时刻只有一个是活动的。引擎按 isActive + priority
选出活动相机(优先级最高的活动相机胜出),并把世界渲染到它的 viewport(一个屏幕矩形)里。
2D 游戏用**正交(Orthographic)**投影,此时 orthoSize——可见半高(世界单位)——就是缩放:
越小越放大。
生成一个正交相机并设为活动:
import { defineSystem, Commands, Transform, Camera, ProjectionType } from 'esengine';
const spawnCamera = defineSystem([Commands()], (cmds) => { cmds.spawn() .insert(Transform, { position: { x: 0, y: 0, z: 0 } }) .insert(Camera, { projectionType: ProjectionType.Orthographic, orthoSize: 540, // half-height in world units → zoom isActive: true, });});Camera 组件
Section titled “Camera 组件”SDK 默认值为 2D 调好(正交、活动、orthoSize 540)。移动相机就是移动它实体的 Transform。
| 属性 | 类型 | 默认 | 说明 |
|---|---|---|---|
projectionType |
ProjectionType |
Orthographic |
Orthographic(2D)或 Perspective。 |
orthoSize |
number | 540 |
可见半高(世界单位,正交)——你的缩放。可动画。 |
fov |
number | 60 |
视场角(度,仅透视;1–179)。 |
nearPlane |
number | 0.1 |
近裁剪距离。 |
farPlane |
number | 1000 |
远裁剪距离。 |
aspectRatio |
number | 1.77 |
宽 / 高。0 = 从视口自动推算。 |
isActive |
boolean | true |
该相机是否有资格成为活动相机。 |
priority |
number | 0 |
活动相机之间的优先级(高者胜)。 |
viewport |
Vec4 | {0,0,1,1} |
屏幕矩形 x, y, w, h(0–1,分屏/画中画)。 |
clearFlags |
ClearFlags |
ColorAndDepth |
渲染前清什么:Nothing / Color / Depth / ColorAndDepth。 |
pixelPerfect |
boolean | false |
吸附到世界像素网格,让像素画清晰(正交)。 |
showFrustum |
boolean | false |
仅编辑器:绘制相机的视锥 gizmo。 |
orthoSize 是缩放控制——减半即让屏上尺寸翻倍。因为它是 animatable 的,最平滑的缩放方式
是用一个 tween:
import { defineSystem, Res, Tween, TweenTarget } from 'esengine';
const zoomIn = defineSystem([Res(Tween)], (tween) => { tween.to(cameraEntity, TweenTarget.CameraOrthoSize, 270, 0.5); // zoom to 2×});Perspective 只用于伪 3D 的分层场景;2D 游戏保持 Orthographic,尺寸才不随深度变化。
设计分辨率适配
Section titled “设计分辨率适配”发布的游戏里,主相机通常不会按原始 orthoSize 渲染。引擎每帧解析一个适配源,当其
生效时,把项目的设计分辨率(默认 1920×1080)letterbox 进实际屏幕宽高比——可见半高由
designResolution.y / 2 加一个缩放模式算出,让世界单位在任何设备上都是设计像素。适配源
按此顺序:
ScreenScaling资源(项目设置 → 显示 → 相机适配),当其scaleMode是真实模式 (≥ 0)时;- 否则,场景的
Canvas组件(若存在); - 否则无——相机按原始
orthoSize渲染。
默认 orthoSize 为 540,恰是 designResolution.y / 2,所以有无适配的视图在设计宽高比
下一致。缩放模式、letterbox 实例与 ScreenScaling 字段详见
屏幕与设计分辨率。
给相机加一个 FollowTarget 并指向一个实体。跟随是帧率无关的阻尼移动,带一个死区,所以相机
平滑漂移并忽略细小移动:
import { defineSystem, Query, Commands, Camera, FollowTarget } from 'esengine';
const follow = defineSystem([Query(Camera), Commands()], (cameras, cmds) => { for (const [cameraEntity] of cameras) { cmds.entity(cameraEntity).insert(FollowTarget, { target: playerEntity, // the Entity to follow offsetY: 40, deadzone: 24, damping: 0.25, }); }});| 属性 | 类型 | 默认 | 说明 |
|---|---|---|---|
target |
Entity | -1 |
要跟随的实体(-1 = 无)。 |
offsetX |
number | 0 |
相对目标的世界空间 X 偏移。 |
offsetY |
number | 0 |
相对目标的世界空间 Y 偏移。 |
deadzone |
number | 0 |
目标可漫游而相机不动的半径(世界单位)。 |
damping |
number | 0.25 |
平滑时间常数(秒,越大越慢;0 = 立即吸附)。 |
数据形状导出为 FollowTargetData 类型。跟随系统直接写相机的 Transform(所以 director
混合的是已跟随的视图),且只在 play 模式运行——编辑器的导航视图永远不会被它拖走。
屏幕 ↔ 世界坐标
Section titled “屏幕 ↔ 世界坐标”读 CameraView 资源在两个空间之间转换——用于把实体放到光标下,或检测什么可见。无活动相机
时每个方法返回 null:
import { defineSystem, Res, CameraView, Input } from 'esengine';
const pick = defineSystem([Res(CameraView), Res(Input)], (view, input) => { const world = view.screenToWorld(input.mouseX, input.mouseY); // { x, y } | null const screen = view.worldToScreen(x, y); // { x, y } | null const mouse = view.getWorldMousePosition(); // { x, y } | null const bounds = view.getWorldBounds(); // { left, right, bottom, top } | null});| 方法 | 返回 | 说明 |
|---|---|---|
screenToWorld(sx, sy) |
{x, y} | null |
屏幕像素 → 世界位置。 |
worldToScreen(wx, wy) |
{x, y} | null |
世界位置 → 屏幕像素。 |
getWorldMousePosition() |
{x, y} | null |
世界空间里的光标。 |
getWorldBounds() |
{left, right, bottom, top} | null |
可见的世界矩形。 |
shakeCamera 只给渲染的视图加一个衰减扰动——绝不碰 Transform,所以相机总会恢复,场景
保持干净:
import { shakeCamera } from 'esengine';
shakeCamera(app, { amplitude: 12, rotation: 0, frequency: 22, duration: 0.4 });shakeCamera 与 setViewTarget 的第一个参数既可以是 App(宿主代码),也可以是
CameraDirector 资源状态——在系统里直接传 Res(CameraDirector) 给你的值:
import { defineSystem, Res, Input, CameraDirector, shakeCamera } from 'esengine';
const shakeOnHit = defineSystem([Res(Input), Res(CameraDirector)], (input, director) => { if (input.isKeyPressed('Space')) shakeCamera(director, { amplitude: 16 });});| 选项 | 默认 | 说明 |
|---|---|---|
amplitude |
12 |
峰值位置偏移(世界单位)。 |
rotation |
0 |
峰值旋转震动(弧度)。 |
frequency |
22 |
每秒振荡次数。 |
duration |
0.4 |
衰减到零的秒数。 |
setViewTarget 把活动视图切到另一个相机实体,可选地在 time 秒里混合而非硬切——用于
电影化的镜头交接:
import { setViewTarget, BlendCurve } from 'esengine';
// Cut instantly:setViewTarget(app, cutsceneCamera);// Or ease over 1.5s:setViewTarget(app, cutsceneCamera, { time: 1.5, curve: BlendCurve.EaseInOut });BlendCurve 取值为 Linear、EaseIn、EaseOut、EaseInOut。time <= 0(或没有先前视图)
即硬切。位置、缩放、旋转会插值;离散字段(投影、视口)在中点翻转。
相机 director
Section titled “相机 director”setViewTarget 与 shakeCamera 是相机 director 的 API——一个 per-app 资源
(CameraDirector),每帧从全屏相机候选中解析出一个主视点:已提交的视图目标(或活动/
最高优先级相机),过渡进行中则向新目标混合,震屏只叠加在渲染视图之上。其状态
(CameraDirectorState)可读;把它当只读,用 setViewTarget / shakeCamera 去改:
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
target |
number | -1 |
已提交的视图目标实体;-1 = 回退到 isActive / 最高 priority 的选择。 |
blending |
boolean | false |
视图目标混合正在进行。 |
duration / curve |
number | 0 / EaseInOut |
当前混合的时长(秒)与缓动。 |
currentMain |
CameraPOV | null |
null |
最后解析出的主 POV,未加震屏——也是屏幕↔世界转换所用的视图。 |
shakes |
ActiveShake[] |
[] |
活动的瞬时震屏(由 shakeCamera 压入,衰减完即丢弃)。 |
其余字段(hasPending、pendingTarget、pendingTime、pendingCurve、from、
startTime、shakeSeq)是 setViewTarget 写入的 director 内部请求/混合簿记。
import { defineSystem, Res, CameraDirector } from 'esengine';
const watchDirector = defineSystem([Res(CameraDirector)], (director) => { if (director.blending) { /* e.g. suppress player input during the hand-off */ } const pov = director.currentMain; // CameraPOV | null — the view being rendered if (pov) { /* pov.x, pov.y, pov.orthoSize, ... */ }});CameraPOV
Section titled “CameraPOV”CameraPOV 是相机被作者的视图参数的一个纯快照——director 混合的单位,也是
currentMain 存的形状:
| 字段 | 类型 | 说明 |
|---|---|---|
entity |
number | 来源相机实体,-1 表示合成 POV(编辑器视图)。 |
isActive |
boolean | 权威的“这就是主相机”标志。 |
x / y / z |
number | 相机位置(世界单位)。 |
rotation |
number | Z 旋转,弧度。 |
projection |
number | 一个 ProjectionType 值。 |
orthoSize |
number | 作者写的正交半高(世界单位)。 |
fov |
number | 视场角,度(透视)。 |
near / far |
number | 裁剪距离。 |
viewport |
{x, y, z, w} |
屏幕矩形,0–1(z/w 是宽/高)。 |
clearFlags |
number | 一个 ClearFlags 值。 |
priority |
number | 活动相机间的优先级。 |
pixelPerfect |
boolean | 像素网格吸附标志。 |
混合期间,连续字段(位置、按最短角路径的 rotation、orthoSize、fov、near/far)
插值;离散字段(projection、pixelPerfect)在中点翻转,viewport / clearFlags /
priority 取目标值。
匹配画布宽高比
Section titled “匹配画布宽高比”updateCameraAspectRatio(world, aspect) 把 aspectRatio 写到世界里每个 Camera
组件上。各发布运行时启动时用真实画布的 width / height 调用它,让作者字段与实际表面保持
同步(渲染本身每帧从活动视口推导宽高比):
import { updateCameraAspectRatio } from 'esengine';
updateCameraAspectRatio(app.world, canvas.width / canvas.height);多相机与分屏
Section titled “多相机与分屏”有多个 Camera 实体时,isActive + priority 决定谁是活动的。用 viewport 给每个相机一个
子矩形来共享屏幕——例如双人分屏,左右各半:
.insert(Camera, { viewport: { x: 0, y: 0, w: 0.5, h: 1 }, isActive: true }); // left.insert(Camera, { viewport: { x: 0.5, y: 0, w: 0.5, h: 1 }, isActive: true }); // right- 用
orthoSize驱动缩放,别用 Transform 缩放——缩放相机实体会扭曲渲染。 - 跟随带死区,相机才不会因细小移动抖动;调大
damping得到更慵懒、电影化的感觉。 - 震屏而非移动:冲击用
shakeCamera,视图总能恢复。 - 像素画开
pixelPerfect消除亚像素抖动。 - 场景与过场切换优先用混合(带
time的setViewTarget),而不是瞬移相机。