タワーディフェンスが作りたい:その3 〜ゲージの表示周り〜

ayumegu(プログラマー)
よろしくお願いします。

はい、引き続きちょこちょこタワーディフェンスをつくっています。
今回はHPのゲージや、ダメージを表示するところをの説明でも・・・。

作ったものとしては

  • 位置が変わっても位置が変わらずに表示される
  • ダメージ、ゲージなどは位置に関係なくて前に描画される
  • 距離が離れると非表示になる

こんな感じのものです。

カメラ構成

ヒエラルキーはこんな感じになっています。

メインのカメラはPlayerの下にあるカメラで、今回の3D空間上のUI表示用に
UI Root(3D)のカメラを使います。
UI Rootの方は2Dのメニュー(最前面)表示用に使います
さらにもう一つカメラがあってそれがMapの下にMap表示用のかめらがあるので
カメラは全部で4つ使っています。

  • 3dUIカメラ:レイヤー 3DUIのみ表示 Depth:0
  • 2dUIカメラ:レイヤー UIのみ表示 Depth:2
  • マップ用カメラ:レイヤー Mapのみ表示 Depth:1
  • Playerカメラ:上記以外のレイヤーを表示 Depth:-1

3dUIカメラはこんなような感じになっています

projectionはPerspective、Clippingでどれくらいはなれたら非表示にするかというのができます。

早速表示

ゲージのprefabとダメージテキストのprefabをつくります。
ゲージはEnemyのクラスのStartとりあえず作成します。
親はUI Root(3D)のしたです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
   private Transform gaugeTran;
  protected Gauge gauge;
  private int maxHp = 10;
  private int hp = 10;
  private bool isDead = false;
  private Transform target;
  
  void Start () {
      GameObject gaugeObj = Instantiate(Resources.Load(CommonConst.PREFAB_UI_GAUGE)) as GameObject;
      gaugeObj.transform.parent = BattleCommonFunc.ui3DCameraTran.parent;
      gaugeObj.transform.localScale = Vector3.one;
      gaugeObj.gameObject.SetActive(false);
      gaugeTran = gaugeObj.transform;
      gauge = gaugeObj.GetComponent<Gauge>();
  }
  

普通に作成しているだけです。
位置などはUpdateで計算します

ちなみにゲージのクラスはこんな風になっています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class Gauge : MonoBehaviour {

  public UISprite gaugeSprite;
  public UILabel gaugeLabel;

  private Transform tran;
  private bool isShow = true;

  void Awake()
  {
      tran = transform;
  }
  
  // ゲージの表示、非表示きりかえ
  public void Show(bool flg)
  {
      if(isShow != flg)
      {
          foreach(Transform child in tran)
          {
              child.gameObject.SetActive(flg);
          }
          isShow = flg;
      }
  }

  // ゲージだけ表示
  public void SetGauge(float amount)
  {
      Show(true);
      gaugeSprite.fillAmount = amount;
      gaugeLabel.text = "";
  }
  
  // ゲージとテキストを表示
  public void SetGauge(float amount, string str)
  {
      SetGauge(amount);
      gaugeLabel.text = str;
  }
}

こちらがEnemyクラスのUpdateメソッドです。
死んだときと、HPが最大のときは非表示にしています。
位置を合わせているのはGetMainTo3DUIPosというメソッド。
下の方で説明します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
   void Update () {
      if(isDead) return;

      if(hp != maxHp)
      {
          gaugeTran.gameObject.SetActive(true);
          gauge.SetGauge((float)hp / (float)maxHp, hp.ToString());
      }

      if(gaugeTran.gameObject.activeSelf)
      {
          gaugeTran.position = BattleCommonFunc.GetMainTo3DUIPos(transform.position);
          gaugeTran.LookAt(BattleCommonFunc.ui3DCameraTran.position);
          gaugeTran.SetEulerAnglesY(0);
          gaugeTran.AddPositionY(1f);
      }
  }

で、こちらが要のGetMainTo3DUIPosです。
メインカメラの座標を3dカメラの座標に変換しているところです。

1
2
3
4
5
6
   public static Vector3 GetMainTo3DUIPos(Vector3 pos)
  {
      Vector3 uiPos = mainCamera.GetComponent<Camera>().WorldToScreenPoint(pos);
      uiPos = ui3DCamera.GetComponent<Camera>().ScreenToWorldPoint(uiPos);
      return uiPos;
  }

このメソッドをダメージの表示のところでも使えばOKです。

EnemyClassのあたり判定部分

1
2
3
4
5
6
7
8
9
10
11
12
   void OnTriggerEnter(Collider other) {
      if(!isDead && other.tag == CommonConst.TAG_PLAYER_ATTACK)
      {
          GameObject damageText = Instantiate(Resources.Load(CommonConst.PREFAN_UI_DAMAGE_TEXT)) as GameObject;
          damageText.transform.parent = BattleCommonFunc.ui3DCameraTran.parent;
          damageText.transform.localScale = Vector3.one;
          DamageText dmgTxt = damageText.GetComponent<DamageText>();
          dmgTxt.SetText("10", tran);       // TODO とりあえず固定でダメージ10

          StartCoroutine(Hit (10));      // ダメージを食らったアニメーションなど ここでは割愛
      }
  }

DamageTextに関してはこのクラスのUpdateで位置の計算をしたり、アルファ、移動処理をしています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class DamageText : MonoBehaviour {
  
  private Transform tran;
  private Transform targetTran;
  private float movePos = 0;
  private UILabel damageLebel;

  public void SetText(string str, Transform targetTran)
  {
      this.targetTran = targetTran;
      damageLebel = GetComponent<UILabel>();
      damageLebel.text = str;
      tran = transform;
      Destroy (gameObject, 1);
  }

  void Update () {
      if(tran != null)
      {
          movePos += 100f * Time.deltaTime;
          tran.position = BattleCommonFunc.GetMainTo3DUIPos(targetTran.position);
          tran.AddLocalPositionY(movePos + 100f);
          tran.LookAt(BattleCommonFunc.ui3DCameraTran.position);
          tran.SetEulerAnglesY(0);
          damageLebel.color = new Vector4(damageLebel.color.r, damageLebel.color.g, damageLebel.color.b, damageLebel.color.a - Time.deltaTime);
      }
  }
}

最後に

今回はそれほどだいしたことをしていませんが・・・。
実はちょっとあきてきている・・・。

ちなみに現在は下図のような状態までつくりました。

簡単にUIをのせてスライムがクリスタルに向かってぞろぞろスポーンして向かっていくという・・・。
A*も乗りましたがいろいろ修正が必要かもな〜といったところ。
敵のAIとか〜経路探索がみんな同じ道とおっててなんだかつまんない・・・。

ここまでの作業時間はトータルで15時間くらいだろうか。
うん、アセットの力は偉大である。
ここまでではまったのが、クオータニオンとオイラーとかの回転・・・。
あとはUnity4からもってきたProjectorがバグっている件・・・。
Unity5のprojectorをインポートしなおしてshaderを差し替えたらうまく動いた。
これの解決に30分ぐらいついやした・・・。