テラシュールウェアさんのところで、ヒットチェックに関する記事が書かれていましたので補足的なことを解説してみようと思います。

内容はリンク先を参照してみてください。

 

[Unity]地面の接地判定を取得する3つの方法

http://terasur.blog.fc2.com/blog-entry-555.html

・CharacterControllerのisGroundを使う
・CheckSphereを使う
・SphereCastを足元に撃つ

能動的にスクリプトでコリジョン判定を確認する場合はこの例のような感じになるとおもいます。

以前も最適化の記事でPhysicsタイプの関数は全体的に重た目なのでなるべく限定して使用したいということを記述してきましたが、ゲームエンジンのスクリプトがなぜ速度をあまり必要としないかという解説が公式のアンリアルスクリプトの項目に寄せられていまして、 ゲームのスクリプトが他のアプリケーションのスクリプトと異なる点は基本的に待機関数がメイン つまり何かの行動を起こした時に実行される関数が処理の大半を占めるためそれほど高速に動作しなくてもゲームは成立するという説明がされています。

Physics関数は基本的にUpdate内部でコールしますが レイキャストなどのコリジョンチェックはフレームごとに呼び出されるためにゲームの実行速度に影響が出やすいです。 これを回避するための方法としてはWaitForSeconds()を使用してレイキャストの頻度をコントロールする さらにレイキャストの距離を短めに設定して必要ないヒットチェックをしないことなどヒットチェックを最小限に抑える方法があります。

ただしあまり レイキャストの頻度を下げてしまうと オブジェクトが高速に移動する場合にヒットチェックをすり抜ける可能性も考えられますので極端にレイキャストの頻度を減らすことは出来ません。

そこでPhysics関数の弱点を補ってPhysics関数をなるだけつかわない例もありますというのを解説します。

 

■トリガーを使用したコリジョンのヒット判定

Unity3Dでの待機関数というと名前が ”On~ ”で始まる関数全般ですが、 これを積極的に使用して重た目な関数は補助的に使用することで、最適化が期待できると考えられます。

待機型関数の場合はヒットチェックが実行中常に監視されていて 能動的に使用するPhysics関数と比べてコライダーを検出するだけのシンプルな動作で比較的動作が軽いです。

そこでトリガーを使用したヒットチェックの例を解説します。

EXAMPLE1:

TriggerSample1

図のようにキャラクターコントローラやキャラクターモーターを中心にトリガーを効率よく配置して、それぞれのトリガーにスクリプトを設定します。

スクリプト例:

static var triggered=0;

function OnTriggerEnter  (other : Collider) {
triggered=1;
}

function OnTriggerExit (other : Collider) {
triggered=0;
}

 

とても単純なスクリプトですが周囲のコリジョンコライダーにヒットした時、離れた時にフラグのOn、Offを行います。

フラグはキャラクターコントローラーに設定したスクリプトからトリガーのフラグ変数を読み取ってもよいですが、キャラクターコントローラにフラグをまとめてしまって各トリガーに状態を書き込んでもらったほうが見やすくなりそうな気もします。またはStaticタイプを指定すれば外部からも参照しやすくなります 使いやすく工夫してみてください。

static型は使用しない時 nullを放り込んでおけば実質メモリ消費がないはず

トリガーの設置位置はAIのステアリング操作ならば左右斜め前方に配置するとか 壁との距離を一定に保ちたい場合は左右にそれぞれ配置するなど うまくいく配置を試す必要があると思います。

もちろん配置した後ゲームの進行に合わせてトリガーを移動させたりスケールをかけたりゲームにおけるコリジョンの使い方と同じようにするのは自由です。格闘ゲームでは技によって判定が大きくなったり小さくなったりしますよね。

 

EXAMPLE2:

 

hittrig

そのほかに 敵との距離に応じてAIで状態遷移をする場合などは下図のようにキャラクター全体を覆うトリガーでヒットチェックを行うことでレイキャストの使用を避ける事が出来ます

 

TrigHitArray

図のように複数の敵が存在してそれぞれがプレイヤーの攻撃エリア(トリガー)に接触する場合。

攻撃をするための配列を用意して接触時OnTriggerEnterが有効になった時に配列に敵の情報をプッシュし

トリガーから敵が離脱した時に、その敵の情報(IDやTransform)をリムーブすることで効率よく索敵ができます。

その後の攻撃は一旦配列に取り込んだ敵だけを対象にすればよいため無駄にレイキャストをすることもなくスクリプトの高速化につながります。

●Physics.OverlapSphere

配列に取り込む処理を簡略化するのにPhysics.OverlapSphereを使用することも出来ます。

これは球型の判定を発生させて球内部のゲームオブジェクトを配列に格納したい場合に使用します。

例えば 球型の爆発によりエネミーにダメージを与えたい場合は配列を読み取ってそれぞれのゲームオブジェクトに対してダメージを加算します。 またタワーデフィフェンス系ゲームならば攻撃エリアにはいった敵を一気に配列に格納することで レイキャストで距離を判定しなくても配列を参照してゲームオブジェクトのTransform を参照するだけで攻撃有効な距離(例えば一番近くにいる敵)の計算が出来ます。OnTriggerEnter()に仕込むと良いと思いますが処理速度の実測をしていないので自分で配列管理するのとどちらが良いのかははっきりとはわかりません。

ダメージを与えるだけなら敵にスクリプトを仕込んでSendMessageとばしてもいいんですけど 敵が多いと重くてね。。

using UnityEngine;
using System.Collections;

public class Example : MonoBehaviour {
    void ExplosionDamage(Vector3 center, float radius) {
        Collider[] hitColliders = Physics.OverlapSphere(center, radius);
        int i = 0;
        while (i < hitColliders.Length) {
            hitColliders[i].SendMessage("AddDamage");
            i++;
        }
    }
}

EXAMPLE3:実装例

dotaSample

以前製作していた DOTA系のミニオンキャラクターのトリガーの配置ですがゲームのルール上 道で敵と出会った時にキャラクター同士すれ違いができないように 道全体をカバーするようにトリガーを配置してトリガー同士が重なる時にヒットチェックがでるようになっています。敵と自分の間に遮蔽物がある場合があるので、敵がトリガーにヒットしたときに一回だけ相手に向かってレイキャストします。 帰ってきた情報がトリガーにヒットした相手のコライダーと同じならば、敵が見える位置にいるので攻撃に移ります。

あとは状態に応じてトリガーのスケールなどを変化させることで細かい調整をしています。ここらへんはトライアンドエラーですね。 レイキャストだけの使用だとすり抜けが起きてしまうのでこのような構造になりました。

ちなみにこのゲームは諸事情あって棚上げになってます。

製作過程を見たい方は 過去ログを参照してみてください。