在 Unity 中沿光线投射实例化预定义数量的对象

Instantiate predefined number of object along a raycast in Unity

我有一个基于 2 个点每帧渲染的光线投射,这 2 个点每帧改变位置。

我需要的是一个系统,不需要一个方向,也不需要一些对象,而是取2个点,然后根据需要实例化或销毁尽可能多的对象,从一侧获取实例化的对象到另一个减 spaceBetweenPoints。如果你愿意,你可以把它想象成一个 Angry Birds Style slingshot HUD,除了没有重力,并且基于光线投射。

我的脚本:

    public int numOfPoints; // The number of points that are generated (This would need to chnage based one the distance in the end)
    public float spaceBetweenPoints; // The spacing between the generated points
    private GameObject[] predictionPoints; // The prefab to be gernerated
    private Vector2 firstPathStart; // The starting point for the raycast (Changes each frame)
    private Vector2 firstPathEnd; // The ending point for the raycast (Changes each frame)
    
    void start()
    {
         predictionPoints = new GameObject[numOfPoints];
         for (int i = 0; i < numOfPoints; i++)
         {
              predictionPoints[i] = Instantiate(predictionPointPrefab, firePoint.position, 
              Quaternion.identity);
         }
    }

    void Update
    {
         Debug.DrawLine(firstPathStart, firstPathEnd, UnityEngine.Color.black);
         DrawPredictionDisplay();
    }

    void DrawPredictionDisplay()
    {
         for (int i = 0; i < numOfPoints; i++)
        {
            predictionPoints[i].transform.position = predictionPointPosition(i * spaceBetweenPoints);
        }
    }

    Vector2 predictionPointPosition(float time)
    {
        Vector2 position = (Vector2)firstPathStart + direction.normalized * 10f * time;
        return position;
    }

当前系统只是简单地获取起始位置和方向,然后根据时间在该方向上移动预设数量的对象。这种做法也会导致问题,因为它是无止境的,而不是一直到光线投射结束:(请原谅我的绘画技巧)

Blue line = raycast
Black dots = instantiated prefab
Orange dot = raycast orange
Green dot = end of raycast

备注:

希望我没听错。

首先,计算 A 到 B 的直线,即 B 减去 A。 要获得所需对象的数量,请将线大小除以对象的间距。您还可以添加预测点对象的直径以避免重叠。 然后为了得到每个对象的位置,写(几乎)相同的for循环。

这是我想出来的,没有测试,如果有帮助请告诉我!

public class CustomLineRenderer : MonoBehaviour
{
    public float SpaceBetweenPoints;
    public GameObject PredictionPointPrefab;
    
    // remove transforms if you need to
    public Transform start;
    public Transform end;

    private List<GameObject> _predictionPoints;
    
    // these are your raycast start & end point, make them public or whatever
    private Vector2 _firstPathStart;
    private Vector2 _firstPathEnd;

    private void Awake()
    {
        _firstPathStart = start.position;
        _firstPathEnd = end.position;
        _predictionPoints = new List<GameObject>();
    }

    private void Update()
    {
        _firstPathStart = start.position;
        _firstPathEnd = end.position;
        
        // using any small value to clamp everything and avoid division by zero
        if (SpaceBetweenPoints < 0.001f) SpaceBetweenPoints = 0.001f;

        var line = _firstPathEnd - _firstPathStart;
        var objectsNumber = Mathf.FloorToInt(line.magnitude / SpaceBetweenPoints);
        var direction = line.normalized;

        // Update the collection so that the line isn't too short
        for (var i = _predictionPoints.Count; i <= objectsNumber; ++i)
            _predictionPoints.Add(Instantiate(PredictionPointPrefab));

        for (var i = 0; i < objectsNumber; ++i)
        {
            _predictionPoints[i].SetActive(true);
            _predictionPoints[i].transform.position = _firstPathStart + direction * (SpaceBetweenPoints * i);
        }

        // You could destroy objects, but it's better to add them to the pool since you'll use them quite often
        for (var i = objectsNumber; i < _predictionPoints.Count; ++i)
            _predictionPoints[i].SetActive(false);
    }
}

如果你问我,我会说如果你知道一点数学技巧就很容易了。我并不是说我非常擅长数学,但是一旦你掌握了它,下次就很容易实现。在这里,如果我试图解释一切,我将无法解释清楚。看下面的代码,我把整段代码都注释掉了,方便大家理解。

基本上我使用了一种名为 Vector2.Lerp() 线性插值的方法,这意味着该方法将 return 根据第三个参数 t 的值 point1, and point2 之间的值来自 0 to 1.

public class TestScript : MonoBehaviour
{
    public Transform StartPoint;
    public Transform EndPoint;
    public float spaceBetweenPoints;

    [Space]
    public Vector2 startPosition;
    public Vector2 endPosition;

    [Space]
    public List<Vector3> points;

    private float distance;

    private void Update()
    {
        startPosition = StartPoint.position; //Setting Starting point and Ending point.
        endPosition = EndPoint.position;
    
        //Finding the distance between point
        distance = Vector2.Distance(startPosition, endPosition);

        //Generating the points
        GeneratePointsObjects();

        Debug.DrawLine(StartPoint.position, EndPoint.position, Color.black);
    }

    private void OnDrawGizmos()
    {
        //Drawing the Dummy Gizmo Sphere to see the points
        Gizmos.color = Color.black;
        foreach (Vector3 p in points)
        {
            Gizmos.DrawSphere(p, spaceBetweenPoints / 2);
        }
    }

    private void OnValidate()
    {
        //Validating that the space between two objects is not 0 because that would be Raise an exception "Devide by Zero"
        if (spaceBetweenPoints <= 0)
        {
            spaceBetweenPoints = 0.01f;
        }
    }

    private void GeneratePointsObjects()
    {
        //Vlearing the list so that we don't iterate over old points
        points.Clear();

        float numbersOfPoints = distance / spaceBetweenPoints; //Finding numbers of objects to be spawned by dividing "distance" by "spaceBetweenPoints"
        float increnment = 1 / numbersOfPoints; //finding the increment for Lerp this will always be between 0 to 1, because Lerp() takes value between 0 to 1;

        for (int i = 1; i < numbersOfPoints; i ++)
        {
            Vector3 v = Vector2.Lerp(startPosition, endPosition, increnment * i); //Find next position using Vector2.Lerp()
            points.Add(v);//Add the newlly found position in List so that we can spwan the Object at that position.
        }
    }
}

更新: 添加,如何在位置上设置预制件

我只是简单地销毁了旧对象并实例化了新对象。但请记住,在您的游戏中频繁实例化和销毁对象会耗尽玩家机器上的内存。 Os 我建议您使用对象池。作为参考,我将在教程中添加 link。

private void Update()
{
    startPosition = StartPoint.position; //Setting Starting point and Ending point.
    endPosition = EndPoint.position;

    //Finding the distance between point
    distance = Vector2.Distance(startPosition, endPosition);

    //Generating the points
    GeneratePointsObjects();

    //Update: Generating points/dots on all to location;
    InstenciatePrefabsOnPositions();

    Debug.DrawLine(StartPoint.position, EndPoint.position, Color.black);
}

private void InstenciatePrefabsOnPositions()
{
    //Remove all old prefabs/objects/points
    for (int i = 0; i < pointParent.childCount; i++)
    {
        Destroy(pointParent.GetChild(i).gameObject);
    }

    //Instantiate new Object on the positions calculated in GeneratePointsObjects()
    foreach (Vector3 v in points)
    {
        Transform t = Instantiate(pointPrefab);
        t.SetParent(pointParent);
        t.localScale = Vector3.one;
        t.position = v;
        t.gameObject.SetActive(true);
    }
}

希望这对您有所帮助,请参阅下面的 link 以获得更多参考

OBJECT POOLING in Unity

Vector2.Lerp