大幅优化

Signed-off-by: TRAfoer <lhf190@outlook.com>
This commit is contained in:
2025-10-05 11:45:32 +08:00
parent e145d65d38
commit 725009e354
66 changed files with 616229 additions and 175087 deletions

View File

@@ -10,6 +10,7 @@ using System.Reflection;
using System.Linq.Expressions;
using Sirenix.Utilities;
using System.Collections;
using DG.Tweening;
//又在写大粪 ——神币
namespace Ichni.Editor
@@ -114,10 +115,18 @@ namespace Ichni.Editor
if (Keyboard.current.backquoteKey.wasPressedThisFrame)
{
ConsoleUI.SetActive(isHide);
isHide = !isHide;
if (!isHide) StartCoroutine(WindowAnim.ShowPanelOnScale(InputCommand.gameObject));
if (!isHide)
{
ConsoleUI.SetActive(isHide);
transform.DOScale(Vector3.one, 0.15f).SetEase(Ease.OutBack);
}
else transform.DOScale(Vector3.zero, 0.15f).SetEase(Ease.InCirc).OnComplete(() =>
{
ConsoleUI.SetActive(false);
});
}
}
@@ -180,7 +189,7 @@ namespace Ichni.Editor
{
string getFloatorInt(string a)
{
return a.Contains(".") ? $"float.Parse({a})" : a;
return a.Contains(".") ? $"{a}f" : a;
}
// 处理命令格式,适配 DynamicExpresso
string trimmed = input.Trim();

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing.Printing;
using System.Linq;
using System.Text.RegularExpressions;
using Ichni.RhythmGame;

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using DG.Tweening;
using Ichni.RhythmGame;
using Unity.VisualScripting;
@@ -35,6 +36,21 @@ namespace Ichni.Editor
tabList.Add(tab);
return tab;
}
public async Task<HierarchyTab> GenerateTabAsync(GameElement targetElement, GameElement parentElement)
{
var request = InstantiateAsync(hierarchyTabPrefab, tabContainer);
// 等待实例化过程完成
while (!request.isDone)
{
await Task.Yield(); // 异步地等待下一帧
}
HierarchyTab tab = request.Result[0].GetComponent<HierarchyTab>();
tab.SetTab(targetElement, parentElement);
tabList.Add(tab);
// 返回实例化的游戏对象
return tab;
}
public bool isExpand = false;
private RectTransform rectTransform;
public void Expand()
@@ -127,7 +143,7 @@ namespace Ichni.Editor
{
elem.parentElement.connectedTab.ExpandOrFold(true);
}
else if (elem.parentElement.connectedTab.ienumerator is null)
else if (!elem.parentElement.connectedTab.isExpandDone)
{
elem.parentElement.connectedTab.ExpandOrFold();
elem.parentElement.connectedTab.ExpandOrFold(true);//合上再展开,这思路也是没谁了

View File

@@ -1,5 +1,6 @@
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using DG.Tweening;
using Ichni.RhythmGame;
using Michsky.MUIP;
@@ -35,9 +36,8 @@ namespace Ichni.Editor
public void SetTab(GameElement targetElement, GameElement parentElement)
{
tabMainRect.localScale = Vector3.zero;
StartCoroutine(WindowAnim.ShowPanelOnScale(tabMainRect.gameObject));
transform.localScale = Vector3.one;
transform.DOScale(Vector3.one, 0.2f).SetEase(Ease.OutCirc).From(new Vector3(1, 0, 1));
connectedGameElement = targetElement;
tabButtonText.text = targetElement.elementName;
targetElement.connectedTab = this;
@@ -69,7 +69,6 @@ namespace Ichni.Editor
float lineX = 30 * i - 15;
var d = Instantiate(indentationLinePrefab, tabRect);
d.GetComponent<RectTransform>().anchoredPosition = new Vector2(lineX, 0);
StartCoroutine(WindowAnim.ShowPanelOnScale(d.gameObject));
}
parentTab.SetStatus();
}
@@ -82,7 +81,13 @@ namespace Ichni.Editor
deleteButton.onConfirm = () => EditorManager.instance.operationManager.CopyPasteDeleteModule.DeleteElement(connectedGameElement);
SetStatus();
}
private void OnDestroy()
{
transform.DOKill();
}
public void SetStatus()
{
expandButton.gameObject.SetActive(!connectedGameElement.childElementList.IsNullOrEmpty());
@@ -155,7 +160,6 @@ namespace Ichni.Editor
EditorManager.instance.uiManager.inspector.SetInspector(connectedGameElement);
}
public IEnumerator ienumerator = null;
public void ExpandOrFold()
{
ExpandOrFold(false);
@@ -173,15 +177,12 @@ namespace Ichni.Editor
// float startTime = Time.realtimeSinceStartup;
// connectedGameElement.childElementList.Sort();//TODO: 后续可以让玩家手动快速排序
Debug.Log(FixedList.Count);
ienumerator = ExpandOverTime(FixedList);
StartCoroutine(ienumerator);
ExpandAsync(FixedList);
}
else
{
//expandButton.transform.Rotate(new Vector3(0, 0, 180));
StopCoroutine(ienumerator);
ienumerator = null;
for (int i = childTabList.Count - 1; i >= 0; i--)
{
childTabList[i].SetExpansion(isExpanded);
@@ -210,22 +211,37 @@ namespace Ichni.Editor
{
expandButton.transform.DORotate(new Vector3(0, 0, !isExpanded ? 0f : 180f), 0.2f);
}
private IEnumerator ExpandOverTime(List<GameElement> FixedList)//帧率过低的时候等一下再实例化
public bool isExpandDone = true;
private void ExpandImmediately(List<GameElement> FixedList)
{
float StrandTimeWhileStartUp = EditorManager.instance.CurrentFrameRate;
float startTime = Time.realtimeSinceStartup;
float frameTime = 1f / StrandTimeWhileStartUp * 3f;
for (var index = 0; index < FixedList.Count; index++)
{
int hasYield = 0;
while (Time.realtimeSinceStartup - startTime > 1f / StrandTimeWhileStartUp * 3f && hasYield <= 1)
{
yield return null;
hasYield += 1;
}
var childElement = FixedList[index];
EditorManager.instance.uiManager.hierarchy.GenerateTab(childElement, connectedGameElement);
print($"生成子Tab{childElement.elementName},索引:{index},总数:{FixedList.Count}");
Debug.Log($"生成子Tab{childElement.elementName},索引:{index},总数:{FixedList.Count}");
// 如果处理时间超过帧时间限制,直接继续执行而不等待
// 移除了原来的 yield return null 等待逻辑
}
}
async void ExpandAsync(List<GameElement> FixedList)
{
isExpandDone = false;
for (var index = 0; index < FixedList.Count; index++)
{
var childElement = FixedList[index];
await EditorManager.instance.uiManager.hierarchy.GenerateTabAsync(childElement, connectedGameElement);
Debug.Log($"生成子Tab{childElement.elementName},索引:{index},总数:{FixedList.Count}");
if (!isExpanded) break;
}
isExpandDone = true;
}
}
}

View File

@@ -17,7 +17,7 @@ namespace Ichni.Editor
public UnityAction onCloseWindow;
public UnityAction onQuit;
protected void InitializeWindow(string titleText, UnityAction closeAction = null)
protected void InitializeWindow(string titleText, UnityAction closeAction = null, Vector3 targetScale = default)
{
title.text = titleText;
onCloseWindow = closeAction;
@@ -30,7 +30,7 @@ namespace Ichni.Editor
Destroy(gameObject);
});
});
StartCoroutine(WindowAnim.ShowPanelOnScale(gameObject));
this.transform.DOScale(targetScale == default ? Vector3.one : targetScale, 0.2f).SetEase(Ease.OutBack).From(Vector3.zero);
}
}
}

View File

@@ -13,7 +13,23 @@ namespace Ichni.Editor
public TMP_Text intervalUnitText;
public float time;
public int index;
public bool isInScreen
{
get => _isInScreen;
set
{
_isInScreen = value;
if (_isInScreen)
{
transform.localScale = Vector3.one;
}
else
{
transform.localScale = Vector3.zero;
}
}
}
private bool _isInScreen = false;
// [Title("poss")]
// public Vector3 position;

View File

@@ -16,7 +16,7 @@ namespace Ichni.Editor
public GameObject timePointerPrefab;
public List<TimePointer> timePointerList;
public List<TimePointer> ActivePointer => timePointerList.FindAll(pointer => pointer.gameObject.activeSelf);
public List<TimePointer> ActivePointer => timePointerList.FindAll(pointer => pointer.isInScreen);
private TimePointer NowPointer;
public RectTransform moveTabPoint;
@@ -45,7 +45,6 @@ namespace Ichni.Editor
timePointer.intervalUnitText.text = (i * timeline.timePerBeat).ToString("F3");
timePointer.GetComponent<RectTransform>().localPosition = new Vector3(i * timePointerInterval, 0f, 0f);
timePointer.index = i;
timePointer.gameObject.SetActive(false);
}
// for (int i = -1; i > -songInformation.delay / timeline.timePerBeat; i--)
// {
@@ -77,21 +76,22 @@ namespace Ichni.Editor
{
while (NowPointer.index < timePointerList.Count - 1 && NowPointer.index * timeline.timePerBeat < EditorManager.instance.songInformation.songTime)
{
NowPointer.gameObject.SetActive(false);
NowPointer.isInScreen = false;
NowPointer = timePointerList[NowPointer.index + 1];
}
while (NowPointer.index > 0 && NowPointer.index * timeline.timePerBeat >= EditorManager.instance.songInformation.songTime)
{
NowPointer.gameObject.SetActive(true);
NowPointer.isInScreen = true;
NowPointer = timePointerList[NowPointer.index - 1 >= 0 ? NowPointer.index - 1 : 0];
}
}
for (int i = NowPointer.index + 1; i < timePointerList.Count; i++)
{
timePointerList[i].gameObject.SetActive(true);
timePointerList[i].isInScreen = true;
if (timePointerList[i].GetComponent<RectTransform>().position.x > rightSide.position.x)
{
timePointerList[i].gameObject.SetActive(false);
timePointerList[i].isInScreen = false;
break;
}
}

View File

@@ -6,43 +6,7 @@ using UnityEngine;
public static class WindowAnim
{
public static IEnumerator ShowPanelOnScale(GameObject gameObject)
{
AnimationCurve animationCurve = new AnimationCurve(
new Keyframe(0, 0),
new Keyframe(0.25f, 0.55f),
new Keyframe(0.5f, 0.85f),
new Keyframe(0.75f, 0.97f),
new Keyframe(1, 1)
);
float timer = 0;
while (timer <= 1)
{
gameObject.transform.localScale = Vector3.one * animationCurve.Evaluate(timer);
timer += Time.deltaTime * 5f;
yield return null;
}
gameObject.transform.localScale = Vector3.one;
}
public static IEnumerator HidePanelOnscale(GameObject gameObject, bool DestoryOrNot = false)
{
AnimationCurve animationCurve = new AnimationCurve(
new Keyframe(0, 0),
new Keyframe(0.25f, 0.55f),
new Keyframe(0.5f, 0.85f),
new Keyframe(0.75f, 0.97f),
new Keyframe(1, 1)
);
float timer = 1;
while (timer >= 0)
{
gameObject.transform.localScale = Vector3.one * animationCurve.Evaluate(timer);
timer += Time.deltaTime * 5f;
yield return null;
}
gameObject.transform.localScale = Vector3.zero;
}
public static IEnumerator Shake(GameObject gameObject)
{
float timer = 0f;

View File

@@ -20,7 +20,9 @@ namespace Ichni.RhythmGame
{
switch (elementToSelect)
{
case NoteBase note when EditorManager.instance.songInformation.songTime > note.exactJudgeTime:
case NoteBase note when elementToSelect is not Hold && EditorManager.instance.songInformation.songTime > note.exactJudgeTime:
return;
case Hold hold when EditorManager.instance.songInformation.songTime > hold.holdEndTime:
return;
case PathNode pathNode1 when !pathNode1.isShowingSphere:
return;

View File

@@ -33,13 +33,13 @@ namespace Ichni.RhythmGame
return EffectState.Error;
}
public override void UpdateEffect(float triggerTime)
{
EffectState state = CheckEffectState(triggerTime);
float songTime = EditorManager.instance.songInformation.songTime;
triggerTime -= generateTime;
if (state == EffectState.Before && nowEffectState != EffectState.Before)
{
nowEffectState = EffectState.Before;
@@ -52,31 +52,33 @@ namespace Ichni.RhythmGame
{
PreExecute();
}
nowEffectState = EffectState.Middle;
effectProgressPercent = (songTime - triggerTime) / effectTime;
Execute();
}
else if (state == EffectState.After && nowEffectState != EffectState.After)
{
if (nowEffectState != EffectState.Middle)
PreExecute();
nowEffectState = EffectState.After;
effectProgressPercent = 1;
Adjust();
}
}
}
namespace Beatmap
{
public abstract class NoteGenerateEffect_BM : NoteEffectBase_BM
{
public float generateTime;
public NoteGenerateEffect_BM()
{
}
public NoteGenerateEffect_BM(float effectTime, float generateTime) : base(effectTime)
{
this.generateTime = generateTime;

View File

@@ -157,7 +157,7 @@ namespace Ichni.RhythmGame
public partial class Hold
{
protected override void Update()
public override void Update()
{
if (Keyboard.current.hKey.wasPressedThisFrame)
{
@@ -168,44 +168,67 @@ namespace Ichni.RhythmGame
}
float songTime = EditorManager.instance.songInformation.songTime;
// 边界检查
if (holdEndTime < exactJudgeTime)
{
LogWindow.Log("Hold end time is earlier than exact judge time.", Color.red);
return;
}
float songTime = EditorManager.instance.songInformation.songTime;
// 1. 重置逻辑:当时间回退到判定时间之前
if (isFirstJudged && songTime < exactJudgeTime)
{
isFirstJudged = false;
isHolding = false;
isFinalJudged = false;
holdingTime = 0;
noteAudioSubmodule.PlayHoldStartAudio();
// 移除音频播放 - 重置时不应该播放任何音频
}
// 2. 状态恢复当时间从结束状态回退到hold区间内
if (isFinalJudged && songTime >= exactJudgeTime && songTime <= holdEndTime)
{
isFirstJudged = true;
isHolding = true;
isFinalJudged = false;
holdingTime = songTime - exactJudgeTime; // 修复更新holdingTime
}
// 3. 处理hold过程中的逻辑
if (isHolding)
{
holdingTime = songTime - exactJudgeTime;
if (songTime > holdEndTime)
// 检查hold是否结束
if (songTime >= holdEndTime && !isFinalJudged)
{
isHolding = false;
isFinalJudged = true;
//noteAudioSubmodule?.PlayNoteJudgeAudios(EditorManager.instance.currentJudgeType);//有待商榷
// 可以考虑在这里播放hold结束的音频
// noteAudioSubmodule?.PlayHoldEndAudio();
}
}
// 4. 第一次判定:进入判定区间
if (!isFirstJudged && songTime >= exactJudgeTime)
{
isFirstJudged = true;
if (!isHolding && songTime < holdEndTime)
// 检查是否可以开始hold
if (songTime <= holdEndTime)
{
// 播放开始判定的音频
noteAudioSubmodule?.PlayNoteJudgeAudios(EditorManager.instance.currentJudgeType);
// 如果需要在这里播放hold开始音频
// noteAudioSubmodule.PlayHoldStartAudio();
isHolding = true;
holdingTime = songTime - exactJudgeTime;
}
}
if (noteJudgeSubmodule != null && !EditorManager.instance.cameraManager.isSceneCameraActive)
{
foreach (NoteJudgeUnit unit in noteJudgeSubmodule.judgeUnitList.Where(unit => unit.isShowingJudge))

View File

@@ -36,9 +36,9 @@ namespace Ichni.RhythmGame
public override void AfterInitialize()
{
base.AfterInitialize();
float beyondTime = 0f;
foreach (EffectBase effectBase in noteVisual.effectSubmodule.effectCollection["Generate"])
{
if (effectBase is NoteGenerateEffect ge)
@@ -59,7 +59,7 @@ namespace Ichni.RhythmGame
finishEffects.AddRange(noteVisual.effectSubmodule.effectCollection["Good"]);
finishEffects.AddRange(noteVisual.effectSubmodule.effectCollection["Bad"]);
finishEffects.AddRange(noteVisual.effectSubmodule.effectCollection["Miss"]);
foreach (EffectBase effectBase in finishEffects)
{
finishTime = Mathf.Max(finishTime, effectBase.effectTime);
@@ -68,7 +68,12 @@ namespace Ichni.RhythmGame
if (exactJudgeTime - beyondTime - 0.5f > -EditorManager.instance.songInformation.delay)
{
gameObject.SetActive(false);
EditorManager.instance.noteManager.RegisterNote(this, exactJudgeTime - beyondTime - 0.5f, exactJudgeTime + finishTime + 1.5f);
if (this is Hold hold)
{
EditorManager.instance.noteManager.RegisterNote(hold, hold.exactJudgeTime - beyondTime - 0.5f, hold.holdEndTime + finishTime + 1.5f);
}
else EditorManager.instance.noteManager.RegisterNote(this, exactJudgeTime - beyondTime - 0.5f, exactJudgeTime + finishTime + 1.5f);
}
}
@@ -115,8 +120,8 @@ namespace Ichni.RhythmGame
foreach (SampleWindow i in SampleWindow.instances.Where(i => i.gameElement)) i.OnceSpawnNote();
}
protected virtual void Update()
public virtual void Update()
{
var editor = EditorManager.instance;
var cameraManager = editor.cameraManager;

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.transform.localScale = Vector3.zero; // 重置缩放
textPool.Enqueue(textObj);
}
}
private GameObject GetTextFromPool()
{
if (textPool.Count > 0)
{
GameObject textObj = textPool.Dequeue();
textObj.transform.localScale = Vector3.one; // 重置缩放
return textObj;
}
// 如果池子空了,动态创建一个(应该很少发生)
GameObject newTextObj = Instantiate(positionTextPrefab, textContainer);
return newTextObj;
}
private void ReturnTextToPool(GameObject textObj)
{
textObj.transform.localScale = Vector3.zero; // 重置缩放;
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

@@ -1,6 +1,5 @@
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Eventing.Reader;
using System.Linq;
using DG.Tweening;
using Ichni;
@@ -33,10 +32,8 @@ public partial class GraphicalFlexibleFloatWindow : MovableWindow
public int BeatNextDeviver = 1;
public void Initialize(IBaseElement baseElement, string title, FlexibleFloat[] FlexibleFloats, string[] Subtitles)
{
InitializeWindow(title, null, new Vector3(1f, 1f, 1f));
scalevalue = 5;
transform.localScale = Vector3.zero;
StartCoroutine(WindowAnim.ShowPanelOnScale(gameObject));
this.connectedBaseElement = baseElement;
this.title.text = title;
unitList = new List<FlexibleFloatTab>();

View File

@@ -46,8 +46,7 @@ namespace Ichni.Editor
if (targetGameElement != null)
transform.position = targetGameElement.transform.position;
transform.localScale = Vector3.zero;
StartCoroutine(WindowAnim.ShowPanelOnScale(gameObject));
transform.DOScale(Vector3.one, 0.2f).SetEase(Ease.OutBack);
targetGameElement.OnDestroyAsObservable()
.Subscribe(_ =>
{

View File

@@ -64,7 +64,7 @@ public class SampleWindow : MovableWindow//该window高度为300横的要XWid
trackPositioner.enabled = isFocus;
trackPositioner.targetObject = trackHead;
}
InitializeWindow(title);
InitializeWindow(title, null, new Vector3(1.2f, 1.2f, 1.2f));
//
@@ -135,7 +135,10 @@ public class SampleWindow : MovableWindow//该window高度为300横的要XWid
LineMovepoint.localPosition = new(0, -beatDeviver * (songBeat - (int)songBeat), 0);
NoteMovepoint.localPosition = new(0, -beatDeviver * songBeat, 0);
if (RectTransformUtility.RectangleContainsScreenPoint(windowRect, Mouse.current.position.ReadValue()))
if (RectTransformUtility.RectangleContainsScreenPoint(windowRect, Mouse.current.position.ReadValue()) &&
selectedGameObject != DeviverInputField.gameObject &&
selectedGameObject != verticalInputField.gameObject &&
selectedGameObject != horizontalInputField.gameObject)
{
DetectNote();

View File

@@ -204,19 +204,14 @@ namespace Ichni
Instantiate(EditorManager.instance.basePrefabs.generalSecondaryWindow,
EditorManager.instance.uiManager.mainPage.mainCanvas.GetComponent<RectTransform>()).GetComponent<GeneralSecondaryWindow>();
QuitWindow.Initialize("Do You Want To Save?", () =>
{
Destroy(QuitWindow.gameObject);
});
QuitWindow.Initialize("Do You Want To Save?");
var container = QuitWindow.GenerateContainer("Save confirm");
var beatmapToolsSettings = container.GenerateSubcontainer(3);
var yesButton = QuitWindow.GenerateButton(beatmapToolsSettings, "Yes", () =>
{
isQuit = true;
EditorManager.instance.projectManager.saveManager.Save();
Application.Quit();
SaveAndQuit();
});
var noButton = QuitWindow.GenerateButton(beatmapToolsSettings, "No", () =>
@@ -236,5 +231,12 @@ namespace Ichni
// }
}
async void SaveAndQuit()
{
isQuit = true;
LogWindow.Log("Start Saving...", Color.yellow);
await projectManager.saveManager.SaveAllCoroutine();
Application.Quit();
}
}
}

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();
@@ -152,6 +151,19 @@ namespace Ichni.Editor
{
StartCoroutine(EditorManager.instance.musicPlayer.PlayBackMusic());
}
if (Keyboard.current.leftCtrlKey.isPressed)
{
if (Keyboard.current.rightArrowKey.wasPressedThisFrame)
{
EditorManager.instance.musicPlayer.audioSource.pitch += 0.1f;
LogWindow.Log($"Music pitch: {EditorManager.instance.musicPlayer.audioSource.pitch}", Color.white);
}
else if (Keyboard.current.leftArrowKey.wasPressedThisFrame)
{
EditorManager.instance.musicPlayer.audioSource.pitch -= 0.1f;
LogWindow.Log($"Music pitch: {EditorManager.instance.musicPlayer.audioSource.pitch}", Color.white);
}
}
}
}
@@ -308,68 +320,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

@@ -1,17 +1,19 @@
using System.Collections;
using System.Collections.Generic;
using UniRx;
using UnityEngine;
namespace Ichni.RhythmGame
{
public class NoteManager : MonoBehaviour
{
private List<(NoteBase note, float activationTime, float finishTime)> pendingNotes = new List<(NoteBase, float, float)>();
public List<(NoteBase note, float activationTime, float finishTime)> pendingNotes = new List<(NoteBase, float, float)>();
private int nextNoteIndex = 0;
public void RegisterNote(NoteBase note, float activationTime, float finishTime)
{
pendingNotes.Add((note, activationTime, finishTime));
AllNotesRegistered();
}
// 在所有物体注册完毕后,对列表进行一次排序
@@ -20,20 +22,39 @@ namespace Ichni.RhythmGame
pendingNotes.Sort((a, b) => a.activationTime.CompareTo(b.activationTime));
}
void Update()
void LateUpdate()
{
foreach ((NoteBase note, float activationTime, float finishTime) note in pendingNotes)
List<(NoteBase note, float activationTime, float finishTime)> toRemove = new List<(NoteBase, float, float)>();
foreach ((NoteBase note, float activationTime, float finishTime) in pendingNotes)
{
if (EditorManager.instance.songInformation.songTime >= note.activationTime &&
EditorManager.instance.songInformation.songTime <= note.finishTime)
if (note == null)
{
note.note.gameObject.SetActive(true);
toRemove.Add((note, activationTime, finishTime));
continue;
}
if (EditorManager.instance.songInformation.songTime >= activationTime &&
EditorManager.instance.songInformation.songTime <= finishTime)
{
if (!note.gameObject.activeSelf)
{
note.gameObject.SetActive(true);
}
}
else
{
note.note.gameObject.SetActive(false);
if (note.gameObject.activeSelf)
{
note.gameObject.SetActive(false);
}
}
}
for (int i = toRemove.Count - 1; i >= 0; i--)
{
pendingNotes.Remove(toRemove[i]);
}
}
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading.Tasks;
using Ichni.Editor;
using Ichni.RhythmGame;
using Ichni.RhythmGame.Beatmap;
@@ -111,15 +112,21 @@ namespace Ichni
public void Save()
{
LogWindow.Log("Start Saving...");
_ = SaveAllCoroutine();
SaveProjectInfo();
SaveSongInfo();
SaveBeatMap();
SaveCommandScripts();
}
public async Task SaveAllCoroutine()
{
await Task.Run(() =>
{
SaveProjectInfo();
SaveSongInfo();
SaveBeatMap();
SaveCommandScripts();
});
LogWindow.Log("Save Complete", Color.green);
}
private void SaveProjectInfo()
{
EditorManager.instance.projectInformation.SaveBM();
@@ -535,11 +542,11 @@ namespace Ichni
File.Delete(oldestSave);
saveFiles.RemoveAt(saveFiles.Count - 1);
LogWindow.Log("AutoSave finished, the oldest file deleted");
LogWindow.Log("AutoSaving... , the oldest file deleted");
}
else
{
LogWindow.Log("AutoSave finished");
LogWindow.Log("AutoSaving...");
}
// 依次重命名存档
@@ -558,9 +565,17 @@ namespace Ichni
private void SaveBeatMap(string autoSavePath)
{
EditorManager.instance.beatmapContainer.SaveBM();
ES3.Save("Beatmap", EditorManager.instance.beatmapContainer.matchedBM as BeatmapContainer_BM,
autoSavePath, ProjectManager.SaveSettings);
SaveBeatMapCoroutine(autoSavePath);
}
async void SaveBeatMapCoroutine(string autoSavePath)
{
await Task.Run(() =>
{
EditorManager.instance.beatmapContainer.SaveBM();
ES3.Save("Beatmap", EditorManager.instance.beatmapContainer.matchedBM as BeatmapContainer_BM, autoSavePath, ProjectManager.SaveSettings);
});
LogWindow.Log("Auto-saved", Color.green);
}
private List<string> GetSortedSaveFiles()