首页 > 其他分享 >Unity 模拟足球网的物理效果

Unity 模拟足球网的物理效果

时间:2024-07-26 16:00:23浏览次数:9  
标签:Vector3 网格 private Unity edge triangles 顶点 足球网 模拟

以下是模拟出足球网的效果,花光了好多细胞写出来的,满满的干货

只需要把脚本挂载在足球网对象身上即可,代码比较通用,可以用在其他网格也可以的,只需要调节参数即可,主页也写了足球发射的脚本,搭配这个足球网的效果,可以模拟出足球踢进网时的物理效果

using UnityEngine;
using System.Collections.Generic;

//球网物理效果
public class NetImpact : MonoBehaviour
{
    [Header("施加球网的力")]public float impactForce = 300f; // 施加到球网上的力大小
    [Header("碰撞层级")]public LayerMask soccerBallLayer; // 足球对象的层级
    [Header("球网弹簧力")]public float springForce = 20f; // 弹簧力
    [Header("球网阻力")]public float damping = 6f; // 阻尼
    [Header("距离衰退")]public float distanceDecay = 1f; // 距离衰减因子
    [Header("球网向后速度")]public float initialRetreatSpeed = -10f; // 初始后退速度因子
    [Header("碰撞影响到球网范围半径")]public float influenceRadius = 5f; // 碰撞影响的半径
    [Header("球网连贯性的边界刚度")]public float boundaryStiffness = 10f; // 边界刚度

    private MeshFilter meshFilter; //网格过滤器
    private Mesh mesh; //网格
    private Vector3[] originalVertices; //原始顶点位置数组
    private Vector3[] displacedVertices; //偏移后的顶点位置数组
    private Vector3[] vertexVelocities; //顶点速度数组
    private List<Edge> edges; //网格边列表
    private HashSet<int> boundaryVertices; //边界顶点集合
    private Vector3 netNormal; //网格法线方向

    //边的结构体
    private struct Edge
    {
        public int indexA; //第一关顶点索引
        public int indexB; //第二个顶点索引
        public float restLength; //边的原始长度

        public Edge(int a, int b, float length)
        {
            indexA = a;
            indexB = b;
            restLength = length;
        }
    }

    void Start()
    {
        InitializeMesh(); //初始化网格
        CalculateNetNormal(); //计算网格的法线方向
    }

    void Update()
    {
        UpdateVertices(); //更新顶点位置
        ApplySpringForces(); //应用弹簧力
        ApplyMeshChanges(); //应用网格变更
    }

    //初始化网格数据,包含顶点、边和边界顶点
    private void InitializeMesh()
    {
        meshFilter = GetComponent<MeshFilter>();
        if (meshFilter == null) throw new System.Exception("MeshFilter组件缺失.");

        mesh = meshFilter.mesh;
        if (mesh == null) throw new System.Exception("Mesh未赋值.");

        originalVertices = mesh.vertices; //获取网格的原始顶点
        displacedVertices = new Vector3[originalVertices.Length]; //初始化偏移顶点数组
        vertexVelocities = new Vector3[originalVertices.Length]; //初始化顶点速度数组
        System.Array.Copy(originalVertices, displacedVertices, originalVertices.Length); //复制原始顶点到偏移顶点数组

        edges = GetMeshEdges(mesh); //获取网格的边
        boundaryVertices = GetBoundaryVertices(mesh); //获取网格的边界顶点
    }

    //计算网格法线方向
    private void CalculateNetNormal()
    {
        netNormal = -transform.forward;
    }

    //更新顶点位置,应用弹簧力跟阻尼
    private void UpdateVertices()
    {
        for (int i = 0; i < displacedVertices.Length; i++)
        {
            Vector3 displacement = displacedVertices[i] - originalVertices[i]; //计算顶点的位移
            Vector3 velocity = vertexVelocities[i]; //获取顶点的当前速度
            velocity -= displacement * springForce * Time.deltaTime; //应用弹簧力
            velocity *= 1f - damping * Time.deltaTime;  //应用阻尼

            vertexVelocities[i] = velocity; //更新顶点速度
            displacedVertices[i] += velocity * Time.deltaTime; //更新定点位置

            // 约束边界顶点
            if (boundaryVertices.Contains(i))
            {
                //插值边界顶点位置
                displacedVertices[i] = Vector3.Lerp(displacedVertices[i], originalVertices[i], boundaryStiffness * Time.deltaTime);
            }
        }
    }

    //应用弹簧力到网格的每条边
    private void ApplySpringForces()
    {
        foreach (var edge in edges)
        {
            Vector3 delta = displacedVertices[edge.indexB] - displacedVertices[edge.indexA]; //计算两端顶点的位移
            float distance = delta.magnitude; //计算边的当前长度
            float forceMagnitude = springForce * (distance - edge.restLength); //计算弹簧力大小
            Vector3 force = forceMagnitude * delta.normalized; //计算力向量

            vertexVelocities[edge.indexA] += force * Time.deltaTime; //对第一关顶点应用力
            vertexVelocities[edge.indexB] -= force * Time.deltaTime; //对第二个顶点应用方向
        }
    }

    //更新网格顶点数据,并重新计算边界和法线
    private void ApplyMeshChanges()
    {
        mesh.vertices = displacedVertices; //更新网格顶点
        mesh.RecalculateBounds(); //重新计算网格边界
        mesh.RecalculateNormals(); //重新计算网格法线
    }

    private void OnCollisionEnter(Collision collision)
    {
        //检测碰撞对象是否足球
        if ((soccerBallLayer.value & (1 << collision.gameObject.layer)) == 0) return; 

        Vector3 contactPoint = collision.contacts[0].point; //获取碰撞点
        Vector3 collisionVelocity = collision.relativeVelocity * impactForce; //计算碰撞速度
        collision.gameObject.GetComponent<Rigidbody>().velocity = Vector3.one; //设置足球的速度,产生一种被球网拦截的效果
        ApplyImpact(contactPoint, collisionVelocity); // 对网格应用碰撞影响
    }

    //对影响半径年的顶点应用冲击力
    private void ApplyImpact(Vector3 point, Vector3 force)
    {
        Vector3 localPoint = transform.InverseTransformPoint(point); //讲碰撞点转换为局部坐标

        for (int i = 0; i < displacedVertices.Length; i++)
        {
            Vector3 vertexWorldPosition = transform.TransformPoint(displacedVertices[i]); //讲顶点位置转换为世界坐标
            float distance = Vector3.Distance(vertexWorldPosition, point); //计算顶点与碰撞点的距离
            if (distance < influenceRadius)
            {
                //根据距离计算衰减后的力
                float attenuatedForceMagnitude = force.magnitude * Mathf.Exp(-distance * distanceDecay);
                if (attenuatedForceMagnitude < 0.01f) attenuatedForceMagnitude = 0.01f;
                Vector3 attenuatedForce = force.normalized * attenuatedForceMagnitude;

                //将力应用于网格法线方向相反的方向
                Vector3 forceDirection = -netNormal;
                vertexVelocities[i] += forceDirection * attenuatedForce.magnitude * initialRetreatSpeed * Time.deltaTime;
            }
        }
    }

    //从网格三角形生成边列表
    private List<Edge> GetMeshEdges(Mesh mesh)
    {
        var triangles = mesh.triangles; //获取网格的三角形
        var edges = new HashSet<(int, int)>(); //存储边的集合

        for (int i = 0; i < triangles.Length; i += 3)
        {
            //为每个三角形添加边
            AddEdge(edges, triangles[i], triangles[i + 1]);
            AddEdge(edges, triangles[i + 1], triangles[i + 2]);
            AddEdge(edges, triangles[i + 2], triangles[i]);
        }

        var edgeList = new List<Edge>(); //存储边的列表
        foreach (var edge in edges)
        {
            //计算每条边的原始长度
            float restLength = (originalVertices[edge.Item1] - originalVertices[edge.Item2]).magnitude;
            edgeList.Add(new Edge(edge.Item1, edge.Item2, restLength));
        }

        return edgeList;
    }

    //确保边以一致的顺序添加到集合中
    private void AddEdge(HashSet<(int, int)> edges, int a, int b)
    {
        /**
         if (a > b) (a, b) = (b, a); 
        上面写法等同下面写法,是一种元组结构赋值语法,交换变量
         if (a > b)
        {
            int temp = a;
            a = b;
            b = temp;
        }
         */

        if (a > b) (a, b) = (b, a); //确保顶点索引按顺序排列
        edges.Add((a, b)); //添加边到集合
    }

    //从网格三角形中识别边界顶点
    private HashSet<int> GetBoundaryVertices(Mesh mesh)
    {
        var boundaryVertices = new HashSet<int>(); //存储边界顶点的集合
        var triangles = mesh.triangles; //获取网格的三角形
        var edgeCount = new Dictionary<(int, int), int>(); //边的出现次数

        for (int i = 0; i < triangles.Length; i += 3)
        {
            //统计每条边的出现次数
            AddEdgeCount(edgeCount, triangles[i], triangles[i + 1]);
            AddEdgeCount(edgeCount, triangles[i + 1], triangles[i + 2]);
            AddEdgeCount(edgeCount, triangles[i + 2], triangles[i]);
        }

        //识别只出现一次的边,即是边界边
        foreach (var edge in edgeCount)
        {
            if (edge.Value == 1)
            {
                boundaryVertices.Add(edge.Key.Item1);
                boundaryVertices.Add(edge.Key.Item2);
            }
        }

        return boundaryVertices;
    }

    //统计边出现次数
    private void AddEdgeCount(Dictionary<(int, int), int> edgeCount, int a, int b)
    {
        if (a > b) (a, b) = (b, a); //确保顶点索引索引顺序排列
        if (edgeCount.ContainsKey((a, b)))
        {
            edgeCount[(a, b)]++; //增加边的计数
        }
        else
        {
            edgeCount[(a, b)] = 1; //初始化边的计数
        }
    }
}

标签:Vector3,网格,private,Unity,edge,triangles,顶点,足球网,模拟
From: https://blog.csdn.net/m0_63261376/article/details/140717676

相关文章

  • 暑假集训CSP提高模拟8
    暑假集训CSP提高模拟8\(T1\)P155.基础的生成函数练习题(gf)\(100pts\)原题:[ABC093C]SameIntegers设通过操作\(a,b,c\)所能达到的最小整数为\(x\)。观察到对同两个数进行操作\(2\)等价于分别对这两个数各进行操作\(1\),在\(a,b,c\)达到\(x\)的过程中优先......
  • 模拟算法概览
    前言LeetCode上的模拟算法题目主要考察通过直接模拟问题的实际操作和过程来解决问题。这类题目通常不需要高级的数据结构或复杂的算法,而是通过仔细的逻辑和清晰的步骤逐步解决。适合解决的问题模拟算法适合用来解决那些逻辑明确、步骤清晰且可以逐步执行的问题。这类题型通......
  • Ryujinx(Switch模拟器) v1.1.1361 汉化版
    Ryujinx是一款免费、开源的NintendoSwitch模拟器,它可以在电脑上模拟NintendoSwitch游戏机的运行环境,让玩家们能够在PC上畅玩Switch游戏。Ryujinx支持大部分NintendoSwitch游戏,包括TheLegendofZelda:BreathoftheWild、SuperMarioOdyssey等知名游戏,而且还......
  • 【题解】「CSP模拟赛」雨天 rain
    雨天rain考场上打了一个动态开点线段树,但是被卡空间了......
  • 基于GD32的矩阵按键usb-hid设备,详细教程,完全模拟的电脑数字键盘的所有功能,包括长按、
    本文采用的是基于GD32F350的一个4×5的矩阵键盘键盘板。矩阵键盘的电路原理图大致如下,由四个列引脚和五个行引脚来检测判断按键的按下。本文四个列引脚分别是PA15PB8PB9PC13,五个行引脚分别是PB10PB11PB12PB13PB14。typedefstruct{uint32_tGPIO_Group;......
  • AI外包团队 Unity3D结合AI教育大模型 开发AI教师 AI外教 AI英语教师 AI课堂案例
    自2022年底ChatGPT引爆全球之后,大模型技术便迎来了一段崭新的快速发展期,由其在GPT4.0发布后,AI与教育领域结合产品研发、已成为教育+AI科技竞争的新高地、未来产业的新赛道、经济发展的新引擎和新产品的诞生地。据不完全统计,目前国内已有包括科大讯飞、百度、阿里、华为、网易在......
  • 暑假集训CSP提高模拟7
    这个T1的\(n^{3}\)的SPJ效率还是太慢了,膜拜SPJ大神学长,还会画画A.Permutations&Primes这题感觉挺水的但是感觉有不是那么水,主要还是因为我赛时没想出正解,在打的表里找了一组好看的规律,打上了然后就过了.对偶数来说,我的规律正好是正解的特化,但是对奇数来说,我的规律就......
  • 【unity实战】完美的2D横版平台跳跃玩家控制器,使用InputSystem+有限状态机实现人物加
    最终效果文章目录最终效果前言素材目录结构动画配置检测脚本状态机玩家有限状态机玩家控制脚本定义人物不同状态待机移动跳跃下落状态落地状态墙壁滑行状态蹬墙跳状态蹬墙跳下落状态一段近战攻击状态二段近战攻击状态冲锋状态土狼时间状态攀爬开始状态攀爬进行状态功能......
  • 「模拟赛」暑期集训CSP提高模拟6(7.23)
    \(140pts,Rank23\)题目列表A.花间叔祖B.合并rC.回收波特D.斗篷花间叔祖\(98pts\)题意:给定一个数组,选择一个大于等于2的模数,然后把数组中的数变成\(mod\)该模数后的数。只能操作一次,问操作后最少有几种不同的数。赛事分析:开始5分钟想到了算\(a_i\)中所有......
  • 盖世计划-0724-B班模拟 C. 游戏 (game)
    首先,Alice先去\(n\)个商店中购买物品。其中第\(i\)个商店售卖编号为\(i\)的物品,且每个物品的售价为\(a_i\)。Alice的总花费不能超过\(k\)。接着,Bob再去另外\(m\)个商店中购买物品。其中第\(i\)个商店售卖编号为\(n+i\)的物品,且每个物品的售价为\(1\)。Bob的......