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

Posted

技术标签:

【中文标题】在 Unity 中沿光线投射实例化预定义数量的对象【英文标题】:Instantiate predefined number of object along a raycast in Unity 【发布时间】:2020-11-15 12:57:22 【问题描述】:

我有一个光线投射,每帧都基于 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;
    

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

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

注意事项:

direction 是我在编辑器中设置的动力,我需要它来整理我目前正在工作的内容,但在基于积分运行时应该没有必要。

【问题讨论】:

你能告诉我吗,1.这是3D还是2D。 2.我怎么知道StartPointEndPoint的位置? @Ankit 当然,正如问题标签所说,它是 2D 的。光线投射的开始和结束位置由变量firstPathStartfirstPathEnd 确定,我根据每帧的其他不太相关的参数进行更改。还有其他问题我可以回答吗? 这样就好了,我拿两个dummy object来代表Start和End位置 我制作了一个脚本,它为 List 中的所有点提供 Vector3 位置,然后你可以运行一个循环并 Instenciate 所有你想要的对象,这就足够了吗? 【参考方案1】:

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

基本上我使用了一个名为Vector2.Lerp() Liner Interpolation 的方法,这意味着此方法将根据来自0 to 1 的第三个参数t 的值返回point1, and point2 之间的值。

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.
        
    

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

我只是简单地销毁旧对象并实例化新对象。但是请记住,在统一的游戏中频繁实例化和销毁对象会占用玩家机器上的内存。我建议你使用对象池。作为参考,我将添加指向教程的链接。

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);
    

希望对您有所帮助,请参阅以下链接以获取更多参考

OBJECT POOLING in Unity

Vector2.Lerp

【讨论】:

只有一个小缺陷:numbersOfPoints 怎么可能是float? ;) @derHugo 你是对的。它应该是整数而不是浮点数。但只要我们不处理控制变量中的 您的脚本运行良好,但有 2 个例外我们需要解决。 1. 如何将OnDrawGizmos() 替换为游戏对象预制件?我需要玩家能够看到小玩意儿无法切割的点。 2. spaceBetweenPoints 不像点之间的间距,而是小玩意的大小,因此有多少小玩意。如果我将spaceBetweenPoints 设置为 0.5,我会得到一堆背靠背放置的小 Gizmo,如果我将其设置为 2,我会得到 3 个大 Gizmo,它们都背靠背放置。 让我根据您的需要编辑我的脚本,spaceBetweenPoints 在位置方面可以正常工作,我只是输入与球体无线电相同的值。这样如果空间更小,就不会显得笨重。 @derHugo 我尝试将 numbersObPoints 更改为 int 并且我不得不再次将其类型转换为浮动,同时在下一个语句中与 1 相除,所以我决定保留它 @987654338 @【参考方案2】:

希望我的理解是正确的。

首先,计算 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);
    

【讨论】:

我认为您的想法是正确的,而且您的脚本似乎比较接近。但是,无论您做什么,距离似乎都基于spaceBetweenPoints,这意味着当您将spaceBetweenPoints 设置为0.5f 之类的小值时,您会得到一条与光线投射方向相同的短线,但是当它从光线投射开始,而不是移除适当数量的点,它只是将它们靠得很近,并且不会到达光线投射的末尾。而如果你使用更大的数字,比如 10,方向仍然是正确的,并且点分布在 10f,但是 再一次,而不是删除适当数量的点,这些点远远超出了光线投射的长度。但是,如果您可以修复您当前的代码,以便它删除正确数量的点,而不是让它们消失,直到我们用完点。您的脚本可能有效。 再次,希望我理解正确。在这里,我只是在给定一组固定长度(您的游戏对象数组)的情况下显示或隐藏点。这样你就不会有很多 Instantiate & Destroy 因为它对内存的使用非常繁重。如果您想动态创建更多对象来填充线条,它非常非常相似:只需使用 Gameobject 列表。在运行 for 循环之前,如果集合太小,请更新它。看我的代码sn-p,我更新了。 我可以禁用这些点,如果它成为问题,我可以稍后解决。我想您大部分都了解我要做什么,但我确实遇到的一个问题是您更新的代码依赖于变换,而我的 Raycast 的起点和终点使用 Vector2。您的新代码还会在运行时创建大量 ArgumentOutOfRangeException: Index was out of range 错误。 我使用两个私有字段,它们是 Vector2 的,如果需要,转换只是在这里测试脚本。删除它们并设置您的 Raycasts 点,这绝对是一样的。我可能是除以0,看看更新的sn-p。

以上是关于在 Unity 中沿光线投射实例化预定义数量的对象的主要内容,如果未能解决你的问题,请参考以下文章

Unity 2D 发送 X 数量的“部队”,这些数量等于我通过光线投射或其他方式向特定目标投入的数量 [关闭]

光线投射和二维阵列在 Unity 中的魔方无法按预期工作

2-6 光线投射

Unity 中的光线投射和计时器问题

Hololens 2 的光线投射问题

我如何添加光线投射统一命中的对象并将它们添加到列表中