我正在用头撞墙--> 任务:对于我的小项目(带有倾斜相机的等轴测图),我想让玩家为他的能力创建一个自定义效果区域,这意味着,他激活技能时,他可以拖动鼠标并创建一个自定义区域(例如指示技能将覆盖的区域的指示器),固定宽度和最大长度与技能的最大长度相关。起点应该在其最大半径内(但他可以将其延伸很远)。 所以,我的想法是使用网格(带有网格过滤器和网格渲染器的游戏对象)。我用它创建了一个预制件。 为了处理循环,我在这里有这个异步任务,它会等待直到满足条件:
public class FreeAOEState : SpellState
{
public override async Task ExcecuteState(SpellTemplate spellTemplate)
{
var spellRadius = spellTemplate.GetParameter<float>(SpellParameterType.SpellRange);
var maxLength = spellTemplate.GetParameter<float>(SpellParameterType.AreaSize);
var width = spellTemplate.GetTargetParameter<float>(SpellTargetFeatures.Width);
bool isDrawningStarted = false;
MeshFilter meshFilter = new();
// control points for spline
List<Vector3> controlPoints = new();
float totalLength = 0f;
spellTemplate.spellIndicator.InitializeFreeAOE(spellRadius);
while(!isDrawningStarted)
{
if (Input.GetKeyDown(KeyCode.Mouse0))
{
if(spellTemplate.spellIndicator.StartFreeAOEDrawning(spellRadius,
out Vector3 startPoint, controlPoints))
{
spellTemplate.spellIndicator.InstantiateFreeAOE(startPoint, out meshFilter);
isDrawningStarted = true;
}
}
await Task.Yield();
}
var time = 0f;
while (time < 2f * Config.MAX_TIME_READY_SPELL)
{
time += Time.deltaTime;
var result = spellTemplate.spellIndicator.UpdateFreeAEOIndicatorWithSpline(
width,
maxLength,
ref totalLength,
controlPoints,
meshFilter);
if (Input.GetKeyUp(KeyCode.Mouse0) || result.IsDrawingFinished)
{
spellTemplate.SpellEffect();
await Task.Delay(400);
break;
}
await Task.Yield();
}
spellTemplate.spellIndicator.HideCursor();
}
}
介绍:
public struct FreeAOEIndicatorResult
{
public bool IsDrawingFinished;
public float TotalLength;
}
一个简单的结构作为输出拼写模板.spellIndicator.UpdateFreeAEOIndicatorWithSpline()来检查何时满足条件并且可以切断循环。
想要的行为如下:玩家单击鼠标左键(暂时)并实例化网格预制件if所选点位于咒语半径(距玩家的距离)内。这是关闭第一个 while 循环的条件。第二个循环应该简单地添加从鼠标移动记录的点,并将它们添加到 controlPoints 列表中,以从样条线创建网格。当鼠标左键被释放或者网格体的长度大于法术的 maxLength 时,就会脱离它。 使用的方法位于spellTemplate.spellIndicator脚本中,如下:
第一种方法:检查光线投射是否击中以玩家为中心的半径内的点,半径=spellRadius
public bool StartFreeAOEDrawning(float spellRadius, out Vector3 startPoint,
List<Vector3> controlPoints)
{
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
startPoint = Vector3.zero;
var playerPos = Player.Instance.playerTransform.position;
Debug.DrawRay(ray.origin, ray.direction * 100f, Color.green, 2f); // visual debug
if (Physics.Raycast(ray, out RaycastHit hitInfo, Mathf.Infinity))
{
if (Vector3.Distance(hitInfo.point, playerPos) > spellRadius)
{
return false;
}
startPoint = hitInfo.point;
controlPoints.Add(startPoint);
DrawHitPoint(hitInfo.point);
return true;
}
return false;
}
创建光线投射击中点的视觉表示
private void DrawHitPoint(Vector3 hitPoint)
{
// yellow sphere to check impact point
GameObject hitMarker = GameObject.CreatePrimitive(PrimitiveType.Sphere);
hitMarker.transform.position = hitPoint;
hitMarker.transform.localScale = Vector3.one * 0.3f;
hitMarker.GetComponent<Renderer>().material.color = Color.yellow;
Destroy(hitMarker, 2f);
}
现在就实例化网格
public void InstantiateFreeAOE(Vector3 startPoint, out MeshFilter meshFilter)
{
_currentArea = Instantiate(meshPrefabToInstantiate, startPoint, Quaternion.identity);
_currentArea.transform.position = startPoint;
if (_currentArea.TryGetComponent(out meshFilter))
{
meshFilter.mesh = new Mesh();
_currentMeshFilter = meshFilter;
}
else
{
meshFilter = null;
}
}
只需设置 maxradius 画布即可直观地表示最大半径
public void InitializeFreeAOE(float spellRadius)
{
spellCastRadiusCanvas.SetActive(true);
_spellCastRadiusCanvasRectTransform.sizeDelta = new Vector2(spellRadius * 2f, spellRadius * 2f);
_spellCastRadiusImageRect.sizeDelta = new Vector2(spellRadius * 2f, spellRadius * 2f);
}
现在,在主循环中,我们必须捕获鼠标位置,保存点并将它们发送到样条线创建器,并创建一个具有宽度的网格,如果长度> maxLength则返回
public FreeAOEIndicatorResult UpdateFreeAEOIndicatorWithSpline(
float width,
float maxLength,
ref float totalLength,
List<Vector3> controlPoints,
MeshFilter meshFilter)
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity))
{
Vector3 currentPoint = hit.point;
if (controlPoints.Count > 0)
{
float distance = Vector3.Distance(controlPoints[^1], currentPoint);
if (distance > 0.5f)
{
controlPoints.Add(currentPoint);
}
}
//here, we're generating the curve
var curvePoints = GenerateCatmullRomCurve(controlPoints, 20);
// here we draw the curve
DrawMeshFromCurve(curvePoints, width, meshFilter);
// retrieve the length
float length = 0f;
for (int i = 1; i < curvePoints.Count; i++)
{
length += Vector3.Distance(curvePoints[i - 1], curvePoints[i]);
}
totalLength = length;
// our condition to cut the loop
if (totalLength >= maxLength)
{
return new FreeAOEIndicatorResult { IsDrawingFinished = true, TotalLength = totalLength };
}
return new FreeAOEIndicatorResult { IsDrawingFinished = false, TotalLength = totalLength };
}
return new FreeAOEIndicatorResult { IsDrawingFinished = true, TotalLength = totalLength };
}
现在按照创建曲线并在其周围创建网格的方法
public List<Vector3> GenerateCatmullRomCurve(List<Vector3> controlPoints, int resolution)
{
List<Vector3> curvePoints = new ();
for (int i = 0; i < controlPoints.Count - 3; i++)
{
Vector3 p0 = controlPoints[i];
Vector3 p1 = controlPoints[i + 1];
Vector3 p2 = controlPoints[i + 2];
Vector3 p3 = controlPoints[i + 3];
for (int j = 0; j < resolution; j++)
{
float t = j / (float)resolution;
curvePoints.Add(CatmullRom(p0, p1, p2, p3, t));
}
}
return curvePoints;
}
private Vector3 CatmullRom(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
{
float t2 = t * t;
float t3 = t2 * t;
return 0.5f * ((2f * p1) +
(-p0 + p2) * t +
(2f * p0 - 5f * p1 + 4f * p2 - p3) * t2 +
(-p0 + 3f * p1 - 3f * p2 + p3) * t3);
}
void DrawMeshFromCurve(List<Vector3> curvePoints, float width, MeshFilter meshFilter)
{
if (curvePoints.Count < 2)
return;
int maxPoints = 1000;
if (curvePoints.Count > maxPoints)
{
curvePoints = curvePoints.GetRange(0, maxPoints);
}
int numSegments = curvePoints.Count - 1;
int[] triangles = new int[numSegments * 6];
Vector3[] vertices = new Vector3[curvePoints.Count * 2];
for (int i = 0; i < curvePoints.Count; i++)
{
Vector3 direction = (i < curvePoints.Count - 1) ?
(curvePoints[i + 1] - curvePoints[i]).normalized :
(curvePoints[i] - curvePoints[i - 1]).normalized;
Vector3 offset = Vector3.Cross(direction, Vector3.up) * (width / 2);
vertices[i * 2] = curvePoints[i] - offset;
vertices[i * 2 + 1] = curvePoints[i] + offset;
if (i < curvePoints.Count - 1)
{
int baseIndex = i * 2;
triangles[i * 6] = baseIndex;
triangles[i * 6 + 1] = baseIndex + 1;
triangles[i * 6 + 2] = baseIndex + 2;
triangles[i * 6 + 3] = baseIndex + 1;
triangles[i * 6 + 4] = baseIndex + 3;
triangles[i * 6 + 5] = baseIndex + 2;
}
}
Mesh mesh = new()
{
vertices = vertices,
triangles = triangles
};
mesh.RecalculateNormals();
meshFilter.mesh = mesh;
}
现在,我相当确定曲线和网格的创建方式是正确的,但当我运行游戏时,光线投射正确地击中平面,如下图所示:
如您所见,黄色球体的位置正确。但是当我拖动鼠标时,网格会在远离该点的地方创建,如下图所示:
现在,我也尝试了 chatgpt,但几个小时后我不明白为什么它会这样。我怀疑它与前 4 个点有关:事实上,第一条曲线是在记录 4 个点后创建的。为什么它从那里开始而不是从起点开始的原因超出了我的范围。你能帮我一下吗?如果您需要更多信息,我可以集成...我想我把核心放在这里。 非常感谢!
如前所述,我相信不匹配来自于全局世界空间中的光线投射命中,而
Mesh.vertices
位于本地空间中,并且 meshFilter
本身有位置偏移。
您必须首先将曲线点转换为局部空间。
尝试
vertices[i * 2] = meshFilter.transform.InverseTransformPoint(curvePoints[i] - offset);
vertices[i * 2 + 1] = meshFilter.transform.InverseTransformPoint(curvePoints[i] + offset);