[Unity] Androidでも画面バラバラエフェクトを使える速度にしたい

前回作ってみた「[Unity] FF10エンカウントエフェクトを再現する [画面バラバラ]」は手持ちのMemopad7(Android)で実行すると、10×10に分割した画面で1秒近く処理が止まります。さすがに使い物にならないので、動的生成をやめて速度が出るかどうかを試してみました。

 

 

 

環境

Unity 5.0.0

 

参考サイト

その1 Inspectorが変わる!

Editor スクリプティング 入門

Asset Storeに出すためのEditor開発入門

知って得するUnity エディタ拡張編

「宴」実装時に得られたUnityプログラムノウハウ

 

改善目標

Memopad7(ME176) Android機で負荷なく動くようにすること。

 

エディタ拡張でエフェクト用GameObjectを作成する

なんとなくエディタ拡張で作れたらかっこいいなと感じたので、GameObjectを作成するメニューに独自のメニューを追加して対応。参考サイトを元に作成しました。
このエディタ拡張に関するものを読み漁るのが一番大変でした。
結局、別にエディタ拡張でなくても良かったのですがこれがやりたかったんだ。

using UnityEngine;
using UnityEditor;
using System.Collections;

namespace Shattered
{
  [CustomEditor(typeof(Shattered))]
  public class ShatteredPropertyDrawer : Editor
  {
    /// <summary>
    /// カスタムメニュー「GameObject/3D Object/Custom/ShatteredDisplay」
    /// </summary>
    [MenuItem("GameObject/3D Object/Custom/ShatteredDisplay")]
    private static void Create()
    {
      const int DivisionX = 5;
      const int DivisionY = 5;

      // ルートを作成し、その下へすべて追加
      GameObject rootObj = new GameObject("Shattered");

      // 最後に制御用のコンポーネントを追加します
      var shat = rootObj.AddComponent<Shattered>();
      shat.DivisionX = DivisionX;
      shat.DivisionY = DivisionY;
      shat.GenerateDevris();
    }

    /// <summary>
    /// Shattered用インスペクター拡張
    /// </summary>
    public override void OnInspectorGUI()
    {
      base.OnInspectorGUI();

      Shattered shat = target as Shattered;
      if( GUILayout.Button("再作成") ) {
        shat.GenerateDevris();
      }
    }
  }
}

 

欠片情報を保持するShatteredコンポーネント

最初の実装は、メニューが選ばれた時点でMeshを作成し、MeshFilterなどすべて設定したGameObjectを作成していました。ただこれを行うとPrefab化したときMesh情報が無くなってしまいます。3角形をMesh化してAssetsフォルダに保存するのも嫌なので、Meshは動的に作ることに。そのため頂点位置をシリアライズ対象として持つようにしました。

実行時に形状を作成する為、シーンウィンドウ上で何も見えないのがすごい不満。これもエディタ拡張の機能で出来るのかな・・・。

こんなコード載せなくても良いと思いますが・・・。

static List<ShatteredDebri> MeshToDebris( Mesh mesh )
{
  List<ShatteredDebri> debris = new List<ShatteredDebri>();
  int polygonCount = mesh.triangles.Length / 3;
  for( int n = 0; n < polygonCount; n++ ) {
    ShatteredDebri debri = new ShatteredDebri();
    debri.Vertex[0] = mesh.vertices[mesh.triangles[n * 3 + 0]];
    debri.Vertex[1] = mesh.vertices[mesh.triangles[n * 3 + 1]];
    debri.Vertex[2] = mesh.vertices[mesh.triangles[n * 3 + 2]];
    debri.UV[0] = mesh.uv[mesh.triangles[n * 3 + 0]];
    debri.UV[1] = mesh.uv[mesh.triangles[n * 3 + 1]];
    debri.UV[2] = mesh.uv[mesh.triangles[n * 3 + 2]];
    for( int i = 0; i < debri.Vertex.Length; i++ ) {
      debri.Vertex[i].x /= Screen.width;
      debri.Vertex[i].y = debri.Vertex[i].z / Screen.height;
      debri.Vertex[i].z = 0;
    }
    for( int i = 0; i < debri.UV.Length; i++ ) {
      debri.UV[i].x /= Screen.width;
      debri.UV[i].y /= Screen.height;
    }
    debris.Add(debri);
  }
  return debris;
}

 

Startメソッド内で生成

Startメソッド内となっていますが、キャプチャー終了時に頂点情報からMeshを作成するようにしました。

void OnCaptureFinish()
{
  var capture = this.gameObject.GetComponent<DisplayCapture>();
  this.Material.mainTexture = capture.Texture;
  Destroy(capture);

  // データからGameObjectを生成
  foreach( var debri in this._debris ) {
    var obj = debri.CreateGameObject("Debri");
    obj.transform.SetParent(this.transform);

    obj.GetComponent<MeshRenderer>().material = this.Material;

    obj.layer = LayerMask.NameToLayer("EffectLayer");

    // ランダムで力を加える
    var rigid = obj.GetComponent<Rigidbody>();
    var powerx = UnityEngine.Random.Range(-100f, 100f);
    var powery = UnityEngine.Random.Range(0f, 100f);
    var powerz = UnityEngine.Random.Range(-100f, 100f);
    rigid.AddForce(new Vector3(powerx, powery, powerz));
  }

  // 画面サイズを適応し、初期位置へ移動
  if( TargetCamera != null ) {
    var z = CameraDistance;
    var p1 = TargetCamera.ScreenToWorldPoint(new Vector3(0, 0, z));
    var p2 = TargetCamera.ScreenToWorldPoint(new Vector3(TargetCamera.pixelWidth, TargetCamera.pixelHeight, z));
    var scale = (p1 - p2);
    scale.x = Mathf.Abs(scale.x);
    scale.y = Mathf.Abs(scale.y);
    scale.z = 1;
    transform.position = p1;
    transform.localScale = scale;
  }

  _is_finished = false;
}

 

画面キャプチャー

Unity 5からRenderTextureがフリー版でも使用可能に!見た目は変わりませんが対応しておきましょう。

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

/// <summary>
/// 画面をキャプチャーしTexture2Dを生成します。
/// </summary>
public class DisplayCapture : MonoBehaviour
{
  /// <summary>
  /// キャプチャーが完了したときに呼び出すハンドラー
  /// </summary>
  public Action ResultHandler = null;

  /// <summary>
  /// テクスチャー
  /// </summary>
  public Texture2D Texture = null;

  /// <summary>
  /// キャプチャー画像を保存済みかどうか
  /// </summary>
  bool saved_screen_capture = false;

  /// <summary>
  /// Awake this instance.
  /// </summary>
  void Awake()
  {
    saved_screen_capture = false;
  }

  /// <summary>
  /// Raises the post render event.
  /// </summary>
  void Update()
  {
    if( saved_screen_capture != true ) {
      // キャプチャー
      Take();

      // 通知
      if (ResultHandler != null) {
        ResultHandler();
      }

      // 破棄
      // Destroy(this);
    }
  }

  /// <summary>
  /// 画面をキャプチャーします
  /// </summary>
  protected void Take()
  {
    // Texture2D screenShot = new Texture2D(1920, 1080, TextureFormat.RGB24, false);
    Texture2D screenShot = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, false);
    RenderTexture rt = new RenderTexture(screenShot.width, screenShot.height, 24);

    // Render from all!
    foreach( Camera cam in Camera.allCameras ) {
      RenderTexture prev = cam.targetTexture;
      cam.targetTexture = rt;
      cam.Render();
      cam.targetTexture = prev;
    }

    RenderTexture.active = rt;
    screenShot.ReadPixels(new Rect(0, 0, screenShot.width, screenShot.height), 0, 0);

    //various other post processing here..

    screenShot.Apply();

    this.Texture = screenShot;
    saved_screen_capture = true;
  }
}

 

サンプル実行

Unity 5にしたら右下にDevelopment Buildと表示されるようになった。リリース用の出力があるのだろうけど、とりあえずこのまま。
3Dモデル : Animated Knight and Slime Monster

 

実行速度

前回記事の完全動的生成よりはもちろん高速になりました。欠片の厚みを無くし、欠片1つで8ポリゴンだったものを1ポリゴンに。(欠片を回転させるならカリングOFFで描画すればいいかな)

ただ多少のモタツキは完全になくなりませんでした。0.1秒程度止まる感じ。

 

まとめ

形状は静的に作成するので同じ形になりますが、10 x 10くらいに分割すれば問題ないでしょう。ただCanvasのUI込みで画面キャプチャーってどうやるんだろうか。UGUIが比較的新しい為か、情報が見つからない。


希木小鳥

Diablo1でハクスラの世界に。今はBorderlands2をプレイ中。ぬるゲーマー。

あわせて読みたい