入力
Turian の入力システムは、名前付きアクションを通じて物理デバイスとゲームロジックを分離します。アクションを .inputactions アセットで定義すると——キーボード、マウス、ゲームパッドのすべてのバインディングが同じアクション名にマッピングされます——スクリプトは「jump アクションが押されているか」だけを問い合わせ、「Space が押されているか」を尋ねることはありません。
クイックスタート
1. 入力アクションアセットを作成する
アセットブラウザで、右クリック → 作成 → 入力アクション。選択したフォルダにデフォルトの .inputactions ファイル(ZON 形式)が書き込まれます。
ファイルをダブルクリックして入力アクションエディターを開きます。
2. アクションを定義する
エディターにはアクションのリストが表示されます。各アクションには以下があります:
- 名前 — スクリプトが使用する文字列(例:
jump、move、look)。 - 種類 —
button(オン/オフ)、axis(1D −1…+1)、またはvector(2D、x/y)。 - ロール — 軸には
Positive/Negative、ベクターにはRight/Left/Up/Down、ボタンにはButtons。各ロールは最大4つのバインディングを保持できます。
各バインディングに対して、デバイスとコードを選択します:
| デバイス | コード例 |
|---|---|
key |
a、space、left_shift、left_ctrl |
mouse |
right、left、middle |
gamepad_button |
south、east、left_shoulder、left_trigger |
gamepad_axis |
left_x、left_y、right_x、right_y、left_trigger、right_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 値、単位円にクランプ |
バインディングがキー、マウスボタン、ゲームパッドスティックのいずれであっても、4つすべてのメソッドは同じように動作します——アクションの種類は、どのクエリが意味的に適切かを決定するだけです。
生デバイスクエリ
デバイス固有の状態が必要な 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 に触れる必要はありません。
ランタイム再バインド
エンジンは、ゲーム内の「キーを押して再バインド」メニュー用に2つの関数を公開しています:
// 設定メニューの更新内で:
if (frame.input.captureBinding()) |binding| {
// ユーザーが何かを押した——適用する
_ = frame.input.rebind("jump", .pos, 0, binding);
}
captureBinding() は、押下しきい値を超えた最初の新しく押されたキー、マウスボタン、ゲームパッドボタン、またはゲームパッド軸を返します。rebind は、アクション名、ロール(.pos/.neg/.up/.down)、インデックスによってバインディングを置換または追加します。
永続化: 再マップしたバインディングをセッション間で保存するには、設定システムが必要です(問題 #13、未実装)。進捗は問題 #42 で追跡しています。
engine.Frame リファレンス
第2パラメータを受け取るすべてのライフサイクルフックは 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)シグネチャも引き続きサポートされています。