Edited at

UnityでScriptを使い、イベントトリガーの動作が出来るようになるまで


はじめに

趣味でUnityを触ってみているのですが、クリック等のイベントトリガーでの処理が中々想定通りに動かず、仕組みの理解に苦労したので覚書。

こんな風にクリックで画像が変わったり、場面を切り替えたりすることが出来るようになるまでにハマったポイントになります。

out.gif


  • 対象読者:Unity初心者で、Scriptの扱い方がいまいちピンとこない方

  • Unity version: 1029.1.5f.1 Personal

  • OS: Windows 10

  • 今回のケース:2D


Scriptを活用する前に:Unity内のデータ構造はどうなっているのか?

まずは、Unityプログラム内でScriptを扱う為のGameObjectとComponentの説明です。


Sceneの中にGameObjectを置いてキャラクター等を配置

Unityでゲームを作るためには、まずSceneを作ります

File > New SceneでSceneを作成すると、左側にGameObjectを追加出来る枠が出てきます。ここを右クリック or GameObject選択 > 任意のGameObjectを選択して追加することで、Sceneにキャラクター等のGameObjectを追加していくことが出来ます。カメラの位置やキャラクター情報、管理システム用のインスタンスもGameObjectとして扱われます。

なので、データとしてはSceneの中にたくさんのGameObjectが詰まっている構造になります。

GameObject.png


GameObjectの中にComponentを設定して動きを付ける

GameObjectをただ置くだけでもかわいいキャラクター画像を設定したりすることが出来ますが、キャラクターに動きが無いとゲームとしてはちょっと寂しい。

そこで、各GameObjectはComponentと呼ばれる要素を追加することで、ゲームらしい挙動を実現します。

Scene枠内のGameObjectをクリックすると、右側にそのGameObjectと対応するInspector画面が表示され、その中で各Componentを設定することが出来ます。

というわけで、データとしてはGameObjectにはたくさんのComponentが詰まっている構造になります。

Component.png


ScriptはGameObjectのComponentとして好きに追加することが出来る。

先ほどの画像に、「Fox Action Event (Script)」というComponentが見えると思います。こちらは今回使用しているScriptで、このようにComponentとしてGameObjectに追加して使用します。

Add Component > New Scriptを選ぶ、もしくは下に表示されるProject内の適切なフォルダ(Scripts等)で右クリック > Create > C# Scriptを選択すると、このようなScriptが出来ます。

後は中身をガリガリ書いてGameObjectに追加すれば、好きな処理をゲームに組み込むことが出来ます。

using System.Collections;

using System.Collections.Generic;
using UnityEngine;

public class BackgroundCreater: MonoBehaviour
{
void Start()
{
//シーン起動後に勝手に呼ばれる
}

void Update()
{
//フレーム単位で呼ばれる
}
}


システム内部では:Scene⇒GameObject⇒Componentのクラスを毎回生成

Scriptを書く際に注意する必要があるのが、そのクラスのインスタンスがどう扱われるか?です。

マニュアルやComponentに追加したScriptの挙動より、Sceneが起動する度に生成され、Sceneが終わると削除されていました。

UnitySequence.png

また、自身を生成してくれたGameObjectを参照することが出来るというのもポイントです。

*SceneManager.LoadScene();はシーンを切り替えるためのメソッドです。


Scriptを利用してイベントを扱う:クリックでキャラクター画像を切り替える


下準備:カメラとEventSystemの設定

イベントを扱うには、いくつかのGameObjectに対して設定が必要です。まずはカメラにPhysics 2D Raycaster(Script)を追加します。

受けるイベントのMaskが出来ますが、必要なければEverythingを選択。

CameraSetting.png

これでイベントが検知できるようになりました。次にEventSystemというGameObjectを追加し、GameObjectまでイベントが届くようにします。


GameObjectのイベント処理

準備が終わったら後は各GameObjectでEventを扱う為の設定をします。



  • collider componentを追加して当たり判定を付ける。(画像ではBox collider 2Dを設定)


  • Event Triggerで、受けたいイベントとその際に呼ぶメソッドを指定する。

今回はFox Action EventというScript内、FoxClickメソッドを呼ぶようにします。

event_setting.png


イベント処理のサンプル

切り替える画像はSprite Rendererで制御するので、ScriptではSpriteRendererを書き換えるようにしています。

また、Script内のpublic変数でSpriteを指定すると、それぞれの画像を指定することが出来ます。


FoxActionEvent.cs

using UnityEngine;

using System.Collections.Generic;

public class FoxActionEvent : MonoBehaviour
{
//書き換えるSpriteRenderer用の変数
SpriteRenderer MainSpriteRenderer;
//Sprite切り替え用のDictionary
public Dictionary<int, Sprite> SpriteDictionary = null;
//今どっちを表示しているかのstate
public static int state = 0;

//表示Spriteの本体
public Sprite foxIdle;
public Sprite foxSurprised;

//最初に呼ばれるメソッドで初期化
private void Start()
{
//MonoBehaviour.gameObjectでcomponentを持っているGameObjectを参照できる。
MainSpriteRenderer = gameObject.GetComponent<SpriteRenderer>();
FoxActionEvent.SpriteDictionary = new Dictionary<int, Sprite>();
FoxActionEvent.SpriteDictionary[0] = foxIdle;
FoxActionEvent.SpriteDictionary[1] = foxSurprised;
//Spriteの書き換え
setCurrentSprite();
}

public void FoxClick()
{
Debug.Log("FoxClick " + this + "\n" + count);
//stateを変えて
goToNextState();
//Spriteの書き換え
setCurrentSprite();
}

private void goToNextState()
{
state = (state + 1) % 2;
}

private void setCurrentSprite()
{
MainSpriteRenderer.sprite = FoxActionEvent.SpriteDictionary[state];
}
}


また、上記をシーケンスに直すとこんな感じです。FoxActionEventをComponentとして持つGameObjectのfoxが、Scriptから参照できます。

(上のコードだとstartでとってるけど)

FoxSequence.png


データ構造の話から見るハマりポイント

上記でイベントの説明は終わりなんですが、データ構造の話と何が関係してるか?以下2点が関係してきます。


  • シーンが起動する度に生成され、シーンが終わると削除

  • 自身を生成してくれたGameObjectを参照することが出来る


Sceneが起動する度に生成され、Sceneが終わると削除⇒シーン切り替え時の状態を覚える工夫がいる

気付いてる方は多いと思いますが、毎回Sceneを切り替える度にインスタンスをdelete/newするので、Sceneを跨いでも必要なデータはstaticにする等で消えないようにする必要があります。

DontDestroyOnLoadといったGameObjectを別Sceneに受け渡すメソッドもありますが、今度はscene開始時のnewで余分にインスタンスを作らないよう気を付けないといけないです。

[Unity]DontDestroyOnLoadでオブジェクトが増えていく

じゃあとりあえずシングルトンにするか~ってやりすぎるとそれもメモリを食っちゃうので、最小限で収まるよう気を付けたいところですね。


自身を生成してくれたGameObjectを参照することが出来る⇒参照されるのはEventTriggerを持っているGameObjectではない

イベント処理の話で書いてたEvent Trigger、実はPublicなメソッドであれば、自分のComponentでないScriptのものでも呼ぶことが出来ます

これはこれで便利なんですが注意点。その際のgameObjectはScriptをcomponentに持つGameObjectで、EventTriggerをcomponentに持つGameObjectではないです。

ScriptはGameObjectAに設定し、EventTriggerをGameObjectBに設定してScriptのメソッドを呼ぶと、Script内のgameObjectはGameObjectAの物になります。

そのgameObjectに対してSpriteをいくら書き換えても、GameObjectBのSpriteが変更されることはないので注意です。


最後に

新しいことをはじめる際は、結構完全に理解したまで行くのにも躓くポイントが出てきますよね。

そこを越えて必要な情報が腹落ちしてくれば楽しみが増えてきますね!


「完全に理解した」

製品を利用をするためのチュートリアルを完了できたという意味。

「なにもわからない」

製品が本質的に抱える問題に直面するほど熟知が進んだという意味。

「チョットデキル」

同じ製品を自分でも1から作れるという意味。または開発者本人。



参考

起動周り(awake書いてないけど)

UnityのメソッドAwakeとStartの違い

イベント

Unity(3D・2D) EventSystemでクリックイベントの制御

Sprite切り替えの参考諸々

Unity Spriteの切り替え

【Unity】 Unity2D-スプライト画像をスクリプトから切り替える方法

Unity オブジェクトの画像(Sprite)を動的に変更する方法

Sprite指定の参考

UnityのInspectorで変数を表示する方法まとめ

画面切り替えでデータを残す

Unityでシーンを移動した時のデータやゲームオブジェクトの引き継ぎ

[Unity]DontDestroyOnLoadでオブジェクトが増えていく

使ったAsset

Sunny Land