Unity:根据玩家输入成功创建网格作为自定义指示器

问题描述 投票:0回答:1

我正在用头撞墙--> 任务:对于我的小项目(带有倾斜相机的等轴测图),我想让玩家为他的能力创建一个自定义效果区域,这意味着,他激活技能时,他可以拖动鼠标并创建一个自定义区域(例如指示技能将覆盖的区域的指示器),固定宽度和最大长度与技能的最大长度相关。起点应该在其最大半径内(但他可以将其延伸很远)。 所以,我的想法是使用网格(带有网格过滤器和网格渲染器的游戏对象)。我用它创建了一个预制件。 为了处理循环,我在这里有这个异步任务,它会等待直到满足条件:

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

现在,我相当确定曲线和网格的创建方式是正确的,但当我运行游戏时,光线投射正确地击中平面,如下图所示: RaycastHitPoint

如您所见,黄色球体的位置正确。但是当我拖动鼠标时,网格会在远离该点的地方创建,如下图所示: wrongly mesh created

现在,我也尝试了 chatgpt,但几个小时后我不明白为什么它会这样。我怀疑它与前 4 个点有关:事实上,第一条曲线是在记录 4 个点后创建的。为什么它从那里开始而不是从起点开始的原因超出了我的范围。你能帮我一下吗?如果您需要更多信息,我可以集成...我想我把核心放在这里。 非常感谢!

c# unity-game-engine unityscript spline interaction
1个回答
0
投票

如前所述,我相信不匹配来自于全局世界空间中的光线投射命中,而

Mesh.vertices
位于本地空间中,并且
meshFilter
本身有位置偏移。

您必须首先将曲线点转换为局部空间。

尝试

vertices[i * 2] = meshFilter.transform.InverseTransformPoint(curvePoints[i] - offset);         
vertices[i * 2 + 1] = meshFilter.transform.InverseTransformPoint(curvePoints[i] + offset);
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.