なんとなく、某、中2病的な記事を書くかもしれません。
イデア
Unityでは、シーンがあり、それはオブジェクトで構成されています。そして、オブジェクトはゲームにおける「もの」を表現します。ゲームの世界で「存在しているモノ」です。しかし、ゲームは「その世界に存在しているモノ」だけで成りたつものでは無く、むしろ、目に見えない概念があります。たとえば、「得点」や「ルール」「システム」「時」などです。そして、それをここでは「イデア」と呼びましょう。(哲学的な思索は他の人にまかせます)
イデアはどう記述されるか?
さて、ゲームにおいて、イデアを記述するにはどうすれば良いでしょうか? ここでは、ゲームにおいて「得点」を表現することを考えてみましょう。つまり、得点はどこに記述すれば良いのでしょうか?
それにはいくつか方法が考えられるでしょう。先の12/16日のadvent calendarの記事にもあるように
- 得点をゲームオブジェクトとして扱かい、それをFindする方法。
- シングルトン
があると思います。ただし、僕はもう一つあって、
- Scriptableオブジェクトを使う
があるように思えます。さて、ここではそれぞれを考察して行きましょう。そして、最後にScriptableオブジェクトを利用してMVC的なアプローチを考えたいと思います。
得点をゲームオブジェクトとして扱かい、それをFindする方法。
実装
さて、まず、得点オブジェクトを作ります。
さて、このScoreオブジェクトに、次のようなScriptComponentを追加します。
using UnityEngine;
using System.Collections;
public class Score : MonoBehaviour {
private int point;
// Use this for initialization
void Start () {
this.point = 0;
}
public void AddScore(int p){
this.point += p;
}
public void showScore(){
Debug.Log (this.point);
}
}
そうしてできた、Scoreオブジェクトを、Findを使い探せば良いのですね。マウスをクリックしたら、得点することにしましょう。
using UnityEngine;
using System.Collections;
public class PointGetter : MonoBehaviour {
private Score score;
// Use this for initialization
void Start () {
this.score = GameObject.Find("Score").GetComponent<Score>();
}
// Update is called once per frame
void Update(){
if( Input.GetMouseButtonDown(0) ){
this.score.AddScore(1);
this.score.showScore();
}
}
}
考察
さて、この方法は実は僕はあまり好きではありません。その理由は2つあります。
1つめに、Unityのシーンのオブジェクトの階層構造に依存しているということにあります。たとえば、間違って、Scoreオブジェクトをシーンの階層から削除してしまったり、名前を変えてしまったり、名前が重複したりすれば、途端にゲームは例外を吐いて、得点が動かなくなったりするかもれません。
2つめに、そもそも、得点がゲーム内のモノではないにもかかわらず、ゲームのオブジェクトとなっているということです。先程言いましたが、得点はイデアであり「そのゲームの世界のモノ」では無く、概念であるということです。得点をScoreオブジェクトとして、シーンに取り入れることは、得点という概念をモノ化しているということです。そしてそれは、モノであるために余計が情報が付加されており、とても気持ちが悪いということです。得点が座標を持っているなんてキモチワルイと思いませんか?
これが、もし1人か小規模の人数の短期の開発であれば、このようなやりかたは有効でしょう。しかし、もし長期で、多人数で、エンジニア以外(コードを書かない人)がUnityで作業するときには、あまり良くない方法だと思います。
シングルトンを使う方法
実装
得点をシングルトンで表現しましょう。
using UnityEngine;
public class Score {
private int point;
private static Score instance = new Score();
public static Score GetInstance(){
return instance;
}
private Score (){
this.point = 0;
}
public void AddScore(int p){
this.point += p;
}
public void showScore(){
Debug.Log (this.point);
}
}
さて、このオブジェクトにアクセスするには、クラスから辿れば良いですね。
using UnityEngine;
using System.Collections;
public class PointGetter : MonoBehaviour {
private Score score;
// Use this for initialization
void Start () {
this.score = Score.GetInstance();
}
// Update is called once per frame
void Update(){
if( Input.GetMouseButtonDown(0) ){
this.score.AddScore(1);
this.score.showScore();
}
}
}
考察
先程の、Findを使う実装よりかは、マシだと個人的には思います。Unityのシーンの階層構造に依存せずに、得点計算がスクリプトのみで完結しているというのが個人的には好きです。
しかし一方で欠点もあります。それは、シングルトン自体の、問題です。
- 基本的にグローバル変数と似たようなものであり、依存性が分かりづらい。
- 密結合になりやすい
- テストがしにくい
などなど、たぶんここらへんは、オブジェクト指向の設計をやっていれば大抵は理解できるでしょう。
また、そもそもイデアは一つだとは限らないということです。シングルトンパターンは、そのオブジェクトが確実に一つだと分かる時に使うべきです。
Scriptableオブジェクトを使う方法
実装
さて、得点をscriptableオブジェクトにしてみましょう。
using UnityEngine;
public class Score : ScriptableObject {
public int point = 0;
public void AddScore(int p){
this.point += p;
}
public void showScore(){
Debug.Log (this.point);
}
}
さて、次に、Scriptableオブジェクトを生成します。次のコードはassetsに置けば勝手に実行し、スクリプタブルオブジェクトができます。
using UnityEngine;
using UnityEditor;
[InitializeOnLoad]
public class NewBehaviourScript {
static NewBehaviourScript (){
Score item = ScriptableObject.CreateInstance<Score>();
string path = AssetDatabase.GenerateUniqueAssetPath("Assets/" + typeof(Score) + ".asset");
AssetDatabase.CreateAsset(item, path);
AssetDatabase.SaveAssets();
}
}
さて、あとは使いましょう。
using UnityEngine;
using System.Collections;
public class PointGetter : MonoBehaviour {
public Score score;
// Update is called once per frame
void Update(){
if( Input.GetMouseButtonDown(0) ){
this.score.AddScore(1);
this.score.showScore();
}
}
}
そして、ScoreのScriptableオブジェクトを追加しておきます。
考察
うーん、これも微妙な気がします。得点をゲームオブジェクト化するよりかは、余計な情報が無いのは利点ですが、コードのみで完結しません。
おそらく、Scriptableオブジェクトの管理をうまくやればこのようなやり方は一番良いのでは?と思います。
それには、Scriptableオブジェクトを含んだプレハブ化が有効な気がしています。なぜなら、比較的にシーンの状態よりAssetの状態の変更が少なく、プレハブ化によって、Scriptableオブジェクトとコンポーネントの**依存関係が壊れにくいのでは?**とは思います。
MVC的なアプローチ
そもそも、ゲームにおけるイデアはつまり、MVCのモデルに相当するのではないかと勝手に思っています。
とすれば、過去にあるMVCの設計をそのまま、ゲームに持ち込めば良いのではと思います、ので、その実装を考えてみましょう。
実装
まず、モデルクラスを実装してみましょう。
using UnityEngine;
public delegate void UpdateHandler();
public class Score : ScriptableObject {
public int point;
public event UpdateHandler Update;
public Score(){
this.point = 0;
}
public void AddScore(int p){
this.point += p;
this.Update();
}
}
さて、ここでオブザーバーパターンを使っています。なので、delegateで、状態の変更をViewに通知するってことになります。
さてViewを作りましょう。
using UnityEngine;
using System.Collections;
public class PointViewer : MonoBehaviour {
public Score score;
// Use this for initialization
void Start () {
this.score.Update += PointShow;
}
void PointShow(){
Debug.Log (this.score.point);
}
}
一応、コントローラーです。先程の実装から変わってません。
using UnityEngine;
using System.Collections;
public class PointGetter : MonoBehaviour {
public Score score;
// Update is called once per frame
void Update(){
if( Input.GetMouseButtonDown(0) ){
this.score.AddScore(1);
}
}
}
そして最後に、繋げましょう。
考察
これを実装しながら思うには、結局のところ初期化処理をコードでやるか、UnityEditorでやるかの違いがあるように思えました。
まとめ。
なんか上手く行かないにゃあー。