前回はシーンの切り替えを作成しました。今回はゲームマネージャーを作ってゲーム全体の制御をしていきたいと思います。わからない、うまくいかない事があったら質問される前に、一回、動画の方で手順を確認してください
この記事は本のように順を追って解説しています。この記事は途中のページになります。
この記事を見ていて、現在の状況がわからない場合や忘れてしまった事などが出てきたら↓のリンクから目次ページへ飛べますので立ち戻って見てください。
<ゲームを統括する物を作ろう>
Game Managerとは
さて、基本的なところがそこそこできてきたところなので、ゲームを統括するゲームマネージャーを作りましょう。
ゲームマネージャーというのはゲームの進行管理や全体的なパラメーターを保持するクラスです。
別にUnityにGame Managerという機能があるわけではなく、自分自身で作るものです。
例えば今現在作っている2Dアクションゲームであったら、現在のステージの進行状況だったり、コンティニュー位置だったり、セーブデータを取り扱ったり、スコアを保持していたりするものです。
結局自分で作るものなので、お好きな機能を乗っければいいのですが、基本的にゲームを管理し、全シーンに存在するただ一つのものであることが多いです。
まぁ、別に必ず作らなければいけないものではないですが、あるととても便利なので作ることをオススメします。
<静的な(Static)とは>
さて、ゲームマネージャー、すなわち全シーンに存在するものを作成する場合、Staticにするといいと思います。
今まで度々Staticというものが出てきましたが、これは静的なという意味になります。
静的なとは何かというと「動かないもの」です。
プログラム上で「動かないもの」というのはメモリを確保したらその領域はもう変わらないものになります。
領域が確保されてしまえば、例え中身が無くなっても破棄されません。(中に入っているものが無くなっても箱だけは残るような感じ)
要はプログラム上で「動かない」とは、メモリを確保したり破棄したりしてクルクル動くものではなく、メモリを確保したらそこはもう変わらないということです。
よって、プログラム上でStaticにし、一度でもメモリが確保されると常に存在するものになります。(メモリを確保しているだけなので、存在するが、中身がNullの場合もあります)
常に変わらないメモリ領域に存在する為、プログラム上のStaticは他のプログラムから非常にアクセスしやすいものになります。
今までは、別のプログラムにアクセスしようと思ったら、そのプログラムのインスタンスを捕まえてからアクセスしていましたが、最初からメモリ上に存在する場所が確定しているのでインスタンスを捕まえなくてもアクセスできます。
あと予めメモリを用意しておく為ちょっと軽いです。
以上の特徴から、色々なオブジェクトからアクセスされ、全シーンに存在するゲームマネージャーとは非常に相性のいいものになります。
<Singleton>
シングルトンとは
さて、ゲームマネージャーを作っていく上で、作り方としてはシングルトンにするといいと思います。
たまーにオンラインゲームとかしていると「シングルトン」みたいな名前の人を見かけたりする事があって言葉だけ聞いた事がある人もいると思いますが、これはC#の機能でもUnityの機能でもなく「プログラミングの手法」になります。
基本的にC#は設計図(クラス)を作ってそれを元に実体(インタンス)を作っていました。設計図さえあれば、実体はいくつでも作成する事が可能でした。
さて、ゲームを作る上でいくつも存在されては困るものがあります。
例えば、現在のステージの進行状況だったり、コンティニュー位置だったり、セーブデータだったり、スコアだったり。
これらの機能をゲームマネージャーに載せた場合、ゲームマネージャーはいくつも存在されると困るのです。
その為、ゲームマネージャーを「絶対に1つしか存在しないもの」にしたいです。
この、「絶対に1つしか存在しないもの」を定義するやり方をシングルトンと言います。
もっと詳しく言うと「クラスのインスタンスが絶対に1つしか存在しない設計」の事をシングルトンと言います。
ここまで解説しといて何ですが、個人開発でわざわざシングルトンにする必要はあまり無いのかもしれません。自分が「これは絶対に1個しか作らないぞ」と心に決めておけば間違う事は無いので。
まぁ、でもそんなに手間がかかるものでも無いので、一応シングルトンにしておくといいと思います。ゲームマネージャーらしくただ一つしか存在しない事が保証されるので、ちょっとプロっぽい感じになれます。
<GameMangerの作り方>
Awakeを使おう
さて、ではシングルトンの設計でゲームマネージャーを作っていこうと思います。
スクリプトの名前をGameManagerにしたいところなんですが、Unityのバグ?なのか演出なのかわかりませんが、スクリプトの名前をGameManagerにするとスクリプトのアイコンが歯車になります。
んー。なんか怖いので別の名前にしましょう。多分問題はないと思いますが、Unity側が意図した挙動でも無いと思うので触らぬ神に祟りなしです。裏で何かがこのスクリプトを捕まえているはずなのでどんな影響が出るかわかりません。
自分はGManagerと言う名前にしました。
さて、今まで最初にする処理はStartに書いていましたが、実はStartより前に処理されるメソッドが存在します。
Awakeと言います。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class GManager : MonoBehaviour { private void Awake() { } }
↑のようにAwakeという名前でメソッドを書くと、それは特別なメソッドになります。Startとよく似ていてUnity側から呼ばれます。
これはインスタンス化された瞬間に呼ばれるメソッドで、主に初期化などを行うのに使われます。
詳しい事は↓の記事にて検証していますので、興味がある方はみてみてください。
シングルトンでゲームマネージャーを書こう
Awakeの中にシングルトンの設計になるように処理を書いていきます。
ゲームマネージャーはゲーム中ただ1つのものである事を保証したい為、シングルトンである方がよく、Awakeは「インスタンス化した瞬間に呼ばれる」という特徴から、「ただ1つのインスタンス」を可能にする為にちょうどいいタイミングで呼ばれるからです。
Unityでのシングルトンの基本形は↓のようになります。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class GManager : MonoBehaviour { public static GManager instance = null; private void Awake() { if(instance == null) { instance = this; DontDestroyOnLoad(this.gameObject); } else { Destroy(this.gameObject); } } }
さて、順番に見ていきましょう。
public static GManager instance = null;
publicの後にstaticとついています。これで最初からメモリに領域が確保された状態になります。
ゲームマネージャーは多くのスクリプトからアクセスされる事が想定されるため、staticにした方がいいのです。
本来ならこの書き方はお行儀がよくないんですが、今回はチュートリアルという事でスルーします。チーム開発をされる方は、ちゃんとgetなどで読み取り専用にしてください。
今まではGetComponentやGameObject.Findなどでインスタンスを捕まえてからアクセスしていましたが、staticにすれば
GManager.instance.○○○
という風に書く事でどこからでもアクセスできるようになります。
これは最初からメモリ領域が確保されている為、そこにある事が確定しているからできる事です。インスタンスをいちいち捕まえに行かなくてもGManager.instanceと書くだけでインスタンスにアクセスする事ができます。
次にAwakeの中身です。
private void Awake() { if(instance == null) { instance = this; DontDestroyOnLoad(this.gameObject); } else { Destroy(this.gameObject); } }
この部分がシングルトンのキモになります。
staticな変数instanceはメモリ領域は確保されていますが、初回では中身が入っていないので、中身を入れます。
if(instance == null) { instance = this; DontDestroyOnLoad(this.gameObject); }
thisというのは自分自身のインスタンスという意味になります。この場合、GManagerのインスタンスという意味になります。
つまり、最初から確保されたメモリ領域に自分自身を入れることになります。
DontDestroyOnLoad(this.gameObject);
これは、シーンに切り替え時に破棄されない状態にする命令です。
いくらインスタンスを入れても、次のシーンへ行った瞬間、シーンごと削除されてしまってはNullの状態に戻ってしまうので消されないようにする必要があります。
this.gameObjectというのは「このスクリプトがくっついているゲームオブジェクト」という意味になります。
このスクリプトがくっついているゲームオブジェクトをシーン移行時削除しないようにする意味があります。
一番最初に開かれるシーン(このサイトの場合タイトルシーン)にカラのゲームオブジェクトを作成し、適当にGameManagerなどわかりやすい名前をつけてください。
そのゲームオブジェクトにGManagerをセットし、再生すると
このような感じでDontDestroyOnLoadに指定されたゲームオブジェクトは別のシーンに隔離され、ここに入っているものはシーン切り替え時に破棄されません。
そして、
else { Destroy(this.gameObject); }
中身がすでに入っていた場合、自身のインスタンスがくっついているゲームオブジェクトを破棄します。
こうすることで、「ただ1つの」という状態を保証できるようになります。
後は、このゲームマネージャーをプレハブにして全シーンに置いていきましょう。
本来ならゲームマネージャーを1番最初にスタートするシーンに置いておけばいいのですが、そうすると、開発中も1番最初にスタートするシーンから再生しなければいけなくなります。これは非常にめんどくさいので、プレハブにして全シーンに置くことで、どのシーンからスタートしても大丈夫な状態にします。
プレハブを忘れてしまった方は↓の記事を参考にしてください。
全シーンに置いておくと、前のシーンから次のシーンへ行くときにゲームマネージャーが2つ存在することになるのですが、既にゲームマネージャーが存在する場合
Destory(this.gameObject);
で、後から出てきたゲームマネージャーは削除されるので問題ありません。Destoryするものをthisではなく、this.gameObjectとしているのはこのためですね。
ちょっと無駄な処理が挟まってしまいますが、開発をスムーズにするためこのような形にしています。
あとは、ゲームマネージャーに全体で使うであろうパラメーターを書いていけばOKになります。
<ゲーム全体で使うパラメータを管理しよう>
スコアを加算しよう
さて、それではゲームマネージャーを活用して、様々なパラメーターを追加していきましょう。
まずはスコアを追加してみようと思います。
例えば、敵を倒したらスコアをプラス10にしたかった場合
↓ゲームマネージャー側
using System.Collections; using System.Collections.Generic; using UnityEngine; public class GManager : MonoBehaviour { public static GManager instance = null; public int score; //New! private void Awake() { if(instance == null) { instance = this; DontDestroyOnLoad(this.gameObject); } else { Destroy(this.gameObject); } } }
↓敵側
if(倒されたら) { GManager.instance.score += 10; }
こんな感じにすれば実装可能です。簡単ですね。
シングルトンなのでいちいちGetComponentしなくてもアクセスすることができます。
この数値をUGUIのTextなどに渡してあげれば画面にスコアを表示する事ができます。この辺りは次回で実際にやってみようと思います。
まぁ、本来そのままpublicで公開せずにカプセル化するのが一般的ですけどチュートリアルなので今はこれでいきましょう。より丁寧にするならget setを使うといいと思います。get setの解説はまた機会があればしようと思います。
ステージを管理しよう
ステージ管理も簡単で、同じように
public int stageNum; public int continueNum;
こうしてあげて、ステージをクリアーしたらstageNumを変えてあげましょう。コンティニューポイントを通過したらcontinueNumを変えてあげればシーン全体で使用したい情報を簡単に取り扱う事ができます。
あとはシーンをロードする際にこの数字をみてどのシーンをロードするのか、どのポイントから始めるのかを他のスクリプトでこの値を拾って決めればOKです。
<まとめ>
今回制作したスクリプトは↓の通りです。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class GManager : MonoBehaviour { public static GManager instance = null;public int score;
public int stageNum;
public int continueNum;
private void Awake()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(this.gameObject);
}
else
{
Destroy(this.gameObject);
}
}
}
とりあえずゲームマネージャーの基本形ができたのでここから先、全体的な機能を作るときはこいつに追加してく形になります。
何かうまくいかない事があった場合は↓の記事を参考にしてみてください
最低限↓の動画の要件を満たしていない質問は受けかねるので、ご理解ください。
また、筆者も間違えることはありますので、何か間違っている点などありましたら、動画コメント欄にでも書いていただけるとありがたいです。