2013年03月

2013年03月13日

うにばな(SendMessage オブジェクトプールなど 最適化)



前回更新から間が開いてしまい 申し訳ないです。 シェーダーか背景と予告したのですが

Sendmessage()メソッドの検索が多かったのでちょっと寄り道して データ最適化の話をします

コードを綺麗に見せるスクリプトを記事に埋め込むと余裕の文字数オーバーしてしまうので見づらいのは
勘弁して下さい。 色とかこりたいんですけど 記事のデータ量制限がきびしくてね シンプルにならざる。





■Sendmessage()で複数の値を送信する方法

Sendmessage ()で複数の値を送信するには 配列、クラスなどを利用します



●配列を使用する場合

●送信側

var goTo : Vector3;
var arr = new Array();

arr.Push (goTo);
arr.Push (true);
other.SendMessage ("GoTo", arr);



●受信側


function GoTo(arr){
var dest : Vector3;
var input : boolean;

dest = arr[0];
input = arr[1];
}




●クラスを使用する場合

●クラスの宣言

public class DamageParams
{
public var damage : float = 0.0;
public var type : int = 0;
public var source : NetworkPlayer = Network.player;

//コンストラクタここから
function damageParams( damage : float, type : int, source : NetworkPlayer)
{ this.damage = damage; this.type = type; this.source = source; }
}


●送信側

function dealDamage( other : GameObject )
{ other.SendMessage( "takeDamage", new DamagePrams(10, 2, Network.player) ); }


●受信側

function takeDamage( params : DamageParams )
{
if(params.type != 3)
health -= params.damage;
}


参照:http://answers.unity3d.com/questions/170384/multiple-paramaters-with-send-message.html


●NotificationCenter(通知センター)
Unity公式のフォーラムWiki に記述がありますがNotificationCenter直訳だと通知センターを使用した

SendMessageを効率よくオブジェクト間で送信するスクリプトがあります。 ※リンク先を参照してください

http://wiki.unity3d.com/index.php?title=NotificationCenter
http://wiki.unity3d.com/index.php/NotificationCenter3_5
http://wiki.unity3d.com/index.php?title=NotificationCenterGenerics


これは あらかじめ通信させたいGameObjectを配列に登録しておけば効率よくデータ送信できるというスクリプトです。

カスタマイズされて複数のタイプが存在しますが ジェネリック型Hashテーブルを使用したタイプが扱いに便利そうです




●プログラマ以外の方にもわかるように一応解説しますよ

スクリプトは シングルトンパターン で記述されています。

シングルトンというのは複雑な処理を行うメソッドでシーンの中に常に1つだけ存在させたいスクリプトを扱う場合に使用される記述法です。



シングルトンパターンの例)

using UnityEngine;
public class MySingleton : MonoBehaviour
{
private static MySingleton instance;
public static MySingleton Instance
{
get
{
if (instance == null)
//アプリケーション起動時にこのシングルトンクラス(”My・・”)が存在しなければGameObjectを生成してコンポーネント追加
{
instance = new GameObject ("MySingleton").AddComponent ();
}
return instance;
}
}
public void OnApplicationQuit () // アプリケーションの終了時にこのコンポーネントを削除
{
instance = null;
}
}


参照:http://wiki.unity3d.com/index.php/Singleton

シングルトンパターンのスクリプトはシーン中に置いておく必要はなくてアプリケーション(ゲーム)起動時に自動でインスタンスが生成されて アプリケーションの終了時に破棄されます。

リンク先にカスタマイズされたされたシングルトンパターンの例がありますので参考にしてください

※例はC#ですがUnityScriptに書き換えるときに注意するのはアクセサー(get / set)です。C#からUnityScriptへ移植するときに悩んだ方もいるのではないでしょうか。

アクセサはC#固有のものでJavaやC++にはありませんが UnityScriptではメソッドが用意されています


例) UnityScriptの場合

class JSClass extends MonoBehaviour
{
var x: String = '';
function get X() : String { return x; }
function set X(value : String) { x = value; }
}




● set { setアクセサー(setter ) 値の変更時の処理を書く value という名前の変数に代入された値が格納される。 }
● get { getアクセサー (getter ) 値の取得時の処理を書く メソッドの場合と同様に、値はreturnキーワードを用いて返す }


例のようにfunctionをつけてクラスの内部で使用します。

Nofitication Centerに関して本家フォーラムのスレッドでStatic変数を使用して値をやり取りするのとどちらがいいのかという議論がなされていますが static変数はメモリ常駐してしまうのでメモリの管理が必要になる点が問題

メモリに関しては使わない変数にはnullを代入しておくなどオーソドックスな手法で対応出来ますが 結論としては

ケースバイケースかなw Nofitication Centerを使用したサンプルはいままで一例(デジタルチューターのチュートリアル)しか見たことがなかったりなので ほんとにこれ使われてるのかしらという疑問もあったり。。



と ここまでSendMessage ()の解説でした が UnityのコミュニティWiki のGeneral Performance Tipsによると

http://wiki.unity3d.com/index.php/General_Performance_Tips

SendMessage ()メソッドは あまり高速ではなく1度のコールでダイレクトにコンポーネントを指定する呼び出しに比べて100サイクルぐらい遅いよ だからあんまり使わないほうがいいよ

的なことが書かれています。 な なんだってー (先に言え(# ゚Д゚)

100サイクルって何を基準にしてるんでしょうね?



ではSendMessage()は使えないのかというとそういうわけでもなくてオブジェクト数が少ない時や処理に余裕があるときは簡単便利なので問題ないと思います。 あとコンポーネントを直接呼び出せない場合 例えばStart()で他のオブジェクトを呼び出すときに処理がコケる場合があって そういう場合はSendMessage()なら通知が通ったりします 謎ですが。 早すぎるのもいけないのか?



General Performance Tips に他にも有効そうな情報が書いてありました


Find系関数は実行が遅いので 一度Find検索したらその結果をプールしておくとよい。」

Find関連メソッドも実行速度が遅いのですが シーン全体をサーチするのでオブジェクトが多いほど重くなります。

そこでFindメソッドはAwake()またはStart()文中で使用して配列に格納しておき実行時にはFindメソッドの代わりに配列を参照することで処理速度の向上が期待出来ます。

事前にオブジェクトを配列に登録しておく仕組みを 「オブジェクトプール」と呼んだりします。





■オブジェクトプール


実装方法)


Instantiate と Destroy のメソッドは両方とも重たい処理でアプリケーションの実行中に操作を行うと処理負荷が高いので

Instantiateで一度作成したコピーPrefabを配列に登録しておき SetActiveRecursively()を使用してモデルを非表示状態にします。

このときInstantiate の座標を原点にしないで下さい

※SetActiveRecursively()はモデルは非表示になりますが子供にしたパーティクルエフェクトなどはシーンに残ることがありますので カメラに映らない場所にオブジェクトを生成します



モデルを使用するときはオブジェクト名.SetActiveRecursively(true) でオブジェクトを表示状態にします



● spawnerのクラスの中でPrefabを作成する度にPrefabを配列に登録する


※Static型配列を使用すればどこからでもアクセスできます。

Prefabを削除するときは配列から登録を抹消します。



■ オブジェクトプールの例

オブジェクトプールのスクリプトはUnity本体にバンドルされている”Angrybots”のスクリプト

”Spawner.js”
を参照してみてください。

http://www.booncotter.com/unity-prefactory-a-better-pool-manager/

http://tasogare66.blog.fc2.com/blog-entry-16.html
C#にリライトされたバージョンがこちらに掲載されていました 



●Spawner.js



#pragma strict

static var spawner : Spawner;

var caches : ObjectCache[];

var activeCachedObjects : Hashtable;


class ObjectCache {
var prefab : GameObject;
var cacheSize : int = 10;

private var objects : GameObject[];
private var cacheIndex : int = 0;

function Initialize () {
objects = new GameObject[cacheSize];

// Instantiateしたオブジェクトを配列に格納して
for (var i = 0; i < cacheSize; i++) {
objects[i] = MonoBehaviour.Instantiate (prefab) as GameObject;
objects[i].SetActiveRecursively (false); // オブジェクトを非表示状態にする
objects[i].name = objects[i].name + i;
}
}

function GetNextObjectInCache () : GameObject {
var obj : GameObject = null;


obj = objects[cacheIndex];

// 非アクティブ状態 のオブジェクトみつかればそれを使用
if (!obj.active)
break;

// インデックスがキャッシュサイズを超えないように再計算
cacheIndex = (cacheIndex + 1) % cacheSize;

}

// アクティブ状態のオブジェクトが見つかれば削除する
if (obj.active) {
Debug.LogWarning (
"Spawn of " + prefab.name +
" exceeds cache size of " + cacheSize +
"! Reusing already active object.", obj);
Spawner.Destroy (obj);
}


// インデックスがキャッシュサイズを超えないように再計算
cacheIndex = (cacheIndex + 1) % cacheSize;
return obj;
}
}

function Awake () {

spawner = this;

//キャッシュするオブジェクト数の変数設定
var amount : int = 0;

// キャッシュ検索のループ
for (var i = 0; i < caches.length; i++) {
// Initialize each cache
caches[i].Initialize ();

// Count
amount += caches[i].cacheSize;
}

// オブジェクト個数分のハッシュテーブル配列の作成
activeCachedObjects = new Hashtable (amount);
}

static function Spawn (prefab : GameObject, position : Vector3, rotation : Quaternion) : GameObject {
var cache : ObjectCache = null;

// キャッシュ配列を検索して生成するオブジェクトが登録されているか検索
if (spawner) {
for (var i = 0; i < spawner.caches.length; i++) {
if (spawner.caches[i].prefab == prefab) {
cache = spawner.caches[i];
}
}
}

// キャッシュを検索して見つからなかったら普通にInstantiate() でオブジェクト生成
if (cache == null) {
return Instantiate (prefab, position, rotation) as GameObject;
}
// キャッシュ配列から次のオブジェクトを取りだす
var obj : GameObject = cache.GetNextObjectInCache ();

// オブジェクトにPosition,Rotation をセット
obj.transform.position = position;
obj.transform.rotation = rotation;
// オブジェクトを表示
obj.SetActiveRecursively (true);
spawner.activeCachedObjects[obj] = true;

return obj;
}

static function Destroy (objectToDestroy : GameObject) {
if (spawner && spawner.activeCachedObjects.ContainsKey (objectToDestroy)) {
objectToDestroy.SetActiveRecursively (false);
spawner.activeCachedObjects[objectToDestroy] = false;
}
else {
objectToDestroy.Destroy (objectToDestroy);
}
}



■ハッシュテーブル配列の拡張はtemp配列を作って一度配列を退避させた後もとの配列サイズを拡張して

CopyToメソッドを使用してデータをもとの配列にもどしてあげればよいです。

例)

static function EnlargeArrays(count:int, icount:int)
{
tempVerts:Vector3[] = Vertices;
Vertices = new Vector3[Vertices.Length + count];
   tempVerts.CopyTo(Vertices, 0);

}



■その他 空のGameObjectを作成してspawn時にPrefabをその子供として生成する方法

no title


配列を使わなくてもこんなかんじで親子にすれば 子供のtransform情報から配列が生成できます

可視化できるのでデバッグはしやすくなりますね








■オブジェクト配列の取得方法

例1<transforrmを直接配列に格納する場合>


for (var child : Transform in transform) {

実行文


}



例2<GetComponentsInChildrenを使用する場合>




var allChildren = gameObject.GetComponentsInChildren(Transform);

for (var child : Transform in allChildren) {

実行文

}



のようにFindメソッドを使わずにGameObjectの配列を取得することが出来ます

うまく使用すればシーン全体をサーチするFindメソッドに比べて高速な動作が期待出来て少し幸せ。





■まとめ:

●SendMessage()メソッドはあまり速度を期待できないので 出来る限りコンポーネントを直接呼び出すほうが

処理速度的な向上は期待出来ます。


●Findメソッドが全般に速度がいまいちなので一度Findしたデータは配列にとっておいて使いまわすことで

処理速度が改善できます。


●オブジェクトプール 配列にキャッシュすることでメモリ管理上は オブジェクト生成削除に起因したヒープ圧縮

による遅延が改善される効果があります。(以前の 高速化の2の記事を参照してください)


単純にNofitication Center ()をSendMessage()呼び出しを使用しない書き方にすれば良いような気もしたりなのですが。

Static変数を管理するスクリプトを作成してデータの一括管理でもいいような気もします そこらへんはケースバイケースで。。


akinow at 11:08|PermalinkComments(0)TrackBack(0) Clip to Evernote Unity3d | シリーズ講座

2013年03月06日

トイレの紙さま


こんにちは な 生存報告です


少し前ですが 都内某所で猫好きなプログラマーさんたちが対策会議を開かれたとかなんとか
なんの会議でしょうねぇ  げへへへ(ゲス顔      

と いちおう時事ネタをふっておきました


容疑者はJavaしか使えないと言っているがこっそりC#を勉強していたかもしれない  

とか言われてもねぇ。。 




ここしばらく 2週間ぐらい 風邪をひいて大変なことになっていました(当社比ですが
とくにお腹がねー 

トイレで苦しむ時って走馬灯のように人生の出来事が頭のなかをめぐります


♪ 知らず知らずー歩いてきたー細く長いこの道ー  


みたいなリフレインが頭のなかで繰り返し


♪ ああー 川の流れのようにー


もう上から入った水状のものがそのまま下からデますからねー 脱水症状おこさないように
水飲まないといけないし 死ねるわ


この唄だれのでしたっけ 美空はるみさん?NHK的には    なんか怒られそうダナ うん


今回 震災からちよっとづつためていた 非常用の飲料水をほとんど使いきってしまいました
消費期限も切れる頃だしいいかなー と思ったのですけどでも水ってそんなに腐るものなのかい?
というシンプルな疑問があって 友人に聞いてみたところ


「なにいってんの ぶぎょうくん水は腐るんだよ だから消費期限ついてるんだよ( ・´ー・`)どや」

って言われました。


いや それはお風呂とかで雑菌が入れば短期間で腐るけども ペットボトルのとかどう見ても
処理してある水は腐りそうもないじゃないの  ということで
 
ネットでペットボトルの水は本当に悪くなるのかを調べてみたところ 
サントリーの中の人の解説がありました

サントリーの人の解説によればペットボトルの水は滅菌処理した後でボトルに詰めてあるので
数年置いても腐るものではないとか。
 
じゃあなんで消費期限があるの?かというと 

どんなにしっかりボトルに栓をしても空気中にわずかながらも水が揮発してしまうそうで
そうすると表示している内容量を満たすことが出来ないため 店舗側の品質管理の問題上
消費期限を設けているということらしいです。 なので多少古くなった水でもモウマンタイだそうですよ


友人の頭のなかでは

水は腐るもの+ペットボトルの水には消費期限がある ⇛ 水は腐るから消費期限がある(結論 ピコーン)

てな感じだったんでしょう


最近だと5年もつ水とかが売れているらしいですが ちょっとしっかり栓をした水ってことですね

ということでいま買い置きしているお水は ちょっとばかり古くても飲めるそうなので 
あわてて廃棄しないようにしてくださいな



■今後の予定

そういえば うにばな の更新をしないといけませんかった 学生さんが春休みだし 

「シェーダー」と「背景」 なんていう検索が多かったのでそこらへんを中心に解説します たぶん  






akinow at 12:57|PermalinkComments(4)TrackBack(0) Clip to Evernote 日記