我不理解,这check那check就是出不来

Signed-off-by: TRAfoer <lhf190@outlook.com>
This commit is contained in:
2025-10-03 18:21:40 +08:00
parent b9c23e3af8
commit ba1824d5d6
25 changed files with 197808 additions and 181369 deletions

View File

@@ -14,7 +14,7 @@ namespace Ichni.RhythmGame
{
public partial class Hold : NoteBase
{
public static List<Hold> holdingHoldList = new();
//public static List<Hold> holdingHoldList = new();
public float holdEndTime;
public float holdingTime;
@@ -65,7 +65,6 @@ namespace Ichni.RhythmGame
{
EditorManager.instance.projectManager.notePrefabManager.LoadNotePrefab(hold, GetNoteTypeName(hold) + "_Prefab");
}
return hold;
}
@@ -157,17 +156,24 @@ namespace Ichni.RhythmGame
public partial class Hold
{
protected override void Update()
// 添加类级别缓存
private static HashSet<Hold> holdingHoldSet = new HashSet<Hold>(); // 替换List为HashSet
public override void UpdateNote()
{
if (Keyboard.current.hKey.wasPressedThisFrame)
{
foreach (KeyValuePair<string, List<EffectBase>> effect in noteVisual.effectSubmodule.effectCollection)
// 优化避免字典枚举和Lambda
var effectCollection = noteVisual.effectSubmodule.effectCollection;
foreach (var pair in effectCollection)
{
effect.Value.ForEach(x => x.Disrupt());
var effects = pair.Value;
for (int i = 0; i < effects.Count; i++)
{
effects[i].Disrupt();
}
}
}
if (holdEndTime < exactJudgeTime)
{
LogWindow.Log("Hold end time is earlier than exact judge time.", Color.red);
@@ -192,7 +198,6 @@ namespace Ichni.RhythmGame
{
isHolding = false;
isFinalJudged = true;
//noteAudioSubmodule?.PlayNoteJudgeAudios(EditorManager.instance.currentJudgeType);//有待商榷
}
}
@@ -206,56 +211,74 @@ namespace Ichni.RhythmGame
}
}
// 优化避免LINQ查询
if (noteJudgeSubmodule != null && !EditorManager.instance.cameraManager.isSceneCameraActive)
{
foreach (NoteJudgeUnit unit in noteJudgeSubmodule.judgeUnitList.Where(unit => unit.isShowingJudge))
var judgeUnits = noteJudgeSubmodule.judgeUnitList;
for (int i = 0; i < judgeUnits.Count; i++)
{
unit.UpdateJudge();
var unit = judgeUnits[i];
if (unit.isShowingJudge)
{
unit.UpdateJudge();
}
}
}
if (noteVisual != null)
{
noteVisual.effectSubmodule.effectCollection["Generate"].ForEach(e => e.UpdateEffect(exactJudgeTime));
noteVisual.effectSubmodule.effectCollection["StartHold"].ForEach(e => e.UpdateEffect(exactJudgeTime));
noteVisual.effectSubmodule.effectCollection["Holding"].ForEach(e => e.UpdateEffect(exactJudgeTime));
// 优化:缓存常用引用,避免重复字典查找
var effectSubmodule = noteVisual.effectSubmodule;
var effectColl = effectSubmodule.effectCollection;
// 优化手动遍历避免Lambda
UpdateEffectList(effectColl["Generate"], exactJudgeTime);
UpdateEffectList(effectColl["StartHold"], exactJudgeTime);
UpdateEffectList(effectColl["Holding"], exactJudgeTime);
UpdateEffectList(effectColl["GeneralJudge"], holdEndTime);
UpdateEffectList(effectColl["AfterJudge"], holdEndTime);
noteVisual.effectSubmodule.effectCollection["GeneralJudge"].ForEach(e => e.UpdateEffect(holdEndTime));
switch (EditorManager.instance.currentJudgeType)
// 优化switch语句
var judgeType = EditorManager.instance.currentJudgeType;
switch (judgeType)
{
case NoteJudgeType.Perfect:
noteVisual.effectSubmodule.effectCollection["Perfect"].ForEach(e => e.UpdateEffect(holdEndTime));
UpdateEffectList(effectColl["Perfect"], holdEndTime);
break;
case NoteJudgeType.Good:
noteVisual.effectSubmodule.effectCollection["Good"].ForEach(e => e.UpdateEffect(holdEndTime));
UpdateEffectList(effectColl["Good"], holdEndTime);
break;
case NoteJudgeType.Bad:
noteVisual.effectSubmodule.effectCollection["Bad"].ForEach(e => e.UpdateEffect(holdEndTime));
UpdateEffectList(effectColl["Bad"], holdEndTime);
break;
case NoteJudgeType.Miss:
noteVisual.effectSubmodule.effectCollection["Miss"].ForEach(e => e.UpdateEffect(holdEndTime));
UpdateEffectList(effectColl["Miss"], holdEndTime);
break;
}
noteVisual.effectSubmodule.effectCollection["AfterJudge"].ForEach(e => e.UpdateEffect(holdEndTime));
if (EditorManager.instance.cameraManager.haveGameCamera)
{
noteScreenPosition = EditorManager.instance.cameraManager.gameCamera.gameCamera.WorldToScreenPoint(noteVisual.noteVisualPosition);
}
}
// 优化持有列表的添加和移除
// 优化使用HashSet避免Contains的GC
if (isHolding)
{
if (!holdingHoldList.Contains(this))
holdingHoldList.Add(this);
holdingHoldSet.Add(this);
}
else
{
if (holdingHoldList.Contains(this))
holdingHoldList.Remove(this);
holdingHoldSet.Remove(this);
}
}
// 辅助方法避免Lambda产生的GC
private void UpdateEffectList(List<EffectBase> effects, float time)
{
for (int i = 0; i < effects.Count; i++)
{
effects[i].UpdateEffect(time);
}
}

View File

@@ -22,6 +22,7 @@ namespace Ichni.RhythmGame
[Title("NoteVisual")]
public NoteVisualBase noteVisual;
public float NoteAppearTime => noteVisual ? noteVisual.NoteAppearTime : -1;
[Title("Submodules")]
public TimeDurationSubmodule timeDurationSubmodule { get; set; }
@@ -33,9 +34,22 @@ namespace Ichni.RhythmGame
[FormerlySerializedAs("isJudged")] public bool isFirstJudged;
public override int HierarchyPriority => -10;
public override void Initialize(string name, Guid elementGuid, List<string> tags, bool isFirstGenerated, GameElement parentElement)
{
base.Initialize(name, elementGuid, tags, isFirstGenerated, parentElement);
NoteManager.instance.AddNote(this);
}
public override void AfterInitialize()
{
base.AfterInitialize();
}
/// <summary>
/// 在MovableTrack上更新Note的位置注意HoldNote需要重写这个方法
/// </summary>
public virtual void UpdateNoteInMovableTrack()
{
TrackTimeSubmoduleMovable trackTimeSubmoduleMovable = track.trackTimeSubmodule as TrackTimeSubmoduleMovable;
@@ -75,7 +89,7 @@ namespace Ichni.RhythmGame
foreach (SampleWindow i in SampleWindow.instances.Where(i => i.gameElement)) i.OnceSpawnNote();
}
protected virtual void Update()
public virtual void UpdateNote()
{
var editor = EditorManager.instance;
var cameraManager = editor.cameraManager;
@@ -91,13 +105,13 @@ namespace Ichni.RhythmGame
if (isFirstJudged && songTime < exactJudgeTime)
{
isFirstJudged = false;
noteVisual.GetComponent<Collider>().enabled = !isFirstJudged;
if (noteVisual != null) noteVisual.GetComponent<Collider>().enabled = !isFirstJudged;
}
else if (!isFirstJudged && songTime >= exactJudgeTime)
{
noteAudioSubmodule?.PlayNoteJudgeAudios(editor.currentJudgeType);
isFirstJudged = true;
noteVisual.GetComponent<Collider>().enabled = !isFirstJudged;
if (noteVisual != null) noteVisual.GetComponent<Collider>().enabled = !isFirstJudged;
}
// 判定单元更新
@@ -235,6 +249,11 @@ namespace Ichni.RhythmGame
foreach (SampleWindow i in SampleWindow.instances.Where(i => i.gameElement)) i.OnceSpawnNote();
}
NoteManager.instance.RemoveNote(this);
if (noteVisual != null)
{
noteVisual.OnDelete();
}
}
public int CompareTo(NoteBase other)

View File

@@ -1,7 +1,9 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Ichni.Editor;
using UniRx;
using UnityEngine;
using UnityEngine.Serialization;
@@ -21,7 +23,10 @@ namespace Ichni.RhythmGame
public List<GameObject> effectPrefabList;
public virtual Vector3 noteVisualPosition => noteMain.transform.position;
public NoteGenerateEffect generateEffect;
public float NoteAppearTime => generateEffect is null ? note.exactJudgeTime : note.exactJudgeTime - generateEffect.generateTime;
public EffectSubmodule effectSubmodule { get; set; }
public SelectSubmodule selectSubmodule { get; set; }
@@ -34,8 +39,19 @@ namespace Ichni.RhythmGame
noteVisual.isHighlighted = isHighlighted;
noteVisual.SetHighlight();
noteVisual.SetEditorSubmodules();
Observable.NextFrame().Subscribe(_ =>
{
noteVisual.AfterInitialize();
});
return noteVisual;
}
public override void AfterInitialize()
{
base.AfterInitialize();
generateEffect ??= effectSubmodule.effectCollection["Generate"].First(i => i is NoteGenerateEffect) as NoteGenerateEffect;
if (generateEffect == null) throw new Exception("NoteVisual必须有一个生成特效。");
}
public override void SetDefaultSubmodules()
{
@@ -48,7 +64,7 @@ namespace Ichni.RhythmGame
{
selectSubmodule ??= new SelectSubmodule(this, note);
}
public override void SetUpInspector()
{
base.SetUpInspector();
@@ -63,7 +79,7 @@ namespace Ichni.RhythmGame
inspector.GenerateToggle(this, settingsSubcontainer, "Highlight", nameof(isHighlighted))
.AddListenerFunction(SetHighlight);
}
public virtual void Recover()
{

View File

@@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using Lean.Pool;
using TMPro;
using UnityEngine;
using UnityEngine.InputSystem;
@@ -19,28 +18,47 @@ namespace Ichni.Editor
[Tooltip("指定网格所在的平面0 = XZ (y=0), 1 = XY (z=0), 2 = YZ (x=0)")]
public int gridPlane = 0;
[Tooltip("网格基础缩放值,单位 1")] public float baseScale = 1f;
[Tooltip("网格基础缩放值,单位 1")]
public float baseScale = 1f;
[Tooltip("调整缩放的影响因子,建议值 15")] public float scaleMultiplier = 1f;
[Tooltip("调整缩放的影响因子,建议值 15")]
public float scaleMultiplier = 1f;
[Tooltip("距离因子,用于计算对数缩放(例如距离大于该值时切换到下一级单位)")]
public float distanceFactor = 10f;
[Tooltip("位置文本更新频率(秒)")]
public float textUpdateFrequency = 0.1f;
[FormerlySerializedAs("showPositionText")]
public bool canShowPositionText;
public bool isShowingPositionText;
public Transform textContainer;
public GameObject positionTextPrefab;
// 内部缓存材质
private Material gridMaterial;
public Dictionary<GameObject, Vector3> positionTexts = new Dictionary<GameObject, Vector3>();
// 对象池相关
private Queue<GameObject> textPool = new Queue<GameObject>();
private const int POOL_SIZE = 50;
// 性能优化缓存
private float lastTextUpdateTime = 0f;
private Vector3 lastCameraPosition;
private float lastGridScale;
private Plane gridPlaneCache;
private Vector2 screenCenter;
public float logScale;
public float gridScale; // 1, 4, 16, 64...
[FormerlySerializedAs("showPositionText")] public bool canShowPositionText;
public bool isShowingPositionText;
public Transform textContainer;
public GameObject positionTextPrefab;
public Dictionary<GameObject, Vector3> positionTexts;
void Start()
{
positionTexts = new Dictionary<GameObject, Vector3>();
InitializeTextPool();
sceneCamera = EditorManager.instance.cameraManager.sceneCamera.sceneCamera;
// 实例化材质,避免修改共享材质
@@ -52,6 +70,12 @@ namespace Ichni.Editor
float screenWidth = Screen.width;
float lineWidth = lineWidthOf3840 * (screenWidth / 3840f);
gridMaterial.SetFloat("_LineWidth", lineWidth);
// 预计算屏幕中心
screenCenter = new Vector2(Screen.width / 2f, Screen.height / 2f);
// 预计算网格平面
UpdateGridPlaneCache();
}
void Update()
@@ -84,84 +108,266 @@ namespace Ichni.Editor
if (canShowPositionText && isShowingPositionText)
{
GetPoints();
// 添加更新频率控制
bool shouldUpdate = Time.time - lastTextUpdateTime >= textUpdateFrequency ||
Vector3.Distance(sceneCamera.transform.position, lastCameraPosition) > gridScale * 0.5f ||
Mathf.Abs(gridScale - lastGridScale) > 0.1f;
foreach (KeyValuePair<GameObject, Vector3> positionText in positionTexts)
if (shouldUpdate)
{
positionText.Key.transform.position = positionText.Value + new Vector3(gridScale / 6, 0, gridScale / 12);
float scaleFactor = gridScale * 1.5f;
positionText.Key.transform.localScale = new Vector3(scaleFactor, scaleFactor, scaleFactor);
Vector3 direction = EditorManager.instance.cameraManager.currentCamera.transform.position - positionText.Key.transform.position;
positionText.Key.transform.forward = -direction.normalized;
GetPoints();
lastTextUpdateTime = Time.time;
lastCameraPosition = sceneCamera.transform.position;
lastGridScale = gridScale;
}
else
{
// 只更新文本朝向(性能较轻)
UpdateTextOrientations();
}
}
else if (isShowingPositionText && !canShowPositionText)
{
ClearAllTexts();
isShowingPositionText = false;
}
}
#region
private void InitializeTextPool()
{
for (int i = 0; i < POOL_SIZE; i++)
{
GameObject textObj = Instantiate(positionTextPrefab, textContainer);
textObj.SetActive(false);
textPool.Enqueue(textObj);
}
}
private GameObject GetTextFromPool()
{
if (textPool.Count > 0)
{
GameObject textObj = textPool.Dequeue();
textObj.SetActive(true);
return textObj;
}
// 如果池子空了,动态创建一个(应该很少发生)
GameObject newTextObj = Instantiate(positionTextPrefab, textContainer);
return newTextObj;
}
private void ReturnTextToPool(GameObject textObj)
{
textObj.SetActive(false);
textPool.Enqueue(textObj);
}
private void UpdateTextOrientations()
{
foreach (var textObj in positionTexts.Keys)
{
if (textObj != null && textObj.activeInHierarchy)
{
Vector3 direction = sceneCamera.transform.position - textObj.transform.position;
textObj.transform.forward = -direction.normalized;
}
}
}
private void ClearAllTexts()
{
foreach (var textObj in positionTexts.Keys)
{
if (textObj != null)
{
ReturnTextToPool(textObj);
}
}
positionTexts.Clear();
}
#endregion
#region
void GetPoints()
{
Ray sceneCameraRay = sceneCamera.ScreenPointToRay(new Vector2(Screen.width / 2f, Screen.height / 2f));
if (Physics.Raycast(sceneCameraRay, out RaycastHit sceneCameraHit, float.MaxValue, LayerMask.GetMask("Grid")))
// 使用平面射线检测替代 Physics.Raycast性能更好
Ray sceneCameraRay = sceneCamera.ScreenPointToRay(screenCenter);
if (gridPlaneCache.Raycast(sceneCameraRay, out float enter))
{
if (sceneCameraHit.collider.gameObject == gameObject)
Vector3 point = sceneCameraRay.GetPoint(enter);
// 添加距离检查,太远就不显示文本
float distanceToCamera = Vector3.Distance(sceneCamera.transform.position, point);
if (distanceToCamera > 50f)
{
Vector3 point = sceneCameraHit.point;
ClearAllTexts();
return;
}
float radius = gridScale * 16f;
float step = gridScale * 4f;
float radius = gridScale * 16f;
float step = gridScale * 4f;
float minX = point.x - radius;
float maxX = point.x + radius;
float minZ = point.z - radius;
float maxZ = point.z + radius;
// 计算可见区域边界
Vector2Int minMaxX = CalculateBounds(point.x, radius, step);
Vector2Int minMaxZ = CalculateBounds(point.z, radius, step);
// 对于 X 与 Z 方向,根据网格间距取整,确保从整点开始
minX = Mathf.Floor(minX / step) * step;
maxX = Mathf.Ceil(maxX / step) * step;
minZ = Mathf.Floor(minZ / step) * step;
maxZ = Mathf.Ceil(maxZ / step) * step;
// 使用 HashSet 来跟踪需要显示的位置(避免重复计算)
HashSet<Vector3> requiredPositions = new HashSet<Vector3>();
List<Vector3> newPositions = new List<Vector3>();
// 添加距离检测逻辑
bool withinDistance = Vector3.Distance(sceneCamera.transform.position, point) <= 50f;
for (float x = minX; x <= maxX; x += step)
for (int x = minMaxX.x; x <= minMaxX.y; x++)
{
for (int z = minMaxZ.x; z <= minMaxZ.y; z++)
{
for (float z = minZ; z <= maxZ; z += step)
{
Vector3 position = new Vector3(x, 0, z);
if (withinDistance && !positionTexts.ContainsValue(position))
{
GameObject posText = LeanPool.Spawn(positionTextPrefab);
posText.transform.position = position + new Vector3(gridScale / 8, 0, gridScale / 16);
posText.transform.forward = -transform.up;
posText.GetComponent<TMP_Text>().text = $"({Mathf.RoundToInt(position.x)}, {Mathf.RoundToInt(position.z)})";
posText.transform.SetParent(textContainer);
positionTexts.Add(posText, position);
}
newPositions.Add(new Vector3(x, 0, z));
}
Vector3 position = new Vector3(x * step, 0, z * step);
requiredPositions.Add(position);
}
}
List<GameObject> toRemove = new List<GameObject>();
// 清除不在新范围内的Text
foreach (KeyValuePair<GameObject, Vector3> positionText in positionTexts)
{
if (!newPositions.Contains(positionText.Value))
{
LeanPool.Despawn(positionText.Key);
toRemove.Add(positionText.Key);
}
}
UpdateTextDisplay(requiredPositions);
}
}
foreach (GameObject text in toRemove)
{
positionTexts.Remove(text);
}
// 辅助方法:计算边界(避免重复的数学运算)
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
_ => 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();
}
}
#endregion
void OnDestroy()
{
// 清理资源
if (gridMaterial != null)
{
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();
}
}
}

View File

@@ -28,6 +28,7 @@ namespace Ichni
public BackgroundController backgroundController;
public GridController gridController;
public CameraManager cameraManager;
public NoteManager noteManager;
public Ichni.Editor.PostProcessingManager postProcessingManager;
public Canvas judgeHintCanvas;
public Canvas inspectorCanvas;
@@ -59,7 +60,7 @@ namespace Ichni
isLoaded = false;
projectManager = new ProjectManager();
operationManager = new OperationManager();
noteManager = new NoteManager();
if (!ES3.FileExists(Application.streamingAssetsPath + "/EditorSettings.es3"))
{
editorSettings = new EditorSettings(300, 3, 100, 100, 60);
@@ -120,6 +121,7 @@ namespace Ichni
private void Update()
{
if (isLoaded) projectManager.autoSaveManager.UpdateAutoSave();
noteManager.UpdateNote();
}
public void LoadProject(string projectName)

View File

@@ -14,10 +14,9 @@ namespace Ichni.Editor
public partial class InputListener : MonoBehaviour
{
public static InputListener instance;
private PointerEventData pointerEventData;
private bool isPointerOverUI;
public bool isPointerOverUI;
public TMP_Text hoveredUIText;
public GameObject hoveredUI;
//public GameObject hoveredUI;
public EventSystem eventSystem;
public List<GraphicRaycaster> graphicRaycasters;
private List<SelectionConnector> lastHitConnectors = new List<SelectionConnector>();
@@ -47,7 +46,7 @@ namespace Ichni.Editor
if (currentMousePosition != lastMousePosition || frameCount % uiCheckFrameInterval == 0)
{
lastMousePosition = currentMousePosition;
isPointerOverUI = IsPointerOverUI(out hoveredUI);
isPointerOverUI = IsPointerOverUI();
}
SceneCameraOperation();
MusicPlayerOperation();
@@ -308,68 +307,34 @@ namespace Ichni.Editor
{
private TMP_Text UIText => EditorManager.instance.UIText;
public bool IsPointerOverUI(out GameObject hoveredUI)
{
hoveredUI = null;
// 类级别缓存
private PointerEventData EventData;
private List<RaycastResult> raycastResults = new List<RaycastResult>();
public bool IsPointerOverUI()
{
if (Mouse.current == null) return false;
pointerEventData = new PointerEventData(eventSystem)
if (EventData == null)
{
position = Mouse.current.position.ReadValue()
};
EventData = new PointerEventData(eventSystem);
}
EventData.position = Mouse.current.position.ReadValue();
List<RaycastResult> allResults = new List<RaycastResult>();
raycastResults.Clear(); // 复用列表
EventSystem.current.RaycastAll(EventData, raycastResults);
// 使用EventSystem的RaycastAll来确保检测所有UI
EventSystem.current.RaycastAll(pointerEventData, allResults);
// 或者手动检测所有GraphicRaycaster
foreach (var raycaster in FindObjectsOfType<GraphicRaycaster>())
for (int i = 0; i < raycastResults.Count; i++)
{
if (!raycaster.enabled || !raycaster.gameObject.activeInHierarchy)
continue;
List<RaycastResult> results = new List<RaycastResult>();
raycaster.Raycast(pointerEventData, results);
allResults.AddRange(results);
var result = raycastResults[i];
if (result.gameObject != null &&
result.gameObject.activeInHierarchy &&
result.gameObject.GetComponent<RectTransform>() != null)
{
return true;
}
}
// 移除无效结果
allResults.RemoveAll(r =>
r.gameObject == null ||
!r.gameObject.activeInHierarchy ||
!r.gameObject.GetComponent<RectTransform>());
if (allResults.Count > 0)
{
// 完整排序
// allResults.Sort((a, b) =>
// {
// // 先按sorting layer
// int layerCompare = SortingLayer.GetLayerValueFromID(b.sortingLayer)
// .CompareTo(SortingLayer.GetLayerValueFromID(a.sortingLayer));
// if (layerCompare != 0) return layerCompare;
// // 再按sorting order
// int orderCompare = b.sortingOrder.CompareTo(a.sortingOrder);
// if (orderCompare != 0) return orderCompare;
// // 最后按depth
// return b.depth.CompareTo(a.depth);
// });
// hoveredUI = allResults[0].gameObject;
// string text = $"UI: {hoveredUI.name}, Layer: {SortingLayer.IDToName(allResults[0].sortingLayer)}, Order: {allResults[0].sortingOrder}";
// if (UIText.text != text)
// {
// UIText.text = text;
// }
return true;
}
//UIText.text = "No UI";
return false;
}
}

View File

@@ -0,0 +1,162 @@
using System.Collections;
using System.Collections.Generic;
using Ichni.RhythmGame;
using UniRx;
using UnityEngine;
namespace Ichni
{
public class NoteManager
{
public static NoteManager instance;
public NoteManager()
{
instance = this;
}
private List<NoteBase> allNotes = new List<NoteBase>();
public List<NoteBase> activeNotes = new List<NoteBase>();
private List<NoteBase> notesToRemove = new List<NoteBase>();
public int currentActiveNoteCount => activeNotes.Count;
private float lastKnownTime = -1f;
public void AddNote(NoteBase note)
{
allNotes.Add(note);
// 只在添加时排序一次
allNotes.Sort((a, b) => a.NoteAppearTime.CompareTo(b.NoteAppearTime));
note.UpdateNote(); // 初始化状态
Observable.NextFrame().Subscribe(_ =>
{
note.noteVisual?.generateEffect?.PreExecute();
note.noteVisual?.noteMain.SetActive(true);
note.UpdateNote(); // 确保在下一帧也更新一次,防止遗漏
});
}
public void RemoveNote(NoteBase note)
{
activeNotes.Remove(note);
allNotes.Remove(note);
}
public void UpdateNote()
{
if (allNotes.Count == 0) return;
float currentTime = EditorManager.instance.songInformation.songTime;
// 检测时间方向
bool isTimeMovingForward = currentTime >= lastKnownTime;
lastKnownTime = currentTime;
// 清空待移除列表
notesToRemove.Clear();
// 处理时间前进的情况 - 添加新音符
if (isTimeMovingForward)
{
// 查找所有应该激活但尚未激活的音符
for (int i = 0; i < allNotes.Count; i++)
{
var note = allNotes[i];
// 如果音符应该出现但还没激活
if (note.NoteAppearTime <= currentTime
)
{
if (!activeNotes.Contains(note)) activeNotes.Add(note);
}
else if (note.exactJudgeTime < currentTime) break;
}
}
// 处理时间倒退的情况 - 调整活跃列表
else
{
// 移除在当前时间之后出现的音符
for (int i = activeNotes.Count - 1; i >= 0; i--)
{
var note = activeNotes[i];
if (note.NoteAppearTime > currentTime)
{
notesToRemove.Add(note);
}
}
for (int i = 0; i < allNotes.Count; i++)
{
var note = allNotes[i];
// 如果音符应该出现但还没激活
if (note.isFirstJudged && note.exactJudgeTime > currentTime
)
{
if (!activeNotes.Contains(note)) activeNotes.Add(note);
}
// else if (note.NoteAppearTime < currentTime)
// {
// break; // 提前退出循环,优化性能
// }
}
}
// 更新所有活跃音符
for (int i = 0; i < activeNotes.Count; i++)
{
var note = activeNotes[i];
note.UpdateNote();
// Debug.Log($"Updating Note {note.elementName} at time {currentTime}");
// 标记已判定的音符等待移除
if (note.isFirstJudged)
{
notesToRemove.Add(note);
}
}
// 移除标记的音符
for (int i = 0; i < notesToRemove.Count; i++)
{
activeNotes.Remove(notesToRemove[i]);
}
}
// 重置所有状态(用于歌曲重新开始等)
public void ResetAll()
{
activeNotes.Clear();
notesToRemove.Clear();
lastKnownTime = -1f;
// 不重置音符状态,让音符自己的逻辑处理
}
// 跳转到指定时间
public void SeekToTime(float targetTime)
{
// 清空当前活跃列表
activeNotes.Clear();
notesToRemove.Clear();
// 添加在目标时间之前应该出现的音符
for (int i = 0; i < allNotes.Count; i++)
{
var note = allNotes[i];
// 添加在目标时间之前应该出现的音符
if (note.NoteAppearTime <= targetTime)
{
activeNotes.Add(note);
}
}
lastKnownTime = targetTime;
}
// 获取所有音符(只读)
public IReadOnlyList<NoteBase> GetAllNotes()
{
return allNotes.AsReadOnly();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6fe401fc051728b49a4e682f518ffc54
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: