Files
ichni_Creator_Studio/Assets/Scripts/Console/consoleOnMono.cs
TRADER_FOER 19bf49289e 修修补补
Signed-off-by: TRADER_FOER <lhf190@outlook.com>
2026-07-17 21:09:34 +08:00

1387 lines
58 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using Ichni;
using Ichni.Editor;
using Ichni.RhythmGame;
using Ichni.StartMenu;
using I2.Loc.SimpleJSON;
using TMPro;
using UnityEngine;
using UnityEngine.InputSystem;
public class consoleOnMono : MonoBehaviour
{
private const string LegacyImportFileName = "old_tbm_trams.json";
private const int ElementsPerFrame = 120;
private const float LegacyHoldNoteVisualPositionScale = 0.1f;
private const string LegacyEnvironmentThemeBundleName = "basic";
private const string LegacyEnvironmentFallbackObjectName = "EmptyObject";
private bool isImportingLegacyTbm;
private readonly Dictionary<string, GameElement> legacyIdToElement = new();
private readonly Dictionary<string, JSONNode> legacyIdToNode = new();
private readonly Dictionary<Track, JSONNode> pendingTrackPathNodes = new();
private readonly Dictionary<Track, JSONNode> pendingTrackTimeNodes = new();
private readonly Dictionary<Track, JSONNode> pendingTrackRendererNodes = new();
private readonly Dictionary<Track, Color> legacyTrackColorMultipliers = new();
void Update()
{
if (Keyboard.current.f7Key.wasPressedThisFrame)
{
if (!isImportingLegacyTbm)
{
StartCoroutine(Read());
}
}
}
public IEnumerator Read()
{
isImportingLegacyTbm = true;
legacyIdToElement.Clear();
legacyIdToNode.Clear();
pendingTrackPathNodes.Clear();
pendingTrackTimeNodes.Clear();
pendingTrackRendererNodes.Clear();
legacyTrackColorMultipliers.Clear();
string path = SelectLegacyImportFilePath();
if (string.IsNullOrEmpty(path))
{
LogWindow.Log("Legacy tbm import canceled.", Color.yellow);
isImportingLegacyTbm = false;
yield break;
}
if (!File.Exists(path))
{
LogWindow.Log($"Legacy tbm import file not found: {path}", Color.red);
isImportingLegacyTbm = false;
yield break;
}
LogWindow.Log($"Start legacy tbm import: {path}");
JSONNode root;
try
{
root = JSONNode.Parse(File.ReadAllText(path));
}
catch (Exception e)
{
Debug.LogException(e);
LogWindow.Log($"Legacy tbm parse failed: {e.Message}", Color.red);
isImportingLegacyTbm = false;
yield break;
}
JSONNode elements = root["BeatMap"]["value"]["beatMapElementList_BM"];
if (elements == null || elements.Count == 0)
{
LogWindow.Log("Legacy tbm has no BeatMap.value.beatMapElementList_BM.", Color.red);
isImportingLegacyTbm = false;
yield break;
}
for (int i = 0; i < elements.Count; i++)
{
JSONNode node = elements[i];
string id = GetString(node, "id", string.Empty);
if (!string.IsNullOrEmpty(id) && !legacyIdToNode.ContainsKey(id))
{
legacyIdToNode.Add(id, node);
}
}
LegacyImportStats stats = new();
yield return GenerateLegacyMainElements(elements, stats);
yield return GenerateLegacyTrackSubmodules(stats);
yield return GenerateLegacyPathNodes(elements, stats);
yield return GenerateLegacyTrackPoints(elements, stats);
yield return GenerateLegacyNotes(elements, stats);
yield return GenerateLegacyNoteVisualOverrides(elements, stats);
yield return GenerateLegacyAnimations(elements, stats);
yield return RefreshLegacyImport(stats);
yield return ApplyLegacyPositionSyncInitialPass();
LogWindow.Log(
$"Legacy tbm import finished. Created {stats.created}, skipped {stats.skipped}, failed {stats.failed}.",
stats.failed > 0 ? Color.yellow : Color.green);
isImportingLegacyTbm = false;
}
private static string SelectLegacyImportFilePath()
{
OpenFileName file = new();
file.structSize = Marshal.SizeOf(file);
file.filter = "Legacy TBM or JSON (*.tbm;*.json)\0*.tbm;*.json\0All Files (*.*)\0*.*\0\0";
file.file = new string(new char[4096]);
file.maxFile = file.file.Length;
file.fileTitle = new string(new char[512]);
file.maxFileTitle = file.fileTitle.Length;
file.initialDir = Application.streamingAssetsPath.Replace('/', '\\');
file.title = "Select Legacy TBM Import File";
file.defExt = "json";
file.flags = 0x00080000 | 0x00001000 | 0x00000800 | 0x00000008;
return LocalDialog.GetOpenFileName(file) && !string.IsNullOrEmpty(file.file)
? Path.GetFullPath(file.file)
: string.Empty;
}
private IEnumerator GenerateLegacyMainElements(JSONNode elements, LegacyImportStats stats)
{
for (int i = 0; i < elements.Count; i++)
{
JSONNode node = elements[i];
string typeName = GetLegacyTypeName(node);
switch (typeName)
{
case "ElementFolder_BM":
Debug.Log($"[LegacyImport] Processing ElementFolder_BM: {GetElementName(node, "unnamed")}");
TryCreateLegacyElement(node, () =>
{
GameElement parent = GetLegacyParentOrAncestor(node, e => e is ElementFolder);
return ElementFolder.GenerateElement(GetElementName(node, "Legacy Folder"),
CreateGuid(), new List<string>(), true, parent);
}, stats);
break;
case "GameCamera_BM":
Debug.Log($"[LegacyImport] Processing GameCamera_BM: {GetElementName(node, "unnamed")}");
TryCreateLegacyElement(node, () =>
{
GameElement parent = GetLegacyParentOrAncestor(node) ?? CreateFallbackFolder();
if (EditorManager.instance.cameraManager.haveGameCamera)
{
GameCamera existingCamera = EditorManager.instance.cameraManager.gameCamera;
ApplyLegacyGameCamera(existingCamera, node, parent);
Debug.Log($"[LegacyImport] Map GameCamera_BM to existing camera under parent='{parent.elementName}'");
return existingCamera;
}
GameCamera camera = GameCamera.GenerateElement(GetElementName(node, "Legacy Camera"),
CreateGuid(), new List<string>(), true, parent,
GetCameraViewType(node), GetFloat(node, "perspectiveAngle", 60f), GetFloat(node, "orthographicSize", 5f));
if (camera != null)
{
ApplyLegacyGameCamera(camera, node, parent);
}
return camera;
}, stats);
break;
case "Track_BM":
Debug.Log($"[LegacyImport] Processing Track_BM: {GetElementName(node, "unnamed")}");
TryCreateLegacyElement(node, () =>
{
GameElement parent = GetLegacyParentOrAncestor(node, e => e is ElementFolder);
if (parent == null)
{
parent = CreateFallbackFolder();
Debug.Log("[LegacyImport] Track_BM no parent, assigned to fallback folder");
}
Track track = Track.GenerateElement(GetElementName(node, "Legacy Track"),
CreateGuid(), new List<string>(), true, parent);
ApplyLegacyDuration(track, node);
Debug.Log($"[LegacyImport] Track_BM created: {track.elementName}, duration overridden={track is IHaveTimeDurationSubmodule timed && timed.timeDurationSubmodule.isOverridingDuration}");
return track;
}, stats);
break;
case "EnvironmentObject_BM":
Debug.Log($"[LegacyImport] Processing EnvironmentObject_BM: {GetElementName(node, "unnamed")}");
TryCreateLegacyElement(node, () =>
{
GameElement parent = GetLegacyParentOrAncestor(node) ?? CreateFallbackFolder();
if (!TryFindLegacyEnvironmentObject(node, out string themeBundleName, out string objectName))
{
return null;
}
EnvironmentObject environmentObject = EnvironmentObject.GenerateElement(
GetElementName(node, "Legacy Environment Object"),
CreateGuid(), new List<string>(), true, themeBundleName, objectName, parent,
GetBool(node, "isStatic", false));
ApplyLegacySubstantialObject(environmentObject, node);
Debug.Log($"[LegacyImport] EnvironmentObject_BM created: oldObject='{GetString(node, "objectName", "?")}', newObject='{themeBundleName}/{objectName}', parent='{parent.elementName}'");
return environmentObject;
}, stats);
break;
case "TimeTrackPoint_BM":
Debug.Log($"[LegacyImport] Processing TimeTrackPoint_BM as CrossTrackPoint: {GetElementName(node, "unnamed")}");
TryCreateLegacyElement(node, () =>
{
ElementFolder parent = GetLegacyParentOrAncestor(node, e => e is ElementFolder) as ElementFolder;
if (parent == null)
{
parent = CreateFallbackFolder();
Debug.Log("[LegacyImport] TimeTrackPoint_BM no folder parent, assigned to fallback folder");
}
return CrossTrackPoint.GenerateElement(GetElementName(node, "New Cross Track Point"),
CreateGuid(), new List<string>(), true, parent,
ConvertLegacyTrackSwitch(node["trackSwitch"]),
ConvertLegacyTrackPercent(node["trackPercent"]));
}, stats);
break;
case "TrackPath_BM":
Debug.Log($"[LegacyImport] Queue TrackPath_BM for track: {GetString(node, "id", "?")}");
QueueTrackNode(node, pendingTrackPathNodes, stats);
break;
case "TimeTrackMovable_BM":
case "TimeTrackStatic_BM":
Debug.Log($"[LegacyImport] Queue {typeName} for track: {GetString(node, "id", "?")}");
QueueTrackNode(node, pendingTrackTimeNodes, stats);
break;
case "TrackRendererAutoOrient_BM":
case "TrackRendererPathGenerator_BM":
Debug.Log($"[LegacyImport] Queue {typeName} for track: {GetString(node, "id", "?")}");
QueueTrackNode(node, pendingTrackRendererNodes, stats);
break;
}
if (i % ElementsPerFrame == 0)
{
yield return null;
}
}
}
private IEnumerator GenerateLegacyTrackSubmodules(LegacyImportStats stats)
{
int index = 0;
Debug.Log($"[LegacyImport] Processing {pendingTrackPathNodes.Count} pending TrackPath nodes...");
foreach (KeyValuePair<Track, JSONNode> pair in pendingTrackPathNodes)
{
Track track = pair.Key;
JSONNode node = pair.Value;
Debug.Log($"[LegacyImport] Replace TrackPath for Track '{track.elementName}'");
ReplaceTrackPathSubmodule(track, node);
stats.created++;
if (++index % ElementsPerFrame == 0) yield return null;
}
Debug.Log($"[LegacyImport] Processing {pendingTrackTimeNodes.Count} pending TimeTrack nodes...");
foreach (KeyValuePair<Track, JSONNode> pair in pendingTrackTimeNodes)
{
Track track = pair.Key;
JSONNode node = pair.Value;
Debug.Log($"[LegacyImport] Replace TimeTrack for Track '{track.elementName}'");
ReplaceTrackTimeSubmodule(track, node);
stats.created++;
if (++index % ElementsPerFrame == 0) yield return null;
}
Debug.Log($"[LegacyImport] Processing {pendingTrackRendererNodes.Count} pending TrackRenderer nodes...");
foreach (KeyValuePair<Track, JSONNode> pair in pendingTrackRendererNodes)
{
Track track = pair.Key;
JSONNode node = pair.Value;
Debug.Log($"[LegacyImport] Replace TrackRenderer for Track '{track.elementName}'");
ReplaceTrackRendererSubmodule(track, node);
stats.created++;
if (++index % ElementsPerFrame == 0) yield return null;
}
}
private IEnumerator GenerateLegacyPathNodes(JSONNode elements, LegacyImportStats stats)
{
int pathNodeCount = 0;
for (int i = 0; i < elements.Count; i++)
{
JSONNode node = elements[i];
if (GetLegacyTypeName(node) != "PathNode_BM")
{
continue;
}
pathNodeCount++;
TryCreateLegacyElement(node, () =>
{
Track track = GetLegacyParentOrAncestor(node, e => e is Track) as Track;
if (track == null)
{
Debug.Log($"[LegacyImport] SKIP PathNode_BM: parent Track is null");
return null;
}
if (track.trackPathSubmodule == null)
{
Debug.Log($"[LegacyImport] SKIP PathNode_BM: Track '{track.elementName}' has no TrackPathSubmodule");
return null;
}
int index = Mathf.Clamp(GetInt(node, "index", track.trackPathSubmodule.pathNodeList.Count),
0, track.trackPathSubmodule.pathNodeList.Count);
PathNode pathNode = PathNode.GenerateElement(GetElementName(node, "Legacy PathNode"),
CreateGuid(), new List<string>(), true, track, true, index);
Vector3 position = GetVector3(node["position"], Vector3.zero);
Vector3 normal = GetVector3(node["normal"], Vector3.up);
Color color = GetColor(node["color"], Color.white);
if (legacyTrackColorMultipliers.TryGetValue(track, out Color multiplier))
{
color = MultiplyPathNodeColor(color, multiplier);
}
float size = Mathf.Max(0.001f, GetFloat(node, "size", 1f));
pathNode.transformSubmodule.originalPosition = position;
pathNode.transformSubmodule.originalEulerAngles = NormalToEuler(normal);
pathNode.transformSubmodule.originalScale = Vector3.one * size;
pathNode.transformSubmodule.Refresh();
pathNode.colorSubmodule.originalBaseColor = color;
pathNode.colorSubmodule.Refresh();
pathNode.Refresh();
Debug.Log($"[LegacyImport] PathNode_BM created: '{pathNode.elementName}', index={index}, pos={position}, size={size}");
return pathNode;
}, stats);
if (i % ElementsPerFrame == 0)
{
yield return null;
}
}
Debug.Log($"[LegacyImport] Total PathNode_BM entries processed: {pathNodeCount}");
}
private IEnumerator GenerateLegacyTrackPoints(JSONNode elements, LegacyImportStats stats)
{
int trackPercentPointCount = 0;
for (int i = 0; i < elements.Count; i++)
{
JSONNode node = elements[i];
string typeName = GetLegacyTypeName(node);
switch (typeName)
{
case "TrackPercentPoint_BM":
trackPercentPointCount++;
TryCreateLegacyElement(node, () =>
{
Track parent = GetLegacyParentOrAncestor(node, e => e is Track) as Track;
if (parent == null)
{
Debug.LogWarning($"[LegacyImport] SKIP TrackPercentPoint_BM: Track parent is null, id={GetString(node, "id", "?")}");
return null;
}
TrackPercentPoint point = TrackPercentPoint.GenerateElement(GetElementName(node, "Legacy Track Percent Point"),
CreateGuid(), new List<string>(), true, parent, ConvertLegacyTrackPercentPoint(node));
point.MotionAngles = GetBool(node, "MotionAngles", point.MotionAngles);
point.Refresh();
return point;
}, stats);
break;
}
if (i % ElementsPerFrame == 0)
{
yield return null;
}
}
Debug.Log($"[LegacyImport] TrackPoints summary: TrackPercentPoint={trackPercentPointCount}");
}
private IEnumerator GenerateLegacyNotes(JSONNode elements, LegacyImportStats stats)
{
int[] noteCounts = new int[4]; // 0=Tap, 1=Stay, 2=Hold, 3=unknown skipped
for (int i = 0; i < elements.Count; i++)
{
JSONNode node = elements[i];
string typeName = GetLegacyTypeName(node);
switch (typeName)
{
case "Tap_BM":
noteCounts[0]++;
TryCreateLegacyElement(node, () =>
{
GameElement parent = GetLegacyParentOrAncestor(node, e => e is Track);
if (parent == null)
{
Debug.Log($"[LegacyImport] SKIP Tap_BM: parent is null, id={GetString(node, "id", "?")}");
return null;
}
float judgeTime = GetFloat(node, "exactJudgeTime", 0f);
Debug.Log($"[LegacyImport] Creating Tap_BM at t={judgeTime}, parent='{parent.elementName}'");
return Tap.GenerateElement(GetElementName(node, "Legacy Tap"),
CreateGuid(), new List<string>(), true, parent, judgeTime);
}, stats);
break;
case "Stay_BM":
noteCounts[1]++;
TryCreateLegacyElement(node, () =>
{
GameElement parent = GetLegacyParentOrAncestor(node, e => e is Track);
if (parent == null)
{
Debug.Log($"[LegacyImport] SKIP Stay_BM: parent is null, id={GetString(node, "id", "?")}");
return null;
}
float judgeTime = GetFloat(node, "exactJudgeTime", 0f);
Debug.Log($"[LegacyImport] Creating Stay_BM at t={judgeTime}, parent='{parent.elementName}'");
return Stay.GenerateElement(GetElementName(node, "Legacy Stay"),
CreateGuid(), new List<string>(), true, parent, judgeTime);
}, stats);
break;
case "Hold_BM":
noteCounts[2]++;
TryCreateLegacyElement(node, () =>
{
GameElement parent = GetLegacyParentOrAncestor(node, e => e is Track);
if (parent == null)
{
Debug.Log($"[LegacyImport] SKIP Hold_BM: parent is null, id={GetString(node, "id", "?")}");
return null;
}
float start = GetFloat(node, "exactJudgeTime", 0f);
float end = ConvertLegacyHoldEndTime(node, start);
Debug.Log($"[LegacyImport] Creating Hold_BM: start={start}, end={end}, duration={end - start}, parent='{parent.elementName}'");
return Hold.GenerateElement(GetElementName(node, "Legacy Hold"),
CreateGuid(), new List<string>(), true, parent, start, end);
}, stats);
break;
default:
if (typeName.EndsWith("_BM") && !IsKnownSkippedType(typeName))
{
noteCounts[3]++;
stats.skipped++;
}
break;
}
if (i % ElementsPerFrame == 0)
{
yield return null;
}
}
Debug.Log($"[LegacyImport] Notes summary: Tap={noteCounts[0]}, Stay={noteCounts[1]}, Hold={noteCounts[2]}, unknownSkipped={noteCounts[3]}");
}
private IEnumerator GenerateLegacyNoteVisualOverrides(JSONNode elements, LegacyImportStats stats)
{
int noteVisualCount = 0;
int appliedCount = 0;
for (int i = 0; i < elements.Count; i++)
{
JSONNode node = elements[i];
string typeName = GetLegacyTypeName(node);
if (!IsLegacyNoteVisualType(typeName))
{
continue;
}
noteVisualCount++;
string legacyId = GetString(node, "id", string.Empty);
if (string.IsNullOrEmpty(legacyId) || legacyIdToElement.ContainsKey(legacyId))
{
stats.skipped++;
Debug.Log($"[LegacyImport] SKIP NoteVisual override: duplicate or no ID, type={typeName}, id={legacyId}");
continue;
}
NoteBase note = GetLegacyParentOrAncestor(node, e => e is NoteBase) as NoteBase;
if (note == null || note.noteVisual == null)
{
stats.skipped++;
Debug.Log($"[LegacyImport] SKIP NoteVisual override: Note parent or generated NoteVisual is null, type={typeName}, id={legacyId}");
continue;
}
try
{
IHaveTransformSubmodule transformHost = note.noteVisual;
TransformSubmodule transformSubmodule = transformHost.transformSubmodule;
Vector3 legacyPosition = GetVector3(node["position"], transformSubmodule.originalPosition);
if (note is Hold)
{
legacyPosition *= LegacyHoldNoteVisualPositionScale;
}
transformSubmodule.originalPosition = legacyPosition;
transformSubmodule.originalEulerAngles = GetVector3(node["eulerAngles"], transformSubmodule.originalEulerAngles);
transformSubmodule.originalScale = GetVector3(node["scale"], transformSubmodule.originalScale);
transformSubmodule.Refresh();
transformHost.UpdateTransform(false);
note.noteVisual.Refresh();
legacyIdToElement.Add(legacyId, note.noteVisual);
stats.created++;
appliedCount++;
Debug.Log($"[LegacyImport] APPLY NoteVisual override {typeName} -> Note '{note.elementName}', pos={transformSubmodule.originalPosition}, euler={transformSubmodule.originalEulerAngles}, scale={transformSubmodule.originalScale}, holdPositionScale={(note is Hold ? LegacyHoldNoteVisualPositionScale : 1f)}");
}
catch (Exception e)
{
stats.failed++;
Debug.LogError($"[LegacyImport] FAILED NoteVisual override type={typeName}, id={legacyId}: {e.Message}");
}
if (i % ElementsPerFrame == 0)
{
yield return null;
}
}
Debug.Log($"[LegacyImport] NoteVisual override summary: found={noteVisualCount}, applied={appliedCount}");
}
private IEnumerator GenerateLegacyAnimations(JSONNode elements, LegacyImportStats stats)
{
int displacementCount = 0;
int swirlCount = 0;
int trackColorCount = 0;
int baseColorCount = 0;
int emissionColorCount = 0;
int environmentBaseColorCount = 0;
int environmentEmissionColorCount = 0;
int positionSyncCount = 0;
for (int i = 0; i < elements.Count; i++)
{
JSONNode node = elements[i];
string typeName = GetLegacyTypeName(node);
switch (typeName)
{
case "Displacement_BM":
displacementCount++;
TryCreateLegacyElement(node, () =>
{
GameElement parent = GetLegacyParentOrAncestor(node, e => e is IHaveTransformSubmodule);
if (parent == null)
{
Debug.Log($"[LegacyImport] SKIP Displacement_BM: transform parent is null, id={GetString(node, "id", "?")}");
return null;
}
return Displacement.GenerateElement(GetElementName(node, "Legacy Displacement"),
CreateGuid(), new List<string>(), true, parent,
ConvertLegacyFlexibleFloat(node["positionX"]),
ConvertLegacyFlexibleFloat(node["positionY"]),
ConvertLegacyFlexibleFloat(node["positionZ"]));
}, stats);
break;
case "Swirl_BM":
swirlCount++;
TryCreateLegacyElement(node, () =>
{
GameElement parent = GetLegacyParentOrAncestor(node, e => e is IHaveTransformSubmodule);
if (parent == null)
{
Debug.Log($"[LegacyImport] SKIP Swirl_BM: transform parent is null, id={GetString(node, "id", "?")}");
return null;
}
return Swirl.GenerateElement(GetElementName(node, "Legacy Swirl"),
CreateGuid(), new List<string>(), true, parent,
ConvertLegacyFlexibleFloat(node["swirlX"]),
ConvertLegacyFlexibleFloat(node["swirlY"]),
ConvertLegacyFlexibleFloat(node["swirlZ"]));
}, stats);
break;
case "PositionSync_BM":
positionSyncCount++;
TryCreateLegacyElement(node, () =>
{
GameElement parent = GetLegacyParentOrAncestor(node, e => e is IHaveTransformSubmodule);
if (parent == null)
{
Debug.LogWarning($"[LegacyImport] SKIP PositionSync_BM: transform parent is null, id={GetString(node, "id", "?")}");
return null;
}
GameElement target = GetLegacyPositionSyncTarget(node);
if (target == null)
{
Debug.LogWarning($"[LegacyImport] SKIP PositionSync_BM: target '{GetString(node, "targetElementName", "?")}' not found, id={GetString(node, "id", "?")}");
return null;
}
return PositionSync.GenerateElement(GetElementName(node, "Legacy Position Sync"),
CreateGuid(), new List<string>(), true, parent, target, ConvertLegacyFlexibleBool(node["enabling"], true));
}, stats);
break;
case "BaseColorChange_BM":
baseColorCount++;
TryCreateLegacyElement(node, () =>
{
GameElement parent = GetLegacyParentOrAncestor(node, e => e is IHaveColorSubmodule);
if (parent == null)
{
Debug.LogWarning($"[LegacyImport] SKIP BaseColorChange_BM: color parent is null, id={GetString(node, "id", "?")}");
return null;
}
return BaseColorChange.GenerateElement(GetElementName(node, "Legacy Base Color Change"),
CreateGuid(), new List<string>(), true, parent,
ConvertLegacyFlexibleFloat(node["colorR"]),
ConvertLegacyFlexibleFloat(node["colorG"]),
ConvertLegacyFlexibleFloat(node["colorB"]),
ConvertLegacyFlexibleFloat(node["colorA"]));
}, stats);
break;
case "EmissionColorChange_BM":
emissionColorCount++;
TryCreateLegacyElement(node, () =>
{
GameElement parent = GetLegacyParentOrAncestor(node, e => e is IHaveColorSubmodule);
if (parent == null)
{
Debug.LogWarning($"[LegacyImport] SKIP EmissionColorChange_BM: color parent is null, id={GetString(node, "id", "?")}");
return null;
}
return EmissionColorChange.GenerateElement(GetElementName(node, "Legacy Emission Color Change"),
CreateGuid(), new List<string>(), true, parent,
ConvertLegacyFlexibleFloat(node["colorR"]),
ConvertLegacyFlexibleFloat(node["colorG"]),
ConvertLegacyFlexibleFloat(node["colorB"]),
ConvertLegacyFlexibleFloat(node["colorI"]));
}, stats);
break;
case "EnvironmentObjectBaseColor_BM":
environmentBaseColorCount++;
ApplyLegacyEnvironmentBaseColor(node, stats);
break;
case "EnvironmentObjectEmissionColor_BM":
environmentEmissionColorCount++;
ApplyLegacyEnvironmentEmissionColor(node, stats);
break;
case "TrackGlobalColor_BM":
trackColorCount++;
TryCreateLegacyElement(node, () =>
{
Track parent = GetLegacyParentOrAncestor(node, e => e is Track) as Track;
if (parent == null)
{
Debug.Log($"[LegacyImport] SKIP TrackGlobalColor_BM: Track parent is null, id={GetString(node, "id", "?")}");
return null;
}
return TrackGlobalColorChange.GenerateElement(GetElementName(node, "Legacy Track Global Color"),
CreateGuid(), new List<string>(), true, parent,
ConvertLegacyFlexibleFloat(node["colorR"]),
ConvertLegacyFlexibleFloat(node["colorG"]),
ConvertLegacyFlexibleFloat(node["colorB"]),
ConvertLegacyFlexibleFloat(node["colorA"]));
}, stats);
break;
}
if (i % ElementsPerFrame == 0)
{
yield return null;
}
}
Debug.Log($"[LegacyImport] Animations summary: Displacement={displacementCount}, Swirl={swirlCount}, PositionSync={positionSyncCount}, TrackGlobalColor={trackColorCount}, BaseColor={baseColorCount}, EmissionColor={emissionColorCount}, EnvBaseColor={environmentBaseColorCount}, EnvEmissionColor={environmentEmissionColorCount}");
}
private IEnumerator RefreshLegacyImport(LegacyImportStats stats)
{
int refreshed = 0;
foreach (GameElement element in legacyIdToElement.Values)
{
try
{
if (element is Track track)
{
track.trackPathSubmodule?.Refresh();
track.trackTimeSubmodule?.Refresh();
track.trackRendererSubmodule?.Refresh();
Debug.Log($"[LegacyImport] Refreshed Track: '{track.elementName}' (submodules: path={track.trackPathSubmodule != null}, time={track.trackTimeSubmodule != null}, renderer={track.trackRendererSubmodule != null})");
}
else
{
Debug.Log($"[LegacyImport] Refreshing element: '{element.elementName}' ({element.GetType().Name})");
}
element.Refresh();
refreshed++;
}
catch (Exception e)
{
stats.failed++;
Debug.LogWarning($"[LegacyImport] Refresh failed on '{element.elementName}': {e.Message}");
}
yield return null;
}
Debug.Log($"[LegacyImport] Refresh complete: {refreshed} elements refreshed, {stats.failed} failures");
}
private IEnumerator ApplyLegacyPositionSyncInitialPass()
{
float songTime = CoreServices.TimeProvider?.SongTime ?? 0f;
int syncCount = 0;
foreach (GameElement element in legacyIdToElement.Values)
{
if (element is IHaveTransformSubmodule transformHost)
{
transformHost.UpdateTransform(false);
}
}
foreach (GameElement element in legacyIdToElement.Values)
{
if (element is not PositionSync sync)
{
continue;
}
sync.ScheduledUpdate(UpdatePhase.Animation, songTime);
sync.ScheduledUpdate(UpdatePhase.Effect, songTime);
syncCount++;
yield return null;
}
Debug.Log($"[LegacyImport] PositionSync initial pass complete: {syncCount} syncs applied at songTime={songTime}");
}
private void TryCreateLegacyElement(JSONNode node, Func<GameElement> create, LegacyImportStats stats)
{
string legacyId = GetString(node, "id", string.Empty);
string typeName = GetLegacyTypeName(node);
string elementName = GetElementName(node, "unnamed");
if (string.IsNullOrEmpty(legacyId) || legacyIdToElement.ContainsKey(legacyId))
{
stats.skipped++;
Debug.Log($"[LegacyImport] SKIP (duplicate or no ID) type={typeName}, id={legacyId}, name={elementName}");
return;
}
try
{
GameElement element = create.Invoke();
if (element == null)
{
stats.skipped++;
Debug.Log($"[LegacyImport] SKIP (create returned null) type={typeName}, id={legacyId}, name={elementName}");
return;
}
legacyIdToElement.Add(legacyId, element);
stats.created++;
Debug.Log($"[LegacyImport] CREATE {typeName} -> {element.elementName}, id={legacyId}");
}
catch (Exception e)
{
stats.failed++;
Debug.LogError($"[LegacyImport] FAILED type={typeName}, id={legacyId}, name={elementName}: {e.Message}");
}
}
private void QueueTrackNode(JSONNode node, Dictionary<Track, JSONNode> queue, LegacyImportStats stats)
{
string legacyId = GetString(node, "id", "?");
Track track = GetLegacyParentOrAncestor(node, e => e is Track) as Track;
if (track == null)
{
stats.skipped++;
Debug.Log($"[LegacyImport] SKIP QueueTrackNode id={legacyId}: parent Track not found");
return;
}
queue[track] = node;
Debug.Log($"[LegacyImport] Queued {GetLegacyTypeName(node)} id={legacyId} -> Track '{track.elementName}'");
}
private void ReplaceTrackPathSubmodule(Track track, JSONNode node)
{
track.trackPathSubmodule?.Delete();
track.trackPathSubmodule = new TrackPathSubmodule(track,
(Track.TrackSpaceType)GetInt(node, "trackSpaceType", (int)Track.TrackSpaceType.CatmullRom),
(Track.TrackSamplingType)GetInt(node, "trackSamplingType", (int)Track.TrackSamplingType.TimeDistributed),
GetBool(node, "isClosed", false),
GetBool(node, "isShowingPath", false));
Debug.Log($"[LegacyImport] ReplaceTrackPathSubmodule done for '{track.elementName}': spaceType={track.trackPathSubmodule.trackSpaceType}");
}
private void ReplaceTrackTimeSubmodule(Track track, JSONNode node)
{
track.trackTimeSubmodule?.Delete();
if (GetLegacyTypeName(node) == "TimeTrackStatic_BM")
{
float totalTime = Mathf.Max(0.1f, GetFloat(node, "trackTotalTime", GetFloat(node, "visibleTrackTimeLength", 1f)));
track.trackTimeSubmodule = new TrackTimeSubmoduleStatic(track, totalTime, GetCurveType(node));
Debug.Log($"[LegacyImport] ReplaceTrackTimeSubmodule(Static) for '{track.elementName}': totalTime={totalTime}");
return;
}
float start = GetFloat(node, "trackStartTime", 0f);
float end = Mathf.Max(start + 0.1f, GetFloat(node, "trackEndTime", start + 1f));
float visibleLength = Mathf.Max(0.1f, GetFloat(node, "visibleTrackTimeLength", 1f));
track.trackTimeSubmodule = new TrackTimeSubmoduleMovable(track, start, end, visibleLength, GetCurveType(node));
Debug.Log($"[LegacyImport] ReplaceTrackTimeSubmodule(Movable) for '{track.elementName}': start={start}, end={end}, visibleLength={visibleLength}");
}
private void ReplaceTrackRendererSubmodule(Track track, JSONNode node)
{
track.trackRendererSubmodule?.Delete();
bool enableEmission = GetBool(node, "emissionEnabled", false);
float emissionIntensity = GetFloat(node, "emissionIntensity", 0f);
Color emissionColor = GetColor(node["emissionColor"], Color.white);
legacyTrackColorMultipliers[track] = enableEmission
? new Color(
emissionColor.r * Mathf.Pow(2f, emissionIntensity),
emissionColor.g * Mathf.Pow(2f, emissionIntensity),
emissionColor.b * Mathf.Pow(2f, emissionIntensity),
1f)
: Color.white;
Vector2 uvScale = Vector2.one;
Vector2 uvOffset = Vector2.zero;
if (GetLegacyTypeName(node) == "TrackRendererPathGenerator_BM")
{
track.trackRendererSubmodule = new TrackRendererSubmodulePathGenerator(track, enableEmission,
emissionIntensity, true, uvScale, uvOffset);
Debug.Log($"[LegacyImport] ReplaceTrackRendererSubmodule(PathGenerator) for '{track.elementName}': emission={enableEmission}, intensity={emissionIntensity}");
}
else
{
track.trackRendererSubmodule = new TrackRendererSubmoduleAutoOrient(track, enableEmission,
emissionIntensity, true, uvScale, uvOffset);
Debug.Log($"[LegacyImport] ReplaceTrackRendererSubmodule(AutoOrient) for '{track.elementName}': emission={enableEmission}, intensity={emissionIntensity}");
}
track.trackRendererSubmodule.Refresh();
}
private bool TryFindLegacyEnvironmentObject(JSONNode node, out string themeBundleName, out string objectName)
{
themeBundleName = string.Empty;
objectName = string.Empty;
string legacyObjectName = GetString(node, "objectName", string.Empty);
ThemeBundle themeBundle = ThemeBundleManager.instance.loadedThemeBundleList
.FirstOrDefault(bundle => string.Equals(bundle.themeBundleName, LegacyEnvironmentThemeBundleName, StringComparison.OrdinalIgnoreCase));
if (themeBundle == null)
{
Debug.LogWarning($"[LegacyImport] SKIP EnvironmentObject_BM: theme bundle '{LegacyEnvironmentThemeBundleName}' is not loaded, id={GetString(node, "id", "?")}, oldObject='{legacyObjectName}'");
return false;
}
GameObject asset = themeBundle.assetList_GameObject
.FirstOrDefault(obj => obj != null && string.Equals(obj.name, legacyObjectName, StringComparison.OrdinalIgnoreCase));
if (asset == null)
{
asset = themeBundle.assetList_GameObject
.FirstOrDefault(obj => obj != null && string.Equals(obj.name, LegacyEnvironmentFallbackObjectName, StringComparison.OrdinalIgnoreCase));
if (asset == null)
{
Debug.LogWarning($"[LegacyImport] SKIP EnvironmentObject_BM: object '{legacyObjectName}' and fallback '{LegacyEnvironmentFallbackObjectName}' not found in theme bundle '{themeBundle.themeBundleName}', id={GetString(node, "id", "?")}");
return false;
}
Debug.LogWarning($"[LegacyImport] EnvironmentObject_BM object '{legacyObjectName}' not found in '{themeBundle.themeBundleName}', fallback to '{asset.name}', id={GetString(node, "id", "?")}");
}
themeBundleName = themeBundle.themeBundleName;
objectName = asset.name;
return true;
}
private void ApplyLegacySubstantialObject(SubstantialObject substantialObject, JSONNode node)
{
if (substantialObject == null) return;
if (substantialObject.transformSubmodule != null)
{
substantialObject.transformSubmodule.originalPosition = GetVector3(node["position"], substantialObject.transformSubmodule.originalPosition);
substantialObject.transformSubmodule.originalEulerAngles = GetVector3(node["eulerAngles"], substantialObject.transformSubmodule.originalEulerAngles);
substantialObject.transformSubmodule.originalScale = GetVector3(node["scale"], substantialObject.transformSubmodule.originalScale);
substantialObject.transformSubmodule.Refresh();
((IHaveTransformSubmodule)substantialObject).UpdateTransform(false);
}
ApplyLegacyDuration(substantialObject, node);
substantialObject.Refresh();
}
private void ApplyLegacyEnvironmentBaseColor(JSONNode node, LegacyImportStats stats)
{
string legacyId = GetString(node, "id", string.Empty);
if (string.IsNullOrEmpty(legacyId) || legacyIdToElement.ContainsKey(legacyId))
{
stats.skipped++;
Debug.Log($"[LegacyImport] SKIP EnvironmentObjectBaseColor_BM: duplicate or no ID, id={legacyId}");
return;
}
GameElement parent = GetLegacyParentOrAncestor(node, e => e is IHaveColorSubmodule);
if (parent is not IHaveColorSubmodule colorHost || colorHost.colorSubmodule == null)
{
stats.skipped++;
Debug.LogWarning($"[LegacyImport] SKIP EnvironmentObjectBaseColor_BM: color parent is null, id={legacyId}");
return;
}
ColorSubmodule colorSubmodule = colorHost.colorSubmodule;
colorSubmodule.originalBaseColor = GetColor(node["baseColor"], colorSubmodule.originalBaseColor);
colorSubmodule.Refresh();
colorHost.UpdateColor(false);
parent.Refresh();
legacyIdToElement.Add(legacyId, parent);
stats.created++;
Debug.Log($"[LegacyImport] APPLY EnvironmentObjectBaseColor_BM -> '{parent.elementName}', color={colorSubmodule.originalBaseColor}");
}
private void ApplyLegacyEnvironmentEmissionColor(JSONNode node, LegacyImportStats stats)
{
string legacyId = GetString(node, "id", string.Empty);
if (string.IsNullOrEmpty(legacyId) || legacyIdToElement.ContainsKey(legacyId))
{
stats.skipped++;
Debug.Log($"[LegacyImport] SKIP EnvironmentObjectEmissionColor_BM: duplicate or no ID, id={legacyId}");
return;
}
GameElement parent = GetLegacyParentOrAncestor(node, e => e is IHaveColorSubmodule);
if (parent is not IHaveColorSubmodule colorHost || colorHost.colorSubmodule == null)
{
stats.skipped++;
Debug.LogWarning($"[LegacyImport] SKIP EnvironmentObjectEmissionColor_BM: color parent is null, id={legacyId}");
return;
}
ColorSubmodule colorSubmodule = colorHost.colorSubmodule;
colorSubmodule.emissionEnabled = true;
colorSubmodule.originalEmissionColor = GetColor(node["emissionColor"], colorSubmodule.originalEmissionColor);
colorSubmodule.originalEmissionIntensity = GetFloat(node, "intensity", colorSubmodule.originalEmissionIntensity);
colorSubmodule.Refresh();
colorHost.UpdateColor(false);
parent.Refresh();
legacyIdToElement.Add(legacyId, parent);
stats.created++;
Debug.Log($"[LegacyImport] APPLY EnvironmentObjectEmissionColor_BM -> '{parent.elementName}', color={colorSubmodule.originalEmissionColor}, intensity={colorSubmodule.originalEmissionIntensity}");
}
private void ApplyLegacyDuration(GameElement element, JSONNode node)
{
if (element is not IHaveTimeDurationSubmodule timed) return;
float start = GetFloat(node, "enableStartTime", -32767f);
float end = GetFloat(node, "enableEndTime", 32767f);
if (Mathf.Approximately(start, 0f) && Mathf.Approximately(end, 0f))
{
return;
}
timed.timeDurationSubmodule.isOverridingDuration = true;
timed.timeDurationSubmodule.startTime = start;
timed.timeDurationSubmodule.endTime = end;
}
private GameElement GetLegacyPositionSyncTarget(JSONNode node)
{
string targetElementName = GetString(node, "targetElementName", string.Empty);
if (string.IsNullOrEmpty(targetElementName))
{
return null;
}
GameElement result = null;
int matchCount = 0;
foreach (KeyValuePair<string, GameElement> pair in legacyIdToElement)
{
if (pair.Value is IHaveTransformSubmodule &&
string.Equals(pair.Value.elementName, targetElementName, StringComparison.Ordinal))
{
result ??= pair.Value;
matchCount++;
}
}
if (matchCount > 1)
{
Debug.LogWarning($"[LegacyImport] PositionSync target name '{targetElementName}' matched {matchCount} elements, using '{result.elementName}'");
}
return result;
}
private void ApplyLegacyGameCamera(GameCamera camera, JSONNode node, GameElement parent)
{
if (camera == null) return;
ReparentLegacyElement(camera, parent);
camera.elementName = GetElementName(node, camera.elementName);
camera.cameraViewType = GetCameraViewType(node);
camera.perspectiveAngle = GetFloat(node, "perspectiveAngle", camera.perspectiveAngle);
camera.orthographicSize = GetFloat(node, "orthographicSize", camera.orthographicSize);
if (camera.cam != null)
{
camera.cam.orthographic = camera.cameraViewType == GameCamera.CameraViewType.Orthographic;
camera.cam.fieldOfView = camera.perspectiveAngle;
camera.cam.orthographicSize = camera.orthographicSize;
}
if (camera.transformSubmodule != null)
{
camera.transformSubmodule.originalPosition = GetVector3(node["position"], camera.transformSubmodule.originalPosition);
camera.transformSubmodule.originalEulerAngles = GetVector3(node["eulerAngles"], camera.transformSubmodule.originalEulerAngles);
camera.transformSubmodule.Refresh();
camera.UpdateTransform(false);
}
ApplyLegacyDuration(camera, node);
camera.Refresh();
Debug.Log($"[LegacyImport] ApplyLegacyGameCamera: parent='{parent?.elementName ?? "null"}', pos={camera.transformSubmodule?.originalPosition}, euler={camera.transformSubmodule?.originalEulerAngles}, view={camera.cameraViewType}");
}
private void ReparentLegacyElement(GameElement element, GameElement parent)
{
if (element == null) return;
element.parentElement?.childElementList.Remove(element);
if (parent != null)
{
parent.childElementList.Remove(element);
parent.childElementList.Add(element);
element.parentElement = parent;
element.transform.SetParent(parent.transform);
}
else
{
element.parentElement = null;
element.transform.SetParent(null);
}
RefreshLegacyHierarchyParent(element, parent);
}
private void RefreshLegacyHierarchyParent(GameElement element, GameElement parent)
{
if (EditorManager.instance?.uiManager?.hierarchy == null) return;
if (parent != null)
{
EnsureLegacyHierarchyTab(parent);
}
if (element.connectedTab != null)
{
element.connectedTab.parentTab?.childTabList.Remove(element.connectedTab);
element.connectedTab.SetTab(element, parent, false);
}
else if (parent == null || parent.connectedTab != null)
{
EditorManager.instance.uiManager.hierarchy.GenerateTab(element, parent, false);
}
parent?.connectedTab?.SetStatus();
}
private void EnsureLegacyHierarchyTab(GameElement element)
{
if (element == null || element.connectedTab != null) return;
EnsureLegacyHierarchyTab(element.parentElement);
if (element.parentElement == null || element.parentElement.connectedTab != null)
{
EditorManager.instance.uiManager.hierarchy.GenerateTab(element, element.parentElement, false);
}
}
private GameElement GetLegacyParentOrAncestor(JSONNode node, Func<GameElement, bool> predicate = null)
{
string parentId = GetString(node, "attachedElementId", string.Empty);
HashSet<string> visited = new();
while (!string.IsNullOrEmpty(parentId) && parentId != "BeatMapContainer" && visited.Add(parentId))
{
if (legacyIdToElement.TryGetValue(parentId, out GameElement parentElement) &&
(predicate == null || predicate(parentElement)))
{
return parentElement;
}
if (!legacyIdToNode.TryGetValue(parentId, out JSONNode parentNode))
{
break;
}
if (GetLegacyTypeName(parentNode) == "GameCamera_BM")
{
GameCamera camera = EditorManager.instance?.cameraManager?.gameCamera;
if (camera != null && (predicate == null || predicate(camera)))
{
if (!legacyIdToElement.ContainsKey(parentId))
{
legacyIdToElement.Add(parentId, camera);
Debug.Log($"[LegacyImport] Restored missing GameCamera_BM parent mapping id={parentId} -> '{camera.elementName}'");
}
return camera;
}
}
parentId = GetString(parentNode, "attachedElementId", string.Empty);
}
return null;
}
private ElementFolder CreateFallbackFolder()
{
const string fallbackId = "__legacy_import_fallback_folder";
if (legacyIdToElement.TryGetValue(fallbackId, out GameElement existing))
{
Debug.Log("[LegacyImport] Reusing existing fallback folder");
return existing as ElementFolder;
}
ElementFolder folder = ElementFolder.GenerateElement("Legacy Import", CreateGuid(), new List<string>(), true, null);
legacyIdToElement.Add(fallbackId, folder);
Debug.Log("[LegacyImport] Created fallback folder: 'Legacy Import'");
return folder;
}
private static string GetLegacyTypeName(JSONNode node)
{
string type = GetString(node, "__type", string.Empty);
int commaIndex = type.IndexOf(",", StringComparison.Ordinal);
if (commaIndex >= 0) type = type.Substring(0, commaIndex);
int dotIndex = type.LastIndexOf(".", StringComparison.Ordinal);
return dotIndex >= 0 ? type.Substring(dotIndex + 1) : type;
}
private static bool IsKnownSkippedType(string typeName)
{
return IsLegacyNoteVisualType(typeName) ||
typeName.Contains("NoteEffect") ||
typeName is "Displacement_BM" or "Swirl_BM" or "TrackGlobalColor_BM" or "TimeTrackPoint_BM" or
"EnvironmentObject_BM" or "EnvironmentObjectBaseColor_BM" or "EnvironmentObjectEmissionColor_BM" or
"BaseColorChange_BM" or "EmissionColorChange_BM" or "TrackPercentPoint_BM" or "PositionSync_BM";
}
private static bool IsLegacyNoteVisualType(string typeName)
{
return typeName.Contains("NoteVisual") ||
typeName.Contains("NoteHoldVisual");
}
private static FlexibleInt ConvertLegacyTrackSwitch(JSONNode node)
{
FlexibleInt result = new();
JSONNode list = node["animatedIntList_BM"];
for (int i = 0; i < list.Count; i++)
{
JSONNode item = list[i];
result.Add(new AnimatedInt(GetFloat(item, "startTime", 0f), GetInt(item, "value", 0)));
}
return result;
}
private static FlexibleFloat ConvertLegacyTrackPercent(JSONNode node)
{
return ConvertLegacyFlexibleFloat(node);
}
private static float ConvertLegacyHoldEndTime(JSONNode node, float start)
{
if (HasJsonKey(node, "holdEndTime"))
{
return Mathf.Max(start + 0.1f, GetFloat(node, "holdEndTime", start + 0.1f));
}
float duration = GetFloat(node, "holdTime", 0.1f);
return start + Mathf.Max(0.1f, duration);
}
private static FlexibleFloat ConvertLegacyTrackPercentPoint(JSONNode node)
{
JSONNode trackPercent = node["trackPercent"];
if (trackPercent != null && trackPercent.Count > 0)
{
return ConvertLegacyFlexibleFloat(trackPercent);
}
float percent = GetFloat(node, "percent", 0f);
return new FlexibleFloat(new List<AnimatedFloat>
{
new(0f, 0.1f, percent, percent, AnimationCurveType.Linear)
});
}
private static FlexibleFloat ConvertLegacyFlexibleFloat(JSONNode node)
{
FlexibleFloat result = new();
JSONNode list = node["animatedFloatList_BM"];
for (int i = 0; i < list.Count; i++)
{
JSONNode item = list[i];
float startTime = GetFloat(item, "startTime", 0f);
float endTime = Mathf.Max(startTime + 0.001f, GetFloat(item, "endTime", startTime + 1f));
result.Add(new AnimatedFloat(startTime, endTime,
GetFloat(item, "startValue", 0f),
GetFloat(item, "endValue", 1f),
GetCurveType(item, "presetAnimationCurveType")));
}
result.Sort();
return result;
}
private static FlexibleBool ConvertLegacyFlexibleBool(JSONNode node, bool fallback)
{
FlexibleBool result = new();
if (node == null || node.Count == 0)
{
result.Add(new AnimatedBool(0f, fallback));
return result;
}
JSONNode list = node["animatedBoolList_BM"];
if (list == null || list.Count == 0)
{
list = node["animatedBoolList"];
}
for (int i = 0; i < list.Count; i++)
{
JSONNode item = list[i];
result.Add(new AnimatedBool(GetFloat(item, "time", 0f), GetBool(item, "value", fallback)));
}
if (result.animations.Count == 0)
{
result.Add(new AnimatedBool(0f, fallback));
}
return result;
}
private static AnimationCurveType GetCurveType(JSONNode node)
{
return GetCurveType(node["presetAnimationCurve"], "animationCurveType");
}
private static AnimationCurveType GetCurveType(JSONNode node, string key)
{
int value = GetInt(node, key, 0);
return Enum.IsDefined(typeof(AnimationCurveType), value) ? (AnimationCurveType)value : AnimationCurveType.Linear;
}
private static GameCamera.CameraViewType GetCameraViewType(JSONNode node)
{
int value = GetInt(node, "cameraViewType", (int)GameCamera.CameraViewType.Perspective);
return Enum.IsDefined(typeof(GameCamera.CameraViewType), value)
? (GameCamera.CameraViewType)value
: GameCamera.CameraViewType.Perspective;
}
private static Color MultiplyPathNodeColor(Color color, Color multiplier)
{
return new Color(color.r * multiplier.r, color.g * multiplier.g, color.b * multiplier.b, color.a);
}
private static Vector3 GetVector3(JSONNode node, Vector3 fallback)
{
if (node == null) return fallback;
return new Vector3(GetFloat(node, "x", fallback.x), GetFloat(node, "y", fallback.y), GetFloat(node, "z", fallback.z));
}
private static Color GetColor(JSONNode node, Color fallback)
{
if (node == null) return fallback;
return new Color(GetFloat(node, "r", fallback.r), GetFloat(node, "g", fallback.g),
GetFloat(node, "b", fallback.b), GetFloat(node, "a", fallback.a));
}
private static Vector3 NormalToEuler(Vector3 normal)
{
if (normal.sqrMagnitude < 0.0001f) return Vector3.zero;
return Quaternion.FromToRotation(Vector3.up, normal.normalized).eulerAngles;
}
private static string GetElementName(JSONNode node, string fallback)
{
return GetString(node, "elementName", fallback);
}
private static string GetString(JSONNode node, string key, string fallback)
{
if (node == null || node[key] is JSONLazyCreator) return fallback;
string value = node[key];
return string.IsNullOrEmpty(value) ? fallback : value;
}
private static int GetInt(JSONNode node, string key, int fallback)
{
if (node == null || node[key] is JSONLazyCreator) return fallback;
return node[key].AsInt;
}
private static float GetFloat(JSONNode node, string key, float fallback)
{
if (node == null || node[key] is JSONLazyCreator) return fallback;
return node[key].AsFloat;
}
private static bool HasJsonKey(JSONNode node, string key)
{
return node != null && node[key] is not JSONLazyCreator;
}
private static bool GetBool(JSONNode node, string key, bool fallback)
{
if (node == null || node[key] is JSONLazyCreator) return fallback;
return node[key].AsBool;
}
private static Guid CreateGuid()
{
return Guid.NewGuid();
}
private sealed class LegacyImportStats
{
public int created;
public int skipped;
public int failed;
}
}