農工大アドカレ6日目、当日生成記事でございます!
内容は薄め、C#初心者向けだよ!
イベントとは
C# でイベントはこんな感じのやつだよ!
using System;
public class Example
{
public event Action OnEventTriggered;
public void TriggerEvent()
{
OnEventTriggered?.Invoke();
}
}
OnEventTriggered
に処理を登録しておくと、Invoke()
でイベント発火されたときに処理を実行できる!あとでもうちょっとちゃんと説明するよ!
イベントを使うと何が嬉しいのか
一言でいうと、コードが綺麗になるよ!
イベントを使わない場合
ゲームの中で状態を遷移させたいことがあるよね?例えば、待機状態からプレイ状態に変えたいとするよ。これに伴ってメニューのUIも変化するとしよう。こんな風にステートが定義されている。
public enum GameState
{
Waiting,
Playing,
}
GameStateManager クラスからステートを変えられるようにしよう。
using System;
public class GameStateManager
{
private MenuPresenter menuPresenter;
public void ChangeGameState(GameState gameState)
{
switch (gameState)
{
case GameState.Waiting:
break;
case GameState.Playing:
// プレイ状態になるときの処理
menu.toPlaying();
break;
}
}
}
一見問題なさそうだけど、GameStateManager
が MenuPresenter
のメソッドを直接呼び出しているよね?これはよくないよ!なぜなら、
- メニュー以外にも UI がある場合、
GameStateManager
はどんどん肥大化していく - このままだと、
MenuPresenter
が変わったらGameStateManager
も変えないといけない - これは 「
GameStateManager
がMenuPresenter
に依存している状態」だよ
イベントを使う場合
まずは GameStateManager
に OnGameStateChanged
というイベントを追加するよ。ステートを変更するときはこのイベントを発火するだけ!
using System;
public class GameStateManager
{
public event Action<GameState> OnGameStateChanged;
public void ChangeGameState(GameState gameState)
{
OnGameStateChanged?.Invoke(gameState);
}
}
MenuPresenter
はこのイベントをサブスクライブするよ!上の OnGameStateChanged.Invoke(gameState)
が呼ばれると、
OnGameStateChanged
に登録されているメソッドが呼ばれるよ!+=
で登録、-=
で解除するよ!-=
を忘れるとメモリリークする可能性があるから気をつけてね!
using System;
public class MenuPresenter: MonoBehaviour
{
private GameStateManager gameStateManager; // DI などで注入すると良い(今回は省略)
private void Start()
{
// シーン開始時にイベントに登録
gameStateManager.OnGameStateChanged += OnGameStateChanged;
}
private void OnDestroy()
{
// シーン終了時にイベントから解除
gameStateManager.OnGameStateChanged -= OnGameStateChanged;
}
public void toPlaying()
{
// プレイ状態になるときのUI処理。
}
private void OnGameStateChanged(GameState gameState)
{
switch (gameState)
{
case GameState.Waiting:
break;
case GameState.Playing:
toPlaying();
break;
}
}
}
これで、GameStateManager
は MenuPresenter
に依存していないよ!だから、
-
MenuPresenter
が変わっても、GameStateManager
は変更しなくていい - メニュー以外の UI があっても、
GameStateManager
は変更しなくていいし、肥大化しない
ちなみに、IGameStateManager
というインターフェースを作ってあげると、MenuPresenter
と GameStateManager
の依存関係をさらに緩和できるよ!
using System;
public interface IGameStateManager
{
event Action<GameState> OnGameStateChanged { get; }
}
R3 を用いる
R3はC#でリアクティブプログラミングを行うためのライブラリだよ!R3を使うと、イベントを使ったコードをさらに綺麗に書くことができるよ!
上記の例を R3 を使って書くとこんな風に書けるよ!
using System;
using R3;
public class MenuPresenter: MonoBehaviour
{
private GameStateManager gameStateManager; // DI などで注入すると良い(今回は省略)
private void Start()
{
Observable.FromEvent<GameState>(h => gameStateManager.OnGameStateChanged += h, h => gameStateManager.OnGameStateChanged -= h)
.Where(gameState => gameState == GameState.Playing)
.Subscribe(_ => toPlaying());
.AddTo(this); // このコンポーネントが破棄されたら購読を解除する
}
public void toPlaying()
{
// プレイ状態になるときのUI処理。
}
}
なんかスッキリしたね!Where
でフィルタリング、Subscribe
で購読、AddTo
で購読解除を自動化しているよ!OnDestroy
での購読解除を忘れる心配もなくなった!
R3は普通のイベントだけでなく、Unity の Update
やインターバルなど色々なものをリアクティブに扱うことができるよ!ぜひ使ってみてね!
まとめ
自信ないので何か間違ったことを言っていたら教えてください!