如何使游戏对象沿着贝塞尔曲线在 Unity 中按顺序跟随路径
How to make game objects follow a path in order in Unity along a bezier curve
我有一个沿着贝塞尔曲线移动的对象,但我有多个对象需要按顺序沿着这条路径移动,但它们都遵循相同的路径time.The对象是[=中的蛇类敌人21=]射击游戏。
到目前为止,我已经尝试将所有对象设为子对象,但在这样做时,当遵循贝塞尔曲线时,它们与父对象保持在一条直线上。
我还将所有对象分开并将贝塞尔脚本附加到这些对象上,以便它们都遵循相同的路线并且这有效,但只有它们同时遵循相同的路径。
public class BezierFollow : MonoBehaviour{
[SerializeField]
private Transform[] routes;
private int routeToGo;
private float tParam;
private Vector2 enemyPosition;
[SerializeField]
public float speedModifier = 0.5f;
private bool coroutineAloud;
// Start is called before the first frame update
void Start()
{
routeToGo = 0;
tParam = 0f;
//speedModifier = 0.5f;
coroutineAloud = true;
}
// Update is called once per frame
void Update()
{
if (coroutineAloud)
{
StartCoroutine(GoByTheRouteRoutine(routeToGo));
}
}
private IEnumerator GoByTheRouteRoutine(int routeNumber)
{
coroutineAloud = false;
Vector2 p0 = routes[routeNumber].GetChild(0).position;
Vector2 p1 = routes[routeNumber].GetChild(1).position;
Vector2 p2 = routes[routeNumber].GetChild(2).position;
Vector2 p3 = routes[routeNumber].GetChild(3).position;
while(tParam < 1)
{
tParam += Time.deltaTime * speedModifier;
enemyPosition = Mathf.Pow(1 - tParam, 3) * p0 +
3 * Mathf.Pow(1 - tParam, 2) * tParam * p1 +
3 * (1 - tParam) * Mathf.Pow(tParam, 2) * p2 +
Mathf.Pow(tParam, 3) * p3;
transform.position = enemyPosition;
yield return new WaitForEndOfFrame();
}
tParam = 0f;
routeToGo += 1;
if(routeToGo > routes.Length - 1)
routeToGo = 0;
coroutineAloud = true;
}}
这是路由脚本,我认为您不需要,但会包含它
public class Route : MonoBehaviour{
[SerializeField]
private Transform[] controlPoints;
private Vector2 gizmosPos;
private void OnDrawGizmos()
{
for(float t = 0; t <= 1; t += 0.05f)
{
gizmosPos = Mathf.Pow(1 - t, 3) * controlPoints[0].position +
3 * Mathf.Pow(1 - t, 2) * t * controlPoints[1].position +
3 * (1 - t) * Mathf.Pow(t, 2) * controlPoints[2].position +
Mathf.Pow(t, 3) * controlPoints[3].position;
Gizmos.DrawSphere(gizmosPos, 0.25f);
}
Gizmos.DrawLine(new Vector2(controlPoints[0].position.x, controlPoints[0].position.y),
new Vector2(controlPoints[1].position.x, controlPoints[1].position.y));
Gizmos.DrawLine(new Vector2(controlPoints[2].position.x, controlPoints[2].position.y),
new Vector2(controlPoints[3].position.x, controlPoints[3].position.y));
}}
我认为我需要做的是让每个对象都不是子对象,并且都附加了脚本以跟随路线,但在它沿着路径前进之前也有延迟时间但不确定如何进行这个。我在想这可能需要在一个单独的脚本中完成,因为在贝塞尔曲线脚本中它被设置为一旦到达终点,对象就会在路线的起点再次开始
这种方法怎么样:
- 将 BezierCurve 附加到所有需要该行为的游戏对象。不要保持任何 Parent-Child 关系。
- 防止 BezierCurve 自动变为 follow/start。保留一个布尔值以备不时之需。
- 创建一个新脚本 BezierCurveBatch,附加到主(父)游戏对象并让 List
包含子对象的引用。在同一个脚本中,保持 float,假设 delayStartCurve 来管理两个 BezierCurve 开始之间的时间。
- 在 BezierCurveBatch 中,在每个 delayStartCurve.
之后从 List children 开始 BezierCurve
我提供了演示脚本代码。未经测试,但应该可以。
public class BezierCurve
{
//Starts following bezier curve.
public void StartFollow()
{
//some code here.
}
}
public class BezierCurveBatch : MonoBehaviour
{
[SerializeField]
List<BezierCurve> m_lstChildren;
[SerializeField]
float m_delayStartCurve = 10;
float m_timeLeftToStartNextChild = 0;
bool m_isRunBatchCurve = false;
/// <summary>
/// Start batch follow after each interval.
/// </summary>
public void StartBatch()
{
m_isRunBatchCurve = true;
}
private void Update()
{
if (!m_isRunBatchCurve)
return;
m_timeLeftToStartNextChild -= Time.deltaTime;
if (m_timeLeftToStartNextChild <= 0.0f)
{
if (m_lstChildren.Count > 0) //if we have children left.
{
BezierCurve l_bCurveToStart = m_lstChildren[0]; //Getting top object.
m_lstChildren.RemoveAt(0); //removing top object.
l_bCurveToStart.StartFollow(); //Start follow bezier curve
m_timeLeftToStartNextChild = m_delayStartCurve; //resetting time.
}
if (m_lstChildren.Count == 0) //After processing last object, check if need to continue for next object.
m_isRunBatchCurve = false;
}
}
}
我知道您要实现的目标,我相信您可以在不更改当前代码的情况下使用额外的脚本来实现。
我在这里制作了一个名为 EnemyBehavior 的新脚本。
public class EnemyBehavior : MonoBehaviour{
public Path pathToFollow;
//PATH INFO
public int currentWayPointID = 0;
//speeds
public float speed = 2;
public float reachDistance = 0.4f;
public float rotationSpeed = 5f;
float distance; //DISTANCE TO NEXT PATH POINT
public bool useBezier = false;
//STATE MACHINES
public enum EnemyStates
{
ON_PATH,
IDLE
}
public EnemyStates enemyState;
public int enemyID;
void Update()
{
switch (enemyState)
{
case EnemyStates.ON_PATH:
MoveOnThePath(pathToFollow);
break;
case EnemyStates.IDLE:
break;
}
}
void MoveToFormation()
{
//transform.position = Vector3.MoveTowards(transform.position, formation.gridList[enemyID], speed * Time.deltaTime);
//if(Vector3.Distance(transform.position, formation.gridList[enemyID])<= 0.001f)
{
//transform.SetParent(formation.gameObject.transform);
transform.eulerAngles = Vector3.zero;
enemyState = EnemyStates.IDLE;
}
}
void MoveOnThePath(Path path)
{
if (useBezier)
{
//MOVING THE ENEMY
distance = Vector3.Distance(path.bezierObjList[currentWayPointID], transform.position);
transform.position = Vector3.MoveTowards(transform.position, path.bezierObjList[currentWayPointID], speed * Time.deltaTime);
//ROTATION OF YOUR ENEMY
var direction = path.bezierObjList[currentWayPointID] - transform.position;
if (direction != Vector3.zero)
{
direction.z = 0;
direction = direction.normalized;
var rotation = Quaternion.LookRotation(direction);
transform.rotation = Quaternion.Slerp(transform.rotation, rotation, rotationSpeed * Time.deltaTime);
}
}
else
{
distance = Vector3.Distance(path.pathObjList[currentWayPointID].position, transform.position);
transform.position = Vector3.MoveTowards(transform.position, path.pathObjList[currentWayPointID].position, speed * Time.deltaTime);
//ROTATION OF ENEMY
var direction = path.pathObjList[currentWayPointID].position - transform.position;
if (direction != Vector3.zero)
{
direction.y = 0;
direction = direction.normalized;
var rotation = Quaternion.LookRotation(direction);
transform.rotation = Quaternion.Slerp(transform.rotation, rotation, rotationSpeed * Time.deltaTime);
}
}
}}
将此脚本附加到您打算使用该路径的所有游戏对象,并且不要忘记在检查器中为这些对象分配路径。
如果是 2D 游戏,您可能需要调整方向以满足您的需要,但这应该适合您。
让我知道你的进展如何,随时直接与我联系。
我有一个沿着贝塞尔曲线移动的对象,但我有多个对象需要按顺序沿着这条路径移动,但它们都遵循相同的路径time.The对象是[=中的蛇类敌人21=]射击游戏。
到目前为止,我已经尝试将所有对象设为子对象,但在这样做时,当遵循贝塞尔曲线时,它们与父对象保持在一条直线上。 我还将所有对象分开并将贝塞尔脚本附加到这些对象上,以便它们都遵循相同的路线并且这有效,但只有它们同时遵循相同的路径。
public class BezierFollow : MonoBehaviour{
[SerializeField]
private Transform[] routes;
private int routeToGo;
private float tParam;
private Vector2 enemyPosition;
[SerializeField]
public float speedModifier = 0.5f;
private bool coroutineAloud;
// Start is called before the first frame update
void Start()
{
routeToGo = 0;
tParam = 0f;
//speedModifier = 0.5f;
coroutineAloud = true;
}
// Update is called once per frame
void Update()
{
if (coroutineAloud)
{
StartCoroutine(GoByTheRouteRoutine(routeToGo));
}
}
private IEnumerator GoByTheRouteRoutine(int routeNumber)
{
coroutineAloud = false;
Vector2 p0 = routes[routeNumber].GetChild(0).position;
Vector2 p1 = routes[routeNumber].GetChild(1).position;
Vector2 p2 = routes[routeNumber].GetChild(2).position;
Vector2 p3 = routes[routeNumber].GetChild(3).position;
while(tParam < 1)
{
tParam += Time.deltaTime * speedModifier;
enemyPosition = Mathf.Pow(1 - tParam, 3) * p0 +
3 * Mathf.Pow(1 - tParam, 2) * tParam * p1 +
3 * (1 - tParam) * Mathf.Pow(tParam, 2) * p2 +
Mathf.Pow(tParam, 3) * p3;
transform.position = enemyPosition;
yield return new WaitForEndOfFrame();
}
tParam = 0f;
routeToGo += 1;
if(routeToGo > routes.Length - 1)
routeToGo = 0;
coroutineAloud = true;
}}
这是路由脚本,我认为您不需要,但会包含它
public class Route : MonoBehaviour{
[SerializeField]
private Transform[] controlPoints;
private Vector2 gizmosPos;
private void OnDrawGizmos()
{
for(float t = 0; t <= 1; t += 0.05f)
{
gizmosPos = Mathf.Pow(1 - t, 3) * controlPoints[0].position +
3 * Mathf.Pow(1 - t, 2) * t * controlPoints[1].position +
3 * (1 - t) * Mathf.Pow(t, 2) * controlPoints[2].position +
Mathf.Pow(t, 3) * controlPoints[3].position;
Gizmos.DrawSphere(gizmosPos, 0.25f);
}
Gizmos.DrawLine(new Vector2(controlPoints[0].position.x, controlPoints[0].position.y),
new Vector2(controlPoints[1].position.x, controlPoints[1].position.y));
Gizmos.DrawLine(new Vector2(controlPoints[2].position.x, controlPoints[2].position.y),
new Vector2(controlPoints[3].position.x, controlPoints[3].position.y));
}}
我认为我需要做的是让每个对象都不是子对象,并且都附加了脚本以跟随路线,但在它沿着路径前进之前也有延迟时间但不确定如何进行这个。我在想这可能需要在一个单独的脚本中完成,因为在贝塞尔曲线脚本中它被设置为一旦到达终点,对象就会在路线的起点再次开始
这种方法怎么样:
- 将 BezierCurve 附加到所有需要该行为的游戏对象。不要保持任何 Parent-Child 关系。
- 防止 BezierCurve 自动变为 follow/start。保留一个布尔值以备不时之需。
- 创建一个新脚本 BezierCurveBatch,附加到主(父)游戏对象并让 List
包含子对象的引用。在同一个脚本中,保持 float,假设 delayStartCurve 来管理两个 BezierCurve 开始之间的时间。 - 在 BezierCurveBatch 中,在每个 delayStartCurve. 之后从 List
我提供了演示脚本代码。未经测试,但应该可以。
public class BezierCurve
{
//Starts following bezier curve.
public void StartFollow()
{
//some code here.
}
}
public class BezierCurveBatch : MonoBehaviour
{
[SerializeField]
List<BezierCurve> m_lstChildren;
[SerializeField]
float m_delayStartCurve = 10;
float m_timeLeftToStartNextChild = 0;
bool m_isRunBatchCurve = false;
/// <summary>
/// Start batch follow after each interval.
/// </summary>
public void StartBatch()
{
m_isRunBatchCurve = true;
}
private void Update()
{
if (!m_isRunBatchCurve)
return;
m_timeLeftToStartNextChild -= Time.deltaTime;
if (m_timeLeftToStartNextChild <= 0.0f)
{
if (m_lstChildren.Count > 0) //if we have children left.
{
BezierCurve l_bCurveToStart = m_lstChildren[0]; //Getting top object.
m_lstChildren.RemoveAt(0); //removing top object.
l_bCurveToStart.StartFollow(); //Start follow bezier curve
m_timeLeftToStartNextChild = m_delayStartCurve; //resetting time.
}
if (m_lstChildren.Count == 0) //After processing last object, check if need to continue for next object.
m_isRunBatchCurve = false;
}
}
}
我知道您要实现的目标,我相信您可以在不更改当前代码的情况下使用额外的脚本来实现。
我在这里制作了一个名为 EnemyBehavior 的新脚本。
public class EnemyBehavior : MonoBehaviour{
public Path pathToFollow;
//PATH INFO
public int currentWayPointID = 0;
//speeds
public float speed = 2;
public float reachDistance = 0.4f;
public float rotationSpeed = 5f;
float distance; //DISTANCE TO NEXT PATH POINT
public bool useBezier = false;
//STATE MACHINES
public enum EnemyStates
{
ON_PATH,
IDLE
}
public EnemyStates enemyState;
public int enemyID;
void Update()
{
switch (enemyState)
{
case EnemyStates.ON_PATH:
MoveOnThePath(pathToFollow);
break;
case EnemyStates.IDLE:
break;
}
}
void MoveToFormation()
{
//transform.position = Vector3.MoveTowards(transform.position, formation.gridList[enemyID], speed * Time.deltaTime);
//if(Vector3.Distance(transform.position, formation.gridList[enemyID])<= 0.001f)
{
//transform.SetParent(formation.gameObject.transform);
transform.eulerAngles = Vector3.zero;
enemyState = EnemyStates.IDLE;
}
}
void MoveOnThePath(Path path)
{
if (useBezier)
{
//MOVING THE ENEMY
distance = Vector3.Distance(path.bezierObjList[currentWayPointID], transform.position);
transform.position = Vector3.MoveTowards(transform.position, path.bezierObjList[currentWayPointID], speed * Time.deltaTime);
//ROTATION OF YOUR ENEMY
var direction = path.bezierObjList[currentWayPointID] - transform.position;
if (direction != Vector3.zero)
{
direction.z = 0;
direction = direction.normalized;
var rotation = Quaternion.LookRotation(direction);
transform.rotation = Quaternion.Slerp(transform.rotation, rotation, rotationSpeed * Time.deltaTime);
}
}
else
{
distance = Vector3.Distance(path.pathObjList[currentWayPointID].position, transform.position);
transform.position = Vector3.MoveTowards(transform.position, path.pathObjList[currentWayPointID].position, speed * Time.deltaTime);
//ROTATION OF ENEMY
var direction = path.pathObjList[currentWayPointID].position - transform.position;
if (direction != Vector3.zero)
{
direction.y = 0;
direction = direction.normalized;
var rotation = Quaternion.LookRotation(direction);
transform.rotation = Quaternion.Slerp(transform.rotation, rotation, rotationSpeed * Time.deltaTime);
}
}
}}
将此脚本附加到您打算使用该路径的所有游戏对象,并且不要忘记在检查器中为这些对象分配路径。
如果是 2D 游戏,您可能需要调整方向以满足您的需要,但这应该适合您。 让我知道你的进展如何,随时直接与我联系。