自由相机状态机
[!WARNING] 本条目描述的是
FreecamController(camera/FreecamController.java),不是core/FreecamCore.java。FreecamCore只是 FML 的 coremod 入口,只负责挂载早期 Mixin,详见 Mixin 加载阶段。
基本信息
| 属性 | 值 |
|---|---|
| 类型 | 特殊机制(客户端相机状态机) |
| 实现类 | com.caedis.freecam.camera.FreecamController |
| 单例 | 静态 INSTANCE,FreecamController.instance() 获取;reset() 整体换新实例 |
| 触发条件 | 按下/松开 切换自由相机 的 F4;或按住 F4 时用数字键 1~9 选三脚架槽位 |
| 服务端同步 | 无——全部逻辑在客户端,对世界数据零影响 |
相机如何脱离玩家
enable()(FreecamController.java:111)的完整步骤:
- 前置检查:已激活 / 已被禁用 /
thePlayer == null/theWorld == null时直接返回; cameraEntity = new CameraEntity(mc.theWorld, mc.thePlayer)——在玩家当前位置与朝向新建脱离实体(见 脱离实体);cameraEntity.setCollisionMode(GeneralConfig.collisionMode);- 备份
previousRenderViewEntity = mc.renderViewEntity与previousPerspective = mc.gameSettings.thirdPersonView; - 强制
thirdPersonView = 0(第一人称)并把mc.renderViewEntity指向相机实体; - 置
active = true、playerControlled = false、activeSlot = TripodSlot.NONE、三轴速度归零; - 调用
applyPerspectiveOffset()按 进入视角 偏移。
关键点:它不移动也不冻结玩家。玩家照常受输入控制,只是 renderViewEntity 被换走,且移动输入在
MixinMovementInputFromOptions 里被清零,所以移动键被相机接管。渲染侧的鼠标视角则由
MixinEntityRenderer 从 EntityClientPlayerMP.setAngles 重定向到 renderViewEntity.setAngles。
相机如何重新挂回
disable()(FreecamController.java:159)无条件还原:
| 字段 | 还原为 |
|---|---|
mc.renderViewEntity |
previousRenderViewEntity |
mc.gameSettings.thirdPersonView |
previousPerspective |
previousPerspective |
-1(哨兵值) |
previousRenderViewEntity / cameraEntity |
null |
active |
false |
activeSlot |
TripodSlot.NONE |
触发退出的路径(全部走 disable()):
- 松开 F4 且本轮没有选过三脚架(
ClientEventHandler.onKeyInput); - 重复按同一个三脚架槽位 → 视为"关闭该机位"(
toggleTripod); tick()中pendingDisable为真(受伤排程,见 受伤时禁用);tick()中 已被禁用;- 玩家死亡(
thePlayer.isDead); cameraEntity.worldObj != mc.theWorld(换了世界);- 断线(
onDisconnect→disable()),随后reset()换新单例。
FreecamController.reset() 在连上服务器时也会被调用(ClientConnectedToServerEvent),
因此换图/重连后所有 三脚架机位 与速度倍率都会归零。
每 tick 逻辑
tick()(FreecamController.java:252)在 ClientTickEvent.END 阶段被调用,顺序为:
pendingDisable或 已被禁用 →disable(),并清空pendingDisable;- 未激活 / 无相机 /
mc.isGamePaused()→ 返回(暂停时不推进相机); - 玩家死亡 →
disable();cameraEntity.worldObj != mc.theWorld→disable(); - 每 tick 重新下发
setCollisionMode(GeneralConfig.collisionMode),使碰撞模式可运行时热改; cameraEntity.onUpdate()(只刷新prev*/lastTick*插值字段,不做实体逻辑);playerControlled为真 → 直接返回,相机不接受键盘输入;- 读 WASD / 潜行 / 疾跑 7 个原版按键的原始 lwjgl 按键状态;
speed = MovementConfig.speed * speedMultiplier,疾跑再 × 1.5;- 按 移动模式 走 CREATIVE 或 STATIC 位移;
clampToRenderDistance()收边。
移动与转向
| 常量 | 值 | 作用 |
|---|---|---|
DEFAULT_SPEED_SCALE |
0.5 |
STATIC 模式的速度系数 |
CREATIVE_SPEED_SCALE |
2.0 |
CREATIVE 模式的速度系数 |
CREATIVE_ACCELERATION |
0.15 |
CREATIVE 模式下速度趋近目标的插值系数 |
CREATIVE_FRICTION |
0.6 |
CREATIVE 模式下每 tick 的摩擦衰减 |
SPRINT_MULTIPLIER |
1.5 |
按住疾跑键的倍率 |
DIAGONAL_FACTOR |
sin(45°) |
同时前进+平移时的对角线归一化因子 |
SPEED_SCROLL_STEP |
0.1F |
滚轮每格的速度档位步进 |
SPEED_MULTIPLIER_MIN / MAX |
0.1F / 10.0F |
倍率上下限 |
两种模式的差别:
- STATIC(
tickDefaultMovement):直接把本 tick 的方向向量乘速度后moveEntity,松键立即停住; - CREATIVE(
tickCreativeMovement):先算出target速度,再对三个轴分别做v += (target - v) * 0.15的逼近,然后整体乘0.6摩擦;绝对值小于0.001的轴直接归零, 因此松键后会滑行一小段再停住。
朝向由 MixinEntityRenderer 把鼠标的 yaw/pitch 直接写进相机实体,控制器自身不处理旋转。
视角偏移(仅首次启用时)
applyPerspectiveOffset()(FreecamController.java:179)只在 enable() 末尾调用,
enableTripod() 不调用它——这是 enable() 与 enableTripod() 的一个实质差异。
| 模式 | 距离常量 | 位移 |
|---|---|---|
INSIDE |
— | 不偏移 |
FIRST_PERSON |
FIRST_PERSON_DISTANCE = 0.4 |
沿视线方向前移 0.4 格(眼睛前方) |
THIRD_PERSON |
THIRD_PERSON_DISTANCE = 4.0 |
沿视线方向后移 4.0 格 |
THIRD_PERSON_MIRROR |
THIRD_PERSON_DISTANCE = 4.0 |
沿视线方向前移 4.0 格,并把 rotationYaw 与 prevRotationYaw 各 +180° |
渲染距离收边
clampToRenderDistance()(FreecamController.java:298)是一个反作弊式护栏,不是性能限制:
maxDist = (renderDistanceChunks - CLAMP_MARGIN_CHUNKS) * 16.0,CLAMP_MARGIN_CHUNKS = 2(负值时钳到 0);- 对相机与玩家的三轴球面距离做比较,超出则按
scale = maxDist / dist缩放偏移量; - 用
moveEntity而非setPosition施加修正,使修正同样受 碰撞模式 约束, 避免玩家离开三脚架后相机被硬塞进方块里。
源码注释写明:把相机压在渲染距离边缘附近会 xray 到未加载区块的矿物。
玩家操控模式
togglePlayerControl()(FreecamController.java:217)在已激活时翻转 playerControlled,
并用 GTNHLib AboveHotbarHUD 在物品栏上方输出 msg.freecam.control.player / msg.freecam.control.camera。
开启后发生三处变化:
tick()不再移动相机(由 移动模式 那段整体跳过);- 四个点击/拾取 Mixin 注入不再取消,允许玩家正常操作世界;
MixinEntityRenderer.getMouseOver被取消并改用thePlayer.rayTrace(reach, partialTicks), 即准星判定回到玩家身上。
反馈消息
全部通过 AboveHotbarHUD.renderTextAboveHotbar(text, 20, true, true) 输出,语言键见
assets/freecam/lang/en_US.lang:
| 语言键 | 触发 |
|---|---|
msg.freecam.enable / msg.freecam.disable |
toggle() 后按新状态二选一 |
msg.freecam.speed |
adjustSpeed(),格式 Speed: %.1fx |
msg.freecam.tripod.open / msg.freecam.tripod.close |
toggleTripod(),格式带机位编号 %d |
msg.freecam.tripod.reset |
resetTripods() |
msg.freecam.control.player / msg.freecam.control.camera |
togglePlayerControl() |
⚠️ 该 mod 只提供 en_US.lang,没有 zh_CN.lang,因此上述消息在中文环境下显示为语言键本身。
相关条目
- 脱离实体 -
CameraEntity继承EntityPlayer后被裁掉的全部行为与子步进碰撞 - 三脚架相机槽位 -
TripodSlot的 9 个复用机位与TripodRegistry - Mixin 加载阶段 - 视角重定向、点击取消、伤害检测等补丁分别落在哪个 Mixin
- 移动速度 - 决定
tick()里speed基值的配置项 - 移动模式 - STATIC 与 CREATIVE 两种积分方式
- 相机碰撞模式 -
moveEntity子步进所遵守的碰撞规则 - 进入视角 - 只在
enable()生效的 4 种起点视角 - 玩家控制开关 - 翻转
playerControlled的按键 - 切换自由相机 - 进入/退出的主按键