首页 > 解决方案 > 我可以使用哪些方法来处理球上的软体物理

问题描述

我正在 Unity 中为其中一个对象制作一个带有橡皮球的游戏。我浏览了各种可用的软体物理资产,但没有一个看起来特别符合我的要求。我希望编写一个会弹跳的球,并在它落地时进行拉伸。这将是我的游戏的中心点,我希望它看起来尽可能好,但是我在网上找到的所有解决方案都是小补丁或写得不好。我也想研究解决方案,以便将来适应并从中学习,我不想只是购买资产并将其投入游戏中。

我首先被零世界及其网格变形视频所吸引。然而,这些对于我的需求来说似乎异常复杂,我被他的 Squishy Sphere 视频所吸引,在那里它们不能很好地工作。最终我发现了 Catlike Coding 及其基于向量等的网格变形。它在视觉上效果非常好,但是根据我的需要调整它比我希望的要复杂得多。

编辑2:我添加了这个类的其余部分以更好地展示功能

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MeshDeformerInput : MonoBehaviour
{

public float force = 10f;
public float forceOffset = 0.01f;

void HandleInput(Ray inputRay)
{
    RaycastHit hit;

    Debug.DrawRay(inputRay.origin, inputRay.direction * 50000000, Color.red);

    if (Physics.Raycast(inputRay, out hit))
    {
        MeshDeformer deformer = hit.collider.GetComponent<MeshDeformer>();

        if (deformer)
        {
            Vector3 point = hit.point;
            point += hit.normal * forceOffset;
            deformer.AddDeformingForce(point, force);
        }
    }
}

void OnCollisionStay(Collision collision)
{
    foreach (ContactPoint contact in collision.contacts)
    {
        Debug.Log("Hit!");
        // Visualize the contact point
        Debug.DrawRay(contact.point, contact.normal, Color.white);

        Ray newRay = new Ray(contact.point, contact.normal);
        HandleInput(newRay);
    }
}

我遇到了 OnCollisionEnter 的问题,因为当球滚动时它不会继续正确地进行碰撞检测。我转而继续希望在球滚动时改变矢量,而不是在一个点上,它工作得更好一点。

在它调用 HandleInput 之后,将光线和静态力带入我的网格变形器,从这里开始它与本教程中的代码相同

https://catlikecoding.com/unity/tutorials/mesh-deformation/

我遇到的问题首先是球滚动时接触点移动太慢,导致错误放置的凹陷。弹簧物理响应太慢,增加速度会导致网格物理完全崩溃。其次,球在接触点上方盘旋,因为对撞机仍然没有移动,这会导致我的游戏出现重大问题。我想我要问的是这种方法是否有任何替代方法,或者如果这个方法很好,我只是在接近这个错误?我已经读到 Unity 没有附带标准的软体物理,但这是一个我永远无法完全正确的功能,因为它默认没有,还是我只是在错误的地方寻找?

编辑1:链接失效是一个公平点,所以这里是代码。我将尝试将其全部包含在内,因为它相当厚,而且我觉得描述它是不够的。

首先是我的实际网格变形器。该代码使用了一个圆角立方体,它是用我放在最后的代码构建的,因此我遇到了问题。每个顶点都被编码在一个循环中,以根据与碰撞点的距离进行缩进。最初它是从您从相机点击的地方绘制的光线,我调整了它以改为从与物体的接触点绘制光线。它似乎至少在正确的位置工作,只是不是马上就可以保持凹痕的时间比它应该的要长。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(MeshFilter))]
public class MeshDeformer : MonoBehaviour 
{


Mesh deformingMesh;
Vector3[] originalVertices, displacedVertices, vertexVelocities;

public float forceOffset = 0.1f;
public float springForce = 20f;
public float damping = 5f;
float uniformScale = 1f;

void Start()
{
    deformingMesh = GetComponent<MeshFilter>().mesh;
    originalVertices = deformingMesh.vertices;
    displacedVertices = new Vector3[originalVertices.Length];
    vertexVelocities = new Vector3[originalVertices.Length];

    for (int i = 0; i < originalVertices.Length; i++)
    {
        displacedVertices[i] = originalVertices[i];
    }
}

public void AddDeformingForce(Vector3 point, float force)
{
    Debug.Log("Deformer called with: " + point + " point");
    point = transform.InverseTransformPoint(point);
    for (int i = 0; i < displacedVertices.Length; i++)
    {
        AddForceToVertex(i, point, force);
    }
}
void AddForceToVertex(int i, Vector3 point, float force)
{
    Vector3 pointToVertex = displacedVertices[i] - point;
    pointToVertex *= uniformScale;
    float attenuatedForce = force / (1f + pointToVertex.sqrMagnitude);
    float velocity = attenuatedForce * Time.deltaTime;
    vertexVelocities[i] += pointToVertex.normalized * velocity;
}

void Update()
{
    uniformScale = transform.localScale.x;
    for (int i = 0; i < displacedVertices.Length; i++)
    {
        UpdateVertex(i);
    }
    deformingMesh.vertices = displacedVertices;
    deformingMesh.RecalculateNormals();
}
void UpdateVertex(int i)
{
    Vector3 velocity = vertexVelocities[i];
    Vector3 displacement = displacedVertices[i] - originalVertices[i];
    displacement *= uniformScale;
    velocity -= displacement * springForce * Time.deltaTime;
    velocity *= 1f - damping * Time.deltaTime;
    vertexVelocities[i] = velocity;
    displacedVertices[i] += velocity * (Time.deltaTime / uniformScale);
}
}

不确定这是否会有所帮助,但这是球体编码,以防万一。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;



[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class CubeSphere : MonoBehaviour
{
public int gridSize;
public float radius = 1f;

private Mesh mesh;
private Vector3[] vertices;
private Vector3[] normals;
private Color32[] cubeUV;



private void Awake()
{
    Generate();
}

private void Generate()
{
    GetComponent<MeshFilter>().mesh = mesh = new Mesh();
    mesh.name = "Procedural Cube";
    CreateVertices();
    CreateTriangles();
    CreateColliders();

}

private void CreateVertices()
{
    int cornerVertices = 8;
    int edgeVertices = (gridSize + gridSize + gridSize - 3) * 4;
    int faceVertices = (
        (gridSize - 1) * (gridSize - 1) +
        (gridSize - 1) * (gridSize - 1) +
        (gridSize - 1) * (gridSize - 1)) * 2;
    vertices = new Vector3[cornerVertices + edgeVertices + faceVertices];
    normals = new Vector3[vertices.Length];
    cubeUV = new Color32[vertices.Length];

    int v = 0;
    for (int y = 0; y <= gridSize; y++)
    {
        for (int x = 0; x <= gridSize; x++)
        {
            SetVertex(v++, x, y, 0);
        }
        for (int z = 1; z <= gridSize; z++)
        {
            SetVertex(v++, gridSize, y, z);
        }
        for (int x = gridSize - 1; x >= 0; x--)
        {
            SetVertex(v++, x, y, gridSize);
        }
        for (int z = gridSize - 1; z > 0; z--)
        {
            SetVertex(v++, 0, y, z);
        }
    }
    for (int z = 1; z < gridSize; z++)
    {
        for (int x = 1; x < gridSize; x++)
        {
            SetVertex(v++, x, gridSize, z);
        }
    }
    for (int z = 1; z < gridSize; z++)
    {
        for (int x = 1; x < gridSize; x++)
        {
            SetVertex(v++, x, 0, z);
        }
    }

    mesh.vertices = vertices;
    mesh.normals = normals;
    mesh.colors32 = cubeUV;
}

private void SetVertex(int i, int x, int y, int z)
{
    Vector3 v = new Vector3(x, y, z) * 2f / gridSize - Vector3.one;
    float x2 = v.x * v.x;
    float y2 = v.y * v.y;
    float z2 = v.z * v.z;
    Vector3 s;
    s.x = v.x * Mathf.Sqrt(1f - y2 / 2f - z2 / 2f + y2 * z2 / 3f);
    s.y = v.y * Mathf.Sqrt(1f - x2 / 2f - z2 / 2f + x2 * z2 / 3f);
    s.z = v.z * Mathf.Sqrt(1f - x2 / 2f - y2 / 2f + x2 * y2 / 3f);
    normals[i] = s;
    vertices[i] = normals[i] * radius;
    cubeUV[i] = new Color32((byte)x, (byte)y, (byte)z, 0);
}

private void CreateTriangles()
{
    int[] trianglesZ = new int[(gridSize * gridSize) * 12];
    int[] trianglesX = new int[(gridSize * gridSize) * 12];
    int[] trianglesY = new int[(gridSize * gridSize) * 12];
    int ring = (gridSize + gridSize) * 2;
    int tZ = 0, tX = 0, tY = 0, v = 0;

    for (int y = 0; y < gridSize; y++, v++)
    {
        for (int q = 0; q < gridSize; q++, v++)
        {
            tZ = SetQuad(trianglesZ, tZ, v, v + 1, v + ring, v + ring + 1);
        }
        for (int q = 0; q < gridSize; q++, v++)
        {
            tX = SetQuad(trianglesX, tX, v, v + 1, v + ring, v + ring + 1);
        }
        for (int q = 0; q < gridSize; q++, v++)
        {
            tZ = SetQuad(trianglesZ, tZ, v, v + 1, v + ring, v + ring + 1);
        }
        for (int q = 0; q < gridSize - 1; q++, v++)
        {
            tX = SetQuad(trianglesX, tX, v, v + 1, v + ring, v + ring + 1);
        }
        tX = SetQuad(trianglesX, tX, v, v - ring + 1, v + ring, v + 1);
    }

    tY = CreateTopFace(trianglesY, tY, ring);
    tY = CreatebottomFace(trianglesY, tY, ring);

    mesh.subMeshCount = 3;
    mesh.SetTriangles(trianglesZ, 0);
    mesh.SetTriangles(trianglesX, 1);
    mesh.SetTriangles(trianglesY, 2);

}
private int CreateTopFace(int[] triangles, int t, int ring)
{
    int v = ring * gridSize;
    for (int x = 0; x < gridSize - 1; x++, v++)
    {
        t = SetQuad(triangles, t, v, v + 1, v + ring - 1, v + ring);
    }
    t = SetQuad(triangles, t, v, v + 1, v + ring - 1, v + 2);

    int vMin = ring * (gridSize + 1) - 1;
    int vMid = vMin + 1;
    int vMax = v + 2;

    for (int z = 1; z < gridSize - 1; z++, vMin--, vMid++, vMax++)
    {
        t = SetQuad(triangles, t, vMin, vMid, vMin - 1, vMid + gridSize - 1);
        for (int x = 1; x < gridSize - 1; x++, vMid++)
        {
            t = SetQuad(
                triangles, t,
                vMid, vMid + 1, vMid + gridSize - 1, vMid + gridSize);
        }
        t = SetQuad(triangles, t, vMid, vMax, vMid + gridSize - 1, vMax + 1);
    }

    int vTop = vMin - 2;
    t = SetQuad(triangles, t, vMin, vMid, vMin - 1, vMin - 2);
    for (int x = 1; x < gridSize - 1; x++, vTop--, vMid++)
    {
        t = SetQuad(triangles, t, vMid, vMid + 1, vTop, vTop - 1);
    }
    t = SetQuad(triangles, t, vMid, vTop - 2, vTop, vTop - 1);
    return t;
}

private int CreatebottomFace (int[] triangles, int t, int ring)
{
    int v = 1;
    int vMid = vertices.Length - (gridSize - 1) * (gridSize - 1);
    t = SetQuad(triangles, t, ring - 1, vMid, 0, 1);
    for (int x = 1; x < gridSize - 1; x++, v++, vMid++)
    {
        t = SetQuad(triangles, t, vMid, vMid + 1, v, v + 1);
    }
    t = SetQuad(triangles, t, vMid, v + 2, v, v + 1);

    int vMin = ring - 2;
    vMid -= gridSize - 2;
    int vMax = v + 2;

    for (int z = 1; z < gridSize - 1; z++, vMin--, vMid++, vMax++)
    {
        t = SetQuad(triangles, t, vMin, vMid + gridSize - 1, vMin + 1, vMid);
        for (int x = 1; x < gridSize - 1; x++, vMid++)
        {
            t = SetQuad(
                triangles, t,
                vMid + gridSize - 1, vMid + gridSize, vMid, vMid + 1);
        }
        t = SetQuad(triangles, t, vMid + gridSize - 1, vMax + 1, vMid, vMax);
    }

    int vTop = vMin - 1;
    t = SetQuad(triangles, t, vTop + 1, vTop, vTop + 2, vMid);
    for (int x = 1; x < gridSize - 1; x++, vTop--, vMid++)
    {
        t = SetQuad(triangles, t, vTop, vTop - 1, vMid, vMid + 1);
    }
    t = SetQuad(triangles, t, vTop, vTop - 1, vMid, vTop - 2);

    return t;
}

private void OnDrawGizmos()
{
    if (vertices == null)
    {
        Debug.Log("Returning");
        return;
    }
    for (int i = 0; i < vertices.Length; i++)
    {
        Gizmos.color = Color.black;
        //Gizmos.DrawSphere(vertices[i], 0.1f);
        Gizmos.color = Color.yellow;
        //Gizmos.DrawRay(vertices[i], normals[i]);
    }
}

private void CreateColliders()
{
    gameObject.AddComponent<SphereCollider>();
}

private static int SetQuad(int[] triangles, int i, int v00, int v10, int v01, int v11)
{
    triangles[i] = v00;
    triangles[i + 1] = triangles[i + 4] = v01;
    triangles[i + 2] = triangles[i + 3] = v10;
    triangles[i + 5] = v11;
    return i + 6;
}


}

现在,你们中的一些人可能会看到偏移、阻尼和比例等数字并不完全正确。我一直在编辑它们,试图让顶点加速它们的移动。然而,由于弹簧的影响,当它们移动得太快以使其变形更快以匹配球滚动时的实际接触点时,它会导致它在整个地方反弹。阻尼最初是为了阻止这种情况,但它会再次减慢它的速度。

另一个问题是,如果球静止不动,即使它与地面接触,变形也会恢复正常。我考虑添加一个检查现有联系人的语句,但担心区分对象以维护表单会导致复杂性增加,最终无论如何都不起作用。

最后,实际的对撞机不会随着变形而改变。由于顶点的方向在某种程度上基于对撞机的位置,我不确定如何解决这个问题。因此,进退两难,我不确定是否值得完全放弃这种技术,或者我是否可以采取解决方案来解决这个问题。

标签: c#unity3dgame-physicsmesh

解决方案


我继续寻找替代方案,我真正看到的唯一一个效果很好的是来自 Bullet Physics 下的资产商店的那个。它包括用于软体球体的脚本,并且可能适用于我的情况。我将对其进行修改以尝试使其正常工作,但这里是被带到此页面的任何人的链接。

https://assetstore.unity.com/packages/tools/physics/bullet-physics-for-unity-62991

截屏

我会在接下来的几天里继续检查,以防有人有更好的解决方案。


推荐阅读