【5/8 更新】2d 粒子模擬系統:ui 分區、標籤管理、邊界限制與力向量可視化

畫面 UI 三區塊化
標籤動態顯示與排序
畫面邊界位置強制約束
萬有引力向量可視化
以下分別介紹每個部分的需求、實作與程式碼重點。
一、畫面 UI 三區塊化
需求:
左側輸入區(僅在按下「開始」前顯示)
中間世界區(永遠用來渲染粒子本體與標籤)
右側控制區(開始後顯示所有操作按鈕/滑桿/圖表,不覆蓋世界區)
實作:
在 Canvas 下建立三個 Panel:
LeftPanel
、WorldPanel
、RightPanel
LeftPanel
:固定寬 300px,掛VerticalLayoutGroup
+ContentSizeFitter
,放置「粒子數量」和「速度上限」輸入,以及「開始」按鈕。WorldPanel
:錨點全螢幕 Stretch,掛載CanvasGroup
,由腳本控制顯示。RightPanel
:固定寬 300px,同樣加上垂直排版,用於暫停/重置/開啟資料夾按鈕、滑桿與圖表。
// UILayoutManager.cs — 自動將左右兩側 Panel 固定 300px
using UnityEngine;
public class UILayoutManager : MonoBehaviour
{
public RectTransform leftPanel, rightPanel;
void Awake()
{
// 左側 300px
leftPanel.anchorMin = new Vector2(0,0);
leftPanel.anchorMax = new Vector2(0,1);
leftPanel.pivot = new Vector2(0,0.5f);
leftPanel.sizeDelta = new Vector2(300f, 0f);
// 右側 300px
rightPanel.anchorMin = new Vector2(1,0);
rightPanel.anchorMax = new Vector2(1,1);
rightPanel.pivot = new Vector2(1,0.5f);
rightPanel.sizeDelta = new Vector2(300f, 0f);
}
}
在 SimulationUIBindings.OnStartClicked()
中呼叫 UIPanelController.OnStart()
,隱藏左側、啟用中間世界區和右側控制。
二、標籤動態顯示與排序
需求:
畫面保持乾淨 → 預設隱藏所有粒子標籤
想看時才顯示 → 由 UI Toggle 切換
showLabels
每次只顯示「速度最快的前 N 筆」標籤
// ParticleManager.cs
[Header("標籤顯示設定")]
public int maxLabelsToShow = 20; // 最多同時顯示的標籤數
public bool showLabels = false; // Toggle 控制
public void UpdateLabels()
{
if (!showLabels)
{
foreach (var lbl in activeLabels)
lbl.gameObject.SetActive(false);
return;
}
// 依速度排序,只顯示前 N 筆
var list = new List<(GameObject p, TMP_Text lbl, float v)>();
for (int i = 0; i < activeParticles.Count; i++)
list.Add((activeParticles[i], activeLabels[i],
activeParticles[i].GetComponent<Rigidbody2D>().velocity.magnitude));
list.Sort((a,b)=> b.v.CompareTo(a.v));
for (int k = 0; k < list.Count; k++)
{
var (p,lbl,v) = list[k];
if (k < maxLabelsToShow)
{
lbl.gameObject.SetActive(true);
// 縮小+抖動以避免重疊
float s = p.transform.localScale.x;
Vector3 pos = p.transform.position + Vector3.up*(s+0.1f)
+ new Vector3(Random.Range(-0.05f,0.05f),Random.Range(-0.05f,0.05f),0);
lbl.transform.position = pos;
lbl.fontSize = 14;
lbl.text = $"ID:{k} | v:{v:F1}";
}
else lbl.gameObject.SetActive(false);
}
}
在 SimulationController.Update()
中調用 particleManager.UpdateLabels()
,並由右側 Toggle 綁定 particleManager.showLabels
。
三、畫面邊界位置強制約束
需求:粒子有時因物理計算會飛出視野,必須「手動」將位置 clamp 回螢幕範圍。
// ParticleManager.cs
[Header("邊界限制")]
public Camera mainCamera;
private Vector2 minBounds, maxBounds;
public void UpdateBounds()
{
if (mainCamera==null||!mainCamera.orthographic) return;
float v = mainCamera.orthographicSize;
float h = v * mainCamera.aspect;
Vector3 c = mainCamera.transform.position;
minBounds = new Vector2(c.x - h, c.y - v);
maxBounds = new Vector2(c.x + h, c.y + v);
}
public void ClampParticles()
{
foreach (var p in activeParticles)
{
Vector3 pos = p.transform.position;
pos.x = Mathf.Clamp(pos.x, minBounds.x, maxBounds.x);
pos.y = Mathf.Clamp(pos.y, minBounds.y, maxBounds.y);
p.transform.position = pos;
}
}
在 SimulationController.Update()
內,於標籤更新後呼叫 ClampParticles()
,保證所有粒子都不會跑出螢幕。
四、萬有引力向量可視化
需求:有時看不出粒子是否真的互相吸引,加入 debug 模式下的力向量繪製。
// GravitationalSystem.cs
[Header("調試繪製")]
public bool debugDrawForces = false;
public Color forceLineColor = Color.cyan;
public void ApplyForces()
{
var ps = particleManager.GetActiveParticles();
for (int i = 0; i < ps.Count; i++)
{
var rbA = ps[i].GetComponent<Rigidbody2D>();
for (int j = i+1; j < ps.Count; j++)
{
var rbB = ps[j].GetComponent<Rigidbody2D>();
Vector2 dir = rbB.position - rbA.position;
float d = dir.magnitude;
if (d < 0.01f) continue;
float mag = gravitationalConstant * (rbA.mass * rbB.mass) / (d*d);
Vector2 f = mag * dir.normalized;
rbA.AddForce(f);
rbB.AddForce(-f);
if (debugDrawForces)
{
Debug.DrawLine(rbA.position, rbA.position + f.normalized*0.5f, forceLineColor);
Debug.DrawLine(rbB.position, rbB.position - f.normalized*0.5f, forceLineColor);
}
}
}
}
將 gravitationalConstant
調高至 ~10~20,並在 Inspector 勾選 debugDrawForces
,即可在 Scene 視窗中看到力向量,確認萬有引力真實存在。
總結
今天重點更新涵蓋:
UI 三區塊化:左輸入、中世界、右控制
標籤動態顯示:只顯示前 N 筆、由 Toggle 開關
畫面邊界 Clamp:手動約束所有粒子不飛出螢幕
力向量可視化:Debug 模式下繪製萬有引力方向
Subscribe to my newsletter
Read articles from 郭俊鑫 directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
