2013年1月23日水曜日

Unityで活かせるLINQの底力!! (前編)

最近、アセットストアでダウンロードしたコードに たまたま、LINQ を活用しているものがあって興味をもった。

そして「なるほどLINQ で書くと美しく見やすいコードを書ける場合があるのだ!」と素直に感じた。

調べてみたら、一般論としてUnity でどのような場合に活用すると良いか、Unity Gems に良い記事があったので2 回の投稿に分けて翻訳を紹介させてもらいたい。

長文ゴメン!!でもLINQをUnityでどう活用するか知らなかった人は覚えとくと、きっと役立つぜ!
http://unitygems.com/linq-1-time-linq/
Nov, 23, 2012

次の場合、この記事を読むとピッタリだ!

  • GameObject.FindGameObjectsWithTag() を使用して、ゲームオブジェクト自身でなく、それにアタッチされる全ての Transform を見つける方法がほしい場合
  • リストや配列の操作、ソート、及び変更を行いたいけれども、どうもコード量が余分に増えがちである場合
  • LINQ は聞いたことがあるけれど、仕組が良く分からない場合
  • 複雑な条件(あるターゲットに最も近い5つのタグつきオブジェクトのレンダラのマテリアルを全て)を戻り値とする条件を(あるいはもっと複雑な条件も)、一行のコードだけで出来ることを信じられない場合

上のトンチに対して、あなたが頭の中で「一行のコードでどうやって??」と悩んでる間に一緒に次の問題を考えることにしたい。「英語の文章で and が5連続で入って文法的に正しいものはどういうものだろう?」。想像つくだろうか。この答えは、文末にこの主題と完全に関連ない形で文末に載せてある(ズルして先に見ないでね)。 ※翻訳ブログでは次回記事


はじめに

LINQ とは Language Integrated Native Query の略称であり、.NET上でオブジェクトでも動作するデータベースのクエリの働きをするものである。強力であり、内容の理解が進むと非常に使いやすい。

LINQ は全ての for next ループを書き出すのに比べて遅い場合があります。半分ぐらいのスピードしか出ない場合があるため、毎フレーム行われる Update関数では慎重に使用されるべきだ。一方、キャッシング、プロセッシングでは優秀だ。

C# では実際に SQL 文に近い記述が出来る。
var transformsWithTag = from go in GameObject.FindGameObjectsWithTag("YourTag") 
     where Vector3.Distance(go.transform.position - transform.position) < 100
     select go.transform;
しかし関数ベースのアプローチがより強力かつ個人的に可読性が高いとおもわれるため、そちらを使用して行く。

列挙体の使用

Linq は列挙体を使用して既存の列挙体のコレクションを列挙しつつ、変更もかけていくものだ。このため Linq は配列、リスト、ディクショナリ、さらにあらゆるコレクションであるもので全て使用できる。Linq は列挙体を戻すために、これを繋ぎ合わせることができることは注目に値し、それゆえに強力だ。

Linq によりコレクションを編集する一連の関数を書くことが出来て、自身のコードを無名関数の形式で提供出来る。

早速始めてみよう!

LINQの有効化


Linq を使用するためには import または using ディレクティブをソースコードに追加する必要がある。
//C#
using System.Collections.Generic; //足しておくといつでも便利
Always a good idea
using System.Linq;

//JavaScript
import System.Linq;
有効化はこれだけで完了!

はじめてのLinqステートメント

まず簡単なことから始めよう - GameObject.FindGameObjectsWithTag で戻されるゲームオブジェクトと紐付けされた Transform の配列を作成してみる。

  • 全てのゲームオブジェクトを見つける
  • ゲームオブジェクトの Transform を選択
  • 結果を配列に変換

次のようなコードになる :
//C#

var transformArray = GameObject.FindGameObjectsWithTag("MyTag")
    .Select(go => go.transform)
    .ToArray();

//Javascript

var transformArray = GameObject.FindGameObjectsWithTag("MyTag")
    .Select(function (go) go.transform)
    .ToArray();
実現したいセレクション処理に無名関数を使用していることに注目してほしい。Select により列挙体(ここではゲームオブジェクト)の中身を取得し、適当な名前をつけて(ここでは go )、何らかの計算結果を戻して(ここでは transform )、ToArray が処理全体を実行したうえの結果を配列に格納します。

C# ではラムダ関数: go => go.transformを使用し、JavaScript では無名関数: function(go) go.transform を使用する。

さらに、、、この関数はひとつのステートメントに限定されない! { and }  をいれて好きなだけの量のコードを記述出来る。この点はまた後ほど説明する。

クエリ実行

次のトピックはクエリだ - 問い合わせを行うところから始めたい。現在の位置からワールド座標で 10 単位ほど離れたオブジェクトの Transform を全て取得して見よう。
//C#

var transformArray = GameObject.FindGameObjectsWithTag("MyTag")
    .Select(go => go.transform)
    .Where(t => Vector3.Distance(t.position - transform.position) < 10
    .ToArray();

//Javascript

var transformArray = GameObject.FindGameObjectsWithTag("MyTag")
    .Select(function (go) go.transform)
    .Where(function (t) Vector3.Distance(t.position - transform.position) < 10)
    .ToArray();
ここでは Where 関数を select の後に入れた - このため列挙体はすでに Transform の列挙体である - Where無名関数は真または偽を返す必要があるため、列挙体のTransform と現在のTransformの間の距離を計算する簡単なテストを行う。

ソート実行

Danger という名前のスクリプトが、オブジェクトにアタッチされているとして、このスクリプトには dangerLevel という float 変数を含むものとする。次に、Transform のリストをこの dangerLevel によってソートしていく(ここでは dangerLevel が範囲内にない場合は危険性がなく対象外という意味とする)。
//C#

var transformArray = GameObject.FindGameObjectsWithTag("MyTag")
    .Select(go => go.transform)
    .Where(t => Vector3.Distance(t.position - transform.position) < 10
    .OrderByDescending(t => {
       var danger = t.GetComponent();
       return danger ? danger.dangerLevel : 0;
      })
    .ToArray();

//Javascript

var transformArray = GameObject.FindGameObjectsWithTag("MyTag")
    .Select(function (go) go.transform)
    .Where(function (t) Vector3.Distance(t.position - transform.position) < 10)
    .OrderByDescending(function (t) {
          var danger = t.GetComponent(Danger);
          return danger ? danger.dangerLevel : 0;
         })
    .ToArray();
ここでは OrderByDescending を追加しました(降順に並べます)。最初にコンポーネントを取得して、次にその値を使用するために複数行に渡る関数を記述した。簡単なことであるのごお分かり頂けただろうか?通常の関数みたいに扱うことが出来る。

ここまでで十分イケてることは分かった - だけど、距離順 - 危険度順に並べて距離も重要なパラメータとして扱うにはどうすべきだろうか?
//C#

var transformArray = GameObject.FindGameObjectsWithTag("MyTag")
    .Select(go => go.transform)
    .Where(t => Vector3.Distance(t.position - transform.position) < 10
    .OrderByDescending(t => {
       var danger = t.GetComponent();
       return danger ? danger.dangerLevel : 0;
      })
    .ThenBy(t => Vector3.Distance(t.position, transform.position)
    .ToArray();

//Javascript

var transformArray = GameObject.FindGameObjectsWithTag("MyTag")
    .Select(function (go) go.transform)
    .Where(function (t) Vector3.Distance(t.position - transform.position) < 10)
    .OrderByDescending(function (t) {
          var danger = t.GetComponent(Danger);
          return danger ? danger.dangerLevel : 0;
         })
    .ThenBy(function (t) Vector3.Distance(t.position, transform.position)
    .ToArray();
ThenBy および ThenByDescending により何個でも連結が出来る(ただし sqrMagnitude でも同じ結果が得られて、パフォーマンス観点からは平方根計算も省けるそちらを使用すべきであるが、分かり易くするためだけが理由で、ここでは Vector3.Distance とした)。
---

次回に続くぜ!

2013年1月16日水曜日

Unityメモリ管理「ヒープのフラグメンテーション、オブジェクトプール、およびガーベージコレクション」

オブジェクトプールの利便性についてはこのブログでも過去に何度か取り上げてきた。

関連記事




今回の記事では

  1. Unity Gemsならではのオブジェクトプールに関するまとまった考え方
  2. 記事の一番最後にダウンロードできるサンプルのUnityプロジェクト

があるので是非そちらも試してみて欲しい。

なお、筆者環境ではサンプルシーンのうち1つだけがUnity4.0でうまく動作しなかったが、オブジェクトプールをプログラミングせずに雰囲気だけでも知るのに良いサンプルだとおもう!

-------
http://unitygems.com/memorymanagement/

October 21, 2012

ヒープ フラグメンテーション、ガーベージコレクション、およびオブジェクト プーリング


ヒープは大きなメモリのゾーンであり、データがランダムに格納する。実際にはそれほどランダムだなく、OSはメモリをみて必要なデータを当てはめるのに十分な最初のメモリゾーンを探す。ヒープのサイズおよび場所は選択したプレイヤーのプラットフォームにより幅がある。

例えば、整数をリクエストすると、OS は 4 バイト連続で空きメモリの箇所を探す。実際のところ、プログラム実行に伴い、メモリのロケーションは実際のロジックなしに使用および解放され、自身のプログラムはヒープのフラグメンテーションが生じる可能性がある。

図でヒープ フラグメンテーションの例を示す。明らかにこれは端的に誇張しているが、考え方は変わらない。data3 を割り当てているが、 state1 では state2 で破棄された data3 があった。
state1 と最後で 同じ量のデータあり、データは追加されていないことに留意してほしい。動作によってヒープ フラグメンテーションが作成されている。

Unity は .NET 管理メモリを使用する。 C プログラムではヒープ フラグメンテーション により、空きメモリ自体は十分にあっても十分な大きさの連続ブロックが見つからないために、メモリブロックを割り当てることが不可能な状況が作り上げられることがあった - 管理メモリはこの制約がない。もしメモリのブロックが .NET 管理メモリで見つからない場合は、ガーベージコレクション システムが使用されてアイテムを移動してヒープのフラグメンテーションを取り除く。これは当然ながら、時間消費の大きい演算であり、継続的に高いパフォーマンスを求められるゲームにおいて顕著なフレームレート低下を引き起こす可能性がある。

プログラマにとっての解決策はオブジェクト プーリングの概念を使用することだ。もし date3 を state1 で破棄する代わりに、後で使用できるように無効にしたらどうだろう。それによってヒープ フラグメンテーションが避けられたはずだ。

データを破棄せず、スリープさせて保持し、必要なときに揺り起こす。

この解決策には多くの利点があり、ひとつめとして前述のケースが回避でき、二つめとして Instantiate および Destory 関数のコールを回避できて gameObject.SetActiveRecursevely (true/false); を使用するのみだ。

最後に、破棄をしないため、高価な処理であるガーベージコレクションが行われない。
ObjectScript.cs
using UnityEngine;
using System.Collections;
 
public class Test : MonoBehaviour {
    public GameObject prefab;
    GameObject[] objectArray = new GameObject[5];  
        public static int counter; 
 
         void Start(){
        for(int i = 0;i < objectArray.Length;i++){
            objectArray[i] = (GameObject)Instantiate(prefab,new Vector3(0,0,0),Quaternion.identity);
            objectArray[i].SetActiveRecursively(false);
        }
        }
        void Createobject(){
        if(counter < objectArray.Length){    
                // 配列の中を反復
            for(int i = 0;i < objectArray.Length;i++){  
                // 無効なオブジェクトを探す
                if(!objectArray[i].active){    
                    // カウンタを増加させ、オブジェクトを有効化し、位置を決定
                    counter++;                                              
                    objectArray[i].SetActiveRecursively(true);             
                    objectArray[i].transform.position = new Vector3(0,0,0);  
                    return;
                }
            }return;
        }else return;
    }
}
void OnCollisionEnter(CollisionEnter){
    if(other.gameObject.tag=="Object"){
        other.gameobject.SetActiveRecursively(false);       //オブジェクトを無効化
        ObjectScript.counter--;                             //カウンタを減らす

シーンには同時に 5 オブジェクト以上はなく、ひとつが無効化されるとどこでも再度有効化できる。ガーベージコレクションは必要でない。

このテクニックを敵に使用できる。もし敵の波が向かってきたときに、やっつけた敵を破棄するかわりに、その敵を後ろの順番ににまわして、そのうち先ほどと同じ敵を気付かずにやっつけるようなことになる。

弾数についても同様で、あなたまたはNPCキャラクターが撃った弾丸を破棄するかわりに、無効化する。もう一回撃つときに銃口の前の同じ位置と正しい速度で有効化すればよい。

メモリ レベルでのプーリングはOSにより管理される別の処理であり、オブジェクトのサイズに関わらず同じ量のメモリを予約する。結果的にメモリ上に小さな空きがたくさん出来ることは防げる。

配列の再利用


オブジェクトの再利用に加えて、自身のコードにより異なるコールや同じコールの中で何回も再利用されるバッファを作成することは検討の価値がある。これはつまり、配列または List はヒープ上に割り当てられ、そして Update毎または何らか頻繁に発生する状況で、配列を作成することはガーベージコレクションのサイクルにつながってしまう。

void Update()
{
      if(readyToFire && Input.GetKeyDown(KeyCode.F))
      {
          var nearestFiveEnemies = new Enemy[5];
 
          // 最も近い敵をみつけて配列の
          // リストを埋める動作を行う
 
          TargetEnemies(nearestFiveEnemies);
      }
}

この例ではもっとも近い 5 人の敵をターゲットとしていて、自動小銃で撃っています。問題は発射ボタンを押すたびにメモリを割り当ててることだ。もしその代わりに生存期間の長い、コードのどこででも使用できる敵の配列を作成すれば、メモリを割り当てる必要がなく、結果的にスローダウンを防止できる。

次がもっともシンプルなケースとなる:
Enemy[] _nearestFiveEnemies = new Enemy[5];
 
void Update()
{
      if(readyToFire && Input.GetKeyDown(KeyCode.F))
      {
          // 最も近い敵をみつけて配列の
          // リストを埋める動作を行う
 
          TargetEnemies(_nearestFiveEnemies);
      }
}
しかしもしかすると、敵の配列が頻繁に必要であればもっと汎用的とすることができるかもしれない。
Enemy[] _enemies = new Enemy[100];
 
void Update()
{
      if(readyToFire && Input.GetKeyDown(KeyCode.F))
      {
          // 最も近い敵をみつけて配列の
          // リストを埋める動作を行う
 
          TargetEnemies(_enemies, 5);
      }
}
TargetEnemies のコードを書き直してカウントをとるようにすることで、効率的に一般的な目的でコードのどこからで使用できる、敵のバッファを使用して、さらなる割り当ての必要性を回避できる。

二つめの例はかなり極端なケースで、これは本当に大きくメモリ問題を引き起こすようなデータコレクショがある場合に限るべきだ - 一般的には数バイトのメモリ節約よりもつねに読みやすく、分かりやすいコードを優先すべきだ。
static 変数およびメモリ管理のデモのビデオをふたつ用意した。

このビデオで使用されたプロジェクトはここからダウンロードできます。


----

長らく翻訳してきたUnity Gemsメモリ管理についてはこれでひととおり完了だ。

機会があればまとめ記事として整理してみるぜ!

2013年1月12日土曜日

Unityメモリ管理「Static関数」

前回のUnity Gems記事に続いて、Unityメモリ管理「Static関数」について翻訳を紹介する:

-------
http://unitygems.com/memorymanagement/

October 21, 2012

---------

Static 関数


Static 関数は非Static クラスで実装することが出来て、この場合クラスの Static メンバのみが関数の中でアクセス出来る。static 関数はクラスをinstanceでなくclass経由でコールされ、存在してないかもしれないメンバをアクセスするのは不具合のもとであるため理屈に合う。

逆に、引数を渡すことは可能であり、この種の関数を使用することは良くあるはずだ。
Vector3.Distance(vec1.vec2);
vec1.Normalize();
 
Application.LoadLevel (1);
 
if (GUI.Button(Rect(10,10,50,50),btnTexture)){}
このリストはどこまでも続けることが出来る。二つめの例は static メソッドでないことに留意してほしい。Vector3 型 の vec1インスタンスを通してコールされる。その他の例は static 関数をコールするためにクラスが使用されることを示す。

Static 関数は非Static関数よりも速い傾向があり、それはインスタンスの関数のコンパイラのコールはインスタンスの存在チェックでわずかなオーバーヘッドが生じるためであり、static 関数の場合はクラスが存在することが保証されている。ただし、この時間はきわめて小さな差だ。

もうひとつの Static クラスの長所はその存在が永続的でコンパイラに既知であるため、C# では、拡張メソッドを定義するために使用できることだ - これらは、どのような型の変数にでも追加できる関数のようなメソッドだ。これも static の良いところで、この方法で拡張したクラスは実際には開かれておらず(private および protected 変数は拡張メソッドで利用可能でない)、しかし、既存オブジェクトに新しい関数を提供するのに良い方法であり、開発者にとってコードが読みやすく、より簡単なAPIにつながる。例えばTransform に新しい関数を追加できる。
transform.MyWeirdNewFunction(x);

拡張関数の例:

既存のものを拡張する配列のための関数を作成したいとして(順に並べる、ソートの関数、等)、static 関数を宣言してこのリストを拡張することが出来る。
using UnityEngine;
using System.Collections;
 
public static class ExtensionTest {
 
    public static int BiggestOfAll(this int[] integer){
        int length = integer.Length;
        int biggest = integer[0];
        for(int i = 1;i < length;i++){
            if(integer[i] > biggest)biggest = integer[i];
        }
        return biggest;
    }
}
static 関数を宣言していて、渡された引数が関数をコールしているインスタンスであることを確認して下さい。この引数の型を修正することは他のオブジェクトで作業することを可能にします。

次に配列を宣言して値で埋めます。
using UnityEngine;
using System.Collections;

public class Test : MonoBehaviour {

        int[] array = {1,25,12,120,12,6};

    void Start () {
        print(array.BiggestOfAll());
        }
}
配列名のドットを入力すると、自身の新しい関数が候補として自動表示されるはずだ。

新しくより汎用的な、型に関わらず配列を受け入れる関数を作成すると決めた場合はstaticなジェネリック拡張メソッドを実装する。
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
 
public static class ExtensionTest {
 
    public static T TypelessBiggestOfAll <T> (this T[] t){
        int length = t.Length;
        var biggest = t[0];
        for(int i = 1;i < length;i++){
            biggest =((Comparer <T>.Default.Compare(t[i], biggest) > 0) ? t[i] : biggest);
        }
        return biggest;
    }
}
using System.Collections.Generic; が追加されていることに留意してほしい。コンパイラはTの型が分からないため、値を < または > で単純に比較できない。しかし Comparer<T> 使用できる。例では、3つ組の演算子が使用されている。始めのうちはコーディングが少し難しいかもしれないが、行数を何行か節約できる。
using UnityEngine;
using System.Collections;

public static class GameManager{

    public static void ClampTransform(this Transform tr,Vector3 move, Vector3 min, Vector3 max){
        Vector3 pos = tr.position + move;
        if(pos.x < min.x ) pos.x = min.x;
        else if(pos.x > max.x) pos.x = max.x;

        if(pos.y  < min.y ) pos.y = min.y;
        else if(pos.y> max.y) pos.y = max.y;

        if(pos.z < min.z) pos.z = min.z;
        else if(pos.z > max.z) pos.z = max.z;

        tr.position = pos;
    }
}

3つ組の演算子は次のように表現される:       a ? b : c;
a が実行され、もし true ならば b がコマンド実行され、もし false ならば c がコマンド実行される。通常の if 文と比較した長所は 比較から戻された値にある。
       result = a ? b : c;           結果は a にもとづいて値 b または c を受け取る
これにより配列の異なる型を宣言して、同じ関数を使用することが可能だ。
using UnityEngine;
using System.Collections;

public class Test : MonoBehaviour {

    int[] arrayInt = {1, 25, 12, 120, 12, 6};
    float[] arrayFl = {0.5f, 52.456f, 654.25f, 41.2f};
    double[]arrayDb = {0.1254, -15487.258, 654, 8795.25, -2};

    void Start () {
        print(arrayInt.TypelessBiggestOfAll());
        print (arrayFl.TypelessBiggestOfAll());
        print (arrayDb.TypelessBiggestOfAll());
       }
}

これにより 120, 654.25, 8795.25 の値が出力される。

次にこれが便利となる事例を示したい。多くの Unity ユーザはオブジェクトを領域内に制限する方法を聞く。例えば、オブジェクトを画面の範囲内に保持したい場合、次の関数を使用してオブジェクトの Transform を はさむ(Clamp)することができる。
そしてこのようにして必要なところでどこでも使用することができる:
Vector3 minVector = new Vector3(-10,0,-10);
Vector3 maxVector = new Vector3(10,0,10);
transform.ClampTransform(move,minVector,maxVector);
この例では、オブジェクトは平面(y が 0 で固定して制限されていて、 (0,0,0) を中心に 20 x 20 の箱の中に収められる。
------

2013年に入ってもUnityの学習を積極的に進めようぜ!

2013年1月8日火曜日

Unityメモリ管理「Static変数」



前回のUnity Gems記事に続いて、Unityメモリ管理「Static変数」について翻訳を紹介する:

-------
http://unitygems.com/memorymanagement/

October 21, 2012

Static変数


多くの初心者は次の簡単な間違いをしている:
public static int health;
void Start(){
    health=100;
}
別のスクリプトでは次のことを見つけるかもしれない。
void OnCollisionEnter(Collision other){
    if (other.gameObject.tag == “Enemy”){
        EnemyScript.health-=10;
        if(EnemyScript.health <=0)Destroy(other);
    }
}
ゲームを一つの敵で初めて殺すことに成功する。しかし次に敵を追加してみて、なぜ同時に殺されるのだろうと悩むことになる。

理由は簡単で、enemyクラス(staticでない)のインスタンスがたくさんあるがhealthクラスが全てのインスタンスが1つしかないためだ。ひとつ殺すことで全部殺されてしまう!

実際、クラスで宣言されたStatic変数はオブジェクトに属しておらず、クラス自身に属する。

クラス名でなく、インスタンス名を使ってアクセスするのもこの理由のためだ。enemyのhealthについて機能しなかったのは、各enemyが各々のhealth変数を持つ必要があるためだ。

Static変数はStaticクラスと同じように、それを定義しているクラスが始めてアクセスされたときにインスタンス化される。つまり、ゲームでそのクラスのインスタンスがないにも関わらず、もしアクセスしようとすれば、そのクラスは存在することがある。

例えばenemyのカウンタ(counter)を作るとしてゲームでのenemyの数をトラッキングしていく必要があるとする。
static var counter;
 
public void CreateEnemy(){
    GameObject obj=Instantiate(enemyPrefab, new Vector3(0,0,0).Quaternion.identity);
 
    if(obj)counter++;
}
 
public void DestroyEnemy(){
    Destroy(gameObject);
    counter--;
}
このスクリプトは全ての敵にアタッチする各々別のスクリプトであっても全てが同じカウンタ変数を共有する。 この関数をゲームで使用することで、enemyのカウントをトラッキングすることが出来る。インスタンスがオブジェクト作成に失敗した場合を考慮して(例えばヒープのフラグメンテーションによって)参照の偏すが使用されていることをに留意する必要がある。失敗した場合Instantiateはhealthへの参照を返す。もしこのチェックをせずにカウントを増やしてしまうと、何らかの理由でインスタンス化に失敗した場合、カウンタが誤った値になる。もしカウンタをレベルの終わりに使用して新しいレベルロードすると:
if(counter<=0)Application.LoadLevel(nextLevel);
このチェックがなければ、失敗した場合、全ての敵を殺した後カウントが1となってゲームにバグが生じることになる。
ところでプレイヤーについてStatic変数のhealthを使用することは出来るのだろうか。答えとしては、プレイヤーにStatic変数を使用することが出来る。実際にStatic変数にアクセスするのはインスタンスメンバをアクセスするよりも効率的だ。
インスタンスメンバをコールするとき、コンパイラは小さな関数をコールしてオブジェクトの存在チェックを行う必要がある。存在しないデータを修正できるべきではないので、これは当然といえる。
-------

Unityメモリ管理の基礎を積み重ねていこうぜ!

2013年1月7日月曜日

Unityメモリ管理「Staticクラス」


前回のUnity Gems記事に続いて、Unityメモリ管理「Staticクラス」について翻訳を紹介する:

-------
http://unitygems.com/memorymanagement/

October 21, 2012


Staticクラス


Staticクラスはオブジェクトのインスタンス化ができないクラスでありStaticクラスのオブジェクトを作ることは出来ない。

実行すると1つのインスタンスしかない。先ほどインスタンス化ができないと書いたばかりなので混乱したかもしれないが、要はプログラムがこのオブジェクトをヒープに割り当てて、ユーザによるインスタンス化は許可しないということだ。

Staticオブジェクトは最初のコール時に作成されて、プログラムの終了時に破棄される。(あるいはクラッシュ時・・・)
public static class GameManager{
    public static int score;  
    public static float amountOfTime;
}
Game Managerというオブジェクトの型を宣言することは出来ない。
GameManager GM = new GameManager();
これはエラーを返す。
Staticクラスのメンバにアクセスするには単に:
GameManager.score = 10;
これはゲームのどの箇所からもどんなタイミングでもアクセスできます。Staticなクラスまはメンバは全てのファイルが有効範囲であり、プログラム全体が生存期間となる。

Staticクラスの使用例


新しいシーンをロードしたとき、前のシーンの全ての変数は破棄される。もしある変数を保持したい場合、例えばスコアを残す場いい、次のように記す:
DontDestroyOnLoad(score);
Staticクラスの便利なところは生存期間にある。なくなることがないため、シーンのロードのときも保持させたい場合はStaticクラスを使用すれば良い。レベル終了時に値をStaticメンバに渡し、新しいシーン開始時にその値を取得する。
DontDestroyOnLoad(score);
if(newLevel){
    GameManager.score = tempScore;
    Application.LoadLevel(nextLevel);
}
DontDestroyOnLoad(score);
また新しいシーンのスクリプトには:

void Start(){
    scoreTemp = gameManager.score;
}
ちょっとしたテクニックとして、GameManager.scoreはギリギリ最後まで使用を控えることだ。

例えばレベルが10個あって、レベルごとに最大200点獲得できるとする。

さらに別のプレイヤーがゲームを終えるまでに何度もリプレイした後に5000点獲得したとする。

二人目のプレイヤーは一人目ほどのスコアでなかっが、それでもより多くの点を獲得できたのは、書くレベルを何度もリプレイをして得点を積み重ねたためだ。次のレベルロードを待ってStatic変数の地点をスキップする場合、リトライによってそのレベルでの得点の積み重ねはキャンセルされます。

ゲームを中断するとき、Staticクラスのデータはセーブすることが出来る。

StaticクラスおよびStatic変数はプログラム実行で初めて登場したときに割り当てられます。Staticクラスは実際にそのクラスの変数またはメソッドにアクセスするまで作成されることは絶対にない。

-------

Unityメモリ管理の基礎を積み重ねていこうぜ!

2013年1月6日日曜日

NGUI 2.2.7



NGUIバージョンアップ情報です。

http://www.tasharen.com/forum/index.php?topic=11.15
2013/1/5 at 11:05:43 AM

2.2.7:
- 新機能: UICamera.stickyPress オプションを追加し、ひとつのタッチから複数のオブジェクトが OnPress通知を受け取れるようになった。
- 新機能: UICamera.hoveredObject がタッチイベントでも動作するようになり、最後のRaycastの結果を保持するようになった。
- 新機能: "Edit" ボタンを全ての Atlas と Font フィールドに追加し、Atlas/Fontの修正をより容易にした。
- 新機能: Localization.Localize を追加した。なお Localization.Get を static にしてしまいたかったが、下位互換性がなくなるため行っていない。
- 修正: 付属サンプルの Inventory が Unity 4.0 でも正しく動作するように修正。
- 修正: UILabel.text を null にセットできるように修正。
- 修正: UIPanel が場合により rect を正しく描画しなかった不具合を修正。
- 修正: Andrew Osborne 氏により、いくつかの微調整および修正を反映 (コミュニティメンバからの貢献)
- 修正: クリッピング パネルに属する UITexture のmainTexture での不具合を修正。
-------

筆者の環境ではアップグレード後にちょっと不具合が出ているため参考までに追記します。
(直れば後日に追記します)
<2013/1/6 9:30 追記>
NGUI作者が投稿から2時間ほどで以下の不具合の修正版をアップしてくれ、解決しました ^^

http://www.tasharen.com/forum/index.php?topic=2692.0


<追記ここまで>


不具合内容:


  • Example 9 - Quest Log、Example 10 - Localizationで境界線が紫色になってしまう
  • NGUI 2.2.7、Unity 4.0.0.f7


【変更前】

【変更後】



ブックマークに追加

このエントリーをはてなブックマークに追加

自己紹介

自分の写真
Unity3D公式マニュアル翻訳やってる人がスマホ(iPhone, Android)のゲーム開発しています。気軽に面白く初心者が遊べる内容がモットー。Blogでは開発情報をひたすら、Twitterではゲーム作成の過程で参考にしている情報を中心につぶやきます

ページビューの合計

過去7日間の人気投稿