![見出し画像](https://assets.st-note.com/production/uploads/images/143548446/rectangle_large_type_2_33fa137a66587b57e991b117a93a4126.png?width=800)
初心者がUnityでゲーム作る Day10 ~ 石削除と大整理
Day10まとめ
石が消えるようになった
所持アイテム欄のリファクタ完了
石をクリックで消す
マウスonでアウトラインをつけたい
これからドロップアイテムを拾えるようにするが、
どれを拾おうとしたのかわかるようにするため、マウスオーバーしたときだけアウトラインが出るようにしたい。
SpriteRendererにアウトラインを設定する
このシェーダーを使ってアウトラインを作る。
アウトライン専用のマテリアルを作成し、それをSpriteRendererのマテリアル欄にアタッチする。
![](https://assets.st-note.com/img/1717950235185-PDgHN6eKn9.png?width=800)
SpriteRendererの制御方法を確かめる
まずはアウトラインが出し入れできる状態を作る。
アプローチとしては以下が考えられそうだ。
ドロップアイテムに設定されているマテリアル自体を切り替える
マテリアル自体のシェーダーを変更する
ドロップアイテムは大量に複製されることになるので、
マテリアル自体のシェーダーを変更してしまうと全ドロップアイテムが光り出したりしそうだ。
今回はドロップアイテムに設定されているマテリアル自体が切り替わるように設定しよう。
SpriteRendererの触り方を確かめる
公式ドキュメントを読みながらスクリプトでの制御を試みる。
GetComponen<SpriteRenderer>でマテリアルを取ってきて変数に入れっぱなしにしておこうと思ったが、メモリリークの原因になるらしい。
こりゃダメそうだと思ったので別の方法を調べてみる。
マテリアル配列を作って切り替えられるようにする
まずはコードをまるっとコピペ。
作例ではMeshRendererでやってるのでSpriteRendererに置き換える。
![](https://assets.st-note.com/production/uploads/images/143543807/picture_pc_1ee1999b63668e189216a318346b1e27.gif)
続いて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と出てくる)
![](https://assets.st-note.com/img/1717958657771-NbOlafMGR3.png)
一旦クラス分けをあきらめる。
![](https://assets.st-note.com/production/uploads/images/143546152/picture_pc_bf88c96abf32756c953f56fa4b5a51dc.gif)
クリックで破壊
これも本当は別クラスから破壊の関数だけ引っ張ってくる予定だったんだが、DropItemPickUpperの中に書いちゃうことにする。
using UnityEngine.EventSystems;
public class DropItemPickUpper : MonoBehaviour, IPointerClickHandler
public void OnPointerClick(PointerEventData eventData)
{
print($"エンティティ {gameObject.name} がクリックにより消滅");
Destroy(gameObject);
}
岩を殴るのと同じくOnPointerClickを使用する。
UnityEngine.EventSystemsライブラリとIPointerClickHandlerクラスの指定が必要。
![](https://assets.st-note.com/production/uploads/images/143546503/picture_pc_1118f3e9f15234fdf608e9a46140d8e9.gif)
クソ長コードのリファクタ
ここから過去のブラックボックス解体。
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以外は組み終えたので、スクリプトを差し替えてみる。動くかな。。。
![](https://assets.st-note.com/img/1717983469570-CyUKRRccOL.png)
![](https://assets.st-note.com/production/uploads/images/143569523/picture_pc_3d4aba489e1f068c627bea6e06fcd9da.gif)
無事リファクタ成功!
機能も理解できてメンテナンスコストもだいぶ下がったので、以降の実装に問題なく移れそうだ。
つづく!
この記事が気に入ったらサポートをしてみませんか?