
文章目錄
- 一、前言
- 二、制作場景
- 1、制作針模型
- 2、桌面制作
- 3、平行線制作
- 三、物理仿真
- 1、桌面無反彈
- 2、針掉落
- 3、針滾動問題
- 4、針架到平行線上的問題
- 5、針與針相互影響的問題
- 四、UI界面
- 五、撰寫代碼
- 1、針腳本
- 2、物件池
- 3、入口腳本
- 六、運行測驗
- 七、工程原始碼
- 八、完畢
一、前言
嗨,大家好,我是新發,
我有個后端同事去面試的時候被問到了 蒲豐投針問題,可能有少部分同學沒聽過蒲豐投針問題,我這里簡單科普一下,
假設桌面上畫滿間隔均為
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
P1?≈NM?≈π
證明程序網上有很多,這里我就不展開了,我今天要做的,就是使用Unity來對這個實驗進行仿真,看看實驗結果是不是與理論一致,我在網上沒有看到有人使用Unity做過蒲豐投針的仿真實驗,那我就來做全網第一人吧~
二、制作場景
1、制作針模型
Unity創建一個工程,在Hiererchy面板空白處點擊滑鼠右鍵,點擊選單3D Object / Cylinder,創建一個圓柱體,

如下

調整Scale縮放,讓它變成一個很細的圓柱體,

如下:

創建個材質球,調為紅色,賦給它,這樣看起來顯眼一點,效果如下:

2、桌面制作
創建一個Cube,

如下

調整Scale的x和z,讓它成為一個大桌面,

3、平行線制作
也是使用圓柱體,拉長,然后等間距排列即可,確保間距是針的長度的兩倍,效果如下:

三、物理仿真
1、桌面無反彈
我希望針掉落到桌面時不要有彈跳,我們制作一個物理材質,
在Project面板空白處右鍵滑鼠,點擊選單Create / Physic Material,

設定動態阻力和靜態阻力為100,彈性為0,Friction Combine和Bounce 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腳本掛到針物體上,

設定桌面的tag為plane,

設定平行線的tag為line,

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
原創不易,若轉載請注明出處,感謝大家~
喜歡我的可以點贊、關注、收藏,如果有什么技術上的疑問,歡迎留言或私信,拜拜~
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/295500.html
標籤:其他
