using UnityEngine; using UnityEngine.UI; using TMPro; using System.Linq; public class StairDiagramController : MonoBehaviour { [Header("元件綁定")] public RectTransform 繪圖區_panel; public RectTransform 對應資訊區_panel; public GameObject 階梯Prefab; public GameObject 資訊列Prefab; // 預設放一個 TMP Text 或含文字的UI列 public GameObject 線段編號Prefab; [Header("參數設定")] [SerializeField] TMP_InputField 階梯階數_tb; [SerializeField] TextMeshProUGUI 總深_tb,總高_tb, 斜邊_tb, 長邊_tb, 推薦斜邊_tb, 推薦長邊_tb, 最小斜邊_tb, 最小長邊_tb; [SerializeField] TMP_InputField[] 高; [SerializeField] TMP_InputField[] 深; [SerializeField] Image 法規_bg,推薦_bg,最小_bg; [SerializeField] GameObject 簡易圖_pl,斜坡板參數_pl; int 線段編號 = 1; public void 返回參數(){ 簡易圖_pl.SetActive(false); 斜坡板參數_pl.SetActive(true); } public void 生成法規圖(string 畫圖種類){ 法規_bg.color=Color.white;推薦_bg.color=Color.white;最小_bg.color=Color.white; if (畫圖種類 == "法規"){ 法規_bg.color = new Color32(253, 228, 151, 255); } else if (畫圖種類 == "推薦"){ 推薦_bg.color = new Color32(253, 228, 151, 255); } else if (畫圖種類 == "最小"){ 最小_bg.color = new Color32(253, 228, 151, 255); } int 階梯層數 = int.Parse(階梯階數_tb.text); float shortSide = float.Parse(總高_tb.text); float longSide = float.Parse(總深_tb.text); if (shortSide == 0f || longSide == 0f) return; 簡易圖_pl.SetActive(true); 斜坡板參數_pl.SetActive(false); foreach (Transform child in 繪圖區_panel) Destroy(child.gameObject); foreach (Transform child in 對應資訊區_panel) Destroy(child.gameObject); 線段編號 = 1; float[] stepWidths = GetStepWidths(1); float[] stepHeights = GetStepHeights(1); float lastStepWidth = stepWidths[階梯層數 - 1]; double angle = 0; if (shortSide >= 20){ angle = 4.78; }else if(shortSide >=5 && shortSide < 20){ angle = 5.74; }else if(shortSide >=4 && shortSide < 5){ angle = 11.54; }else if(shortSide >=0 && shortSide < 3){ angle = 30; } 計算斜邊(angle, shortSide, longSide, stepWidths[階梯層數 - 1], 斜邊_tb, 長邊_tb); 計算斜邊(9.59, shortSide, longSide, stepWidths[階梯層數 - 1], 推薦斜邊_tb, 推薦長邊_tb); 計算斜邊(11.54, shortSide, longSide, stepWidths[階梯層數 - 1], 最小斜邊_tb, 最小長邊_tb); float hypotenuse = float.Parse(斜邊_tb.text); float hypotenuse2 = float.Parse(推薦斜邊_tb.text); float hypotenuse3 = float.Parse(最小斜邊_tb.text); float longuse = float.Parse(長邊_tb.text); float longuse2 = float.Parse(推薦長邊_tb.text); float longuse3 = float.Parse(最小長邊_tb.text); float otherSide = Mathf.Sqrt(hypotenuse * hypotenuse - shortSide * shortSide); float otherSide2 = Mathf.Sqrt(hypotenuse2 * hypotenuse2 - shortSide * shortSide); float otherSide3 = Mathf.Sqrt(hypotenuse3 * hypotenuse3 - shortSide * shortSide); float setWidthLast = GetStepWidths(1)[階梯層數 - 1]; float 倍數 = Mathf.Round((繪圖區_panel.rect.width - 100f) / (otherSide + setWidthLast) * 100f) / 100f; float bottomY = 100f; float pointA_x = 10f; Vector2 pointA = new Vector2(pointA_x, bottomY); Vector2 pointB = new Vector2(pointA.x + otherSide * 倍數, bottomY); Vector2 pointC = new Vector2(pointB.x, bottomY + shortSide * 倍數); Vector2 pointD = new Vector2(pointB.x - otherSide2 * 倍數, bottomY); Vector2 pointE = new Vector2(pointB.x - otherSide3 * 倍數, bottomY); if(畫圖種類=="法規"){ DrawLine(pointA, pointC, Color.blue, 畫圖種類+"斜坡板長度", hypotenuse); DrawLine(pointA, pointB, Color.black, "通道保留空間", longuse); }else if(畫圖種類=="推薦"){ DrawLine(pointD, pointC, Color.black, 畫圖種類+"斜坡板長度", hypotenuse); DrawLine(pointD, pointB, Color.black, "通道保留空間", longuse2); }else{//最小 DrawLine(pointE, pointC, Color.red, 畫圖種類+"斜坡板長度", hypotenuse); DrawLine(pointE, pointB, Color.black, "通道保留空間", longuse3); } float startX = pointA.x + otherSide * 倍數; for (int i = 0; i < 階梯層數 - 1; i++) startX -= stepWidths[i] * 倍數; float baseX; float baseOtherSide; if (畫圖種類 == "法規"){ baseX = pointA.x; baseOtherSide = otherSide; }else if (畫圖種類 == "推薦"){ baseX = pointD.x; baseOtherSide = otherSide2; }else{ // 最小 baseX = pointE.x; baseOtherSide = otherSide3; } for (int i = 0; i < 階梯層數 - 1; i++) { float leftX = startX + stepWidths.Take(i).Sum() * 倍數; float stairTopY = bottomY + stepHeights.Take(i + 1).Sum() * 倍數; Vector2 stairTop = new Vector2(leftX, stairTopY); // 法規線在對應 X 的 Y 高度 float slopeY = bottomY + (leftX - baseX) * (shortSide / baseOtherSide); Vector2 intersectPoint = new Vector2(leftX, slopeY); // ✅ 關鍵修改:統一與 VB 一樣公式 float 垂直距離 = Mathf.Round((slopeY - stairTopY) / 倍數 * 10f) / 10f; Vector2 mid = (stairTop + intersectPoint) / 2 + Vector2.up * 10; DrawLine(stairTop, intersectPoint, Color.yellow, "間距", 垂直距離); } float stairX = startX; float stairY = bottomY; for (int i = 0; i < 階梯層數; i++){ float w = stepWidths[i] * 倍數; float h = stepHeights[i] * 倍數; DrawStairBox(new Vector2(stairX, stairY), w, h); stairX += w; stairY += h; } } void DrawStairBox(Vector2 bottomLeft, float width, float height){ GameObject box = Instantiate(階梯Prefab, 繪圖區_panel); RectTransform rt = box.GetComponent(); rt.anchorMin = rt.anchorMax = rt.pivot = new Vector2(0, 0); // 左下角錨點 rt.anchoredPosition = bottomLeft; rt.sizeDelta = new Vector2(width, height); } void DrawLine(Vector2 start, Vector2 end, Color color, string 用途 = "", float 實際長度 = -1f){ GameObject line = new GameObject("UILine", typeof(Image)); line.transform.SetParent(繪圖區_panel, false); RectTransform rt = line.GetComponent(); Image image = line.GetComponent(); image.color = color; Vector2 direction = end - start; float length = direction.magnitude; float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg; rt.sizeDelta = new Vector2(length, 10f); rt.pivot = new Vector2(0, 0.5f); rt.anchorMin = new Vector2(0, 0); rt.anchorMax = new Vector2(0, 0); rt.anchoredPosition = start; rt.localRotation = Quaternion.Euler(0, 0, angle); // 加線段編號(畫面旁邊) GameObject label = Instantiate(線段編號Prefab, 繪圖區_panel); TextMeshProUGUI text = label.GetComponent(); text.text = $"L{線段編號}"; RectTransform textRT = label.GetComponent(); textRT.anchorMin = textRT.anchorMax = new Vector2(0, 0); textRT.pivot = new Vector2(0.5f, 0.5f); // 計算文字偏移(避免被線擋住) Vector2 mid = (start + end) / 2; Vector2 offset = Vector2.Perpendicular((end - start).normalized) * 15f; textRT.anchoredPosition = mid + offset; label.transform.SetAsLastSibling(); // 紀錄這條線的資訊到對應區域 if (string.IsNullOrEmpty(用途)) 用途 = "未指定"; if (實際長度 < 0f) 實際長度 = Vector2.Distance(start, end); // fallback 值 AddInfoRow($"L{線段編號}", 用途, 實際長度); 線段編號++; } void AddInfoRow(string 編號, string 用途, float 長度){ GameObject row = Instantiate(資訊列Prefab, 對應資訊區_panel); TextMeshProUGUI[] labels = row.GetComponentsInChildren(); labels[0].text = 編號 + " | "; labels[1].text = $"{用途} | {Mathf.Round(長度 * 10f) / 10f} cm"; } public float[] GetStepWidths(float multiplier){ float[] widths = new float[深.Length]; for (int i = 0; i < 深.Length; i++){ widths[i] = GetDepthValue(深[i], multiplier); } return widths; } public float[] GetStepHeights(float multiplier) { float[] heights = new float[高.Length]; for (int i = 0; i < 高.Length; i++){ heights[i] = GetHeightValue(高[i], multiplier); } return heights; } private float GetDepthValue(TMP_InputField inputField, float multiplier){ if (!string.IsNullOrWhiteSpace(inputField.text) && float.TryParse(inputField.text, out float value)){ return value * multiplier; } return 0f; } private float GetHeightValue(TMP_InputField inputField, float multiplier){ if (!string.IsNullOrWhiteSpace(inputField.text) && float.TryParse(inputField.text, out float value)){ return value * multiplier; } return 0f; } void 計算斜邊(double angle, double 短邊, double 長邊, double setWidth, TextMeshProUGUI 斜邊Text, TextMeshProUGUI 長邊Text){ double angleInRadians = angle * Mathf.Deg2Rad; double hypotenuse = 短邊 / Mathf.Sin((float)angleInRadians); double longSide = hypotenuse * Mathf.Cos((float)angleInRadians) - 長邊 + setWidth; 斜邊Text.text = System.Math.Round(hypotenuse, 1, System.MidpointRounding.AwayFromZero).ToString(); 長邊Text.text = System.Math.Round(longSide, 1, System.MidpointRounding.AwayFromZero).ToString(); } }