新材质和优化?

Signed-off-by: TRAfoer <lhf190@outlook.com>
This commit is contained in:
2025-11-22 22:55:31 +08:00
parent cc6d39418e
commit 117352182b
29 changed files with 17379 additions and 16842 deletions

View File

@@ -1,8 +1,8 @@
using System.Collections.Generic;
using System.Linq;
using Ichni.RhythmGame; // 假设 LeanPool 引用在这里或 Lean.Pool 命名空间
using Lean.Pool; // 引入 LeanPool
using TMPro;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Serialization;
namespace Ichni.Editor
@@ -10,364 +10,317 @@ namespace Ichni.Editor
[RequireComponent(typeof(MeshRenderer))]
public class EditorGrid : MonoBehaviour
{
[Tooltip("指定用于计算缩放的摄像机(若为空则使用 Camera.main")]
public GridController gridController;
[Header("Camera Settings")]
public Camera sceneCamera;
public float cameraDistance;
[Tooltip("指定网格所在的平面0 = XZ (y=0), 1 = XY (z=0), 2 = YZ (x=0)")]
[Header("Grid Configuration")]
[Tooltip("0 = XZ, 1 = XY, 2 = YZ")]
public int gridPlane = 0;
[Tooltip("网格基础缩放值,单位 1")]
public float baseScale = 1f;
[Tooltip("调整缩放的影响因子,建议值 15")]
public float scaleMultiplier = 1f;
[Tooltip("距离因子,用于计算对数缩放(例如距离大于该值时切换到下一级单位)")]
public float distanceFactor = 10f;
[Tooltip("位置文本更新频率(秒)")]
public float textUpdateFrequency = 0.1f;
[FormerlySerializedAs("showPositionText")]
[Header("Text Settings")]
public bool canShowPositionText;
public bool isShowingPositionText;
public Transform textContainer;
public GameObject positionTextPrefab;
// 内部缓存材质
[Tooltip("启用此项可让文字在屏幕上保持固定大小")]
public bool constantScreenSize = true;
[Tooltip("屏幕固定大小的基准系数")]
public float fixedTextSizeFactor => gridController != null ? gridController.fixedTextSizeFactor : 0.02f;
public float textUpdateFrequency = 0.1f;
// --- 运行时状态 ---
private Material gridMaterial;
public Dictionary<GameObject, Vector3> positionTexts = new Dictionary<GameObject, Vector3>();
private float cameraDistance;
public float logScale;
public float gridScale;
// 对象池相关
private Queue<GameObject> textPool = new Queue<GameObject>();
private const int POOL_SIZE = 50;
// 性能优化缓存
private float lastTextUpdateTime = 0f;
// --- 缓存与优化变量 ---
private float lastTextUpdateTime;
private Vector3 lastCameraPosition;
private float lastGridScale;
private Plane gridPlaneCache;
private Vector2 screenCenter;
public float logScale;
public float gridScale; // 1, 4, 16, 64...
// 使用 Vector2Int 作为 Key避免浮点数比较误差同时实现 O(1) 查找
// Key: 网格索引 (x index, z index), Value: 文本对象
private Dictionary<Vector2Int, GameObject> activeTexts = new Dictionary<Vector2Int, GameObject>();
// 避免 GC 的复用容器
private HashSet<Vector2Int> requiredIndices = new HashSet<Vector2Int>();
private List<Vector2Int> toRemoveIndices = new List<Vector2Int>();
void Start()
{
InitializeTextPool();
sceneCamera = EditorManager.instance.cameraManager.sceneCamera.sceneCamera;
// 实例化材质,避免修改共享材质
gridMaterial = GetComponent<MeshRenderer>().material;
// 同步网格平面的值到材质(方便 Shader 内部判断)
gridMaterial.SetFloat("_Plane", gridPlane);
// 设置 Shader 线宽
float lineWidthOf3840 = 2;
float screenWidth = Screen.width;
float lineWidth = lineWidthOf3840 * (screenWidth / 3840f);
float lineWidth = lineWidthOf3840 * (Screen.width / 3840f);
gridMaterial.SetFloat("_LineWidth", lineWidth);
// 预计算屏幕中心
screenCenter = new Vector2(Screen.width / 2f, Screen.height / 2f);
// 预计算网格平面
UpdateGridPlaneCache();
}
void Update()
{
sceneCamera = EditorManager.instance.cameraManager.currentCamera;
// 获取当前相机
if (EditorManager.instance?.cameraManager?.currentCamera != null)
sceneCamera = EditorManager.instance.cameraManager.currentCamera;
// 计算摄像机到网格平面的垂直距离
cameraDistance = 0f;
Vector3 camPos = sceneCamera.transform.position;
Vector3 gridPos = transform.position;
switch (gridPlane)
{
case 0: // XZ 平面:垂直方向为 Y
cameraDistance = Mathf.Abs(camPos.y - gridPos.y);
break;
case 1: // XY 平面:垂直方向为 Z
cameraDistance = Mathf.Abs(camPos.z - gridPos.z);
break;
case 2: // YZ 平面:垂直方向为 X
cameraDistance = Mathf.Abs(camPos.x - gridPos.x);
break;
}
if (sceneCamera == null) return;
// 利用对数函数计算缩放等级:距离越远,网格越大
logScale = Mathf.Floor(Mathf.Log(cameraDistance / distanceFactor + 1, 4));
gridScale = baseScale * Mathf.Pow(4, logScale) * scaleMultiplier;
gridMaterial.SetFloat("_GridScale", 1 / gridScale);
gridMaterial.SetFloat("_DisappearEndDistance", 100 * gridScale);
CalculateGridScale();
UpdateShaderParams();
if (canShowPositionText && isShowingPositionText)
{
// 添加更新频率控制
// 检查是否需要更新网格位置(降低频率)
bool shouldUpdate = Time.time - lastTextUpdateTime >= textUpdateFrequency ||
Vector3.Distance(sceneCamera.transform.position, lastCameraPosition) > gridScale * 0.5f ||
Mathf.Abs(gridScale - lastGridScale) > 0.1f;
Vector3.Distance(sceneCamera.transform.position, lastCameraPosition) > gridScale * 0.5f ||
Mathf.Abs(gridScale - lastGridScale) > 0.1f;
if (shouldUpdate)
{
GetPoints();
UpdateTextPositions();
lastTextUpdateTime = Time.time;
lastCameraPosition = sceneCamera.transform.position;
lastGridScale = gridScale;
}
else
{
// 只更新文本朝向(性能较轻)
UpdateTextOrientations();
}
}
else if (isShowingPositionText && !canShowPositionText)
else if (activeTexts.Count > 0)
{
ClearAllTexts();
isShowingPositionText = false;
}
}
#region
private void InitializeTextPool()
// 使用 LateUpdate 处理文字的朝向和缩放,防止画面抖动
void LateUpdate()
{
for (int i = 0; i < POOL_SIZE; i++)
{
GameObject textObj = Instantiate(positionTextPrefab, textContainer);
textObj.transform.localScale = Vector3.zero; // 重置缩放
textPool.Enqueue(textObj);
}
if (!canShowPositionText || !isShowingPositionText || activeTexts.Count == 0) return;
UpdateTextVisuals();
}
private GameObject GetTextFromPool()
private void CalculateGridScale()
{
if (textPool.Count > 0)
Vector3 camPos = sceneCamera.transform.position;
Vector3 gridPos = transform.position;
cameraDistance = gridPlane switch
{
GameObject textObj = textPool.Dequeue();
textObj.transform.localScale = Vector3.one; // 重置缩放
return textObj;
0 => Mathf.Abs(camPos.y - gridPos.y), // XZ
1 => Mathf.Abs(camPos.z - gridPos.z), // XY
2 => Mathf.Abs(camPos.x - gridPos.x), // YZ
_ => cameraDistance
};
logScale = Mathf.Floor(Mathf.Log(cameraDistance / distanceFactor + 1, 4));
gridScale = baseScale * Mathf.Pow(4, logScale) * scaleMultiplier;
}
private void UpdateShaderParams()
{
gridMaterial.SetFloat("_GridScale", 1 / gridScale);
gridMaterial.SetFloat("_DisappearEndDistance", 100 * gridScale);
}
#region ()
private void UpdateTextPositions()
{
// 每次都获取最新的屏幕中心,保证中心点始终正确
screenCenter = new Vector2(Screen.width / 2f, Screen.height / 2f);
// 1. 射线检测找中心点
Ray sceneCameraRay = sceneCamera.ScreenPointToRay(screenCenter);
if (!gridPlaneCache.Raycast(sceneCameraRay, out float enter))
{
ClearAllTexts();
return;
}
// 如果池子空了,动态创建一个(应该很少发生)
GameObject newTextObj = Instantiate(positionTextPrefab, textContainer);
return newTextObj;
}
Vector3 centerPoint = sceneCameraRay.GetPoint(enter);
private void ReturnTextToPool(GameObject textObj)
{
textObj.transform.localScale = Vector3.zero; // 重置缩放;
textPool.Enqueue(textObj);
}
private void UpdateTextOrientations()
{
foreach (var textObj in positionTexts.Keys)
// 距离剔除
if (Vector3.Distance(sceneCamera.transform.position, centerPoint) > 200f) // 增加最大距离
{
if (textObj != null && textObj.activeInHierarchy)
ClearAllTexts();
return;
}
float radius = gridScale * 12f; // 可视范围半径
float step = gridScale * 4f; // 网格步长
// 2. 计算当前需要的网格索引范围
requiredIndices.Clear();
// 根据不同的平面,取不同的轴进行计算
float xLocal = 0, yLocal = 0;
switch (gridPlane)
{
case 0: xLocal = centerPoint.x; yLocal = centerPoint.z; break; // XZ
case 1: xLocal = centerPoint.x; yLocal = centerPoint.y; break; // XY
case 2: xLocal = centerPoint.y; yLocal = centerPoint.z; break; // YZ (注意轴向)
}
int minX = Mathf.FloorToInt((xLocal - radius) / step);
int maxX = Mathf.CeilToInt((xLocal + radius) / step);
int minY = Mathf.FloorToInt((yLocal - radius) / step);
int maxY = Mathf.CeilToInt((yLocal + radius) / step);
for (int x = minX; x <= maxX; x++)
{
for (int y = minY; y <= maxY; y++)
{
Vector3 direction = sceneCamera.transform.position - textObj.transform.position;
textObj.transform.forward = -direction.normalized;
requiredIndices.Add(new Vector2Int(x, y));
}
}
// 3. 移除不再需要的 (差集)
toRemoveIndices.Clear();
foreach (var kvp in activeTexts)
{
if (!requiredIndices.Contains(kvp.Key))
{
toRemoveIndices.Add(kvp.Key);
}
}
foreach (var key in toRemoveIndices)
{
LeanPool.Despawn(activeTexts[key]);
activeTexts.Remove(key);
}
// 4. 生成新增的
foreach (var index in requiredIndices)
{
if (!activeTexts.ContainsKey(index))
{
SpawnTextAt(index, step);
}
}
}
private void ClearAllTexts()
private void SpawnTextAt(Vector2Int index, float step)
{
foreach (var textObj in positionTexts.Keys)
GameObject textObj = LeanPool.Spawn(positionTextPrefab, textContainer);
// 计算世界坐标
Vector3 worldPos = Vector3.zero;
float xPos = index.x * step;
float yPos = index.y * step;
// 偏移量:稍微偏离网格线
float offset = gridScale / 6f;
switch (gridPlane)
{
if (textObj != null)
case 0: // XZ
worldPos = new Vector3(xPos + offset, transform.position.y, yPos + offset);
break;
case 1: // XY
worldPos = new Vector3(xPos + offset, yPos + offset, transform.position.z);
break;
case 2: // YZ -> 这里 index.x 对应 Y轴, index.y 对应 Z轴 (视具体需求调整)
worldPos = new Vector3(transform.position.x, xPos + offset, yPos + offset);
break;
}
textObj.transform.position = worldPos;
// 设置文本内容
TMP_Text tmp = textObj.GetComponent<TMP_Text>();
if (tmp) tmp.text = $"({Mathf.RoundToInt(xPos)}, {Mathf.RoundToInt(yPos)})";
activeTexts.Add(index, textObj);
}
/// <summary>
/// 处理朝向摄像机 和 动态大小
/// </summary>
private void UpdateTextVisuals()
{
Vector3 camPos = sceneCamera.transform.position;
foreach (var kvp in activeTexts)
{
GameObject obj = kvp.Value;
if (obj == null) continue;
Transform t = obj.transform;
float dist = Vector3.Distance(camPos, t.position);
// 1. 朝向摄像机
t.LookAt(t.position + sceneCamera.transform.rotation * Vector3.forward,
sceneCamera.transform.rotation * Vector3.up);
// 2. 动态计算大小
if (constantScreenSize)
{
ReturnTextToPool(textObj);
// 简单的透视投影公式:大小 = 距离 * 系数
// 这样距离越远,物体本身越大,但在屏幕上看起来一样大
float finalScale = dist * fixedTextSizeFactor;
t.localScale = new Vector3(finalScale, finalScale, finalScale);
}
else
{
// 原有的逻辑:随网格等级缩放
float staticScale = gridScale * 1.5f;
t.localScale = new Vector3(staticScale, staticScale, staticScale);
}
}
positionTexts.Clear();
}
public void ClearAllTexts()
{
foreach (var obj in activeTexts.Values)
{
if (obj != null) LeanPool.Despawn(obj);
}
activeTexts.Clear();
}
#endregion
#region
void GetPoints()
{
// 使用平面射线检测替代 Physics.Raycast性能更好
Ray sceneCameraRay = sceneCamera.ScreenPointToRay(screenCenter);
if (gridPlaneCache.Raycast(sceneCameraRay, out float enter))
{
Vector3 point = sceneCameraRay.GetPoint(enter);
// 添加距离检查,太远就不显示文本
float distanceToCamera = Vector3.Distance(sceneCamera.transform.position, point);
if (distanceToCamera > 50f)
{
ClearAllTexts();
return;
}
float radius = gridScale * 16f;
float step = gridScale * 4f;
// 计算可见区域边界
Vector2Int minMaxX = CalculateBounds(point.x, radius, step);
Vector2Int minMaxZ = CalculateBounds(point.z, radius, step);
// 使用 HashSet 来跟踪需要显示的位置(避免重复计算)
HashSet<Vector3> requiredPositions = new HashSet<Vector3>();
for (int x = minMaxX.x; x <= minMaxX.y; x++)
{
for (int z = minMaxZ.x; z <= minMaxZ.y; z++)
{
Vector3 position = new Vector3(x * step, 0, z * step);
requiredPositions.Add(position);
}
}
UpdateTextDisplay(requiredPositions);
}
}
// 辅助方法:计算边界(避免重复的数学运算)
private Vector2Int CalculateBounds(float center, float radius, float step)
{
int min = Mathf.FloorToInt((center - radius) / step);
int max = Mathf.CeilToInt((center + radius) / step);
return new Vector2Int(min, max);
}
// 更新网格平面缓存
private void UpdateGridPlaneCache()
{
gridPlaneCache = gridPlane switch
{
0 => new Plane(Vector3.up, transform.position), // XZ
1 => new Plane(Vector3.forward, transform.position), // XY
2 => new Plane(Vector3.right, transform.position), // YZ
0 => new Plane(Vector3.up, transform.position),
1 => new Plane(Vector3.forward, transform.position),
2 => new Plane(Vector3.right, transform.position),
_ => new Plane(Vector3.up, transform.position)
};
}
private void UpdateTextDisplay(HashSet<Vector3> requiredPositions)
{
// 第一步:移除不再需要的位置文本
List<GameObject> toRemove = new List<GameObject>();
foreach (var kvp in positionTexts)
{
if (!requiredPositions.Contains(kvp.Value))
{
toRemove.Add(kvp.Key);
}
}
foreach (GameObject textObj in toRemove)
{
positionTexts.Remove(textObj);
ReturnTextToPool(textObj);
}
// 第二步:添加新位置文本
foreach (Vector3 position in requiredPositions)
{
if (!ContainsPosition(position))
{
GameObject textObj = GetTextFromPool();
SetupTextObject(textObj, position);
positionTexts[textObj] = position;
}
}
}
private bool ContainsPosition(Vector3 position)
{
foreach (var pos in positionTexts.Values)
{
if (Vector3.Distance(pos, position) < 0.1f)
return true;
}
return false;
}
private void SetupTextObject(GameObject textObj, Vector3 position)
{
textObj.transform.position = position + new Vector3(gridScale / 6, 0, gridScale / 12);
float scaleFactor = gridScale * 1.5f;
textObj.transform.localScale = new Vector3(scaleFactor, scaleFactor, scaleFactor);
// 设置文本内容
TMP_Text tmpText = textObj.GetComponent<TMP_Text>();
if (tmpText != null)
{
tmpText.text = $"({Mathf.RoundToInt(position.x)}, {Mathf.RoundToInt(position.z)})";
}
// 初始朝向
Vector3 direction = sceneCamera.transform.position - textObj.transform.position;
textObj.transform.forward = -direction.normalized;
}
#endregion
#region
public void ShowPositionText()
{
canShowPositionText = true;
isShowingPositionText = true;
lastTextUpdateTime = 0; // 强制下一次更新
}
public void HidePositionText()
{
canShowPositionText = false;
ClearAllTexts();
}
public void SetGridPlane(int planeIndex)
{
if (planeIndex >= 0 && planeIndex <= 2)
{
gridPlane = planeIndex;
gridMaterial.SetFloat("_Plane", gridPlane);
UpdateGridPlaneCache();
}
gridPlane = planeIndex;
gridMaterial.SetFloat("_Plane", gridPlane);
UpdateGridPlaneCache();
// 切换平面时强制刷新文字
ClearAllTexts();
lastTextUpdateTime = 0;
}
#endregion
void OnDestroy()
{
// 清理资源
if (gridMaterial != null)
{
if (Application.isEditor)
DestroyImmediate(gridMaterial);
else
Destroy(gridMaterial);
if (Application.isEditor) DestroyImmediate(gridMaterial);
else Destroy(gridMaterial);
}
ClearAllTexts();
// 清理对象池
foreach (var textObj in textPool)
{
if (textObj != null)
{
if (Application.isEditor)
DestroyImmediate(textObj);
else
Destroy(textObj);
}
}
textPool.Clear();
// 场景销毁时LeanPool 通常会自动清理,但手动清理引用是个好习惯
activeTexts.Clear();
}
}
}

View File

@@ -1,9 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Ichni.RhythmGame;
using Ichni.RhythmGame.Beatmap;
using Lean.Pool;
using UnityEngine;
namespace Ichni.Editor
@@ -16,59 +13,64 @@ namespace Ichni.Editor
public EditorGrid xPlaneGrid;
public EditorGrid zPlaneGrid;
public bool yPlaneEnabled;
public bool xPlaneEnabled;
public bool zPlaneEnabled;
[Header("State")]
public bool yPlaneEnabled = true;
public bool xPlaneEnabled = false;
public bool zPlaneEnabled = false;
public bool isYPlaneShowingPositionText = true;
public float fixedTextSizeFactor = 0.1f;
public bool isYPlaneShowingPositionText;
private void Start()
{
yPlaneEnabled = true;
xPlaneEnabled = false;
zPlaneEnabled = false;
isYPlaneShowingPositionText = true;
RefreshPlanes();
}
public void SetUpInspector()
{
IHaveInspection inspector = EditorManager.instance.uiManager.inspector;
var container = inspector.GenerateContainer("Grid Controller");
//网格设置
var gridSettings = container.GenerateSubcontainer(3);
var yPlaneToggle =
inspector.GenerateToggle(this, gridSettings, "Y Plane", nameof(yPlaneEnabled))
.AddListenerFunction(RefreshPlanes);
var xPlaneToggle =
inspector.GenerateToggle(this, gridSettings, "X Plane", nameof(xPlaneEnabled))
.AddListenerFunction(RefreshPlanes);
var zPlaneToggle =
inspector.GenerateToggle(this, gridSettings, "Z Plane", nameof(zPlaneEnabled))
inspector.GenerateToggle(this, gridSettings, "Y Plane (XZ)", nameof(yPlaneEnabled))
.AddListenerFunction(RefreshPlanes);
var yPlaneShowPositionToggle =
inspector.GenerateToggle(this, gridSettings, "Show Y Plane Position", nameof(isYPlaneShowingPositionText))
.AddListenerFunction(RefreshPlanes);
inspector.GenerateToggle(this, gridSettings, "X Plane (YZ)", nameof(xPlaneEnabled))
.AddListenerFunction(RefreshPlanes);
inspector.GenerateToggle(this, gridSettings, "Z Plane (XY)", nameof(zPlaneEnabled))
.AddListenerFunction(RefreshPlanes);
inspector.GenerateToggle(this, gridSettings, "Show Y Plane Pos", nameof(isYPlaneShowingPositionText))
.AddListenerFunction(RefreshPlanes);
inspector.GenerateInputField(this, gridSettings, "Fixed Text Size Factor", nameof(fixedTextSizeFactor))
.AddListenerFunction(RefreshPlanes);
}
private void RefreshPlanes()
{
yPlaneGrid.gameObject.SetActive(yPlaneEnabled);
xPlaneGrid.gameObject.SetActive(xPlaneEnabled);
zPlaneGrid.gameObject.SetActive(zPlaneEnabled);
yPlaneGrid.isShowingPositionText = isYPlaneShowingPositionText;
SetGridState(yPlaneGrid, yPlaneEnabled, isYPlaneShowingPositionText);
SetGridState(xPlaneGrid, xPlaneEnabled, false); // 假设其他平面暂时不显示文字
SetGridState(zPlaneGrid, zPlaneEnabled, false);
}
if (!yPlaneGrid.isShowingPositionText)
private void SetGridState(EditorGrid grid, bool active, bool showText)
{
if (grid == null) return;
grid.gameObject.SetActive(active);
if (active)
{
foreach (KeyValuePair<GameObject, Vector3> positionText in yPlaneGrid.positionTexts)
{
LeanPool.Despawn(positionText.Key);
}
grid.canShowPositionText = showText;
grid.isShowingPositionText = showText;
yPlaneGrid.positionTexts.Clear();
// 如果关闭了文字显示,立刻清理
if (!showText)
{
grid.ClearAllTexts();
}
}
}
}