見出し画像

初心者がUnityでゲーム作る Day10 ~ 石削除と大整理

Day10まとめ

  • 石が消えるようになった

  • 所持アイテム欄のリファクタ完了

石をクリックで消す

マウスonでアウトラインをつけたい

これからドロップアイテムを拾えるようにするが、
どれを拾おうとしたのかわかるようにするため、マウスオーバーしたときだけアウトラインが出るようにしたい。


SpriteRendererにアウトラインを設定する

このシェーダーを使ってアウトラインを作る。
アウトライン専用のマテリアルを作成し、それをSpriteRendererのマテリアル欄にアタッチする。

ドロップアイテムの石が光りだした

SpriteRendererの制御方法を確かめる

まずはアウトラインが出し入れできる状態を作る。
アプローチとしては以下が考えられそうだ。

  • ドロップアイテムに設定されているマテリアル自体を切り替える

  • マテリアル自体のシェーダーを変更する

ドロップアイテムは大量に複製されることになるので、
マテリアル自体のシェーダーを変更してしまうと全ドロップアイテムが光り出したりしそうだ。
今回はドロップアイテムに設定されているマテリアル自体が切り替わるように設定しよう。

SpriteRendererの触り方を確かめる

公式ドキュメントを読みながらスクリプトでの制御を試みる。

GetComponen<SpriteRenderer>でマテリアルを取ってきて変数に入れっぱなしにしておこうと思ったが、メモリリークの原因になるらしい。

こりゃダメそうだと思ったので別の方法を調べてみる。

マテリアル配列を作って切り替えられるようにする

まずはコードをまるっとコピペ。
作例ではMeshRendererでやってるのでSpriteRendererに置き換える。

このやり方が正解です! って感じする

続いてUpdateでの監視をやめてマウスオーバーで切り替えにする。

切り替えのクラス

public class MaterialSwitcher : MonoBehaviour
{
    [SerializeField]
    Material[] materialArray = new Material[2];
    private int defaltMat = 0;
    private int outlineMat = 1;

    public void SetDefaultMaterial()
    {
        GetComponent<SpriteRenderer>().material = materialArray[defaltMat];
    }

    public void SetOutlineMaterial()
    {
        GetComponent<SpriteRenderer>().material = materialArray[outlineMat];
    }
}

マウスオーバーの監視クラス

public class DropItemPickUpper : MonoBehaviour
{
    // マテリアル変更クラスとカーソル変更クラスをインスタンス化
    public MaterialSwitcher materialSwitcher;
    GameObject obj;

    void start()
    {
        obj = gameObject;
    }

    // マウスオーバー、アウトの挙動
    // マウスオーバーした瞬間に発火し、Materialをアウトラインつきに変更
    private void OnMouseEnter()
    {
        materialSwitcher.SetOutlineMaterial(obj);
        print($"オブジェクト {gameObject.name} をアウトラインマテリアルにセット");
    }

    // マウスオーバー解除:Materialを戻す
    private void OnMouseExit()
    {
        materialSwitcher.SetDefaultMaterial(obj);
        print($"オブジェクト {gameObject.name} をデフォルトマテリアルにセット");
    }
}

とこんな感じに用意したのだが、問題発生。

prefabにゲームオブジェクトがアタッチできないため、クラスを分けて呼び出す方法がわからない。(Type mismatchと出てくる)

えーん

一旦クラス分けをあきらめる。

まあできたからととりあえずヨシ

クリックで破壊

これも本当は別クラスから破壊の関数だけ引っ張ってくる予定だったんだが、DropItemPickUpperの中に書いちゃうことにする。

using UnityEngine.EventSystems;

public class DropItemPickUpper : MonoBehaviour, IPointerClickHandler    

    public void OnPointerClick(PointerEventData eventData)
    {
        print($"エンティティ {gameObject.name} がクリックにより消滅");
        Destroy(gameObject);
    }

岩を殴るのと同じくOnPointerClickを使用する。
UnityEngine.EventSystemsライブラリとIPointerClickHandlerクラスの指定が必要。

消えるようになった

クソ長コードのリファクタ

ここから過去のブラックボックス解体。
ItemBoxManagerというスクリプトがある。
こいつが300行を超えているので、与しやすいところから順にクラスを分割していく。

全体構造の把握

ざっと数えた感じ、関数が11個入っている。よくないと思う。
やっていることは以下のような感じらしい。

アイテム欄オブジェクト本体

  • 4オブジェクトで構成される

    • 背景

    • 所持アイテム数

    • アイテムのスプライト

    • 選択中の時に出すハイライト

アイテムの管理

  • id、sprite、アイテム種別をDictionaryで持っている

  • これはこれで別日に解体した方がよさそうな気配がしている

スクリプト

  • 選択中ハイライトオブジェクトをSetActiveする関数

  • 選択中ハイライトオブジェクトをSetActive(false)する関数

  • カーソル移動のSEを鳴らす関数

  • アイテム欄の更新関数

    • アイテムのidに応じてSpriteを更新する関数

    • アイテム数の更新関数

  • Start(クソ長い)

    • ボックスに登録されているアイテム情報の初期化

    • for文でアイテム欄1個目から順に以下を実行

      • ゲームオブジェクトのInstantiate

        • 整列はオブジェクトにアタッチされた「HorizontalLayoutGroup」により自動で行われる

      • 各アイテム欄に無を登録

      • オブジェクトをいい感じにリネーム

      • 更新処理用に各コンポーネントを辞書登録

      • 初期アイテムをハードコードで宣言

      • 初期アイテムの付与

      • アイテム欄の表示を更新

      • アイテム欄のGameObjectをDictionaryへ登録

      • アイテム欄オブジェクトのアクティベート

    • 初期カーソル位置をアイテム欄1にセット

  • アイテム欄の移動を開始しましたという関数

  • アイテム欄の移動を完了しましたという関数

  • アイテム欄へのアイテム追加の関数(長い)

    • idで持ってるアイテムを検索

    • 一番左端にあるカラのボックスのBoxIDを取得

      • もう持っているアイテムはそこにスタック

        • 表示更新

      • 持っていないアイテムは最初のカラのboxに入れる

        • アイテム情報を新規に登録

        • 表示更新

      • アイテム欄いっぱいで追加できなかったらfalseを返す

      • 追加できてたらtrueを返す

  • Update

    • アイテム欄の左右移動の処理

      • キー入力を受け取る

        • 前の選択ハイライトオブジェクトを非アクティブに

        • 選択されているBoxIDを変更

        • 最後尾とか最初にいたら逆側にループするようにする

分割のプランニング

https://www.slideshare.net/slideshow/unityzenject/82769092#37

これのp86~「Facade」パターンを使うのがよさそう。
大量に関数が絡み合うことになり、かつ今後参照が色々発生しそうな場所のため。

クラスの切り分け

  • ItemBoxDataBase

    • Dictionaryの管理全般

    • 各ボックスに入っているアイテムの情報を取得・管理する

    • あらゆる関数から読み出されることになる

  • ItemBoxViewRefresher

    • 選択中ハイライトオブジェクトをSetActiveする関数

    • 選択中ハイライトオブジェクトをSetActive(false)する関数

    • アイテムのidに応じてSpriteを更新する関数

  • ItemBoxSoundPlayer

    • カーソル移動のSEを鳴らす関数

    • (アイテム欄にアイテムが入ったときに音を鳴らす新規関数)

      • マイクラ式

  • ItemBoxInitializer

    • Startの処理

      • for文でアイテム欄1個目から順に以下を実行

        • ゲームオブジェクトのInstantiate

          • 整列はオブジェクトにアタッチされた「HorizontalLayoutGroup」により自動で行われる

        • 各アイテム欄に無を登録

        • オブジェクトをいい感じにリネーム

        • 更新処理用に各コンポーネントを辞書登録

        • アイテム欄の表示を更新

        • アイテム欄のGameObjectをDictionaryへ登録

        • アイテム欄オブジェクトのアクティベート

      • 初期カーソル位置をアイテム欄1にセット

  • InitialItemGiver

    • 初期アイテムをハードコードで宣言

    • 初期アイテムの付与

  • ItemBoxInput

    • Update

      • アイテム欄の移動を開始しましたという関数を丸める

      • アイテム欄の移動を完了しましたという関数を丸める

  • ItemBoxDepositter

    • アイテム欄へのアイテム追加の関数(長い)

  • ItemBoxWithdrawer

    • アイテム欄からのアイテム取り出しの関数(新規)

      • Depositterとまとめる可能性ある

もともと9つある関数を7つのクラスに切り分けなおそうという感じ。
依存関係を確認しつつ、順次別クラスに切り出していく。

実装順

上から順。

  • ItemBoxDataBase

    • 膨大なDictionaryの宣言などを全部こいつに任せ、他のクラスに渡す

  • ItemBoxViewRefresher

    • これがないと他のものがほとんど動かない

  • ItemBoxInitializer

    • 初期化しないとだいたい動かせない

  • ItemBoxDepositter

  • InitialItemGiver

    • ItemBoxDepositterの機能を使って作り直す

  • ItemBoxInput

  • ItemBoxSoundPlayer

    • 装飾的用途のため

  • ItemBoxWithdrawer

    • 新規機能のため

ItemDataBaseの初期化は、Awakeで誰よりも早く行う

ItemDataBaseの初期化→Initializerの初期化とならないといけない。
従ってItemDataBaseでは「Awake」を使用してDictionaryの初期化を行う。

初期化をまとめる

今はAwakeとUpdateで処理順を整理できるが、今後どんどんInitialize系のスクリプトが増えると順番を指定しなきゃいけなくなってくる可能性がある。
その場合、Start / Awakeではなく通常の関数として切った上で、初期化用のスクリプトを1つ作ってそこから順番を指定しつつAwakeなりStartで立ち上げる。

Initializerだけを含むスクリプトは実行後破壊する

初期化スクリプトは以降使わないのでDestroyしておくとスッキリしてよいですよとのことだったので

Destroy(this);

を噛ませておく。

差し替え

新規機能となるWithdrawal以外は組み終えたので、スクリプトを差し替えてみる。動くかな。。。

元の状態


ヨッシャー

無事リファクタ成功!
機能も理解できてメンテナンスコストもだいぶ下がったので、以降の実装に問題なく移れそうだ。
つづく!

この記事が気に入ったらサポートをしてみませんか?