You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity Waypoints脚本运行时问题及需求:Speed参数无效、路径点动态更新、循环/往返移动实现

Solutions to Your Waypoint Movement Issues

Hey Daniel, let's work through each of your three waypoint movement problems with clear fixes and explanations:


1. Fixing Runtime Speed Changes Not Affecting Moving Objects

Your initial thought about the coroutine not reading the latest speed value is a common pitfall, but actually, your current code does read the live speed variable each frame (since Vector3.MoveTowards uses speed * Time.deltaTime directly from the class member). The likely confusion comes from thinking go = false blocks all updates—but speed changes should still affect active coroutines.

To make this explicit and avoid any accidental caching, we’ve kept the direct reference to the speed member in the movement logic, ensuring Inspector changes take effect immediately.


2. Dynamically Updating Positions Without Performance Overhead

Running GetLinePointsInWorldSpace every frame is unnecessary. Instead, we can check for changes in the LineRenderer or pointsToMove list only when needed, then update positions only when a change is detected:

  • Cache previous values (line point count, points list count, and a simple position "hash")
  • Compare current values to cached ones each frame (minimal overhead)
  • Update positions only when discrepancies are found

3. Implementing Loop Movement (Two Modes)

To add loop behavior, we’ve introduced an enum to select between Loop (restart from start) and Ping-Pong (reverse direction). The coroutine now runs indefinitely (unless set to stop) and adjusts its path logic when reaching the end of the route.


Full Modified Code

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class Waypoints : MonoBehaviour {
    public GameObject objectPrefab;
    public LineRenderer lineRenderer;
    public List<Transform> pointsToMove;
    public float speed;
    public bool go = false;
    // Add loop mode selection in Inspector
    public LoopMode loopMode = LoopMode.Loop;
    
    private List<GameObject> objectsToMove;
    private List<Vector3> positions;
    // Cache values to detect path changes efficiently
    private int lastLinePointCount;
    private int lastPointsToMoveCount;
    private Vector3 lastPointsToMoveHash;

    // Enum to define loop behavior options
    public enum LoopMode {
        None, // Original stop-at-end behavior
        Loop, // Restart from start after reaching end
        PingPong // Reverse direction after hitting start/end
    }

    void Start() {
        objectsToMove = new List<GameObject>();
        // Initialize cached values for change detection
        UpdateCachedValues();
        positions = GetCombinedPositions();
    }

    private void Update() {
        // Original object spawn trigger logic
        if (go && lineRenderer.positionCount > 0 && CurvedLineRenderer.linesSet) {
            for (int i = 0; i < 3; i++) {
                objectsToMove.Add(Instantiate(objectPrefab, transform.parent));
                StartCoroutine(Move(i));
            }
            go = false;
        }

        // Update positions only if path has changed
        if (HasPathChanged()) {
            positions = GetCombinedPositions();
            UpdateCachedValues();
        }
    }

    // Check if LineRenderer or pointsToMove list has changed
    private bool HasPathChanged() {
        // Check LineRenderer point count
        if (lineRenderer.positionCount != lastLinePointCount) {
            return true;
        }

        // Check pointsToMove list size
        if (pointsToMove.Count != lastPointsToMoveCount) {
            return true;
        }

        // Simple position hash check to detect changes in point positions
        Vector3 currentHash = Vector3.zero;
        foreach (Transform t in pointsToMove) {
            currentHash += t.position;
        }
        if (currentHash != lastPointsToMoveHash) {
            return true;
        }

        return false;
    }

    // Update cached values after a path change
    private void UpdateCachedValues() {
        lastLinePointCount = lineRenderer.positionCount;
        lastPointsToMoveCount = pointsToMove.Count;
        lastPointsToMoveHash = Vector3.zero;
        foreach (Transform t in pointsToMove) {
            lastPointsToMoveHash += t.position;
        }
    }

    // Combine LineRenderer points and pointsToMove into a single path list
    private List<Vector3> GetCombinedPositions() {
        var linePoints = new Vector3[lineRenderer.positionCount];
        lineRenderer.GetPositions(linePoints);
        var combined = linePoints.ToList();
        foreach(Transform objToMove in pointsToMove) {
            combined.Add(objToMove.position);
        }
        return combined;
    }

    private IEnumerator Move(int i) {
        // Initial delay before starting movement
        yield return new WaitForSeconds(i * 10);
        
        int pointIndex = 0;
        float threshold = 0.1f;
        // Direction multiplier: 1 = forward, -1 = backward (for PingPong)
        int direction = 1;

        while (true) {
            // Safety check: skip if path is empty
            if (positions.Count == 0) {
                yield return null;
                continue;
            }

            // Check if object is close enough to the target point
            if (Vector3.Distance(objectsToMove[i].transform.position, positions[pointIndex]) < threshold) {
                switch (loopMode) {
                    case LoopMode.None:
                        // Stop movement when reaching the end
                        yield break;
                    case LoopMode.Loop:
                        // Reset to the first point
                        pointIndex = 0;
                        break;
                    case LoopMode.PingPong:
                        // Reverse direction when hitting start or end
                        if (pointIndex == positions.Count - 1 || pointIndex == 0) {
                            direction *= -1;
                        }
                        // Move to next point in current direction
                        pointIndex += direction;
                        break;
                }
            }

            // Move towards target using the latest speed value
            objectsToMove[i].transform.position = Vector3.MoveTowards(
                objectsToMove[i].transform.position, 
                positions[pointIndex], 
                speed * Time.deltaTime
            );
            yield return null;
        }
    }
}

Key Changes Explained:

  1. Live Speed Updates: The coroutine uses the speed member variable directly every frame, so Inspector changes take effect immediately on moving objects.
  2. Efficient Path Updates: The HasPathChanged() method uses lightweight checks to avoid unnecessary recalculation of the path, keeping performance overhead low.
  3. Flexible Loop Modes: The LoopMode enum lets you switch between stopping, looping from the start, or ping-ponging back and forth—all handled within the persistent coroutine.

内容的提问来源于stack exchange,提问作者Daniel Lip

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 13:57:45