今回はナビゲーション機能のナビメッシュリンクやオフメッシュリンクを使って移動する時の移動速度を制御してみたいと思います。
制御なんて言ってますが難しいことは出来ないので出来る事だけをやります。
ナビゲーション機能のオフメッシュリンクについては
の記事で詳しくやりましたが、↑の記事ではアニメーションカーブ等を使ったりオフメッシュリンク移動時に専用のアニメーション再生をしたりしてオフメッシュリンクの移動をさせていました。
ナビゲーションエージェントの設定でAuto Traverse Off Mesh Linkにチェックを入れていればオフメッシュリンクの位置に来るとStartからEnd位置まで自動で移動します。
しかし、その移動速度はエージェントが勝手に判断しオフメッシュリンクのStartからEndまで移動させる為、通常の移動よりも速いスピードで移動してしまいます。
また高い所から地面に下りるオフメッシュリンクを使う場合も平面を移動する時と同じように同一スピードで落下していきます。
通常であればキャラクターに重力がかかり段々と落下スピードが加速するはずです。
ナビメッシュを
↑のような感じで行った場合に通常の移動と今回の機能を取り付けた移動を確認してみます。
地面の間は自分でナビメッシュリンクでリンクし、高い所から落下する部分はNavigationウインドウの自動オフメッシュリンクで生成されたものです。
単純なナビメッシュリンクやオフメッシュリンクを使った移動は、
↑のような感じで平面を繋いだ所(ナビメッシュリンクを使用)で辺にスピードアップした移動になり、高い所から落下する時(オフメッシュリンクの自動生成)のスピードが平面を移動する時と同じように一定速度で落下しています。
これに今回の機能を取り付けると、
↑のような感じで平面移動ではスピードが上がり過ぎず、落下時は重力値を計算して落下するようになります。
サンプルの作成
サンプルを作成していきます。
移動出来る地面を作成
移動できる地面は先ほど紹介した動画と同じ物を作成します。
ヒエラルキー上にPlaneを2つ作成し、地面を少し離します。
Planeのインスペクタの名前の横のStaticにチェックを入れます。
次に片方のPlaneの子要素にCubeを作成し、Scaleを調整して坂を作成し、インスペクタの名前の横のStaticにチェックを入れます。
Cubeで作った坂は
↑のようになります。
2つのPlane、CubuのレイヤーにはFieldレイヤーを作成し、設定しておきます。
UnityメニューのWindowからNavigationを選択しNavigationウインドウを開きます。
まずはNavigationウインドウでAgentタブを選択し、設定をします。
Agent TypesでHumanoidを選択し、↑のような感じに設定しました。
次にBakeタブを選択し、ベイクの設定をします。
Generated Off Mesh LinksのDrop Heightの数値を上げ、高い所から落下するオフメッシュリンクが生成されるようにしておきます。
デフォルトの状態であればNavigationウインドウのObjectタブのGenerate OffMeshLinksのチェックが入っているので自動でオフメッシュリンクが生成されるようになっています。
最後にBakeボタンを押してナビメッシュのベイクをします。
↑のようにナビメッシュベイクがされます。
操作キャラクターの作成
移動する地面の作成とナビメッシュのベイクが終わったので、次はナビメッシュ上を動くキャラクターを作成します。
キャラクターにはIdle状態とWalk状態を作成し、アニメーションパラメータのSpeedの値で相互に状態が遷移するようにしたAnimatorControllerを作成し取り付けます。
またAdd ComponentからNavigation→NavMeshAgentを取り付けます。
Agent TypeをHumanoidにし、Auto Traverse Off Mesh Linkのチェックを外しておきます。
Speed等を変更しエージェントの最大移動速度の変更等を行います。
このキャラクターにはRigidbodyとCapsuleColliderを取り付けていますが、今回の機能では使っていません。
キャラクターにはMoveOffmeshLinkCharaスクリプトを作成し、取り付けます。
平坦なナビメッシュリンクやオフメッシュリンクの移動機能
まずは平たんなナビメッシュリンクやオフメッシュリンクを通常のナビメッシュエリアの移動と同じようなスピードで移動出来るようにしていきます。
概要
平坦なナビメッシュリンクやオフメッシュリンクの移動は自前の物でも落下が絡まないのでそのまま一定速度で移動させるようにするだけです。
NavMeshAgentのAuto Traverse Off Mesh Linkのチェックを外したので、オフメッシュリンクの位置にキャラクターが来るとその場で移動しなくなります。
その為、オフメッシュリンクを使用する場合は自分でオフメッシュリンクのend位置まで移動させてあげる必要があります。
その為にオフメッシュリンクを今使用しているかどうか調べ、使用しているのであればオフメッシュリンクのデータを取得し、それを元にキャラクターの位置を変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public class MoveOffmeshLinkChara : MonoBehaviour { // アニメータコントローラ private Animator animator; // レイを飛ばす距離 private float rayRange; // 移動する位置 private Vector3 targetPosition; // ナビゲーションエージェント private NavMeshAgent agent; // オフメッシュリンクを使用中かどうか private bool isUseOffmeshLink; // オフメッシュリンクのスタート位置 private Vector3 startPos; // オフメッシュリンクのエンド位置 private Vector3 endPos; // オフメッシュリンクを移動するスピード private float offmeshLinkSpeed; // オフメッシュリンクのendPos方向に回転するスピード private float offmeshLinkRotateSpeed; // オフメッシュリンクの方向 private Vector3 endDirection; void Start () { animator = GetComponent<Animator>(); rayRange = 100f; targetPosition = transform.position; agent = GetComponent<NavMeshAgent>(); agent.SetDestination(targetPosition); offmeshLinkSpeed = agent.speed; offmeshLinkRotateSpeed = agent.angularSpeed; } void Update () { // マウスクリックまたはmouseDownModeがOffの時マウスの位置を移動する位置にする if(Input.GetButtonDown("Fire1")) { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if(Physics.Raycast(ray, out hit, rayRange, LayerMask.GetMask ("Field"))) { targetPosition = hit.point; agent.SetDestination(targetPosition); } } // 目的地に近付いたら走るアニメーションをやめる if(agent.remainingDistance < 0.1f){ animator.SetFloat("Speed", 0f); agent.isStopped = true; } else { animator.SetFloat("Speed", agent.desiredVelocity.magnitude); agent.isStopped = false; } // オフメッシュリンク使用時に自分で移動を制御 if (agent.isOnOffMeshLink) { // 初期設定 if (!isUseOffmeshLink) { isUseOffmeshLink = true; startPos = agent.transform.position; endPos = agent.currentOffMeshLinkData.endPos; endDirection = (endPos - startPos).normalized; Debug.Log ("ナビメッシュリンク開始"); } // キャラクターの向きを設定 var rot = Quaternion.RotateTowards (transform.rotation, Quaternion.LookRotation (endDirection), offmeshLinkRotateSpeed * Time.deltaTime); agent.transform.rotation = Quaternion.Euler (agent.transform.eulerAngles.x, rot.eulerAngles.y, agent.transform.eulerAngles.z); // キャラクターの位置を設定 agent.transform.position = Vector3.MoveTowards (agent.transform.position, endPos, offmeshLinkSpeed * Time.deltaTime); // オフメッシュリンク中は固定値を設定 animator.SetFloat ("Speed", 1f); // オフメッシュリンクを終了させるかどうか if (Vector3.Distance (agent.transform.position, endPos) < 0.1f) { isUseOffmeshLink = false; agent.CompleteOffMeshLink (); animator.SetFloat ("Speed", 0f); Debug.Log ("ナビメッシュリンク終了"); } } } } |
ナビゲーション機能を使った移動の詳細は
の記事を参照してください。
Fieldレイヤーを設定した地面をマウスクリックしたらそこにキャラクターが移動します。
オフメッシュリンクを使っている時の移動スピードはoffmeshLinkSpeed、回転スピードはoffmeshLinkRotateSpeedとして作成しており、StartメソッドでNavMeshAgentの最高速度であるagent.speedと最大回転速度であるagent.angularSpeedを設定しています。
こうする事で通常の移動や回転の最大値以上の速さでオフメッシュリンクを移動する事がなくなります。
エージェントがナビメッシュリンクやオフメッシュリンクを使っているかどうかはNavMeshAgentの参照をagentに入れている場合
1 2 3 | agent.isOnOffMeshLink |
で調べられます。
使用している時は最初にオフメッシュリンクのデータを取得します。
現在のオフメッシュリンクのデータは
1 2 3 | agent.currentOffMeshLinkData |
で得られるので、そこからオフメッシュリンクのend位置を取得し、そこからend位置の方向を計算します。
キャラクターの回転や位置はRotateTowardsやMoveTowardsを使って徐々に変更しています。
その際のスピードを先ほどのoffmeshLinkRotateSpeedやoffmeshLinkSpeedを使います。
キャラクターの位置がendPosとかなり近くなったら
1 2 3 | agent.CompleteOffMeshLink (); |
を使ってオフメッシュリンクの利用の終了を伝えます。
平坦なナビメッシュリンク、オフメッシュリンクの移動はこれで一定速度で移動するようになりました。
高い所から落下するオフメッシュリンクの移動
次はナビメッシュをベイクした時に自動で生成した高い所から降りるオフメッシュリンクの移動処理を作成します。
自動で作成された落下するオフメッシュリンクのオフメッシュリンクタイプは
OffMeshLinkType.LinkTypeDropDown
なので、その時だけ重力値を計算したY座標の値を計算する事にします。
先ほどのスクリプトにフィールドを追加します。
1 2 3 4 5 6 7 8 9 | // オフメッシュリンクデータ private OffMeshLinkData offMeshLinkData; // レイを飛ばす場所 [SerializeField] private Transform rayPosition; // 重力値 private float gravityY = 0f; |
offMeshLinkDataはオフメッシュリンクのデータを入れます。
rayPositionは地面と接触したか判断するレイを飛ばす位置。
gravityYは重力値を入れます。
次にオフメッシュリンクを使っている時の処理を変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | // オフメッシュリンク使用時に自分で移動を制御 if (agent.isOnOffMeshLink) { // 初期設定 if (!isUseOffmeshLink) { isUseOffmeshLink = true; offMeshLinkData = agent.currentOffMeshLinkData; startPos = agent.transform.position; endPos = agent.currentOffMeshLinkData.endPos; endDirection = (endPos - startPos).normalized; Debug.Log ("ナビメッシュリンク開始"); } // キャラクターの向きを設定 var rot = Quaternion.RotateTowards (transform.rotation, Quaternion.LookRotation (endDirection), offmeshLinkRotateSpeed * Time.deltaTime); agent.transform.rotation = Quaternion.Euler (agent.transform.eulerAngles.x, rot.eulerAngles.y, agent.transform.eulerAngles.z); // キャラクターの位置を設定 // 落下タイプのオフメッシュリンクタイプ if (offMeshLinkData.linkType == OffMeshLinkType.LinkTypeDropDown) { // XZ軸方向は徐々に移動 var posXZ = Vector2.MoveTowards (new Vector2 (agent.transform.position.x, agent.transform.position.z), new Vector2 (endPos.x, endPos.z), offmeshLinkSpeed * Time.deltaTime); // Y軸方向は重力値で計算 gravityY += Physics.gravity.y * Time.deltaTime; RaycastHit hit; // Fieldレイヤーに接触したらY座標の位置は接触した位置にする if (Physics.Linecast (rayPosition.position, rayPosition.position + Vector3.down, out hit, LayerMask.GetMask ("Field"))) { agent.transform.position = new Vector3 (posXZ.x, hit.point.y, posXZ.y); Debug.Log ("地面に接触"); // 落下中はY軸は重力値で移動先を計算 } else { agent.transform.position = new Vector3 (posXZ.x, agent.transform.position.y + gravityY * Time.deltaTime, posXZ.y); } // それ以外のオフメッシュリンクタイプ } else { agent.transform.position = Vector3.MoveTowards (agent.transform.position, endPos, offmeshLinkSpeed * Time.deltaTime); } // オフメッシュリンク中は固定値を設定 animator.SetFloat ("Speed", 1f); // オフメッシュリンクを終了させるかどうか if (Vector3.Distance (agent.transform.position, endPos) < 0.1f) { isUseOffmeshLink = false; agent.CompleteOffMeshLink (); animator.SetFloat ("Speed", 0f); gravityY = 0f; Debug.Log ("ナビメッシュリンク終了"); } } Debug.DrawLine (rayPosition.position, rayPosition.position + Vector3.down, Color.red); |
オフメッシュリンク使用時の初期設定でoffMeshLinkDataを取得します。
キャラクターの位置を計算している部分で、オフメッシュリンクタイプが落下タイプかどうか判断し、落下タイプであればX軸とZ軸だけMoveTowardsで一定速度で移動するように計算します。
Y軸は重力を計算したいのでgravityYに重力加速度を足していきます。
Physics.Linecastを使ってrayPositionからレイを1m下に飛ばし、Fieldレイヤーがあればその接触位置のY座標をキャラクターのY座標にします(XZは先ほど計算した値)。
地面に接触していなければ現在のY座標の位置に重力値を足した位置にY座標を設定します。
オフメッシュリンクタイプが落下タイプじゃなければ平坦な時と同じ処理を行います。
オフメッシュリンクの使用が終わったらgravityYの値を0に初期化しています。
これで機能が完成しました。
出来上がりは記事の最初に示した動画となっておりますので、再度確認してみてください。
今回の機能の制限
今回の機能を搭載しても落下のスピードの制御に関しては地面のゲームオブジェクトをStaticにし通常のNavMeshをベイクする時に自動でオフメッシュリンクを生成した時だけしか使えません。
その理由は自分で作成したオフメッシュリンクのタイプが全てOffMeshLinkType.Manualになってしまうからです。
今回の機能の落下の判断はオフメッシュリンクのタイプがOffMeshLinkType.LinkTypeDropDownである時だけ処理分けして移動させるので、自身で作ったオフメッシュリンクを使うときは別の判断で落下の移動かどうかを判断する必要がありそうです。
自分で作成したナビメッシュリンクとオフメッシュリンクに対応する
自分で作成したナビメッシュリンクやオフメッシュリンクでも同じように移動速度や落下速度を操作してみます。
オフメッシュリンクを使っている条件の部分以下を次のように修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | // オフメッシュリンク使用時に自分で移動を制御 if (agent.isOnOffMeshLink) { // 初期設定 if (!isUseOffmeshLink) { isUseOffmeshLink = true; startPos = agent.transform.position; endPos = agent.currentOffMeshLinkData.endPos; endDirection = (endPos - startPos).normalized; Debug.Log ("ナビメッシュリンク開始"); } // キャラクターの向きを設定 var rot = Quaternion.RotateTowards (transform.rotation, Quaternion.LookRotation (endDirection), offmeshLinkRotateSpeed * Time.deltaTime); agent.transform.rotation = Quaternion.Euler (agent.transform.eulerAngles.x, rot.eulerAngles.y, agent.transform.eulerAngles.z); RaycastHit hit; // Fieldレイヤーに接触したらY座標の位置は接触した位置にする if (Physics.Linecast (rayPosition.position, rayPosition.position + Vector3.down, out hit, LayerMask.GetMask ("Field"))) { agent.transform.position = Vector3.MoveTowards (agent.transform.position, endPos, offmeshLinkSpeed * Time.deltaTime); } else { // XZ軸方向は徐々に移動 var posXZ = Vector2.MoveTowards (new Vector2 (agent.transform.position.x, agent.transform.position.z), new Vector2 (endPos.x, endPos.z), offmeshLinkSpeed * Time.deltaTime); // Y軸方向は重力値で計算 gravityY += Physics.gravity.y * Time.deltaTime; agent.transform.position = new Vector3 (posXZ.x, agent.transform.position.y + gravityY * Time.deltaTime, posXZ.y); } // オフメッシュリンク中は固定値を設定 animator.SetFloat ("Speed", 1f); // オフメッシュリンクを終了させるかどうか if (Vector3.Distance (agent.transform.position, endPos) < 0.1f) { isUseOffmeshLink = false; agent.CompleteOffMeshLink (); animator.SetFloat ("Speed", 0f); gravityY = 0f; Debug.Log ("ナビメッシュリンク終了"); } } Debug.DrawLine (rayPosition.position, rayPosition.position + Vector3.down, Color.red); |
Fieldレイヤーを設定したゲームオブジェクトにレイが接触している時は通常の移動。
それ以外の時は地面に接地していないとして重力値を計算して移動させるようにします。
これで対応終了です。(^^)/
ちょっと変えただけでしたね・・・・(^_^;)
↑のようになりました。
地面の途中から落下してますが、これはナビメッシュのベイクが地面の端までないのと地面を確認するレイが腰の辺りから飛ばしている為です。
足元辺りから飛ばすと大丈夫そうですが、その場合は地面を検知出来ずに地面をすり抜ける可能性もあるかも?
終わりに
ナビメッシュリンクやオフメッシュリンクの移動速度に違和感がなくなればさらにナビゲーション機能を積極的に使えますね。
ただ、たまに地面をすり抜けて落下してしまう事がありました。
レイの距離が関係してるのかも?(謎)
あ・・・・ジャンプアップの場合を考慮していなかった・・・・ジャンプアップのオフメッシュリンクはスタート位置とエンド位置の高さの違いで調べて記事の最初にリンクしてある処理でやるといいのかも。