ゾンビとUnity

ゾンビネタとUnityでのゲーム制作について綴るブログです。

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Unity ゲーム製作 10 犯人は誰だ!?

前回の記事
Unity ゲーム製作 9 キューブ2万5千個を高速に作ることはできるのか?
http://bosukete666.blog.fc2.com/blog-entry-266.html

Voxel Terrain の生成を遅くしている原因は、キューブを作っている部分じゃないことが良く分かったので、それ以外で犯人探しをします。

処理を重くしていそうな場所をピックアップしてみます。

1.Random.Range()
2.forブロックで変数を宣言してる


ひとつずつ検証。


1.Random.Range()

Unity の Random.Range() がどのくらいパフォーマンスに優れているのか調べていなかったので、簡単なプロジェクトでテスト。
100万回 Random.Range() してみたところ、私の環境では20ミリ秒くらいで処理できました。
てことは、数万回程度だったら1ミリ秒もかからないはず。
数字を丸める処理がない Random.value の方が速いですが、Random.Range() でもそこまでロスしません。
素晴らしいです。

問題は Random.Range() にあるわけではないようです。

試しに Mathf.PerlinNoise() と比較したところ、Random.Range() の方がずっと速かったです。
PerlinNoise() は特にこれを使わなきゃいけない理由がない限りは、乱数として使う必要がないと分かりました。

Mathf.PerlinNoise
http://docs.unity3d.com/ScriptReference/Mathf.PerlinNoise.html



2.forブロックで変数を宣言してる

簡単に書くと以下。

for () {
  int a;
  Debug.Log(a);
}

forブロックで変数を宣言した場合、1回ループする毎に変数が使うメモリを確保して、解放するという処理が行われます。
メモリの確保と解放は結構コストがかかる処理で、特に最近のメモリが多いマシンだと数バイト程度の小さいメモリの確保・解放は重いと言われています(参考書の受け売り)。
なので、ゲームだと小さいメモリの確保・解放を高速に行うための仕組みを別途作るのですが、Unity がどこまでチューニングしているのかは分かりません。
ただ、Unity がどういう仕組みを持っているにせよ、スクリプトを書く側がちょっと注意すれば簡単に回避できる問題です。

int a;
for () {
  Debug.Log(a);
}

forブロックの外で変数宣言すれば、1回ループする毎にメモリ確保と解放をされることはありません。



でも、こういう場合は例外らしい

for () {
  float a = Random.value;
  Debug.Log(a);
}

↑これは遅いです。

float a;
for () {
  a = Random.value;
  Debug.Log(a);
}

↑こう書くべきです。

for () {
  float a = 0.1f;
  Debug.Log(a);
}

でも、これはロスがありませんでした。

C#のコンパイラが最適化してくれるっぽい。

float a = Random.value; は、処理してみないことには、a にどんな値が入るのか分からないため、毎ループ変数用のメモリを確保・解放という処理になるようです。

float a = 0.1f; のように、変数に定数を初期値として与えている場合、a には常に 0.1f が入ることが分かっているので、毎ループ変数用のメモリを確保・解放という処理はされない模様。

for () {
  Debug.Log(0.1f);
}

こんな風に最適化してくれるんだと思う。

本当にそうなのか?は分かりませんが、計測結果を見た限りだと、そう推測できます。


犯人が分かったので手直し

犯人は forブロックでの変数宣言でした。

手直ししたスクリプト
using UnityEngine;

public class VoxelTerrain : MonoBehaviour {

public enum How2MakeVoxel {
ByCreatePrimitive,
ByInstantiate,
}

public How2MakeVoxel how2make = How2MakeVoxel.ByCreatePrimitive;
public Material rock;
public Material hard;
public Material medium;
public Material soft;
public Material water;
public Material grass;
public GameObject original;

float[,] heightmap = null;
GameObject parent_obj = null;

GameObject MakeVoxel(int x, int y, int z, float height, Material m) {
GameObject g;
if (this.how2make == How2MakeVoxel.ByCreatePrimitive) {
g = GameObject.CreatePrimitive(PrimitiveType.Cube);
g.transform.parent = this.parent_obj.transform;
g.tag = "Terrain";
g.transform.position = new Vector3(x, heightmap[x,z] + height / 2, z);
} else {
g = (GameObject)UnityEngine.Object.Instantiate(this.original, new Vector3(x, heightmap[x,z] + height / 2, z), Quaternion.identity);
g.transform.parent = this.parent_obj.transform;
}
g.name = "Voxel_" + x + "," + y + "," + z;
g.transform.localScale = new Vector3(1, height, 1);
g.GetComponent<MeshRenderer>().material = m;
this.heightmap[x,z] += height;
return g;
}

public void Generate(int x, int y) {
this.parent_obj = new GameObject("Voxel Terrain");
this.heightmap = new float[x,y];

//深        浅
//0123456789
//岩水岩硬硬硬中中柔草 基本パターン

float height;
float s;
GameObject g;

//最も硬い岩盤の層
for (int i = 0; i < y; i++) {
for (int j = 0; j < x; j++) {
height = Random.Range(0.1f, 0.8f);
this.MakeVoxel(j, 0, i, height, this.rock);
}
}
//地下水
height = 1.0f;
for (int i = 0; i < y; i++) {
for (int j = 0; j < x; j++) {
g = this.MakeVoxel(j, 1, i, height, this.water);
UnityEngine.Object.DestroyImmediate(g.GetComponent());
}
}
//地下水の上にある硬い岩盤の層
for (int i = 0; i < y; i++) {
for (int j = 0; j < x; j++) {
s = 2.0f - heightmap[j,i];
height = (s > 0) ? s : 0;
height += Random.Range(0.1f, 0.5f);
this.MakeVoxel(j, 2, i, height, this.rock);
}
}
//硬い土の層
for (int i = 0; i < 3; i++) {
for (int j = 0; j < y; j++) {
for (int k = 0; k < x; k++) {
height = Random.Range(0.1f, 0.25f);
this.MakeVoxel(k, 3 + i, j, height, this.hard);
}
}
}
//中くらいに硬い土の層
for (int i = 0; i < 2; i++) {
for (int j = 0; j < y; j++) {
for (int k = 0; k < x; k++) {
height = Random.Range(0.25f, 0.5f);
this.MakeVoxel(k, 6 + i, j, height, this.medium);
}
}
}
//柔らかい土の層
for (int i = 0; i < y; i++) {
for (int j = 0; j < x; j++) {
height = Random.Range(0.5f, 0.75f);
this.MakeVoxel(j, 8, i, height, this.soft);
}
}
//草の層
for (int i = 0; i < y; i++) {
for (int j = 0; j < x; j++) {
height = Random.Range(0.75f, 1.0f);
this.MakeVoxel(j, 9, i, height, this.grass);
}
}
}

void Start() {
float start = Time.realtimeSinceStartup;
this.Generate(500, 5);
float elapsed = Time.realtimeSinceStartup - start;
Debug.Log("elapsed time : " + elapsed);
}
}

1秒程度で生成できるようになりました!
AnisometricVoxelTerrain3.png
スポンサーサイト

- 0 Comments

Add your comment

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。