以前書いた新しいInput Systemの記事の書き直しです。
新しいInput System(1.0)
Unity 2019.3にて、新しいInput Systemのバージョンが1.0になりました。まぁ、まだPreviewなんですが。とはいえ1.0になったのでソコまでAPIが変化することは無いだろうと期待して、使い方的な記事を書いてみます。
導入
-
Window > Package Manager
でパッケージマネージャーを開く Advance
のShow Preview Packages
を有効にするInput System
を選択してInstall
を押すPlayerSetting > Player > Active Input Handling
をInput Manager(Old)
からInput System Package
に変更- Unityエディターを再起動
作るもの
左右に移動してジャンプする…という超簡単な動作を作ります。
基本的な流れは下の通り。
- InputActionで、プレイヤーが行う操作とキーの割り当てを行う
- スクリプトでキー操作を受け取る処理を記述
以前の「キーの入力状態を取得して操作を記述する」と比較すると少し抽象的になっています。
手順1:Input Actionsを作る
まずInputActions
を作ります。無くても使おうと思えば使えますが(例えばUnityEngine.InputSystem.Gamepad.current.leftStick
等で)これをベースに作ると途中から辛くなってくるので、最初にInput Actions
を作ってしまいます。
Input Actions
は、特定のアクションにどういった操作を割り当てるのかを定義するファイルです。InputSystemではInputActionsで定義したアクションに対して、スクリプトを割り当てるイメージで使えます。
まずはファイルを生成します。
Assets > Create > Input Actions
でInputActionsを作成。ファイル名はPlayerAct
にする。PlayerActi
を選択し、Generate C# Class
にチェックを入れる。PlayerActi
のEdit Asset
を選択。
中身を設定していきます。アクション名を登録し、ActionTypeで入力が返す値を設定する感じです。
ActionMap
の+
をクリック。名前は横スクロールの操作なのでPlatformAction
に設定。Actions
の+
をクリックしてアクションを二つ作成、名前は移動操作の「Move」とジャンプの「Jump」に設定。Move
のActionTypeをValue
に変更、Control TypeはVector2
を設定。Jump
のActionTypeをValue
に変更、ControlTypeはButton
を設定。
アクションにキー操作を登録します。今回はキーボード入力操作とゲームパッドの操作を登録します。キーボード入力は複数のキー入力を一つの入力に統合する2D Vector Compositeを使ってまとめます。
Move
の右にある+
を押しAdd Binding
を選択。BindingのPathをD-Pad[GamePad]
を選択。Move
の右にある+
を押しAdd 2D Vector Composite
を選択。
Up
にW [Keyboard]
を選択。Down
にS [Keyboard]
を選択。
Left
にA [Keyboard]
を選択。Right
にD [Keyboard]
を選択。Jump
の右にある+
を押し、Add Binding
を選択。
BindingのPathにButton West[GamePad]
を選択。Jump
の右にある+
を押し、Add Binding
を選択。
BindingのPathにSpace [Keyboard]
を選択。Save Asset
を選択
次は作成した設定を使用してキャラクターを動かします。
手順2:キャラクターを動かす(コールバックで動かす場合)
まずコールバックベースで動かすアプローチについてです。コールバックはInputActionsが自動で生成してくれるコードを使用して実現します。コードは指定が無ければInputActionと同じフォルダに生成されます。
コードにはファイル名+ActionMap
というインターフェースが含まれており、これを継承したクラスをActionMapに登録すれば、入力が入る度にコールバックが呼ばれるようになります。入力時のパラメーターはcontext.ReadValue<T>()
で取得します。TがInputActionsのControlType
と一致していないと動かないので注意が必要です。またControlType
は実態と型が一致していない事もあるので、そのあたりも注意が必要です。例えばAxisやButtonはfloatです。
新しいインプットシステムは、基本的にinput.Enable()
とinput.Disable()
が必要です。それと破棄時にはinput.Dispose()
した方が良さそな気配があります。
public class MovePlayer : MonoBehaviour, PlayerAct.IPlatformActionActions { PlayerAct.PlatformActionActions input; [SerializeField] MoveComponent move; float horizontal; void Awake() { // インプットを生成して、自身をコールバックとして登録 input = new PlayerAct.PlatformActionActions(new PlayerAct()); input.SetCallbacks(this); } // インプットの有効・無効化 void OnDestroy() => input.Disable(); void OnEnable() => input.Enable(); void OnDisable() => input.Disable(); void Update() => move.MoveHorizontal(horizontal); // -------------- // コールバック // -------------- public void OnJump(InputAction.CallbackContext context) { move.Jump(); } public void OnMove(InputAction.CallbackContext context) { // 押しっぱなしの動作は、直接オブジェクトを動かすのではなく方向性のみを登録する horizontal = context.ReadValue<Vector2>().x; } }
ここで注意すべきは、このイベントは「値の変化時に呼ばれるっぽい」という点です。そのためMoveアクション(WASDキーにバインド)のような常に押し続ける操作はすぐに動きを停止してしまいます。これは値の変化を変数に格納し、後程Updateで反映することで回避出来ます。これバグかと思ったら仕様っぽいです。
またコールバックは「一フレームに複数呼ばれる」事があります。New Input Systemはフレームの間の入力も取得する事ができます。コールバックは設定のタイミングで一気に処理されますが、入力時の時間はcontext.time
で確認出来ます。これをうまく使うと、基本的にフレームを落としても滑らかな操作といった事が可能かもしれません。
手順2:キャラクターを動かす(ポーリングして動かす場合)
コールバックの動作は便利ではありますが面倒くさいという点も確かにあります。いくつかのゲームは普通にUpdateのタイミングでプレイヤーが責任持って入力を取得する…といったモノの方が好ましいです。ということで、今度はポーリングスタイルで入力を取得します。
使う場合は単純にActionMapのActionからReadValue<T>()
で値を取得するだけです。
public class MovePlayer2 : MonoBehaviour { [SerializeField] MoveComponent move; // インプットの登録と破棄 PlayerAct input; void Awake() => input = new PlayerAct(); void OnDisable() => input.Disable(); // インプットの有効・無効化 void OnDestroy() => input.Disable(); void OnEnable() => input.Enable(); void Update() { // スティックの移動を取得して動かす var velocity = input.PlatformAction.Move.ReadValue<Vector2>(); move.MoveHorizontal(velocity.x); // ジャンプボタンが押されているか判定して動かす var isJumpButtonPressed = (input.PlatformAction.Jump.ReadValue<float>() >= InputSystem.settings.defaultButtonPressPoint); if (isJumpButtonPressed) move.Jump(); } }
仕様かバグなのか微妙ですが、一つ注意点として「Interactions」という入力の動作をフィルタリングして特定の動作を行わないとアクションを呼ばない設定があるのですが、これが設定されてるとうまく動かない事があります。
もしコールバックのように一フレーム内で起こった入力をすべて取得したい場合、下のような記述で取得できます。InputStateHistory records
で観測するアクションを指定し、records.StartRecording
でアクションを格納していきます。なおInteractionsのようなアクションをフィルタする機能は使えないっぽいです。ストリームの値を読んでるだけなので是非もないね。
public class MovePlayer3 : MonoBehaviour { [SerializeField] MoveComponent move; // インプットの登録と破棄 PlayerAct input; InputStateHistory records; void Awake() { input = new PlayerAct(); records = new InputStateHistory(input.PlatformAction.Jump.controls); records.historyDepth = 16; } void OnDestroy() { records.Dispose(); input.Disable(); } // インプットの有効・無効化 void OnEnable() => records.StartRecording(); void OnDisable() => records.StopRecording(); void Update() { foreach (var record in records) { var isJumpPressed = record.ReadValue<float>() >= InputSystem.settings.defaultButtonPressPoint; if (isJumpPressed) move.Jump(); } } }
以前はUnsafeを要求していたので、それと比べれば大分楽に使えるようになりましたが、2D Vector Compositeで使用するとうまく動作しなかったり、時々シャックリを起こしたように入力が取れなかったり、何となく妙な癖があります。
キーコンフィグ
動的にアクションに割り当てたキー操作を変更したり出来ます。
// ジャンプの操作にFキーを追加 input = new PlayerAction(); input.Move.Jump.AddBinding("<Keyboard>/f");
補足
なおPlayerInputという簡単に使用できるハイレベルAPIが用意されていますが、これはUnity 2019.3では期待通りに動作しませんでした。正確にはUnityEventを使用するケースで入力内容を取得できません。同梱のサンプルも動作しないのでバグと思われます(2019.2では動作します)。
Unity 2019.3 b7だと正常に動作しました
関連
今回の記事が面倒くさいと感じた人向け