Unityでソシャゲを作りたい#8 画面IDを指定するだけで画面遷移する仕組みをパワーで実装する試み

前回までで、画面個々が勝手に動きやがるのでクソうざいという問題が浮き彫りになったので、管理方式を画一化して処理するタイミングを任意にいじれる仕組みを作っていく方針に大転換しました。

目指す構成

まずは画面描画がそれぞれ独自な点を解消すべく、画面全部をID管理する方式を採用したい方向でいろいろ進めました。

最終的にこんな感じで使えたらいいな像です。

画像1

こうしておくと、画面内の要素がそれぞれ勝手にPOPUPを開いたりしなくなるので画面の重複を防げたり、入力処理の後遷移するかの条件を一括で設定出来たり、何かといいことたくさんだと踏んだわけです。

表示制限

こうするために、まずは表示そのものをある程度制限します。

具体的には、

スタート、ホーム、プレイのような全然違う役割の画面で大雑把に区分し、全画面でレイヤー構造にして3枚までの制限を設けます。

ホームの中の、ホーム画面、クエスト一覧画面、ショップ一覧画面、育成画面のような各画面を一番下層のレイヤーに、

ホーム画面のお知らせ一覧、受け取りギフト一覧のような画面内でのPOPUP要素の内さらにそこからPOPUPが必要なものを2層目に、

お知らせそのもののように、見たら終わりだったり、そこからさらにPOPUPが出てこないようなものを最上層に置く

という制約を設けます。

大区分をPhase、各レイヤーをLayer1~3と命名して、そういうものとして管理します。

画像2

イメージとしてはこんな感じです。

実際にはLayer1に入るGameObjectがそのPhaseのGameObjectで、画面IDが変更されるとLayer1に入れてあるゲームオブジェクトにアタッチした個別のスクリプトが動作して自由なムーブで画面移動するってイメージです。

それを踏まえると最初の図から少し詳しくなってこうなります

画像3

一気に意味不明ですが、コード見ればわかるでしょう。

PhaseManager

まずすべての呼び出しの窓口的な存在で、今何の画面を表示してるのかをIDとして保持し、遷移するかしないかとかを判断してくれるManagerが存在します。

この子は適当なGameObjectにでもアタッチしとくと使いやすいんではないでしょうか。

//Phaseそのものの形を一応作っておいて初期値とか決めておく。
//PhaseIDはA-Zの予定だが、初期値で""だとPhaseID[0]でOutOfRange吐くので"0"
//↑この無駄な初期値はPhaseIDも画面IDと一緒に扱っちゃう予定だったが故の名残
public class Phase{
   public string PhaseID;
   public string Layer1ID;
   public string Layer2ID;
   public string Layer3ID;
   public Phase(){
       PhaseID="0";
       Layer1ID="";
       Layer2ID="";
       Layer3ID="";
   }
}

//manager本体
public class PhaseManager : MonoBehaviour{
   public Phase NowPhase; //Phase
   public bool Loading;    //ロード画面の実装に向けた布石

   Manager Manager{ //manager系統をよこせって言ったらくれるだけの人
       get{return GameObject.Find("Manager").GetComponent<Manager>();}
   }
   //もしIDが切り替わってたらじっしあの描画処理を呼ぶ
   public string PhaseID{
       get{return this.NowPhase.PhaseID;}
       set{
           if(this.NowPhase.PhaseID!=value){
               this.NowPhase.PhaseID=value;
               if(this.NowPhase.PhaseID!=""){
                   Manager.ViewPrintManager.ChangePhase(value);//実際の描画処理
               }
           }
       }
   }
   //以下上記同様
   public string Layer1ID{
       get{return this.NowPhase.Layer1ID;}
       set{
           if(this.NowPhase.Layer1ID!=value){
               this.NowPhase.Layer1ID=value;
               if(this.NowPhase.PhaseID!=""){
                   Manager.ViewPrintManager.ChangeLayer(value);
               }
           }
       }
   }
   public string Layer2ID{
       get{return this.NowPhase.Layer2ID;}
       set{
           if(this.NowPhase.Layer2ID!=value){
               this.NowPhase.Layer2ID=value;
               if(this.NowPhase.PhaseID!=""){
                   Manager.ViewPrintManager.ChangeLayer(value);
               }
           }
       }
   }
   public string Layer3ID{
       get{return this.NowPhase.Layer3ID;}
       set{
           if(this.NowPhase.Layer3ID!=value){
               this.NowPhase.Layer3ID=value;
               if(this.NowPhase.PhaseID!=""){
                   Manager.ViewPrintManager.ChangeLayer(value);
               }
           }
       }
   }

   //念のためStartでNowPhaseの初期化をしておきはする(初期化処理はこう書かない方がいい気がしてきた)
   void Start(){
       NowPhase??=new Phase();
   }
   
   //外部からPhaseを知りたいとき。初期化Getにでも書いておけばはっきり言って不要。
   public string GetPhase(){
       NowPhase??=new Phase();
       return this.PhaseID;
   }
   //レイヤ版。上に同じ
   public string GetLayer(int layerNo){
       NowPhase??=new Phase();
       switch(layerNo){
           case 1:
               return this.Layer1ID;
           case 2:
               return this.Layer2ID;
           case 3:
               return this.Layer3ID;
           default:
               return"";
       }
   }
   //Phaseを切り替える処理
   public bool SetPhase(string id){
       NowPhase??=new Phase();
       this.PhaseID=id;
       return true;
   }
   //Layer1を切り替えるとき
   public bool SetLayer(string id){
       //呼び出したいときはSet呼び出しをして移動可能か判定する
       NowPhase??=new Phase();
       //Phaseの基本遷移可能条件を確認
       //上位レイヤが存在すれば動けない ex)L1はL2が無い時にしか動けない
       
       //PhaseとIDが一致しない場合遷移不可
       if(id[0]!=this.PhaseID[0]){
           return false;
       }
       switch(id[1]){
           case '1':
               //Layer1は上位レイヤ―がある時代入できない
               if(this.Layer2ID==""&&this.Layer3ID==""){
                   this.Layer1ID = id;
                   return true;
               }else {
                   return false;
               }
               break;
           case '2':
               //Layer2は一旦閉じないとだめかつレイヤ3が空でないと遷移できない
               if(this.Layer2ID==""&&this.Layer3ID==""){
                   this.Layer2ID = id;
                   return true;
               }else {
                   return false;
               }
               break;
           case '3':
               //Layer3は一旦閉じないとだめ
               if(this.Layer3ID==""){
                   this.Layer3ID = id;
                   return true;
               }else {
                   return false;
               }
               break;
           default:
               return false;
               break;
       }
      
       return false;
   }
   //POPUPはまだ作ってない
}

思った100倍長いですが構造もやってることもシンプルです。

基本SetLayerくらいしかまともな処理はしてませんが、画面遷移条件を足したいときはここをいじれば一発です。

ViewPrintManager

名前がクソややこしいのは過去のデータを傍らに保存してるせいで、競合して愚直な名前をつけられなくなったからです。バカですね。

この子は実際に描画の処理をしてくれます。

とは言ってもPhaseManagerから見たら実際に描いてるってだけで、ほんとにUIを動かすのは各GameObjectにアタッチしたスクリプトなので、この子はそのスクリプトを呼ぶだけです。

//各個のLayerに入るゲームオブジェクトが持ってるスクリプトに継承させる移動処理そのものの概形をなす抽象クラス
public abstract class Phaseing : MonoBehaviour{
   public abstract void PhaseStart();
   public abstract void LayerSet(string ID);
   public abstract void PhaseEnd();
}

//manager本体
public class ViewPrintManager : MonoBehaviour{
   /*
   Phaseが変わったら変わったIDを挿入して呼び出される
   現在表示中の画面ゲームオブジェクトを格納していて、
   そいつにSet呼び出しで値を渡す
   */
   //各レイヤのゲームオブジェクト
   GameObject Layer1{
       get{return GameObject.Find("Layer1");}
   }
   GameObject Layer2{
       get{return GameObject.Find("Layer2");}
   }
   GameObject Layer3{
       get{return GameObject.Find("Layer3");}
   }
   //現在表示中のGameObjectにアタッチされているPhaseingを継承したコンポーネント
   Phaseing Phaseing;
   
   //Phaseが変わったときにPhaseManagerから呼ばれる処理
   public void ChangePhase(string PhaseID){
       //Phaseが変わったら全レイヤの中身消して新しいPhaseのLayer1標準プレハブをセットする
       //現在の全レイヤの中身を消す
       foreach (Transform a in Layer1.transform) {
           GameObject.Destroy (a.gameObject);
       }
       foreach (Transform b in Layer2.transform) {
           GameObject.Destroy (b.gameObject);
       }
       foreach (Transform c in Layer3.transform) {
           GameObject.Destroy (c.gameObject);
       }
       //次のPhaseのプレハブをセットする
       GameObject __prefab = (GameObject)Resources.Load("SOtmp/"+PhaseID[0]+"/"+PhaseID[0]);
       GameObject prefab =Instantiate(__prefab) as GameObject;
       prefab.transform.SetParent(Layer1.transform, false);
       //標準画面に移動 標準画面IDは[A][1][0000]([PhaseID][LayerNo][UniqueID])で統一する
       ChangeLayer(PhaseID[0]+"10000");
   }
   //layerIDが変わったときにPhaseManagerから呼ばれる処理
   public void ChangeLayer(string ID){
       Layer1.transform.GetChild(0).gameObject.GetComponent<Phaseing>().LayerSet(ID);
   }
}

PhaseingっていうMonoBehaviourを継承した抽象クラスのあたりでちょっと悩みました。抽象クラスってなんぞ?から入ったもので。。。。

抽象クラスってのは、なんとなくの形を決めてくれるやつらしいです。

こいつを継承したクラスを作ると、そいつのクラスとしての形やら名前やらが何であれ、少なくともこの辺の関数は使えるんだろ?って感じでざっくりで呼び出せちゃうわけです。便利ですね。

今回で言うと、

Layer1.transform.GetChild(0).gameObject.GetComponent<Phaseing>().LayerSet(ID);

ここで役立ってます。

この後登場しますが、それぞれのGameObjectにアタッチする個別の移動処理を記述したスクリプトは、それぞれ処理が違うのでクラス名も変わっちゃいます。

すると、GetComponent<"ここ何入れたらいいの?">()ってなるんですが、それぞれのスクリプトにPhaseingを継承させてればこれで呼び出せちゃうんです。

しかも、そいつには少なくともPhaseingクラスの標準装備に指定したLayerSet()関数が存在するものとして扱えるのでこのような書き方ができるというわけだそうです。

さらにPhaseingはMonoBehaviourを継承してるので、Unity側から見ればPhaseingを継承したクラスは普通にスクリプトとして扱えるので、ボタンのOnclickに直接設定出来たりいろいろと自由なわけです。

いやー便利な世の中だ。

個々のスクリプト

ここまでできれば、あとは各個のスクリプトは簡単です。

上でちょっと紹介したように基本はPhaseingだけ継承したクラスを作れば後は普通のUnityのスクリプトと全く一緒です。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/*
MonoVihavierを継承した抽象クラスPhaseingを継承したスクリプトが↓これ
*/
public class A_script :Phaseing{
   Manager Manager{
       get{return GameObject.Find("Manager").GetComponent<Manager>();}
   }
   
   public override void PhaseStart(){
       //Phaseが始まった時の処理
       //呼び出しタイミングとかはManagerの方をいじって決める
       //まだタイミングは未定
   }
   public override void LayerSet(string ID){
       //Phaseの番号がセットされてこいつが動く
       //switch caseとかでID判別して移動処理をする
   }
   public override void PhaseEnd(){
       //Phaseを閉じるときの処理
       //呼び出しタイミングとかはManagerの方をいじって決める
       //まだタイミングは未定
   }
   public void OnclickBTTest(){
       //Mono継承してるのでコンポーネントとしてbuttonのOnclickとかに直接入れれる
       //例えばボタンおされたら画面遷移する時はこの関数をOnclickにして↓の感じで移動する
       Manager.PhaseManager.SetLayer("A10001");
   }
}

こんな感じで、あとはこの最後の簡単なスクリプトの形を作っていくだけで画面がいい感じになっていくとう算段です。

これはひょっとしてだいぶすっきりしたのでは?

今まで作った画面たちをこの形に修正したら残りの画面もある程度詰めて、ロード処理と戦っていく日々がやってきそうな予感ですね。

プレイ画面にたどり着くのはいつになるのやら…

今年中には遊べるようにしたいなぁ~。。。

この記事が気に入ったらサポートをしてみませんか?