【Unity】RayCastを使って障害物との距離を測る

”障害物を乗り越える”をテーマに挑戦している日々なんですが、
曲がりなりにも越える処理を作る事ができました。

しかし、処理時間と転倒時に乗り越えモードの終了ができない、
二つの問題点が残っています(T-T) グスッ

これを解決するのに四苦八苦しているのですが、
いろいろ考えて思いついたので、記事にまとめてみます。


まず、動作の安定感を出す為に問題点を出してみました。
乗り越えモーションは、
ハードル③.jpg
障害物(黒ブロック)の左にあるOverZone(青のCollider)と言うトリガーを利用して、
OverComeModeに切り替えています。

ゾーンの内部で上キーを押せば、乗り越えモーションに切り替わるように
設定しているのですが、ゾーンの幅が1.5スケールあります。

当然、接触と同時にキー入力すれば差は1.5となりますが、
障害物寄りでキー入力すると距離は変化します。

ブロックの幅が1スケールでモーション起点が0~1.5スケールあるので、
距離としては、1~2.5の幅ができます。

最大速度を10としてるので、通過時間に0.1~0.25秒の差があります。

なので、モーションの時間を0.5秒として処理していましたが、
速度も変化するって事が最大の難点になっています。

仮に速度を2で移動させた場合、
1スケール進むのに0.5秒掛かってしまいます。
ゾーンの左端でモーションを起動すると幅が1.5あるので、
障害物の手前で処理が終了して床に落下して障害物に引っ掛かります。

処理時間と落下防止を考える必要があるのですが、
どうするか…

まずは、処理時間調整を考えてみます。
処理時間は、距離と速度が判れば計算できるので、
速度はvelocityから取れるので、距離を計測する方法を調べてみました。

いろいろとあったんですが、実用的なのが二つ。

・オブジェクトをpublicで取得して距離を計算
・Rayで距離を計算

ただ、オブジェクトを取得しておくについては、障害物の数を増やすと
いちいちアタッチする必要があるので、Rayを使う事にしてみました。

まずRayCastなんですが、LineCastと少し違います。

RayCastとLineCastの違いは、
・LineCastは始点と終点を指定してRayを張る
・RayCastは、始点から方向と長さを指定してRayを飛ばす

の違いがあります。

どちらのRayでも、接触するCollider情報を取得する機能があります。
なので取得情報を元に距離を計算しようと言う試みになります。

まず、RayCastの張り方ですが、
スクリプトリファレンス
Physics.Raycast
Physics2D.Raycast
本来は、Physics2D.Raycastを参考に組みたいのですが、少し分かりにくかったので、
Physics.Raycastを参考にRayを張ってみます。
RayCastTest

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class RayCastTest : MonoBehaviour
{
public LayerMask blockLayer;

// Start is called before the first frame update
void Start()
{

}

// Update is called once per frame
void Update()
{
Vector3 origin = transform.position;
Vector3 distance = Vector3.right * 1f;

RaycastHit2D hit = Physics2D.Raycast(origin, Vector2.right, 1f, blockLayer);

// Does the ray intersect any objects excluding the player layer
if (hit)
{
Debug.DrawRay(origin, distance, Color.yellow);
}
else
{
Debug.DrawRay(origin,distance, Color.blue);
}
}
}

説明すると、
Physics2D.Raycast(原点、方向、長さ、指定レイヤー)で張る事ができるようです。
このRayに接触したCollider情報を取得するのにRaycastHit2Dを使います。
2D用は、Physics2D・RaycastHit2Dを使います。
Physics・RaycastHitだと3D用になるので注意でっす。

スクリプトの中身についてですが、
変数originは、Rayの原点座標です。
このスクリプトをアタッチするブロックの座標を原点にします。

方向なんですが、座標で入力するようなのでVector2.rightで指定しました。
これで右方向って事になります。
左ならVector2.right*-1とすれば指定できます。

Rayの長さは1スケールです。

レイヤーについてですが、Rayは接触する全てのCollider情報を取得するようになっています。
レイヤー指定する事で、そのレイヤーに属するColliderのみの情報を取得させる事ができるので、
読み取りたいオブジェクト情報だけを区別するのに便利です。

目視できるようにDebug.DrawRayも設定します。
Debug.DrawRay(原点, 方向と長さ, カラー)で表示させる事ができるようです。

原点は、Rayと同じoriginを使います。
二番目がややこしい”方向と長さ”です。
RayCastと違って、方向と長さを同時に指定する必要があるみたいです。
方向と長さを求めるには、Vector3.rightもしくは、Vector2.rightを使います。

これは、座標で言う所の(1,0,0)を表しています。
なのでVector3.rightに長さを掛けてやると方向と長さが設定できます。

例えば、
Vector3.right*100fとした場合、(1*100,0,0)と同じになると考えてもらえれば
楽だと思います。
X軸に100ってことで、右向き100の長さとなります。
座標入力なんでマイナスを掛ければ逆となります。
Y軸の場合は、Vector2.upに長さを掛ければ上向き・下向きが指定できます。

スクリプトが組めたので、Unityの準備をします。
2Dオブジェクトの正方形スプライトを二枚追加して、グリーンとブルーに色を設定しておきます。
RayCast①.jpg
グリーンは、Rayを飛ばす側にするので、RayCastTestスクリプトをアタッチしておきます。
アタッチしたRayCastTestのBlockLayerをクリックして、blockを選択しておきます。

続いてブルー、
RayCast②.jpg
ブルーは、受ける側なので、レイヤーをblockに設定して、BoxCollider2Dをアタッチしておきます。

これで準備ができたのでPLAYして反応するか見てみます。
RayCast⑤.jpg
RayCast③.jpg
Rayの反応と長さをテストしてみた所、問題無く設定できております。ヽ(´▽`)/~♪

これでCollider情報が取得できるようなので、取り出してみようと思います。
RayCastTest

void Update()
{
Vector3 origin = transform.position;
Vector3 distance = Vector3.right * 1f;

RaycastHit2D hit = Physics2D.Raycast(origin, Vector2.right, 1f, blockLayer);

// Does the ray intersect any objects excluding the player layer
if (hit)
{
Debug.Log(hit.distance);
Debug.DrawRay(origin, distance, Color.yellow);
}
else
{
Debug.DrawRay(origin,distance, Color.blue);
}
}

接触したColliderとの距離は、Ray原点~Collider接点となります。
RaycastHit2D hitに情報が取得されているので、hit.distanceとすれば距離が求められます。

ほんとに距離が合ってるのか調べる事にします。
二つblockを、Rayの長さ1で重なるように並べてプレイしてみます。
RayCast④.jpg
問題無く距離が取得できています。

ちなみにオブジェクトそのものを取得する事もできます。
少し面倒ですが、hit.collider.gameObjectとして取得するとゲームオブジェクトとして、
座標や名前なんかも取得できます。
この辺りは便利なんで覚えておくといいかと思います。

さて、距離が取得できるようになったので、アンドロイド君に追加…
その前に少し修正部分を書いておきます。

OverZoneの幅と位置をだいたい1.5くらいで設定していました。
距離の計算が重要になるので、分かりやすいように幅を2にして、
障害ブロックと隣接するように変更します。
RayCast⑦.jpg
これで距離の計算がしやすくなります。

最後は、乗り越えの距離を考えます。
RayCast⑧.jpg
OverZoneは、2~0の距離なんでRayから読み取るとして、
障害物を抜け切る距離は、アンドロイド君の足元Colliderに依存するんで、
体より後ろ側の部分を1スケールで設定しているので、障害物の幅とColliderを足して、
2スケールと考えておきます。

準備ができたので、まずRayの設定から作ります。
イメージとしては、
RayCast⑩.jpg
X軸は体の中心点からY軸はスネあたりを原点に障害物に届く長さにします。

このイメージでRayを飛ばします。
ActionTest

private RaycastHit2D obstacleHit; //Rayに当たったオブジェクト情報
private const float rayLength = 4f; //Rayの長さ
private const float rayOffset = -1.7f; //RayのY座標オフセット
private const float obstacleWidth = 2f; //障害物の幅

// Update is called once per frame
void Update()
{
SetLinecast();
}

//Rayの設定
private void SetLinecast()
{
//障害物センサー
if (Physics2D.Linecast(rightD1, rightD2, blockLayer))
{
footCollider.SetActive(false);
obstacle = true;
}

//乗り越えモード時の距離計用Ray
if (overComeMode)
{
Vector3 origin = transform.position + (transform.up * rayOffset);
Vector3 distance = Vector3.right * rayLength;

obstacleHit = Physics2D.Raycast(origin, Vector2.right, rayLength, blockLayer);
Debug.DrawRay(origin, distance, Color.red);
}
}

//接触判定
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.tag == overZone)
overComeMode = true;
}

こんな感じでしょうか。
OverZoneトリガーに接触したらRayを飛ばします。

ついでと言ってはなんですが、落下防止を障害物センサーを利用します。
足元ColliderをOverZoneトリガーに接触したらOFFにしてましたが、
速度が遅いと障害物の手前で落下するので、障害物に接触したらOFFに変更しました。

関数名がSetLinecastになってますが、当初はLineCastのみ使う予定だったんで、
まさかRayCastを使う事になるとは…
変更せずに、このまま使います。
(;^o^) \(ToT )あんたほんとにそれでいいの

Rayが準備できたので、続てい乗り越え処理を修正して行きます。
ついでに前回、終了させられなかったOverComeModeの終了も作ります。
ActionTest

private float overComeTime; //乗り越え処理時間

void Update()
{
SetLinecast();

//スライディングモーション
if (sliding)
{
SlidingAction();
return;
}

//乗り越えモーション
if (overCome)
{
OvercomeObstacles();
return;
}

//転倒モーション
if (obstacle)
{
FallDown();
return;
}
}

//キー入力判定
private void KeySelect()
{
if (Input.GetKeyDown(KeyCode.UpArrow))
{
if (overComeMode)
{
overCome = true;
//乗り越え時間を取得する
overComeTime = CalculateTime(obstacleHit.distance, playerRB.velocity.x);
}
else
jump = true;
}

if (Input.GetKeyDown(KeyCode.DownArrow))
{
footCollider.SetActive(false);
sliding = true;
}
}

//障害物を乗り越える
private void OvercomeObstacles()
{
AnimationChange(actionNum.overCome);
overComeProgress += Time.deltaTime;

if (overComeProgress >= overComeTime)
{
overComeProgress = 0f;
overCome = false;
OverComeModeEnd();
}
}

//障害物との距離から乗り越え時間を計算する
private float CalculateTime(float distance,float speed)
{
float calTime = (distance + obstaclewidth) / speed;
return calTime;
}

//転倒アクション
private void FallDown()
{
fallDownProgress += Time.deltaTime;

if (fallDownProgress >= moveStopTime)
{
if (fallDownProgress >= restart)
SpeedController();
else
{
runSpeed = 0.0f;
dashTime = 0.0f;
}
}

if (fallDownProgress < fallDownTime)
AnimationChange(actionNum.fallDown);
else
{
fallDownProgress = 0f;
OverComeModeEnd();
}
}

//乗り越えモード終了
private void OverComeModeEnd()
{
obstacle = false;
footCollider.SetActive(true);
overComeMode = false;
}

まず、OverZone内でキー入力があった場合、乗り越えとジャンプの分岐していたので、
そこで距離と速度を取得します。

CalculateTime関数に、そのデータを渡して処理時間を計算させます。
計算式は単純に、Ray距離+障害物幅2スケールを速度で割っています。

この値をoverComeTimeに代入して、処理時間の上限としました。

OverZoneに接触するとOverComeModeになるので、
転倒処理時もモードに突入しています。

なので、乗り越え・転倒どちらの処理からも終了できるように
OverComeModeEnd関数を用意して終了させるようにしました。

スクリプトが準備できたので、乗り越え動作のアニメーションを修正します。
RayCast⑪.jpg
前回は、0.5フレームまでモーションを作っていましたが、
再生時間が変化するので、乗り越えの形を作ったキーを一つにして対応します。

しかし、乗り越えと言うよりは飛び越えのような… (^.^; オホホホ
その辺りは気にせず準備もできたのでPLAYしてみます。

若干変な動きをしていますが、障害物を越える処理は一定になりました。
おおむね狙い通りでっす。

速度を2で試しましたが、無事に障害物を越えてくれました。
ヽ(´▽`)/~♪

なんとか作れたということで、今回はここまで…
と言いたいところなんですが…

実は、処理を考えてる時に悪魔の囁きがありました。
障害物に近すぎた場合は、乗り越えできないようにするのも…
Ψ(`∀´)Ψウケケケ

と言う事で少し仕様を変更します。
RayCast⑫.jpg
障害物の手前0.5スケールまで近づいたら乗り越え無効にします。
ActionTest

private const float invalidRange = 1.5f; //乗り越え無効距離

//キー入力判定
private void KeySelect()
{
if (Input.GetKeyDown(KeyCode.UpArrow))
{
if (overComeMode)
{
//障害物との距離が0.5より近い場合は無効にする
if (obstacleHit.distance < invalidRange)
return;

overCome = true;
//乗り越え時間を取得する
overComeTime = CalculateTime(obstacleHit.distance, playerRB.velocity.x);
}
else
jump = true;
}

if (Input.GetKeyDown(KeyCode.DownArrow))
{
footCollider.SetActive(false);
sliding = true;
}
}

Rayの長さを取っているので、アンドロイド君の中心点から
障害物までを1.5に設定すると足元Colliderの先端と障害物の距離が0.5になります。

これで近すぎた場合、乗り越えが出来なくなります。
コケろコケろΨ(`∀´)Ψウケケケ

意地の悪い終わり方ですが、
これで操作に緊張感がでるんじゃないかと思います。

前回、保留していた部分も修正できたので、
なんとかではありますが、作れたかと思います。

本題はRayなんで、Rayを使いたい方の参考にして頂ければ幸いかと思います。

今回は以上となります。
( ^ 0 ^ )/~~~~see you again


この記事へのコメント