【Unity】Steam入力APIでFPSキャラクターを操作する

投稿者: | 2023-07-02

Steam入力APIでFPSキャラクターを動かしてアイテムを使ってみました。

IGAファイルを作成

まずゲーム内アクションを定義するファイルを作ります。Steamworksのドキュメントでサンプルをダウンロードして、テキストエディタで編集できます。https://partner.steamgames.com/doc/features/steam_controller/getting_started_for_devs

"In Game Actions"
{
	"actions"
	{
		"InGameControls"
		{
			"title"					"#Set_Ingame"
			"StickPadGyro"
			{
				"Move"
				{
					"title"			"#Action_Move"
					"input_mode"	"joystick_move"
				}
				"Camera"
				{
					"title"			"#Action_Camera"
					"input_mode"	"absolute_mouse"
				}
			}
			"Button"
			{
				"interact"				"#Action_Interact"
				"flashlight"				"#Action_Flashlight"
				"run"				"#Action_Run"
				"spray"				"#Action_Spray"
				"sensor"				"#Action_PickUpAndPlace"
				"pause_menu"		"#Action_Menu"
			}
		}
		"MenuControls"
		{
			"title"		"#Set_Menu"
			"StickPadGyro"
			{
			}
			"AnalogTrigger"
			{
			}
			"Button"
			{
				"menu_up"		"#Menu_Up"
				"menu_down"		"#Menu_Down"
				"menu_left"		"#Menu_Left"
				"menu_right"	"#Menu_Right"
				"menu_select"	"#Menu_Select"
				"menu_cancel"	"#Menu_Cancel"
				"pause_menu"	"#Action_ReturnToGame"
			}
		}
	}
	"localization"
	{
		"english"
		{
			"Set_Ingame"			"In-Game Controls"
			"Set_Menu"				"Menu Controls"
			"Action_Move"			"Movement"
			"Action_Camera"			"Camera"
			"Action_Interact"		"Interact"
			"Action_Flashlight"			"Toggle Flashlight"
			"Action_Run"			"Run"
			"Action_Spray"			"Use Spray"
			"Action_PickUpAndPlace"			"Pick Up and Place Item"
			"Action_Menu"			"Pause Menu"
			"Action_ReturnToGame"	"Return To Game"
			"Menu_Up"				"Up"
			"Menu_Down"				"Down"
			"Menu_Left"				"Left"
			"Menu_Right"			"Right"
			"Menu_Select"			"Select"
			"Menu_Cancel"			"Cancel"
		}
		"japanese"
		{
			"Set_Ingame"			"ゲーム内操作"
			"Set_Menu"				"メニュー操作"
			"Action_Move"			"移動"
			"Action_Camera"			"カメラ"
			"Action_Interact"		"インタラクト"
			"Action_Flashlight"			"懐中電灯を切り替える"
			"Action_Run"			"走る"
			"Action_Spray"			"スプレーを使う"
			"Action_PickUpAndPlace"			"アイテムを拾う/置く"
			"Action_Menu"			"メニューを開く"
			"Action_ReturnToGame"	"ゲームに戻る"
			"Menu_Up"				"上"
			"Menu_Down"				"下"
			"Menu_Left"				"左"
			"Menu_Right"			"右"
			"Menu_Select"			"セレクト"
			"Menu_Cancel"			"キャンセル"
		}
	}
}

編集したらファイル名と拡張子を「game_actions_X.vdf」に変更します。XにはAppIDを入れます。

このファイルをSteamインストールフォルダの中の「controller_config」フォルダに配置します。

controller_configフォルダが無ければ作成します。

コントローラ設定を編集する

Steamを立ち上げて、アプリのライブラリの歯車マーク > 管理 > コントローラレイアウト を開きます。

コントローラレイアウトがない場合は、プロパティからSteam入力を有効にします。

歯車マーク > プロパティ
コントローラ > Steam入力を有効にする

コントローラ設定で、レイアウトを編集をクリックして、ボタンやジョイスティックなどにコマンドを割当てます。

レイアウトを編集
コマンドを追加

ここで、IGAファイルに定義したアクションを割り当てます。

Steamをウィンドウにロック

スクリプトでコントローラの入力値が取得できないとき、公式ドキュメントの方法でSteamをウィンドウにロックするとうまくいきました。

Steam URL

「steam://」から始まるSteam URLをブラウザで入力するとポップアップがでるので、「Steamを開く」をクリックします。

ショートカットの起動オプション

または、Steamクライアントのショートカットに起動オプションを付けます。ショートカットのアイコンを右クリックしてプロパティを開きます。

ショートカット
右クリック

ショートカットタブの「リンク先」の末尾に、ゲームのAppIDを含んだパラメータを付けてOKを押します。

スクリプト

スクリプトでは、まずSteamworks APIとSteam入力を初期化し、デバイスやアクションのハンドルを使って入力値を取得します。

using Steamworks;
using System.Collections;
using UnityEngine;

public class SteamInputTest : MonoBehaviour
{
    // コントローラのハンドル
    InputHandle_t inputHandle;

    // アクションのハンドル
    InputDigitalActionHandle_t runHandle;
    InputDigitalActionHandle_t sprayHandle;

    InputAnalogActionHandle_t cameraActionHandle;
    InputAnalogActionHandle_t moveActionHandle;
    InputActionSetHandle_t inGameActionSetHandle;

    // アクションの現在の状態
    public static InputAnalogActionData_t cameraActionData;
    public static InputAnalogActionData_t moveActionData;
    public static InputDigitalActionData_t runActionData;
    public static InputDigitalActionData_t sprayActionData;

    // Sprayアクションの前回の状態
    static InputDigitalActionData_t previousSprayActionData;

    // Sprayアクションのボタンが押されたときにtrue
    public static bool OnSprayActionDown { get => previousSprayActionData.bState == 0 && sprayActionData.bState == 1; }

    // アクションハンドルがあるかどうか
    bool hasActionHandles;

    // Start is called before the first frame update
    void Start()
    {
        // Steamworks APIを初期化
        if(SteamManager.Initialized)
        {
            Debug.Log("初期化");
        }

        // SteamInputを初期化
        if (SteamInput.Init(false))
        {
            Debug.Log("SteamInput初期化");
        }

        // コントローラーを取得
        StartCoroutine("GetController");
    }



    IEnumerator GetController()
    {
        var inputHandles = new InputHandle_t[16];

        int num = 0;
        while (true)
        {
            // 接続されたコントローラーのハンドルを取得
            var count = SteamInput.GetConnectedControllers(inputHandles);

            // 一つ以上のハンドルがあれば
            if (count > 0)
            {
                // アクションハンドルを取得
                GetActionHandles(inputHandles);

                // コルーチンを終了
                yield break;
            }
    
            num++;

            // 取得できないと一定時間で終了
            if(num >= 1000)
            {
                yield break;
            }

            yield return null;
        }
    }

    // アクションハンドルを取得
    void GetActionHandles(InputHandle_t[] inputHandles)
    {
        // はじめのコントローラーのハンドル
        inputHandle = inputHandles[0];

        // ゲーム中のアクションセットのハンドル
        inGameActionSetHandle = SteamInput.GetActionSetHandle("InGameControls");

        // アクションセットを有効化
        SteamInput.ActivateActionSet(inputHandle, inGameActionSetHandle);

        // デジタルアクションのハンドルを取得
        runHandle = SteamInput.GetDigitalActionHandle("run");
        sprayHandle = SteamInput.GetDigitalActionHandle("spray");

        // アナログアクションのハンドルを取得
        cameraActionHandle = SteamInput.GetAnalogActionHandle("Camera");
        moveActionHandle = SteamInput.GetAnalogActionHandle("Move");

        hasActionHandles = true;

    }

    private void Update()
    {
        if (!hasActionHandles) return;

        // 現在のアクションの状態を取得
        moveActionData = SteamInput.GetAnalogActionData(inputHandle, moveActionHandle);
        cameraActionData = SteamInput.GetAnalogActionData(inputHandle, cameraActionHandle);
        runActionData = SteamInput.GetDigitalActionData(inputHandle, runHandle);
        sprayActionData = SteamInput.GetDigitalActionData(inputHandle, sprayHandle);

    }

    private void LateUpdate()
    {
        // sprayアクションの状態を保存
        previousSprayActionData = sprayActionData;
    }

}

usingディレクティブでSteamworks名前空間の型をインポートします。入力値を得るために必要なコントローラやアクションのハンドルのフィールドを定義しています。

using Steamworks;
using System.Collections;
using UnityEngine;

public class SteamInputTest : MonoBehaviour
{
    // コントローラのハンドル
    InputHandle_t inputHandle;

    // アクションのハンドル
    InputDigitalActionHandle_t runHandle;
    InputDigitalActionHandle_t sprayHandle;

    InputAnalogActionHandle_t cameraActionHandle;
    InputAnalogActionHandle_t moveActionHandle;
    InputActionSetHandle_t inGameActionSetHandle;

入力値を他のクラスから使えるように、アクションの状態の静的フィールドを公開します。

    // アクションの現在の状態
    public static InputAnalogActionData_t cameraActionData;
    public static InputAnalogActionData_t moveActionData;
    public static InputDigitalActionData_t runActionData;
    public static InputDigitalActionData_t sprayActionData;

デジタルアクションの場合、ボタンが押されているときは、InputDigitalActionData_t.bStateの値が1、押されていないときは0になります。Input.GetButtonDownのように、ボタンが押されたフレームだけtrueを返すプロパティを作るために、Sprayアクションは前のフレームの状態のフィールドも定義しました。

    // Sprayアクションの前回の状態
    static InputDigitalActionData_t previousSprayActionData;

    // Sprayアクションのボタンが押されたときにtrue
    public static bool OnSprayActionDown { get => previousSprayActionData.bState == 0 && sprayActionData.bState == 1; }

前のフレームのデータが0で現在のフレームで1のときにtrueを返します。

アクションハンドルが取得できなかったときに、Updateの処理をスルーするためのbool変数もあります。

    bool hasActionHandles;

StartメソッドでSteamworks APIとSteam入力の初期化をしています。コントローラのハンドルは取得できるまでラグがあるのでコルーチンを使っています。

    void Start()
    {
        // Steamworks APIを初期化
        if(SteamManager.Initialized)
        {
            Debug.Log("初期化");
        }

        // SteamInputを初期化
        if (SteamInput.Init(false))
        {
            Debug.Log("SteamInput初期化");
        }

        // コントローラーを取得
        StartCoroutine("GetController");
    }

コルーチンでは、SteamInput.GetConnectedControllersメソッドで、接続中のコントローラのハンドルを取得します。引数にサイズが16のInputHandle_t配列を渡します。

    IEnumerator GetController()
    {
        var inputHandles = new InputHandle_t[16];

        int num = 0;
        while (true)
        {
            // 接続されたコントローラーのハンドルを取得
            var count = SteamInput.GetConnectedControllers(inputHandles);

書き込まれたハンドルの数が変えるので、それが1以上になったら終了します。取得できない場合も一定時間で終了するようにしました。

            if (count > 0)
            {
                // アクションハンドルを取得
                GetActionHandles(inputHandles);

                // コルーチンを終了
                yield break;
            }
    
            num++;

            // 取得できないと一定時間で終了
            if(num >= 1000)
            {
                yield break;
            }

            yield return null;
        }
    }

アクションセットとアクションのハンドルを取得します。アクションセットのハンドルはSteamInput.GetActionSetHandleメソッドを使います。引数にはIGAファイルで指定した文字列を渡します。

    void GetActionHandles(InputHandle_t[] inputHandles)
    {
        // はじめのコントローラーのハンドル
        inputHandle = inputHandles[0];

        // ゲーム中のアクションセットのハンドル
        inGameActionSetHandle = SteamInput.GetActionSetHandle("InGameControls");

アクションセットの有効化にはSteamInput.ActivateActionSetメソッドを使います。コントローラーとアクションセットのハンドルを指定します。

        SteamInput.ActivateActionSet(inputHandle, inGameActionSetHandle);

SteamInput.GetDigitalActionHandleメソッドとSteamInput.GetAnalogActionHandleメソッドで、デジタル/アナログアクションのハンドルを取得します。ここでもIGAファイルで指定した文字列を使います。

        // デジタルアクションのハンドルを取得
        runHandle = SteamInput.GetDigitalActionHandle("run");
        sprayHandle = SteamInput.GetDigitalActionHandle("spray");

        // アナログアクションのハンドルを取得
        cameraActionHandle = SteamInput.GetAnalogActionHandle("Camera");
        moveActionHandle = SteamInput.GetAnalogActionHandle("Move");

        hasActionHandles = true;
    }

Updateメソッドで毎フレームのアクションの状態を取得します。SteamInput.GetAnalogActionDataメソッドにコントローラーとアクションのハンドルを渡します。

    private void Update()
    {
        if (!hasActionHandles) return;

        // 現在のアクションの状態を取得
        moveActionData = SteamInput.GetAnalogActionData(inputHandle, moveActionHandle);
        cameraActionData = SteamInput.GetAnalogActionData(inputHandle, cameraActionHandle);
        runActionData = SteamInput.GetDigitalActionData(inputHandle, runHandle);
        sprayActionData = SteamInput.GetDigitalActionData(inputHandle, sprayHandle);

    }

その後、LateUpdateでsprayアクションを保存しています。

    private void LateUpdate()
    {
        // sprayアクションの状態を保存
        previousSprayActionData = sprayActionData;
    }

}

FPSコントローラー

元のFirstPersonControllerクラスやMouseLookクラスの入力を受ける部分などを仮想メソッドとして切り出して、派生クラスでオーバーライドしました。

そして、上のスクリプトのアクションデータの静的フィールドを使ってプレイヤーを操作します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityStandardAssets.Characters.FirstPerson;
public class TestFPSController : FirstPersonController
{
    // MouseLookの派生クラスのインスタンス
    [SerializeField] TestMouseLook testMouseLook;

    // 移動の入力値を取得
    protected override (float, float) GetInput()
    {
        //return (CrossPlatformInputManager.GetAxis("Horizontal"), CrossPlatformInputManager.GetAxis("Vertical"));

        var x = SteamInputTest.moveActionData.x + Input.GetAxis("Horizontal");
        var y = SteamInputTest.moveActionData.y + Input.GetAxis("Vertical");
        return (x, y);
    }

    // 走っているかどうかを取得
    protected override bool GetIsRunning()
    {
        //return Input.GetKey(KeyCode.LeftShift);
        return SteamInputTest.runActionData.bState == 1 || Input.GetKey(KeyCode.LeftShift);
    }

    // MouseLookのゲッタープロパティ
    protected override MouseLook MouseLook { get => testMouseLook; }
}

// MouseLookの派生クラス
[System.Serializable]
public class TestMouseLook : MouseLook
{
    // カメラの回転のための入力値を取得
    protected override (float, float) GetInput()
    {
        //return (CrossPlatformInputManager.GetAxis("Mouse X") * XSensitivity, CrossPlatformInputManager.GetAxis("Mouse Y") * YSensitivity);

        var x = (SteamInputTest.cameraActionData.x + Input.GetAxis("Mouse X")) * XSensitivity;
        var y = (SteamInputTest.cameraActionData.y + Input.GetAxis("Mouse Y")) * YSensitivity;

        return (x,y);
    }
}

軸の値は足してボタンの値は論理和を使うと、マウスやキーボードと併用できます。

また、プレイヤーにスプレーのエフェクトを出すスクリプトも追加しました。静的プロパティの値によってエフェクトを再生します。

using UnityEngine;

public class TestSpray : MonoBehaviour
{
    // 煙のエフェクトのプレハブ
    [SerializeField] VFXController smokePrefab;

    // プレイヤーの位置でエフェクトを再生
    public void PlayVFX()
    {
        var smoke = Instantiate(smokePrefab);
        smoke.SetUp();
        smoke.transform.SetPositionAndRotation(transform.position, transform.rotation);
        smoke.PlayVFX();

    }
   
    private void Update()
    {
        // sprayアクションが押された時にエフェクトを再生
        if (SteamInputTest.OnSprayActionDown || Input.GetKeyDown(KeyCode.Space))
        {
            PlayVFX();
        }
    }
}

スクリプトを実行

Steamを立ち上げた状態でUnityのプレイボタンを押します。

プレイヤーの移動や回転、加速、アイテムの使用が出来ました。

コメントを残す

メールアドレスが公開されることはありません。