2015年4月8日水曜日

Unity 1つのGameobject、ビルボードでダメージ表示

GUIでダメージなどの表示をすると重なった場合にドローコールが増える、らしい。
なので、ローポリ同士でバッチされてDrawCallの増加を1に抑えることを狙い、
板ポリにテクスチャを張り付け、ビルボードとして数値を表示する。

そのとき一桁ずつGameObjectを生成し、破棄するのはコストが高い
(1つのGameObjectで実現するよりも、多分)
と考え、1つのGameObjectで実現する。

…ふわふわとした走り出しだ!


〇 テクスチャ画像

ここでは、
・縦横4x4の要素
・0~9の並びは左上から右下に向けて
とした。
本番は、白い縁取りをするなどして重なってもわかりやすくしよう。


〇 スクリプト
・ DamageBillboard.cs

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class DamageBillboard : MonoBehaviour {

    Camera targetCamera = null;
    Transform _cameraTransform;
    Transform _transform;
    Transform _pTransform;

    Mesh mesh;      //メッシュフィルターをゲットして入れる先
    Vector3[] ver;  //頂点
    int[] tri;      //面
    Vector2[] uv;   //UV情報

    float startTime = 0;        //開始時間を記録
    float displayTime = 1.0f;   //表示時間、Destroyするまでの時間

    void Awake()
    {
        mesh = gameObject.GetComponent<MeshFilter>().mesh;
    }

    void Start()
    {
        while(targetCamera == null)
            targetCamera = Camera.main;

        _cameraTransform = targetCamera.transform;
        _transform = transform;
        startTime = Time.time;
    }

    void Update()
    {
        //カメラと同じ向きに ※サンプル、カメラの仕様による
        _transform.rotation = _cameraTransform.rotation;

        if (startTime + displayTime < Time.time) Destroy(gameObject);
    }
   
    /// <summary>
    /// 表示する数値の設定 Instantiate後実行
    /// </summary>
    /// <param name="value">表示する値</param>
    public void SetValue(int value)
    {
        CreateMesh(value);

        mesh.vertices = ver;
        mesh.triangles = tri;
        mesh.uv = uv;
    }

    /// <summary>
    /// メッシュの作成
    /// </summary>
    /// <param name="value">表示する数値</param>
    void CreateMesh(int value)
    {
        float numberWidth = 0.6f;  //これが1文字の幅
        float numberHeight = 0.5f; //これの2倍が1文字の高さ
        float margin = 0.0f;      //余白 隣り合う数同士の距離を開けたければ正、詰めたければ負
        float uvW = 4f;     //テクスチャの横の分割数
        float uvH = 4f;     //テクスチャの縦の分割数

        string vStr = value.ToString();     //文字列に変えて
        int size = vStr.Length;             //桁数を出す

        ver = new Vector3[size * 4];
        tri = new int[size * 6];
        uv = new Vector2[size * 4];

        float start = numberWidth / 2f * -size; //頂点の開始位置
        float shift = 0f;

        for (int i = 0; i < size; i++)
        {
            //頂点の座標情報
            shift = margin*(i-(size-1)/2f);
            ver[i * 4] = new Vector3(start + numberWidth * i + shift , numberHeight, 0);
            ver[i * 4 + 1] = new Vector3(start + numberWidth * i + shift, -numberHeight, 0);
            ver[i * 4 + 2] = new Vector3(start + numberWidth * (i + 1) + shift, numberHeight, 0);
            ver[i * 4 + 3] = new Vector3(start + numberWidth * (i + 1) + shift, -numberHeight, 0);

            //頂点が構成する三角面の情報
            tri[i * 6] = i * 4 + 1;
            tri[i * 6 + 1] = i * 4;
            tri[i * 6 + 2] = i * 4 + 2;
            tri[i * 6 + 3] = i * 4 + 1;
            tri[i * 6 + 4] = i * 4 + 2;
            tri[i * 6 + 5] = i * 4 + 3;

            //UV情報
            int t = int.Parse(vStr.Substring(i, 1));
            uv[i * 4] = new Vector2(0 + 1f / uvW * (t % uvW), 1f - 1f / uvH * Mathf.Floor(t / uvH));
            uv[i * 4 + 1] = new Vector2(0 + 1f / uvW * (t % uvW), 1f - 1f / uvH * (Mathf.Floor(t / uvH) + 1));
            uv[i * 4 + 2] = new Vector2(0 + 1f / uvW * ((t % uvW) + 1), 1f - 1f / uvH * Mathf.Floor(t / uvH));
            uv[i * 4 + 3] = new Vector2(0 + 1f / uvW * ((t % uvW) + 1), 1f - 1f / uvH * (Mathf.Floor(t / uvH) + 1));
        }
    }
}

・ 変更したくなるような変数達

displayTime : 表示時間
numberWidth, numberHeight : 一文字あたりの幅、高さの1/2 (なのでこれの2倍が実際の高さ、わかりにくいか…)
margin : 余白 (下記サンプルを見て頂くとわかりやすいかも)
uvW, uvH : テクスチャの縦、横それぞれの分割数


〇プレハブの作成
 以上の二つを使って、このようなプレハブを作成する。



〇 使い方、動作テスト
 下記の動作テスト用スクリプトを空GameObjectにアタッチし、
インスペクターからdamageBillboardに上記DamageBillboardプレハブを登録。
シーンを実行し、スペースキーを押すとランダムな数値が表示される。

・ InstaTest.cs

using UnityEngine;
using System.Collections;

public class InstaTest : MonoBehaviour {

    public GameObject damageBillboard;

 void Update () {
        if (Input.GetKeyDown("space"))
        {
            GameObject temp = Instantiate(damageBillboard);
            temp.GetComponent<DamageBillboard>().SetValue(Random.Range(0, 100000));
        }
    }
}

〇 サンプル
文字サイズ 縦1.0 x 横0.6 のとき
左からマージン +0.2,  0,  -0.2


以上、こんな感じ。

スクリプトでメッシュを作成するにあたり下記サイト様を参考にした。
ありがとうございます。
http://ftvoid.com/blog/post/716
http://narudesign.com/devlog/unity-mesh-vertix-uv-1/
http://narudesign.com/devlog/unity-mesh-vertix-uv-2/



〇余談

今はintの表示に対応している形だが、
floatに変更し、テクスチャ画像に小数点を加えるなどの改造をすれば、floatの表示もできる。

赤色数字の後に、回復用の緑色数字だとかコンボ数表示用の数字だとかを用意しておき、
それぞれの出番の際には10, 20シフトさせて使用するとかとか。

表示をフェードイン/アウトさせようとシェーダーを変え、マテリアルのカラーを弄ろうとすると、
各マテリアルがインスタンス化されるためバッチングされなくなり、
表示した数だけDrawCallは増加することになる。
複数の表示を"同時"に行うという場合に限れば、
sharedMaterialを使用してDrawCallを抑えたフェードイン/アウトができるかも。

0 件のコメント:

コメントを投稿