おもちゃバコ

中身スカスカ♡

Unity: コマンドパターンでプレイヤを管理する

こんにちハロー
ゼロカロリーのジュースは口の中がベトベトしませんね

今回はコマンドパターンをUnityで実際に使用したときの備忘録です.

参考文献

コマンドパターンはこの本で知りました.
ゲーム開発関係なく,プログラマにはおススメの本です!

この記事読むよりおススメです! doggy.hatenablog.com


コマンドパターンとは

Command パターン - Wikipedia より

リクエストのために必要な手続きをCommandオブジェクトとしてカプセル化した上で取り回し[1]、必要に応じてExecute(実行)するパターンである。
オブジェクトであることを生かして命令のキューイングやロギング、Undo等が可能になり[2]、Executeを分離したことで手続きと実行を疎結合にできる。

関係ないですが「Wikipediaは信用できない」とよく言われていますが,個人的には技術記事に関しては信用できると思っています.


実装

作るもの

とりあえず下記の処理を実装してみます.
1. 十字キーでプレイヤ移動する.
2. Bキーでプレイヤがアイテムを持つ.(Log出力だけ)
3. Bキーでプレイヤがアイテムを置く.(Log出力だけ)

キャラクタの基底クラス

今回は操作するプレイヤだけですが,敵(エネミー)を作る際にも使用できるように基底クラスを定義します.

プレイヤクラス

プレイヤ専用みたいになっていますが,エネミーにも使用できます.(多分)

キャラクタの振舞い(コマンド)クラス

「実行」と「実行取り消し」のみの抽象クラスです.

移動コマンド

回転コマンド

持ち上げコマンド

持ち下げコマンド

プレイヤの入力処理を行うクラス

入力に応じたコマンドを返す処理を記述します.
プレイヤは入力装置を使用します.
また,エネミーの場合は,プレイヤの入力判定部分にAIを記述することになると思います.

今回はリストでコマンドを管理していますが,参考文献に挙げた本ではコマンドをnewしており,コマンド履歴を保持するようになっています.

自分もnewするようにするのが便利だと思いますが,UnityではGCが走るので良くないのかなと思って辞めました.
詳しい方に教えていただきたいです.

シーン管理を行うクラス

ここにコマンドを走らせる処理を記述します.
いわゆるゲームの進行管理を行うクラスだと思います.


Unity上の設定

Player

PlayerController.csをアタッチします.
f:id:lambda410:20210509155633p:plain

PlayerInputHandler

親にPlayerInputHandler.cs,子に各種Command.csをアタッチします.
f:id:lambda410:20210509155749p:plain
f:id:lambda410:20210509155856p:plain

GameScene

PlayerInputHandlerとPlayerのprefabをアタッチします.
f:id:lambda410:20210509160201p:plain

Scene配置

今回は,実行時にPlayerInputHandlerとPlayerを生成するようにしたので,基本はGameSceneプレハブを配置するだけです.
あとは,必要に応じてカメラや床などを配置してます.
f:id:lambda410:20210509160428p:plain


実行

十字キーで移動できます.
f:id:lambda410:20210509160353p:plain


まとめ

コマンドパターンを使用したプレイヤ処理を行いました.

EnemyInputHandler.csを作成して,その中にエネミーのAIを記述することで,ほかのスクリプトをいじらずにAI処理が記述できます.
あとは,今までPlayerController.csのUpdate内でいろいろ記述していた処理がなくなり,スクリプトが簡潔になったのが良いですね.

Undoの処理は適当ですが,雰囲気が伝われば幸いです.

プログラムの設計から今まで逃げてきましたが,一度しっかり勉強する必要があるな~と改めて実感しました.

Unity: InputSystemでローカルマルチに対応する(改)

こんにちハンドラ
最近座り方がヤバくて腰が終わってきました.

今回は前に書いたInputSystemでローカルマルチを実現するやつの改良です.
lambda00.hatenablog.com

前回の記事は,「プレイヤ(操作されるもの)に直接イベントを埋め込む」実装を行っていました.
小さな規模のゲームだと大丈夫かもしれませんが,大きな規模のゲームになるとプレイヤが神クラスになる可能性が高いです.

そこで,今回は,Singletonパターンを使用してコントローラを管理するControllerManagerを作成し,コントローラを使用する側でControllerManagerを参照するようにしました.


参考文献

Singletonについて
hikatech.com
nabesi777.hatenablog.com
blog.negativemind.com
qiita.com


Singletonパターン

すごく簡単かつ雑に説明すると
インスタンスが1つしか存在しないことが保証されるクラス設計のこと
です.
実装については,参考文献ママさんなのでそちらを参照してください.


イメージ

SingletonのControllerManagerにアクセスして入力情報をもらってくる感じです.

ControllerManager

本題です.
ControllerManager.csの役割としては,下記の通りです.
・追加されたコントローラ(入力装置)の管理
・指定されたコントローラ番号(ID)に応じた入力を返す
Singletonにした理由は,シーンをまたいでも入力を受け取りたいからです.

まず,Singletonパターンを継承したControllerManager.csを作成し,ControllerManagerオブジェクトにアタッチします.
今回はコントローラのスタートボタン,キーボードのエンターを入力時にControllerオブジェクトが生成されるようにInputSystemを設定しています.

ControllerManager.csでは,入力装置が追加(スタートボタンまたはエンターキーが入力)されたときにアタッチされている「Player Input Manager」に設定されている「Controller」プレハブを生成します.
このとき,OnPlayerJoinedイベントが実行され,子要素としてControllerプレハブが追加されます.

ControllerBehaviour

次に,コントローラの振る舞いについてです.
基本は,前回の記事でプレイヤに直接埋め込んでいた処理をただ移行しただけです.

機能としては,
・ボタン状態の判定: PRESS, HOLD, RELEASE, NONE
十字キーの値を取得
ぐらいです.

ボタン状態の更新をUpdate()で行っているのは,イベントハンドラが呼ばれるタイミングが早すぎてPRESSとRELEASEの判定がきちんとできないからで,特に深い理由はありません.


PlayerController

使用方法についてです.
初めにコントローラIDを指定して,接続されるまでコルーチンで待っています.
接続後,プレイヤの入力が反映されます.


実行

シーン遷移をしたらプレイヤは消滅しますが,ControllerManagerは生きていることが確認できます.
また,Singletonで設計したクラスはDontDestroyOnLoad()しても,1つなのが確認できますね.
(XYZはSingletonを使用せずに,ただDontDestroyOnLoad()したやつです.)


まとめ

これでプレイヤが神クラスになることを回避できた(?)のかな.

Unity: Polygon Collider 2Dから平面のMeshを生成する

こんにちハサミギロチン
バルバトスMGを買ったので,ゴールデンウイークに組み立てるのが楽しみです.

今回はPolygon Collider 2Dから平面のMeshを作成した時の備忘録です.

Unity: Unity 2019.4.24f1


参考文献

今回は独自仕様なのでなし.

動機

スプライトの形に応じたMeshが使えたら便利かもっておもっただけ.
(本当は,2Dのコライダーを3Dゲームに使用する方法がわからなかっただけ)


Prefab

事前にSpriteとPolygon Collider 2Dを当てて,Polygon Collider 2DでSpriteの外枠をなぞったコライダーを生成する.
Prefab構成はこんな感じ.
f:id:lambda410:20210426222841p:plain
f:id:lambda410:20210426223046p:plain

生成アルゴリズム

1.Polygon Collider 2Dで作成したコライダー頂点を取得.
 -> このとき,外枠をなぞるように並んでいることを前提.
2.取得した頂点の重心を計算.
3.頂点を順番になぞり,三角形を構成.
 -> 隣り合う2点の頂点+重心で1つの面

f:id:lambda410:20210426223801p:plain
アルゴリズムのイメージ

スクリプト

多分スクリプトを見たほうが早い.


実行結果

単純な閉じた図形ならこんな感じでMeshを構成できる.
f:id:lambda410:20210426223933p:plain
f:id:lambda410:20210426224002p:plain
f:id:lambda410:20210426224033p:plain


応用

コライダーをスプライトの形に近似して多角形コライダーを作成する.

f:id:lambda410:20210427222011p:plain
実行前

f:id:lambda410:20210427222023p:plain
実行後


まとめ

Polygon Collider 2Dが順番通りの座標配置で良かった~.
これがバラバラだと座標に順番を割り当てる必要があるので面倒.
複雑な構成の場合は,Convex Hullのアルゴリズムをさらう必要があるね.

参考

凸包アルゴリズム
https://www.jaist.ac.jp/~uehara/course/2014/i481f/pdf/ppt-3.pdf

Unity: Addressable Assets Systemを使う

こんにちはんだこて
ぽかぽか陽気.

UnityでAddressable Assets Systemを使用した時の備忘録です.
ほとんど理解していないので,あまりあてにしないでください.

Unity: Unity 2019.4.24f1 Personal


参考文献

qiita.com


導入

Package Managerからインストール.
f:id:lambda410:20210425211517p:plain


設定

今回はローカルで使用します.
要はResources.Loadの代わりに使用する感じです.
1. Create Addressable Settingをクリック
f:id:lambda410:20210425211808p:plain
2. 読み込む素材をAddressableにする.
3. ラベルを設定する.

f:id:lambda410:20210425212239p:plain
手順2,3の流れ(図の番号順に設定)
4. Play Mode ScriptとBuild設定
Play Mode Script: Use Existing Build
Build: New Build > Default Build Script
5. Build設定
今回はローカル仕様なのでこの設定であってるはず(多分).
f:id:lambda410:20210425213710p:plain


スクリプト

今回は複数Sprite読み込みを実装しました.


動作

Play Mode Scriptの設定で,エディタとビルドで結果が変わる可能性があるので注意してください.
ビルドでの実行をおススメします.
キーボードのABCで画像が変化.


まとめ

一手間かかりますがラベル名で参照でき,いろいろと便利だと思います.

Unity: InputSystemでローカルマルチに対応する

こんにちハム太郎
最近は体に気を使ってゼロカロリーのエナドリを飲んでます.

今回の記事は,Unityで複数コントローラの対応に取り組んだときの備忘録です.
かなり説明を端折っているので注意してください.

Unityバージョン: 2019.4.24f1

参考文献

unity.com
Settings | Package Manager UI websitedocs.unity3d.com
www.youtube.com
この動画のほうがこの記事より遥かにわかりやすいです.


InputSystemの導入

2021年の現在におけるUnity推奨の入力システムです.
Package Managerからインストールします.


設定

Edit > Player > Other Settings > Active Input Handlingの設定をBothにします.
Input System Packageだけでも大丈夫だと思いますが,後方互換を考慮してBothにしました.
(なんかBothにしないとエラーが出たのが理由だったりする.)


InputSystem実装手順

  1. Asset > Create > Input Actionsで.inputactionsファイルを保存
  2. .inputactionsファイルを開いて編集
    今回はパッドとスティックに対応させました.
  3. GameObjectにアタッチ
    動かしたいGameObjectにPlayer InputをAdd Componentします.
  4. Behaviorを登録
    今回はイベント駆動(Invoke Unity Events)にしました.
  5. スクリプトを作成

ローカルマルチ実装手順

  1. プレイヤを召喚するManagerを作成
    スタートボタンでプレイヤを生成するようにしてます.
    (ほぼスクリプトは意味ないです. )


終わりに

もう少しまともなブログにしたいね.

Unity: ScriptableObjectをEditor拡張で作成する

こんにちハローキティ
クリーニング代って意外に高いですね.

Editor拡張の勉強でScriptableObjectを作成するやつを開発した時のメモです.

参考サイト

下記のサイトを参考にしました.
49.233.81.186
qiita.com
ekulabo.com

動機

Unityでキャラクターのステータスをハードコーディングしてたのが辛くなったので,別の方法を探したらScriptableObjectを使うのが良いらしいと記事を見ました.

ただScriptableObjectを作成するだけではつまらないので,パラメータを入力してScriptableObjectを作成するEditor拡張を作成してみました.

完成

こんな感じのを作成しました.
Readボタンでマウスクリック選択されているアセットを読み込む.
また,ホットリロード時にも編集内容を表示するためにJSON形式でパラメータを保存など,便利かもしれない機能を盛り込んでいます.
f:id:lambda410:20210418211709p:plain
f:id:lambda410:20210418220639g:plain

実装

  1. ScriptableObjectを作成
    Assetを作成する.
    Editor拡張でしかSetterを使用できないようにしています.

2. EditorWindowを作成

3. キャラクスクリプトを作成
今回は十字キーで動くPlayerとしました.

簡単な解説

  1. ScriptableObjectを作成
    まず,Assetを作成するScriptableObjectを作成します.
    ScriptableObject側でもAsset作成は可能ですが,今回はEditor拡張側で作成するように実装したのでCreateAssetMenu属性はなく,単純な実装となっています.

  2. EditorWindowを作成
     0. CharacterDataAssetメンバ変数を作成.
      (m_character_data_asset)
     1. EditorWindowを作成します.
      (void Create())
     2. パラメータ設定用GUIを定義します.
      (void OnGUI())
     3. WriteボタンでGUIに入力したパラメータでアセットを作成する.   (void ExportAsset())
     4. ReadボタンでGUIのCharacter Nameまたは選択アセットを読み込む.
      (void ImportAsset())
     5. ホットリロード時に表示を保持するためJSON形式で入力内容を保存.
      (void OnEnable(), void OnDisable())

  3. キャラクスクリプトを作成
    SerializeField属性のCharacterDataAssetに作成したアセットをアタッチしてパラメータを読み込む.

終わりに

今度は.csvを読み込んで.assetを作成するEditor拡張を作成したいかもねぎ.
(.xlsxが読めたら最高だよね?)

UnityでEditor拡張に入門した

こんにち母の日

今回はUnityのEditor拡張に入門したので,備忘録として記事にします.
しっかりした記事ではなく,早見表っぽい感じです.

参考サイト

49.233.81.186
とても詳細に書かれているので,このブログを見るより効果的です.

Editorディレクト

UnityEditorはエディタとランタイムでAPIのすみわけを行っているため,Editor拡張を行う際はEditorフォルダ内にスクリプトを作成すること.

f:id:lambda410:20210416224742p:plain
フォルダ階層

Editorフォルダに含めない場合は,下記のコードで囲う.

#if UNITY_EDITOR
~
#endif

標準Editor拡張

f:id:lambda410:20210416232024p:plain
標準Editor拡張欲張りセット

データ管理

  1. EditorPrefs
     プロジェクトをまたいでデータ保存したいときに使用.
  2. EditorUserSettings.Set/GetConfigValue
     プロジェクトで共有可能なデータを暗号化して保存する.
  3. ScriptableObject(下記に詳細ソースコード)
     プロジェクトで共有可能なデータを保存し,チーム内で共有,大量のデータ保存に適している.(マスタデータやEditor拡張で作成したデータの保存など.)
     以下,簡単な使用手順.
     1. インスタンス
     2. アセットとして保存
  4. SerializedObject
     シリアライズ化(データ構造やオブジェクト状態をUnityが保存して再構成可能な形に変換するプロセス)されたデータを加工したもの.

EditorGUI

f:id:lambda410:20210417163642p:plain
EditorGUI欲張りセット

EditorWindow

 EditorWindowの利用
 1. EditorWindowクラスを継承したクラスを作成
 2. EditorWindowを表示するためのトリガメニューを追加
 3. EditorWindowの表示

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using UnityEditor;

// 1. EditorWindowクラスを継承したクラスを作成
public class EditorWindowSample : EditorWindow
{
    // 2. EditorWindowを表示するためのトリガメニューを追加
    [MenuItem("EditorExtSample/Example2")]
    static void Open()
    {
        // 3. EditorWindowの表示
        var sample_window = CreateInstance<EditorWindowSample>();
        sample_window.Show();
    }
}

 いろいろなEditorWindow
 1. Show
  デフォルト
 2. ShowUtility
  タブウィンドウとして扱えない.
 3. ShowPopup
  ウィンドウタイトルと閉じるボタンがない.
 4. PopupWindow
  Popupの汎用版.
 5. ShowAuxWindow
  タブウィンドウとして扱えない.フォーカスを変えると削除される.
 6. ShowAsDropDown
  画面サイズに収まる位置に移動するPopup.
 7. ScriptableWizard
  何かを作るときに使用する.
 8. PreferenceItem
  Unity Preferencesにメニューを追加する.
 9. IHasCustomMenu
  メニューを追加する.

CustomEditor

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using UnityEditor;

[CustomEditor(typeof(TestPlayer))]
public class TestPlayerInspector : Editor
{
    TestPlayer m_player = null;

    // ゲームオブジェクトがアクティブ時に実行
    private void OnEnable()
    {
        m_player = (TestPlayer)target; // コンポーネント取得
    }

    public override void OnInspectorGUI()
    {
        EditorGUI.BeginChangeCheck();

        var pow = EditorGUILayout.IntSlider("Pow", m_player.Power, 0, 100);

        if (EditorGUI.EndChangeCheck())
        {
            // Undo登録
            Undo.RecordObject(m_player, "Cange Power");
            m_player.Power = pow;
        }
    }

    // プレビュー表示
    public override bool HasPreviewGUI()
    {
        return true;
    }
    // プレビュー名
    public override GUIContent GetPreviewTitle()
    {
        return new GUIContent("XYZ");
    }
    // プレビュー設定
    public override void OnPreviewSettings()
    {
        GUIStyle pre_label = new GUIStyle("preLabel");
        GUIStyle pre_button = new GUIStyle("preButton");

        GUILayout.Label("LABEL", pre_label);
        GUILayout.Button("BUTTON", pre_button);
    }
    // プレビュー表示
    public override void OnPreviewGUI(Rect r, GUIStyle background)
    {
        GUI.Box(r, "Preview");
    }
}