2012年12月27日木曜日

Unityメモリ管理「構造体とクラスの比較」


前回記事に引き続いてUnityメモリ管理についてUnity Gemsより「構造体とクラスの比較」について翻訳を紹介したい:

-------

http://unitygems.com/memorymanagement/

October 21, 2012


構造体とクラスの比較


それぞれどの状況で使用すべきだろうか?クラスを使用することでオーバーヘッドとなる変数が追加されることが分かった。もし1000個のオブジェクトを作成するのであれば1000個の変数を作成するが、構造体の場合は生成しない。

次の例をみてほしい:
public class DogC{
    public string name { get; set;}
    public int age { get; set;}
}
 
public struct DogS {
    public string name;
    public int age;
}
 
using UnityEngine;
using System.Collections;
 
public class Memory : MonoBehaviour {
    DogC dogC = new DogC();
    DogS dogS = new DogS();
 
    void Update () {
        if(Input.GetKeyDown(KeyCode.Alpha1))
            Print (dogS);
        if(Input.GetKeyDown(KeyCode.Alpha2))
            Print (dogC);
        if(Input.GetKeyDown(KeyCode.Alpha3))
            print ("Struct: The dog's name is "+dogS.name +" and is "+dogS.age);
        if(Input.GetKeyDown(KeyCode.Alpha4))
            print ("Class: The dog's name is "+dogC.name +" and is "+dogC.age);
     }
 
    void Print(DogS d){
        d.age = 10;
        d.name = "Rufus";
        print ("Struct:The dog's name is "+d.name +" and is "+d.age);
     }
 
    void Print(DogC d){
        d.age = 10;
        d.name = "Rufus";
        print ("Class:The dog's name is "+d.name +" and is "+d.age);
    }
}
まず、3つ目の引数が期待される内容、すなわちdog構造体のnameとageがなくなり、出力できてないことに気付く。関数の終わりになくなっているのである。

クラスは、元のデータをアクセスできる参照を渡され、関数の中で修正し、これらの修正は関数が終わった後も残る。

この結果をどう捉えるべきか。もし構造体が非常に大きく例えば10変数があったとすると、案数に渡すときにスタックは10個のロケーションに広がる。もし構造体の配列を50個渡すと、スタックは500ロケーション大きくなり、スタックはサイズに制限があるため、もし配列がさらに大きければ、スタックオーバーフローが起きるかもしれない。

もしクラスの参照を渡すとき、ひとつの変数のみ渡す。クラスのインスタンスを50個渡す場合はスタックを50変数のみ大きくさせる。

このためクラスはメモリを節約するが構造体は圧倒的に早い。ポイントはこの二つのバランスを取ることだ。構造体を使う判断をするのは、5つから8つより少ないときで、それ以上の場合はクラスを使うべきだ。

常に頭に入れる必要があることとして、構造体である変数をアクセスするとき(例えばtransform.positionとするとき)、その構造体の複製をスタック上に作成していて、その複製作業は作成に時間を要するのだ。

クラスを作成するときは必ずヒープ上にメモリを割り当て、これによりすぐに破棄した値のガーベージコレクションのサイクルにつながる。構造体は必ずスタック上に割り当てられ、逆にガーベージコレクションを引き起こすことがない。

Unityの構造体は普段3つから4つの変数のときに使用されていることに気付く、例えばposition、color、quternion、rotation、scaleなど、これら全てが構造体だ。

生存期間の短く、頻繁に参照されるオブジェクトは構造体にするのが良く、その理由はガーベージコレクションを引き起こさないためです。生存期間が長くサイズが大きいオブジェクトはクラスにするのが良く、その理由はアクセスする度にコピーされることがなく、継続的に存在することでガーベージコレクションを引き起こさない。Vector3などたくさんの小さいオブジェクトやRaycastHitはまさにこの理由でUnityのなかで構造体なのである。

先に進む前に、一番大事なことを最後にとっておいた。構造体を参照で渡すことは可能であり、それにより構造体を修正し、値を保持することが出来る。

もともと変数を定義した関数が終了した後に、構造体への参照を保持することは出来ず、これにより問題が引き起こされるケースはたくさんあり、例えばクロージャの使用(ラムダ関数やインライン関数により構成される)があるが、かなり上級者向けのテクニックである。
void PrintRef(ref DogS d){
    d.age = 10;
    d.name = "Rufus";
    print ("Structure:The dog's name is "+d.name +" and is "+d.age);
}
次のようにこの関数の呼び出しを行う:
void Update(){
    PrintRef(ref dogS);
    print ("Structure:The dog's name is "+dogS.name +" and is "+dogS.age);
}
キーワードのrefによってコンパイラに構造体へ参照をするように指令していて、スタック上で複製されていない。関数の外では、構造体は中で与えられた値を保持している。このキーワードはどのような値の種類でも使用できる。
------

メモリ管理の基礎を積み上げていこうぜ!

2012年12月26日水曜日

Unityメモリ管理「参照の作成」

前回UnityGems記事に引き続いてUnityメモリ管理についてUnity Gemsより「参照の作成」について翻訳を紹介したい:

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

October 21, 2012

参照の作成


ここまで値型変数に対する参照を作成することが出来ることを見てきた。関数の中で整数を作成しつつ、後続で値を使用するために保持したいことを考慮する必要がある。単に関数の中で宣言を行うと自動変数の作成が行われ、関数の終了とともに値は破棄される。

スクリプトの中で、参照つきでヒープ上に動的に割り当てられる変数を作成できる。
using UnityEngine;
using System.Collections;

public class Test:MonoBehaviour{
    int number;

    void Update () {
        if(Input.GetKeyDown (KeyCode.C))
            CreateVariable();
        if(Input.GetKeyDown (KeyCode.Space))
            PrintVariable(number);
    }

    void CreateVariable(){
         //int number = new int();
         number = new int();
         number = 10;
        print ("In function "+number);
    }

    void PrintVariable(int n){
        print (n);
        n+=10;
    }
}
最初にint型のグローバル変数numberを宣言します。この変数をCreateVariable関数の中で使用し、int()を用いて整数の値参照を割り当てます。

状況を整理するために、newキーワードは、ヒープ上に作成される変数に対する参照を戻します。参照は、新規に作成された変数の番地を保持するnumberに格納されます。新規作成された変数は関数の外に存在しています。

コメントアウトされた箇所について、これを含めてしまうと関数が終了したタイミングで参照を失ってしまいます。ガーベージコレクションの次回処理するタイミングで、参照がなくなっていることが判明し、メモリから取り除かれます。ローカル変数を宣言するとき、関数のなかで整数numberに対して行ったときと同様に、グローバルの整数numberが隠されます。

intのようなプリミティブ変数を保持するとき、参照のなかでコンパイラはボクシングと呼ばれる、クラスの中のプリミティブ変数をラッピングする、関数を実行します。この方法で宣言された変数はヒープ上に割り当てられ、解放されたときに、ガーベージコレクションを発生させ、さらにデフォルトのプリミティブな変数よりもメモリを消費します。

基本的なボクシングの状況を想定してみます。Debug.log(object)関数を使用するとき、パラメータはどのような変数でも関数に渡すことが出来るという意味のオブジェクトです。整数を渡すとき、コンパイラはオブジェクトの中に整数を箱詰め(ボックス)します。次にボクシングを使用することによって、最適化することが出来ます:
void PrintingManyTimes(int n){
    object obj = n;
    for(int i = 0;i<500;i++)
        Debug.Log(obj);
}

このようにしてコンパイラはDebug.Logをコールする度にボクシングを行う必要がなくなります。一回の実行だけで、500コール全てに対応します。
 -------

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

2012年12月24日月曜日

Unityのネットワーキングアセット:TNetがリリース


Unityのネットゲーム開発が今後楽になるかもしれない。

このブログでも取り上げたネットワーキングアセット「TNet」が本日12/24リリースされたからだ。作者はUnityのNo.1アセットであるNGUIのMychael Lyaschenko氏である。

現時点では日本語の情報はないために、使用するうえでは色々大変ではあるとおもうが、英語圏の人向けにトップページ、ビデオ紹介、ドキュメント、チュートリアルが整備されているので紹介したい。

ネットワーキング?何のこと?という人はこのブログを見ていない気がするが、イメージがつかない人はビデオ紹介の01:30-01:45だけ見たら「TNet」で何を簡単に開発できるようになるか分かる。


参考記事

ネットゲーム開発に風穴を空けるか?アセットストアNo.1作者のデザイン
http://gamesonytablet.blogspot.com/2012/12/no1.html
ネットゲーム開発に風穴を空けるか?アセットストアNo.1作者が新たなチャレンジ
http://qiita.com/items/4572392ad417f81d7bda


------------


トップページ


ビデオ紹介




ドキュメント
http://www.tasharen.com/?page_id=4523

チュートリアル

http://www.tasharen.com/?page_id=4537

サポート掲示板

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

Asset Storeリンク

http://u3d.as/content/tasharen-entertainment/tnet-tasharen-networking/3QM
----

筆者はまだダウンロードしていない。それにしてもAsset Storeでみると、この作者の過去のAssetは全て☆☆☆☆☆で評価はカンペキだね。。。

ネットゲーム開発も手軽に進めようぜ!




2012年12月20日木曜日

ネットゲーム開発に風穴を空けるか?アセットストアNo.1作者のデザイン

Unityを使ったことある人ならばアセットストアのことはご存知のこととおもう。

知らない人のために説明すると、これは、ゲームの部品を売り買い出来るマーケットであり、3Dキャラクターのモデルから人工知能(AI)、開発エディタのアドオンまで幅広く追加部品を開発者が低価格で購入できる仕組みだ。

2012年度にアセットストアで売り上げがトップだったNGUIというソフトウェアの作者は1000ドル(約80万円)以上を1日で稼ぐ日もあるほどの稼ぎ頭で、今年にはUnity Technologies本社にも採用されたのだが、ネットゲームのためのフレームワーク開発をひそかに進めていて、まもなくリリースすることをNGUIフォーラムの掲示板で発表したというので驚きだ。


NGUIというソフトが有名だったのは、使い勝手の良さだけでなく芸術的ともいえるコーディング技量の高さが挙げられる。

いったいどんな設計を進めてきているのだろうか?

NGUI公式フォーラムで公開された内容を翻訳する:

-----------
http://www.tasharen.com/forum/index.php?topic=2513.0
on: December 10, 2012, 05:16:55 PM

ちょうど1年前にゲームを作成するときにユーザインタフェースを開発して、アセットストアに半ば思いつきでリリースした。これがNGUIの始まり。かなりヒットした。皆が気に入ってくれたのはシンプルさ、デザイン、コメントをふんだんに入れてエレガントに書かれたコードだと自負している。

NGUIのヒットによって、ゲーム開発が止まったものの我慢強く続けて、6ヶ月後にはゲームにマルチプレイヤー機能を追加した。すでにあるソリューションのトップ3を試したのだけど、究極的にはどれにも満足できなかった。結局は時間の制約もあって、そのうちの1つを選択した。でも強い想いとして自分でコードを書いてみたいなという気持ちがずっとあった。NGUIから学んだ全てを詰め込んで、シンプルで、強力で、柔軟で、十分に練り込まれたオープンソースを作りたい、と。

ついぞ数週間前に、まさにそれを始めることにした。新しいプロジェクトの名前はTasharen Networking Framework、略して「TNet」だ。このプロジェクトはもうじきリリースできる準備が整った。

で、どんな機能があるかっていうと:
  • オープンソースである:完全にC#で書かれていて修正したい場合は自身のニーズに合わせられる
  • 可読性が高い:コードは他人のために書かれている:綺麗、かつ存分にコメント入れされている
  • 柔軟性が高い:サーバはスタンドアローン、あるいはUnityの中で別スレッドにできる
  • 順応性が高い:「ホスト」が切断されたら?次のホストを選択すればよく、ゲームは強制終了されない。もし全員が切断されたとしても、それでも状態の保存が出来る。
  • 持続性が高い:リモート ファンクション コールを後続に接続するプレイヤーのために保存が出来るだけでなく、ある時点のサーバー全体の状態を保存して再開ができる。もし再度スタートさせると、あたかもシャットダウンしていないかのごとく再開が出来る。(ある意味でどのプレイヤーもオートセーブ)
  • 強力である:高性能なソケット機能(I/O完了ポート)を存分に活かす
  • 拡張性が高い:サーバの各チャネルは独自のルール(および保存データ)を持った別のエリアとなり、帯域幅に合わせて好きなだけ同時にゲームのホスティングが可能
  • プライバシー遵守:一人で遊ぶ場合、パスワード保護するかビシッとロックする。そのときもチャットを受け取ることは当然できる。
  • 一貫性がある:あちらこちらに if ステートメントを入れる必要がない。マルチプレイヤーで使えるコードはそのままシングルプレイヤーでも使用できる。コールバックの順序は保たれる。例えば、プレイヤーは「切断」の前に「チャネルを退出」通知を受け取ることができる。
  • 効率的である:頻繁にリモート ファンクション コールを最適化して、関数名の代わりにバイトIDを指定できる。通信量を節約する。
  • 分かりやすい:リモート ファンクション コール文法とパラメータの数を自ら調整できる。配列?余裕っす。バイナリ?当たり前っしょ。パラメータが100個?えっと出来るよ(やりたいならね)。
  • エレガントである:同じLAN上のプレイヤーにサーバー接続してもらわなくても、メッセージをブロードキャストを受け取ることが出来る。ローカルサーバのアナウンスに理想的かな。
  • 便利である:サーバにダイレクトにファイル保存できて、あとでロードすることが可能。プレイヤーはアバターのサムネイル画像や、持ち物データ、あるいはゲームマップ全体を他人にロードしてもらうことが出来る。

もっと機能はあるんだけど・・・もう十分に語ったかな。基本的な使用方法の説明に移るとするかな:

質問:ゲームの開始、終了の関数は?
TNServerInstance.Start(port, [fileToLoad]);
TNServerInstance.Stop([fileToSave]]);

質問:接続、切断の関数は?
TNManager.Connect(address);
TNManager.Disconnect();

質問:チャネルへの入室と退出の関数は?
TNManager.JoinChannel(id, levelToLoad);
TNManager.LeaveChannel();

質問:新規オブジェクトの作成と破棄の関数は?
TNManager.Create(gameObject, position, rotation);
TNManager.Destroy(gameObject);

質問:リモート ファンクション コールの送信方法は?
TNObject tno = GetComponent<TNObject>(); // TNBehaviourから継承した場合はこの行スキップ
tno.Send("FunctionName", target, <parameters>);

質問:ビルトインの通知はどういうものがある?
OnNetworkConnect (success, error);
OnNetworkDisconnect()
OnNetworkJoinChannel (success, error)
OnNetworkLeaveChannel()
OnNetworkPlayerJoin (player)
OnNetworkPlayerLeave (player)
OnNetworkPlayerRenamed (player, previousName)
OnNetworkError (error)

質問:サンプルは?
Windows
Mac OS X
Android

----------
最後の質問のリンクからダウンロードできるサンプルは見ごたえがある。Unity Advent Calendar 2012のQiita記事として取り上げたので、実際の動きを見てみたい方はそちらを見るとよいとおもう。

ネットゲーム開発に風穴を空けるか?アセットストアNo.1作者が新たなチャレンジ
http://qiita.com/items/4572392ad417f81d7bda

Unityに詳しい方はこの領域にはPhotonやuLinkを使ったことがあるかもしれないが、掲示板で作者からベータ版を懇願して入手した人のコメントをみるかぎり、それよりも強力だとの声がすでに挙がっている。

作者は今週の日曜日(12/23)ぐらいに$45のアセットとしてリリースしたいと掲示板で語っている。前作のNGUIでは機能限定の無料バージョンもリリースしているので実際のプライシングがどうなるかはこれから注視するしかない。

大げさかもしれないが、来年のアセットストアNo.1をこのソフトで狙っているのかもしれない。個人的にはその勢いでポイント、課金、やガチャ機能までつけたら最強なのだが。


関連記事

大手ゲーム会社から独立の苦悩を語る(前半)
http://gamesonytablet.blogspot.com/2012/10/blog-post.html

大手ゲーム会社から独立の苦悩を語る(後半)

2012年12月18日火曜日

Unityメモリ管理「参照型とヒープ」

Unityでゲームオブジェクトのインスタンス化をする際に、バックグラウンドでのメモリ管理はどうなっているのだろうか。

前回記事に引き続いてUnity Gemsよりメモリ管理について「参照型とヒープ」について翻訳を紹介したい:

http://unitygems.com/memorymanagement/

October 21, 2012


参照型とヒープ


参照型の変数はメモリに参照として格納されたオブジェクトだ。新しいクラスのインスタンスを作成する際に、データはヒープに格納されるとともに、そのデータの番地に関する参照も格納される。この参照は、オブジェクトのアドレスを値として持つ変数だ。クラスのインスタンスを作成する際は、new キーワードを指定して、これにより、オブジェクトの型に対応したメモリの領域確保のためOSへコール要求を行い、さらにオブジェクト初期化に必要なコードを実行する。下の例で犬のクラスDogのインスタンス化を見ていく。
public class Dog{
     public string name { get; set;}
     public int age { get; set;}
     public Dog(string s, int n){
           name = s;
           age = n;
    }                          
}
public class Test : MonoBehaviour {
    Dog dog1 = new Dog("Rufus",10);
    Dog dog2 = new Dog("Brutus",8);
 
    void Update (){                                                  
        if(Input.GetKeyDown(KeyCode.Alpha1))
            print ("The dog1 is named "+dog1.name+" and is "+dog1.age);
        if(Input.GetKeyDown(KeyCode.Alpha2))
             print ("The dog2 is named "+dog2.name+" and is "+dog2.age);
    }
}
dog1とdog2変数は、これらのオブジェクトが格納されるメモリの番地に対する参照に該当する。

次にInstantiate関数の活用を見ていく。例えばEnemyというPrefabがあり、3Dモデルといくつかのコンポーネント、スクリプトが含まれるとする。
using UnityEngine;
using System.Collections;
 
public class Test : MonoBehaviour{
    public GameObject enemyPrefab;
 
    void Update(){
        if(Input.GeyKeyDown(KeyCode.Space))
               Instantiate(enemyPrefab,new Vector3(0,0,0), Quaternion.identity);
    }
}
enemyPrefabの新しいインスタンスを作成し、実行も出来ている。問題はこのオブジェクトに対してアクセスしたくとも参照をもっていない点である。新しく作成したオブジェクトにアクションを加えたい場合、参照とするための変数を作成することになる。
void Update(){
    if(Input.GeyKeyDown(KeyCode.Space))
       GameObject enemyRef = Instantiate(enemyPrefab, new Vector3(0,0,0), Quaternion.identity);
        enemyRef.AddComponent(Rigidbody);
        Destroy(enemyRef);
     }
} 
この例では、enemyRefが宣言され、新しいオブジェクトの番地を割り当てしている。enemyRefがその参照となっている。変数がオブジェクトの番地を知ることが出来るため、てコンポーネントを追加できて、オブジェクトのパブリック変数を追加またはアクセスすることが出来る。最終行では、実践的ではないものの説明の都合上、すぐにオブジェクトを破棄している。

参照が{}の中でのみ存在する自動変数であることに留意すべきだ。その外では、参照変数はもはや存在していない。enemyオブジェクトを取得するためには、例えば次のような方法で探す必要がある:
GameObject enemyRefAgain = GameObject.Find(“Enemy”);
全ての参照型変数はヒープに格納される:

  • クラス
  • オブジェクト
  • 文字列(stringは変数に見えるが、実際はStringクラスのインスタンス)

値型変数と参照型変数の主な差異は、データが格納されている場所を示す、参照型変数の追加である。データを探す前に、この参照にアクセスする必要がある。これにより直接アクセスすることが出来る構造より若干遅くなる。

 他に、問題となってくるポイントは、次のような処理である:
using UnityEngine;
using System.Collections;

public class Memory : MonoBehaviour {
    DogC dogC1 = new DogC();
    DogC dogC2 = new DogC();
    int number1;
    int number2;
 
    void Update () {
        if(Input.GetKeyDown(KeyCode.Space)){
           number2=number1 = 10;
           number1 = 20;
           print (number1+" "+number2);
           dogC1.name = "Rufus";
           dogC1.age = 12;
           dogC2 = dogC1;
           dogC1.name = "Brutus";
           print (dogC1.name+" "+dogC1.age+" "+dogC2.name+" "+dogC2.age);
        }
    }
}

二つのnumberが独立していて、片方を変更しても、もう片方が変更されないことに気付く。

逆に、もうひとつにクラスを割り当てると、片方への変更がもう片方へ反映される。

これは dogC2 = dogC1; としたときにdogC1のアドレスをdogC2の参照変数に割り当てるためである。dogC2データの元の番地はメモリで失われ、dogC1またはdogC2どちらを変更しても同じになる。これにより同一のデータ番地に対する二つの参照が出来たことになる。

生存期間、有効範囲、およびガーベージコレクション


参照型変数の場合、生存期間は明確でなく、ガーベージコレクションがその管理を行う。CやC++の古き時代において、プログラマが動的に割り当てられた変数がメモリから解放されることはプログラマの仕事であった(C言語ではfree()関数、C++言語では~記号)。

C#ではガーベージコレクションにより変数が未だ使用されているかチェックを行い、もし参照が実行コード中にない場合、メモリの番地が解放できるものとしてマーキングを行う。COMインタフェース、あるいは参照カウンタのシステム、になじみのあるプログラマにとってはガーベージコレクションに違和感を感じることがあり、なぜなら参照カウンタのシステムでは2つのオブジェクトが互いに参照しあうことは(例えば親が子への参照を保持し、その子が親に対する参照を保持する)、メモリが永久に解放されない要因となるためであるが、ガーベージコレクションにおいてはそのような制約は存在しない。

ガーベージコレクションは大きなテーマであって、このコレクションのプロセスの実装にあたって、伝統的なC#になじみがある人にとってUnityはいくつかおかしな点があるように感じられ、それは短い生存期間のオブジェクトにとってガーベージコレクションの負荷は小さいと期待するためであるが、これはUnityでは当てはまらない。

メモリのブロックが埋められるとき、システムはヒープに割り当てられた全てのオブジェクトに到達できるか、をチェックする必要があり、すなわち到達出来る場合はオブジェクトはretainされそうでない場合はreleaseされる。通常の.NETガーベージコレクションはGenerationと呼ばれる単位で行われ、この到達できるかチェックする処理負荷を抑えるために、新しく作成されたオブジェクトを先にチェックし、その他の古いオブジェクトをチェックするかどうかはメモリが不足している場合のみチェックしている。Unityは常に全てのオブジェクトをチェックしている様子であるため、ガーベージコレクションは.NET言語で一般に期待するよりも遥かに大きなオーバーヘッドとなる。

到達出来るかのテストはガーベージコレクションの鍵を握っていて、これによりコードが実行中であるか、その可能性があるかどうかをコレクションの際に、有効な参照があるかどうか、コレクション対象のオブジェクトに対する参照があるかどうか、などで判断している。これは関数の中にクロージャがあるかどうかも含まれ、このことは非常に複雑で強力であるがゲームで必要とされるスピードという観点では不足している。

クロージャは、関数の中で定義された無名関数の中でローカル変数またはパラメータを参照することにより作成される。変数の値は、無名関数が呼び出しされたときにルーチンが実行されたときと同じ変数が使用できるよう、保持される。
List<action> actionsToPerformLater = new Listt<action>();

void DisplayAMessage(string message)
{
      var t = System.DateTime.Now;
      actionsToPerformLater.Add(()=>{
          Debug.Log(message + " @ " + t.ToString());
      });
}

void SomeOtherFunction()
{
      DisplayAMessage("Hello");
      DisplayAMessage("World");
      SomeFunctionThatTakesAWhile();
      DisplayAMessage("From Unity Gems");

      foreach(var a in actionsToPerformLater)
      {
           a();
      }

}
このコードの中ではDisplayAMessageをコールする度に、渡されたメッセージおよび現在時刻にもとづいて、クロージャを作成する。最後にforeachループを実行するとき、先の関数を呼び出ししたときに渡された変数をリストに追加して、デバッグメッセージのログが作成される。クロージャは非常に強力なツールで、将来の記事でより深く取り上げたい。

ガーベージコレクションは、いくつかの処理のためにシステムが十分にメモリがないと判断したときに実行され、すなわちオブジェクトが破棄されても、かなり長い期間の間コレクションの対象とならないことがあるということで、通常はオブジェクトが必要なくなったときに、明示的に外部接続をクローズすることや、外部リソースを解放することを行う。

内部オブジェクト(プロジェクト内のクラス)を明示的にリリースすることは必要がないが、プロジェクトの外にあるもの、例えばストリーム(ファイル)およびデータベース接続の場合などシステムに影響がある場合はそれを行うべき理由がある。例えばファイルをクローズしないことで、ファイルが破棄されてもオープンのままでいた場合、後続のアクションにてファイルのアクセスに失敗する可能性があるためだ。

ガーベージコレクションは高価な処理で、ゲームの実行に影響のないタイミングで実行することは有意義で、例えばレベルロードのとき、プレイヤーがポーズメニューを選択したときや、その他プレイヤーが気付きにくいタイミングで行うべきだ。ガーベージコレクションを手動でトリガーするためには System.GC.Collect(); を用いる。
参照型変数の有効範囲について、適切なアクションにて探すことが出来ることを前提に、プログラムのどこからでもアクセスすることが出来て、どのスクリプトからもGameObject.Find()を使用してオブジェクトに対する参照を見つけることが出来る。

ゲーム実行中にインスタンスを探す場合の詳細についてはGetComponentチュートリアルを参照してほしい。
------

少し長かったとおもうが、色つき部分を読み返してみて、あらためて初めて知った内容について復習すると良いとおもう。

Unityメモリ管理の学習を進めていこうぜ!

2012年12月17日月曜日

Unityメモリ管理「値型とスタック」

前回記事に続いてUnityメモリ管理についてUnity Gemsより翻訳を紹介したい:

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

値型とスタック


自動変数はautoで定義されていた。現在はC#で使用されないため、このキーワードを見たことがないかもしれない。昔utoはデフォルト変数を意味していた。
これら2行は全く同じ内容だ。
int variable = 10;
auto int variable = 10;
自動変数の機能はコールスタックに変数を割り当てることだ。スタックとは変数の積み重ねであり、例えばお皿の積み重ねるときのように一番上の皿を減らしたり増やしたりすることは出来るが、下の皿を減らしたり増やしたりすることが出来ない。下記の図を参照してほしい。

スタックはLIFO(Last-In-First-Out)と呼ばれ、その名前どおりに最後に入力したものを最初に出力する。

スタックの一番上が何であるか識別するために、プログラムはスタックポインタと呼ばれるものを用いて、CPUの変数によりスタック上の現在の位置を上図の黒い矢印のように管理する。変数を増やすと、スタックポインタをインクリメント(またはOSのメモリ管理手法によってはデクリメント)する。

スタックは関数コールのために用いられ、プログラム全体はUpdate関数を呼ぶメイン関数であり、それによって他の関数を呼ぶのであり、スクリプトで用いる変数は、意図的に指定しないかぎり、どれもスタック上に乗せられた自動変数となる。

スタックフレームという概念も理解する必要があり、これは単に現在の実行関数によりローカル割り当てされた変数の集合である。(また、戻り番地と呼ばれる、関数が戻った直後に実行される命令の番地、も含まれる)コンパイラはスタックフレームの終わりにてポインタを管理し、そして実際にこのポインタから負のオフセットを用いてローカル変数が占有するメモリを見つける。実際上の利点は、関数が戻った後ローカル変数により領域が使われたり割り当てされることがないことであり、次の関数コールのための領域を確保できることです。

もし再帰プログラム(自らをコールする関数)を記述した場合、後続のコールは全てのローカル変数を必要とし戻り番地はスタック上の領域が必要であるため、潜在的にスタックの領域が不足する可能性があり、またさらにもしコードのバグにより止まることなく自らを呼ぶルーチンの場合、スタックオーバーフローのメッセージが表示される。

このため自動変数の生存期間と有効範囲は同調する。変数は宣言から始まって、波括弧 { から次の波括弧 } まで生存する。もう少し具体的に補足する。
void Update(){
    int number = 10;
    Fct(number);
    print(number);
}

void Fct(int n){
    int inside=20;
    n = n + inside;
    print(n);
}
Update関数がコールされ、最初の命令は変数宣言である。スタックポインタはひとつ押し上げられ、値の数がメモリの番地に記憶される。

二つめの命令は関数コールだ。関数を呼ぶとき、プログラムは停止して関数のアドレスにジャンプして、プログラムの元の位置を取得するため、いくつかのデータがスタックに渡される。パラメータ(がある場合)は、元の値の複製をすることで、スタックに記憶させる。

変数の number は、関数で使用されるためにスタックに記憶されず、新しい変数 n が作成されてスタック上に記憶され変数に number が割り当てられる。

これにより変数 n が関数の中にあり、変数の number を保持するが、その number はFct()には存在しないため見ることが出来ず、参照されることがない。

関数のなかで新たな整数が宣言される。スタックは図で示したようなものとなる。(簡略化しているため注意)

関数の終わりまでの間に、n はprintされ値として30を保持する。関数の中で生成された全ての変数は破棄され失われる。それは n と inside を含む。これら変数はメモリにまだあるにも関わらず、単に無視されシステムによりもはや考慮されることはない。

Updateに戻って、number をprintするが、関数で呼ばれた変数そのものではなかく複製の値であるため、元の値のまま10である。もしUpdateに戻って参照しようとしても、スタックポインタは n の番地を失っていて、コンパイラは変数が有効範囲にないというエラーを表示させる。

ここまで自動変数の有効範囲を見てきて、変数が宣言された{}の中で定義されることが確認できた。この外では変数は存在せず、参照することが出来ない。これらの波括弧{}は関数、if 分岐、およびloopの有効範囲を示す。

生存期間とはプログラムの中で変数が存在する期間を意味し、有効範囲とはプログラムの中で変数を参照することが出来るゾーンを定義する。自動変数の場合、両方は共通点が多い。

生存期間と有効範囲


全ての値型変数はスタックに格納され、それらは:


  • int
  • unsigned
  • float
  • double
  • char
  • struct
  • bool
  • byte
  • enum
  • long
  • short


Cから継承したほとんど全ての型で、例外としてboolは過去存在しなかったぐらいだ。

自動変数にとって、生存期間はコンパイラにより管理される。宣言された有効範囲の終わりは、同時に変数の終わりとなる。プログラマはメモリのロケーションを後続のためにリリースすることを気にする必要がなく、自動的に行われる。

-----

んー、今回はUnityやC#に特化した話はなかったようにおもう。(あったとしても筆者のスキルでは気付けなかった・・・)

その意味では日本語で書かれた記事のほうが読みやすいかもしれない。参考までに同じようなテーマを扱っているITPro記事のリンクを紹介する。

ITPro記事(日経BP)
レジスタとスタックを使ってサブルーチン呼び出しを実行  
2007/06/05

プログラミングの基礎を積み上げていこうぜ!

2012年12月16日日曜日

NGUIでClipping Panelトラブルシューティング、5つのチェックポイント


NGUIでClipped Panelといえば、複数のアイテムからスクロールしてひとつ何かユーザ選択してもらうのに便利なUIのひとつだ。

ただし、設定が分からなくなって正しく動作しないときに何から調べたらよいか分からないのが難点のひとつだ。

NGUI公式フォーラムでトラブルシューティングの方法をFAQとして準備した様子なので翻訳して紹介したい:

http://www.tasharen.com/forum/index.php?topic=6.0
November 26, 2012, 12:30:19 AM

質問

Clipped Panelが正しくクリッピングされない。原因は?

回答


  1. シェーダがオンであることを確認。すなわちGLES1.1はターゲットに出来ず、Quality SettingをFastestにも出来ない。
  2. ウィジェットで使用するAtlasがUnlit/Transparent Coloredシェーダを使用していることを確認。もしSimple Textures (UITextures)が含まれるならば、それも、このシェーダとなっていることを確認。
  3. Clipped Panelが均一にScaleされていること:(1,1,1)
  4. クリッピングしたいウィジェットは、クリッピングされているパネルとの間に余分なパネルがあると正しく動作しない。Panel Toolを開き、クリップしたいウィジェットを選択し、どのパネルが選択表示(ハイライト表示される)されるか確認すること。もし、選択表示されているのがクリッピングされているパネルでない場合、削除すべき余分なパネルがあることを意味します。
  5. シェーダが正しくロード出来ることを確認。NGUIのResourcesフォルダを誤って移動していないか確認すること。

--------

NGUIで便利なUIを使いこなそうぜ!

2012年12月15日土曜日

Unityメモリ管理「メモリゾーン」

Unityはゲーム開発の敷居を下げてくれるがゆえにC#について.NETで学ぶ機会がなかった人も多くインディーズゲーム開発に関わるようになっていると感じる。

そのような人にとって、前回記事に引き続いてUnityのメモリ管理で何を学ぶべきか、短いページ数でまとめてくれているUnity Gemsサイトの情報は有難いとおもう。

前回の紹介に引き続いて「メモリ管理」について翻訳を紹介したい。
http://unitygems.com/memorymanagement/

October 21, 2012

メモリゾーン

プログラムを起動したとき、オペレーションシステムはメモリの一部をプログラムに割り当てる。過去は、それにゾーンが4つあり、コールスタック、ヒープ、レジスタ、とスタティックメモリだ。
C#言語の開発者によりゾーンはさらに2つに絞り込むことが出来ため、現在はスタック、ヒープのみだ。前者は順序があり、高速ですが、制約がある。後者はランダムの順序で、大きく低速だ。
各変数についてどちらを使用するか変数の目的とその結末によって選択できる。
storageクラスのキーワードは3つ(C言語ではregisterを含めた4つめがありました)あり、auto、static(今後説明していく)、およびexternだ。externは出くわすかもしれないキーワードだ。これは単に自らのプロジェクトの外で変数を宣言する、という意味であり、例えば、全く別の非.NET言語で書かれたプラグインDLLの変数だったりする。
C言語のプログラマならば、externの意味がC#のそれとは異なることに留意すべきだ。C#ではexternはManaged Memoryに割り当てされてない変数を指す。
extern int number;
コンパイラはnumberにメモリを割り当てせず、変数が別のどこかで見つかることを期待する。この内容を深堀りするのはここまでとする。さらに詳細は、このリンクにて、より多くの情報を確認出来る。
-------------

まずはキーワードから一つづつ丁寧に。Unity学習のなかにメモリ管理もトピックとしてお忘れなく!

2012年12月13日木曜日

必見!Unityメモリ管理について知るべき9つのこと


Unityのメモリ管理については、知っているつもりで知らない、ということがかなり多い。

筆者自身、このことについては疎いが、Unity Answersの常連が集まってUnityの知識をUnity Gemsというサイトに集約させようとい動きをしている熱心な方がいて、様々な記事を乗せている中にメモリ管理についても触れているため、そのUnity Gemsと呼ばれるサイトの管理者に事前に許諾を得たうえで、今回内容の翻訳を紹介をしたい。

なお、記事内容も多岐に渡っているため、かなりの回数に渡るブログ投稿となる点は事前に了解いただきたい。


-------

http://unitygems.com/memorymanagement/

October 21, 2012

ゲーム実行中にコンピュータあるいはデバイスのメモリ上で何が起きているのか、疑問に思ったことがある人は、この記事により、ゲームを上手に作成したりクラスや関数のメモリ使用を最適化するための技術的情報が得られると確信している。

  1. メモリのゾーン
  2. 値型とスタック
  3. 参照型とヒープ
  4. 構造体とクラスの比較
  5. 参照を作成する
  6. Staticクラス
  7. Static変数
  8. Static関数
  9. ヒープのフラグメンテーション、オブジェクトプール、およびガーベージコレクション
このチュートリアルは、UnityのためにC#に移行する、C/C++プログラマにピッタリである。C#は見た目とかなり違ってメモリを扱い、紛らわしかったり魔法のようだったりと最初は見えて、他のシステム同様に実際に落とし穴やクセがある。

Unityプログラミング、あるいは一般にプロミングを始めたばかりの場合、変数の型の目的を見誤ることがある。例えば、なぜいくつかの変数が更新された、なぜ残りは更新されなかったか、なぜまだ必要だったのに変数がなくなったのか、といった場合である。

初心者にありがちな問題はStatic変数の使用である。インターネットの様々なチュートリアルでで出会うけれども、作者の方がはっきりと書く手間を惜しんで結局「変数に簡単に参照する方法」にとどまりがちである。

問題は、Static変数(さらにはクラス変数)は簡単なトピックでないことである。

教える人によっては、単に使用するのを避けなさい、とだけ言って、Staticで出来ることは他の型でも出来ると教えてたりする。これから見ていくことはそれは仮に出来ないことはなくともベストの解決策ではないということである。

これから内容に深入りする前に、もしC言語の経験があってオブジェクト指向プログラミングの経験がないのであれば、メモリ管理について知っていることやstorageキーワードはオブジェクト指向プログラミングと似ても似つかないことを知るべきである。C#言語をC言語になぞらえることは避けるべきである。

このチュートリアルでは、異なるメモリのゾーン、値の種類、参照の種類、動的メモリ割り当てとStatic変数についてカバーする。
--------

次回より9つの記事を各々分けて紹介していく、ので長丁場ですがお付き合いください!

2012年12月12日水曜日

必見!Unity初心者が学ぶ「Rigidbodyの正しい移動方法」

前回投稿に続いて「Rigidbodyの正しい移動方法」についてUnity Gemsより紹介する:

------
http://unitygems.com/mistakes1/
October 15, 2012

物理エンジンPhysicsは最大のツールであり、プログラミングせずに様々なことを再現できるが、正しく使用することが前提となる。


物理エンジンを使用する場合、KinematicでないRigidbodyを移動する場合はAddForceなどを使用すること。


自前のロジックでRigidbodyを移動する場合FixedUpdateのなかで記述する必要がある。その場合も不自然な動きが発生することがあり、例えば予想外に大きすぎる/小さすぎる力が他のオブジェクトに作用することがある。Input関数はFixedUpdateのなかで記述することは出来ない。ユーザ入力の取得が必要である場合、Updateのなかで入力取得を行いFixedUpdateのなかで物理計算を記載する必要がある。


GravityやTimescaleは変更しないのが鉄則。オブジェクトの動作が速すぎたり遅すぎたりという場合は大きさやカメラからの距離に問題あるケースが多く、重力の大きさ(gravity)を変更することにより効果を調整すると予想外のエラーが多発する。初心者であるならば重力の大きさを変更せず、大きさやバランスを正しくすべきである。


オブジェクトに現実的な大きさを用いること。Unityの1単位は必ず1メートルとすること。人は170センチぐらい、車は3メートル半ぐらい、など。大きさを正しく設定し、カメラを適切な距離に配置することで期待どおりの効果が得られる。挙動が怪しいと感じたときも現実世界の動きを観察し対比をしてみること。
ボールの落下が早すぎる場合、ビー玉と同じ大きさに設定したか確かめると良い。実際にビー玉を机の上で転がしてみると、予想外に動きが早いことをに気づくのでないだろうか?もっと流れる動きを期待する場合にはもっと大きなボールをより離れた距離にカメラを配置する必要があるのだ。
------

Unityを初めて使用したときに物理エンジンがすぐにリアルな動きを再現してくれたことに感動した記憶がある。自分の場合、その前まではAndroidで自前の物理動作を再現しようとして四苦八苦して、それでも不自然な動きに苦労したので、無料のエンジンが提供されていたことに本当に驚いたものだ。

それでも、最初にRigidbodyやIsKinematicの理解からスタートするのは誰もが同じだろう。英語としても聞きなれない用語なので、色々と試行錯誤するだけでなくUnityの関連サイトで調べて、実験することの繰り返しで身につけていく基本中の基本とおもう。

FixedUpdateに関する注意や、現実的な大きさを用いることも、最初のうちは、気付かなかったりする重要事項。

Unityの基本は自分で試行錯誤して肌感覚を身につけようぜ!


2012年12月11日火曜日

Unity初心者が学ぶクォータニオンを使ってオブジェクトの回転の修正方法

前回記事に続いてUnity Gemsからの記事を翻訳する:

-------
http://unitygems.com/mistakes1/

October 15, 2012

クォータニオンを使ってオブジェクトの回転の修正方法


クォータニオンのx、y、z、wパラメータは、インスペクタで表示されるオブジェクトの回転の値と関係がない。x、y、z、wは角度で格納はされている(角度のサイン値、コサイン値で格納)。

クォータニオンのx、y、z、wは、本当に何をやっているのか理解していないかぎり、修正すべきではない。もし何かの回転を角度で修正したい場合は.eulerAnglesを使用して修正すべきである。

もしクォータニオンをより深く理解した場合は幅広いテーマである。ここを参照してほしい。

インスペクタでオブジェクトの角度をセットするには次のようにする:

transform.rotation.eulerAngles = new Vector3(100,0,100);
------------

今回は簡単なTipsだった!

しかし、クォータニオンについてより深く理解する、という前提でのリンク先記事はかなり長そう。初心者なら最初ぐらいは後回しにしても大丈夫かも。

一歩づつ基本を着実に積み上げよう!

JavascriptをC#で呼び出す簡単な検証

昨日記事にて、Javascriptを特殊フォルダ(Standard Assets, Pluginsフォルダ)に配置して、C#から呼び出すことが出来る、ということを翻訳したものの、実際に試してみたことがなかったのでコンパイルが通るかどうかだけの簡単な実験を行った。

Unity初心者が学ぶC#スクリプトとJavascriptの相互参照
http://gamesonytablet.blogspot.jp/2012/12/unitycjavascript.html

検証にあたってはCharacter Controllerをインポートしてみて、これに含まれるCharacterMotor.jsを①Javascriptのフォルダ配置、②C#、Javascriptで呼び出すか、によってコンパイルが通るか確認する、という簡単な方法をとってみた。

正直にいって十分な検証とはいえないが、昨日の記事どおり「特殊フォルダに配置すればコンパイル順序は保証される」は確認できた。

しかし、そもそも論で「出来るかぎりJavascriptかC#のどちらかで統一すべき」という答えが明らかにベストで、その点で言えばC#使いにとってCharacter Controllerのような標準アセットの中に大量のJavascriptが混じっているのは面倒の始まりで、出来るかぎりC#版を探すべきといえる。
(実際、CharacterMotorもC#版に変更した凄い方がいたようだ)

また、逆にJavascriptとC#を混ぜるべき状況というのは記事にあったとおり、アセットストアなどで提供されていたパッケージが使用している言語がC#で自分はJavascript使いであった場合や、その逆のパターンなどにどうしても参照をする方法を残したい場合など、極めて限定的とおもう。

以下、お粗末ながら検証した内容を整理した:

1. コンパイル順序の検証

検証内容:Javascriptのフォルダ配置によりコンパイルが通るかどうか

環境:Unity4.0.0f7

 失敗パターン

  • #1 CharacterMotor.jsを特殊フォルダに配置しない
  • #2 CallCharacterMotor.csで#1を呼び出す コンパイルが失敗!

 成功パターン

  • #1 CharacterMotor.jsを特殊フォルダに配置
  • #2 CallCharacterMotor.csで#1を呼び出す コンパイル成功
※CallCharacterMotor.csは常に特殊フォルダ以外に配置

2. コンパイル順序の検証(二重呼び出し)

検証内容:C#、Javascriptで呼び出しを行うかによりコンパイルが通るかどうか

環境:Unity4.0.0f7

 失敗パターン

  • #1 CharacterMotor.jsを特殊フォルダに配置
  • #2 CallCharacterMotor.csで#1を呼び出す
  • #3 CallCallCharacterMotor.csで#2を呼び出す コンパイル失敗!

 成功パターン

  • #1 CharacterMotor.jsを特殊フォルダに配置
  • #2 CallCharacterMotor.csで#1を呼び出す
  • #3 CallCallCharacterMotor.csで#2を呼び出す コンパイル成功
※CallCharacterMotor.cs、CallCallCharacterMotor.csは常に特殊フォルダ以外に配置

用意したスクリプト

CallCharacterMotor.cs - C#

using UnityEngine;using System.Collections;public class CallCharacterMotor : MonoBehaviour {  private CharacterMotor motor;  // CharacterMotor呼出 (C#→java) void Start () {  motor = GetComponent<CallCharacterMotor>(); }}

CallCallCharacterMotor.js - Javascript

#pragma strictprivate var motor : CallCharacterMotor;// CallCharacterMotor呼出(二重呼び出し java→C#→java)function Start () { motor = GetComponent(CallCharacterMotor);}

CallCallCharacterMotor.cs - C#

using UnityEngine;using System.Collections;public class CallCallCharacterMotor : MonoBehaviour { private CallCharacterMotor motor;  // CallCharacterMotor呼出(二重呼び出し java→C#→C#) void Update () {  motor = GetComponent<CallCharacterMotor>();  }}

うーん、今回はわれながら微妙な検証・・・・より丁寧に掘り下げるべきだな (深く反省)

UnityはC#かJavascriptのどちらかに固めて学習を進めていこうぜ!

2012年12月7日金曜日

Unity初心者が学ぶ増減するリスト情報の管理


前回に引き続きListやDictionariesで増減するオブジェクトコレクションの使い方についてUnity Gemsの内容をみていく:

---------

http://unitygems.com/mistakes1/
October 15, 2012

増減するリスト情報の管理

Array、ArrayList、HashTableを用いないこと。.NETのジェネリックコレクションか組み込みされた配列を使用すべきである。古いコレクションでは多くのコードを記述しないと必要なオブジェクト型を得ることが出来ない一方で、ジェネリックコレクションはその問題を解決し、さらに必要であれば異なったオブジェクト型にも対応することも可能である

組み込みされた配列、たとえばint[1000]は高速で効率的に使用できるコレクションであるが、ランタイムで増減させることはできない。配列が増減しないケースに限って使用すること。

C#では

using System.Collections.Generic;

をファイル冒頭に追記しないとジェネリックコレクションが使用できない。Javascriptではその必要性がない。

ArrayやArrayListの代わりにジェネリックリストを使用すること。リストはランタイムにアイテムが追加、削除することが可能であり、特定の型(あるいはその型のサブクラス)を保有しているため、ソートも容易であり必要時に配列に変更することが出来る。

ListおよびDictionaryについてはMSDNの.NETドキュメントを参考にすることが出来る。UnityScriptあるいはC#での簡単な事例は以下で紹介するので、基本的な考え方は身につけることが出来る。

Listの使用例


//Javascriptを使用してリストの定義
var myList = new List.<int>();
var anotherList = new List.<SomeClass>();

//C#を使用してリストの定義
List<int> myList = new List<int>();
List<SomeClass> anotherList = new List<SomeClass>();

//リストに要素を追加
myList.Add(someValue);

//リストに複数要素を追加
myList.AddRange(someListOrArrayOfValues);

//全ての要素をクリア
myList.Clear();

//リストに挿入
myList.Insert(1, someValue);

//複数の要素を挿入
myList.InsertRange(1, someListOrArrayOfValues);

//特定の値を取り除く
myList.Remove(someValue);

//特定インデックスの要素を取り除く
myList.RemoveAt(1);

//要素のインデックスを取得する
var index = myList.IndexOf(someValue);

//Javascriptで関数を使って何かのインデックスを取得する
var index = anotherList.FindIndex(function(entry) entry.someValue == something);

//リストを配列にする
var myArray = myList.ToArray();

//C#で関数を使って何かのインデックスを取得する
var index = anotherList.FindIndex((entry) => entry.someValue == something)

//アイテム数を取得する
var itemCount = myList.Count

Dictionaryは.NETのジェネリック連想配列であり、以下の使用例がある


//JavascriptでStringからintとして定義する
var myDic = new Dictionary.<String, int>();

//JavascriptでGameObjectからクラスのDictionaryとして定義する
var anotherDic = new Dictionary.<GameObject, SomeClass>();

//C#でStringからintとして定義する
Dictionary<string, int> myDic = new Dictionary<string, int>();

//C#でGameObjectからクラスのDictionaryとして定義する
Dictionary<GameObject, SomeClass> anotherDic = new Dictionary<GameObject, SomeClass>();

//Dictionaryに要素を追加
myDic["Something"] = someIntValue;

//Dictionaryから値を取得
var someValue = myDic["Something"];

//複雑な値を取得しプロパティをひとつ変更する
anotherDic[gameObject].someProperty = someValue;

//値が存在するかチェックheck if a value exists
if(myDic.ContainsKey("Something")) { }

//Dictionaryから要素を削除
myDic.Remove("Something');

//JavascriptでDictionaryの全てのキーをループする
for(var key : String in myDic.Keys) { }

//C#でDictionaryの全てのキーをループする
foreach(int value in myDic.Values) { }

//全ての要素をクリア
myDic.Clear();

//Dictionaryのアイテム数を取得
var count = myDic.Count;
----

リスト情報の管理はUnity独自の内容ではなく、なじみのない人は苦手分野かもしれない。

幅広く勉強をしてゲーム開発に活用しようぜ!

Unity初心者が学ぶC#スクリプトとJavascriptの相互参照

前回記事に続いてUnity Gemsからの記事を翻訳する:


-------
http://unitygems.com/mistakes1/

October 15, 2012

C#スクリプトとJavascriptの相互参照


この参照を正しく行うためには、まず参照を一方向、例えばC#スクリプトからJavascriptをアクセスするまたはその逆方向、に限定する必要がある。二つのクラスが、それぞれ異なる言語で記述されていて、かつ二方向に参照させることは出来ない。

プロジェクトを記述する言語を一つ選ぶこと。もしサードパーティのコンポーネントの場合に完全に独立したものを使用する場合、一番最初にコンパイルされる特別なフォルダにこれらのスクリプトを置く、すなわちStandard Assetsフォルダ、Pro Standard Assetsフォルダ、 またはPluginsフォルダに置いて、かつ参照する側のスクリプトをこれらのフォルダには置かないようにすれば、どの言語で記述されていようとも問題ない。

JavascriptとC#は同じアセンブリにコンパイルされないので、単純に互いに参照させることは出来ない。Plugins、Standard Assets、Pro Standard Assetsフォルダに置いてあるコードは最初にコンパイルされ、逆にこのコードに置かれていないコードは、フォルダに置いてあるコードとスクリプトで定義されたクラスを参照出来ることになる。

このようにしてStandard Assets、Pro Standard Assets、 またはPluginsフォルダ(またはさらに子フォルダ)C#スクリプトにJavascriptを置くことで、C#のコードにJavascriptクラスを参照することが出来る。覚えておくべきこととして、これら特別なフォルダに置かれたJavascriptは、それ以外のフォルダにあるC#を参照することは出来ないし、そのC#とJavascriptが逆の場合もまた然りである。これがプロジェクトで言語を混ぜるべきでない理由です。もし参照しないといけない事態に陥った場合に参照する方法がなくなってしまう。

Unityはスクリプトのコンパイル順序に関するドキュメントをここに置いている。
---------

うんうん、今回の記事もさして難易度が高いものではなく、普段アセットストアからスクリプトをダウンロードしている方はご存知のこととおもう。

そこにチャレンジしたい方は以下のブログ記事を参考にすると良いと思う:

テラシュールウェア:スクリプトの実行順序を変更する
http://terasur.blog.fc2.com/blog-entry-161.html

Unityの基本を積み重ねようぜ!

2012年12月6日木曜日

必見!Unity初心者が学ぶ「未来事象の正しい文法」


前回記事に引き続いて未来事象の正しい文法についてUnity Gemからの学習を進めていく:


-------
http://unitygems.com/mistakes1/

October 15, 2012

未来事象の正しい文法

未来事象を何か発生させたいだけの場合にコルーチンを用いるのは複雑でやりすぎである。C#で正しく記載することを忘れてしまうことで大混乱に陥ったり、そもそも何の目的で使うか見えなくなりがちだ。Unity初心者の皆さんには未来事象を何か発生させたい場合はコルーチンのことは頭から消し去ってもらい次のアドバイスにしたがってほしい。

タイマーを作成するだけなら以下のコードで良い:

float timer;
int waitingTime;

void Update(){
    timer += Time.deltaTime;
    if(timer > waitingTime){
        //Action
       timer = 0;
    }
}

timer変数は毎フレームごとにdeltaTime(前フレームとの差分の秒数)だけ増分される。この変数は毎フレームwaitingTime(待ち時間)と比較を行う。ついにはtimerがwaitingTimeより大きくなると判定条件はTrueとなりステートメントの中のコードが実行され、最終的にはtimerはゼロクリアされ再度実行できるようになる。timerの増分をinsideというbooleanによる判定条件で囲うことによりtimerが特定の条件で発動されるようにプログラミングすることが出来る。

using UnityEngine;
using System.Collections;
public class Test:MonoBehaviour{
    float timer;    
    int waitingTime;
    bool inside;

    void Start(){
        timer = 0.0;
        waitingTime = 2;
        inside = false;
    }

    void Update(){
        if(inside){
            timer += Time.deltaTime;
            if(timer > waitingTime){
                //Action
               timer = 0;
           }
       }
    }

    void OnTriggerEnter(Collider other){
        if (other.gameObject.tag=="Player")inside = true;
    }
   void OnTriggerExit(Collider other){
        if (other.gameObject.tag =="Player"){
            inside=false;
            timer = 0;
        }
   }
}

上記の例ではプレーヤーがあるゾーンにいる時間で発動するトリガーに関するtimerを作成する方法を示している。例えば火のゾーンに入った場合、プレイヤーが死亡するロジックをUpdateに記述するだけで1秒もたたずにプレイヤーを死亡させることが出来る。このタイマーによりx秒ごとにプレイヤーに対してロジックを働かせることが出来る。

さらにUnityはより優れた解決策も提供しており、次でそれを見ていく。

  • オブジェクトが数秒間だけ生存するスクリプトを書く

もしオブジェクトが画面に数秒だけ表示させたいときは何と、Startの中でロジックを書くだけで良い。Destroyは引数をもたせることで、オブジェクトをDestroyするまでの遅延時間をあらかじめ記述することが出来る。

function Start()
{
       //ゲームオブジェクトを5秒後にDestroy
       Destroy(gameObject, 5);
}

  • アクションの遅延

例えばあるアクションを発生させてから、別のアクションを一定時間経過後に発生させたいとする。UnityScriptとC#で同じ結果のために異なるアクションを用いる。次の例ではcheck変数をif条件に含めることでcheckがtrueになるまでステートメントは実行されない。checkがfalseに設定された場合、2秒後にcheckを再度trueに設定される。

UnityScript

#pragma strict
var check :boolean =true;
var i:int =0;

function Update () {
    if(Input.GetKeyDown(KeyCode.A)&&check){
        check = false;
        print("Inside" + i++);
        WaitForIt();
    }
}
function WaitForIt(){
    yield WaitForSeconds(2.0f);
    check=true;
}

C#

using UnityEngine;
using System.Collections;

public class Wait : MonoBehaviour {
    public bool check =true;
    int i =0;

    void Update () {
        if(Input.GetKeyDown(KeyCode.A)&&check){
            check = false;
            print("Inside" + i++);
            StartCoroutine(WaitForIt());
        }
    }
    IEnumerator WaitForIt(){
        yield return new WaitForSeconds(2.0f);
        check=true;
    }
}
これらのスクリプトを実行することでAボタンを押下することで画面にInsideとiの値を表示しているが、次にまた表示されるまでは2秒遅延される。

  • 何かを数秒後に実行する

もし何かを実行した後、数秒後に別の処理を実行させたい場合、Invokeを使用するのが簡便な方法だ。実行したいスクリプトを含めた関数を記述し、次にInvoke("関数名",遅延秒数)で呼び出す。以下の例ではTurnMeBlue関数は2秒後に実行される。


function Start()
{
      //2秒後にオブジェクトを青にする
      Invoke("TurnMeBlue", 2);
}

function TurnMeBlue()
{
       renderer.material.color = Color.blu10
}

さらに発展させて複数のInvokeを用いることで連続で処理を行わせることができる。


var startPosition : Vector3;
var health = 100.0;
var respawning = false;

function Start()
{
    //オブジェクトの開始位置をキャッシュする
    startPosition = transform.position;
}

//オブジェクトをリセットする
function Respawn()
{
      transform.position = startPosition;
      health = 100;
      renderer.enabled = true;
      respawning = false;
}

//キャラクターを隠す
function Hide()
{
     renderer.enabled = false;
     transform.position = new Vector3(1000,1000,1000);
}

function Update()
{
    if(health < 0 && !respawning)
    {
         respawning = true;
         animation.Play("die");
         Invoke("Hide", 3);
         Invoke("Respawn", 10);
    }
}

このスクリプトはInvokeを使用して死亡アニメーションを再生した後、キャラクターを再生成するものである。最初、healthが0以下になるとdieアニメーションが再生させる。3秒後にモデルは非表示に変更され(位置も変更)、10秒後には再生成する。

  • 数秒おきに何かを実行する

コルーチンを用いたループを記述する以外の方法として、実行したいスクリプトを含めた関数を記述し、次にInvokeRepeating("関数名",初回呼出までの遅延秒数,次回呼出までの遅延秒数)と呼出を行う。

実行はいつでも中断することができ、CancelInvoke("関数名")と引数なしで呼出を行いすべて中断させることが出来る。


float health = 100f;

void Start()
{
    InvokeRepeating("Heal", 2, 2);
}

void Heal()
{
    health = Mathf.Clamp(health + 1, 0, 10011
}

執筆時点では2つめの引数に不具合がある。具体的にはStartと同時に最初の呼出をするために最初の引数をゼロとする。この場合は最初の呼び出しだけは2回実行されてしまう。この不具合解消のためには値をゼロではなく0.01fなどに変更することで回避する。

  • コルーチンを用いるべきケース

コルーチンを用いなければUpdateに記述するしかなく、さらに複雑になりすぎる場合にはコルーチンも有効だといえる。

Updateおよび複数のコルーチンを持たせて同じ変数を更新させる仕様とするのはバグの宝庫であり、不具合が見つけにくくなる。

例えばオブジェクトを異なる角度に回転させたいとする。この場合はコルーチンを用いることが有効である。次の例ではRotateByを呼び出しすることで一定時間の後にオブジェクトを新しい角度に変更する。

function OnMouseUp()
{
     RotateBy(Vector3(0,90,0),2);
}

function RotateTo(angle : Vector3, time : float)
{
     var currentRotation = transform.rotation;
     var targetAngle = currentRotation.eulerAngles + angle;
     var targetRotation = Quaternion.Euler(targetAngle);
     var t = 0;
     while(t < 1)
     {
          transform.rotation = Quaternion.Slerp(currentRotation, targetRotation, t);
          t += Time.deltaTime / time;
          yield null;
     }
     transform.rotation = targetRotation;
}

Javascriptでコルーチンを用いる場合は単にyield記載を加えるのみである。Updateのなかでは正しく動作しないので留意すること。
--------
InvokeRepeatingは用いたことがなかったが、不具合情報とその回避まで整理されているのはUnity Answersの英知ともいえる。

最新の情報を活用して正しいプログラミングをしようぜ!

Unity初心者が学ぶCharacter Controllerの正しい使い方

前回記事に引き続いてCharacter Controllerの正しい使い方についてUnity Gemsからの翻訳を投稿する:

-------
http://unitygems.com/mistakes1/

October 15, 2012

Character Controllerの正しい使い方

キャラクタコントローラ(Character Controller)はプレーヤおよびNPCキャラクターによるアクションゲームやシューティングゲーム向けに設計されている。この用途について大変すぐれた機能であり、複雑なロジックを伴う移動床面、別キャラクターが進路を塞ぐ、等々の場面もカバー出来る。

物理(Physics)エンジンを使用したい場合はCharacter Controllerを使用しないこと。このケースではカスタムのスクリプトを作成する必要がある。

多くのゲームではCharacterに不向きなケースがあるので、必要機能をあらかじめ把握してから着手するのが良い。使用するのは必要である状況に限ること。

Character Controllerに物理オブジェクトに作用させることは可能であり、例えば押しのける動作などは実現できる。ただしスクリプトをいくらか付け加える必要があり、物理エンジンとの相互作用が現実的でない動きを引き起こすこともある。
Unityマニュアルの参考例

Character ControllerはデフォルトでRigidbodyがアタッチされてないことに留意する必要があり、このためデフォルトではOnCollisionやOnTriggerは呼出されない。KinematicをオンにしたRigidbodyをアタッチするかOnControllerColliderHitのいずれかで衝突判定を行うことになる。

Character Controllerの動作を制御する際は必ずSimpleMoveあるいはMoveを使用すること。決してPositionを直接更新してはならない。
-----

キャラクタ制御も複雑なコードを記述することなくゲーム開発できるのはUnityの強み。正しくポイントを理解してゲームに生かそうぜ!

2012年12月5日水曜日

Unity初心者が学ぶ「Inputの正しい用法」


ブログ休止前の記事に引き続いてInputの正しい用法についてUnity Gemsからの翻訳を投稿する:

-------
http://unitygems.com/mistakes1/

October 15, 2012

Inputの正しい用法

Unity初心者がよく悩むこととして「Inputがどうして正しく読めなかったんだろう?」があげられる。ドキュメントどおりに設定したはずなのに何故か動かない。大体において、問題はInputを通して物理エンジンを作用させるときに問題が起きる。PhysicsはFixedUpdateに配置するものなので、そこにInputも配置すれば良いだろう、とおもって。そこが大きな勘違いなのである。

Updateはマシン依存であり、言い換えると各々のマシンによりプロセッサ処理能力の違いやマシン上の他のプロセスなどによってでフレームレートが異なってくるのである。

FixedUpdateはユーザ依存である。メニューからEdit→Project Settings→Physicsと選択することでPhysics Managerを用いて値修正を行うことが出来る。Fixed Timestepsは0.02(50FPS)がデフォルト値である。より小さな値を設定することはFixedUpdateをより頻繁に動作させる結果になる。値はあくまで希望であって、実際にはマシンがその速度で動作できるかは別問題であることに留意する必要がある。

UpdateとFixedUpdateはそれぞれ独立して動作するため、一つのUpdateに対して:一つ、あるいは二つ、あるいはゼロ個のFixed Updateが対応することになる。仮にマシンが100fpsで動作したとしてFixedUpdateが50fpsの場合は二つのUpdateに対してFixedUpdateは一つしか対応しないことになる。


誤った使用例:

using UnityEngine;

public class Test:MonoBehaviour{
    void FixedUpdate(){
        if(Input.GetKeyDown(KeyCode.Space)){
           //Action
        }
    }
}

正しい使用例:

using UnityEngine;

public class Test:MonoBehaviour{
    bool action = false;
    void Update(){
         if(Input.GetKeyDown(KeyCode.Space)){
             action = true;
         }
    }
    void FixedUpdate(){
       if(action){
           //Action
           action = false;
        }
    }
}

Updateに含まれるInputは毎フレームチェックされることが保証されているのでInputが無視されることはない。変数がUpdateで修正され、次のFixedUpdateがコールされる都度使用される。booleanをfalseにすることでactionが無制限に繰り返されないようにしている。
---------

PhysicsはFixedUpdateで記述しないといけないところは良く知られているが、仕組みを正しく理解する必要があることをあらためて認識される。

マルチスレッドのプログラミングとおもえば当然かもしれないが重要なところ。

ポイントを押さえてUnityを学習しようぜ!

ブックマークに追加

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

自己紹介

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

ページビューの合計

過去7日間の人気投稿

ブログ アーカイブ