游戏仿真实验Unity仿真蒲丰投针实验,丢针计算圆周率,丢了一百万次针得出的结果是...

Posted 林新发

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了游戏仿真实验Unity仿真蒲丰投针实验,丢针计算圆周率,丢了一百万次针得出的结果是...相关的知识,希望对你有一定的参考价值。


一、前言

嗨,大家好,我是新发。
我有个后端同事去面试的时候被问到了 蒲丰投针问题,可能有少部分同学没听过蒲丰投针问题,我这里简单科普一下。
假设桌面上画满间隔均为 D D D的平行直线,向桌面任意投放一根长为 L L L L < D L<D L<D)的针,可以通过几何概型的计算得出:针与某直线相交的概率为:
P = 2 L π D P = \\frac{2L}{πD} P=πD2L
L = 0.5 D L = 0.5D L=0.5D时,
P = 2 L π D = D π D = 1 π P = \\frac{2L}{πD} = \\frac{D}{πD} = \\frac{1}{π} \\quad\\quad P=πD2L=πDD=π1
也就是说,在 L = 0.5 D L = 0.5D L=0.5D的情况下,我们总共投了 M M M根针,有 N N N根针与平行线相交,则
1 P ≈ M N ≈ π \\frac{1}{P} \\approx \\frac{M}{N} \\approx π \\quad\\quad P1NMπ
证明过程网上有很多,这里我就不展开了,我今天要做的,就是使用Unity来对这个实验进行仿真,看看实验结果是不是与理论一致,我在网上没有看到有人使用Unity做过蒲丰投针的仿真实验,那我就来做全网第一人吧~

二、制作场景

1、制作针模型

Unity创建一个工程,在Hiererchy面板空白处点击鼠标右键,点击菜单3D Object / Cylinder,创建一个圆柱体,

如下

调整Scale缩放,让它变成一个很细的圆柱体,

如下:

创建个材质球,调为红色,赋给它,这样看起来显眼一点,效果如下:

2、桌面制作

创建一个Cube

如下

调整Scalexz,让它成为一个大桌面,

3、平行线制作

也是使用圆柱体,拉长,然后等间距排列即可,确保间距是针的长度的两倍,效果如下:

三、物理仿真

1、桌面无反弹

我希望针掉落到桌面时不要有弹跳,我们制作一个物理材质,
Project面板空白处右键鼠标,点击菜单Create / Physic Material

设置动态阻力和静态阻力为100,弹性为0Friction CombineBounce Combine都设置为Minimum

把物理材质赋值给桌面的碰撞体组件的Material属性,如下,

2、针掉落

给针添加Rigidbody组件,

这样针就会收到重力作用而向下掉落,如下:

3、针滚动问题

我们看到针掉到桌面时,会滚动,

这是因为帧本身的角阻力太小,我们选中针物体,把它的Rigidbody的角阻力设置为100

可以看到掉到桌面后不会滚动了,不过躺平时也会受到角阻力作用,

没关系,我们确保阵掉落的姿势与桌面平行就好了,

4、针架到平行线上的问题

我们看到,当帧落到平行线上时,会架在平行线上,

只需要把平行线弄成触发器就可以了,

效果如下:

5、针与针相互影响的问题

当一根针掉落在另一根针上时,会相互影响,比如架在别的针身上,影响与平行线的检测,比如这样子,

这个问题怎么解决呢?我们给针单独做一个层,

添加一个needle层,

把针设置为needle层,

然后打开Project Settings,进入Physics,把碰撞矩阵里的needle勾选去掉,

可以看到,针与针可以相互穿透,互不影响了,

四、UI界面

使用UGUI简单做一下界面,如下

五、编写代码

1、针脚本

创建一个Needle.cs脚本,编写碰撞检测逻辑,逻辑很简单,我都写了注释,大家应该能看懂,

using UnityEngine;
using System;

public class Needle : MonoBehaviour
{
    /// <summary>
    /// 碰撞到平行线回调
    /// </summary>
    public Action hitLineCb;
    /// <summary>
    /// 碰撞到地面回调
    /// </summary>
    public Action hitPlaneCb;

    private Rigidbody rig;
    /// <summary>
    /// 延迟回收
    /// </summary>
    private float delayHideTimer;
    
    /// <summary>
    /// 是否碰到了平行线
    /// </summary>
    private bool isHitedLine = false;
    private void Awake()
    {
        rig = GetComponent<Rigidbody>();
    }

    private void OnEnable()
    {
        delayHideTimer = 0f;
        rig.isKinematic = false;
        isHitedLine = false;
    }

    public void OnTriggerEnter(Collider collision)
    {
        if (isHitedLine) return;
        // 碰到了平行线
        if("line" == collision.gameObject.tag)
        {
            isHitedLine = true;
            hitLineCb?.Invoke();
        }
    }

    public void OnCollisionEnter(Collision collision)
    {
        // 碰到了桌面
        if("plane" == collision.gameObject.tag)
        {
            // 1秒后回收
            delayHideTimer = 1f;
        }
    }

    private void Update()
    {
        if (delayHideTimer > 0)
        {
            delayHideTimer -= Time.deltaTime;
            if (delayHideTimer <= 0)
            {
                hitPlaneCb?.Invoke();
            }
        }
    }
}

Needle.cs脚本挂到针物体上,

设置桌面的tagplane

设置平行线的tagline

2、对象池

因为要很高频地丢针,如果每次都创建销毁会比较浪费CPU,所以我们写个对象池脚本:NeedlePool.cs,代码如下:

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 对象池
/// </summary>
public class NeedlePool 
{
    /// <summary>
    /// 入池
    /// </summary>
    public void Enqueue(GameObject obj)
    {
        objPool.Enqueue(obj);
    }

    /// <summary>
    /// 出池
    /// </summary>
    public GameObject Dequeue()
    {
        if (objPool.Count == 0) return null; 
        return objPool.Dequeue();
    }

    private Queue<GameObject> objPool = new Queue<GameObject>();
}

3、入口脚本

创建Main.cs作为入口脚本,实现交互和控制,代码如下:

using UnityEngine;
using UnityEngine.UI;

public class Main : MonoBehaviour
{
    public Text totalCntText;
    public Text hitedCntText;
    public Text resultText;

    public GameObject needleObj;
    public Button startBtn;
    public Text btnStateText;
    

    private int totalCnt;
    private int hitedCnt;
    private float timer;
    private bool isStarted = false;

    // 对象池
    private NeedlePool pool = new NeedlePool();

    private void Awake()
    {
        needleObj.SetActive(false);
    }

    private void Start()
    {
        startBtn.onClick.AddListener(() => 
        {
            isStarted = !isStarted;
            btnStateText.text = isStarted ? "停止" : "开始";
        });
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            // 鼠标点击,生成针
            GenNeedle();
            UpdateText();
        }

        if (isStarted)
        {
            // 自动生成针
            timer += Time.deltaTime;
            if (timer > 0.03f)
            {
                // 提高丢针数量,加快速度
                for (int i = 0; i < 7; ++i)
                {
                    GenNeedle();
                }
                UpdateText();
                timer = 0;
            }
        }
    }

    /// <summary>
    /// 生成针
    /// </summary>
    private void GenNeedle()
    {
        ++totalCnt;
        var obj = pool.Dequeue();
        if(null == obj)
            obj = Instantiate(needleObj);
        var x = Random.Range(-100f, 100f);
        var y = 0.1f;
        var z = Random.Range(-60f, 60f);
        obj.transform.position = new Vector3(x, y, z);
        obj.transform.rotation = Quaternion.Euler(new Vector3(90, Random.Range(-360f, 360f), 0));
        obj.SetActive(true);
        var needle = obj.GetComponent<Needle>();
        needle.hitLineCb = () =>
        {
            ++hitedCnt;
            UpdateText();
        };
        needle.hitPlaneCb = () => 
        {
            obj.SetActive(false);
            pool.Enqueue(obj);
        };
    }

    /// <summary>
    /// 更新UI
    /// </summary>
    private void UpdateText()
    {
        totalCntText.text = totalCnt.ToString();
        hitedCntText.text = hitedCnt.ToString();
        if(0 != hitedCnt)
            resultText.text = ((float)totalCnt / hitedCnt).ToString("#0.00000");
    }
}

Main.cs脚本挂到MainPanel上,并赋值成员对象,如下:

六、运行测试

运行Unity,测试效果如下:

投了十万多针,

继续投针,投了一百万针,

π的理论值很接近啦,实验成功~

七、工程源码

本文工程我已上传到CODE CHINA,感兴趣的同学可自行下载学习,
地址:https://codechina.csdn.net/linxinfa/UnityBuffonNeedlesGame
注:我使用的Unity版本为Unity 2021.1.9f1c1 (64-bit)

八、完毕

好了,今天就到这里吧。

我是林新发:https://blog.csdn.net/linxinfa
原创不易,若转载请注明出处,感谢大家~
喜欢我的可以点赞、关注、收藏,如果有什么技术上的疑问,欢迎留言或私信,拜拜~

以上是关于游戏仿真实验Unity仿真蒲丰投针实验,丢针计算圆周率,丢了一百万次针得出的结果是...的主要内容,如果未能解决你的问题,请参考以下文章

游戏仿真实验使用Unity仿真电视机光学三原色显示画面,致敬袁隆平爷爷

游戏仿真实验使用Unity仿真电视机光学三原色显示画面,我是要成为海贼王的男人

R语言对布丰投针(蒲丰投针)实验进行模拟和动态可视化生成GIF动画

MATLAB基础语法之蒙特卡罗模拟_1(布丰投针)

蒙特卡罗简单学习

Multisim仿真同相比例运算放大电路仿真实验