前回更新から間が開いてしまい 申し訳ないです。 シェーダーか背景と予告したのですが
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をその子供として生成する方法

配列を使わなくてもこんなかんじで親子にすれば 子供の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変数を管理するスクリプトを作成してデータの一括管理でもいいような気もします そこらへんはケースバイケースで。。









