2013年2月27日水曜日

NGUI 2.3.4 , TNet 1.5.0 , 1.6.0

NGUIバージョンアップ情報です。なお、同じ作者がリリースしているTNetアセットも併せてリリース情報を載せます:

NGUI


http://www.tasharen.com/forum/index.php?topic=11.15
February 24, 2013, 05:52:43 PM

2.3.4
- 新機能: インスペクタ上でウィジェットのカラーを容易にコピー&ペーストする機能
- 修正: フォーラムで記載されているマイナーな課題に対するランダムな修正内容
- 修正: その他、マイナーなパフォーマンス改善の修正


TNet


http://www.tasharen.com/forum/index.php?topic=2650.0
February 23, 2013, 10:29:19 AM

1.5.0:
- 新機能: ユニバーサル プラグ&プレイ機能を追加して、ゲートウェイ/ルータでのポート開放を容易にした
- 新機能: TNObject.isMine フラグを追加し、これはインスタンス化したクライアントでのみ true (またはプレイヤーが去った場合はホストで true)
- 新機能: 探索クライアントを再設計。複数のゲームロビーサーバー/クライアントを代わりに準備
- 新機能: TNet サーバアプリはポートパラメータを新たにサポートし、ロビーサーバを開始することが可能になった
- 新機能: ゲームサーバは自動的にリモートロビーサーバにより自動的に自身の登録することが可能になった
- 新機能: Tools.externalAddress を追加し、これはインターネットで参照可能なIPアドレスを戻す
- 修正: TNet が ウェブプレイヤーで予告なく UDP を使用しなくなる仕様を修正。単にウェブプレイヤー上で UDP は今後サポートしないこととした
- 編集: localAddress および IsLocalAddress()  を Tools フォルダに移動し staticとした

1.6.0
- 新機能: プレイヤーがシーンに入場した時にオブジェクトをインスタンス化するスクリプトを追加。(ヒント:プレイヤーのアバター使用に便利)
- 新機能: 一時的なゲームオブジェクトを作成できるようにした:オブジェクトを作成したプレイヤーが去るときに破棄される
- 新機能: ステップバイステップのビデオを追加: http://www.youtube.com/watch?v=cTjy-L8C6WM
-----

ビデオが英語なのが残念なところだが、コードを入力してサンプル実行してくれるので、色々と使い方の分からないTNetについて疑問が解けるところは多いとおもう。

NGUIよりTNetのほうが修正多いところみると、今はそっちに本気かな?!

2013年2月25日月曜日

Unity本の感想

今年に発売されたUnity本(Unity4対応)を読んでみたので今日はその感想。




【サンプルスクリプトの言語】 C#
  • Unity初級者でも最初から分かるように丁寧に書かれ、6ジャンルも扱い素晴らしい!
    • 3Dシューティング
    • 3Dアクションパズル
    • ピンボール ゲーム
    • ラジコンカー ゲーム
    • プラットフォーマー ゲーム(いわゆる3D横スクロール)
    • アニメーション ゲーム
  • Unity中級者でも扱ったことがないようなテーマも触れられている
    • 日本語フォントの取り扱い
    • WebCamTexture
    • iOS/Androidネイティブ拡張機能
  • Unity4のMecanimが40ページ近く取り上げている
    • アニメータコントローラ、ブレンドツリー、トランジション
    • アニメータの制御(移動中のコライダのサイズ変更など)
Mecanimについては、知っているかぎりではUnity公式ビデオがこれまで最も詳しく、あまり日本語での情報が少ないとおもっていたが、この本をみたかぎりビデオで取り上げられているようなトピックは十分にカバーできていると感じた。
<参考:Unity公式ビデオ(Unity4.0 Mecanim)>
http://video.unity3d.com/video/7362044/unity-40-mecanim-animation

なおサンプルスクリプトについて許可なくネットワークへの配布は出来ず、商用利用は出版社へ一報が必要と明記されているのでその点は注意だ。

ちなみに、私自身はWebCamTextureは使ったことなかったのだが、ウェブカメラ専用の機能と勘違いしていたので、iPhone/Androidで使用できると知っただけでも有難い(注.文章中にAndroidでの不具合に関する記述はあるが・・・)

自分はすでにUnity初心者ではない、という人もひととおり目を通せば新しい発見はあるだろう、と思える本だ。実際、これをみて全ての機能を試したことがある、という人は十分に上級者の領域に入ってくるだろう。さらに一歩進んで、この本で触れられてないShaderLabなどカスタムシェーダやネットワークゲーム連携に挑戦すると良いとおもう。(あら、本の感想の話からずれちゃった・・・)

この本はUnity開発する人には有難い本だぜ!

2013年2月22日金曜日

Unityでマルチスレッド(後半)

前回記事に続いて、Unity Gemsのマルチスレッドに関する記事を紹介したい:


関連記事

Unityでのマルチスレッド(前半)


http://unitygems.com/threads/
November 2, 2012


デッドロックにハマらない


結局、変数が同時修正される問題の解決策は、一つの変数に対して一度に一つのスレッドしかアクセス出来ないようにすることだ。当然、複数のスレッドが同時に作業出来なくなることをも意味し、矛盾が生じるため、この制御を行なう時間は制限する必要がある。

C# には、特定のオブジェクトに対して特定の時間帯に、一つのスレッドしか存在し得ないようにする lock キーワードが用意されている。なお、値型やプリミティブは lock 出来ないため、あえてオブジェクトにと厳密に申し上げたので注意いただきたい。
int a = 50;

object guard = new object();

void ThreadOneCode()
{
     //コードを追加出来る

     lock(guard)
     {
         a = a + 1;
     }

     //コードを追加出来る

}

void ThreadTwoCode()
{
     //ここにもコードを追加出来る

     lock(guard)
     {
         a = a - 1000;
     }

     //ここにもコードを追加出来る
}
lock(guard) {...} の中の全ては、guard を使用出来るスレッドは同時に一つのみアクセスできることを保証する。これはどのようなオブジェクトにも必要であればこれを使用出来る。

もし互いにネストしているものを lock 使用すると、たくさんの問題に出くわす可能性がある。例えば内部ロックを外すために、必要な何か外部ロックと同じものを必要とするために外せない、といった状況に陥る場合がある(すでにロックしているため外せない)。これはデッドロックと呼ばれ、外すことが不可能であり、結果的に全てを凍結させる。真剣にスレッドプログラミングに取り組むとどこかで遭遇してしまう問題だ。
もしスレッドの扱いは初心者ならば、出来るかぎりメモリや変数を共有しないように努めるべきだ。変数をスレッドに渡して、戻り値として変数を受け取るのは良いが、同じメモリを使用しないようにすべきだ。


スレッドを同期する方法はたくさんあるが、言語に作り込まれているのは Lock のみで、その他はシステム上のオブジェクトの構造により保守される。この方法で対応するのであれば、効率的なコードを書く手法はいくつかある。例えば値を読み込みは複数許容するけど、書き込みするのは一つのスレッドのみ書き込みできる所有者に限定する、などだ。

Unityでの実践的なスレッド活用


Unityでは自身のクラスインスタンスおよびUnityの値型(Vector3, Quaternion) のみ処理できる。前述のとおり、これによる制限は一部発生する。それでもメッシュの頂点編集を二つ目ののスレッドでおこない、Vector3の配列をメインスレッドに戻すなどして実際のオブジェクトを更新させることが出来る。

A* を使用した経路検索コードを記述してマップ上のある地点間の経路を書くことも出来る。

何をするにせよ、何らかの効果を発揮するためには、新しいスレッドからメインゲームのスレッドへと情報を渡す必要が発生する。

どうやってスレッド間で機能する優れたコードを記述すれば良いだろうか。現行の if や チェックを全て踏まえて、処理の準備が出来てるか示すために、クラスが大量のバッファやフラグで一杯々々にならないようにする必要がある。こういうゴチャゴチャが通常バグにつながり、スレッドを、本当の意味で効率的に使用するための障壁になるものだ。

クロージャおよび無名関数を使用することで、こよ作業をより簡単に出来るフレームワークを構築出来る。このチュートリアルの中でコード全体を記述することはないが、このリンク先にはあり、どのように使えば良いかはこれからお見せしたい。

スレッドの活用例


このためのクラスの名前は Loom と呼ばれる。Loomにより別のスレッド上でコードを容易に実行することが出来て、そのもう一つのスレッドも必要な時にメインのゲームスレッドでコードを実行することご出来る。

気にすべき関数はたった二つだ:


  • RunAsync(Action) により、ステートメントのセットを別スレッドで実行する
  • QueueOnMainThread(Action, [任意で指定] float time) により、ステートメントのセットをメインスレッドで実行する (任意で delay を指定)。


Loom をアクセスするには Loom.Current を使用する。これはゲームのメインスレッドと作用する透明なゲームオブジェクトを作成することを行なう。

Loom を使用して、全ての頂点を乗算処理してメッシュを更新する例を示す:
//二つ目のスレッドをメッシュ上でスケール
void ScaleMesh(Mesh mesh, float scale)
{
    //メッシュの頂点を取得
    var vertices = mesh.vertices;
    //アクションを新しいスレッドで実行
    Loom.RunAsync(()=>{
        //頂点を一通りループ
        for(var i = 0; i < vertices.Length; i++)
        {
            //頂点をスケール
            vertices[i] = vertices[i] * scale;
        }
        //メインスレッドでコード実行して
        //メッシュを更新
        Loom.QueueOnMainThread(()=>{
            //頂点をセット
            mesh.vertices = vertices;
            //境界線を再計算
            mesh.RecalculateBounds();
        });
 
    });
}
これはクロージャの使用方法を学ぶのに好いサンプルでもある。

二つ目のスレッドで実行されるアクション (引数なし、戻り値なしのメソッド) はラムダ関数 (または無名関数) を用いて作成される。クロージャの素晴らしいところはクラスの全ての変数および自身の関数のローカル変数の両方にアクセス出来ることだ。

ラムダ関数の定義には ()=>{ ... } を使用して、この関数の中身は全て、別のスレッドで実行される。

頂点が更新されたとき、メインスレッドでメッシュを更新する必要があるためQueueOnMainThreadを使用し、これは次にUpdateサイクルが処理されたときに実行される(コールされるタイミングによって当該フレームまたは次のフレーム)。QueueOnMainThread もまたActionを引数とするため、全く同じテクニックを使用して更新された頂点を元のメッシュに渡す。この辺りが最高に便利なんだ!!

ソースコード


Loom の ソースコードはここでダウンロード出来る。同じことをUnityScript で実現するには次のように記述する:
//二つ目のスレッドでメッシュをスケール
function ScaleMesh(mesh : Mesh, scale : float)
{
    //メッシュの頂点を取得
    var vertices = mesh.vertices;
    //アクションを新規のスレッドで実行
    Loom.RunAsync(function() {
        //頂点を一通りループ
        for(var i = 0; i < vertices.Length; i++)
        {
            //頂点をスケール
            vertices[i] = vertices[i] * scale;
        }
        //メインスレッドでコードを実行
        //してメッシュを更新
        Loom.QueueOnMainThread(function() {
            //頂点をセット
            mesh.vertices = vertices;
            //境界線を再計算
            mesh.RecalculateBounds();
        });

    });
}
----

並行処理を行いたい、というケースでサンプルまでついているので有難いところだ。色々試すと良いだろう。

うーん、いつもながら中身が濃いぜ!

2013年2月21日木曜日

【朗報】NGUIにダイナミックフォント正式採用の方向性


朗報だ。

表題のとおりだが、NGUI作者がダイナミックフォント正式採用の方向性を打ち出した。NGUIのダイナミックフォントはこれまでNGUI熱狂ファンの方が独自で作成していたパッチだったが、バージョンが変わると不具合が出たりと、色々と問題はあったが、これでかなり解決しそうだ。

NGUIのダイナミックフォントが何なのか、どう便利なのか分からない方は以下を参考にすると良い。

[Unity4]NGUIでUnity4のダイナミックフォントを使うパッチ
http://terasur.blog.fc2.com/blog-entry-375.html
[Unity3D]NGUIでダイナミックフォントを使うパッチの使い方
http://terasur.blog.fc2.com/blog-entry-377.html


なお、NGUI作者が正式採用を宣言したのはNGUI掲示板のスレッドだ。2.3.3でパッチに不具合が出た、という人もこの先のリンクからダウンロードしておくと良い。

http://www.tasharen.com/forum/index.php?topic=2707.75
February 19, 2013, 09:10:45 AM

どうやら作者本人がコミットするというより掲示板の常連の方によるプルリクエストで対応のようだ。

期待しているぜ!NGUI!

2013年2月16日土曜日

Playmaker 1.5.0 リリース(エディタ多言語ベータ機能付き!)

2/16にPlaymaker 1.5.0リリースされた様子。すでに持っている方は是非アップグレードだね!



<Asset Storeリンク>

Asset Store説明を翻訳すると:
NEW! FSM テンプレート インスタンス
NEW! アクションとしてFSM テンプレートを活用
NEW! Photon ネットワーキングをフルサポート
NEW! Get/Set プロパティ アクションを強化
NEW! グラフビュー ズームおよびミニマップ
NEW! エディタを日本語、フランス語、等々に

とのこと。

ちょっとだけ開いてみると:

ミニマップが右上に表示され、画面に入りきらないフロー図もここに表示され、全体が把握できたうえで移動も出来るので便利!



エディタの多言語ベータ機能を有効にするにはPlaymakerエディタ右上のボタンを押下。まあベータ機能とはいえ、色々と日本語が見られるのは素敵

---------

Photon ネットワーキングがどういう連携なのかはまた今度調べてみよかな^^


2013年2月14日木曜日

NGUI 2.3.3

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

http://www.tasharen.com/forum/index.php?topic=11.15
February 13, 2013, 03:27:23 PM

2.3.3
(2.3.2はUnity Pro限定のためバージョンを一つスキップ)
- 新機能: UIPanel はフェードを容易にするためにアルファ値を追加、TweenAlphaがパネルを tween 出来るように機能追加
- 新機能: 何の上をマウスオーバーしてるか、知りたい時のために UICamera.debug モードを追加
- 新機能:ウィジットのカラーやアルファ を Unity のアニメーション機能を通じてアニメーションさせたい時にAnimatedColor および AnimatedAlpha スクリプトを追加
- 新機能: Android デバイスでキーボードおよびコントローラを新たにサポート(OUYA)
- 新機能: UIFont.pixelSize を追加、アトラスに含まれない HD/UD フォントを作成出来るように機能追加
- 修正: Unity 4.1 での最適化にあわせて修正
- 修正: PMA シェーダ を使用した時にラベルのシャドウが影響されるように修正
- 修正: UICheckbox.current がチェックボックス イベントのレシーバーで正しく動作するように修正
- 修正: UIButton 関連のスクリプトの初期化のタイミングを、使用された時からStart() された時へと変更
- 修正: TweenOrthoSize により、FOV ではなくサイズが変更されるように修正
- 修正: アトラスが正方形でない時に、スプライト選択ウィンドウが正しく表示されない不具合を修正
- 修正: UIAnchor が Zデプスを保持するように修正、これで3D UIで正しく動作出来るように変更
-------

OUYA対応....気になるので誰か試してくれ‼

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

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

2013年2月11日月曜日

より優れたPlayerPrefsクラス

PlayerPrefsを改善したクラスを提供してところを見つけたのでサイトの情報を紹介したい:

http://www.previewlabs.com/writing-playerprefs-fast/
3/11/2011

Unity でプラットフォーム独自の方法でデータを保存するのに PlayerPrefs を使用することがある。

このクラスはハッシュテーブルの働きをして キーバリューのペアを保存できる。

ビルトインの PlayerPrefs クラスの欠点として iOS で非常に遅く、Android でさらに遅いことだ。Google Nexus One で行ったテストでは、毎秒あたり 平均 6.25 個のキーバリューのペアしか保存出来なかった。300 件以上を保存しようとしていたので、あまりに時間がかかりすぎた。全ての修正毎にファイルI/Oが行われている可能性が高い。現在の PlayerPrefs クラスは大量データを扱うのに不向きだ。

そこでオリジナルの PlayerPrefs を実装しようと決めて、プロトタイプの中で高速保存が出来るパラメーターのチューニングを可能とした。同じデバイス上で現在は300 件以上を瞬時に行えるようになった。

すでに Unity の PlayerPrefs クラスをプロジェクトで使用していて、iOS および Android での最適化方法を探している場合、次の手順によって実行出来る:

PlayerPrefs.cs ファイルをダウンロードしてプロジェクトに追加してほしい。

PlayerPrefs が使用されているスクリプトの冒頭に次の行を追加:
using PlayerPrefs = PreviewLabs.PlayerPrefs;
PlayerPrefs.Flush() を使用して全ての値を保存する。これはデータを大量に書き込みすときか、アプリケーション終了時に行う(メインとなるゲーム クラスの中で) :
public void OnApplicationQuit()
{
 PlayerPrefs.Flush();
}
最適化として機能するために、Hashtableを保持して Flush() が呼ばれたときのみファイル書き込みするようにした。

XML形式は意図的に使用しておらず、今回気にしているローエンド デバイスで不要なオーバーヘッドがかかるためだ。
------

筆者も Unity Store でダウンロードしたプロジェクトサンプルにこのクラスが含まれていて、なるほどとおもってソースコードを眺めた記憶がある。

便利なものはどんどん活用しようぜ!

2013年2月10日日曜日

Mecanim初挑戦

Mecanimなるものに挑戦するためにまずはBlenderを覚えようと思い立って、下記を参考にやってみました。
BlenderでMecanimしようぜ(前編):ivoryfunc
http://qiita.com/items/22d770556978533ad554
BlenderでMecanimしようぜ(後編):ivoryfunc
http://qiita.com/items/22d770556978533ad554
ここではMecanimではリターゲティング(アニメーションを他のキャラに瞬時につけかえられる)の機能を試すことに絞って、Blenderのキャラクターのリギングだけは自前で行いました:



女性キャラはオガワコウサク様(下記リンク)のものを拝借。男性キャラはUnity Blogで公開されているShadowGunサンプルの敵キャラです。

まあ、初めてなので素人な感想ですが:
  • Blenderでのリギング操作も慣れれば思ったよか使いやすい
    • RigifyというツールでAutomatic Weight Paintが出来る
  • ただしBlenderのウェイトペインティングがしんどい
    • Automatic Weight Paintでも修正は必要
    • 修正しないといけないところを特定して修正するのが大変。時間もかなりかかる
ちなみにフレームレートが十分じゃないのは録画ソフト(あるいはその設定)の問題だけで実際はかなりスムーズ。何かもっと良いスクリーンキャプチャのツールみつけないとな・・・

2013年2月7日木曜日

Unityでの「メモリリークと弱い参照での対策」

今回の記事はメモリリークに対して弱い参照を活用した事例についてUnity Gemsからの翻訳を紹介したい:


November 17, 2012


はじめに


次のような場合はこの記事を読むべきだ:

  • ネットワーク越しにAnimationStates を送信したい (transformのミキシングの場合も含めて)
  • メモリリークすることなくオブジェクトに新規のプロパティを追加したい
  • ガーベージコレクションおよび弱い参照について学習したい

イントロダクション

Unityの長年の問題はバックエンドが C++ となっていて、コードのインタフェースが .NET となっていることだ。Unityも頑張って素晴らしいモノを作ってきたけど、いかんせんどうしても必要な値が取得出来ない場合がある。古典的な例はtransformのミキシングで、キャラクターの一部分にだけアニメーションを追加して再生する場合だ。追加、削除することは出来るが、あらかじめ何であるか把握する方法が必要になる。多くの場合では一つのコードにtransformのミキシングを追加する部分を記述して、別の関数の中でRPCコールを通して何であるかを把握したり、また別のスクリプトで削除することも出来る。

この課題を解決にはかなり骨の折れる作業となって、多くのクラスや変数を、格納したり、情報の管理ご必要となるし、さらにはオブジェクトが削除されたかはどうやって把握すれば良いのだろうか?メモリはどのように解放するか?オブジェクトをいつまでもロックしてメモリリークとならないか?場合によってはかなり高い確率でリークしてしまうだろう。
この記事ではあるクラスを他のあらゆるオブジェクトと紐付けて、そのクラスがオブジェクトそのものが生存しているときのみクラスも生存させる方法を確立する。使用するのはとても簡単だ!:
public class Extra
{
    public int anyVariablesYouLike;
}

...

anyObject.Get().anyVariablesYouLike = 1;

初めての紐付け- アニメーション ミキシング

では早速 AnimationState をミキシングしたいデータと紐付けるための基本的な方法をみていく。簡単なヘルパークラスを作成して一つのオブジェクトを他のオブジェクトに紐付けるコードを実現しよう。
public static class Extensions
{
    // 拡張機能を格納するクラス
    static Dictionary<object, Dictionary<Type, object>> _extensions = new Dictionary<object, Dictionary<Type, object>>();
 
    //紐付けされた拡張クラスを修正
    public static T Get<T>(this object o) where T : class, new()
    {
         //このオブジェクトがすでにあるかチェック
         if(!_extensions.ContainsKey(o))
         {
             //ない場合は追加
             _extensions[o] = new Dictionary<Type, object>();
         }
         //この紐付けがすでにあるかチェック
         if(!_extensions[o].ContainsKey(typeof(T)))
         {
             //ない場合は作成する
             _extensions[o][typeof(T)] = new T();
         }
         //紐付けは戻す
         return _extensions[o][typeof(T)];
 
    }
}
早速、このクラスでanyObject.Get<AnyClass>() を使用すればそのクラスのインスタンスを取得出来るようになる。ディクショナリでは O(1)  の参照であるため処理が速いはずだ。このためtransform をアニメーション ステートと紐付けしたいため、それを行うクラスを作成してみたい。RPC経由で送信したときに他のコンピュータ上で見つけることが出来るようにtransform の名前を使用する。
public class Mixing
{
    public List<string> transforms = new List<string>();
}
次に、Get<T> ヘルパー 関数を使用して何かをクラスに追加するとしよう。
Transform spine2;

public void SwingSword()
{
    var state = animation["swingSword"];
    //実際は歩行中の場合のみ使用する
    //だろうが簡便のため、そのテスト内容
    //は割愛する
    state.AddMixingTransform(spine2);
    //このアニメーション ステートに対する Mixing インスタンスを取得し、
    //新しい transform の名前を追加する
    state.Get().transforms.Add(spine2.name);
    state.enabled = true;
    state.weight = 1;
}
早速これにより Mixing クラスをこの特定のアニメーションと紐付け出来た。次にRPC経由で別のプレイヤーに AnimationStates を送信する方法を見ていく。

RPC 送信に適合した AnimationState を表すクラスが必要だ。

RPC のための StoredAnimationState クラス - クリックして展開



これで相当なコード量だ。基本的にこれでアニメーション ステートを表す、シリアライズ向きのクラスを作成出来ました。アニメーションへの適用を戻す方法も組み込まれてます。

それではその他のプレイヤーにどのようにして送信出来るか見ていくことにしよう:
public void SendAnimations()
{
      //transfer 構造を作成
      var transfer = new StoredAnimationState.AnimationTransfer();
      //格納されたアニメーションの一覧を作成
      transfer.StoredAnimations =
         //現在のリストの全てのステート
         animation.Cast<AnimationState>()
            //有効化されたときのみ
            .Where(state=>state.enabled)
            //格納されたアニメーションを作成
            .Select(state=>new StoredAnimationState.AnimationTransfer.StoredAnimation
                   {
                        //アニメーション名
                        name = state.name,
                        //アニメーションデータ
                        data = new StoredAnimationState(state)
                   })
            .ToList();
       //データをシリアライズ
       var m = new MemoryStream();
       var b = new BinaryFormatter();
       b.Serialize(transfer, m);
       //送信
       networkView.RPC("ReceiveAnimations", RPCMode.Others, m.ToArray());

}

[RPC]
void ReceiveAnimations(byte[] data)
{
     //transfer 構造を受信
     var m = new MemoryStream(data);
     var b = new BinaryFormatter();
     var transfer = (StoredAnimationState.AnimationTransfer)b.Deserialize(m);
     //全てのアニメーションを無効化
     //(必要な場合は再度有効化される)
     foreach(var state in animation.Cast<animationstate>())
        state.enabled = false;
     //transferから全てのアニメーションをセットアップ
     //送信
     foreach(var state in transfer.StoredAnimations)
         state.data.Set(animation);
}
これだとメモリリークしまくり!!!

うん、今更でゴメンだけど _extensions ディクショナリから強制的に取り除くことを行わないかぎり、このコードはみるみるうちにメモリを無駄使いする。ゲームオブジェクトを破棄するとき、AnimationState をディクショナリでのキーとして保持してしまっているため、メモリは解放されない。頻繁に発生するとメモリ消費は莫大だ。

一縷の希望


理由なく酷い目にあわせたわけでないので、そこは安心して欲しい。この問題には解決方法がきちんとある。あとは技術的に高度なので大変、という点が残る。

やるべきことは、AnimationState(あるいはキーが何であっても) が破棄された時にMixing (またら他の拡張クラス)を解放することだ。当然に、現在は掴んでいるため破棄できない。

弱い参照


最初にやるべきことはAnimationStateへの強い参照でなく、弱い参照をもつことだ。.NET には WeakReference 型がビルトインされているため、管理することが出来る。これで同じオブジェクトへの二つの弱い参照は同じハッシュコードをもつことがない。このためディクショナリのキーとして使用することができる。これで大丈夫、.NET の全てのオブジェクトには GetHashCode コールがあり、本当のキーが何であり、それを代わりに使用してくれる。ところで int 型なので注意をば。

すべてをジェネリックにすることでオブジェクトの紐付けをする WeakTable<T> クラスという発想が出てくる。これについては、またちょっと後に検証するので現時点ではガーベージコレクションされた時点の元のキーの紐付けを削除する必要がある。

ガーベージコレクションがいつ実行されたかを調べるのは結構大変だ。しかし、実際に調べるにはいくつかの参照されていない、回収されたときに見ることが出来るオブジェクトを作成することが出来る。finalization メソッドを使用することでこれは実現され、 Jeff Richterから借用させてもらう:

ガーベージコレクション 通知クラス - クリックして展開


このクラスはいくつかの参照されていないオブジェクトを作成し、ガーベージコレクションにより finalize されるのを待ち、そしてそれが発生したときに GCDone イベントを発行する。このイベントを、弱い参照が依然として有効であるかどうかチェックするためのキューとして使用出来て、有効でないものについては紐付けされたオブジェクトを削除する...

完成した WeakTable<T> および static 拡張された Get<T> メソッドは次のようになる:


完成した WeakTable - クリックして展開


最初にディクショナリ定義をスイッチして参照の型を使用する必要があり、そしてその後は参照に関する WeakTable を先に取得できるようになる。

紐付けしたクラスの使用が完了したとき、 IDisposable.Dispose を使用して、もう使用しないことを明示する。


結論

この記事はずいぶん技術的に高度な記事としなってしまったが、少しでも意味が伝わったことを願うばかりだ。これで自身のクラスを、自身が制御しない生存期間のあるもの (すなわちAnimation Stateなど) に対して紐付ける方法が分かったはずであり、強力なテクニックとして身についたはずだ。楽しんでもらえてたら何よりだ!
-------------

んー、翻訳してみて何なんだが、ちょっと活用場面がなかなか思いつかないなー。良い活用方法がおもいついたらまた更新してみるわー。すまぬーー

2013年2月3日日曜日

Unityで初めてのAIキャラクター(7.しゃがんで隠れる(隠れる場所を探す))

※ 2013/2/11時点で元記事のうち、この章のみ削除されました

さて、いよいよ本シリーズの最終記事だ。敵キャラも隠れた位置をみつけて、そこからこちらに向けて撃っていく、そういう場面のコーディングで総仕上げといこう。

関連記事


では例のごとく、Unity Gemsからの翻訳をどうぞ!



November 22, 2012

しゃがんで隠れる(隠れる場所を探す))


最後のセクションは良く戦争モノのゲームである場面だが、敵が遠く離れている場合、どこかに隠れ場所を見つけて、そこに隠れながら撃つ動作をする。時々、別の場所に敵は別の地点に移動をして(プレイヤーはこのときにNPCをやっつけるチャンスなので重要だ)。では関数を定義して、どの場所が安全で、プレイヤーとの距離はどこが一番近いかをチェックしていく。

そこでゲーム上での安全な場所を、おそらくは隠れられる何かのすぐ後ろなどに見つけて Safe (安全) とタグ付けする。次にそういう場所の中で最も近い場所を得る関数が必要だ。そこから安全な場所とプレイヤーとの距離をみて、もしその地点の方ご近ければそこに移動して撃ち始める。
Transform safePlace;
void FindClosestSafe() {
        GameObject[] gos;
        gos = GameObject.FindGameObjectsWithTag("Safe");
        GameObject closest=null;
        float distance = Mathf.Infinity;
        Vector3 position = transform.position;
        foreach (GameObject go in gos) {
            Vector3 diff = go.transform.position - position;
            float curDistance = diff.sqrMagnitude;
            if (curDistance < distance) {
                closest = go;
                distance = curDistance;
            }
        }
        safePlace =  closest.transform;
    }
それによって最も近い安全な場所を取得出来るようになる。それでは TakeCover 関数がさらに必要だ。
void TakeCover(){
    if((_transform.position - safePlace.position).sqrMagnitude >range ){
            Move(safePlace);
            animation.CrossFade("walk");
        }else{
            print ("ズキュン ズキュン!!");
        }
    }
これは単に安全な場所に移動して着いたら撃ち始めるというだけだ。着いた瞬間にズキュン ズキュンといくわけだが、まあ要領は分かってもらえるとおもう。

最後に、AIFunction関数にコードを追加する:
bool AIFunction(){
   if((_transform.position - player.position).sqrMagnitude < attackRange &&
         Vector3.Angle(player.transform.position - _transform.position, transform.forward) <= angle){
      if (!Physics.Linecast (_transform.position, player.position, layerMask)){
         FindClosestSafe() ;
         Vector3 a = safePlace.position - _transform.position;
         Vector3 b = player.position - _transform.position;
         if(a.sqrMagnitude < b.sqrMagnitude){
            _delFunc = this.TakeCover;
         }else{
            _delFunc = this.Attack;
         }
      }
      return true;
   }else{
      _delFunc = this.Walk;
      return false;
   }
}
これで完成だ!!!

そしてこのチュートリアルで作り上げた完成コードを掲載する:

完成した短いコード(クリックして展開)


これでチュートリアルもほぼ終わりまで来たわけだが、デリゲートによって様々なことが容易になったことに気付いてもらえただろうか。関数をいくつか追加きたにも関わらず、Update関数は変更せずに済んでいる。デリゲートのお陰でNPCキャラクターのメイン関数を変更することなく、ビヘイビアを追加出来るのだ。

結論


これでNPCキャラクターについて、さらに開発を進めるための基本は覚えたといってよい。これが皆さんの役に立って、さらに発展的なデリゲートのチュートリアルに進んでいくための手助けになれば、と願う。

さらに敵キャラが壁や木をすり抜けたりせずに動き回らせる方法をみてきた。敵キャラがプレイヤーを視界に入れて走ってくるもカバーした。また簡単なデリゲートについても学んだ。後はこの知識を活かしてさらに高度なことを実現してもらいたい。

今回のチュートリアルも気に入ってもらえたら何よりだ。
------


※ 2013/2/11時点で元記事のうち、この章のみ削除されました。

プロジェクトサンプルはアップしていただけました!目次ページを参照ください。


プロジェクトファイルを公開してもらえるように Mike Talbot氏にお願いしてて今週のどこかにはアップロードしてくれるとのこと。楽しみだな~

先人の知恵を活用して開発していこうぜ!

Unityで初めてのAIキャラクター(6.見えるようにする(視界のチェック))

さて、前回記事で敵キャラがプレイヤーとの距離にあわせて行動させた後、実際に敵キャラの視界にプレイヤーが映るかどうかの問題を考えていきながらコーディングを進めていくところをみていく。

関連記事


では例のごとく、Unity Gemsからの翻訳をどうぞ!



November 22, 2012


見えるようにする(視界のチェック)


もしSpace Shooterの基本チュートリアルを読んでいれば、視界のチェックについては基本的な考え方を理解しているはずだ。NPCキャラクターの前方を表す transform.forward を プレイヤーの transform.forward と比較する必要がある。二つのベクトルを比較するとき、角度を求めるために交わったり交差する必要がないことに注目すべきだ。ベクトルは空間上で動かしてもその属性(方向や大きさ)はそのままだ。例えば北西という向きはロンドンでも別の都市でもどちらにいても同じだ方角だし、北東という向きは同様にいつでも同じ方角だ。ロンドンで北東の向きと、パリで北という向きと、それらのなす角度は45度です。今回もこの考え方で大丈夫だ。

NPCキャラクターからプレイヤーへの向きをこれからチェックするが、もし二つのforward ベクトルがある範囲の中にある場合、NPCキャラクターからプレイヤーが見える状況となります。

すでに範囲はあるので、視界の範囲チェックは、次の方法で行う。
float angle = 90f;

if(Vector3.Angle(player.transform.position - _transform.position, transform.forward) <= angle){}
これだけで十分だ。ここで90度を設定した理由は、人の眼で180度の範囲で見えてはいるが、実際には限定した範囲しか見れないためだ。ifステートメントの中に距離をネストするか、また次のようにすることが出来る
if((_transform.position - _player.position).sqrMagnitude < squareRange &&
      (Vector3.Angle(player.transform.position - _transform.position, transform.forward) <= angle)){}
まだ問題があって、仮に敵キャラの背後に素早く入ると視界チェックから外れてしまう。このためNPCキャラクターがまだプレイヤーに近いことが分かっているときは範囲外に出るまで見失わない仕組みが必要だ。
    bool AIFunction(){
        if((_transform.position - player.position).sqrMagnitude < attackRange &&
            Vector3.Angle(player.transform.position - _transform.position, transform.forward) <= angle){
                    _delFunc = this.Attack;
                      return true;
       }else{
            _delFunc = this.Walk;
            return false;
        }
    }
これでプレイヤーが範囲内にいるか、かつ、視界にいるかの両方ごチェック出来る。

さてNPCキャラクターから本当に見えるかどうかは、どのようにチェックすれば良いのだろうか。壁の背後にいるときにNPCキャラクターから見えるようにはしたくない。ここでPhysics.Linecastの登場だ。名前通りに、ひとつの位置からもうひとつの位置の間に線を引き、何かが間にあれば、関数に戻される。深入りする前に、Linecastについて知っておくべきことがある。次のように使用した場合の話だ:
if (!Physics.Linecast (_transform.position, player.position)) {
    //視界に入っている
}
この場合、NPCキャラクターの視界に入らないことに気付くことになる。この関数は何かが間にある場合に true を戻しアクションをその場合はスキップする。ただ問題はNPC自身だ。_transform.position はモデルの真ん中にあることが多く、Linecast がNPCキャラクター自身のコライダとの衝突を検知してしまう。これには解決方法が二つある。シンプルな方法は、NPCの目の前に置き、forwardもビューと方向を合わせてラインをそこから引く方法だ。長所は、NPCキャラクターの目の位置から見るので現実の状況に近いので、個人的にオススメだ。もうひとつの方法ではレイヤーを活用して、NPC自身との衝突は無視させることだ。

インスペクタ上でLayersをクリックし、Add Layerを選択する。User Layer 8 を選択してNPCをそこに追加する。NPCではレイヤーを設定するのに加えて、プレイヤーには t を加える。ラインが NPC の位置からプレイヤーの位置まで繋がっているのだがプレイヤーにもコライダがある。

結局 linecast 関数に少し修正を加える:
int layerMask = 1 << 8;

void Start(){
    layerMask = ~layerMask;
}

void Update(){
    Debug.DrawLine (_transform.position, _player.position, Color.yellow);
    if (!Physics.Linecast (_transform.position, _player.position, layerMask)) {
           ("次のオブジェクトに衝突 "+hit.collider.gameObject.name);
    }
}
レイヤー変数は、値を与える代わりにビット演算をを行う。指示として8ビット目に移動して 1-> 10000000 とシフトさせる。これは通信や組込みシステムでは広く使われてる方法だがでゲームでは登場頻度がやや少ない。

Start でも同じようなビット演算を行ない、ほしい結果が01111111なので各々のビットで 7 回演算するのではなく 2 回で実現出来る。~ 記号は、 !  記号と同様にNOT演算を行うけどビット版だというところご違いだ。ビットの場合、0 を 1 に変更して、1 を 0 に変更して逆の演算結果を戻す(8ビット)。これはコンプリメントという名前で呼ばれることがある。さて、ビットの話に深入りするのはこれぐらいにしようか。

Debug.DrawLine はシーンビューでLinecastを見るためにある。Linecast関数はNPCキャラクターとプレイヤーの全ての衝突判定を行う。もし何もない場合は、NPCキャラクターがこちらの姿を見られるということだ。

結局やらないといけないことは次の通りだ:
bool AIFunction(){
        if((_transform.position - player.position).sqrMagnitude < attackRange &&
        Vector3.Angle(player.transform.position - _transform.position, transform.forward) <= angle){
            if (!Physics.Linecast (_transform.position, player.position, layerMask))
                _delFunc = this.Attack;
                    return true;
       }else{
            _delFunc = this.Walk;
            return false;
        }
    }

最後に、NPCキャラクターの動きがサマになってきた。次回の最終セクションでは、NPCキャラクターが安全に隠れることが出来る地点を見つけ、いきなり撃ち始めるのではなく安全地帯に移動してから撃ってくるところをみていく。
------

2013/2/11時点で元記事の次の章のみが削除されました。

次回、いよいよ最終記事に続くぜ!

関連記事

Unityで初めてのAIキャラクター(5.さあ近づいてこい(範囲チェック))

さて、前回記事でデリゲートを勉強した後、敵キャラがプレイヤーとの距離にあわせて行動できるようにコーディングを進めていくところをみていく。

関連記事


では例のごとく、Unity Gemsからの翻訳をどうぞ!



November 22, 2012


さあ近づいてこい(範囲チェック)


さあ、NPCキャラクターには動作パターンが設定出来てAからB、BからCへの簡単な動作は出来た(もう十分だよね?)。だけどすぐ近くまで移動しても完全に無視される。まあ、まだ近づいたら何をするか指令してないので当たり前だ。

範囲チェック、すなわちプレイヤーとNPCキャラクターの距離を見ていく必要がある。これには二つのアプローチがある:
  • Trigger Colliderを使用する方法
  • Vector3.Distanceを使用する方法

Trigger Colliderを使用する方法:


長所:

  • 使用するのが簡単
  • エンジンに任せるだけ
  • コライダオブジェクトへの参照を取得出来る

短所:

  • エンジン以上にやらせたい場合に対応しづらい

Trigger Colliderを使用するのは簡単で球体コライダをアタッチしてIsTriggerをセットしてそのあとにコリジョンをチェックする。何かが範囲に入ると誰が入ったかを作成された関数参照で確認を行い、それに対するアクションを適用する。エンジンで行われるので毎フレーム全てのコライダを使って全ての三角形とコライダの距離を測って

Vector3.Distanceを使用する方法:


長所:

  • 使用するのが簡単で実装しやすい

欠点:

  • 関数の選択によっては高価な場合がある
  • オブジェクトへの参照が必要

Vector3.Distance を使用することも出来るが平方根演算は本当に遅いので、sqrMagnitudeを使用して平方根演算を避けることにする。

では、2つの例を示してそれぞれが良い場合を見ていく。はじめに、敵キャラがいて周囲の全て(敵、プレイヤー、NPCキャラクター)と衝突判定をする。それぞれの全てについて参照を保持する、ことは出来ず、それに加えて、定期的に新規のオブジェクトまたは破棄されたオブジェクトを定期的にコールする必要がある。このケースではコライダが役に立つ:

void OnTriggerEnter(Collider other){}

何かが衝突するとコライダへの参照を自動的に取得出来る。これにより配列や参照のリストなどは必要がない。もうひとつは参照のことであり、衝突の後は参照は破棄される。欠点としては、コライダ オブジェクトが丸ごと作成、破棄されるのでコンストラクタ、ガーベージコレクションが実施され、NPCキャラクターと衝突するのは誰か自分でチェックする必要がある。


もし敵キャラをある特定のオブジェクト、例えばプレイヤーと相互作用させたい場合で、さらに何らかの理由により定常的に発生する場合はプレイヤーへの参照Start()時に取得し、継続的に使用するかもしれない。
Transform _player;

void Start(){
   _player = GameObject.Find("Player").GetComponent();
}
次にプレイヤーが範囲内にいるかチェックしよう。すでに述べた通り、Vector3.Distanceはひとつの案だが平方根演算があり、最悪とまでは言わないが避けることができる。

距離の二乗(すでにこのチュートリアルでも用いた)をここでは使用して平方根演算を避ける。squareRangeが100だったら実際の距離は10と覚えれば良い。
Transform _player;
Transform _transform;
float squareFarRange = 100f;

void Start(){
   _transform = GetComponent();
   _player = GameObject.Find("Player").GetComponent();
}

function Update(){
    if(AIFunction())//Player in
}

bool AIFunction(){
    if((_transform.position - _player.position).sqrMagnitude < squareFarRange)return true;
    else return false;
}
よっしゃ、これで関数を修正して範囲の中に入ったらNPCキャラクターに追跡され、範囲から出ると通常のじょうたに戻るように出来る。
bool AIFunction(){
    if((_transform.position - player.position).sqrMagnitude < attackRange){
       _delFunc = this.Attack;
      return true;
   }else{
        _delFunc = this.Walk;
        return false;
    }
}
Updateも合わせて修正が必要だ:
if(AIFunction()&&isCorouting){
    StopAllCoroutines();
    del = true;
}
AIFunctionをコールすると戻り値はtrueとなり、このタイミングではもし範囲内にいればすでにプレイヤーはターゲットに設定されていることに注目すべきだ。isCorouting はチェックされていてNPCキャラクターがコルーチンを実行中の場合は、アニメーション実行中であればそれを停止して、プレイヤーの追跡が始まる。delはtrueにセットされて歩行の関数がコールされる。

_delFunc は Walk のみを使用するため変更されることがない。もし修正するのであれば _delFunc に 含まれる関数が Walk であることを確認する必要がある。

これで敵キャラは巡回ルーチンを備えつつ、近づきすぎると攻撃してきて、プレイヤーが離れると再度、巡回に戻る。ところで、まだ攻撃のコードは書いてなかったのでここでそれを追加しよう。

Attack 関数が必要だ:
void Attack(){
   if((_transform.position - player.position).sqrMagnitude >range){
      Move(player);
      animation.CrossFade("walk");
  }else{
      print ("バシッ!!");
   }
}
そしてようやくAI関数を修正する:
bool AIFunction(){
   if((_transform.position - player.position).sqrMagnitude < attackRange){
      _delFunc = this.Attack;
      return true;
   }else{
      _delFunc = this.Walk;
      return false;
   }
}
距離に応じて異なる関数を割り当てる。

そして完成スクリプトは次のとおりだ。

範囲チェックの短いコード(クリックして展開)


これで十分機能するが、前方から行ってもこうほうから行っても結果が同じであることに気付いたかもしれない。さらに、もし木の後ろや家の後ろにいる場合はどうだろう。範囲チェックのロジックだけだと範囲内にさえいれば木の後ろだろうがNPCキャラクターが見えないはずなのに場所を知られてしまう。次のセクションでそれは修正していこうと思う。
---

次回に続くぜ!

関連記事


Unityで初めてのAIキャラクター(4.デリゲートの使い方)


さて、前回記事に引き続き、敵キャラの動きを直していくのだが、ちょっとだけデリゲートに関するお勉強が必要。

関連記事


では例のごとく、Unity Gemsからの翻訳をどうぞ!


November 22, 2012

デリゲートの使い方


FSMチュートリアルに深入りしてデリゲートやクロージャに怯えてPCのない山小屋に閉じこもる前に、シンプルなデリゲートのサンプルを紹介したい。

もしC または C++ の経験があれば、関数ポインタまたはC++のファンクタと比較する必要がある。覚えるべきは:
void Afunction(){
    //内容はここに
}
void (*fct)(int);
fct = &Afunction;
デリゲートは何かと似通ってるが、より簡単だ。

これからコードを修正してデリゲートを使用できるようにする。最初にデリゲートを宣言して、デリゲートのインスタンスを作成して最終的に関数を割り当てる。それでようやくデリゲートを使用できるようになる。
delegate void DelFunc();
DelFunc _delegate;
_delegate = Afunction;

static void Afunction(){

}
void Update(){
   _delegate();
}
これが基本的な宣言方法でインターネットで見つかるチュートリアルに載っている大部分はここまでであり、リスクをとってこれ以上載せることはしないが、我々はもう少し深入りしたい。static 関数が非staticメンバにアクセス出来ないことは知っていると思う。では今回のNPCキャラクターでシーンに多数ある場合はどうしたらいいのだろうと思うかもしれないけど、同じように動作出来るのだ。
float variable;
delegate void DelFunc();
DelFunc _delegate;
_delegate = this.Afunction; //このポインタに注目できてますか?

void Afunction(){
    variable = variable +10;
}
デリゲートを使用する目的そのものは使用できることだけでなく、柔軟性を持たせることだ。目標はひとつの関数が他の複数の関数からコール出来ることだ。次の例をみてほしい:

デリゲートの短いコードサンプル(クリックして展開)

スクリプトをシーンのオブジェクトにアタッチしてコンソールをみてほしい。エディタをみてみてposには [SerializeField] 属性があるが、インスペクタ上にプライベート変数が表示される。

押されたボタンに応じて、デリゲートに関数が割り当てされる。関数のシグニチャはデリゲートのシグニチャと互いに一致する必要があるため、デリゲートが二つ必要だ。ただしbooleanを使用してupdateで使用される方を判定する。

さらに引数を使用することも可能であり、適切なシグニチャを戻り値とするデリゲートを作成して関数を同じシグニチャで渡します。Actionおよび関数についてはFSMチュートリアルで詳細な説明があるのでここでは説明を省略する。

それではAiScriptを修正してデリゲートを含めよう。マイナーチェンジが必要だ:

AiScriptデリゲートの短いコード(クリックして展開)

Updateは短くなりbooleanチェックをかなり外した。boolean型のdel変数によりどの種類のデリゲートが使用されるべきか判定し、(シンプルかコルーチンか)、isCoroutingにより一回のみコールされることが保証される。そうするとその二つをセットしてでりは実行したいアクションに依存する。

キャラクターは巡回する分には十分だが、まだプレイヤーとの関わり合いがない。次にプレイヤーと相互作用させる方法を見ていく。
------

次回に続くぜ!


関連記事


ブックマークに追加

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

自己紹介

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

ページビューの合計

過去7日間の人気投稿