在 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.我怎么知道StartPoint
和EndPoint
的位置?
@Ankit 当然,正如问题标签所说,它是 2D 的。光线投射的开始和结束位置由变量firstPathStart
和firstPathEnd
确定,我根据每帧的其他不太相关的参数进行更改。还有其他问题我可以回答吗?
这样就好了,我拿两个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 中沿光线投射实例化预定义数量的对象的主要内容,如果未能解决你的问题,请参考以下文章