输入

本页面为自动翻译,可能包含错误。如果发现问题,请通过“编辑此页面”链接帮助修正。

Turian 的输入系统通过命名操作将物理设备与游戏逻辑解耦。你可以在 .inputactions 资源中定义操作——键盘、鼠标和手柄绑定都映射到同一个操作名称——你的脚本只需要问"跳跃操作是否按下?",而不需要问"是否按住了 Space?"。


快速开始

1. 创建输入操作资源

资源浏览器中,右键单击 → 创建 → 输入操作。这会在所选文件夹中写入一个默认的 .inputactions 文件(ZON 格式)。

双击该文件打开输入操作编辑器

2. 定义你的操作

编辑器显示一个操作列表。每个操作包含:

  • 名称 —— 脚本中使用的字符串(例如 jumpmovelook)。
  • 类型 —— button(开/关)、axis(1D -1…+1)或 vector(2D,x/y)。
  • 角色 —— 轴使用 Positive/Negative;向量使用 Right/Left/Up/Down;按钮使用 Buttons。每个角色最多可容纳 4 个绑定。

为每个绑定选择一个设备和一个代码

设备 代码示例
key aspaceleft_shiftleft_ctrl
mouse rightleftmiddle
gamepad_button southeastleft_shoulderleft_trigger
gamepad_axis left_xleft_yright_xright_yleft_triggerright_trigger

对于 gamepad_axis+ 复选框选择该轴的正方向(例如摇杆向右、扳机按下)。取消勾选为负方向。

单击保存写入 ZON 文件并重新烹饪到项目中。

3. 打包资源

游戏加载器会自动发现项目中每一个 .inputactions 资源——无需代码。该资源在启动时应用到共享的 engine.Input


在脚本中读取操作

脚本通过 update 接收 engine.Frame 上下文。frame.input 是一个 *const engine.Input

const engine = @import("engine");

pub const PlayerController = struct {
    pub const is_component = true;

    speed: f32 = 5.0,

    pub fn update(self: *@This(), frame: engine.Frame) void {
        const input = frame.input;
        const dt    = frame.time.delta;
        const t     = frame.transform;

        // 按钮操作——数字开/关
        if (input.wasPressed("jump")) {
            // 仅在上升沿触发
        }

        // 轴操作——1D 连续值
        const throttle = input.axis("throttle"); // -1..+1

        // 向量操作——2D(例如 WASD / 左摇杆)
        const move = input.vector("move"); // .x = 右, .y = 前
        t.position.x += move.x * self.speed * dt;
        t.position.z += move.y * self.speed * dt;
    }
};

操作查询 API

方法 返回值 含义
isPressed(name) bool 操作值 ≥ 阈值(按住)
wasPressed(name) bool 在本帧上升至阈值以上
axis(name) f32 1D 值,-1…+1,带死区
vector(name) engine.Vector2 2D 值,限制在单位圆内

无论绑定是按键、鼠标按钮还是手柄摇杆,上述四个方法的运行方式都相同——操作类型仅决定哪种查询在语义上有意义。


原始设备查询

对于需要设备特定状态的 UI 或调试代码,frame.input 也暴露了原始查询:

// 键盘
if (input.isKeyDown(.space)) { ... }
if (input.wasKeyPressed(.a)) { ... }

// 鼠标
const delta = input.mouseDelta(); // 本帧的 .x / .y 像素
const wheel = input.wheelDelta(); // 本帧的滚动刻度
if (input.isMouseDown(.right)) { ... }

// 手柄
const lx = input.gamepadAxis(.left_x); // -1..+1
if (input.isGamepadButtonDown(.south)) { ... }

示例:自由飞行摄像机

示例项目提供了 FreeflyCamera.zig,它使用相同的操作名称通过键盘+鼠标和手柄驱动无碰撞摄像机:

move        — WASD / 左摇杆
look_axis   — 右摇杆(模拟,帧率无关)
ascend      — Space / 手柄 A / 右扳机
descend     — LShift / 手柄 B / 左扳机
boost       — LCtrl / 左肩键
look        — 按住鼠标右键使用鼠标增量观察

该脚本仅读取操作——从不提及键码。在 player-controls.inputactions 中重新绑定所有内容,无需修改 FreeflyCamera.zig


运行时重新绑定

引擎暴露了两个函数,用于游戏中"按某个键重新绑定"的菜单:

// 在设置菜单的更新中:
if (frame.input.captureBinding()) |binding| {
    // 用户按下了某个键——应用它
    _ = frame.input.rebind("jump", .pos, 0, binding);
}

captureBinding() 返回第一个新按下的按键、鼠标按钮、手柄按钮或手柄轴(超过按下阈值)。rebind 通过操作名称、角色(.pos/.neg/.up/.down)和索引替换或附加绑定。

**持久化:**跨会话保存重新映射的绑定需要设置系统(问题 #13,尚未实现)。跟踪进度请查看问题 #42。


engine.Frame 参考

每个接受第二个参数的生命周期钩子都会接收 engine.Frame

pub fn update(self: *@This(), frame: engine.Frame) void
pub fn start(self: *@This(),  frame: engine.Frame) void
// awake、enable、disable、destroy 也接受 frame
字段 类型 含义
frame.time engine.Time 增量时间、经过时间、帧计数器
frame.input *const engine.Input 操作查询 + 原始设备状态
frame.transform *engine.Transform 此对象的位置/旋转/缩放
frame.objects []engine.SceneNode 场景中的所有对象
frame.services *engine.Services 用户注册的服务注册表

使用 frame.service(MyService) 进行类型化 ?*MyService 查找(在 frame.services 中)。

为了向后兼容,仍支持旧的 update(self, time: engine.Time) 签名。


← 所有文档 编辑此页面