寒波襲来
天気予報の「午前中は晴れますが午後からは景色が一変します」というアナウンスを余り信じていなかったけれど午後になると天気予報通りに景色が一変して天気予報のお姉さんに心の中で土下座した皆さんこんにちは。窓の外が一気に真っ白になって、あっという間に雪が積もっていきました。雪国以外では数センチの積雪でも交通機関は麻痺するので、こういう強烈な寒波は台風と同じレベルの脅威ですね。
忘れられたゲーム
エルデンリングが発売されてもう少しで一年経ちます。このゲームはそのクオリティもさることながら、ボリュームも一般のオープンワールドゲームを凌駕するほど膨大だったため、発売されてから数か月間はゲーム関連の各種雑誌やYoutube等々はエルデンリング一色となっていました。
この為、その間に発売されたゲームは例え出来が良いものでも余り話題に上らず、そのまま忘れられていく運命となったものが数多くありました。
『忘れられた都市 - The Forgotten City』もそうした不幸な運命を辿ったゲームの一つだと言えます。
元ネタはスカイリムの有名なMODであまりに好評だった為、独立したゲームとしてリメイク仕直したというこのゲーム。
ジャンルは一人称視点のアドベンチャーゲームで、探索や謎解き、キャラクターとの会話がメインですが、一部アクションゲームの要素もあります。
古代ローマ時代の小さな都市に迷い込んだ現代人が、なんとか元の時代へ戻る方法を探そうとしますが、その都市は「黄金律」と呼ばれる恐ろしい規律によって縛られた閉鎖都市で、その呪縛を解かない限りに現代へは戻ることができない、主人公はこの「黄金律」の規律を侵さずに、その呪縛の謎へ迫っていく・・・といったストーリー。
このミステリーに対する謎解きに加えて、「Outer Wilds」や「Twelve Minutes」のようなタイムリープの要素も加わって非常にやりごたえのあるゲームとなっています。
マルチエンディング形式で世界的な脚本家組合賞も受賞したストーリーは本当に秀逸です。
大団円の真エンディングはご都合主義ながら全ての謎が解け、スッキリと晴れやかな気持ちになると思います。初見の人は、先ずは何も見ずに遊んで欲しいゲームです。
Action型
ゲームに限らず色々なシステム開発で、とあるクラスの処理終了を待ってから処理を行いたい、というケースはよく直面しますが、UnityではそういうケースではDelegateを使用することが多いです。
Delegateは一言でいうと「メソッドを代入できる」型です。
「メソッドを代入できる」とは正確には関数が保存されているアドレスを指すポインタを保持する、という意味で、要はこれを使えばコールバック(Callback)という仕組みが実現できるようになります。
例えば下の図のようなカウントタイマーがあり、その終了によって右側の文字盤の文字を変更したい場合、
カウントタイマー側のクラスにdelegate型の変数を定義します。
public class CountDownTimer : MonoBehaviour { public delegate void OnCompleteDelegate(); // delegate 型の宣言 public OnCompleteDelegate CompleteHandler; // delegate 型の変数を宣言 public void RegistDelegateFunc(OnCompleteDelegate argCall) { CompleteHandler += argCall; // メソッドを代入 } private void Complete_CountDown() { CompleteHandler?.Inovke(); // コールバックを実行 } }
文字盤側のクラスではCountDownTimerクラスのRegistDelegateFuncにてカウント終了のコールバックを受け取る関数を指定することで、カウントタイマーの終了時に文字盤の文字変更の処理を行うことができます。
public class WordBoard : MonoBehaviour { public CountDownTimer _timerCtl; void Start() { // コールバックを指定する _timerCtl.RegistDelegateFunc(()=>CallBack_Timer()); } public void CallBack_Timer() { // 文字盤の文字を変える } }
上記の仕組みを実装して動作させた結果が以下の動画となります。
Action型(System.Action)とはこのDelegate型から派生した型の一つになります。
Action型の使い方
Action型(System.Action)はDelegate型から派生した型であるため、Delegate型と同様の使い方で使用しますが、Action型はDelegate型とは違い、型宣言を行う必要がありません。型の定義と変数宣言を一度に行うことができます。
なので、先ほどのCountDownTimerクラスのソースはAction型を使うと以下のように変更する事が出来ます。
public Action OnCompleteAction; // Action型の変数宣言 public void RegistActionFunc(Action argCall) { OnCompleteAction += argCall; // メソッドを代入 } private void Complete_CountDown() { OnCompleteAction?.Inovke(); }
最初の二行が一行になっていることがわかると思います。型宣言がないため、Actionでは戻り値を定義できません。引数はAction<string, int, ....>のように<> の中に型を列挙することで指定することができます。
Action型の仕様を利用する
例えば先程のカウントタイマーと文字盤のシステムで
上の動画のようにタイマーの文字盤が変わると「少し遅れて」背景の色を変更する、という処理を行いたい場合、通常はカウントタイマーの文字盤が変わったタイミングでBackgroundColorを変える処理をInvokeを使って呼び出します。
public class CountDownTimer : MonoBehaviour { List<Color> _skyboxColorList; // 背景色のリスト int _colorIndex = 0; // リストのIndex // 文字盤が変わったときに呼ばれる関数 public void ChangedTimer() { // BackgroundColorを変える関数を0.2秒後に呼び出す this.Invoke("ChangeSkyColor", 0.2f); } // BackgroundColorを変える private void ChangeSkyColor() { _colorIndex++; Camera.main.backgroundColor = _skyboxColorList[_colorIndex]; } }
Invokeは実行する関数を関数名(string型)で指定するので、必ず関数定義が必要となり、その為、2,3行程度の短い処理でも関数化する必要がありました。
前述のようにAction型では変数宣言のみで使用することができるため、この仕様を利用すると「Actionデリゲート → ラムダ式」のように間接的な関係を作って関数名指定の箇所をラムダ式で記述することができます。
this.Invoke(new Action(() => { _colorIndex++; Camera.main.backgroundColor = _skyboxColorList[ColorIndex]; }).Method.Name, 0.2f);
まぁ、コーディングの作業量が減る程度の効果しかありませんがTipsとして知っておけば自慢できる・・・かも。