曲がりなりにも越える処理を作る事ができました。
しかし、処理時間と転倒時に乗り越えモードの終了ができない、
二つの問題点が残っています(T-T) グスッ
これを解決するのに四苦八苦しているのですが、
いろいろ考えて思いついたので、記事にまとめてみます。
まず、動作の安定感を出す為に問題点を出してみました。
乗り越えモーションは、
障害物(黒ブロック)の左にある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オブジェクトの正方形スプライトを二枚追加して、グリーンとブルーに色を設定しておきます。
グリーンは、Rayを飛ばす側にするので、RayCastTestスクリプトをアタッチしておきます。
アタッチしたRayCastTestのBlockLayerをクリックして、blockを選択しておきます。
続いてブルー、
ブルーは、受ける側なので、レイヤーをblockに設定して、BoxCollider2Dをアタッチしておきます。
これで準備ができたのでPLAYして反応するか見てみます。
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で重なるように並べてプレイしてみます。
問題無く距離が取得できています。
ちなみにオブジェクトそのものを取得する事もできます。
少し面倒ですが、hit.collider.gameObjectとして取得するとゲームオブジェクトとして、
座標や名前なんかも取得できます。
この辺りは便利なんで覚えておくといいかと思います。
さて、距離が取得できるようになったので、アンドロイド君に追加…
その前に少し修正部分を書いておきます。
OverZoneの幅と位置をだいたい1.5くらいで設定していました。
距離の計算が重要になるので、分かりやすいように幅を2にして、
障害ブロックと隣接するように変更します。
これで距離の計算がしやすくなります。
最後は、乗り越えの距離を考えます。
OverZoneは、2~0の距離なんでRayから読み取るとして、
障害物を抜け切る距離は、アンドロイド君の足元Colliderに依存するんで、
体より後ろ側の部分を1スケールで設定しているので、障害物の幅とColliderを足して、
2スケールと考えておきます。
準備ができたので、まずRayの設定から作ります。
イメージとしては、
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関数を用意して終了させるようにしました。
スクリプトが準備できたので、乗り越え動作のアニメーションを修正します。
前回は、0.5フレームまでモーションを作っていましたが、
再生時間が変化するので、乗り越えの形を作ったキーを一つにして対応します。
しかし、乗り越えと言うよりは飛び越えのような… (^.^; オホホホ
その辺りは気にせず準備もできたのでPLAYしてみます。
再生できない場合、ダウンロードは🎥こちら
若干変な動きをしていますが、障害物を越える処理は一定になりました。
おおむね狙い通りでっす。
速度を2で試しましたが、無事に障害物を越えてくれました。
ヽ(´▽`)/~♪
なんとか作れたということで、今回はここまで…
と言いたいところなんですが…
実は、処理を考えてる時に悪魔の囁きがありました。
障害物に近すぎた場合は、乗り越えできないようにするのも…
Ψ(`∀´)Ψウケケケ
と言う事で少し仕様を変更します。
障害物の手前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;
}
}
この記事へのコメント