衝突予測地点にマーカーを表示
軌道予測線に衝突判定の追加
軌道予測線に沿ってRayを飛ばすことで、衝突判定を行いました。
Scriptと実行結果
マウスを動かすことで視点移動を行い、左クリックでオブジェクトを投射します。マウスの左ボタンを押し続けると、初速が大きくなります。初速と向いている方向に応じて、LineRendererによって軌道予測線の描画を行います。また、衝突判定はRayによって行っています。Rayによって検出された衝突位置にマーカーの表示を行い、軌道予測線は衝突位置より先を描画しないように処理をしています。
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 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class PredictionLine : MonoBehaviour { public GameObject preBullet; public GameObject preLineRenderer; public GameObject preMarker; private GameObject objBullet, objMainCamera, objMarker, objMarkerImage; private Rigidbody rigBullet; private GameObject objLineRenderer; private LineRenderer lineRenderer; private int lineDiv = 20, rayDiv = 20; private float drag, shootPow = 0, cameraDis = -1f; private float gravity = 9.80665f, lineStep, rayStep, rotSpeed = 5f; private Vector2 vel; private bool flgCursorLock = false; // Use this for initialization void Start () { objMainCamera = GameObject.Find("Main Camera"); objMainCamera.transform.position = transform.position; objMainCamera.transform.localRotation = transform.rotation; objLineRenderer = Instantiate(preLineRenderer); objLineRenderer.transform.position = Vector3.zero; objLineRenderer.transform.eulerAngles = Vector3.zero; lineRenderer = objLineRenderer.GetComponent<LineRenderer>(); objLineRenderer.SetActive(false); objMarker = Instantiate(preMarker, Vector3.zero, Quaternion.identity); objMarker.SetActive(false); lineStep = 1f / lineDiv; rayStep = 1f / rayDiv; vel = new Vector2(200f * Mathf.Cos(10f * Mathf.Deg2Rad), 200f * Mathf.Sin(10f * Mathf.Deg2Rad)); } //ある時刻における座標 Vector3 GetLinePosition(float t) { float x, y; if (drag != 0) { vel = new Vector2(shootPow * Mathf.Cos(-transform.eulerAngles.x * Mathf.Deg2Rad), shootPow * Mathf.Sin(-transform.eulerAngles.x * Mathf.Deg2Rad)); float r = 1f - drag * Time.fixedDeltaTime; float n = t / Time.fixedDeltaTime; x = vel.x * Time.fixedDeltaTime * r * (1f - Mathf.Pow(r, n)) / (1f - r); y = vel.y * Time.fixedDeltaTime * r * (1f - Mathf.Pow(r, n)) / (1f - r); y = y - r * gravity * Time.fixedDeltaTime * Time.fixedDeltaTime / (1f - r) * (n - r * (1f - Mathf.Pow(r, n)) / (1f - r)); } else { x = vel.x * t; y = vel.y * t - 0.5f * gravity * (t * t + t * Time.fixedDeltaTime); } return new Vector3(0, y, x); } // Update is called once per frame void Update () { //プレイヤーの回転 transform.eulerAngles = new Vector3(gameObject.transform.eulerAngles.x - Input.GetAxis("Mouse Y") * rotSpeed, gameObject.transform.eulerAngles.y + Input.GetAxis("Mouse X") * rotSpeed, 0); Vector3 CameraPos = objMainCamera.transform.position; Vector3 ownPos = transform.position; if (Input.GetMouseButtonDown(0)) { objLineRenderer.SetActive(true); objBullet = Instantiate(preBullet, ownPos, Quaternion.identity); rigBullet = objBullet.GetComponent<Rigidbody>(); drag = rigBullet.drag; } if (Input.GetMouseButton(0)) { shootPow = Mathf.Min(shootPow + 100f * Time.deltaTime, 200f); float hitPos = 1000f; Vector3 hitPoint = Vector3.zero; //当たり判定 for (int count = 0; count < rayDiv; count++) { Vector3 startPos = Quaternion.AngleAxis(transform.eulerAngles.y, Vector3.up) * GetLinePosition(count * rayStep) + ownPos; Vector3 endPos = Quaternion.AngleAxis(transform.eulerAngles.y, Vector3.up) * GetLinePosition(count * rayStep + rayStep) + ownPos; RaycastHit hitInfo; //Debug.DrawLine(startPos, endPos, Color.red); if (Physics.Linecast(startPos, endPos, out hitInfo)) { hitPoint = hitInfo.point; hitPos = (Quaternion.AngleAxis(360f - transform.eulerAngles.y, Vector3.up) * (hitPoint - ownPos)).z; if (!objMarker.activeSelf) objMarker.SetActive(true); objMarker.transform.position = hitInfo.point; objMarker.transform.LookAt(transform); break; } else { if (objMarker.activeSelf) objMarker.SetActive(false); } } //軌道予測線描画 for (int count = 0; count < lineDiv; count++) { Vector3 pos = GetLinePosition(count * lineStep); if (pos.z < hitPos) { Vector3 linePos = Quaternion.AngleAxis(transform.eulerAngles.y, Vector3.up) * pos; lineRenderer.positionCount = count + 1; lineRenderer.SetPosition(count, linePos + ownPos); } else { lineRenderer.positionCount = count + 1; lineRenderer.SetPosition(count, hitPoint); break; } } } if (Input.GetMouseButtonUp(0)) { rigBullet.useGravity = true; rigBullet.AddForce(transform.forward * shootPow, ForceMode.Impulse); shootPow = 0; objMarker.SetActive(false); objLineRenderer.SetActive(false); } } private void LateUpdate() { //カメラの移動 if (Input.GetAxis("Mouse ScrollWheel") != 0) { cameraDis = Mathf.Clamp(cameraDis + Input.GetAxis("Mouse ScrollWheel") * 500f * Time.deltaTime, -3f, -0.2f); } Vector3 cameraPos = new Vector3(0.3f, 0.3f, cameraDis); cameraPos = Quaternion.AngleAxis(transform.eulerAngles.y, Vector3.up) * Quaternion.AngleAxis(transform.eulerAngles.x, Vector3.right) * cameraPos; objMainCamera.transform.position = cameraPos + transform.position; objMainCamera.transform.rotation = transform.rotation; } } |
このScriptを実行するとこのようになります。
カメラの移動
マウスを動かすことでプレイヤーが回転し、それに伴いカメラが移動するようなScriptを書きました。また、スクロールホイールによりカメラを前後に動かすことができます。カメラの移動処理をLateUpdate内で行っている理由は、Update内でカメラの移動処理を行うと、オブジェエクトが移動してからカメラが移動したり、カメラが移動してからオブジェクトが移動したりと、処理の順番が変わるため画面が、がたついてしまいます。そこで、確実に最後に実行されるLateUpdate内で処理を行っています。これにより、オブジェクトの移動等が実行されてからカメラを動かすことができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
void Update () { //プレイヤーの回転 transform.eulerAngles = new Vector3(gameObject.transform.eulerAngles.x - Input.GetAxis("Mouse Y") * rotSpeed, gameObject.transform.eulerAngles.y + Input.GetAxis("Mouse X") * rotSpeed, 0); } private void LateUpdate() { //カメラの移動 if (Input.GetAxis("Mouse ScrollWheel") != 0) { cameraDis = Mathf.Clamp(cameraDis + Input.GetAxis("Mouse ScrollWheel") * 500f * Time.deltaTime, -3f, -0.2f); } Vector3 cameraPos = new Vector3(0.3f, 0.3f, cameraDis); cameraPos = Quaternion.AngleAxis(transform.eulerAngles.y, Vector3.up) * Quaternion.AngleAxis(transform.eulerAngles.x, Vector3.right) * cameraPos; objMainCamera.transform.position = cameraPos + transform.position; objMainCamera.transform.rotation = transform.rotation; } |
衝突判定の追加
GetLinePositionは原点より投射された物体のある時刻における予測位置を計算する関数です。
startPosにRayの開始位置をendPosにRayの終了位置を計算し、代入しています。GetLinePositionは原点よりz方向へ向けて計算しているため、プレイヤーの位置や向いている方向を考慮した座標を計算する必要があります。
Quaternion.AngleAxis(transform.eulerAngles.y, Vector3.up) * GetLinePosition(count * rayStep)によりプレイヤーの回転に合わせた座標を計算し、さらにプレイヤーの現在位置(ownPos)を加算することでプレイヤーの位置や向いている方向を考慮した予測線の座標を計算しています。
LinecastによりstartPosからendPosへ向けてRayを飛ばし、衝突判定を行っています。衝突が検知された場合、hitPosに衝突座標を代入しています。衝突座標を軌道予測線の描画に使用するため、原点よりz方向へ予測した際の座標に変換しています。また、衝突位置へマーカー(objMarker)の移動と表示をしています。
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 |
Vector3 GetLinePosition(float t) { float x, y; if (drag != 0) { vel = new Vector2(shootPow * Mathf.Cos(-transform.eulerAngles.x * Mathf.Deg2Rad), shootPow * Mathf.Sin(-transform.eulerAngles.x * Mathf.Deg2Rad)); float r = 1f - drag * Time.fixedDeltaTime; float n = t / Time.fixedDeltaTime; x = vel.x * Time.fixedDeltaTime * r * (1f - Mathf.Pow(r, n)) / (1f - r); y = vel.y * Time.fixedDeltaTime * r * (1f - Mathf.Pow(r, n)) / (1f - r); y = y - r * gravity * Time.fixedDeltaTime * Time.fixedDeltaTime / (1f - r) * (n - r * (1f - Mathf.Pow(r, n)) / (1f - r)); } else { x = vel.x * t; y = vel.y * t - 0.5f * gravity * (t * t + t * Time.fixedDeltaTime); } return new Vector3(0, y, x); } void Update () { float hitPos = 1000f; Vector3 hitPoint = Vector3.zero; //当たり判定 for (int count = 0; count < rayDiv; count++) { Vector3 startPos = Quaternion.AngleAxis(transform.eulerAngles.y, Vector3.up) * GetLinePosition(count * rayStep) + ownPos; Vector3 endPos = Quaternion.AngleAxis(transform.eulerAngles.y, Vector3.up) * GetLinePosition(count * rayStep + rayStep) + ownPos; RaycastHit hitInfo; //Debug.DrawLine(startPos, endPos, Color.red); if (Physics.Linecast(startPos, endPos, out hitInfo)) { hitPoint = hitInfo.point; hitPos = (Quaternion.AngleAxis(360f - transform.eulerAngles.y, Vector3.up) * (hitPoint - ownPos)).z; if (!objMarker.activeSelf) objMarker.SetActive(true); objMarker.transform.position = hitInfo.point; objMarker.transform.LookAt(transform); break; } else { if (objMarker.activeSelf) objMarker.SetActive(false); } } } |
軌道予測線の描画
軌道予測線をLineRendererを使用して描画します。Rayが衝突した位置(hitPos)より予測線の座標(pos)が大きくなった時、LineRendererへ衝突した座標を代入した後にfor文を抜けることで、予測線が衝突した座標を超えないようにしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
void Update () { //軌道予測線描画 for (int count = 0; count < lineDiv; count++) { Vector3 pos = GetLinePosition(count * lineStep); if (pos.z < hitPos) { Vector3 linePos = Quaternion.AngleAxis(transform.eulerAngles.y, Vector3.up) * pos; lineRenderer.positionCount = count + 1; lineRenderer.SetPosition(count, linePos + ownPos); } else { lineRenderer.positionCount = count + 1; lineRenderer.SetPosition(count, hitPoint); break; } } } } |
UIによるマーカー表示
ゲームオブジェクトによりマーカーを表示すると他のオブジェクトにめり込むことがあります。そこで、衝突面にあわせてマーカーを回転すると当然のことながら、マーカーが斜めを向いてしまいます。マーカーが斜めを向くことで、衝突面の向いている方向がわかるという利点があると思います。しかしながら、マーカーには正面を向いていて欲しいと考えたので、マーカーがめり込まず、尚且つ常に正面を向いているようにするために、UIで表示するようにScriptを変更しました。
Scriptと実行結果
マーカーをゲームオブジェクトからUIで表示する方法へ変更したScriptはこちらです。マーカーにはUIのImageを使用しています。
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 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class PredictionLine : MonoBehaviour { public GameObject preBullet; public GameObject preLineRenderer; public GameObject preMarkerImage; private GameObject objBullet, objMainCamera, objMarkerImage; private Rigidbody rigBullet; private GameObject objLineRenderer; private LineRenderer lineRenderer; private int lineDiv = 20, rayDiv = 20; private float drag, shootPow = 0, cameraDis = -1f; private float gravity = 9.80665f, lineStep, rayStep, rotSpeed = 5f; private Vector3 hitPoint = Vector3.zero; private Vector2 vel; private bool flgCursorLock = false; // Use this for initialization void Start () { objMainCamera = GameObject.Find("Main Camera"); objMainCamera.transform.position = transform.position; objMainCamera.transform.localRotation = transform.rotation; objLineRenderer = Instantiate(preLineRenderer); objLineRenderer.transform.position = Vector3.zero; objLineRenderer.transform.eulerAngles = Vector3.zero; lineRenderer = objLineRenderer.GetComponent<LineRenderer>(); objLineRenderer.SetActive(false); Transform trfCanvas = GameObject.Find("Canvas").transform; objMarkerImage = Instantiate(preMarkerImage, trfCanvas); objMarkerImage.SetActive(false); lineStep = 1f / lineDiv; rayStep = 1f / rayDiv; vel = new Vector2(200f * Mathf.Cos(10f * Mathf.Deg2Rad), 200f * Mathf.Sin(10f * Mathf.Deg2Rad)); } //ある時刻における座標 Vector3 GetLinePosition(float t) { float x, y; if (drag != 0) { vel = new Vector2(shootPow * Mathf.Cos(-transform.eulerAngles.x * Mathf.Deg2Rad), shootPow * Mathf.Sin(-transform.eulerAngles.x * Mathf.Deg2Rad)); float r = 1f - drag * Time.fixedDeltaTime; float n = t / Time.fixedDeltaTime; x = vel.x * Time.fixedDeltaTime * r * (1f - Mathf.Pow(r, n)) / (1f - r); y = vel.y * Time.fixedDeltaTime * r * (1f - Mathf.Pow(r, n)) / (1f - r); y = y - r * gravity * Time.fixedDeltaTime * Time.fixedDeltaTime / (1f - r) * (n - r * (1f - Mathf.Pow(r, n)) / (1f - r)); } else { x = vel.x * t; y = vel.y * t - 0.5f * gravity * (t * t + t * Time.fixedDeltaTime); } return new Vector3(0, y, x); } // Update is called once per frame void Update () { //プレイヤーの回転 transform.eulerAngles = new Vector3(gameObject.transform.eulerAngles.x - Input.GetAxis("Mouse Y") * rotSpeed, gameObject.transform.eulerAngles.y + Input.GetAxis("Mouse X") * rotSpeed, 0); Vector3 CameraPos = objMainCamera.transform.position; Vector3 ownPos = transform.position; if (Input.GetMouseButtonDown(0)) { objLineRenderer.SetActive(true); objBullet = Instantiate(preBullet, ownPos, Quaternion.identity); rigBullet = objBullet.GetComponent<Rigidbody>(); drag = rigBullet.drag; } if (Input.GetMouseButton(0)) { shootPow = Mathf.Min(shootPow + 100f * Time.deltaTime, 200f); float hitPos = 1000f; //当たり判定 for (int count = 0; count < rayDiv; count++) { Vector3 startPos = Quaternion.AngleAxis(transform.eulerAngles.y, Vector3.up) * GetLinePosition(count * rayStep) + ownPos; Vector3 endPos = Quaternion.AngleAxis(transform.eulerAngles.y, Vector3.up) * GetLinePosition(count * rayStep + rayStep) + ownPos; RaycastHit hitInfo; //Debug.DrawLine(startPos, endPos, Color.red); if (Physics.Linecast(startPos, endPos, out hitInfo)) { hitPoint = hitInfo.point; hitPos = (Quaternion.AngleAxis(360f - transform.eulerAngles.y, Vector3.up) * (hitPoint - ownPos)).z; Vector3 direc = Vector3.Normalize(hitPoint - CameraPos); float dis = Vector3.Distance(CameraPos, hitPoint) - 0.1f; float markerSize = Mathf.Max(4.6f / (dis * Mathf.Tan(Mathf.Deg2Rad * 30f)), 0.3f); objMarkerImage.transform.localScale = new Vector3(markerSize, markerSize, markerSize); if (!objMarkerImage.activeSelf && !Physics.Raycast(CameraPos, direc, dis)) objMarkerImage.SetActive(true); Debug.DrawRay(CameraPos, direc * dis, Color.green); break; } else { if (objMarkerImage.activeSelf) objMarkerImage.SetActive(false); } } //軌道予測線描画 for (int count = 0; count < lineDiv; count++) { Vector3 pos = GetLinePosition(count * lineStep); if (pos.z < hitPos) { Vector3 linePos = Quaternion.AngleAxis(transform.eulerAngles.y, Vector3.up) * pos; lineRenderer.positionCount = count + 1; lineRenderer.SetPosition(count, linePos + ownPos); } else { lineRenderer.positionCount = count + 1; lineRenderer.SetPosition(count, hitPoint); break; } } } if (Input.GetMouseButtonUp(0)) { rigBullet.useGravity = true; rigBullet.AddForce(transform.forward * shootPow, ForceMode.Impulse); shootPow = 0; objMarkerImage.SetActive(false); objLineRenderer.SetActive(false); } } private void LateUpdate() { //カメラの移動 if (Input.GetAxis("Mouse ScrollWheel") != 0) { cameraDis = Mathf.Clamp(cameraDis + Input.GetAxis("Mouse ScrollWheel") * 500f * Time.deltaTime, -3f, -0.2f); } Vector3 cameraPos = new Vector3(0.3f, 0.3f, cameraDis); cameraPos = Quaternion.AngleAxis(transform.eulerAngles.y, Vector3.up) * Quaternion.AngleAxis(transform.eulerAngles.x, Vector3.right) * cameraPos; objMainCamera.transform.position = cameraPos + transform.position; objMainCamera.transform.rotation = transform.rotation; if (objMarkerImage.activeSelf) objMarkerImage.transform.position = RectTransformUtility.WorldToScreenPoint(objMainCamera.GetComponent<Camera>(), hitPoint); } } |
このScriptを実行するとこのようになります。
マーカーをUIへ表示
UIで表示するとカメラと衝突座標の間にオブジェクトがある場合でも、マーカーが表示されてしまいます。そこで、カメラから衝突座標へRayを飛ばし、カメラと衝突座標の間にオブジェクトがある場合、マーカーを非表示にする処理を加えています。また、カメラと衝突座標の距離によってマーカーの大きさが変わるようにしています。マーカーの位置計算はカメラの座標が必要になるため、カメラが移動した後にワールド座標からスクリーン座標へ変換し、移動しています。
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 |
// Update is called once per frame void Update () { ・・・・・・・・・・・・・・・ //当たり判定 for (int count = 0; count < rayDiv; count++) { ・・・・・・・・・・・・・・・・・ if (Physics.Linecast(startPos, endPos, out hitInfo)) { hitPoint = hitInfo.point; hitPos = (Quaternion.AngleAxis(360f - transform.eulerAngles.y, Vector3.up) * (hitPoint - ownPos)).z; Vector3 direc = Vector3.Normalize(hitPoint - CameraPos); float dis = Vector3.Distance(CameraPos, hitPoint) - 0.1f; float markerSize = Mathf.Max(4.6f / (dis * Mathf.Tan(Mathf.Deg2Rad * 30f)), 0.3f); objMarkerImage.transform.localScale = new Vector3(markerSize, markerSize, markerSize); if (!objMarkerImage.activeSelf && !Physics.Raycast(CameraPos, direc, dis)) objMarkerImage.SetActive(true); Debug.DrawRay(CameraPos, direc * dis, Color.green); break; } else { if (objMarkerImage.activeSelf) objMarkerImage.SetActive(false); } } private void LateUpdate() { ・・・・・・・・・・・・ if (objMarkerImage.activeSelf) objMarkerImage.transform.position = RectTransformUtility.WorldToScreenPoint(objMainCamera.GetComponent<Camera>(), hitPoint); } } |
参考サイト
-
前の記事
rigidbodyの軌道予測線を作成してみた(空気抵抗あり) 2018.08.09
-
次の記事
軌道予測線をUIに描画 2018.09.12
コメントを書く