这里的代码的功能有
- 给出矩形四个转角点
- 生成若干个物体(这里是广告牌)围绕这四个转角点匀速转圈
- 转角呈圆角形状 过度自然
这段代码可以简单拓展到绕多边形形状转动,应该不需要改什么代码,后面有时间会拓展玩玩看
下面的代码放好之后挂载脚本然后在inspector面板弄好参数以及脚本中的下面这段话改成能找到你要生成的物体的代码,运行就可以看效果了
adPref = GameObject.Find("cycladgo");
上代码:
Util工具类
/// <summary>
/// 已知两个点 求他们连线的中点
/// </summary>
/// <param name="vctrOne"></param>
/// <param name="vctrTwo"></param>
/// <returns></returns>
public static Vector3 GetTwoVctrMiddle(Vector3 vctrOne, Vector3 vctrTwo)
{
Vector3 mid = new Vector3((vctrOne.x + vctrTwo.x) / 2,
(vctrOne.y + vctrTwo.y) / 2,
(vctrOne.z + vctrTwo.z) / 2);
return mid;
}
public delegate IEnumerator IenumDelegate(params object[] bc);
public static void ReStartCoroutine(MonoBehaviour o, ref Coroutine cor, IenumDelegate eFun, params object[] bc)
{
//Debug.LogError(" Util ReStartCoroutine ");
StopCoroutine(o, ref cor);
//Debug.Log("cor == null " + (cor == null));
cor = o.StartCoroutine(eFun(bc));
}
public static void DisableMRAndMC(Transform trn)
{
MeshRenderer mr = trn.GetComponent<MeshRenderer>();
if (mr)
{
mr.enabled = false;
}
MeshCollider mc = trn.GetComponent<MeshCollider>();
if (mc)
{
mc.enabled = false;
}
}
单个广告牌对象类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
public class SingleADInfo : MonoBehaviour
{
/// <summary>
/// 当前运行所在轨道信息
/// </summary>
SinglePathMsg curTracePathMsg;
/// <summary>
/// 将要运行的轨道信息
/// </summary>
SinglePathMsg nextTracePathMsg;
SphereCollider selfInsideCol;
public SphereCollider SelfInsideCol { get => selfInsideCol; }
bool isInTurnCrnrState = false;
Collider lastCrossCol;
Rigidbody selfRigidBody;
public Rigidbody SelfRigidBody { get => selfRigidBody; }
float turnCrnrTime;
float straightPathTraceTime;
Coroutine turnCrnrLookRotAdjustCor;
Coroutine directLineLookRotAdjustCor;
Vector3 turningLastFramePos;
// Start is called before the first frame update
void Start()
{
selfInsideCol = gameObject.AddCompIfNone<SphereCollider>();
selfInsideCol.radius = HallADLoopMgr.Instance.adSelfColRadius;
selfInsideCol.isTrigger = true;
selfRigidBody = gameObject.AddCompIfNone<Rigidbody>();
selfRigidBody.useGravity = false;
selfRigidBody.isKinematic = true;
}
public void FirstLaunch()
{
gameObject.SetActive(true);
float time = curTracePathMsg.GetStartTraceTime();
gameObject.transform.position = curTracePathMsg.PortPointStartTrn.position;
transform.LookAt(curTracePathMsg.DirectLineTracePathPosEnd);
StraightPathMove(time);
}
public void SetInfo(SinglePathMsg singlePathMsg, float pathTraceTime, float crnrTurnTime)
{
curTracePathMsg = singlePathMsg;
nextTracePathMsg = curTracePathMsg.NextPathInfo;
turnCrnrTime = crnrTurnTime;
straightPathTraceTime = pathTraceTime;
}
bool IsTouchDirectPathEndPt(Collider other)
{
// Debug.LogError(" SingleADInfo " +
//"IsTouchDirectPathEndPt curTracePathMsg.CrnrContainsThisCol(other) " +
//curTracePathMsg.CrnrContainsThisCol(other));
// Debug.LogError(" isInTurnCrnrState " + isInTurnCrnrState);
if (curTracePathMsg.ThisColDirectPathEndPos(other) &&
!isInTurnCrnrState)
{
return true;
}
return false;
}
bool IsTouchCrnrPathEndPt(Collider other)
{
if (curTracePathMsg.NextPathInfo.ThisColDirectPathStartPos(other) &&
isInTurnCrnrState)
{
return true;
}
return false;
}
void OnTriggerEnter(Collider other)
{
//Debug.LogError("SingleADInfo OnTriggerEnter ");
//转弯过程中如果路径太鬼畜 可能会多次重复碰到同一个碰撞体
//直线行走过程中直线行走的结束点
if (IsTouchDirectPathEndPt(other))
{
isInTurnCrnrState = true;
CornerTurn();
}
else if (IsTouchCrnrPathEndPt(other))//转弯过程中 碰到了转弯的结束点
{
isInTurnCrnrState = false;
curTracePathMsg = nextTracePathMsg;
nextTracePathMsg = curTracePathMsg.NextPathInfo;
StraightPathMove();
}
lastCrossCol = other;
}
private void StraightPathMove(float time = 0)
{
transform.DOPause();
if (directLineLookRotAdjustCor == null)
{
Util.ReStartCoroutine(this, ref directLineLookRotAdjustCor, DirectLineLookAdjust);
}
transform.DOMove(curTracePathMsg.DirectLineTracePathPosEnd,
time <= 0 ? straightPathTraceTime : time);
}
IEnumerator TurnCrnrLookAdjust(object[] o)
{
Vector3 spdDir;
while (isInTurnCrnrState)
{
turningLastFramePos = transform.position;
yield return null;
spdDir = transform.position - turningLastFramePos;
//直接 transform.forward =spdDir会不自然
transform.forward = Vector3.Lerp(transform.forward, spdDir, 20f * Time.deltaTime);
}
turnCrnrLookRotAdjustCor = null;
}
IEnumerator DirectLineLookAdjust(object[] o)
{
while (!isInTurnCrnrState)
{
transform.forward = Vector3.Lerp(transform.forward, curTracePathMsg.PortStartToEndNormalizedDir, 1f * Time.deltaTime);
yield return null;
}
directLineLookRotAdjustCor = null;
}
private void CornerTurn()
{
Vector3 turnEndPos = nextTracePathMsg.DirectLineTracePathPosStart;
Vector3 turnStartPos = curTracePathMsg.DirectLineTracePathPosEnd;
Vector3 middlePos1 = Util.GetTwoVctrMiddle(curTracePathMsg.DirectLineTracePathPosEnd,
curTracePathMsg.PortPointEndTrn.position);
middlePos1 = Util.GetTwoVctrMiddle(middlePos1, curTracePathMsg.PortPointEndTrn.position);
Vector3 middlePos2 = Util.GetTwoVctrMiddle(nextTracePathMsg.DirectLineTracePathPosStart,
nextTracePathMsg.PortPointStartTrn.position);
middlePos2 = Util.GetTwoVctrMiddle(middlePos2, curTracePathMsg.PortPointEndTrn.position);
Vector3 arcMiddlePt = Util.GetTwoVctrMiddle(middlePos1, middlePos2);
Vector3[] path = new Vector3[3];
//这里起始位置改成transform.position而不是turnStartPos是因为这样做
//1 转弯衔接自然
//2 如果path[0]就是turnStartPos
//则doPath 的transform.position 到 turnStartPos这段位移十分鬼畜
path[0] = transform.position;
path[1] = arcMiddlePt;
path[2] = turnEndPos;
//DebugUtil.InstantiatePrimitive(turnStartPos, Color.blue, PrimitiveType.Cylinder);
//DebugUtil.InstantiatePrimitive(arcMiddlePt, Color.green, PrimitiveType.Cube);
//DebugUtil.InstantiatePrimitive(turnEndPos, Color.yellow, PrimitiveType.Sphere);
transform.DOPause();
Util.ReStartCoroutine(this, ref turnCrnrLookRotAdjustCor, TurnCrnrLookAdjust);
transform.DOPath(path, turnCrnrTime, PathType.CatmullRom, PathMode.Full3D);
}
void OnTriggerExit(Collider other)
{
if (other.name.Contains(""))
{
}
}
}
单个路径对象类
public class SinglePathMsg
{
SinglePathMsg nextPathInfo;
public SinglePathMsg NextPathInfo { get => nextPathInfo; }
Transform portPointStart;
public Transform PortPointStartTrn { get => portPointStart; }
Transform portPointEnd;
public Transform PortPointEndTrn { get => portPointEnd; }
Vector3 directLineTracePathPosStart;
public Vector3 DirectLineTracePathPosStart { get => directLineTracePathPosStart; }
Vector3 directLineTracePathPosEnd;
public Vector3 DirectLineTracePathPosEnd { get => directLineTracePathPosEnd; }
GameObject directLineTracePathPosStartColGo;
public GameObject DirectLineTracePathPosStartColGo { get => directLineTracePathPosStartColGo; }
GameObject directLineTracePathPosEndColGo;
public GameObject DirectLineTracePathPosEndColGo { get => directLineTracePathPosEndColGo; }
Vector3 portSrartToEndNormalizedDir;
public Vector3 PortStartToEndNormalizedDir { get => portSrartToEndNormalizedDir; }
float crnrToCrnrDis;
public float CrnrToCrnrDis { get => crnrToCrnrDis; }
float portToPortDis;
public float PortToPortDis { get => portToPortDis; }
float cornerPosColRadius;
public float CornerPosColRadius { get => cornerPosColRadius; }
public SinglePathMsg(Transform ptStart, Transform ptEnd,
float directLinePercent, float cornerPosColRadius)
{
portPointStart = ptStart;
portPointEnd = ptEnd;
portSrartToEndNormalizedDir = (ptEnd.position - ptStart.position).normalized;
portToPortDis = (ptEnd.position - ptStart.position).magnitude;
this.cornerPosColRadius = cornerPosColRadius;
SetTurnCorner(directLinePercent);
//Debug.LogError(" HallADLoopMgr SinglePathMsg cornerPosColRadius " +
// cornerPosColRadius);
//Debug.LogError(" HallADLoopMgr SinglePathMsg this.cornerPosColRadius " +
// this.cornerPosColRadius);
}
public bool ThisColDirectPathEndPos(Collider collider)
{
if (collider == null)
{
return false;
}
if (collider.gameObject == directLineTracePathPosEndColGo)
{
return true;
}
return false;
}
public bool ThisColDirectPathStartPos(Collider collider)
{
if (collider == null)
{
return false;
}
if (collider.gameObject == directLineTracePathPosStartColGo)
{
return true;
}
return false;
}
public bool CrnrContainsThisCol(Collider collider)
{
if (collider == null)
{
return false;
}
if (collider.gameObject == directLineTracePathPosStartColGo ||
collider.gameObject == directLineTracePathPosEndColGo)
{
return true;
}
return false;
}
public void SetNextPathInfo(SinglePathMsg nextPathInfo)
{
this.nextPathInfo = nextPathInfo;
}
public Vector3 GetClosestPortPos(Vector3 pos)
{
float dis1 = Vector3.Distance(pos, directLineTracePathPosStart);
float dis2 = Vector3.Distance(pos, directLineTracePathPosEnd);
if (dis1 < dis2)
{
return directLineTracePathPosStart;
}
else
{
return directLineTracePathPosEnd;
}
}
/// <summary>
/// 如果广告牌从这个路径的某个端点开始,
/// 则为了保持速度一致,
/// 这个路径一开始的直线行走时间不和转角到转角时间一致
/// </summary>
/// <returns></returns>
public float GetStartTraceTime()
{
float time = ((portToPortDis - crnrToCrnrDis) / 2f + crnrToCrnrDis) / crnrToCrnrDis * HallADLoopMgr.Instance.singlePathADMoveTime;
return time;
}
public void SetTurnCorner(float directLinePercent)
{
directLineTracePathPosStart = portPointStart.position +
portSrartToEndNormalizedDir * (1 - directLinePercent) / 2f * portToPortDis;
directLineTracePathPosStartColGo = new GameObject("crnrStartCollider");
directLineTracePathPosStartColGo.transform.position = directLineTracePathPosStart;
SphereCollider col = directLineTracePathPosStartColGo.gameObject.AddComponent<SphereCollider>();
col.radius = cornerPosColRadius;
//Debug.LogError(" HallADLoopMgr SetTurnCorner cornerPosColRadius " + cornerPosColRadius);
//Debug.LogError(" HallADLoopMgr SetTurnCorner col.radius " + col.radius);
col.isTrigger = true;
directLineTracePathPosEnd = portPointStart.position +
portSrartToEndNormalizedDir * (directLinePercent + (1 - directLinePercent) / 2f) * portToPortDis;
directLineTracePathPosEndColGo = new GameObject("crnrEndCollider");
directLineTracePathPosEndColGo.transform.position = directLineTracePathPosEnd;
col = directLineTracePathPosEndColGo.gameObject.AddComponent<SphereCollider>();
col.radius = cornerPosColRadius;
col.isTrigger = true;
crnrToCrnrDis = (directLineTracePathPosStart - directLineTracePathPosEnd).magnitude;
}
}
总管理类
public class HallADLoopMgr : MonoBehaviour
{
static HallADLoopMgr instance;
public static HallADLoopMgr Instance {
get => instance;
}
public List<Transform> loopPortTrns = new List<Transform>();
public Transform startSpawnPort;
List<SinglePathMsg> pathMsgs = new List<SinglePathMsg>();
SinglePathMsg startPathMsg;
/// <summary>
/// 一条直线路径上的两个端点之间直线行走部分的百分比
/// </summary>
public float directLinePercent = 0.8f;
/// <summary>
/// 广告牌行走路径顶视图看下去是否顺时针
/// </summary>
public bool clockWise = true;
/// <summary>
/// 广告牌直线行走的时间
/// </summary>
public float singlePathADMoveTime = 10f;
/// <summary>
/// 广告牌转角行走的时间
/// </summary>
float aDTurnCrnrTime;
/// <summary>
/// 转角部分的碰撞体的半径 不能太小 速度大而半径小容易导致检测不到
/// </summary>
public float cornerPosColRadius = 1f;
public float adSelfColRadius = 1f;
/// <summary>
/// 每隔几秒生成一个广告牌
/// </summary>
float spawnTime;
/// <summary>
/// 总的广告牌生成数量
/// </summary>
public float spawnCount = 10;
Coroutine spawnCor;
GameObject adPref;
// Start is called before the first frame update
void Start()
{
instance = this;
adPref = GameObject.Find("cycladgo");
if (adPref == null)
{
Debug.LogError(" HallADLoopMgr Start not found ad go ");
return;
}
adPref.gameObject.SetActive(false);
for (int i = 0; i < loopPortTrns.Count; i++)
{
Util.DisableMRAndMC(loopPortTrns[i]);
}
//为了在转弯路径与直线路径的速度一致,
//转弯路径与直线路径的经过时间的比值与转弯路径长度和直线路径长度比值一致
aDTurnCrnrTime = (1 - directLinePercent) * singlePathADMoveTime;
GeneratePath();
spawnTime = ((aDTurnCrnrTime + singlePathADMoveTime) * pathMsgs.Count) / spawnCount;
//Debug.LogError(" HallADLoopMgr Start spawnTime " + spawnTime);
Util.ReStartCoroutine(this, ref spawnCor, SpawnAD);
}
IEnumerator SpawnAD(object[] o)
{
float curHaveSpawnCount = 0;
while (curHaveSpawnCount < spawnCount)
{
Spawn(startPathMsg);
yield return new WaitForSeconds(spawnTime);
curHaveSpawnCount++;
}
}
void Spawn(SinglePathMsg matchMsg)
{
GameObject adGo = Instantiate(adPref);
SingleADInfo singleADInfo = adGo.AddCompIfNone<SingleADInfo>();
singleADInfo.SetInfo(matchMsg, singlePathADMoveTime, aDTurnCrnrTime);
singleADInfo.FirstLaunch();
}
private void GeneratePath()
{
SinglePathMsg lastGenerateInfo = null;
SinglePathMsg curGenerateInfo = null;
SinglePathMsg firstGenerateInfo = null;
for (int i = 0; i < loopPortTrns.Count; i++)
{
MeshRenderer mr = loopPortTrns[i].GetComponent<MeshRenderer>();
if (mr != null)
{
mr.enabled = false;
}
//取余是让最后一个路径的两个端点能够正确设置
curGenerateInfo = new SinglePathMsg(loopPortTrns[i],
loopPortTrns[(i + 1) % loopPortTrns.Count],
directLinePercent, cornerPosColRadius);
//Debug.LogError(" HallADLoopMgr GeneratePath cornerPosColRadius " +
// cornerPosColRadius);
if (i == 0)
{
firstGenerateInfo = curGenerateInfo;
}
if (lastGenerateInfo != null)
{
lastGenerateInfo.SetNextPathInfo(curGenerateInfo);
}
pathMsgs.Add(curGenerateInfo);
lastGenerateInfo = curGenerateInfo;
}
//最后一个生成的路径信息的下一个路径是最开始生成的路径
lastGenerateInfo.SetNextPathInfo(firstGenerateInfo);
foreach (var pathMsg in pathMsgs)
{
if (pathMsg.PortPointStartTrn == startSpawnPort)
{
startPathMsg = pathMsg;
break;
}
}
}
}