@@ -1,8 +1,9 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Ichni.RhythmGame;
|
||||
using Ichni.RhythmGame.Beatmap;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Ichni.Editor.Commands;
|
||||
using Ichni.RhythmGame;
|
||||
using Ichni.RhythmGame.Beatmap;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
@@ -126,9 +127,9 @@ namespace Ichni.Editor
|
||||
copiedElement = gameElement;
|
||||
}
|
||||
|
||||
public void PasteElement(GameElement parentElement)
|
||||
{
|
||||
if (parentElement == null)
|
||||
public void PasteElement(GameElement parentElement)
|
||||
{
|
||||
if (parentElement == null)
|
||||
{
|
||||
LogWindow.Log("No parent element selected to paste.", Color.red);
|
||||
return;
|
||||
@@ -137,44 +138,73 @@ namespace Ichni.Editor
|
||||
if (copiedElement == null)
|
||||
{
|
||||
LogWindow.Log("No element copied.");
|
||||
return;
|
||||
}
|
||||
|
||||
LogWindow.Log("Pasted element: " + copiedElement.elementName + " to " + parentElement.elementName);
|
||||
pastedElementList = new List<GameElement>();
|
||||
AffiliatedPaste(copiedElement, parentElement);
|
||||
|
||||
// 粘贴完成后,对所有新生成的元素执行 AfterInitialize 和 Refresh,
|
||||
// 与 EditorManager.LoadProject 的加载后流程对齐,确保 Manager 注册等关键步骤不遗漏。
|
||||
return;
|
||||
}
|
||||
|
||||
CommandManager.ExecuteCommand(new PasteElementCommand(this, copiedElement, parentElement));
|
||||
}
|
||||
|
||||
public GameElement PasteElementRaw(GameElement sourceElement, GameElement parentElement)
|
||||
{
|
||||
if (sourceElement == null || parentElement == null) return null;
|
||||
|
||||
LogWindow.Log("Pasted element: " + sourceElement.elementName + " to " + parentElement.elementName);
|
||||
pastedElementList = new List<GameElement>();
|
||||
AffiliatedPaste(sourceElement, parentElement);
|
||||
|
||||
// 粘贴完成后,对所有新生成的元素执行 AfterInitialize 和 Refresh,
|
||||
// 与 EditorManager.LoadProject 的加载后流程对齐,确保 Manager 注册等关键步骤不遗漏。
|
||||
foreach (GameElement pasted in pastedElementList)
|
||||
{
|
||||
if (pasted == null) continue;
|
||||
pasted.AfterInitialize();
|
||||
pasted.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteElement(GameElement gameElement)
|
||||
pasted.AfterInitialize();
|
||||
pasted.Refresh();
|
||||
}
|
||||
|
||||
return pastedElementList.FirstOrDefault();
|
||||
}
|
||||
|
||||
public void DeleteElement(GameElement gameElement)
|
||||
{
|
||||
if (gameElement == null)
|
||||
{
|
||||
LogWindow.Log("No element selected to delete.", Color.red);
|
||||
return;
|
||||
}
|
||||
|
||||
LogWindow.Log("Deleted element: " + gameElement.elementName + " and all its children.");
|
||||
|
||||
if (gameElement.parentElement != null)
|
||||
{
|
||||
gameElement.parentElement.childElementList.Remove(gameElement); //从父物体的子物体列表中移除,避免报null
|
||||
gameElement.parentElement.connectedTab.childTabList.Remove(gameElement.connectedTab);
|
||||
gameElement.parentElement.connectedTab.SetStatus();
|
||||
}
|
||||
|
||||
EditorManager.instance.operationManager.RemoveSelectElement(gameElement);
|
||||
gameElement.Delete();
|
||||
EditorManager.instance.uiManager.inspector.ClearInspector();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
CommandManager.ExecuteCommand(new DeleteElementCommand(gameElement));
|
||||
}
|
||||
|
||||
public void DeleteElementRaw(GameElement gameElement, bool writeLog = true)
|
||||
{
|
||||
if (gameElement == null) return;
|
||||
|
||||
var deletedElements = gameElement.GetAllGameElementsFromThis();
|
||||
var deletedGuids = deletedElements.Select(element => element.elementGuid).ToList();
|
||||
var deletedTabs = deletedElements
|
||||
.Select(element => element.connectedTab)
|
||||
.Where(tab => tab != null)
|
||||
.ToList();
|
||||
|
||||
if (writeLog)
|
||||
LogWindow.Log("Deleted element: " + gameElement.elementName + " and all its children.");
|
||||
|
||||
if (gameElement.parentElement != null)
|
||||
{
|
||||
gameElement.parentElement.childElementList.Remove(gameElement); //从父物体的子物体列表中移除,避免报null
|
||||
if (gameElement.parentElement.connectedTab != null)
|
||||
{
|
||||
gameElement.parentElement.connectedTab.childTabList.Remove(gameElement.connectedTab);
|
||||
gameElement.parentElement.connectedTab.SetStatus();
|
||||
}
|
||||
}
|
||||
|
||||
EditorManager.instance.operationManager.RemoveSelectElement(gameElement);
|
||||
gameElement.Delete();
|
||||
deletedGuids.ForEach(guid => GameElement_BM.identifier.Remove(guid));
|
||||
deletedTabs.ForEach(tab => EditorManager.instance.uiManager.hierarchy.tabList.Remove(tab));
|
||||
EditorManager.instance.uiManager.inspector.ClearInspector();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用递归的方式复制粘贴物体及其所有子物体
|
||||
|
||||
@@ -230,9 +230,15 @@ namespace Ichni
|
||||
|
||||
public class BeatmapClipManager
|
||||
{
|
||||
private string clipFolder => Application.streamingAssetsPath + "/Clips";
|
||||
private string GetClipPath(string clipName) => clipFolder + "/" + clipName + ".json";
|
||||
|
||||
public void SaveClip(string clipName)
|
||||
{
|
||||
LogWindow.Log("Start Saving Clip...");
|
||||
clipName = clipName?.Trim();
|
||||
|
||||
if (!ValidateClipName(clipName)) return;
|
||||
|
||||
if (EditorManager.instance.operationManager.currentSelectedElements.Count != 1) // TODO: 后续适应多选
|
||||
{
|
||||
@@ -256,8 +262,11 @@ namespace Ichni
|
||||
public void LoadClip(string clipName)
|
||||
{
|
||||
LogWindow.Log("Start Loading Clip...");
|
||||
clipName = clipName?.Trim();
|
||||
|
||||
if (!ES3.FileExists(Application.streamingAssetsPath + "/Clips/" + clipName + ".json"))
|
||||
if (!ValidateClipName(clipName)) return;
|
||||
|
||||
if (!ES3.FileExists(GetClipPath(clipName)))
|
||||
{
|
||||
LogWindow.Log("Clip not found", Color.red);
|
||||
return;
|
||||
@@ -278,9 +287,10 @@ namespace Ichni
|
||||
}
|
||||
Debug.Log(selectedElement.elementName + " " + selectedElement.elementGuid);
|
||||
|
||||
_LoadClip(selectedElement, clipName);
|
||||
|
||||
LogWindow.Log("Load Clip Complete", Color.green);
|
||||
if (_LoadClip(selectedElement, clipName))
|
||||
{
|
||||
LogWindow.Log("Load Clip Complete", Color.green);
|
||||
}
|
||||
}
|
||||
|
||||
private void _SaveClip(GameElement element, string clipName)
|
||||
@@ -290,7 +300,8 @@ namespace Ichni
|
||||
element.GetAllGameElementsFromThis().ForEach(e =>
|
||||
{
|
||||
e.SaveBM();
|
||||
clip.Add(e.matchedBM);
|
||||
if (e.matchedBM != null)
|
||||
clip.Add(e.matchedBM);
|
||||
e.submoduleList.ForEach(s =>
|
||||
{
|
||||
s.SaveBM();
|
||||
@@ -299,47 +310,59 @@ namespace Ichni
|
||||
});
|
||||
});
|
||||
|
||||
string filePath = Application.streamingAssetsPath + "/Clips/" + clipName + ".json";
|
||||
Directory.CreateDirectory(clipFolder);
|
||||
string filePath = GetClipPath(clipName);
|
||||
ES3.Save("Clip", clip, filePath, ProjectManager.SaveSettings);
|
||||
}
|
||||
|
||||
private void _LoadClip(GameElement target, string clipName)
|
||||
private bool _LoadClip(GameElement target, string clipName)
|
||||
{
|
||||
try
|
||||
{
|
||||
string filePath = Application.streamingAssetsPath + "/Clips/" + clipName + ".json";
|
||||
string filePath = GetClipPath(clipName);
|
||||
List<BaseElement_BM> clip = ES3.Load<List<BaseElement_BM>>("Clip", filePath, ProjectManager.SaveSettings);
|
||||
|
||||
if (clip == null || clip.Count == 0)
|
||||
{
|
||||
Debug.LogError("Clip is empty or null");
|
||||
return;
|
||||
LogWindow.Log("Clip is empty or null", Color.red);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证第一个元素
|
||||
if (!(clip[0] is GameElement_BM first))
|
||||
{
|
||||
Debug.LogError("First element is not a GameElement_BM");
|
||||
return;
|
||||
LogWindow.Log("First element is not a GameElement_BM", Color.red);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 处理目标对象 - 添加清理机制
|
||||
target.SaveBM();
|
||||
if (target.matchedBM is not GameElement_BM targetBM)
|
||||
{
|
||||
LogWindow.Log("Load Clip failed: target did not create a valid BM.", Color.red);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool targetAdded = GameElement_BM.identifier.TryAdd(target.elementGuid, target.matchedBM as GameElement_BM);
|
||||
if (!targetAdded)
|
||||
{
|
||||
Debug.LogWarning("Target element already exists in identifier dictionary");
|
||||
}
|
||||
targetBM.matchedElement = target;
|
||||
|
||||
List<GameElement> loadedElements = new List<GameElement>();
|
||||
|
||||
// 处理第一个元素及其附加元素
|
||||
ProcessGameElement(first, clip, target.elementGuid);
|
||||
if (!ProcessGameElement(first, clip, target.elementGuid, loadedElements)) return false;
|
||||
|
||||
// 处理后续元素
|
||||
for (var index = 1; index < clip.Count; index++)
|
||||
{
|
||||
if (clip[index] is GameElement_BM gameElement)
|
||||
{
|
||||
ProcessGameElement(gameElement, clip, null);
|
||||
if (!ProcessGameElement(gameElement, clip, null, loadedElements)) return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -348,14 +371,28 @@ namespace Ichni
|
||||
}
|
||||
}
|
||||
|
||||
EditorManager.instance.beatmapContainer.ExecuteLowPriorityActions();
|
||||
loadedElements.ForEach(element =>
|
||||
{
|
||||
if (element == null) return;
|
||||
element.AfterInitialize();
|
||||
element.Refresh();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"Failed to load clip: {ex.Message}");
|
||||
LogWindow.Log("Failed to load clip: " + ex.Message, Color.red);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessGameElement(GameElement_BM gameElement, List<BaseElement_BM> clip, Guid? attachToGuid)
|
||||
private bool ProcessGameElement(
|
||||
GameElement_BM gameElement,
|
||||
List<BaseElement_BM> clip,
|
||||
Guid? attachToGuid,
|
||||
List<GameElement> loadedElements)
|
||||
{
|
||||
List<BaseElement_BM> attachedElements = GameElement_BM.GetAllAttachedBaseElements(gameElement, clip);
|
||||
|
||||
@@ -365,7 +402,8 @@ namespace Ichni
|
||||
if (!elementAdded)
|
||||
{
|
||||
Debug.LogError($"Failed to add element with GUID: {gameElement.elementGuid}");
|
||||
return;
|
||||
LogWindow.Log("Failed to add clip element: " + gameElement.elementName, Color.red);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 更新附加元素
|
||||
@@ -381,7 +419,31 @@ namespace Ichni
|
||||
}
|
||||
|
||||
gameElement.ExecuteBM();
|
||||
if (gameElement.matchedElement == null)
|
||||
{
|
||||
LogWindow.Log("Failed to generate clip element: " + gameElement.elementName, Color.red);
|
||||
return false;
|
||||
}
|
||||
|
||||
loadedElements.Add(gameElement.matchedElement);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ValidateClipName(string clipName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(clipName))
|
||||
{
|
||||
LogWindow.Log("Clip name cannot be empty.", Color.red);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (clipName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0)
|
||||
{
|
||||
LogWindow.Log("Clip name contains invalid characters.", Color.red);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user