优化:

Signed-off-by: TRADER_FOER <lhf190@outlook.com>
This commit is contained in:
2026-06-29 22:17:57 +08:00
parent a6f24c4258
commit 5158c529f7
74 changed files with 935476 additions and 18854 deletions

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Ichni.RhythmGame.Beatmap
{
@@ -35,5 +36,15 @@ namespace Ichni.RhythmGame.Beatmap
{
(matchedElement as DisplacementTracker).targetDisplacement = GetElement(targetDisplacementGuid) as ICanBeTrackedDisplacement;
}
/// <summary>
/// 不应通过子模块路径到达此处——Tracker_BM 由 IExportOverride 在父级处理。
/// 如被执行说明数据流异常,报错以便排查。
/// </summary>
public void OnExport(BaseElement_BM parentBM)
{
Debug.LogError($"DisplacementTracker_BM 不应出现在导出子模块中!" +
$"应通过 IExportOverride.GetExportElement() 在父级处理。element: {elementName}");
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Ichni.RhythmGame.Beatmap
{
@@ -35,5 +36,15 @@ namespace Ichni.RhythmGame.Beatmap
{
(matchedElement as ScaleTracker).targetScale = GetElement(targetScaleGuid) as ICanBeTrackedScale;
}
/// <summary>
/// 不应通过子模块路径到达此处——Tracker_BM 由 IExportOverride 在父级处理。
/// 如被执行说明数据流异常,报错以便排查。
/// </summary>
public void OnExport(BaseElement_BM parentBM)
{
Debug.LogError($"ScaleTracker_BM 不应出现在导出子模块中!" +
$"应通过 IExportOverride.GetExportElement() 在父级处理。element: {elementName}");
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Ichni.RhythmGame.Beatmap
{
@@ -35,5 +36,15 @@ namespace Ichni.RhythmGame.Beatmap
{
(matchedElement as SwirlTracker).targetSwirl = GetElement(targetSwirlGuid) as ICanBeTrackedSwirl;
}
/// <summary>
/// 不应通过子模块路径到达此处——Tracker_BM 由 IExportOverride 在父级处理。
/// 如被执行说明数据流异常,报错以便排查。
/// </summary>
public void OnExport(BaseElement_BM parentBM)
{
Debug.LogError($"SwirlTracker_BM 不应出现在导出子模块中!" +
$"应通过 IExportOverride.GetExportElement() 在父级处理。element: {elementName}");
}
}
}

View File

@@ -30,7 +30,64 @@ namespace Ichni.RhythmGame.Beatmap
return Vector3Interferometer.GenerateElement(elementName, Guid.NewGuid(), tags, false,
parent as AnimationBase, InterferomType, InterferomValueVector3);
}
/// <summary>
/// 导出预处理:将自己的干涉仪效果烘焙到父元素的动画数据中。
/// </summary>
public void OnExport(BaseElement_BM parentBM)
{
// 将干涉仪值按分量应用到父元素的 FlexibleFloat_BM 数据上
if (parentBM is Swirl_BM swirl)
{
ApplyToFlexibleFloat(swirl.eulerAngleX, 0);
ApplyToFlexibleFloat(swirl.eulerAngleY, 1);
ApplyToFlexibleFloat(swirl.eulerAngleZ, 2);
}
else if (parentBM is Scale_BM scale)
{
ApplyToFlexibleFloat(scale.scaleX, 0);
ApplyToFlexibleFloat(scale.scaleY, 1);
ApplyToFlexibleFloat(scale.scaleZ, 2);
}
else if (parentBM is Displacement_BM disp)
{
ApplyToFlexibleFloat(disp.positionX, 0);
ApplyToFlexibleFloat(disp.positionY, 1);
ApplyToFlexibleFloat(disp.positionZ, 2);
}
}
private void ApplyToFlexibleFloat(FlexibleFloat_BM flex, int axis)
{
if (flex == null) return;
float value = InterferomValueVector3[axis];
foreach (var anim in flex.animatedFloatList)
{
switch (InterferomType)
{
case InterferomType.Additive:
anim.startValue += value;
anim.endValue += value;
break;
case InterferomType.Multiplicative:
anim.startValue *= value;
anim.endValue *= value;
break;
case InterferomType.Override:
anim.startValue = value;
anim.endValue = value;
break;
}
}
}
}
public interface ICanNotInExport { }
public interface ICanNotInExport
{
/// <summary>
/// 导出时预处理:将自身效果烘焙到 parentBM 上,或执行其他必要变换。
/// 调用后该元素将被从导出结果中移除。
/// </summary>
void OnExport(BaseElement_BM parentBM);
}
}

View File

@@ -56,10 +56,27 @@ namespace Ichni.RhythmGame.Beatmap
}
}
// 导出时跳过不应出现在最终结果中的主元素(如干涉仪 BM、Tracker BM
// 这些元素的效果已在父级 GetExportElement() 中烘焙完毕
if (forExport && elementToAdd is ICanNotInExport)
{
continue;
}
elementList.Add(elementToAdd);
List<BaseElement_BM> submodules = gameElement.submoduleList.ConvertAll(s => s.matchedBM);
submodules.RemoveAll(s => s == null);
// 导出时ICanNotInExport 子模块先执行导出策略(如将干涉仪效果烘焙到父元素),再移除
if (forExport)
{
foreach (var sub in submodules.OfType<ICanNotInExport>())
{
sub.OnExport(elementToAdd);
}
submodules.RemoveAll(s => s is ICanNotInExport);
}
// 当使用了替代元素时,将其 Guid 附给子模块
if (elementToAdd != gameElement.matchedBM)
{

View File

@@ -13,14 +13,16 @@ namespace Ichni.RhythmGame
#region [UI映射] Hierarchy & UI Mapping
//与游戏物体连接的Tab
public HierarchyTab connectedTab;
public Action RefreshAction;
public class EnableType : IBaseElement
{
public Type type;
public bool enable;
public BaseElement_BM matchedBM { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
}
public List<EnableType> enableTypes;
#endregion
@@ -30,6 +32,7 @@ namespace Ichni.RhythmGame
{
if (connectedTab != null) connectedTab.tabButtonText.text = this.elementName;
gameObject.name = elementName;
RefreshAction?.Invoke();
}
/// <summary>
@@ -65,7 +68,7 @@ namespace Ichni.RhythmGame
Destroy(gameObject); //销毁
}
#endregion
#region [] Inspector UI
/// <summary>
@@ -134,7 +137,7 @@ namespace Ichni.RhythmGame
#endregion
#region [] Utility Nodes Retrieving
/// <summary>
/// 获取所有子GameElement
/// </summary>
@@ -171,7 +174,7 @@ namespace Ichni.RhythmGame
{
if (et.enable) enabledTypes.Add(et.type);
}
return childElementList.FindAll(child =>
child != null && enabledTypes.Any(t => t.IsAssignableFrom(child.GetType()))
);

View File

@@ -54,8 +54,17 @@ namespace Ichni.RhythmGame
#endregion
#region [] Tool Methods
public Action PasteAction = null;
private void PasteTrackList()
{
if (PasteAction == null)
{
PasteAction = () =>
{
PasteTrackList();
};
}
List<Track> trackList = (parentElement as ElementFolder).trackList;
trackSwitch = new FlexibleInt();
trackPercent = new FlexibleFloat();
@@ -66,6 +75,8 @@ namespace Ichni.RhythmGame
trackList.IndexOf(track)));
trackPercent.animations.Add(new AnimatedFloat(trackTimeSubmodule.trackStartTime,
trackTimeSubmodule.trackEndTime, 0, 1, trackTimeSubmodule.animationCurveType));
track.RefreshAction -= PasteAction;
track.RefreshAction += PasteAction;
}
}
#endregion

View File

@@ -193,8 +193,12 @@ namespace Ichni.RhythmGame
Refresh();
SaveBM();
parentElement.SaveBM();
return new Displacement_BM(elementName, elementGuid, tags, parentElement.matchedBM as GameElement_BM,
var result = new Displacement_BM(elementName, elementGuid, tags, parentElement.matchedBM as GameElement_BM,
positionX.ConvertToBM(), positionY.ConvertToBM(), positionZ.ConvertToBM());
// 将 Tracker 自身的干涉仪效果烘焙到导出数据中
((IHaveVector3Interferometer)this).ApplyVector3InterferometersBM(
result.positionX, result.positionY, result.positionZ);
return result;
}
#endregion

View File

@@ -184,8 +184,12 @@ namespace Ichni.RhythmGame
Refresh();
SaveBM();
parentElement.SaveBM();
return new Scale_BM(elementName, elementGuid, tags, parentElement.matchedBM as GameElement_BM,
var result = new Scale_BM(elementName, elementGuid, tags, parentElement.matchedBM as GameElement_BM,
scaleX.ConvertToBM(), scaleY.ConvertToBM(), scaleZ.ConvertToBM());
// 将 Tracker 自身的干涉仪效果烘焙到导出数据中
((IHaveVector3Interferometer)this).ApplyVector3InterferometersBM(
result.scaleX, result.scaleY, result.scaleZ);
return result;
}
#endregion
}

View File

@@ -184,8 +184,12 @@ namespace Ichni.RhythmGame
Refresh();
SaveBM();
parentElement.SaveBM();
return new Swirl_BM(elementName, elementGuid, tags, parentElement.matchedBM as GameElement_BM,
var result = new Swirl_BM(elementName, elementGuid, tags, parentElement.matchedBM as GameElement_BM,
eulerAngleX.ConvertToBM(), eulerAngleY.ConvertToBM(), eulerAngleZ.ConvertToBM());
// 将 Tracker 自身的干涉仪效果烘焙到导出数据中
((IHaveVector3Interferometer)this).ApplyVector3InterferometersBM(
result.eulerAngleX, result.eulerAngleY, result.eulerAngleZ);
return result;
}
#endregion
}

View File

@@ -80,7 +80,8 @@ namespace Ichni.RhythmGame
var export = new BeatmapContainer_BM(gameElementList, true);
if (export.elementList.Any(i => i is ICanNotInExport))
{
Debug.LogError("Export Error!");
Debug.LogError($"导出失败elementList 仍包含 ICanNotInExport 元素。" +
$"这些元素本应在 BeatmapContainer_BM 构造时被过滤。");
}
return export;
}

View File

@@ -43,7 +43,7 @@ namespace Ichni.RhythmGame
{
".mp3" => LoadMP3(songLocation),
".ogg" => ES3.LoadAudio(songLocation, AudioType.OGGVORBIS),
".wav" => ES3.LoadAudio(songLocation, AudioType.WAV),
".wav" => LoadWav(songLocation),
_ => throw new Exception("Unsupported audio format: " + extension)
};
@@ -75,6 +75,84 @@ namespace Ichni.RhythmGame
position => { mpegFile = new MpegFile(filepath); });
return ac;
}
/// <summary>手动解码 WAV 文件为 PCM AudioClip。
/// 支持 8/16/24/32-bit PCM 以及 32-bit IEEE floatformatTag=3
/// Unity 的 AudioType.WAV 不支持 32-bit WAV导出版本 GetData 返回全零),
/// 所以需要自己解析 WAV 头 + 转换采样到 float PCM。</summary>
private AudioClip LoadWav(string filepath)
{
string filename = Path.GetFileNameWithoutExtension(filepath);
using var stream = File.OpenRead(filepath);
using var reader = new BinaryReader(stream);
// ── RIFF 头 ──
if (new string(reader.ReadChars(4)) != "RIFF")
throw new Exception("Not a valid WAV file");
reader.ReadInt32(); // file size
if (new string(reader.ReadChars(4)) != "WAVE")
throw new Exception("Not a valid WAV file");
int channels = 0, sampleRate = 0, bitsPerSample = 0;
short formatTag = 1; // 1 = PCM, 3 = IEEE float
float[] samples = null;
// ── 逐 chunk 解析 ──
while (stream.Position < stream.Length - 8)
{
string chunkId = new string(reader.ReadChars(4));
int chunkSize = reader.ReadInt32();
long chunkEnd = stream.Position + chunkSize;
switch (chunkId)
{
case "fmt ":
formatTag = reader.ReadInt16();
channels = reader.ReadInt16();
sampleRate = reader.ReadInt32();
reader.ReadInt32(); // avgBytesPerSec
reader.ReadInt16(); // blockAlign
bitsPerSample = reader.ReadInt16();
break;
case "data":
int bytesPerSample = bitsPerSample / 8;
int totalFloats = chunkSize / bytesPerSample;
samples = new float[totalFloats];
for (int i = 0; i < totalFloats; i++)
{
if (formatTag == 3 && bitsPerSample == 32) // IEEE float
samples[i] = reader.ReadSingle();
else if (bitsPerSample == 32) // 32-bit PCM
samples[i] = reader.ReadInt32() / 2147483648f;
else if (bitsPerSample == 24) // 24-bit PCM
{
int s = reader.ReadByte() | (reader.ReadByte() << 8) | (reader.ReadByte() << 16);
if ((s & 0x800000) != 0) s |= ~0xFFFFFF; // sign extend
samples[i] = s / 8388607f;
}
else if (bitsPerSample == 16) // 16-bit PCM
samples[i] = reader.ReadInt16() / 32767f;
else if (bitsPerSample == 8) // 8-bit PCM (unsigned)
samples[i] = (reader.ReadByte() - 128) / 128f;
}
break;
}
// 跳到 chunk 末尾(含 word alignment padding
stream.Position = chunkEnd;
if ((chunkSize & 1) != 0) stream.Position++;
}
if (samples == null)
throw new Exception("No data chunk found in WAV file");
int totalSamples = samples.Length / channels;
AudioClip ac = AudioClip.Create(filename, totalSamples, channels, sampleRate, false);
ac.SetData(samples, 0);
return ac;
}
#endregion
#region [] Inspector