最近公司有个项目需要做个Demo,功能是在3D场景中实时更新物体的GPS位置让物体在3d场景中移动,然后可以回访轨迹。GPS坐标转换为Unity坐标网上有比较多的博客,这里就不说了,主要分享一下物体运动和轨迹绘制以及回放的实现方法。
效果看视频:
Unity轨迹回放功能录屏
逻辑是,通过NavMeshAgent组件和Unity自带的路网烘培让小球动起来,然后鼠标按下的时候记录一下小球所在的位置,用于之后的轨迹的绘制,然后通过LineRenderer在场景中画出轨迹,UI的Slider来控制物体是在哪个点,代码如下:
PlayerMove挂载在NavMeshAgent的物体上,鼠标点击路网让他移动,按下空格键回放轨迹
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.UI;
/// <summary>
/// 物体移动
/// </summary>
public class PlayerMove : MonoBehaviour
{
/// <summary>
/// ID
/// </summary>
public string ID
{
get;
set;
}
/// <summary>
/// 回放时得到下个点的速度
/// </summary>
public float getPointSpeed;
/// <summary>
/// 回放时移动速度
/// </summary>
public float replayMoveSpeed;
/// <summary>
/// 存放所有行走的路径点
/// </summary>
private List<Vector3> roadPoints = new List<Vector3>();
/// <summary>
/// 寻路组件
/// </summary>
private NavMeshAgent meshAgent;
/// <summary>
/// 轨迹回放控制器
/// </summary>
ReplayController replayController;
// Start is called before the first frame update
void Start()
{
meshAgent = GetComponent<NavMeshAgent>();
replayController = GameObject.Find("ReplayController").GetComponent<ReplayController>();
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0)&&!replayController.isPlaying)//按下鼠标左键
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit[] raycastHits = Physics.RaycastAll(ray);
foreach (RaycastHit hit in raycastHits)
{
if (hit.transform.CompareTag("Road"))
{
roadPoints.Add(transform.position);//添加到路径点列表,以便回放
meshAgent.SetDestination(hit.point);
break;
}
}
}
if (Input.GetKeyDown(KeyCode.Space))
{
roadPoints.Add(transform.position);//添加到路径点列表,以便回放
replayController.OnInit(roadPoints, meshAgent);
replayController.ReplayRuningData();
}
}
}
MySlider类,继承于Slider类,主要是记录一下鼠标是否在拖动Slider
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class MySlider : Slider, IBeginDragHandler, IEndDragHandler
{
/// <summary>
/// 是否在拖动滑块
/// </summary>
public bool IsDraging
{
get;
private set;
}
public void OnBeginDrag(PointerEventData eventData)
{
IsDraging = true;
}
public void OnEndDrag(PointerEventData eventData)
{
IsDraging = false;
}
public override void OnPointerDown(PointerEventData eventData)
{
base.OnPointerDown(eventData);
IsDraging = true;
}
public override void OnPointerUp(PointerEventData eventData)
{
base.OnPointerUp(eventData);
IsDraging = false;
}
}
ReplayController类,主要包括存放物体移动的点的列表,和两个协程(一个让物体回放轨迹,一个更新Slider)。之后项目中,将鼠标点击时存入roadPoints 的点换成GPS转换为Unity坐标的点即可。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.UI;
/// <summary>
/// 轨迹回放控制器
/// </summary>
public class ReplayController : MonoBehaviour
{
/// <summary>
/// 存放所有行走的路径点
/// </summary>
private List<Vector3> roadPoints = new List<Vector3>();
/// <summary>
/// 寻路组件
/// </summary>
private NavMeshAgent meshAgent;
/// <summary>
/// 绘制轨迹组件
/// </summary>
private LineRenderer lineRenderer;
/// <summary>
/// 进度条
/// </summary>
private MySlider progressSlider;
/// <summary>
/// UI面板
/// </summary>
private Canvas canvas;
/// <summary>
/// 当前进度
/// </summary>
private float process;
/// <summary>
/// 当前速度
/// </summary>
float speed;
int index;
/// <summary>
/// 当前会放到第几个点
/// </summary>
int Index
{
get
{
return index;
}
set
{
// if (value>0)
{
List<Vector3> posList = new List<Vector3>();
posList = roadPoints.GetRange(0,Mathf.Clamp(value + 1,0, roadPoints.Count-1));
lineRenderer.positionCount = posList.Count;
lineRenderer.SetPositions(posList.ToArray());
}
index = value;
}
}
/// <summary>
/// 是否在回放
/// </summary>
public bool isPlaying;
private void Start()
{
progressSlider = transform.Find("Canvas/ProgressSlider").GetComponent<MySlider>();
canvas = transform.Find("Canvas").GetComponent<Canvas>();
canvas.gameObject.SetActive(false);
lineRenderer = GetComponent<LineRenderer>();
}
/// <summary>
/// s初始化
/// </summary>
public void OnInit(List<Vector3> roadPoints, NavMeshAgent meshAgent)
{
this.roadPoints = roadPoints;
this.meshAgent = meshAgent;
canvas.gameObject.SetActive(true);
Index = 0;
}
/// <summary>
/// 回放
/// </summary>
public void ReplayRuningData()
{
isPlaying = true;
StartCoroutine("IReplayRuningData");
StartCoroutine("IOnDragingProcessSlider");
}
/// <summary>
/// 回放协程
/// </summary>
/// <returns></returns>
IEnumerator IReplayRuningData()
{
meshAgent.enabled = false;
meshAgent.transform.position = roadPoints[Index];
meshAgent.enabled = true;
//lineRenderer.positionCount = roadPoints.Count;
//lineRenderer.SetPositions(roadPoints.ToArray());
while (true)
{
yield return new WaitUntil(() => progressSlider.IsDraging == false);//没有拖动滑块的时候才更新
yield return 0;
if (!meshAgent.hasPath)
{
if (Index >= roadPoints.Count)
{
lineRenderer.positionCount = 0;
progressSlider.value = 0;
canvas.gameObject.SetActive(false);
Index = 0;
isPlaying = false;
break;
}
meshAgent.SetDestination(roadPoints[Index]);
float distance = Vector3.Distance(meshAgent.transform.position, roadPoints[Mathf.Clamp(Index + 1, 0, roadPoints.Count - 1)]);
float time = distance / meshAgent.speed;
process = (float)Index / (roadPoints.Count - 1);
if (time != 0)
{
speed = process / time;
}
else
{
speed = 0;
}
yield return IUpDateProgressSlider();
Index++;
}
}
}
/// <summary>
/// 更新进度条
/// </summary>
/// <param name="process"></param>
/// <returns></returns>
IEnumerator IUpDateProgressSlider()
{
while (progressSlider.value < process&& !progressSlider.IsDraging)
{
yield return new WaitForEndOfFrame();
progressSlider.value += speed * Time.deltaTime;
}
}
/// <summary>
/// 鼠标拖动滑块
/// </summary>
IEnumerator IOnDragingProcessSlider()
{
while (true)
{
yield return new WaitUntil(() => progressSlider.IsDraging == true);//没有拖动滑块的时候才更新
float value = progressSlider.value;
Index = Mathf.Clamp(Mathf.RoundToInt(roadPoints.Count * value)-1, 0, roadPoints.Count - 1);
meshAgent.enabled = false;
meshAgent.transform.position = roadPoints[Index];
meshAgent.enabled = true;
}
}
}
Demo的Unity包下载地址链接:https://pan.baidu.com/s/1q_1wnp7aqm_nuAjnjJYygQ
提取码:45ta
Demo开发用的Unity2017.3.1。
有可以改进的地方欢迎大家提出来