概要
何か物を飛ばすとき、着弾地点にマーカーを表示したい時がありませんか?
本記事は、飛ばす物体の軌跡の予測線と、着弾座標にマーカーを表示する実装例を紹介します。
凸凹した地形など、着弾地点の高さがあらかじめ分からない場合に重宝します。
弾の射出速度だけを調節して、地形に沿った着弾マーカーを表示する実装例
実装
スクリプト
以下の3つのスクリプトを使用します。(長いので折り畳んでいます)
(初速度と開始座標が既に決まっているのであれば、DrawArc.csをちょっと変えるだけで十分です。)
- DrawArc.cs
- ・初速度と開始座標を元に放物線を表示
- ・コライダーと衝突した場所にマーカーを表示
スクリプト
DrawArc.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DrawArc : MonoBehaviour
{
/// <summary>
/// 放物線の描画ON/OFF
/// </summary>
//[SerializeField]
private bool drawArc = true;
/// <summary>
/// 放物線を構成する線分の数
/// </summary>
//[SerializeField]
private int segmentCount = 60;
/// <summary>
/// 放物線を何秒分計算するか
/// </summary>
private float predictionTime = 6.0F;
/// <summary>
/// 放物線のMaterial
/// </summary>
[SerializeField, Tooltip("放物線のマテリアル")]
private Material arcMaterial;
/// <summary>
/// 放物線の幅
/// </summary>
[SerializeField, Tooltip("放物線の幅")]
private float arcWidth = 0.02F;
/// <summary>
/// 放物線を構成するLineRenderer
/// </summary>
private LineRenderer[] lineRenderers;
/// <summary>
/// 弾の初速度や生成座標を持つコンポーネント
/// </summary>
private ShootBullet shootBullet;
/// <summary>
/// 弾の初速度
/// </summary>
private Vector3 initialVelocity;
/// <summary>
/// 放物線の開始座標
/// </summary>
private Vector3 arcStartPosition;
/// <summary>
/// 着弾マーカーオブジェクトのPrefab
/// </summary>
[SerializeField, Tooltip("着弾地点に表示するマーカーのPrefab")]
private GameObject pointerPrefab;
/// <summary>
/// 着弾点のマーカーのオブジェクト
/// </summary>
private GameObject pointerObject;
void Start()
{
// 放物線のLineRendererオブジェクトを用意
CreateLineRendererObjects();
// マーカーのオブジェクトを用意
pointerObject = Instantiate(pointerPrefab, Vector3.zero, Quaternion.identity);
pointerObject.SetActive(false);
// 弾の初速度や生成座標を持つコンポーネント
shootBullet = gameObject.GetComponent<ShootBullet>();
}
void Update()
{
// 初速度と放物線の開始座標を更新
initialVelocity = shootBullet.ShootVelocity;
arcStartPosition = shootBullet.InstantiatePosition;
if (drawArc)
{
// 放物線を表示
float timeStep = predictionTime / segmentCount;
bool draw = false;
float hitTime = float.MaxValue;
for (int i = 0; i < segmentCount; i++)
{
// 線の座標を更新
float startTime = timeStep * i;
float endTime = startTime + timeStep;
SetLineRendererPosition(i, startTime, endTime, !draw);
// 衝突判定
if (!draw)
{
hitTime = GetArcHitTime(startTime, endTime);
if (hitTime != float.MaxValue)
{
draw = true; // 衝突したらその先の放物線は表示しない
}
}
}
// マーカーの表示
if (hitTime != float.MaxValue)
{
Vector3 hitPosition = GetArcPositionAtTime(hitTime);
ShowPointer(hitPosition);
}
}
else
{
// 放物線とマーカーを表示しない
for (int i = 0; i < lineRenderers.Length; i++)
{
lineRenderers[i].enabled = false;
}
pointerObject.SetActive(false);
}
}
/// <summary>
/// 指定時間に対するアーチの放物線上の座標を返す
/// </summary>
/// <param name="time">経過時間</param>
/// <returns>座標</returns>
private Vector3 GetArcPositionAtTime(float time)
{
return (arcStartPosition + ((initialVelocity * time) + (0.5f * time * time) * Physics.gravity));
}
/// <summary>
/// LineRendererの座標を更新
/// </summary>
/// <param name="index"></param>
/// <param name="startTime"></param>
/// <param name="endTime"></param>
private void SetLineRendererPosition(int index, float startTime, float endTime, bool draw = true)
{
lineRenderers[index].SetPosition(0, GetArcPositionAtTime(startTime));
lineRenderers[index].SetPosition(1, GetArcPositionAtTime(endTime));
lineRenderers[index].enabled = draw;
}
/// <summary>
/// LineRendererオブジェクトを作成
/// </summary>
private void CreateLineRendererObjects()
{
// 親オブジェクトを作り、LineRendererを持つ子オブジェクトを作る
GameObject arcObjectsParent = new GameObject("ArcObject");
lineRenderers = new LineRenderer[segmentCount];
for (int i = 0; i < segmentCount; i++)
{
GameObject newObject = new GameObject("LineRenderer_" + i);
newObject.transform.SetParent(arcObjectsParent.transform);
lineRenderers[i] = newObject.AddComponent<LineRenderer>();
// 光源関連を使用しない
lineRenderers[i].receiveShadows = false;
lineRenderers[i].reflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.Off;
lineRenderers[i].lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off;
lineRenderers[i].shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
// 線の幅とマテリアル
lineRenderers[i].material = arcMaterial;
lineRenderers[i].startWidth = arcWidth;
lineRenderers[i].endWidth = arcWidth;
lineRenderers[i].numCapVertices = 5;
lineRenderers[i].enabled = false;
}
}
/// <summary>
/// 指定座標にマーカーを表示
/// </summary>
/// <param name="position"></param>
private void ShowPointer(Vector3 position)
{
pointerObject.transform.position = position;
pointerObject.SetActive(true);
}
/// <summary>
/// 2点間の線分で衝突判定し、衝突する時間を返す
/// </summary>
/// <returns>衝突した時間(してない場合はfloat.MaxValue)</returns>
private float GetArcHitTime(float startTime, float endTime)
{
// Linecastする線分の始終点の座標
Vector3 startPosition = GetArcPositionAtTime(startTime);
Vector3 endPosition = GetArcPositionAtTime(endTime);
// 衝突判定
RaycastHit hitInfo;
if (Physics.Linecast(startPosition, endPosition, out hitInfo))
{
// 衝突したColliderまでの距離から実際の衝突時間を算出
float distance = Vector3.Distance(startPosition, endPosition);
return startTime + (endTime - startTime) * (hitInfo.distance / distance);
}
return float.MaxValue;
}
}
- ShootBullet.cs
- ・弾の初速度と初期座標を保持
- ・キー入力で弾を生成して射出
スクリプト
ShootBullet.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ShootBullet : MonoBehaviour
{
/// <summary>
/// 弾のPrefab
/// </summary>
[SerializeField, Tooltip("弾のPrefab")]
private GameObject bulletPrefab;
/// <summary>
/// 砲身のオブジェクト
/// </summary>
[SerializeField, Tooltip("砲身のオブジェクト")]
private GameObject barrelObject;
/// <summary>
/// 弾を生成する位置情報
/// </summary>
private Vector3 instantiatePosition;
/// <summary>
/// 弾の生成座標(読み取り専用)
/// </summary>
public Vector3 InstantiatePosition
{
get { return instantiatePosition; }
}
/// <summary>
/// 弾の速さ
/// </summary>
[SerializeField, Range(1.0F, 20.0F), Tooltip("弾の射出する速さ")]
private float speed = 1.0F;
/// <summary>
/// 弾の初速度
/// </summary>
private Vector3 shootVelocity;
/// <summary>
/// 弾の初速度(読み取り専用)
/// </summary>
public Vector3 ShootVelocity
{
get { return shootVelocity; }
}
void Update ()
{
// 弾の初速度を更新
shootVelocity = barrelObject.transform.up * speed;
// 弾の生成座標を更新
instantiatePosition = barrelObject.transform.position;
// 発射
if (Input.GetKeyDown(KeyCode.Space))
{
// 弾を生成して飛ばす
GameObject obj = Instantiate(bulletPrefab, instantiatePosition, Quaternion.identity);
Rigidbody rid = obj.GetComponent<Rigidbody>();
rid.AddForce(shootVelocity * rid.mass, ForceMode.Impulse);
// 5秒後に消える
Destroy(obj, 5.0F);
}
}
}
- AngleController.cs
- ・砲台の角度をキー入力で変更する
スクリプト
AngleController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AngleController : MonoBehaviour
{
/// <summary>
/// 感度
/// </summary>
[SerializeField, Range(0.01F, 5.0F), Tooltip("感度")]
private float sensitivity = 1.0F;
/// <summary>
/// 砲身のオブジェクト
/// </summary>
[SerializeField, Tooltip("砲身のオブジェクト")]
private GameObject barrelObject;
void Update ()
{
if (Input.GetKey(KeyCode.LeftArrow))
{
this.transform.Rotate(new Vector3(0F, -1.0F * sensitivity, 0F));
}
if (Input.GetKey(KeyCode.RightArrow))
{
this.transform.Rotate(new Vector3(0F, 1.0F * sensitivity, 0F));
}
if (Input.GetKey(KeyCode.UpArrow))
{
barrelObject.transform.Rotate(new Vector3(-1.0F * sensitivity, 0F, 0F));
}
if (Input.GetKey(KeyCode.DownArrow))
{
barrelObject.transform.Rotate(new Vector3(1.0F * sensitivity, 0F, 0F));
}
}
}
スクリプト以外
-
弾
RigidBodyを付けたSphereなどをPrefab化し、ShootBulletスクリプトのBulletPrefabにアタッチします。
レイヤーは『Ignore Raycast』に設定しておきます。
-
マーカー
着弾点を示すマーカーをPrefab化し、DrawArcスクリプトのPointerPrefabにアタッチします。
今回は下図のような、LineRendererで矢印を描いたものを使用します。
作業環境
- Unity2017.3