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入力を有効にします。
コントローラ設定で、レイアウトを編集をクリックして、ボタンやジョイスティックなどにコマンドを割当てます。
ここで、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のプレイボタンを押します。
プレイヤーの移動や回転、加速、アイテムの使用が出来ました。