【Unity】New Input System をバッチリ使う

 Unity の New Input System、使ってますか。

 長らく Unity 製ゲームでまともにゲームパッドキーコンフィグに対応しようとすると Rewired というアセットを使わざるを得なかったんですが、Unity が公式にこれら機能に対応してくれました。ありがとう Unity。ちょっと遅いぞ Unity。

 早速飛びついて一本ゲームを作るまで一応使い込んだので、解説します。

 

二つの使い方

 さて、New Input System のコア部分には二つの使い方があります。

  • ラッパーである PlayerInput を使う方法
  • C# クラスを Generate して、それを new して使う方法

アセット設定

 で、ネットを検索するとどうも前者の情報しか出てこないんですよね。しかし、自分的には PlayerInput を Component でいちいち追加しないといけないのは GameObject の取り回しが面倒なので避けたい。ので、自分は後者の方法でやってます。後者の方法で New Input System を使うには、新規作成した inputactions アセットで Generate C# Class にチェックを入れるだけ。あ、この前段階のパッケージインポート云々は適当なチュートリアルサイト漁ってください。

 この記事ではクラス名は GeneralInput とします。

 

セットアップ例

 アリスではこんな感じ。Gamepad と Keyboard 両対応です。

 

入力を取る

public enum EInputName {/*略*/}
GeneralInput mInput = null;
InputAction[] mActions = new InputAction[(int)EInputName.Max];
private void Start()
{
    mInput = new GeneralInput();
	mActions[(int)EInputName.Decide] = mInput.Game.Decide;
	mActions[(int)EInputName.Cancel] = mInput.Game.Cancel;

	mActions[(int)EInputName.Jump] = mInput.Game.Jump;
	mActions[(int)EInputName.Dash] = mInput.Game.Dash;
	mActions[(int)EInputName.Menu] = mInput.Game.Menu;
	mActions[(int)EInputName.Attack] = mInput.Game.Attack;
	mActions[(int)EInputName.Change] = mInput.Game.Change;
	mActions[(int)EInputName.Map] = mInput.Game.Map;

	mInput.Enable();
}

public bool isHold( EInputName in ) => mActions[(int)in].IsPressed();
public bool isTrig( EInputName in ) => mActions[(int)in].WasPressedThisFrame();
public bool isReleased( EInputName in ) => mActions[(int)in].WasReleasedThisFrame();

 はい。簡単ですね。ここまでは解説なしで大丈夫だとおもいます。

 

ゲームパッドを接続しているか?

static public bool HasPad() => Gamepad.current != null;

 基本的にはこれでOKです。ただし、ゲームパッドを差しているけどキーボードを使いたい人に対処したいなら、今入力しているものがパッドなのかキーボードなのかを検知し、その都度切り替えてあげましょう。

 

キーコンフィグと保存

 RebindingOperation というものを使います。
パラメータを色々とセットして Start() すると、キーを押すかキャンセルするかするまで待機します。onComplete まで来たらすでにキーバインドは変更されているので、わざわざセットする必要はありません。
 ついでに、キーバインドは SaveBindingOverridesAsJson で Json 取得可能なので、そのまま保存してしまいましょう。

InputActionRebindingExtensions.RebindingOperation mRebindingOperation = null;

public void rebindButton( int index = 0 )
{
    mInput.Disable();
	mRebindingOperation = mActions[mCursor].PerformInteractiveRebinding( index )
		.WithBindingGroup( "Gamepad" )
		.WithControlsHavingToMatchPath("<gamepad>")
		.OnMatchWaitForAnother( 0.2f )
		.OnCancel( op => onCancelKeyBinding() )
		.OnComplete( op => onFinishKeyBinding() )
		.Start();
	}
}


void onFinishKeyBinding()
{
	disposeOperation();

	// セーブする
	PlayerPrefs.SetString( "KeyBinding", mInput.Game.Get().SaveBindingOverridesAsJson() );
	PlayerPrefs.Save();
}

void onCancelKeyBinding() => disposeOperation();

void disposeOperation()
{
	mRebindingOperation?.Dispose();
	mRebindingOperation = null;
	mInput.Enable();
}

 index は何番目のキーコンフィグを変更するか指定します。

 上のコードでは Input を Disable / Enable していますが、別のスキームに差し替える方法もあるようです。Enable() 直後は謎の入力が入っていたりするので、ウェイトをかけるなどして二連続でキーコンフィグに突入しないよう注意してください。

 特定の行動にスティックなどを Bind したくない場合は

.WithControlsExcluding( "leftStick" )

 などをパラメータとして追加してください。
 キーボードを検知したい場合は、Gamepad の部分を二箇所 Keyboard にし、index を

mActions[mCursor].GetBindingIndex( InputBinding.MaskByGroup( "Keyboard" ) )

 のように取得してください。

 

キーコンフィグのロード

 ロードの場合は保存した String を LoadBindingOverridesFromJson で読みます。

public void loadKeyBinding()
{
	if ( PlayerPrefs.HasKey( "KeyBinding" ) )
	{
		try {
			mInput.LoadBindingOverridesFromJson( PlayerPrefs.GetString( "KeyBinding" ) );
		}
		catch
		{
			Debug.Log( $"[Keybinding] error" );
		}
	}
}

 ここで一点注意。General Input は new するだけでどのクラスでも使えますが、LoadBinding したキー設定は、そのインスタンスにしか適用されません*1。LoadBinding したインスタンスを使いまわしたい場合は、どこかに static な GeneralInput を持っておいて、それを Get() して使いましょう。

 

手動でボタンを書き換える

mInput.FindAction( "Decide" ).ApplyBindingOverride( 0, "<Gamepad>/buttonEast" );
mInput.FindAction( "Cancel" ).ApplyBindingOverride( 0, "<Gamepad>/buttonSouth" );

 これだけ。例えば Switch 環境だけ決定ボタンとキャンセルボタンを逆にしたい、みたいな設定は簡単。

コントローラを震わせる

public IEnumerator seq_vibrate( float power = 1f, float duration = 0.15f )
{
	Gamepad.current.SetMotorSpeeds( power, power );
	yield return new WaitForSeconds( duration );
	Gamepad.current.SetMotorSpeeds( power, power );
}

 これも簡単ですね。SetMotorSpeeds は二つの周波数の振動をコントロールできるようで、具体的にどういうモノかはお手持ちのコントローラで試してみてください。

ボタンアイコンの表示

 これは少しめんどいです。でも、最近のゲームは大体やってるので根性で頑張りましょう。

 まず、TextMeshPro にスプライトアイコンを表示する……あたりのくだりは省略します。いい感じに全ボタン分のアイコンを設定してください。

//--------------------------------------------------------------------------
static public string GetSpriteName( string device_layout_name, string controlPath )
{
	return GetSpriteName( GetPadType( device_layout_name ), controlPath );
}

static public string GetSpriteName( EPadType pad_type, string controlPath )
{
	var prefix = pad_type == EPadType.XBox ? "XB" : pad_type == EPadType.DualShock ? "PS" : "GP";
	switch( controlPath )
	{
		case "buttonSouth":		return prefix + "_S";
		case "buttonNorth":		return prefix + "_N";
		case "buttonEast":		return prefix + "_E";
		case "buttonWest":		return prefix + "_W";
		case "start":			return prefix + "_ST";
		case "select":			return prefix + "_SE";
		case "leftTrigger":		return prefix + "_L";
		case "rightTrigger":	return prefix + "_R";
		case "leftShoulder":	return prefix + "_L2";
		case "rightShoulder":	return prefix + "_R2";
		case "leftStickPress":	return prefix + "_L3";
		case "rightStickPress": return prefix + "_R3";
		case "dpad":			return "LStick";
		case "dpad/up":			return "Up";
		case "dpad/down":		return "Down";
		case "dpad/left":		return "Left";
		case "dpad/right":		return "Right";
		case "leftStick":		return "LStick";
		case "rightStick":		return "RStick";
	}

	return "INVALID";
}

//--------------------------------------------------------------------------
static EPadType GetPadType( string device_layout_name )
{
	if ( InputSystem.IsFirstLayoutBasedOnSecond( device_layout_name, "DualShockGamepad" ) )
	{
		return EPadType.DualShock;
	}
	else if ( InputSystem.IsFirstLayoutBasedOnSecond( device_layout_name, "XInputController" ) )
	{
		return EPadType.XBox;
	}
	else return EPadType.Gamepad;
}

static public string GetSpriteNameOfKeyboard() => "KB_KEY";

 なんとなく分かったでしょうか。pad_type と controlPath によって適切なボタンの Sprite アイコンを呼んでいます。ただ、キーコンフィグを想定するとどのアクションにどの controlPath が設定されているのかわからないので、 GetSpriteName の呼び方にコツがあります。

public string getSpriteName( string action_name )
{
	var act = mInput.FindAction( action_name );
	if ( act == null ) return "";
	if ( !HasPad() )
	{
		var b = act.GetBindingIndex( InputBinding.MaskByGroup( "Keyboard" ) );
		act.GetBindingDisplayString( b, out var deviceLayoutName2, out var controlPath2 );
		return $"<sprite name=\"{GetSpriteNameOfKeyboard()}\">{controlPath2.ToUpper()}";
	}
	else
	{
		act.GetBindingDisplayString( 0, out var deviceLayoutName, out var controlPath );

		return $"<sprite name=\"{GetSpriteName( deviceLayoutName, controlPath )}\">";
	}
}

 特定のアクションに割り振られているキーは GetBindingDisplayString で取得します。キーボードの場合はキーが返ってくるのでよしなにしてください。一番目の引数は例によって index なので、メインキー、サブキーを併記したい場合はそんな感じに改変してみてください。

 

まとめ

 大体ゲームに必要な機能は網羅できたとおもいます。よき new input system ライフを*2

 え?よく分からなかった? そうかもしれません……でも大丈夫! そんな人のためにサンプルプロジェクト&UnityPackage も作りました。コインいっこでね。

 こちらからどうぞ↓

note.com

 

*1:これに気付かなかったため、アリスではマスター直前まで「キーコンできてるように見えてできてなかった」致命的な不具合が存在した。

*2:Rewired への恨み節は割愛します……