2013年3月29日金曜日

TNet 1.65, 1.66


TNet バージョンアップ情報です:

http://www.tasharen.com/forum/index.php?topic=2650.0
March 20, 2013, 09:15:53 PM


1.6.5:
- 新機能: TNManager.channelID を追加し、現在のチャネルを取得できる関数を準備した
- 新機能: 各々のチャネルでカスタム文字列を特定できる機能を追加し、これによりチャネルに関する説明文をつけることが出来るようにした
- 新機能: 存在しない RFC 関数を実行しようとするとエラーメッセージが Unity で表示されるように機能を追加
- 修正: バージョンが一致しないときも保存ファイルがロードされてしまう不具合を修正。
- 修正: TcpChannel を Channel に名称変更(TCP とは関係がないため)。
- 修正: プレイヤーが接続状態でチャネルに入室しているときに TNManager.isInChannel の戻り値が true となるように修正。
- 修正:「 接続状態であればデータ送信を行う」ケースとなっていた処理の多くを、より正しい対応である「チャネルに入室していればデータ送信を行う」処理に修正。
- 修正: その他、様々なマイナー修正

1.6.6:
- 新機能: ロビーで TCP および UDP のいずれかを選択して使用できる機能を追加できるように サーバアプリを再構築した。
- 修正: Star Dots での開発から学んだ様々な微修正や修正を組み込んだ。
-----------

TNetも日々改善が進んでいるぜ!しかし、Unity New GUI を開発する時間は一体いつ確保できているんだ??

2013年3月22日金曜日

NGUI 2.5.0

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

※Unity 3.5.4以前を使用している方はアップグレードしてはいけません!!その場合はAsset Storeからうっかりダウンロードしないようにも注意が必要

http://www.tasharen.com/forum/index.php?topic=11.25
March 20, 2013, 03:34:35 PM

2.5.0
- 削除: Unity 3.5.4 より前のサポートを終了。3.5.4 より前のバージョンを使用している場合はアップデートしないこと!
- 廃止: Sliced, tiled, および filled スプライトを廃止予定(depreceated)
- 新機能: 通常のスプライトで、スプライト描画方法に関するオプションを新たに機能追加。
- 新機能: NGUI ウィジェットに 視覚できる配置用のハンドルを機能追加。
- 新機能: ウィジェットの追加により、ない場合は UI 階層を自動作成する機能を追加。
- 新機能: NGUI メニューをデザインし直し、新たなオプションおよびショートカットキーで再設計。
- 修正: ウィジェット選択ボックスが padding を正しく考慮しない不具合を修正。
- 修正: ピボットの変更によりウィジェットを視覚的に移動する不具合を修正。
- 修正: フォント シンボル がオフセットのために inner rect の代わりに padding を使用するように修正。
- 修正: フォント シンボル をゲーム内で使用する際に、事前にエディタで使用されている必要がある不具合を修正。
- 修正: Tween の初期化/スタート時の方法を修正。
- 修正: 下位互換のため、UISlider.fullSize プロパティを再度追加。
- 修正: Unity 4.1 関連の修正。
- 修正: 様々な軽微な修正やマイナーな変更。
-------

過去のバージョンとの下位互換も少しづつながら手を入れ始めてきた様子。別記事で取り上げたとおり、色々と悩みながら開発を進めているのは事実のようだ。

関連記事

NGUI作者、新バージョンに大いに悩む
http://gamesonytablet.blogspot.com/2013/03/ngui.html

2013年3月18日月曜日

NGUI作者、新バージョンに大いに悩む

なにやらNGUI作者が新バージョンをどうするか悩んでるようだ。Unityのアセットストア売り上げNo.1でUnity Technologies社にも入社して何を悩むのかとおもうかもしれないが、大真面目に新バージョンについてどうするか、ということのようだ。

NGUI掲示板での告白がGoogle翻訳ではうまく伝わりきらないんで、意訳と口語体バリバリの"超訳"でどうぞ!

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


今日はめずらしく真剣にみんなの意見を聞きたいことがあって投稿してみることにしたよ。是非意見を聞かせてくれよ!

下位互換ってマジ大変・・・


実はNGUIの開発をやってきて、前々から下位互換で身動きがとれなくなってたんだ。新しい解決策を見つけるにつれて(たとえばイベントのハンドリングを例にすると分かり良いのだけど)、過去プロジェクトとの下位互換を考えると NGUI をどうしても変更できない場面があった。だからこそ NGUI はコア部分は一年前と対して変わってないし、その証拠として NGUI フリー版でも現行バージョンの NGUI でされたプロジェクトは開けるようにしてきたんだ。

いやさ、考えてみると Unity も同じ悩みあんだろうね。いっつも下位互換を維持して修正しないのと、何か変更して良く修正すんのと、両方の質問があるときは勝つのがいつも下位互換だったんだよね、チクショー。

ま、知っているとおもうけどね、Unity でのオイラの仕事は "次世代" UI システムを作ることなわけさ、NGUI の後継になるようなモンをね。これを最高級の仕事にするためにはね、時には妥協も必要ってわけさ。イベントシステムを変えていきたいのは良い例でさ(これは Unity の将来バージョンで個別に同梱されるとおもうけどさ)。

イベントとかスプライトとかグッと改善したいんだけど・・・


もっと具体例を言わせてもらうとね、NGUI にはちいと知られた OnPress(bool isPressed) イベントってもんがあるわけさ。新システムは  OnPressEvent() および OnReleaseEvent() を代わりに送信すんだけど、この方が Playmaker とか他のツールと相性が良いわけでね。新しいイベントシステムではね、OnDragEnterEvent / OnDragExitEvent なんか追加しちゃったりするつもりでね、これでボタンをタッチした後にスライドして別のとこに行ってもバッチリ対応できるように考えてるわけさ。このイベントシステムは Unity ネイティブなんでね(C++ 側でビルドしてるから)、自然とレスポンスも大分と早くなるわけで。OnGUI ボタンともうまく連携するわけで、デバッグでまだ使い続けたい人にも役立つはずさ。

他に変更する箇所の例をあげるとなー、UISpriteとか UIFilledSpriteとか UISlicedSprite とか UITiledSprite(種類多かったなー)をついに一個のスプライトでまとめて置き換えるつもりなんでさ。コイツが出来れば、コンテンツの描画とか、デプスと Z バッファの優先順位、イベント通知(OnSubmit とか値が変わった時、とかね)の完全なリプレースとか、その他にも色々選択肢が広がるわけさ。

聞いてりゃ良くなることばっかりと分かるだろうけどさ、改善するともう下位互換はボロボロになるわけで。全部変更して NGUI の過去プロジェクトと下位互換を維持するなんて無理!!って言ってももう10万ユーザもいること考えると、見捨てることになるとトホホなんだよね。

配信方法もネックでさ・・・


まだとっておきのこと言ってなかったな。大幅変更して NGUI 3.0 にしたとして、アセットストアで既存バージョンをただ置き換えちゃうのはイケテない。何でかって、必要なときに古いバージョンをダウンロードする方法ないんだし、それを買ってくれたみんなに強制するなんざオイラ自身も許せないわけさ。あと考えられる方法で #ifdef 使って新しいシステムと切り分けるのもおバカなアプローチで、コードが倍になるしグチャグチャになっちゃう。これもプライドが許さんな。フリー版でリリースしてよ!って声あるかもしんないけど、それは勘弁ってだけじゃなくて、過去に購入したかどうかチェックするだけでも気が狂うような作業になるわけさ。10万ユーザ分で切り分ける作業はチョット時間ないッス。

長ったらしくてスマンだけど、別のプロダクトとして販売するから皆にそれで選んでもらいたいってことなんだよね。

頼む、意見をくれ!(;´Д`)


でね、オイラの質問が出てくるわけさ。下位互換とオサラバして NGUI 新バージョンを作成してそこから今年後半ぐらいに新しい UI システムにスムーズに移行できるようにしたら、それでも使ってくれる?あと、もうひとつ重要なことなんだけどオイラの立場にたってもらって、それは時間を費やすべきことだとおもってもらえるだけのプロダクトだと思うかな?

あの・・・、実はオイラの結論は「いや、作るべきじゃない」に傾いてきているんだ。まあ、予想は皆としてはフリー版で対応してくれよっていう声が強いんじゃないかな、と思ってることが大きいんだけどね。是非みんなの意見も聞きたいわけさ。

この究極の選択、皆はどう思う?
------------

ちいと口語体過ぎたかな・・・でも言っていることは合っているぜ?!

意見ある人はどんな英語でも良いから、フォーラムで回答してあげようぜ!

2013年3月17日日曜日

Missing Scriptsエラーの解消(後編)

前回記事に続いて、Missing Scripts エラーをエディタで自動的に解消するスクリプトの解説だ。

関連記事


Missing Scriptsエラーの解消(前編)
http://gamesonytablet.blogspot.com/2013/03/missing-scripts.html

ではいつもどおり、Unity Gemsからの翻訳をどうぞ!

----------
http://unitygems.com/lateral1/
November 14, 2012

データを利用可能なスクリプトのフィールドと比較


ここからが難所だ。振り返ると、Missing Script が出たゲームオブジェクトで GetComponent を行うと null が戻る。とすると null しか戻らないものに対してどうやってプロパティの一覧を取得するのか、ということが課題となる。

さらに問題なのが、Unity 自身が C++ バックエンドの中でスクリプトの取得およびエディタ割り当てを内部で実行していて、Editor クラスでも手が届かないのでアクセス権もないことだ。

これで私はかなり悩まされた(実は始める前からこの辺りは相当問題になるだろうことは理解していたが、まあ話をわかり易くするため、その辺りの前後関係は省略する)。

事実関係を整理すると:

  • Missing Script で GetComponent を行うと null が戻る
  • シリアライズされたデータは、それに対する参照がないとアクセスする方法はない
  • インスペクタではどのスクリプトか分からなくともプロパティの一覧を表示する
  • 明白なことは、最後のポイントがこの問題の鍵だということだ。C# で利用可能なものは、そのデータストリームに対するアクセスが必要だ。さらに、それはインスペクタで表示される Editor でなければならない

このため、やるべきことはそのシステムに適切に統合して、Editor の中で Missing Object  を検知できるようにすべきだ。つまり MonoBehaviour 自身のカスタム エディタを記述してみよう。

必然的に、これを行うためにはデータストリームをインスペクタ上の serializedObject で受け渡しをしないといけない。これができれば一歩前に進める。次にやるべきことはMonoBehaviourが Missing Script かどうかの判定だ。このために取得したオブジェクトの SerializedProperties をみる必要がある。

さて、次はデバッグの出番だ。データストリームには何が含まれているか、確認が必要だ。

SerializedObjectの値を見ていくには GetIterator() をコールする。すると SerializedProperty が戻るので、さらに Next または NextVisible をコールして、値の間を遷移していく。これら二つのコールは共に boolean 型の引数を指定してデータのドリルダウンが出来る。最初のコールでは true でないといけないが、逆にその後のコールでは false でないといけない。
var property = serializedObject.GetIterator():
var first = true;
while(property.NextVisible(first))
{
     first = false;
     Debug.Log(property.name);
}
シリアライズ (インスペクタで表示) されたデータの各メンバーに対して Debug.Log を使用することで、現在 null 格納されている m_Script 変数に格納されていることが分かる。そうすると、ストリームの残りはデフォルトのインスペクタが描画しているパブリックのプロパティを保有している。これで解決策は完了で、データストリームはアクセスが可能だ。

後やるべきことは、これらの変数を候補となるスクリプトで整理して、全部が m_Script とマッチングするかチェックして、それさえ出来れば変数としてセット出来る。
// シリアライズ プロパティのスクリプトのコピーを作成
// して、後に参照
var script = iterator.Copy();
// 全てのスクリプトの一覧を取得
var candidates = scripts.ToList();
// 残りの引数をチェック
// して、マッチングするものがあるか確認
while(iterator.NextVisible(false) && candidates.Count>0)
{
    // 候補のスクリプトを現在のプロパティを保有する
    // サブセットに設定
    candidates = candidates.Where(c=>c.properties.ContainsKey(iterator.name)).ToList();
}
// もし候補が一つだけの場合は
// それを使用
if(candidates.Count==1)
{
    // スクリプトの参照をセット
    script.objectReferenceValue = candidates[0].script;
    // データストリームを更新
    serializedObject.ApplyModifiedProperties();
    serializedObject.UpdateIfDirtyOrScript();
}
// もし複数マッチングした場合は
// ユーザに選択肢を提供
else if(candidates.Count > 0)
{
    foreach(var candidate in candidates)
    {
        if(GUILayout.Button("Use " + candidate.script.name))
        {
            // スクリプト設定を編集
            script.objectReferenceValue = candidate.script;

            serializedObject.ApplyModifiedProperties();
            serializedObject.UpdateIfDirtyOrScript();
        }
    }
}
// それ以外はエラーメッセージ表示
else
{

    GUILayout.Label("> 適切なスクリプトは見つかりませんでした");
}

既存の壊れたオブジェクトを修正して正しいスクリプトを参照させる


前述のスクリプトでこれを確認することが出来る。m_Script の SerializeProperty である、objectReferenceValue を新しいスクリプトの値と同じにして、親の SerializeObject で ApplyModifiedProperties() をコールする。

Bodging に関するテクニック


さて賢明な方はすでにお気付きと思うが、欠点として、スクリプトを一つづつしか修正出来ない。どうやって全てのスクリプトをチェックして、一つづつ選択せずに直せるのだろうか。

その解決策が Bodging だ。二段論法でいうと:


  • 実際のデータ上でアクセスできる場所はインスペクタ
  • インスペクタはエディタ上で選択しないと修正できない


このためエディタをうまく騙して Missing Objects を各々選択させて、前述のコード実行を待機して、次のスクリプトに進む処理が必要だ。

これは Update 関数で工夫を凝らす必要があり、インスペクタ上でコードを追加して、どのコードが作業完了か認識させる必要がある。
        if(GUILayout.Button("Fix Now", GUILayout.Width(80)))
{
    FixMissingScripts.tried = false;
    EditorPrefs.SetBool("Fix", true);
    processList.AddRange(
        Resources.FindObjectsOfTypeAll(typeof(GameObject)).Cast().Where(c=>c.GetComponents().Any(o=>o==null))
        );
}

fix ボタンをクリックすると壊れたスクリプトは processList に移動され、カスタムエディタの Update 関数で処理される。ここではインスペクタのコードで作業済みの static 変数に false を渡していることに注目して欲しい。

次に Update で行うことは
if(!trying)
{
    if(processList.Count > 0)
    {
        FixMissingScripts.tried = false;
        var first = processList[0];
        FixMissingScripts.tryThisObject = first;
        processList.RemoveAt(0);
        Selection.activeObject = first;
        if(processList.Count==0)
        {
            nextTime = 10;
        }
        Repaint();
        trying = true;

    }
}
if(trying && FixMissingScripts.tried)
    trying = false;
何か作業中かチェックして、そうでなければ次をキューに入れて、完了時にインスペクタに作業済みの変数を更新するように指示する。

もし作業済みならば、現在のスクリプトの処理が完了したか確認をして、完了していれば次のアイテムへと進む。

結論


Bodging は有効に活用できる。これは時に水平思考の一種ぎ必要だ。どうすれば特定のデータストリームを取得出来るだろうか。しばらく画面を眺めたうえでアハ体験に遭遇するようなことご必要で、インスペクタで取得出来るならばインスペクタ自身になるためにはどうすれば良いだろうか。残りは残念ながら Bodging により、最初の回避策を上手に活用して 100 個長のスクリプトでも正しい動作をさせる必要がある。

このプログラムは次のリンク先からダウンロード出来る。

プログラムをダウンロード


メニューから Window > Fix Missing Scripts とすることで修正用のウィンドウが開く。
--------

やったぜ!これでMissing Scriptsの解消が出来る!と安心するのは早計。

実際使ってみた感想としては:

  • 進捗状況が分からない
  • Missing がスクリプト以外の場合はみつけてくれない

など課題はある。考えようによっては上記スクリプトをカスタマイズすればそのあたりは解消できそうだし、ダメ元でチャレンジというぐらいのスタンスが良いのかも。

エディタプログラミングも勉強すると便利だぜ!

Missing Scriptsエラーの解消(前編)

Unity使っててプロジェクトをインポートするときなどに稀に Missing (Mono Script) などと表示されていることがある。

慣れている方なら「仕方ないな」といって手動で正しいスクリプト見つけて修正していくのだが、これをエディタ上で自動的に修正してくれるパッケージがあったので紹介したい。

試す前にバックアップをとっておくことをオススメするが、再現ビデオにあるとおりうまく行った場合は複数のスクリプトを使用する変数から自動判断して修正してくれる。
(もし変数が同じスクリプトが複数ある場合は、選択するダイアログがある)

プログラムをダウンロード

メニューから Window > Fix Missing Scripts とすることで修正用のウィンドウが開く。

まずまず便利といったところだろうか。Unity Gemsというサイトでこのプログラムのロジックまで説明してあるので紹介したい! (ちょっと長いので前編・後編に分ける)

----------
http://unitygems.com/lateral1/
November 14, 2012

次のケースでこのチュートリアルが有効活用出来ます:

  • Missing Scripts エラー を Unity で修正する方法を理解したい
  • 複雑な問題を切り分けるのにデバッグを使うアプローチを理解したい
  • SerializedObjects および SerializedProperties の理解を深める
  • Bodging テクニックで出来ることを知りたい


はじめに


 "Missing MonoBehaviour" エラーは Unity でプログラミング始めて間もなく遭遇するエラーだ。インポートやプロジェクトの移動を行った後、あるいはウッカリ変更した何かによって、突然コンソールに表示されてスクリプトの参照が壊れてしまう現象だ。スクリプトそのものをリストアしても参照は復旧しない。
これが一つや二つのオブジェクトなら良いが、これが100だったら本当に悪夢そのものだ。

私もこの問題に遭遇したが幸いに自分なりの解決方法を見つけた。自分が辿った手順が何か別の複雑な問題を解決するヒントになるだろうと思ったので共有することにした。こういう問題では世界中を敵にしたような絶望を感じがちだが糸口はあるのだ。

問題の解決方法


この問題の解決は四つのステップに分かれている。

  1. Missing Scripts の発生したゲームオブジェクトおよびプレハブの特定
  2. 全ての利用可能なスクリプトを特定
  3. データ (インスペクタで表示されているフィールド) を利用可能なスクリプトのフィールドと比較
  4. 既存の壊れたオブジェクトを修正して正しいスクリプトを参照させる

Missing Scripts の発生したゲームオブジェクトおよびプレハブの特定


さて最初のステップは容易だろう。Unityで現在のシーンやプロジェクトビューにある全てのゲームオブジェクトは一覧に出来る。良くある方法で、FindMissingScripts スクリプトで全てのオブジェクトを選択する必要がある方法は不便なのでここでは別の方法を考える。

全てのオブジェクトを見つけるには Resources.FindAllObjectsOfType を使用すれば良いだけだ。これでリソースを見つけられるだけでなく、それに加えてUnity 独自のものも、全て見つけられるといって良い。まずは全てのゲームオブジェクトを見つけて、それからそれらの全てのコンポーネントを見つけて Null でないか見つけることになる。

Linq では魔法のように一行で記述することも出来るが、可読性のために分割してみる:
brokenList =
    // 全てのゲームオブジェクトを見つける
    Resources.FindObjectsOfTypeAll(typeof(GameObject))
        // FindObjectsOfTypeAll は Object[] を戻す
        // このため GameObject enumeration ひ変更
        .Cast()
        // 各々のオブジェクトをチェック
        .Where(
            // 全てのゲームオブジェクトを取得
            c=>c.GetComponents()
                // null を戻すものごあるか確認
                .Any(o=>o==null)
        )
        // これをリストに型変換
        .ToList();
これでプロジェクトやシーンでの有無にかかわらず、壊れてるスクリプトを見つけることが出来る。

全ての利用可能なスクリプトを特定


これも同じような方法で対処出来る容易なものだ。MonoScript 型のオブジェクトを全て見つけるだけだ。ここでは条件がもう一つだけある。次のステップではインスペクタで表示されているプロパティにもとづいて Missing Scripts とマッチングを行う。すなわちスキャンしている全てのスクリプトのプロパティを知る必要があるということだ。ここは時間をじっくりかけて SerializeField をセットしてある public および private フィールドを取得したうえで 参照を速やかに行えるように Dictionary に格納する。
// 候補となる全てのスクリプト
static List scripts;
// スクリプトをスキャンしたかどうか

static bool _initialized = false;

// スキャンされたスクリプトを保持
class ScannedScript
{
    //全ての serialized プロパティ
    public Dictionary properties;
    // インスタンス id
    public int id;
    // スクリプト自身
    public MonoScript script;
}

// これらスキャン完了したスクリプトを初期化
void Initialize()
{
    if(_initialized)
        return;
    _initialized = true;

    ScanAll();

}

void ScanAll()
{
    // 全てのスクリプトを取得
    scripts = Resources.FindObjectsOfTypeAll(typeof(MonoScript))
        // MonoScript のコレクションとする
        .Cast()
        // システム スクリプトでないことをチェック
        .Where(c=>c.hideFlags == 0)
        // コンパイル完了であり、
        // クラスが取得出来ることをチェック
        .Where(c=>c.GetClass() != null)
        // 各々についてスキャンされたスクリプトを作成
        .Select(c=>new ScannedScript { id = c.GetInstanceID(),
            script = c,
            // プロパティは全て [SerializeField] をセットし
            // public および private とする必要がある
            properties = c.GetClass().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
                .Where(p=>p.IsPublic || (!p.IsPublic && p.IsDefined(typeof(SerializeField), false)))
                .ToDictionary(p=>p.Name)
        })
        .ToList();
}
実行することで結果として、候補となるスクリプトが得られ、各々のスクリプトには シリアライズされた dictionary が得られる。

テクニカルかつ高度だがわかり易い。
------------

後編に続くぜ!

関連記事

Missing Scriptsエラーの解消(後編)
http://gamesonytablet.blogspot.com/2013/03/missing-scripts_17.html

2013年3月13日水曜日

NGUI 2.3.5, 2.3.6

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

http://www.tasharen.com/forum/index.php?topic=11.25
March 09, 2013, 05:08:31 AM

2.3.5:
- 新機能: フォント シンボルにオフセットを設定可能とする機能を追加、位置調整がより容易となる
- 修正: UISlider が、デリゲートをコールする前に "現在の" プロパティをセットするように修正
- 修正: 2.3.4により発生していたチェックボックスのアニメーションでの不具合を修正
- 修正: その他、軽微でマイナーな修正をいくつか対応

2.3.6
- 新機能: シンボルや顔文字(emoticon)を簡単に追加する機能を追加(フォント選択でやり方は確認可能)
- 新機能: UIPanel について、条件により判定する良くある間違いでの警告を行う機能を追加
- 新機能: ウィジェットおよびスプライトのインスペクタにいくつかの改善を追加
- 修正: フォントで存在しないのに "symbols" オプションをラベルに表示する必要があった不具合を修正
- 修正: ハードコードされていた画面の高さにもとづいたタッチの閾値を除外した
- 修正: スライダーが "フルサイズ" のプロパティを必要としていた不具合を修正
------

ちなみに、例のDynamic Fontの正式採用は次バージョンのUnity Pro版でリリースして、問題なければPro版以外でリリース予定とのことだ。


パッチでも良いが次こそ正式採用したリリース版を期待しているぜ!

関連記事


【朗報】NGUIにダイナミックフォント正式採用の方向性
http://gamesonytablet.blogspot.com/2013/02/ngui.html

2013年3月6日水曜日

AI開発&クォータニオン入門~8.コルーチンでアニメショーンを待機

関連記事

今シリーズの最終回。例のごとく、Unity Gemsからの翻訳をどうぞ!

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

http://unitygems.com/quaternions-rotations-part-1-c/
October 14, 2012


コルーチンを使用してアニメショーンを待機する


すでに範囲内にあれば攻撃のコルーチンが開始される。

ここで実現したいことは通常の動作をポーズさせて、攻撃アニメショーンを再生してアニメショーンが半分進んだところで、攻撃がヒットしてダメージを適用することだ。次の方法でこれを実現する:

最初に - ルーチンをより便利なものとするため、Attack をコールしたときに敵をウェイクアップするようにする。これは敵同士で衝突した時も Attack をトリガー出来るので便利だ。
IEnumerator Attack(Transform victim)
{
    sleeping = false;
    _busy = true;
    target = victim;
    _attack.enabled = true;
    _attack.time = 0;
    _attack.weight = 1;
    //Wait for half way through the animation
    yield return StartCoroutine(WaitForAnimation(_attack, 0.5f));
    // 範囲内にまだいるかチェック
    if(victim && (victim.position - _transform.position).sqrMagnitude < _maximumAttackEffectRangeSquared)
    {
        // ダメージを適用
        victim.SendMessage("TakeDamage", 1 + Random.value * 5, SendMessageOptions.DontRequireReceiver);
    }
    // アニメショーン終了を待機
    yield return StartCoroutine(WaitForAnimation(_attack, 1f));
    _attack.weight = 0;
    _busy = false;
}
 _busy = true とセットすることでルーチンを実行している間はUpdateを無効にする。 _attack はこちらで有効化して制御するアニメショーンであり、最初にセットして、重み付けの weight = 1 とする。これでアニメショーンが再生開始される。次が素晴らしいところだが、アニメショーンをモニタリングする新しいコルーチンを実行して、半分まで完了するのを待機する。ルーチンはこの後に見ていく。

アニメショーンが 50%  完了すると、攻撃した被害者 (victim) がすでに死亡してないかチェックして、まだ範囲内にいるか、さらにはそれらの条件が満たされてる場合はTakeDamage メッセージを渡してランダムな量のダメージが反映される。

次にアニメショーン終了を待機してから busy ステータスを外して、アニメショーンを無効化する。

このアニメショーンを待機するコルーチンについて見ていこう。
public static IEnumerator WaitForAnimation(AnimationState state, float ratio)
{
    state.wrapMode = WrapMode.ClampForever;
    state.enabled = true;
    state.speed = state.speed == 0 ? 1 : state.speed;
    while(state.normalizedTime < ratio-float.Epsilon)
    {
        yield return null;
    }
}
見ての通りかなり簡単だ。最初にアニメショーンがループやリセットしないようにする。セットしないと、終了のところを逃がすことがあるためだ。さらに enabled を true にセットし、speed がゼロとなることを確実にして、ルーチンが終わらないような不具合に陥らないようにあらかじめ手当てする。

次にループは .normalizedTime を待機して渡した値より大きいか等しくなるまで待機する。(浮動小数点の誤差を吸収する係数)

様々なステート


これで攻撃およびダメージのメッセージを送信出来るようになったため、メッセージをハンドリングする方法を見ていく。このデモでプレイヤーは無敵だが、敵同士はダメージを与えることができる。

敵キャラは TakeDamage を受け取ると次の動作を行う。
void TakeDamage(float amount)
{
    StopCoroutine("Attack");
    health -= amount;
    if(health < 0)
        StartCoroutine(Die());
    else
        StartCoroutine(Hit());
}
まずは現在の攻撃は全て取り止めて、ダメージを与えて、Hit または Die コルーチンのどちらかを開始する。
IEnumerator Die()
{
    _busy = true;
    _animation.Stop();
    yield return StartCoroutine(PlayAnimation(_die));
    Destroy(gameObject);
}

IEnumerator Hit()
{
    _busy = true;
    _animation.Stop();
    yield return StartCoroutine(PlayAnimation(_hit));
    _busy = false;
}
両方のコルーチンはアニメショーン終了まで再生し、Die はオブジェクトを破棄して、Hit はそれを再び有効化する。

さて、ようやくこれでこのチュートリアルは終わりまで一息だ。RotateToFaceTargetは初期に作成した回転スクリプトを置き換え、常にプレイヤーをみるのではなくターゲットの位置をみるようにするので、内容は良くみてほしい。この仕組みによって敵同士がターゲットとなりえる。他に注目すべきは WalkingAnimationだ:
using UnityEngine;
using System.Collections;

public class WalkingAnimation : MonoBehaviour {

    Transform _transform;
    Vector3 _lastPosition;
    AnimationState _walk;

    public float minimumDistance = 0.01f;

    // Use this for initialization
    void Start () {
        _transform = transform;
        _lastPosition = _transform.position;
        _walk = animation["walk"];
        _walk.layer = 2;
    }

    // Update is called once per frame
    void Update () {
        var moved = (_transform.position - _lastPosition).magnitude;
        _lastPosition = _transform.position;
        if(moved < minimumDistance)    
        {
            _walk.weight = 0;
        }
        else
        {
            _walk.weight = moved * 100;
            _walk.enabled = true;
            _walk.speed = 1;
        }
    }
}
このスクリプトは敵が移動する速度にもとづいて歩行アニメショーンをブレンドする。

最後に


このチュートリアルご役立ったならば何よりだ。コメントを残すのは自由で、他にもどんな機能を紹介したら良いかなどもコメントあると有難い。

Mike(whydoidoit)氏
-----------

今シリーズはいかがだっただろうか。筆者自身はUnity Gemsから色んなことを学べて感謝しているが、やはりプロジェクトサンプルをもとに自分のアイデアを加えて色々なテストをしてみるのがベストだとおもう。

今年はUnityは日本語情報も充実してきているぜ!

ブックマークに追加

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

自己紹介

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

ページビューの合計

過去7日間の人気投稿