ぐっちーの駄弁り部屋

個人的に制作しているものの進捗や日常について不定期投稿

 UnityでGoogleスプレッドシートからenumの定義ファイルを作る

どうも皆さんお久しぶりです
ぐっちーでございます

最後にブログの記事を書いたのは桜が咲き誇る春のことだったみたいですが今はすっかり紅葉が見事な季節になっております

言い訳をすると仕事が忙しかったり新作ゲームが豊富だったりで大変だったのです(特にゲームが楽しかったです)

さて、今日はゲーム開発とかVRとかではなくツール開発したよって話になります
これを作ろうとしてたわけではなく別のものを作る過程であったほうが楽だろうなと思って作成したものなのですが
割と普通の開発でも使い勝手がいいんじゃなかろうかというものができた次第であります

初めに

まずはこちらをご覧ください

別々に投稿したツイートですが一つ目はGoogleスプレッドシートからアセットを生成してるもので
二つ目はそのアセットからenumの定義ファイルを吐き出しているものになります

結果としてGoogleスプレッドシートにあるデータからenumの定義を生成することに成功しました
enumってそんな面倒なことしなくても作れるよね?」「わざわざGoogleスプレッドシート経由する必要ある?」
と思われる方いらっしゃると思います ごもっともな意見だとは思います。

しかしゲーム開発において武器やアイテムの種類、属性などプログラマのコード変更の意思に関係なく変化するデータの扱う際にいちいちプログラム変更を依頼するのは面倒だしされるのも面倒ではありませんか?

プログラマが触れる範囲でも定義を更新できるようにしておいた方が何かと都合がよかったりするのです

大まかな機能の流れ

大まかな流れはこんなかんじ
EnumDataは管理用のクラス名でScriptableObjectを継承しておりEnumDataAssetはそれをもとに生成したアセットのことです

つまるところGoogleスプレッドシートで作ったデータを読み込んでScriptableObjectのアセットに変換、それを使ってenum定義ファイルを吐き出しているというわけですね

スプレッドシートの構成

スプレッドシートの構成はこんな感じ
最近アニメ化されたアークナイツというソシャゲの素材管理ツールを作ろうとしていた副産物が今回のツールなのでデータの中身もアークナイツですw
ほかのシートに素材の加工レシピとかをまとめてあってこのシートは種類とかランクの定義を置いてあります
レシピをツール内でデータ化するときもこれらのデータを使いたいのでenum化したかったわけなんですね

話がそれましたが今回は表示名の右隣の列に変数名(スクリプトからそのenumにアクセスするときの名前)が来るようにしております
つくり的にばらばらになっていても大丈夫なのですが見づらくなるのでこのように並べています

Unityはenumをインスペクタに公開したとき変数名がそのまま表示されます
まぁ、これでも悪くはないんですが数が増えてきたりit1234みたいなIDで定義されるとなんのこっちゃとなってしまいますね
私の場合自前のAttributeで表示名を自由に設定できるようにしているのでそれ用のパラメータとして表示名の列を用意しています
需要があればそちらの記事も書こうと思いますがいつのまにかUnityの標準機能としてInspectorNameというものがあるみたいです

docs.unity3d.com

Unityからしばらく離れていたので気が付かなかった…

では詳細部分に入っていきます

UnityでのGoogleスプレッドシート読み込み

まずはUnityにGoogleスプレッドシートの情報を引っ張ってきます
以下のサイトを参考にしたというかほぼそのまま使わせていただいています

qiita.com

UnityからGoogleスプレッドシートにアクセスしてデータを引っ張ってくるにはシート固有のIDと参照するシートの名前が必要になります
詳しい説明は先駆者様にお願いするとして補足をするとUnityWebRequest.Getで指定するURLの末尾が以下のようになっています

tqx=out:csv&sheet="+_SHEET_NAME

out:csvとありますね
そのまんまなのですがこの方法でデータを引っ張ってくるとCSV形式(コンマ区切り)でデータが入ってきます
そのためstring.Split()を使ってデータを分割してあげればほしいデータにアクセスできるというわけなんですね
EnumDataを吐き出すためのウインドウはUnityのエディタ拡張で作成して各種データと表示名の列および変数名の列を指定できるようにしています

そのためA列とかB列みたいに指定されたものを配列のインデックスに変換してあげれば任意の列のデータが引っ張ってこれます

EnumDataに変換

先にEnumDataの実態を載せておきます

EnumData.cs · GitHub

EnumDataクラスにはenum定義ファイルを吐き出す際のクラス名(DataClassName)と各要素の定義データ(EnumDataUnit)のリストを持ちます
EnumDataUnitクラスは変数名と表示名をメンバとして持ちます

csvのデータをEnumDataに変換するのは簡単で上の行から順番に任意の列にあるデータを読み込んでEnumDataUnitを生成してEnumDataのインスタンスへ登録してあげればよいわけですね

解析スクリプト(クリックで展開)

private EnumData convertEnumDataFromCSV(string text)
        {
            var data = Resources.Load(Exportpath + "/" + _ClassName) as EnumData;
            if (data == null)
            {
                data = ScriptableObject.CreateInstance<EnumData>();
            }
            data.DataClassName = _ClassName;
            var reader = new StringReader(text);

            // 1行目はラベルなのでスキップ
            reader.ReadLine();
            while (reader.Peek() != -1)
            {
                var row = reader.ReadLine();
                var elem = row.Split(',');
                var disp = elem[_DisplayNameColumn].Replace("\"", "");
                var vari = elem[_VariableNameColumn].Replace("\"", "");
                if (disp == "" || vari == "")
                {
                    continue;
                }
                data.RegisterDataUnit(new EnumDataUnit(disp, vari));
            }

            return data;
        }

前述の通りEnumDataはScriptableObjectですのでアセットとして出力しておきます
Googleスプレッドシートからenumを生成するのであればcsvから直接生成することもできるのですがアセットとしてしておくことでスプレッドシートを使用しない場合でも後述の方法で定義ファイルを生成できるのでアセット化しています
スクリプトからのアセット生成はAssetDatabase.CreateAsset()にEnumData(ScriptableObjectのインスタンス)とアセット名を渡してあげることで行われます

アセット生成コード(クリックで展開)

var enumData = convertEnumDataFromCSV(request.downloadHandler.text);
var fileName = enumData.DataClassName + ".asset";
var folderPath = ResourceFolderPath + Exportpath;
if (!Directory.Exists(folderPath))
{
    Directory.CreateDirectory(folderPath);
}
var assetPath = folderPath + "/" + fileName;
var assets = AssetDatabase.FindAssets(enumData.DataClassName, new string[] { folderPath });
if (assets.Length == 0)
{
    AssetDatabase.CreateAsset(enumData, assetPath);
}
Debug.Log($"[EnumData][Converter]: EnumDataAssetを出力(更新)しました\n{assetPath}");
var asset = Resources.Load(Exportpath + "/" + enumData.DataClassName);
Selection.activeObject = asset;

上記コードではフォルダがなかった時に自動で生成するようにしています

先ほどの画像のウインドウでEnumData生成を押せばここまでのことをやってくれてEnumDataAssetが生成されます
次にそのアセットを使ってenum定義ファイルを作っていきます

enum定義ファイルを出力

ここまで来たらあとは半分くらいUnity関係ないですw
まずはそれっぽいウインドウを用意します

今回のウインドウはファイル更新のボタンを押すとEnumDataAssetを格納してあるフォルダを見てAssetのリストを更新します
するとその下のプルダウンからアセットが選択できるようになっており、スクリプトファイル生成ボタンを押せば特定のフォルダに定義ファイルが吐き出されます

ここでの技術ポイントはどうやってenum定義ファイル、すなわちcsファイルを吐き出すかという点です
csファイルも所詮はテキストファイルですのでC#の機能でファイルの作成が可能です

登場するのはStreamWriterでこれにスクリプトの中身を渡してあげます
scriptというstring型の変数にスクリプトの中身を格納して以下のコードに渡してあげればFolderPathで指定したフォルダに
fileNameと同名のファイルが生成されます

var fileName = "/" + _Data.DataClassName + ".cs";
var path = UtilApp.ConvertAssetsPath2AbsPath(FolderPath + fileName);
using (StreamWriter sw = new StreamWriter(path, false)
{
    sw.WriteLine(script);
    Debug.Log($"[EnumData][ScriptGenerator]: {path}を出力しました");
}

UtilApp.ConvertAssetsPath2AbsPath()はAssetsから始まるパスをドライブの絶対パスへ変換する自前のスクリプトです

ConvertAssetsPath2AbsPath(クリックで展開)

/// <summary>
/// Assets/ パス⇒絶対パスへの変換
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static string ConvertAssetsPath2AbsPath(string path)
{
    var ret = "";
#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
    ret = path.Replace("Assets", Application.dataPath).Replace("/", "\\");
#else
    ret = path.replace("Assets", Application.dataPath);
#endif
    return ret;
}
#endregion

定義ファイルを生成するコードはこんな感じ

EnumDataScriptGenerator.cs · GitHub

上の方にreadonlyで定義してあるのがクラス名や変数の定義部分のひな型で#と大文字アルファベットで記述している部分をstring.Replace()によって置き換えます

foreach (var unit in _Data.Units)
{
    var value = UnitBlock;
    value = value.Replace("#DISPLAYNAME", unit.DisplayName);
    value = value.Replace("#VARIABLENAME", unit.VariableName);
    var hash = UtilApp.GetHash(unit.VariableName).ToString();
    value = value.Replace("#HASH", hash);

    code += value;
}
var classBlock = "";
classBlock += ScriptBlock;
classBlock = classBlock.Replace("#CLASSNAME", _Data.DataClassName);
classBlock = classBlock.Replace("#CODE", code);

script = script.Replace("#CLASS", classBlock);

UtilApp.GetHash()はstringをuintのハッシュ値に変換するメソッドです
連番ではなくハッシュ値を使っているのはシリアライズした際にデータの破損が発生するのを防ぐ意図があります

これにて定義ファイル完成です!

ただ、ちょっとめんどくさいことにこれでファイルを生成した際に自動でコンパイルは走らないので明示的に読んであげましょう

CompilationPipeline.RequestScriptCompilation();

終わりに

今回はUnityとGoogleスプレッドシートの連携、スクリプトからScriptableObjectAssetの作成、スクリプトからスクリプトファイルの作成という3点を個人的な趣味から作ったツールの実装過程を紹介しながら紹介しました

個人開発でそこまでしなくてもとも思いましたが私は淡々とデータを打ち込むような作業が死ぬほど嫌いで
頭を使わなくていいことは自動化したい系なのでとにかく楽をしたいがために作りました

作業の効率化をできるところはしていくことで別のことに時間が使えますしね

本題の素材管理ツールができたらまた記事にしますのでお待ちいただければと思います

では今日はこの辺で~

参考リンク

qiita.com

qiita.com

qiita.com

VRMとVIVEFacialTrackerでVtuberごっこしてみた

f:id:gucchi512:20220409233259p:plain どうも皆さんこんばんは グッチーです

新年度になりましたね。実感がわかないですが社会人2年目に突入しました
去年よりはいろんな方面で成長はしているはずですがまだまだ知識・技術力不足だなぁと思う今日この頃です

今年(度)はあれこれインプット・アウトプット増やしてこうと思って事始めとしてだいぶ前に購入していたVIVEFacialTrackerを使ってみましたので導入手順とかをまとめておこうと思います

本題に入る前に

まずは何故今までこのFacialTrackerで遊んでこなかったのかというお話をしようと思います

この通りです。後述しますがVIVEFacialTrackerはVIVEProEyeと併せて使うことが前提になっているためトラッカーから伸びてるTypeCケーブルがべらぼうに短いです。
それはまだいいんですがTypeCをさすことのできるPCを当時持っていなかったというのが大きかったです。
そのためそもそもトラッカーを利用することができず今更遊んだというわけです

VIVEFacialTrackerとは

では本題に参りましょう。VIVEFacialTrackerとは顔(主に口回り)のトラッキングを行うことができるトラッカーです。
リップシンクのように発声に合わせて口が動くのではなく口回りの動きをトラッキングを可能にするのであくびなど発声を伴わない口の動きをとることもできますし、リアルの動きがアバターの顔にリンクするのでアバターがより人間らしい表情をしてくれます
ただこちらのFacialTrackerですが「フェイシャル」と言ってるのにかかわらず口元の動きしかトラッキングしてくれません。目の動きや瞬きなどはとることができません。そういったところまでトラッキングしたい場合はVIVEProEyeというトラッカーを出しているHTC社が発売しているHMDを利用する必要があります
そもそもこのVIVEFacialTrackerはVIVEProEyeと併せて使うことを前提に作られているようで本来の使い方としてはこのトラッカーから伸びているTypeCケーブルをHMDに接続し、付属のテープでHMDに固定して利用できるようになっていました。
とはいえPCに接続さえできれば単体でも動作するので興味のある方は購入してみてください
通販以外にもHMDが購入できるリアル店舗にも置いてあることがありますのでいろいろ探してみてください(私は購入を思いついた日にその足で日本橋ツクモに買いに行きました)

www.vive.com

今回やったこと

今住んでいる部屋はお世辞にもVRを、ましてやVIVE製品特有のベースステーションを配置してトラッキングエリアを構築できるような広さをしていないのと購入する資金がないのでVIVEProEyeを利用せずトラッカー単体での動作になりますのでご了承ください

動作環境

今回利用した環境です

  • Windows10 64-bit
  • Unity 2020.3.26f
  • SteamVR 1.21.12
  • SRanipalSDK 1.3.3.0
  • SRanipal Runtime 1.3.2.0
  • VRoidStudio v0.13.1 (必要な場合のみ 詳細は後述)

下準備

SDKとSR_Runtimeの入手

まずはこのトラッカーを使うための環境構築からです。
以下のサイトからこれらが入ったファイルをダウンロードしてきます

developer.vive.com

こちらのサイトに飛ぶとクイックスタートが書かれたPDFを閲覧ができますのでそれに従って設定していけばOKです

内容をざっくりまとめておくと
1. このサイトに飛んで
hub.vive.com
2. これらのファイルをダウンロード
f:id:gucchi512:20220409221620p:plain
3. VIVE_SRanipalInstaller_1.3.2.0.msiを実行してRuntimeをインストールSDKはZipファイルを展開する
4. SRRuntimeを起動
5. Unityプロジェクトを開く(もしくは作成)
6. SDK-v1.3.3.0\SDK\02_UnityにあるVive-SRanipal-Unity-Plugin.unitypackageをUnityプロジェクトにインポート

これでフェイシャルトラッカーを使ってトラッキングをする準備は整いました。

サンプルシーンで遊んでみる

では早速サンプルシーンで遊んでみましょう

Assets/ViveSR/Scenes/Lip/LipSample_v2.unity

前述の準備を行った後にこちらのシーンを再生するとトラッキングを試すことができます。シーンを再生したらトラッカーのセンサーがある方の面を口元に近づけていろいろ動かしてみましょう。

f:id:gucchi512:20220409223342p:plain

思っている以上に近づけないと反応しなかったりするので注意です。
顔のトラッキングがしたいだけならここまでで完了です。お疲れさまでした。
次の項からVroidStudioで作成した自分のアバターを操作していく方法を紹介していきます

VRM導入編

今回の記事ではVRMファイルのインポート方法などについては省略します
UniVRMを使っていい感じにするのが定番化と思います

やらなければならないことまとめ

先にやらなければならないことだけまとめておきます。こちらのフェイシャルトラッカーですがセンサーの値を用いてブレンドシェイプの値を操作しています。
対象となるブレンドシェイプは決まっているため自分の用意したモデルにそのブレンドシェイプが存在しない場合追加する必要があります。
逆に既に対象となるブレンドシェイプが用意できている場合には以下の操作は不要ですので飛ばしてください
対象となるブレンドシェイプは以下の画像の通りです f:id:gucchi512:20220409224154p:plain

VRoid製VRMファイルにブレンドシェイプを追加する

追加するにあたりいくつか注意事項があります

  • 今回専用ツールを使います。有料のツールになりますのでご了承ください
  • ラッキングを可能にするためにはVRoidStudio v0.11.3/v0.12.1/v0.13.1のいずれかで作成したVRMファイルが必要です 正式版のVRoidStudioでは頂点数の関係でツールを利用できません

以下のツールを使ってブレンドシェイプを追加していきます

booth.pm

方法は以下のサイトを参考にしてください

hinzka.hatenablog.com

サンプルシーンにVRMをインポートしたときにできるPrefabを配置しメニューバーからHANA_Tool/Readerを選択しReaderを起動します。

SkinnedMeshRendererにFaceオブジェクトをD&DしSRanipalから始まるファイルを選択します。この時に選択するファイルはVRMファイルをエクスポートしたVRoidStudioのバージョンによって切り替えてください

f:id:gucchi512:20220409230614p:plain

ReadBlendShapesをクリックすると実行されてブレンドシェイプが追加されます

私はこのあたりの知識が乏しいのでツールを購入し利用させていただきましたが、
ブレンドシェイプの追加に詳しい方はツールを使わなくても可能なのかなと思いました。

テスト用のコンポーネントをPrefabに刺してチェック

ブレンドシェイプが追加できたらそのアバターのPrefabにテスト用のコンポーネントをさしていきます
SDKに入っているコンポーネントを利用していきます。VRMMetaなどのコンポーネントが刺さっている親オブジェクトを選択しAddComponentからSRanipal_AvaterLipSample_v2というコンポーネントを追加します

f:id:gucchi512:20220409225548p:plain

そうしたらLipShapeTablesのElementを一つ増やします。
するとSkinnedMeshRendererを設定するよう促されますのでFaceという名前の子オブジェクトをD&Dします

f:id:gucchi512:20220409225957p:plain

するとブレンドシェイプの一覧が表示され割り当てが確認できます。VRM標準のブレンドシェイプが残っている場合それらも表示されますが何の値も入ってなくて大丈夫です必要なところが正しく入っていれば動作します。以下の画像のようになってればOK

f:id:gucchi512:20220409230147p:plain

SDKのサンプルシーンは再生するとMainCameraの正面に鏡が移動してくるようになってるみたいなのでそれを利用して動作確認を行います。
MainCamera/AvaterSample以下にあるサンプル用のオブジェクトを消して、配置したPrefabのGameObjectを代わりに置きます
カメラの位置に顔の真ん中が来るように位置を調整します(なぜがこのシーンのMainCameraは変な方向を向いています)

f:id:gucchi512:20220409231338p:plain
マニピュレータがMainCameraの位置
この時カメラより前にアバターがいるとMainCameraに映ってしまいますので注意してください。
そしてシーンを再生すると…

アバターが私の口に合わせて動いてくれました!
バストアップくらいで切り取ってゲーム画面やコメント欄(っぽいもの)を表示しておけば気分はVtuber

今後

いい感じに動かすことができたので後はカスタマイズするだけですね
記事執筆時点では何もしていませんがよりリアルにするならオートブリンクを実装するとかが手っ取り早そうな気がします。
あと今回はサンプルコードを使って顔の動きを実装していますのでそれを参考に独自実装をしたほうがいい気もしますね。軽く調べた感じだとセンサーの値をとってこれるようなのでそれを使えばいいのかな?
記事にはしていませんが実はFinalIKを使った首から下の挙動については検証済みなのでそれと組み合わせてお手軽全身Vtuberごっこができそうですね

私の持っているHMDはOculusRift でHMDと両手のコントローラを使った3点トラッキングなのでかなり動きがぎこちないです。

おしまい

というわけで結構前に購入したVIVEFacialTrackerで遊んでみたという記事でした。
VRChatとかは最近出来てないですがそういった自分だけのアバターを動かせる環境を自作するというのも面白いかもしれませんね。
というわけで超久々の技術紹介記事でした!
それでは今日はこの辺で(`・ω・´)

参考記事

developer.vive.com

hinzka.hatenablog.com

note.com

shitakami.hatenablog.com

2021年を振り返る

f:id:gucchi512:20211230144152p:plain みなさまお久しぶりです。グッチーです。
技術ブログのはずが本年ほぼ更新がないまま年末を迎えてしまいました…
技術的なトピックに触れたりするのは少なくないですし、社会人になってコードレビューとかを受ける機会も増えたので技術的に成長した1年ではあったのですが記事に起こすレベルのものではなかったりまだ人に説明できるほどマスターできてなかったりしたので記事にしなくていいやと放置していたらこのザマでした(´・ω・`)
今回の記事はタイトルにもある通り今年1年を振り返っていくものにしたいと思います

あれはまだ学生だった時のこと…

今年の3月までは学生だったんですね。すごい昔のことのように感じます。
といっても1~3月はこれといって何かあったわけではなく卒論の追い込みをかけてたり、それの発表をしたり卒業したりしたくらいですかね。あの頃作っていたゲームは作ってる最中に何が面白いのかがわからなくなって没になりました。
しれっと大学生活を振り返る記事も書いていますのでよければご覧ください(唐突な宣伝)

gucchi512.hatenablog.com

後述しますが新生活が始まったことがあり卒業間際の記憶がほぼ残ってないですwww
今になって大学生活は自由にいろんなものに見て触れることができて楽しかったなあと思います

社会人になったらしい

4月からは社会人の仲間入りを果たしたわけですが特に最初の4ヶ月くらいは研修で社会人としての心得やらゲームの作り方やらを学びました。
「ゲームの作り方?Unity使ってたし余裕だぜ!」そう思ってた時期が私にもありました。
確かに私はUnityを使ってゲームを作った経験が何度かあるのでC#でコードを書いて動くものを作ることはできます。しかし研修で学んだゲームの作り方というのは面白いゲームの作り方や何を売りにしてゲームを作るかというプロの視点でのゲームの作り方なのです。

研修の中で個人やチームを組んでゲームを作ることもあったのですがメンターの方からいただく意見はこれまで受けたことのないような的を得た意見ばかりで、時には傷口に指を突っ込まれるような痛い意見もいただきました。
また、そういった経験を通じてゲームを見る目が変わったと思います。「なぜこのゲームは売れるのか」「何が面白いのか」といったことを観察するようになったり、アイデアを生むために今まで以上にたくさんのエンタメに触れるようになりました。審美眼というのは1日2日で養われるものではありませんがエンタメのこの先を作るものとしては必要となる力なのだと考えさせられました。
あとはゲームのデバッグ方法も意外と勉強になってたりします。
とあるメンターの方はたまに進捗を見てもらう際に必ず1個以上のバグを見つけるのですがその方曰く「ユーザーがどんな動きをするかはわからないからどんな動きにも耐えうる設計にする必要がある」とのことでした。
実装を行う際に想定通りの操作をしてバグが出ないことをチェックするのは当たり前なのですがバグというのは想定通りの動きをしなかった時によく見られるものです。研修期間で割とその辺は今まで以上に注意して実装するようにはなったのですがその上を行くようなデバッグプレイを見せられた時はまだまだ想定する力が足りていないのだと痛感しました。

現在はプロジェクトに入って実際に業務に携わっているわけですが規模感が今までの比ではないので別ラインの実装をしているプログラマさんだけではなくモーションを作っている人やUIやモデルを作っているデザイナーさんなど非常に多くの人たちと関わるなかで感じたことはどの職種の人も業務は違えどどうすれば今のゲームが面白くなるかを考えて日々仕事をしていることは共通していました。まさにこれこそがプロとしての心構えなのだと痛感しました。

OSAKA

大学を卒業するまでの22年間を田舎で過ごした私が今年踏み入れた日本を代表する大都市の一つ大阪
比較的街中に住んでいるので手の届くところに全てが揃っていることへの便利さは感動すら覚えました。
この記事を執筆している現在は地元に帰省しているのですが本当に周りに何にもないのでよく今までここで生活していたと思うほどでした。車がないと生活できないというのは田舎ではよくあることですが車がなくても生活できるところで生活するとここまで不便に感じるものなのかと思いました。
f:id:gucchi512:20211230143308j:plainf:id:gucchi512:20211230143349j:plain 大阪はご飯は美味しいし遊ぶところもたくさんあって時々散策をしていたのですが特に食べ物に関してはどこで食べても正解ばかりなので食巡りを繰り返した結果明らかに太りました。(´・ω・`)

あとは期間限定でモンハン酒場WESTが開催されていたので同期のプログラマと一緒に行ってきました。f:id:gucchi512:20211230143436j:plainf:id:gucchi512:20211230143419j:plainf:id:gucchi512:20211230143411j:plain こういったコラボカフェなんかも都会でしか大体開催されないので大阪に来て本当に良かったと思っています。

最後に

会社であったことをかければもう少し技術ブログ的なものに近くなるのかと思うのですがそういったことは外に出せないのでざっくりと個人的な振り返りをしてみました。
来年はもう少しアウトプットを増やせたらいいなと思いますね(毎年言ってできていない気がするので期待しないでください)
それでは今年はこの辺で。良いお年をお迎えください(`・ω・´)

知らない間にUnityの入力周りが変わっていた件について

皆さんお久しぶりです。ぐっちーです

ここ最近個人開発だったり、ブログネタだったりを集めることがなかったのでブログの更新が滞ってましたが新生活も落ち着いてきたので個人開発を再開しようかなと思った次第です。

なので何か進捗があればTwiterか記事にできるようなものならこちらに上げようかなと思います。

さて本題ですが仕事でUnity使ってるわけではないのでかなり長い期間Unityを触っていなかったんですが久々にUnityでゲーム開発しようと思って色々調べていたら気になる記載が…

Unityの新入力システム・InputSystemを使おう

forpro.unity3d.jp

え、Unity入力関係変わってたの?

一応以前のInput.GetButtonDownみたいなのも使えるみたいですが使い方を調べていたら圧倒的にInputSystemのほうが使いやすそうでしたので今回は備忘録的な感じで導入と簡単な使い方を残しておきたいと思います。

InputSystemとは

InputSystemは各種入力デバイスからの入力を汎用的・統合的に取り扱うように導入されたシステムで2020年4月にversion1.0.0がリリースされたみたいです(リリース今年じゃなかったのか)

Unity対応プラットフォーム全てに対応しているようで今後こちらが標準になっていくみたいです

InputManagerとの違い

これまではInputManagerというもので入力を扱ってきたかと思います。Input.GetAxisで上下左右を取ったり、Input.GetButtonDownでボタン入力を取得したりですね。 また、Preferenceから"Horizontal"や”Fire”などの名前でボタンを割り当てて使う場合もあったかと思います。昔からUntyを使っている人たちは非常に馴染み深いと思います。

InputSystemがこれと何が違うかというとまず、スマホゲーなどを開発した際の仮想的なスティックやボタンにも対応している点です。

これまでは自分でタッチ判定などを作成して対応する必要がありましたがInputSystemにはそれらの仮想的なボタンをコントローラのボタンと同じように扱う機能が備わっているので非常に扱いやすくなっています。PCとスマホ両方でリリースするゲームとなったときに非常に助かる機能だと思います。

また、InputManagerにキーを登録する際には各コントローラの割当番号的なもので登録していました。こちらの番号はコントローラによって異なるので逐一確認しなければなりませんでした。これが非常にめんどくさかったわけですがInputSystemではその必要がないみたいです。

対応しているコントローラーは * XboxコントローラーなどXInput方式のもの * DualShock(PS4のやつ) * Joy-Con(ビルドプラットフォームがSwitchの場合)

です。PS5のDualSenseはまだ対応していないようですが拡張プラグインを作成している方がいて、Github上で公開されていましたのでひとまずはそちらを使うのがいいかと思います。

github.com

ちなみにスマホの加速度センサーやジャイロにも対応しているみたいです

導入編

インストール

ではでは導入していきましょう。めんどくさいことはないのでサクサク行きます。

いつ頃からか忘れましたがUnityではこういったパッケージやアセット関連の導入をPackageManagerに統一したみたいですね。非常にいいことだと思います。UnityEditorからAssetStoreに接続するの結構時間かかってたイメージなので助かります。

InputSystemも例にもれずPackageManagerから導入します。PackageManagerはアルファベット順になっているのでそれで探すか検索窓に”InputSystem”と入れて出てきたらInstallを選択します。

インストールするとネイティブバックエンドを有効にするか聞かれますのでYesと答えます。するとUnityが再起動しますがその後からはInputManagerによる入力は使えなくなるので注意してください。(InvalidOperationExeption吐くみたい)

一応ProjectSetting>Player>OtherSetting>ActiveInputHandleから使いたい方を選択できるようになっているので両方使いたいよって方はそちらからどうぞ

これでInputSystemを使用することができるようになりました。そうです。実際のところこれだけです。 以下のようなコードを記述すれば実際に入力をとってこれます。

using UnityEngine;
using UnityEngine.InputSystem;

public class AppInputExample : MonoBehavior{
 private float speed = 5.0f;
 void Update(){
  if(Gamepad.current == null) return;

  if(Gamepad.current.buttonNorth.wasPresseddThisFrame) Debug.Log("☓(A)ボタンが押されました");

  Debug.Log(Gamepad.current.leftStick.ReadValue()); //vector2
 }
}

コントローラ右側のボタン群を東西南北で表現してるのは面白いと思いますww

基本的にはInputDevice.currentの形で接続されているデバイスからの入力が取れるのでKeyboard.currentでキーボードの入力が、Mouse.currentでマウスの入力が取れます。

もう少し設定する

上記のように値を取得してもいいでのすがゲームを作るなら「このボタンで攻撃」「このキーでジャンプ」みたいなものが作りたいかと思います。そういった設定をする機能ももちろんあります。

Actionという概念でそのような動作をキーやボタンにバインドすることができます

Actionを使用するにはまず何かしらのオブジェクトにPlayerInputコンポーネントをアタッチします

アタッチしたらCreateActionsボタンからInputActionAssetを作成してActionsにアタッチします

このInputActionAssetというのが操作とキーやボタンを結びつける役割を担っています
InputActionAssetを開くと以下のようなウインドウが表示され最初からいくつかの設定がされているのがわかります。

例えばこの画像で行くとMoveというActionにパッドのLスティック、キーボードのWASD、XRコントローラやJoystickの入力などが割り当てられています。これはどれかの入力の値をMoveというActionから取得できるよという意味でMoveにさえアクセスすれば入力方式によってプログラム側で分けて記述する必要なく移動方向が取得できるということです。

それでは次の項でActionの使い方に触れていきます

使い方編

Actionにバインドを追加する

では任意の操作を追加していきましょう。今回はジャンプボタンを追加します バインドの追加はActions右側の+をクリックして行います。名前をJumpにしてその下のを選択します

そうしたらPathを選択し設定したいキーを割り当てます。今回はパッドの☓ボタン(Aボタン)を割り当てます。

キーマウでも動かしたいならJumpの右側にある+を選択してバインドを追加し、同様の手順でキーを追加しましょう

スクショミスってますがUse in control schemeを適切に設定してください。

このような感じで操作と入力を結びつけて扱いやすくなりましたので今度はスクリプトからこれらを読んでいきます。

スクリプトからActionを処理する

実はActionの値をスクリプトで受け取る方法はいくつかあります。メッセージを送信するものやUnityEvent、C#のEventを発火させるものなど様々ですがこの記事では個人的に一番使いやすいと感じたものをご紹介します。

InputActionAssetを選択するとInspectorに次のような表示がされると思います

ここでGenerate C# Classを選択すると先程のActionたちをC#のクラスにしてくれます すると先程のActionがプロパティになってくれるので非常に直感的に値の処理ができます。

using UnityEngine;

public class PlayerController : Monobehavior{
 AppInputAction _Input;
 float _Speed = 1f;
 void Awake(){
  _Input = new AppInputAction();
  _Input.Enable();
  _Input.Player.Fire.performed += OnFire;
  _Input.Player.Jump.performed += OnJump;
 }
 
 void Update(){
  var dir = _Input.Player.Move.ReadValue<Vector2>();
  transform.Translate(dir * _Speed * Time*deltaTime);
 }

 void OnFire(InputAction.CallbackContext){
  //攻撃処理的な
 }

 void OnJump(InputAction.CallbackContext){
  //ジャンプ
 }
}

個人的にこの方法が一番使いやすいってだけなので他の方法が良いかもって人はそっちで大丈夫です

その他詳しい使い方

その他の詳しい使い方は冒頭でも紹介した記事で紹介されていますのでそちらで確認してみてください

forpro.unity3d.jp

まとめ

簡単にまとめます

  • いつの間にかInput関連が新しくなってた
  • 従来と違いかなり汎用的なシステムになった
  • 専用のアセットでアクションと入力を結びつけて扱うとより実践的
  • クラス化することで更に使いやすく

こんなところでしょうか。入力周りって個人的に面倒なイメージだったんですがかなり改善されそうな予感

では今回はこのへんで!次はいつになることやら...