设置扩展

This commit is contained in:
SoulliesOfficial
2026-07-19 16:44:58 -04:00
parent dda354ebb9
commit 9fd5f098ab
110 changed files with 8386 additions and 2311 deletions

View File

@@ -213,7 +213,7 @@ Material:
- _Dst: 10
- _DstBlend: 0
- _DstBlendAlpha: 0
- _EdgeValue: 0.5578781
- _EdgeValue: 0.99275833
- _EnvironmentReflections: 1
- _FNLfanxiangkaiguan: 0
- _Face: 1
@@ -258,7 +258,7 @@ Material:
- _Mask_scale: 1
- _Metallic: 0
- _OcclusionStrength: 1
- _Opacity: 0.44212192
- _Opacity: 0.0072416663
- _Parallax: 0.005
- _Pass: 0
- _QueueOffset: 0

View File

@@ -21,7 +21,6 @@ Material:
- _CIRCLE_ON_ON
- _CUSTOMDATA
- _CUSTOMDATADIS_ON
- _CUSTOMDATA_OFF
- _DISSOLVEAR_ON
- _DISSOLVEPLUSTEXAR_ON
- _DISSOLVEPLUSTEXUVCLIP_ON
@@ -336,8 +335,8 @@ Material:
- _BaseMapFoldOut: 0
- _BaseMapUVRotation: 0
- _BaseMapUVRotationSpeed: 0
- _BaseOptionBigBlockItemFoldOut: 0
- _BigBlockModeSettingFoldOut: 0
- _BaseOptionBigBlockItemFoldOut: 1
- _BigBlockModeSettingFoldOut: 1
- _Blend: 0
- _BlendMode: 0
- _BlendModePreserveSpecular: 1
@@ -374,7 +373,7 @@ Material:
- _Contrast: 1
- _ContrastFoldOut: 0
- _Contrast_Toggle: 0
- _Cull: 2
- _Cull: 0
- _CullMode: 0
- _Cullmode: 0
- _CustomData: 1

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0116a7bc799e0ef48b8adfaffa13c043
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 039e11588f40d8c4b83e546855d509fc
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,28 @@
<linker>
<assembly fullname="Unity.Addressables, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" preserve="all">
<type fullname="UnityEngine.AddressableAssets.Addressables" preserve="all" />
</assembly>
<assembly fullname="Unity.Localization, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="UnityEngine.Localization.Locale" preserve="all" />
<type fullname="UnityEngine.Localization.Tables.SharedTableData" preserve="all" />
<type fullname="UnityEngine.Localization.Tables.StringTable" preserve="all" />
<type fullname="UnityEngine.Localization.LocaleIdentifier" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Localization.Metadata.MetadataCollection" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Localization.Tables.TableEntryData" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Localization.Tables.DistributedUIDGenerator" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Localization.Tables.SharedTableData/SharedTableEntry" preserve="nothing" serialized="true" />
<type fullname="UnityEngine.Localization.Metadata.FallbackLocale" preserve="nothing" serialized="true" />
</assembly>
<assembly fullname="Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" preserve="all">
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider" preserve="all" />
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider" preserve="all" />
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.InstanceProvider" preserve="all" />
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.SceneProvider" preserve="all" />
</assembly>
<assembly fullname="UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<type fullname="UnityEngine.Object" preserve="all" />
</assembly>
<assembly fullname="YarnSpinner.Unity">
<type fullname="Yarn.Unity.UnityLocalization.LineMetadata" preserve="nothing" serialized="true" />
</assembly>
</linker>

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 5f22cc995c7cc9348ba8e089a0f4193c
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 0f8134d47a03bfe4184e2d4ccafe0e03
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,276 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &4028587832294944382
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4844063139079138537}
- component: {fileID: 2675052568297457887}
- component: {fileID: 314392640301343753}
m_Layer: 5
m_Name: Text (TMP)
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &4844063139079138537
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4028587832294944382}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 3372188176019791897}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &2675052568297457887
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4028587832294944382}
m_CullTransparentMesh: 1
--- !u!114 &314392640301343753
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4028587832294944382}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: Button
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 87c1b020037c81841b3d98dd497cbdeb, type: 2}
m_sharedMaterial: {fileID: -5812992765432063453, guid: 87c1b020037c81841b3d98dd497cbdeb,
type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: -1183493901
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 30
m_fontSizeBase: 30
m_fontWeight: 400
m_enableAutoSizing: 1
m_fontSizeMin: 18
m_fontSizeMax: 30
m_fontStyle: 0
m_HorizontalAlignment: 2
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_characterHorizontalScale: 1
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 0
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!1 &8350981944019684425
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3372188176019791897}
- component: {fileID: 2182965281669324128}
- component: {fileID: 1480504358368706369}
- component: {fileID: 3951511035263236737}
- component: {fileID: 449795761659242553}
m_Layer: 5
m_Name: SelectionBoxButton
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &3372188176019791897
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8350981944019684425}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 4844063139079138537}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 250, y: 50}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &2182965281669324128
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8350981944019684425}
m_CullTransparentMesh: 1
--- !u!114 &1480504358368706369
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8350981944019684425}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: a42035fa13ca6c849afeedc8abcf13a7, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &3951511035263236737
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8350981944019684425}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Button
m_Navigation:
m_Mode: 3
m_WrapAround: 0
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
m_SelectOnRight: {fileID: 0}
m_Transition: 1
m_Colors:
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
m_HighlightedSprite: {fileID: 0}
m_PressedSprite: {fileID: 0}
m_SelectedSprite: {fileID: 0}
m_DisabledSprite: {fileID: 0}
m_AnimationTriggers:
m_NormalTrigger: Normal
m_HighlightedTrigger: Highlighted
m_PressedTrigger: Pressed
m_SelectedTrigger: Selected
m_DisabledTrigger: Disabled
m_Interactable: 1
m_TargetGraphic: {fileID: 1480504358368706369}
m_OnClick:
m_PersistentCalls:
m_Calls: []
--- !u!114 &449795761659242553
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8350981944019684425}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: b2c45ae5390d4a4aa16e62b5704dbf0a, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Ichni.Menu.UI.SelectionBoxButton
button: {fileID: 3951511035263236737}
labelText: {fileID: 314392640301343753}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 5b3427e100cf597448a79c121640e3d5
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -10124,7 +10124,7 @@ MonoBehaviour:
PrefabModifications: []
SerializationNodes: []
isLoading: 1
isStarting: 1
isStarting: 0
isDelaying: 0
isPlaying: 0
isPausing: 0

File diff suppressed because it is too large Load Diff

View File

@@ -21,7 +21,7 @@ namespace Ichni.RhythmGame.Beatmap
#if UNITY_STANDALONE
return new FullScreenNearTimeJudgeUnit(attachedNote);
#elif UNITY_ANDROID || UNITY_IOS
if (SettingsManager.instance.gameSettings.ShouldUseFullScreenJudge())
if (SettingsManager.instance.ShouldUseFullScreenJudge())
{
return new FullScreenNearTimeJudgeUnit(attachedNote);
}
@@ -30,7 +30,7 @@ namespace Ichni.RhythmGame.Beatmap
return new TouchAreaJudgeUnit(attachedNote, areaRadius);
}
#else
return SettingsManager.instance.gameSettings.ShouldUseFullScreenJudge()
return SettingsManager.instance.ShouldUseFullScreenJudge()
? new FullScreenNearTimeJudgeUnit(attachedNote)
: new TouchAreaJudgeUnit(attachedNote, areaRadius);
#endif

View File

@@ -32,6 +32,47 @@ namespace Ichni.RhythmGame
}
}
/// <summary>
/// Rebuilds the list of elements whose active state is driven by a finite time duration.
/// This preserves the existing runtime filter, while avoiding a LINQ allocation during
/// every project load.
/// </summary>
public void RebuildRuntimeTimeDurations()
{
List<TimeDurationSubmodule> runtimeTimeDurations = GameManager.Instance.timeDurations;
runtimeTimeDurations.Clear();
float songLength = GameManager.Instance.songInformation.songLength;
for (int i = 0; i < gameElementList.Count; i++)
{
if (gameElementList[i] is not IHaveTimeDurationSubmodule timeDurationHost)
{
continue;
}
TimeDurationSubmodule timeDuration = timeDurationHost.timeDurationSubmodule;
if (timeDuration != null &&
(timeDuration.startTime > 0f || timeDuration.endTime < songLength))
{
runtimeTimeDurations.Add(timeDuration);
}
}
}
/// <summary>
/// Immediately applies the active state for the registered time-duration elements.
/// Used at loading-state boundaries so the first rendered frame already matches the
/// current song timeline.
/// </summary>
public void SyncRuntimeTimeDurationStates(float songTime)
{
List<TimeDurationSubmodule> runtimeTimeDurations = GameManager.Instance.timeDurations;
for (int i = 0; i < runtimeTimeDurations.Count; i++)
{
runtimeTimeDurations[i]?.DetectAndSwitchActiveState(songTime);
}
}
public void SetUpInspector()
{
throw new System.NotImplementedException();
@@ -43,4 +84,4 @@ namespace Ichni.RhythmGame
}
}
}
}

View File

@@ -0,0 +1,113 @@
using System.Collections.Generic;
using Sirenix.OdinInspector;
using SLSUtilities.LeanPoolAssistance;
using UnityEngine;
namespace Ichni
{
/// <summary>
/// 面向对象池特效的通用质量控制组件。
///
/// 挂载在由 LeanPool 生成与回收的 VFX 根节点上。每次 <see cref="OnSpawn"/> 时会先恢复受管理子对象的
/// Prefab 初始激活状态,再依据当前 <see cref="VfxQuality"/> 关闭非必要的视觉子树。
/// 设置切换仅影响之后新生成的 VFX不会中断正在播放的效果。
/// </summary>
public class VFXObject : PooledObject
{
[Title("VFX Quality")]
[Tooltip("Medium 与 Low 都会关闭的次要视觉子树。应放入高频、长寿命或大面积覆盖的装饰层。")]
public List<GameObject> disableAtMediumAndLower = new List<GameObject>();
[Tooltip("仅 Low 会额外关闭的视觉子树。Medium 仍保留这些效果以维持基本演出层次。")]
public List<GameObject> disableAtLowOnly = new List<GameObject>();
// 仅缓存两个质量列表中出现过的对象。没有被列出的子对象永远不受本组件控制。
private readonly Dictionary<GameObject, bool> originalActiveStates = new Dictionary<GameObject, bool>();
private bool qualityObjectsCached;
private void Awake()
{
CacheOriginalActiveStates();
}
/// <summary>
/// 对象从 LeanPool 取出时执行一次质量裁剪。
/// 先恢复原始状态可避免同一个对象先以 Low 生成、回收后再以 High 生成时仍错误保持隐藏。
/// </summary>
public override void OnSpawn()
{
// LeanPool 的 Broadcast IPoolable 模式或错误的重复通知不应重新裁剪一个已播放的效果。
if (spawnExecuted)
{
return;
}
CacheOriginalActiveStates();
RestoreOriginalActiveStates();
ApplyCurrentQuality();
base.OnSpawn();
}
/// <summary>
/// 读取生成瞬间的 VFX Quality 并关闭对应的完整视觉子树。
/// 未初始化 SettingsManager 的编辑器预览或独立加载流程默认使用 High保持作者配置的完整视觉。
/// </summary>
private void ApplyCurrentQuality()
{
VfxQuality currentQuality = SettingsManager.instance?.settingsSaveData?.vfxQuality ?? VfxQuality.High;
if (currentQuality <= VfxQuality.Medium)
{
SetObjectsActive(disableAtMediumAndLower, false);
}
if (currentQuality == VfxQuality.Low)
{
SetObjectsActive(disableAtLowOnly, false);
}
}
/// <summary>
/// 记录 Quality 列表对象在 Prefab 中的初始激活状态。
/// 这样即使某个对象本来就由作者设为 inactive也不会在下一次 Spawn 时被强制打开。
/// </summary>
private void CacheOriginalActiveStates()
{
if (qualityObjectsCached)
{
return;
}
CacheOriginalActiveStates(disableAtMediumAndLower);
CacheOriginalActiveStates(disableAtLowOnly);
qualityObjectsCached = true;
}
private void CacheOriginalActiveStates(List<GameObject> qualityObjects)
{
foreach (GameObject qualityObject in qualityObjects)
{
if (!originalActiveStates.ContainsKey(qualityObject))
{
originalActiveStates.Add(qualityObject, qualityObject.activeSelf);
}
}
}
private void RestoreOriginalActiveStates()
{
foreach (KeyValuePair<GameObject, bool> originalState in originalActiveStates)
{
originalState.Key.SetActive(originalState.Value);
}
}
private static void SetObjectsActive(List<GameObject> qualityObjects, bool isActive)
{
foreach (GameObject qualityObject in qualityObjects)
{
qualityObject.SetActive(isActive);
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 922e341faf5784c41b3b0466e0468972

View File

@@ -68,6 +68,12 @@ namespace Ichni.RhythmGame
public override void Execute()
{
// 玩家关闭游戏内音乐特效时,不再覆盖 SettingsManager 已归零的 RTPC。
if (!SettingsManager.instance.settingsSaveData.enableInGameAudioEffects)
{
return;
}
// 在 10Hz无效果与 peak Hz最强截止之间基于曲线插值
float intensity = intensityCurve != null ? intensityCurve.Evaluate(effectProgressPercent) : 0f;
float currentFreq = Mathf.Lerp(MinFrequency, peak, intensity);

View File

@@ -66,6 +66,12 @@ namespace Ichni.RhythmGame
public override void Execute()
{
// 玩家关闭游戏内音乐特效时,不再覆盖 SettingsManager 已归零的 RTPC。
if (!SettingsManager.instance.settingsSaveData.enableInGameAudioEffects)
{
return;
}
// 在 22000Hz无效果与 bottom Hz最强截止之间基于曲线插值
float intensity = intensityCurve != null ? intensityCurve.Evaluate(effectProgressPercent) : 0f;
float currentFreq = Mathf.Lerp(MaxFrequency, bottom, intensity);

View File

@@ -87,7 +87,7 @@ namespace Ichni.RhythmGame
if (!GameManager.Instance.songPlayer.isUpdating || isFinalJudged) return true;
if (SettingsManager.instance.gameSettings.autoPlay && isFirstJudged) ExecuteProcessJudge();
if (SettingsManager.instance.settingsSaveData.autoPlay && isFirstJudged) ExecuteProcessJudge();
if (isHolding)
{
@@ -109,7 +109,7 @@ namespace Ichni.RhythmGame
UpdateHoldEffects();
if (SettingsManager.instance.gameSettings.autoPlay && !isFirstJudged && currentSongTime >= exactJudgeTime)
if (SettingsManager.instance.settingsSaveData.autoPlay && !isFirstJudged && currentSongTime >= exactJudgeTime)
{
ExecuteStartJudge(currentSongTime);
}
@@ -227,7 +227,7 @@ namespace Ichni.RhythmGame
protected override void SetJudgeArea()
{
if (!SettingsManager.instance.gameSettings.debugMode || NoteJudgeSubmodule == null) return;
if (!SettingsManager.instance.settingsSaveData.debugMode || NoteJudgeSubmodule == null) return;
if (isDuringJudging && !isFirstJudged)
{

View File

@@ -201,7 +201,7 @@ namespace Ichni.RhythmGame
}
// AutoPlay
if (SettingsManager.instance.gameSettings.autoPlay && !isFirstJudged && currentSongTime >= exactJudgeTime)
if (SettingsManager.instance.settingsSaveData.autoPlay && !isFirstJudged && currentSongTime >= exactJudgeTime)
{
ExecuteStartJudge(currentSongTime);
}
@@ -239,7 +239,7 @@ namespace Ichni.RhythmGame
protected virtual void SetJudgeArea()
{
if (!SettingsManager.instance.gameSettings.debugMode || NoteJudgeSubmodule?.judgeUnitList == null) return;
if (!SettingsManager.instance.settingsSaveData.debugMode || NoteJudgeSubmodule?.judgeUnitList == null) return;
if (isDuringJudging && !isFirstJudged)
{

View File

@@ -63,7 +63,7 @@ namespace Ichni.RhythmGame
if(trackRendererSubmodule != null)
{
trackRendererSubmodule.meshGenerator.autoUpdate = false;
trackRendererSubmodule.meshGenerator.autoUpdate = true;
}
}
#endregion
@@ -83,7 +83,7 @@ namespace Ichni.RhythmGame
}
#region [IScheduledElement] Scheduler Interface
public void ScheduledUpdate(UpdatePhase phase, float songTime)
public override void ScheduledUpdate(UpdatePhase phase, float songTime)
{
ManualUpdate(songTime);
}

View File

@@ -89,10 +89,10 @@ namespace Ichni.RhythmGame
return;
}
if (forceImmediateRebuild)
/*if (forceImmediateRebuild)
{
meshGenerator.RebuildImmediate();
}
}*/
}
public void RequestMeshRebuild()

View File

@@ -100,7 +100,7 @@ namespace Ichni.RhythmGame
private void OnTap(int id, Vector2 position)
{
if (SettingsManager.instance.gameSettings.debugMode)
if (SettingsManager.instance.settingsSaveData.debugMode)
{
GenerateTapMark(id, position);
}
@@ -110,7 +110,7 @@ namespace Ichni.RhythmGame
private void OnTouch(int id, Vector2 position)
{
if (SettingsManager.instance.gameSettings.debugMode)
if (SettingsManager.instance.settingsSaveData.debugMode)
{
GenerateTouchMark(id, position);
}
@@ -120,7 +120,7 @@ namespace Ichni.RhythmGame
private void OnSwipe(int id, Vector2 position, bool isGeneric, bool isFirst, Vector2 direction)
{
if (SettingsManager.instance.gameSettings.debugMode)
if (SettingsManager.instance.settingsSaveData.debugMode)
{
GenerateSwipeMark(id, position, isGeneric, isFirst, direction);
@@ -378,7 +378,7 @@ namespace Ichni.RhythmGame
if (_activeTouches.ContainsKey(touchId))
{
_activeTouches.Remove(touchId);
if (SettingsManager.instance.gameSettings.debugMode)
if (SettingsManager.instance.settingsSaveData.debugMode)
{
GenerateEndMark(position);
}
@@ -390,7 +390,7 @@ namespace Ichni.RhythmGame
if (_activeTouches.ContainsKey(touchId))
{
_activeTouches.Remove(touchId);
if (SettingsManager.instance.gameSettings.debugMode)
if (SettingsManager.instance.settingsSaveData.debugMode)
{
GenerateCanceledMark(position);
}
@@ -540,4 +540,4 @@ namespace Ichni.RhythmGame
ss.Play();
}
}
}
}

View File

@@ -54,7 +54,7 @@ namespace Ichni
public SummaryPageCanvas summaryPageCanvas;
public float SongTime => songPlayer.isStarting ? 0 :
songPlayer.songTimeSegment - songInformation.offset - (SettingsManager.instance.gameSettings.beatmapOffset / 1000f);
songPlayer.songTimeSegment - songInformation.offset - (SettingsManager.instance.settingsSaveData.beatmapOffset / 1000f);
public bool IsPlaying => songPlayer != null && songPlayer.isPlaying;

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Reflection;
using Sirenix.OdinInspector;
using SLSUtilities.General;
using SLSUtilities.Rendering.PostProcessing;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
@@ -13,6 +14,11 @@ namespace Ichni
{
public class PostProcessingManager : Singleton<PostProcessingManager>
{
/// <summary>
/// 当前已加载的 GameScene 后处理管理器。MenuScene 等未创建该对象的场景会返回 <c>null</c>。
/// </summary>
public static PostProcessingManager Instance => instance;
public static Volume GlobalVolume => instance.globalVolume;
[ShowInInspector]
@@ -26,6 +32,29 @@ namespace Ichni
// Force instantiate the volume profile on awake so it doesn't happen mid-game
_ = globalVolume.profile;
}
// GameScene 的 Global Volume 创建时,重新应用已加载的玩家设置,覆盖场景切换时的默认值。
SettingsManager.instance?.ApplyPostProcessingSettings();
}
/// <summary>
/// 将全局后处理状态与 Anime Bloom 档位写入 GameScene 的运行时 Profile。
/// Volume.profile 会提供运行时实例,因此不会修改磁盘上的 SampleSceneProfile 资产。
/// </summary>
public void ApplySettings(bool enablePostProcessing, BloomQuality bloomQuality)
{
globalVolume.enabled = enablePostProcessing;
if (!enablePostProcessing || !globalVolume.profile.TryGet(out AnimeBloom animeBloom))
{
return;
}
animeBloom.active = bloomQuality != BloomQuality.Off;
if (animeBloom.active)
{
animeBloom.diffusion.value = bloomQuality == BloomQuality.Performance ? 5 : 8;
}
}
}
}
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using DG.Tweening;
using Ichni.RhythmGame;
using Ichni.RhythmGame.Beatmap;
@@ -51,8 +50,7 @@ namespace Ichni
fakeLoadPercent = 0f;
Observable.EveryUpdate()
.Where(_ => ThemeBundleManager.instance.waitingBundleAmount.Value == 0 &&
GameManager.Instance.gameLoadingCanvas.allPartsSet)
.Where(_ => ThemeBundleManager.instance.waitingBundleAmount.Value == 0 && GameManager.Instance.gameLoadingCanvas.allPartsSet)
.First()
.Subscribe(_ =>
{
@@ -72,6 +70,8 @@ namespace Ichni
{
fakeLoadObserver.Dispose();
GameManager.Instance.beatmapContainer.RebuildRuntimeTimeDurations();
Observable.Timer(TimeSpan.FromSeconds(1f)).First().Subscribe(_ =>
{
GameManager.Instance.gameLoadingCanvas.FadeOut();
@@ -79,33 +79,27 @@ namespace Ichni
GameManager.Instance.songPlayer.isStarting = true;
GameManager.Instance.beatmapContainer.gameElementList.ForEach(element => element.BeforeStart());
GameManager.Instance.gameUICanvas.readyText.DOFade(1, 0.5f)
.SetLoops(4, LoopType.Yoyo).SetEase(Ease.InOutFlash).Play();
// isStarting maps the Ready screen to song time zero. Apply the
// duration-controlled active states before that screen becomes visible.
GameManager.Instance.beatmapContainer.SyncRuntimeTimeDurationStates(GameManager.Instance.SongTime);
GameManager.Instance.gameUICanvas.readyText.DOFade(1, 0.5f).SetLoops(4, LoopType.Yoyo).SetEase(Ease.InOutFlash).Play();
Observable.Timer(TimeSpan.FromSeconds(2)).First().Subscribe(_ =>
{
GameManager.Instance.songPlayer.isDelaying = true;
GameManager.Instance.songPlayer.isStarting = false;
// SongInformation.delay can make this timeline position negative.
// Re-sync immediately after the state switch so negative-time
// elements are correct before the first scheduler tick.
GameManager.Instance.beatmapContainer.SyncRuntimeTimeDurationStates(GameManager.Instance.SongTime);
Observable.NextFrame().Subscribe(_ =>
{
GameManager.Instance.timeDurations = GameManager.Instance.beatmapContainer.gameElementList
.Select(x => (x as IHaveTimeDurationSubmodule)?.timeDurationSubmodule)
.Where(x => x != null && (x.startTime > 0 || x.endTime < GameManager.Instance.songInformation.songLength)).ToList();
GameManager.Instance.beatmapContainer.gameElementList.ForEach(element => element.WhenStart());
});
});
});
});
});
/*Observable.EveryUpdate()
.Where(_ => !GameManager.Instance.audioManager.isStarting)
.First()
.Subscribe(_ =>
{
});*/
});
}
public void Load(string chapterName, string musicName, string difficultyName)
@@ -143,4 +137,4 @@ namespace Ichni
}
}
}
}

View File

@@ -154,6 +154,16 @@ namespace Ichni
StopMusicEvent.Post(gameObject);
}
/// <summary>
/// 清除由谱面音乐特效写入的滤波 RTPC。
/// SettingsManager 在玩家关闭游戏内音乐特效时调用此方法,确保设置立即生效。
/// </summary>
public void ResetInGameAudioEffects()
{
HighPassFilter.SetValue(gameObject, 0f);
LowPassFilter.SetValue(gameObject, 0f);
}
private void OnMusicEvent(object in_cookie, AkCallbackType in_type, AkCallbackInfo in_info)
{
Debug.Log(in_type + " " + in_info);

View File

@@ -3,7 +3,6 @@ using System.Collections;
using System.Collections.Generic;
using AK.Wwise;
using Ichni.Menu;
using Ichni.Menu.UI;
using Ichni.Story;
using Ichni.Story.UI;
using Ichni.UI;
@@ -24,7 +23,6 @@ namespace Ichni
public LoginPage loginPage;
public ChapterSelectionUIPage chapterSelectionUIPage;
public StoryUIPage storyUIPage;
public SelectionBoxUIPage selectionBoxUIPage;
// dialogUIPage 已随旧对话系统移除;阶段 3 将接入新的 VN 对话页面引用
public SongSelectionUIPage songSelectionUIPage;
public TransitionUIPage transitionUIPage;
@@ -88,7 +86,7 @@ namespace Ichni
// 返回目标是一次性运行态信息。无论是否能恢复页面,都不能让它在下次重进菜单时重复生效。
information?.ClearMenuReturnDestination();
Application.targetFrameRate = SettingsManager.instance.gameSettings.targetFrame;
Application.targetFrameRate = SettingsManager.instance.settingsSaveData.targetFrame;
asyncOperation = SceneManager.LoadSceneAsync("GameScene");
asyncOperation.allowSceneActivation = false;
}

View File

@@ -1,13 +1,15 @@
using System.Collections.Generic;
using Ichni.Menu.UI;
using Ichni.UI;
using Sirenix.OdinInspector;
using UnityEngine;
namespace Ichni.Story.UI
{
/// <summary>
/// 用于在剧情系统中管理并顺序展示 MessageBox 的独立 UI 页面。
/// 支持动态实例化给定的 MessageBox Prefab并在所有消息展示完毕后进行大页面的 FadeOut。
/// 剧情与菜单共用的弹窗页面。
/// MessageBox 与 SelectionBox 共用同一遮罩、容器和显示队列:两者不会重叠,
/// 后续系统只需向本页提交“消息”或“选项”请求,无需再创建平行的 UIPage。
/// </summary>
public class MessageUIPage : UIPageBase
{
@@ -20,16 +22,28 @@ namespace Ichni.Story.UI
[Tooltip("生成的 MessageBox 挂载在哪?如果不填,默认挂载在自己身上")]
public Transform messageContainer;
// 用于排队的内部消息数据结构
private struct PendingMessage
[Tooltip("通用多选弹窗预制体。TutorialBlock、奖励选择等系统都通过本页动态生成它。")]
public SelectionBox defaultSelectionBoxPrefab;
private enum PopupType
{
public string title;
public string content;
public MessageBox customPrefab; // 支持未来传入不同种类的 Prefab
Message,
Selection
}
private Queue<PendingMessage> _messageQueue = new Queue<PendingMessage>();
private MessageBox _currentActiveBox;
// 用于统一排队的内部弹窗数据结构。
private struct PendingPopup
{
public PopupType type;
public string title;
public string content;
public MessageBox messagePrefab;
public List<SelectionBoxOption> selectionOptions;
}
private readonly Queue<PendingPopup> _popupQueue = new();
private MessageBox _currentMessageBox;
private SelectionBox _currentSelectionBox;
private bool _isPageActive = false;
protected override void Awake()
@@ -71,6 +85,83 @@ namespace Ichni.Story.UI
EnqueueMessage(title, content, defaultMessageBoxPrefab);
}
/// <summary>
/// Inspector 专用的运行时调试入口。
/// 通过正常队列创建一条基础测试文本,因此可以同时验证新 Prefab 的布局、
/// 入退场动画、输入拦截与队列收尾,而不会绕过正式流程或污染剧情数据。
/// </summary>
[Button("调试:创建空 MessageBox", ButtonSizes.Medium)]
private void DebugCreateEmptyMessageBox()
{
if (!Application.isPlaying)
{
Debug.LogWarning("[MessageUIPage] 空 MessageBox 调试按钮只能在 Play Mode 中使用。", this);
return;
}
ShowCustomMessage(
"MessageBox Debug",
"这是一条用于检查基础布局、文本换行与 Rail 动效的测试文本。\n\n" +
"This is a basic MessageBox test message.");
}
/// <summary>
/// Inspector 专用的运行时调试入口。
/// 通过正常队列创建一条基础测试文本,因此可以同时验证新 Prefab 的布局、
/// 入退场动画、输入拦截与队列收尾,而不会绕过正式流程或污染剧情数据。
/// </summary>
[Button("调试:创建空 SelectionBox", ButtonSizes.Medium)]
private void DebugCreateEmptySelectionBox()
{
if (!Application.isPlaying)
{
Debug.LogWarning("[MessageUIPage] 空 SelectionBox 调试按钮只能在 Play Mode 中使用。", this);
return;
}
ShowSelectionBox(
"SelectionBox Debug",
"这是一条用于检查基础布局、文本换行与 Rail 动效的测试文本。\n\n" +
"This is a basic SelectionBox test message.",
new List<SelectionBoxOption>
{
new SelectionBoxOption("Option 1", () => Debug.Log("Option 1 selected")),
new SelectionBoxOption("Option 2", () => Debug.Log("Option 2 selected")),
new SelectionBoxOption("Option 3", () => Debug.Log("Option 3 selected"))
});
}
/// <summary>
/// 将一组运行时选项加入通用弹窗队列。
/// 若 MessageBox 或其它 SelectionBox 正在显示,选项会按提交顺序等待,而不会叠在同一遮罩上。
/// </summary>
public bool ShowSelectionBox(string title, string content, IReadOnlyList<SelectionBoxOption> options)
{
if (defaultSelectionBoxPrefab == null)
{
Debug.LogError("[MessageUIPage] 未配置 defaultSelectionBoxPrefab无法显示选项框。");
return false;
}
if (options == null || options.Count == 0)
{
Debug.LogWarning("[MessageUIPage] 未提供任何 SelectionBoxOption忽略空选项框。");
return false;
}
_popupQueue.Enqueue(new PendingPopup
{
type = PopupType.Selection,
title = title,
content = content,
// 复制调用方传入的列表,防止调用方随后改动集合而改变已排队的玩家选择。
selectionOptions = new List<SelectionBoxOption>(options)
});
BeginQueueIfNeeded();
return true;
}
/// <summary>
/// 将指定消息入队。如果是空闲状态,则唤醒大页面并开始处理队列。
/// </summary>
@@ -82,22 +173,30 @@ namespace Ichni.Story.UI
return;
}
_messageQueue.Enqueue(new PendingMessage
_popupQueue.Enqueue(new PendingPopup
{
type = PopupType.Message,
title = title,
content = content,
customPrefab = prefab
messagePrefab = prefab
});
// 如果当前页面没有在显示中,则开始整体流程
if (!_isPageActive)
{
_isPageActive = true;
// 唤醒大页面(执行 UIPageBase 的渐入并拦截底层射线)
this.FadeIn(0.5f, false, ShowNextInQueue);
}
BeginQueueIfNeeded();
}
/// <summary>
/// 若当前没有弹窗,激活共享遮罩后开始依次显示队列。遮罩从出现瞬间拦截输入,
/// 避免 UIPageBase 默认淡入结束前的短暂底层点击窗口。
/// </summary>
private void BeginQueueIfNeeded()
{
if (_isPageActive) return;
_isPageActive = true;
mainCanvasGroup.gameObject.SetActive(true);
mainCanvasGroup.interactable = true;
mainCanvasGroup.blocksRaycasts = true;
FadeIn(0f, false, ShowNextInQueue);
}
/// <summary>
@@ -105,40 +204,85 @@ namespace Ichni.Story.UI
/// </summary>
private void ShowNextInQueue()
{
if (_messageQueue.Count == 0)
if (_popupQueue.Count == 0)
{
// 队列处理完毕,大页面整体退场
_currentActiveBox = null;
// 队列处理完毕,大页面整体退场
_currentMessageBox = null;
_currentSelectionBox = null;
_isPageActive = false;
this.FadeOut();
FadeOut(0f);
return;
}
PendingMessage msg = _messageQueue.Dequeue();
// 实例化预制体
_currentActiveBox = Instantiate(msg.customPrefab, messageContainer);
// 订阅“当这个消息框彻底结束关闭时”的事件
_currentActiveBox.onAllMessagesClosed.AddListener(() =>
PendingPopup popup = _popupQueue.Dequeue();
if (popup.type == PopupType.Selection)
{
// 销毁旧实例
if (_currentActiveBox != null)
{
Destroy(_currentActiveBox.gameObject);
}
// 检查并显示下一个
ShowNextInQueue();
});
ShowSelectionPopup(popup);
return;
}
// 配置并启动 MessageBox
_currentActiveBox.Clear();
_currentActiveBox.AddInfo(msg.title, msg.content, null);
// MessageBox.SetUp() 内部会激活自己并执行自己的 FadeIn
_currentActiveBox.gameObject.SetActive(true);
_currentActiveBox.SetUp();
ShowMessagePopup(popup);
}
private void ShowMessagePopup(PendingPopup popup)
{
if (popup.messagePrefab == null)
{
Debug.LogError("[MessageUIPage] 队列中的 MessageBox Prefab 已丢失,跳过该消息。");
ShowNextInQueue();
return;
}
MessageBox messageBox = Instantiate(popup.messagePrefab, messageContainer);
_currentMessageBox = messageBox;
// MessageBox 只显示一条消息;队列顺序始终由本页统一管理。
messageBox.Closed += HandleMessageBoxClosed;
messageBox.gameObject.SetActive(true);
messageBox.SetUp(popup.title, popup.content);
}
private void HandleMessageBoxClosed(MessageBox messageBox)
{
messageBox.Closed -= HandleMessageBoxClosed;
Destroy(messageBox.gameObject);
if (_currentMessageBox == messageBox)
_currentMessageBox = null;
ShowNextInQueue();
}
private void ShowSelectionPopup(PendingPopup popup)
{
if (defaultSelectionBoxPrefab == null)
{
Debug.LogError("[MessageUIPage] 队列中的 SelectionBox Prefab 已丢失,跳过该选项框。");
ShowNextInQueue();
return;
}
SelectionBox selectionBox = Instantiate(defaultSelectionBoxPrefab, messageContainer);
_currentSelectionBox = selectionBox;
selectionBox.Closed += HandleSelectionBoxClosed;
if (!selectionBox.SetUp(popup.title, popup.content, popup.selectionOptions))
{
selectionBox.Closed -= HandleSelectionBoxClosed;
Destroy(selectionBox.gameObject);
_currentSelectionBox = null;
ShowNextInQueue();
}
}
private void HandleSelectionBoxClosed(SelectionBox selectionBox)
{
if (selectionBox != _currentSelectionBox) return;
selectionBox.Closed -= HandleSelectionBoxClosed;
Destroy(selectionBox.gameObject);
_currentSelectionBox = null;
ShowNextInQueue();
}
}
}

View File

@@ -156,8 +156,8 @@ namespace Ichni.Story.Dialogue
{
action?.Invoke();
}
MessageUIPage.instance.FadeIn();
// MessageUIPage 会在收到第一项消息/选项请求时自行开启统一队列与淡入,
// 此处不能再次直接 FadeIn否则会与当前弹窗流程重复播放页面动画。
}
if (storyUIPage != null)

View File

@@ -55,7 +55,8 @@ namespace Ichni.Story
if (!TrySetBoolAndSave(tutorialBlock.tutorialProgressVariable, true))
return false;
// completedBlockIds 只负责教程节点自身的 Completed 外观与不可重复点击
// completedBlockIds 只负责教程节点自身的 Completed 外观;
// Completed TutorialBlock 仍可点击,以便玩家重复进入教程。
// 后续节点应在 StoryData 中以 VariableCondition 读取上面的剧情变量。
treeController.OnBlockCompleted(tutorialBlock.blockId);
return true;

View File

@@ -1,5 +1,6 @@
using Ichni.Menu;
using Ichni.Menu.UI;
using Ichni.Story.UI;
using System.Collections.Generic;
using UnityEngine;
@@ -13,7 +14,7 @@ namespace Ichni.Story
public static class TutorialFlowController
{
/// <summary>
/// 由 <see cref="UI.TutorialBlockView"/> 点击时调用。配置无效或 SelectionBox 页面未配置时只报错,不修改剧情进度。
/// 由 <see cref="UI.TutorialBlockView"/> 点击时调用。配置无效或 MessageUIPage 未配置时只报错,不修改剧情进度。
/// </summary>
public static void RequestTutorial(StoryBlockDefinition tutorialBlock)
{
@@ -23,17 +24,16 @@ namespace Ichni.Story
return;
}
MenuManager menuManager = MenuManager.instance;
SelectionBoxUIPage selectionBoxPage = menuManager?.selectionBoxUIPage;
if (selectionBoxPage == null)
MessageUIPage messageUIPage = MessageUIPage.instance;
if (messageUIPage == null)
{
Debug.LogWarning("[TutorialFlowController] MenuManager 未配置 SelectionBoxUIPage教程不会被解锁。");
Debug.LogWarning("[TutorialFlowController] MessageUIPage 未就绪,教程不会被解锁。");
return;
}
// TutorialFlowController 只描述“有哪些选择、各自触发什么效果”;
// 具体按钮由通用 SelectionBox 在运行时生成,后续其它系统可复用同一 UI。
selectionBoxPage.Show(
messageUIPage.ShowSelectionBox(
tutorialBlock.tutorialName,
string.Empty,
new List<SelectionBoxOption>
@@ -105,7 +105,8 @@ namespace Ichni.Story
string.IsNullOrWhiteSpace(tutorialBlock.tutorialProgressVariable) ||
tutorialBlock.tutorialDifficultySaveId < 0)
{
Debug.LogWarning($"[TutorialFlowController] TutorialBlock '{tutorialBlock?.blockId}' 的 Tutorial Key、Progress Variable 或 Difficulty Save ID 未配置完整。");
Debug.LogWarning($"[TutorialFlowController] TutorialBlock '{tutorialBlock?.blockId}' 的 Tutorial Key、Progress Variable 或 Difficulty Save ID 未配置完整。\n" +
$"Tutorial Key:{tutorialBlock.tutorialKey}, Progress Variable:{tutorialBlock.tutorialProgressVariable}, Difficulty Save ID:{tutorialBlock.tutorialDifficultySaveId}");
return false;
}

View File

@@ -4,8 +4,9 @@ using UnityEngine;
namespace Ichni.Story.UI
{
/// <summary>
/// 教程块视图。点击后由 <see cref="TutorialFlowController"/> 显示确认窗口
/// 教程块视图。点击后由 <see cref="TutorialFlowController"/> 请求通用 SelectionBox
/// 玩家选择游玩或跳过时都会写入教程剧情变量并刷新故事树。
/// 节点处于 Completed 时仍保持可点击,方便玩家随时回顾教程。
/// </summary>
public class TutorialBlockView : StoryBlockView
{
@@ -25,16 +26,5 @@ namespace Ichni.Story.UI
TutorialFlowController.RequestTutorial(definition);
}
/// <summary>
/// 已处理的教程不再重复弹出“游玩 / 跳过”确认窗口。
/// 教程重玩将来应由独立的教程入口提供,不复用剧情进度节点。
/// </summary>
public override void ApplyState(StoryBlockState newState)
{
base.ApplyState(newState);
if (button != null && newState == StoryBlockState.Completed)
button.interactable = false;
}
}
}

View File

@@ -1,78 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Localization.Settings;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
namespace Ichni
{
public class GameSettings
{
public int masterVolume = 50;
public int musicVolume = 50;
public int soundEffectVolume = 50;
public int uiVolume = 50;
public int targetFrame = 60;
public int resolutionLevel = 3;
public int beatmapOffset = 0;
public int languageIndex = 0;
public bool debugMode = false;
public bool judgeType = false;
public bool autoPlay = false;
public bool ShouldUseFullScreenJudge()
{
return judgeType || InformationTransistor.instance?.difficulty?.ForceFullScreenJudge == true;
}
public GameSettings()
{
}
public GameSettings(int masterVolume, int musicVolume, int soundEffectVolume, int uiVolume,
int beatmapOffset, int targetFrame, int resolutionLevel, int languageIndex, bool debugMode,
bool judgeType, bool autoPlay)
{
this.masterVolume = masterVolume;
this.musicVolume = musicVolume;
this.soundEffectVolume = soundEffectVolume;
this.uiVolume = uiVolume;
this.beatmapOffset = beatmapOffset;
this.targetFrame = targetFrame;
this.resolutionLevel = resolutionLevel;
this.languageIndex = languageIndex;
this.debugMode = debugMode;
this.judgeType = judgeType;
this.autoPlay = autoPlay;
}
public void ApplyVolume()
{
AkSoundEngine.SetRTPCValue("MasterVolume", masterVolume);
AkSoundEngine.SetRTPCValue("MusicVolume", musicVolume);
AkSoundEngine.SetRTPCValue("SoundFXVolume", soundEffectVolume);
AkSoundEngine.SetRTPCValue("UIVolume", uiVolume);
}
public void ApplyGraphic()
{
Application.targetFrameRate = targetFrame;
UniversalRenderPipelineAsset currentUrpAsset = GraphicsSettings.defaultRenderPipeline as UniversalRenderPipelineAsset;
currentUrpAsset.renderScale = 0.5f + 0.1f * resolutionLevel;
}
public void ApplyLanguage()
{
I2.Loc.LocalizationManager.CurrentLanguage = MenuManager.instance.languageList[languageIndex];
LocalizationSettings.SelectedLocale = LocalizationSettings.AvailableLocales.Locales[languageIndex];
I2.Loc.LocalizationManager.UpdateSources();
}
}
}

View File

@@ -1,17 +1,34 @@
using Ichni.Menu;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.Localization.Settings;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using UnityEngine.SceneManagement;
using UnityEngine.Serialization;
namespace Ichni
{
public class SettingsManager : SerializedMonoBehaviour
{
private const string SettingsSaveFileName = "SettingsSave.json";
private const string SettingsSaveKey = "SettingsSave";
private const string PerformantQualityName = "Performant";
private const string BalancedQualityName = "Balanced";
private const string HighFidelityQualityName = "High Fidelity";
public static SettingsManager instance;
[FormerlySerializedAs("settingsPage")]
public SettingsUIPage settingsUIPage;
public GameSettings gameSettings;
[FormerlySerializedAs("gameSettings")]
public SettingsSaveData settingsSaveData;
// 记录相机在玩家关闭后处理前的原始状态。重新开启时必须恢复原始值,不能无条件将所有相机改为开启。
private readonly Dictionary<int, bool> postProcessingCameraStates = new Dictionary<int, bool>();
private void Awake()
{
@@ -19,6 +36,8 @@ namespace Ichni
{
instance = this;
DontDestroyOnLoad(gameObject);
SceneManager.sceneLoaded += OnSceneLoaded;
RenderPipelineManager.beginCameraRendering += ApplyPostProcessingSettingBeforeCameraRender;
}
else
{
@@ -26,6 +45,18 @@ namespace Ichni
}
}
private void OnDestroy()
{
if (instance != this)
{
return;
}
SceneManager.sceneLoaded -= OnSceneLoaded;
RenderPipelineManager.beginCameraRendering -= ApplyPostProcessingSettingBeforeCameraRender;
instance = null;
}
private void Start()
{
LoadGameSettings();
@@ -34,27 +65,269 @@ namespace Ichni
[Button]
public void SaveGameSettings()
{
string savePath = Application.persistentDataPath + "/GameData/GameSettings.json";
ES3.Save("GameSettings", gameSettings, savePath);
string savePath = Application.persistentDataPath + "/GameData/" + SettingsSaveFileName;
ES3.Save(SettingsSaveKey, settingsSaveData, savePath);
}
[Button]
public void LoadGameSettings()
{
string savePath = Application.persistentDataPath + "/GameData/GameSettings.json";
string savePath = Application.persistentDataPath + "/GameData/" + SettingsSaveFileName;
if (ES3.FileExists(savePath))
{
gameSettings = ES3.Load<GameSettings>("GameSettings", savePath);
// 保留原有 ES3 Key确保本次数据模型重命名不会让已有本地设置失效。
settingsSaveData = ES3.Load<SettingsSaveData>(SettingsSaveKey, savePath);
}
else
{
gameSettings = new GameSettings(
50, 50, 50, 50,
0, 60, 3, 0, false, false, false);
settingsSaveData = CreateDefaultSettingsSaveData();
}
gameSettings.ApplyVolume();
gameSettings.ApplyGraphic();
ApplyAudioSettings();
ApplyGraphicSettings();
}
/// <summary>
/// 将音频分类的当前设置写入 Wwise RTPC。
/// </summary>
public void ApplyAudioSettings()
{
AkSoundEngine.SetRTPCValue("MasterVolume", settingsSaveData.masterVolume);
AkSoundEngine.SetRTPCValue("MusicVolume", settingsSaveData.musicVolume);
AkSoundEngine.SetRTPCValue("SoundFXVolume", settingsSaveData.soundEffectVolume);
AkSoundEngine.SetRTPCValue("UIVolume", settingsSaveData.uiVolume);
ApplyInGameAudioEffectsSettings();
}
/// <summary>
/// 应用游戏内音乐特效开关。关闭时立即清除歌曲正在使用的高通与低通滤波,
/// 防止设置变更后残留上一帧的 RTPC 值。
/// </summary>
public void ApplyInGameAudioEffectsSettings()
{
if (settingsSaveData.enableInGameAudioEffects)
{
return;
}
GameManager.Instance?.songPlayer?.ResetInGameAudioEffects();
}
/// <summary>
/// 将现有图形设置应用到运行时。更细的 Quality、VSync 与平台策略会在 Graphics 分类重构时接入。
/// </summary>
public void ApplyGraphicSettings()
{
int qualityLevelIndex = GetQualityLevelIndex(settingsSaveData.graphicsQualityPreset);
if (QualitySettings.GetQualityLevel() != qualityLevelIndex)
{
QualitySettings.SetQualityLevel(qualityLevelIndex, true);
}
Application.targetFrameRate = settingsSaveData.targetFrame;
UniversalRenderPipelineAsset currentUrpAsset = GraphicsSettings.currentRenderPipeline as UniversalRenderPipelineAsset;
currentUrpAsset.renderScale = 0.5f + 0.1f * settingsSaveData.renderScaleLevel;
ApplyDisplaySettings();
ApplyPostProcessingSettings();
}
/// <summary>
/// 应用后处理总开关与 Bloom 档位。
/// 关闭总开关时,已存在的 URP 相机会直接关闭 <c>renderPostProcessing</c>,并关闭 GameScene 的 Global Volume。
/// 这样既不会执行原生后处理,也不会执行 ScriptablePostProcessor 的自定义 Render Pass。
/// </summary>
public void ApplyPostProcessingSettings()
{
bool enablePostProcessing = settingsSaveData.enablePostProcessing;
UniversalAdditionalCameraData[] cameraDataList =
FindObjectsByType<UniversalAdditionalCameraData>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
foreach (UniversalAdditionalCameraData cameraData in cameraDataList)
{
int instanceId = cameraData.GetInstanceID();
if (!postProcessingCameraStates.ContainsKey(instanceId))
{
postProcessingCameraStates.Add(instanceId, cameraData.renderPostProcessing);
}
cameraData.renderPostProcessing = enablePostProcessing && postProcessingCameraStates[instanceId];
}
if (PostProcessingManager.Instance != null)
{
PostProcessingManager.Instance.ApplySettings(enablePostProcessing, settingsSaveData.bloomQuality);
}
}
/// <summary>
/// 处理场景切换后新加载的相机与 Global Volume。SettingsManager 会跨场景保留,
/// 因此不能只在首次读取存档时应用一次图形设置。
/// </summary>
private void OnSceneLoaded(Scene scene, LoadSceneMode loadSceneMode)
{
if (settingsSaveData != null)
{
ApplyPostProcessingSettings();
}
}
/// <summary>
/// 兜底处理运行时晚于场景加载创建的相机(例如 Theme 或演出对象中的相机)。
/// 仅在玩家关闭后处理时执行,保证该相机在首次渲染前也不会运行后处理。
/// </summary>
private void ApplyPostProcessingSettingBeforeCameraRender(ScriptableRenderContext context, Camera camera)
{
if (settingsSaveData == null || settingsSaveData.enablePostProcessing ||
!camera.TryGetComponent(out UniversalAdditionalCameraData cameraData))
{
return;
}
int instanceId = cameraData.GetInstanceID();
if (!postProcessingCameraStates.ContainsKey(instanceId))
{
postProcessingCameraStates.Add(instanceId, cameraData.renderPostProcessing);
}
cameraData.renderPostProcessing = false;
}
/// <summary>
/// 将 VSync、窗口模式和分辨率立即应用到 Standalone / Unity Editor。
/// 移动端由系统管理显示模式与分辨率,因此不执行这些设置。
/// </summary>
public void ApplyDisplaySettings()
{
#if UNITY_STANDALONE || UNITY_EDITOR
QualitySettings.vSyncCount = settingsSaveData.vSyncEnabled ? 1 : 0;
if (settingsSaveData.displayResolutionWidth <= 0 || settingsSaveData.displayResolutionHeight <= 0)
{
settingsSaveData.displayResolutionWidth = Screen.width;
settingsSaveData.displayResolutionHeight = Screen.height;
}
Screen.SetResolution(settingsSaveData.displayResolutionWidth, settingsSaveData.displayResolutionHeight,
GetFullScreenMode(settingsSaveData.displayMode));
#endif
}
/// <summary>
/// 首次创建设置存档时,继承 Project Settings 为当前平台指定的默认 Quality Level。
/// 之后玩家的选择由 SettingsSaveData 持久化,不会在下次启动时被平台默认值覆盖。
/// </summary>
private SettingsSaveData CreateDefaultSettingsSaveData()
{
SettingsSaveData defaultSettings = new SettingsSaveData();
defaultSettings.graphicsQualityPreset = GetGraphicsQualityPreset(QualitySettings.GetQualityLevel());
#if UNITY_STANDALONE || UNITY_EDITOR
defaultSettings.displayMode = GetGraphicsDisplayMode(Screen.fullScreenMode);
defaultSettings.displayResolutionWidth = Screen.width;
defaultSettings.displayResolutionHeight = Screen.height;
#endif
return defaultSettings;
}
private FullScreenMode GetFullScreenMode(GraphicsDisplayMode displayMode)
{
return displayMode switch
{
GraphicsDisplayMode.Fullscreen => FullScreenMode.ExclusiveFullScreen,
GraphicsDisplayMode.Windowed => FullScreenMode.Windowed,
_ => FullScreenMode.FullScreenWindow
};
}
private GraphicsDisplayMode GetGraphicsDisplayMode(FullScreenMode fullScreenMode)
{
return fullScreenMode switch
{
FullScreenMode.ExclusiveFullScreen => GraphicsDisplayMode.Fullscreen,
FullScreenMode.Windowed => GraphicsDisplayMode.Windowed,
_ => GraphicsDisplayMode.BorderlessFullscreen
};
}
/// <summary>
/// 将存档枚举映射到 Project Settings 中同名的 Quality Level。
/// 若配置缺失,回退到 Balanced这是移动端的首发默认档位。
/// </summary>
private int GetQualityLevelIndex(GraphicsQualityPreset qualityPreset)
{
string qualityName = GetQualityName(qualityPreset);
string[] qualityNames = QualitySettings.names;
for (int index = 0; index < qualityNames.Length; index++)
{
if (qualityNames[index] == qualityName)
{
return index;
}
}
Debug.LogWarning($"[SettingsManager] 未找到 Quality Level '{qualityName}',回退至 Balanced。");
return FindQualityLevelOrFallback(BalancedQualityName);
}
/// <summary>
/// 将当前 Quality Level 索引转换为可保存的枚举。未知的自定义档位统一按 Balanced 保存。
/// </summary>
private GraphicsQualityPreset GetGraphicsQualityPreset(int qualityLevelIndex)
{
string[] qualityNames = QualitySettings.names;
if (qualityLevelIndex < 0 || qualityLevelIndex >= qualityNames.Length)
{
return GraphicsQualityPreset.Balanced;
}
return qualityNames[qualityLevelIndex] switch
{
PerformantQualityName => GraphicsQualityPreset.Performant,
HighFidelityQualityName => GraphicsQualityPreset.HighFidelity,
_ => GraphicsQualityPreset.Balanced
};
}
private int FindQualityLevelOrFallback(string qualityName)
{
string[] qualityNames = QualitySettings.names;
for (int index = 0; index < qualityNames.Length; index++)
{
if (qualityNames[index] == qualityName)
{
return index;
}
}
return Mathf.Clamp(QualitySettings.GetQualityLevel(), 0, qualityNames.Length - 1);
}
private string GetQualityName(GraphicsQualityPreset qualityPreset)
{
return qualityPreset switch
{
GraphicsQualityPreset.Performant => PerformantQualityName,
GraphicsQualityPreset.HighFidelity => HighFidelityQualityName,
_ => BalancedQualityName
};
}
/// <summary>
/// 将当前语言索引应用到既有的 I2 与 Unity Localization 管线。
/// 语言将于 Localization 重构时改为稳定的 Locale ID本阶段只迁移数据承载方式。
/// </summary>
public void ApplyLanguageSettings()
{
I2.Loc.LocalizationManager.CurrentLanguage = MenuManager.instance.languageList[settingsSaveData.languageIndex];
LocalizationSettings.SelectedLocale = LocalizationSettings.AvailableLocales.Locales[settingsSaveData.languageIndex];
I2.Loc.LocalizationManager.UpdateSources();
}
/// <summary>
/// 判断本谱面是否启用全屏判定。谱面强制要求的配置优先于玩家开关。
/// </summary>
public bool ShouldUseFullScreenJudge()
{
return settingsSaveData.judgeType || InformationTransistor.instance?.difficulty?.ForceFullScreenJudge == true;
}
}
}
}

View File

@@ -0,0 +1,129 @@
using System;
using UnityEngine.Serialization;
namespace Ichni
{
/// <summary>
/// 与 Project Settings 中同名 Quality Level 对应的玩家图形档位。
/// 枚举值仅用于设置存档与 UI 顺序;实际应用时由 SettingsManager 按名称查找 Quality Level
/// 因此调整 Project Settings 的档位顺序不会错误切换到其他渲染 Asset。
/// </summary>
public enum GraphicsQualityPreset
{
Performant = 0,
Balanced = 1,
HighFidelity = 2
}
/// <summary>
/// PC 显示模式。移动端不使用此设置,相关 UI 也不会显示。
/// </summary>
public enum GraphicsDisplayMode
{
Fullscreen = 0,
BorderlessFullscreen = 1,
Windowed = 2
}
/// <summary>
/// Anime Bloom 的质量档位。
/// Off 会保持其他后处理可用,但不执行 BloomPerformance 与 Standard 分别使用预先确定的
/// Diffusion 值。具体数值由 <see cref="PostProcessingManager"/> 统一应用,避免散落在 UI 或谱面代码中。
/// </summary>
public enum BloomQuality
{
Off = 0,
Performance = 1,
Standard = 2
}
/// <summary>
/// Note 与其他演出特效将使用的全局质量档位。
/// 本阶段先完成存档、UI 与运行时读取入口;各特效物体后续按自身开销实现 Low / Medium / High 的实际降级策略。
/// </summary>
public enum VfxQuality
{
Low = 0,
Medium = 1,
High = 2
}
/// <summary>
/// 玩家设置的唯一持久化数据模型。
///
/// 此类只保存设置值,不负责将音量、图形或语言应用到运行时;
/// 运行时应用逻辑统一由 <see cref="SettingsManager"/> 处理。
/// 后续按 Audio、Gameplay、Graphics 等类别拆分字段时,必须保留
/// <see cref="schemaVersion"/>,并在 SettingsManager 中补充对应的迁移逻辑。
/// </summary>
[Serializable]
public class SettingsSaveData
{
/// <summary>
/// 设置存档结构版本。当前为首次建立 SettingsSaveData 的 V1。
/// </summary>
public const int CurrentSchemaVersion = 1;
public int schemaVersion = CurrentSchemaVersion;
// Audio
public int masterVolume = 50;
public int musicVolume = 50;
public int soundEffectVolume = 50;
public int uiVolume = 50;
/// <summary>
/// 是否启用游戏内音乐特效。当前控制 HighPassFilterEffect 与 LowPassFilterEffect
/// 关闭时会立即清除正在歌曲上的滤波 RTPC后续同类特效也应复用此开关。
/// </summary>
public bool enableInGameAudioEffects = true;
// Graphics
/// <summary>
/// 玩家选择的 Quality Preset。新存档会读取当前平台在 Project Settings 中配置的默认档位。
/// </summary>
public GraphicsQualityPreset graphicsQualityPreset = GraphicsQualityPreset.Balanced;
public int targetFrame = 60;
/// <summary>
/// URP Render Scale 档位05 分别对应 0.51.0。
/// 保留 FormerlySerializedAs 以兼容场景中旧的字段名;预发布 ES3 存档可直接重置。
/// </summary>
[FormerlySerializedAs("resolutionLevel")]
public int renderScaleLevel = 3;
/// <summary>
/// 后处理总开关。关闭后,所有 URP 相机都会停用 post-processingGlobal Volume 也不会参与计算;
/// 因此原生与自定义后处理都不会产生渲染开销。
/// </summary>
public bool enablePostProcessing = true;
/// <summary>
/// Anime Bloom 的质量档位。默认 Standard对应 Diffusion 8。
/// </summary>
public BloomQuality bloomQuality = BloomQuality.Standard;
/// <summary>
/// 后续 Note / 演出特效读取的质量档位。默认 High以保持当前未分级特效的视觉基线。
/// </summary>
public VfxQuality vfxQuality = VfxQuality.High;
// Desktop display settings. These values are saved on every platform but only applied on PC and in the Unity Editor.
public bool vSyncEnabled = false;
public GraphicsDisplayMode displayMode = GraphicsDisplayMode.BorderlessFullscreen;
public int displayResolutionWidth = 0;
public int displayResolutionHeight = 0;
// Gameplay
public int beatmapOffset = 0;
// Localization
public int languageIndex = 0;
// Developer settings. 这些字段会在后续分类重构时迁出正式 Release 设置。
public bool debugMode = false;
public bool judgeType = false;
public bool autoPlay = false;
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: c2ca67619927cac4a91d5b0612d6f7eb
guid: a52b8dd62f9e43b69cd3fa97ad8ed71c
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -12,9 +12,9 @@ namespace Ichni.UI
public int selectedIndex;
public string selectedOption;
public void SetUp(int initialValue, List<string> options, string title = "")
public void SetUp(int initialValue, List<string> options, string title = "", bool preservePrefabTitle = false)
{
base.SetUp(title);
base.SetUp(title, preservePrefabTitle: preservePrefabTitle);
this.options = options;
this.dropdown.items = new List<CustomDropdown.Item>();
@@ -45,4 +45,4 @@ namespace Ichni.UI
dropdown.UpdateItemLayout();
}
}
}
}

View File

@@ -19,8 +19,18 @@ namespace Ichni.UI
public UnityAction updateValueAction;
public virtual void SetUp(string title = "", string subTitle = "")
/// <summary>
/// 初始化设置控件的标题区域。
/// <paramref name="preservePrefabTitle"/> 用于已经由 Unity Localization 直接驱动标题的控件:
/// 运行时代码不会覆盖或隐藏 Prefab 中的本地化标题。
/// </summary>
public virtual void SetUp(string title = "", string subTitle = "", bool preservePrefabTitle = false)
{
if (preservePrefabTitle)
{
return;
}
if (title != "")
{
SetTitle(title, subTitle);
@@ -51,4 +61,4 @@ namespace Ichni.UI
}
}
}
}
}

View File

@@ -7,9 +7,9 @@ namespace Ichni.UI
public bool value;
public SwitchManager switchManager;
public void SetUp(bool initialValue, string title = "")
public void SetUp(bool initialValue, string title = "", bool preservePrefabTitle = false)
{
base.SetUp(title);
base.SetUp(title, preservePrefabTitle: preservePrefabTitle);
switchManager.onValueChanged.AddListener(isOn =>
{
@@ -34,4 +34,4 @@ namespace Ichni.UI
return value;
}
}
}
}

View File

@@ -1,158 +1,323 @@
using System;
using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
using Sirenix.OdinInspector;
using SLSUtilities.WwiseAssistance;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Serialization;
using UnityEngine.UI;
namespace Ichni.Menu.UI
{
public partial class MessageBox : SerializedMonoBehaviour
/// <summary>
/// 单条文本提示框。
/// 多条弹窗的 FIFO 顺序由 MessageUIPage 统一管理;本类只负责当前一条消息的布局、
/// 入退场视觉和确认输入,避免与 UIPage 的队列职责重复。
/// </summary>
public sealed class MessageBox : MonoBehaviour
{
public CanvasGroup canvasGroup;
[Header("Base")]
[SerializeField] private CanvasGroup canvasGroup;
[SerializeField] private Button receiveButton;
[Tooltip("当该消息框结束所有消息并彻底淡出消失时触发。")]
public UnityEvent onAllMessagesClosed = new UnityEvent();
[Header("Text")]
[SerializeField] private TMP_Text titleText;
[SerializeField] private TMP_Text contentText;
public int infoIndex;
public List<MessageBoxInfo> infos;
public Sprite defaultIcon;
public Image iconImage;
public TMP_Text titleText;
public TMP_Text contentText;
public Button receiveButton;
public Tweener fadeTweener;
[Header("Layout References")]
[Tooltip("可变高度的最终布局节点。动画期间不会修改它、Title 或 Content 的 RectTransform。")]
[SerializeField] private RectTransform messageBody;
[SerializeField] private RectTransform backgroundVisual;
[SerializeField] private RectMask2D titleTextMask;
[SerializeField] private RectTransform contentLayoutRoot;
[SerializeField] private RectMask2D contentTextMask;
[SerializeField] private RectTransform railRoot;
[SerializeField] private RectTransform leftRail;
[SerializeField] private RectTransform rightRail;
private void Start()
[Header("Content Height Limit")]
[SerializeField, Min(0f)] private float maximumBodyHeight = 600f;
[SerializeField, Min(1f)] private float minimumContentFontSize = 18f;
[Header("Animation")]
[Tooltip("背景与文本 Mask 从中心展开前的延迟Rail 的默认延迟为 0因此会先行动。")]
[SerializeField, Min(0f)] private float bodyOpenDelay = 0.1f;
[SerializeField, Min(0f)] private float bodyOpenDuration = 0.5f;
[SerializeField, Min(0f)] private float railOpenDelay;
[SerializeField, Min(0f)] private float railOpenDuration = 0.5f;
[SerializeField, Min(0f)] private float bodyCloseDelay;
[SerializeField, Min(0f)] private float bodyCloseDuration = 0.5f;
[SerializeField, Min(0f)] private float railCloseDelay = 0.1f;
[SerializeField, Min(0f)] private float railCloseDuration = 0.5f;
[SerializeField, Min(0f)] private float closedMaskHorizontalPadding = 520f;
[SerializeField] private Ease bodyEase = Ease.OutCubic;
[SerializeField] private Ease railEase = Ease.OutQuart;
/// <summary>
/// 退场动画结束后触发。MessageUIPage 订阅它以销毁当前实例并继续 FIFO 队列。
/// </summary>
public event Action<MessageBox> Closed;
private Sequence transitionSequence;
private CanvasGroup leftRailCanvasGroup;
private CanvasGroup rightRailCanvasGroup;
private Vector2 backgroundOpenSizeDelta;
private Vector2 leftRailOpenPosition;
private Vector2 rightRailOpenPosition;
private float contentMaximumFontSize;
private void Awake()
{
receiveButton.onClick.AddListener(()=>PlayNextMessage(true));
receiveButton.GetComponent<RectTransform>().sizeDelta = new Vector2(Screen.width, Screen.height);
backgroundOpenSizeDelta = backgroundVisual.sizeDelta;
leftRailOpenPosition = leftRail.anchoredPosition;
rightRailOpenPosition = rightRail.anchoredPosition;
leftRailCanvasGroup = leftRail.GetComponent<CanvasGroup>();
rightRailCanvasGroup = rightRail.GetComponent<CanvasGroup>();
contentMaximumFontSize = contentText.fontSize;
receiveButton.onClick.AddListener(HandleReceiveButtonClicked);
}
public void SetUp()
private void OnDestroy()
{
if (infos.Count == 0)
transitionSequence?.Kill();
receiveButton.onClick.RemoveListener(HandleReceiveButtonClicked);
}
/// <summary>
/// 写入唯一一条待显示文本,并播放入场动画。
/// 多条提示请分别提交给 MessageUIPage而不要在一个 MessageBox 中追加消息。
/// </summary>
public void SetUp(string title, string content)
{
titleText.text = title ?? string.Empty;
contentText.text = content ?? string.Empty;
RefreshLayoutAndFitContentText();
Open();
}
private void HandleReceiveButtonClicked()
{
AudioManager.Post(AK.EVENTS.CONFIRM);
Close();
}
/// <summary>
/// 使用最终宽度计算文字换行与内容高度。正文超过 maximumBodyHeight 时,
/// 通过二分查找缩小至可容纳的最大字号;不会引入滚动或截断内容。
/// </summary>
private void RefreshLayoutAndFitContentText()
{
SetOpenVisualState();
ForceRebuildLayout();
if (maximumBodyHeight <= 0f)
{
return;
}
infoIndex = 0;
PlayNextMessage();
FadeIn();
}
public void SetUpIcon(Sprite icon)
{
iconImage.sprite = icon;
}
public void AddInfo(string title, string content, UnityAction action = null, Sprite icon = null)
{
MessageBoxInfo info = new MessageBoxInfo(icon, title, content, action);
infos.Add(info);
}
contentText.enableAutoSizing = false;
contentText.fontSize = contentMaximumFontSize;
ForceRebuildLayout();
public void PlayNextMessage(bool playAudio = false)
{
if (playAudio)
if (messageBody.rect.height <= maximumBodyHeight)
{
AudioManager.Post(AK.EVENTS.CONFIRM);
}
if (infoIndex >= infos.Count)
{
FadeOut();
return;
}
infos[infoIndex].action?.Invoke();
if (titleText != null) titleText.text = infos[infoIndex].title;
if (contentText != null) contentText.text = infos[infoIndex].content;
this.iconImage.sprite = infos[infoIndex].icon != null ? infos[infoIndex].icon : defaultIcon;
infoIndex++;
}
public void Clear()
{
infos = new List<MessageBoxInfo>();
infoIndex = 0;
iconImage.sprite = defaultIcon;
titleText.text = string.Empty;
contentText.text = string.Empty;
}
}
public partial class MessageBox
{
// 废弃对 I2.Loc 参数化功能的支持
// public void SetParameter(string paramName, string paramValue, bool localize = false)
// {
// }
}
float minimumFontSize = Mathf.Min(minimumContentFontSize, contentMaximumFontSize);
float lowerBound = minimumFontSize;
float upperBound = contentMaximumFontSize;
float bestFontSize = minimumFontSize;
public partial class MessageBox
{
public void FadeIn(float duration = 0.5f, bool ignoreTimeScale = false)
// 字号与内容高度单调相关;六次二分已足够平滑且只在弹窗创建时执行。
for (int iteration = 0; iteration < 6; iteration++)
{
float candidate = (lowerBound + upperBound) * 0.5f;
contentText.fontSize = candidate;
ForceRebuildLayout();
if (messageBody.rect.height <= maximumBodyHeight)
{
bestFontSize = candidate;
lowerBound = candidate;
}
else
{
upperBound = candidate;
}
}
contentText.fontSize = bestFontSize;
ForceRebuildLayout();
if (messageBody.rect.height > maximumBodyHeight)
{
Debug.LogWarning(
$"[MessageBox] 正文在最小字号 {minimumFontSize:F1} 下仍超过最大高度 {maximumBodyHeight:F1}" +
"已保留完整内容并允许消息框继续增高。",
this);
}
}
private void ForceRebuildLayout()
{
Canvas.ForceUpdateCanvases();
LayoutRebuilder.ForceRebuildLayoutImmediate(contentLayoutRoot);
LayoutRebuilder.ForceRebuildLayoutImmediate(messageBody);
Canvas.ForceUpdateCanvases();
}
/// <summary>
/// 恢复最终视觉尺寸以完成布局。这里不会改变 MessageBox、Title 或 Content 的尺寸,
/// 因此 TMP 在展开和收拢全过程都不会重新换行。
/// </summary>
private void SetOpenVisualState()
{
backgroundVisual.sizeDelta = backgroundOpenSizeDelta;
titleTextMask.padding = Vector4.zero;
contentTextMask.padding = Vector4.zero;
leftRail.anchoredPosition = leftRailOpenPosition;
rightRail.anchoredPosition = rightRailOpenPosition;
leftRailCanvasGroup.alpha = 1f;
rightRailCanvasGroup.alpha = 1f;
}
private void SetClosedVisualState()
{
backgroundVisual.sizeDelta = GetClosedBackgroundSizeDelta();
titleTextMask.padding = ClosedMaskPadding;
contentTextMask.padding = ClosedMaskPadding;
float halfRootWidth = railRoot.rect.width * 0.5f;
leftRail.anchoredPosition = new Vector2(halfRootWidth, leftRailOpenPosition.y);
rightRail.anchoredPosition = new Vector2(-halfRootWidth, rightRailOpenPosition.y);
leftRailCanvasGroup.alpha = 0f;
rightRailCanvasGroup.alpha = 0f;
}
private Vector4 ClosedMaskPadding =>
new(closedMaskHorizontalPadding, 0f, closedMaskHorizontalPadding, 0f);
/// <summary>
/// Background 使用左右 Stretch 锚点。以最终实际宽度抵消它的 SizeDelta X
/// 可将图像视觉收拢到中心而不改变 MessageBox 的布局宽度。
/// </summary>
private Vector2 GetClosedBackgroundSizeDelta() =>
new(backgroundOpenSizeDelta.x - backgroundVisual.rect.width, backgroundOpenSizeDelta.y);
private static Tween CreateMaskPaddingTween(RectMask2D mask, Vector4 targetPadding, float duration, Ease ease) =>
DOTween.To(() => mask.padding, value => mask.padding = value, targetPadding, duration).SetEase(ease);
private static void AddRailTransition(
Sequence sequence,
RectTransform rail,
CanvasGroup railCanvasGroup,
Vector2 targetPosition,
float targetAlpha,
float delay,
float duration,
Ease positionEase)
{
sequence.Insert(delay, rail.DOAnchorPos(targetPosition, duration).SetEase(positionEase));
sequence.Insert(delay, railCanvasGroup.DOFade(targetAlpha, duration).SetEase(Ease.Linear));
}
/// <summary>
/// 只展开 Background 与两个文字 MaskMessageBox、标题、正文均已是最终布局尺寸。
/// 每个 Sequence 都先 Pause再在结尾显式调用 Play符合项目的 DOTween 使用约定。
/// </summary>
private void Open()
{
transitionSequence?.Kill();
canvasGroup.gameObject.SetActive(true);
fadeTweener = canvasGroup.DOFade(1f, duration).OnComplete(() =>
canvasGroup.alpha = 1f;
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
SetClosedVisualState();
transitionSequence = DOTween.Sequence().SetAutoKill(true).Pause();
transitionSequence.Insert(
bodyOpenDelay,
backgroundVisual.DOSizeDelta(backgroundOpenSizeDelta, bodyOpenDuration).SetEase(bodyEase));
transitionSequence.Insert(
bodyOpenDelay,
CreateMaskPaddingTween(titleTextMask, Vector4.zero, bodyOpenDuration, bodyEase));
transitionSequence.Insert(
bodyOpenDelay,
CreateMaskPaddingTween(contentTextMask, Vector4.zero, bodyOpenDuration, bodyEase));
AddRailTransition(
transitionSequence,
leftRail,
leftRailCanvasGroup,
leftRailOpenPosition,
1f,
railOpenDelay,
railOpenDuration,
railEase);
AddRailTransition(
transitionSequence,
rightRail,
rightRailCanvasGroup,
rightRailOpenPosition,
1f,
railOpenDelay,
railOpenDuration,
railEase);
transitionSequence.OnComplete(() =>
{
canvasGroup.interactable = true;
canvasGroup.blocksRaycasts = true;
});
if (ignoreTimeScale)
{
fadeTweener.SetUpdate(true);
}
fadeTweener.Play();
transitionSequence.Play();
}
public void FadeOut(float duration = 0.5f, bool ignoreTimeScale = false)
/// <summary>
/// 反向收拢 Background、文字 Mask 和两侧 Rail完成后通知 MessageUIPage 显示下一项。
/// </summary>
private void Close()
{
transitionSequence?.Kill();
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
fadeTweener = canvasGroup.DOFade(0f, duration).OnComplete(() =>
float halfRootWidth = railRoot.rect.width * 0.5f;
transitionSequence = DOTween.Sequence().SetAutoKill(true).Pause();
transitionSequence.Insert(
bodyCloseDelay,
backgroundVisual.DOSizeDelta(GetClosedBackgroundSizeDelta(), bodyCloseDuration).SetEase(Ease.InCubic));
transitionSequence.Insert(
bodyCloseDelay,
CreateMaskPaddingTween(titleTextMask, ClosedMaskPadding, bodyCloseDuration, Ease.InCubic));
transitionSequence.Insert(
bodyCloseDelay,
CreateMaskPaddingTween(contentTextMask, ClosedMaskPadding, bodyCloseDuration, Ease.InCubic));
AddRailTransition(
transitionSequence,
leftRail,
leftRailCanvasGroup,
new Vector2(halfRootWidth, leftRailOpenPosition.y),
0f,
railCloseDelay,
railCloseDuration,
Ease.InQuart);
AddRailTransition(
transitionSequence,
rightRail,
rightRailCanvasGroup,
new Vector2(-halfRootWidth, rightRailOpenPosition.y),
0f,
railCloseDelay,
railCloseDuration,
Ease.InQuart);
transitionSequence.OnComplete(() =>
{
canvasGroup.gameObject.SetActive(false);
onAllMessagesClosed?.Invoke();
Closed?.Invoke(this);
});
if (ignoreTimeScale)
{
fadeTweener.SetUpdate(true);
}
fadeTweener.Play();
transitionSequence.Play();
}
}
public struct MessageBoxInfo
{
public Sprite icon;
public string title;
public string content;
public UnityAction action;
public MessageBoxInfo(Sprite icon, string title, string content, UnityAction action)
{
this.icon = icon;
this.title = title;
this.content = content;
this.action = action;
}
}
}
}

View File

@@ -4,96 +4,306 @@ using DG.Tweening;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
namespace Ichni.Menu.UI
{
/// <summary>
/// 可复用的多选弹窗元素。调用方提供标题、内容与任意数量的 <see cref="SelectionBoxOption"/>
/// 本类动态实例化对应按钮,并确保一次显示最多只会执行一个选项回调。
/// 通用多选弹窗。
/// 标题、正文与按钮均在播放前完成最终布局;入退场只改变 Background、两处文本 Mask 和 Rail
/// 因而不会在动画期间触发 TMP 重新换行或按钮布局抖动。
/// </summary>
public class SelectionBox : MonoBehaviour
public sealed class SelectionBox : MonoBehaviour
{
[Header("Selection Box")]
public CanvasGroup canvasGroup;
public TMP_Text titleText;
public TMP_Text contentText;
public RectTransform optionContainer;
public SelectionBoxButton optionButtonPrefab;
[Header("Base")]
[SerializeField] private CanvasGroup canvasGroup;
[SerializeField] private CanvasGroup optionCanvasGroup;
private readonly List<SelectionBoxButton> _generatedButtons = new();
private bool _isResolved;
[Header("Text & Options")]
[SerializeField] private TMP_Text titleText;
[SerializeField] private TMP_Text contentText;
[SerializeField] private RectTransform optionContainer;
[SerializeField] private SelectionBoxButton optionButtonPrefab;
[Header("Layout References")]
[SerializeField] private RectTransform messageBody;
[SerializeField] private RectTransform backgroundVisual;
[SerializeField] private RectMask2D titleTextMask;
[SerializeField] private RectTransform contentLayoutRoot;
[SerializeField] private RectMask2D contentTextMask;
[SerializeField] private RectTransform railRoot;
[SerializeField] private RectTransform leftRail;
[SerializeField] private RectTransform rightRail;
[Header("Animation")]
[SerializeField, Min(0f)] private float bodyOpenDelay = 0.1f;
[SerializeField, Min(0f)] private float bodyOpenDuration = 0.5f;
[SerializeField, Min(0f)] private float railOpenDelay;
[SerializeField, Min(0f)] private float railOpenDuration = 0.5f;
[SerializeField, Min(0f)] private float bodyCloseDelay;
[SerializeField, Min(0f)] private float bodyCloseDuration = 0.5f;
[SerializeField, Min(0f)] private float railCloseDelay = 0.1f;
[SerializeField, Min(0f)] private float railCloseDuration = 0.5f;
[SerializeField, Min(0f)] private float closedMaskHorizontalPadding = 600f;
[SerializeField] private Ease bodyEase = Ease.OutCubic;
[SerializeField] private Ease railEase = Ease.OutQuart;
/// <summary>
/// 选项被选择并且自身淡出结束后触发。页面容器使用它销毁本次生成的 SelectionBox
/// 玩家选择任一选项、弹窗完成退场后触发。MessageUIPage 使用它继续共享 FIFO 队列
/// </summary>
public event Action<SelectionBox> Closed;
private readonly List<SelectionBoxButton> generatedButtons = new();
private Sequence transitionSequence;
private CanvasGroup leftRailCanvasGroup;
private CanvasGroup rightRailCanvasGroup;
private Vector2 backgroundOpenSizeDelta;
private Vector2 leftRailOpenPosition;
private Vector2 rightRailOpenPosition;
private bool isResolved;
private void Awake()
{
canvasGroup ??= GetComponent<CanvasGroup>();
backgroundOpenSizeDelta = backgroundVisual.sizeDelta;
leftRailOpenPosition = leftRail.anchoredPosition;
rightRailOpenPosition = rightRail.anchoredPosition;
leftRailCanvasGroup = leftRail.GetComponent<CanvasGroup>();
rightRailCanvasGroup = rightRail.GetComponent<CanvasGroup>();
}
private void OnDestroy()
{
transitionSequence?.Kill();
ClearGeneratedButtons();
}
/// <summary>
/// 根据运行时选项初始化弹窗。至少需要一个选项;失败时返回 false且不会显示半配置的弹窗
/// 创建本次选择所需的按钮并播放入场动画。空选项没有可执行的玩家选择,因此返回 false
/// </summary>
public bool SetUp(string title, string content, IReadOnlyList<SelectionBoxOption> options)
{
if (canvasGroup == null || optionContainer == null || optionButtonPrefab == null ||
options == null || options.Count == 0)
if (options == null || options.Count == 0)
{
Debug.LogWarning("[SelectionBox] 缺少 CanvasGroup、选项容器、选项按钮预制体未提供任何选项。");
Debug.LogWarning("[SelectionBox] 未提供任何 SelectionBoxOption忽略空选项。", this);
return false;
}
ClearGeneratedButtons();
_isResolved = false;
if (titleText != null) titleText.text = title ?? string.Empty;
if (contentText != null) contentText.text = content ?? string.Empty;
isResolved = false;
titleText.text = title ?? string.Empty;
contentText.text = content ?? string.Empty;
foreach (SelectionBoxOption option in options)
{
SelectionBoxButton button = Instantiate(optionButtonPrefab, optionContainer);
_generatedButtons.Add(button);
generatedButtons.Add(button);
button.SetUp(option.label, () => Select(option.action));
}
gameObject.SetActive(true);
canvasGroup.alpha = 0f;
canvasGroup.interactable = true;
canvasGroup.blocksRaycasts = true;
canvasGroup.DOFade(1f, 0.2f).Play();
RefreshFinalLayout();
Open();
return true;
}
private void Select(UnityAction action)
{
if (_isResolved) return;
_isResolved = true;
if (isResolved) return;
isResolved = true;
// 先禁用所有输入,再执行回调。回调可以触发存档、场景切换或打开下一层 UI
// 因而不能让同一帧内的其它按钮再次触发。
// 先阻断输入,再执行可能触发存档、跳转或新 UI 的业务回调。
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
foreach (SelectionBoxButton button in _generatedButtons)
foreach (SelectionBoxButton button in generatedButtons)
{
if (button?.button != null)
button.button.interactable = false;
button.button.interactable = false;
}
action?.Invoke();
canvasGroup.DOFade(0f, 0.2f).OnComplete(() => Closed?.Invoke(this)).Play();
Close();
}
/// <summary>
/// 在最终尺寸下重建正文、主体和按钮行的布局。后续动画不再改变这些布局节点的尺寸。
/// </summary>
private void RefreshFinalLayout()
{
SetOpenVisualState();
Canvas.ForceUpdateCanvases();
LayoutRebuilder.ForceRebuildLayoutImmediate(contentLayoutRoot);
LayoutRebuilder.ForceRebuildLayoutImmediate(messageBody);
LayoutRebuilder.ForceRebuildLayoutImmediate(optionContainer);
Canvas.ForceUpdateCanvases();
}
private void SetOpenVisualState()
{
backgroundVisual.sizeDelta = backgroundOpenSizeDelta;
titleTextMask.padding = Vector4.zero;
contentTextMask.padding = Vector4.zero;
optionContainer.localScale = new Vector3(1f, optionContainer.localScale.y, optionContainer.localScale.z);
optionCanvasGroup.alpha = 1f;
optionCanvasGroup.interactable = true;
optionCanvasGroup.blocksRaycasts = true;
leftRail.anchoredPosition = leftRailOpenPosition;
rightRail.anchoredPosition = rightRailOpenPosition;
leftRailCanvasGroup.alpha = 1f;
rightRailCanvasGroup.alpha = 1f;
}
private void SetClosedVisualState()
{
backgroundVisual.sizeDelta = GetClosedBackgroundSizeDelta();
titleTextMask.padding = ClosedMaskPadding;
contentTextMask.padding = ClosedMaskPadding;
optionCanvasGroup.alpha = 0f;
optionCanvasGroup.interactable = false;
optionCanvasGroup.blocksRaycasts = false;
float halfRootWidth = railRoot.rect.width * 0.5f;
leftRail.anchoredPosition = new Vector2(halfRootWidth, leftRailOpenPosition.y);
rightRail.anchoredPosition = new Vector2(-halfRootWidth, rightRailOpenPosition.y);
leftRailCanvasGroup.alpha = 0f;
rightRailCanvasGroup.alpha = 0f;
}
private Vector4 ClosedMaskPadding =>
new(closedMaskHorizontalPadding, 0f, closedMaskHorizontalPadding, 0f);
private Vector2 GetClosedBackgroundSizeDelta() =>
new(backgroundOpenSizeDelta.x - backgroundVisual.rect.width, backgroundOpenSizeDelta.y);
private static Tween CreateMaskPaddingTween(RectMask2D mask, Vector4 targetPadding, float duration, Ease ease) =>
DOTween.To(() => mask.padding, value => mask.padding = value, targetPadding, duration).SetEase(ease);
private static void AddRailTransition(
Sequence sequence,
RectTransform rail,
CanvasGroup railCanvasGroup,
Vector2 targetPosition,
float targetAlpha,
float delay,
float duration,
Ease positionEase)
{
sequence.Insert(delay, rail.DOAnchorPos(targetPosition, duration).SetEase(positionEase));
sequence.Insert(delay, railCanvasGroup.DOFade(targetAlpha, duration).SetEase(Ease.Linear));
}
private void Open()
{
transitionSequence?.Kill();
canvasGroup.gameObject.SetActive(true);
canvasGroup.alpha = 1f;
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
SetClosedVisualState();
transitionSequence = DOTween.Sequence().SetAutoKill(true).Pause();
transitionSequence.Insert(
bodyOpenDelay,
backgroundVisual.DOSizeDelta(backgroundOpenSizeDelta, bodyOpenDuration).SetEase(bodyEase));
transitionSequence.Insert(
bodyOpenDelay,
CreateMaskPaddingTween(titleTextMask, Vector4.zero, bodyOpenDuration, bodyEase));
transitionSequence.Insert(
bodyOpenDelay,
CreateMaskPaddingTween(contentTextMask, Vector4.zero, bodyOpenDuration, bodyEase));
AddRailTransition(
transitionSequence,
leftRail,
leftRailCanvasGroup,
leftRailOpenPosition,
1f,
railOpenDelay,
railOpenDuration,
railEase);
AddRailTransition(
transitionSequence,
rightRail,
rightRailCanvasGroup,
rightRailOpenPosition,
1f,
railOpenDelay,
railOpenDuration,
railEase);
float optionRevealTime = bodyOpenDelay + bodyOpenDuration;
transitionSequence.Insert(
optionRevealTime,
optionCanvasGroup.DOFade(1f, 0.4f).From(0f).SetEase(Ease.Linear));
transitionSequence.Insert(
optionRevealTime,
optionContainer.DOAnchorPos(Vector2.zero, 0.4f).From(new Vector2(0f, 100f)).SetEase(Ease.OutQuad));
transitionSequence.OnComplete(() =>
{
canvasGroup.interactable = true;
canvasGroup.blocksRaycasts = true;
optionCanvasGroup.interactable = true;
optionCanvasGroup.blocksRaycasts = true;
});
// 项目约定DOTween 动画必须由代码显式触发 Play。
transitionSequence.Play();
}
private void Close()
{
transitionSequence?.Kill();
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
optionCanvasGroup.interactable = false;
optionCanvasGroup.blocksRaycasts = false;
float halfRootWidth = railRoot.rect.width * 0.5f;
transitionSequence = DOTween.Sequence().SetAutoKill(true).Pause();
transitionSequence.Insert(
bodyCloseDelay,
backgroundVisual.DOSizeDelta(GetClosedBackgroundSizeDelta(), bodyCloseDuration).SetEase(Ease.InCubic));
transitionSequence.Insert(
bodyCloseDelay,
CreateMaskPaddingTween(titleTextMask, ClosedMaskPadding, bodyCloseDuration, Ease.InCubic));
transitionSequence.Insert(
bodyCloseDelay,
CreateMaskPaddingTween(contentTextMask, ClosedMaskPadding, bodyCloseDuration, Ease.InCubic));
transitionSequence.Insert(
bodyCloseDelay,
optionContainer.DOScaleX(0f, bodyCloseDuration).SetEase(Ease.InCubic));
AddRailTransition(
transitionSequence,
leftRail,
leftRailCanvasGroup,
new Vector2(halfRootWidth, leftRailOpenPosition.y),
0f,
railCloseDelay,
railCloseDuration,
Ease.InQuart);
AddRailTransition(
transitionSequence,
rightRail,
rightRailCanvasGroup,
new Vector2(-halfRootWidth, rightRailOpenPosition.y),
0f,
railCloseDelay,
railCloseDuration,
Ease.InQuart);
transitionSequence.OnComplete(() =>
{
canvasGroup.gameObject.SetActive(false);
Closed?.Invoke(this);
});
// 项目约定DOTween 动画必须由代码显式触发 Play。
transitionSequence.Play();
}
private void ClearGeneratedButtons()
{
foreach (SelectionBoxButton button in _generatedButtons)
foreach (SelectionBoxButton button in generatedButtons)
{
if (button != null)
Destroy(button.gameObject);
Destroy(button.gameObject);
}
_generatedButtons.Clear();
generatedButtons.Clear();
}
}

View File

@@ -1,73 +0,0 @@
using System.Collections.Generic;
using Ichni.UI;
using UnityEngine;
namespace Ichni.Menu.UI
{
/// <summary>
/// 通用 SelectionBox 的页面宿主,职责等同于 MessageBox 的页面容器:
/// 显示时动态生成一个 SelectionBox选择完成后统一淡出并销毁本次实例。
/// </summary>
public class SelectionBoxUIPage : UIPageBase
{
[Header("Selection Box Page")]
public SelectionBox selectionBoxPrefab;
public RectTransform selectionBoxContainer;
private SelectionBox _currentSelectionBox;
private bool _isShowing;
/// <summary>当前是否已有一个 SelectionBox 等待玩家选择。</summary>
public bool IsShowing => _isShowing;
/// <summary>
/// 生成并显示一组运行时选项。页面显示期间拒绝重复请求,避免两个弹窗争夺输入。
/// </summary>
public bool Show(string title, string content, IReadOnlyList<SelectionBoxOption> options)
{
if (_isShowing)
{
Debug.LogWarning("[SelectionBoxUIPage] 已有 SelectionBox 显示中,忽略重复请求。");
return false;
}
if (mainCanvasGroup == null || selectionBoxPrefab == null || selectionBoxContainer == null)
{
Debug.LogWarning("[SelectionBoxUIPage] 缺少 CanvasGroup、SelectionBox Prefab 或容器,无法显示选项框。");
return false;
}
_currentSelectionBox = Instantiate(selectionBoxPrefab, selectionBoxContainer);
_currentSelectionBox.Closed += HandleSelectionBoxClosed;
if (!_currentSelectionBox.SetUp(title, content, options))
{
Destroy(_currentSelectionBox.gameObject);
_currentSelectionBox = null;
return false;
}
_isShowing = true;
// UIPageBase 默认淡入完成后才开启射线;选择框从出现瞬间就必须阻断底层输入。
mainCanvasGroup.gameObject.SetActive(true);
mainCanvasGroup.interactable = true;
mainCanvasGroup.blocksRaycasts = true;
FadeIn();
return true;
}
private void HandleSelectionBoxClosed(SelectionBox selectionBox)
{
if (selectionBox != _currentSelectionBox) return;
FadeOut(0.2f, false, () =>
{
if (_currentSelectionBox != null)
Destroy(_currentSelectionBox.gameObject);
_currentSelectionBox = null;
_isShowing = false;
});
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: e8b607f8c5654e1eb3bf1f7ca33fd3a4

View File

@@ -11,6 +11,7 @@ namespace Ichni.Menu
public ValueModifier musicVolumeModifier;
public ValueModifier sfxVolumeModifier;
public ValueModifier uiVolumeModifier;
public Switch inGameAudioEffectsSwitch;
public override void Initialize()
{
@@ -19,7 +20,7 @@ namespace Ichni.Menu
mainVolumeModifier.updateValueAction = () =>
{
gameSettings.masterVolume = mainVolumeModifier.GetValue();
gameSettings.ApplyVolume();
SettingsManager.instance.ApplyAudioSettings();
};
musicVolumeModifier.SetUp(gameSettings.musicVolume, 10, "Menu UI/Settings_Music_Volume");
@@ -27,7 +28,7 @@ namespace Ichni.Menu
musicVolumeModifier.updateValueAction = () =>
{
gameSettings.musicVolume = musicVolumeModifier.GetValue();
gameSettings.ApplyVolume();
SettingsManager.instance.ApplyAudioSettings();
};
sfxVolumeModifier.SetUp(gameSettings.soundEffectVolume, 10, "Menu UI/Settings_SFX_Volume");
@@ -35,7 +36,7 @@ namespace Ichni.Menu
sfxVolumeModifier.updateValueAction = () =>
{
gameSettings.soundEffectVolume = sfxVolumeModifier.GetValue();
gameSettings.ApplyVolume();
SettingsManager.instance.ApplyAudioSettings();
};
uiVolumeModifier.SetUp(gameSettings.uiVolume, 10, "Menu UI/Settings_UI_Volume");
@@ -43,7 +44,15 @@ namespace Ichni.Menu
uiVolumeModifier.updateValueAction = () =>
{
gameSettings.uiVolume = uiVolumeModifier.GetValue();
gameSettings.ApplyVolume();
SettingsManager.instance.ApplyAudioSettings();
};
inGameAudioEffectsSwitch.SetUp(gameSettings.enableInGameAudioEffects,
"Menu UI/Settings_InGame_Audio_Effects");
inGameAudioEffectsSwitch.updateValueAction = () =>
{
gameSettings.enableInGameAudioEffects = inGameAudioEffectsSwitch.GetValue();
SettingsManager.instance.ApplyInGameAudioEffectsSettings();
};
}
@@ -53,6 +62,7 @@ namespace Ichni.Menu
musicVolumeModifier.SetValue(gameSettings.musicVolume);
sfxVolumeModifier.SetValue(gameSettings.soundEffectVolume);
uiVolumeModifier.SetValue(gameSettings.uiVolume);
inGameAudioEffectsSwitch.SetValue(gameSettings.enableInGameAudioEffects);
}
}
}
}

View File

@@ -25,7 +25,7 @@ namespace Ichni.UI
languageDropdown.updateValueAction = () =>
{
gameSettings.languageIndex = languageDropdown.selectedIndex;
gameSettings.ApplyLanguage();
SettingsManager.instance.ApplyLanguageSettings();
};
}
@@ -34,4 +34,4 @@ namespace Ichni.UI
languageDropdown.SetValue(gameSettings.languageIndex);
}
}
}
}

View File

@@ -2,22 +2,70 @@ using System.Collections;
using System.Collections.Generic;
using Ichni.UI;
using UnityEngine;
using UnityEngine.Serialization;
namespace Ichni.Menu
{
public class GraphicsSettingsWindow : SettingsWindow
{
public ValueModifier resolutionLevelModifier;
private static readonly List<string> QualityPresetOptions = new List<string>
{
"Performant",
"Balanced",
"High Fidelity"
};
private static readonly List<string> DisplayModeOptions = new List<string>
{
"Fullscreen",
"Borderless Fullscreen",
"Windowed"
};
private static readonly List<string> BloomQualityOptions = new List<string>
{
"Off",
"Performance",
"Standard"
};
private static readonly List<string> VfxQualityOptions = new List<string>
{
"Low",
"Medium",
"High"
};
private readonly List<Vector2Int> _availableDisplayResolutions = new List<Vector2Int>();
private readonly List<string> _displayResolutionOptions = new List<string>();
public Dropdown qualityPresetDropdown;
[FormerlySerializedAs("resolutionLevelModifier")]
public ValueModifier renderScaleModifier;
public ValueModifier targetFrameModifier;
public Switch postProcessingSwitch;
public Dropdown bloomQualityDropdown;
public Dropdown vfxQualityDropdown;
public Switch vSyncSwitch;
public Dropdown displayModeDropdown;
public Dropdown displayResolutionDropdown;
public override void Initialize()
{
resolutionLevelModifier.SetUp(gameSettings.resolutionLevel, 1, "Menu UI/Settings_Resolution_Level");
resolutionLevelModifier.SetMinMax(0, 5);
resolutionLevelModifier.updateValueAction = () =>
qualityPresetDropdown.SetUp((int)gameSettings.graphicsQualityPreset, QualityPresetOptions,
"Menu UI/Settings_Quality_Preset");
qualityPresetDropdown.updateValueAction = () =>
{
gameSettings.resolutionLevel = resolutionLevelModifier.GetValue();
gameSettings.ApplyGraphic();
gameSettings.graphicsQualityPreset = (GraphicsQualityPreset)qualityPresetDropdown.selectedIndex;
SettingsManager.instance.ApplyGraphicSettings();
};
renderScaleModifier.SetUp(gameSettings.renderScaleLevel, 1, "Menu UI/Settings_Render_Scale");
renderScaleModifier.SetMinMax(0, 5);
renderScaleModifier.updateValueAction = () =>
{
gameSettings.renderScaleLevel = renderScaleModifier.GetValue();
SettingsManager.instance.ApplyGraphicSettings();
};
targetFrameModifier.SetUp(gameSettings.targetFrame, 30, "Menu UI/Settings_Target_Frame");
@@ -25,14 +73,148 @@ namespace Ichni.Menu
targetFrameModifier.updateValueAction = () =>
{
gameSettings.targetFrame = targetFrameModifier.GetValue();
gameSettings.ApplyGraphic();
SettingsManager.instance.ApplyGraphicSettings();
};
InitializePostProcessingSettings();
#if UNITY_STANDALONE || UNITY_EDITOR
InitializeDesktopDisplaySettings();
#else
vSyncSwitch.gameObject.SetActive(false);
displayModeDropdown.gameObject.SetActive(false);
displayResolutionDropdown.gameObject.SetActive(false);
#endif
}
public override void SetValuesFromSettings()
{
resolutionLevelModifier.SetValue(gameSettings.resolutionLevel);
qualityPresetDropdown.SetValue((int)gameSettings.graphicsQualityPreset);
renderScaleModifier.SetValue(gameSettings.renderScaleLevel);
targetFrameModifier.SetValue(gameSettings.targetFrame);
if (postProcessingSwitch != null)
{
postProcessingSwitch.SetValue(gameSettings.enablePostProcessing);
bloomQualityDropdown.SetValue((int)gameSettings.bloomQuality);
vfxQualityDropdown.SetValue((int)gameSettings.vfxQuality);
}
#if UNITY_STANDALONE || UNITY_EDITOR
vSyncSwitch.SetValue(gameSettings.vSyncEnabled);
displayModeDropdown.SetValue((int)gameSettings.displayMode);
displayResolutionDropdown.SetValue(GetDisplayResolutionIndex());
#endif
}
/// <summary>
/// 初始化后处理、Bloom 与 VFX 档位。
/// 三个标题由 Prefab 上的 Unity Localization 组件管理,因此这里保留原有标题显示,不再由脚本写入本地化 Key。
/// 在旧 Prefab 尚未布置这些控件前保留兼容分支,避免更新脚本后立即破坏既有设置页。
/// </summary>
private void InitializePostProcessingSettings()
{
if (postProcessingSwitch == null)
{
return;
}
postProcessingSwitch.SetUp(gameSettings.enablePostProcessing, preservePrefabTitle: true);
postProcessingSwitch.updateValueAction = () =>
{
gameSettings.enablePostProcessing = postProcessingSwitch.GetValue();
SettingsManager.instance.ApplyPostProcessingSettings();
};
bloomQualityDropdown.SetUp((int)gameSettings.bloomQuality, BloomQualityOptions, preservePrefabTitle: true);
bloomQualityDropdown.updateValueAction = () =>
{
gameSettings.bloomQuality = (BloomQuality)bloomQualityDropdown.selectedIndex;
SettingsManager.instance.ApplyPostProcessingSettings();
};
vfxQualityDropdown.SetUp((int)gameSettings.vfxQuality, VfxQualityOptions, preservePrefabTitle: true);
vfxQualityDropdown.updateValueAction = () =>
{
gameSettings.vfxQuality = (VfxQuality)vfxQualityDropdown.selectedIndex;
};
}
#if UNITY_STANDALONE || UNITY_EDITOR
private void InitializeDesktopDisplaySettings()
{
BuildDisplayResolutionOptions();
vSyncSwitch.SetUp(gameSettings.vSyncEnabled, "Menu UI/Settings_VSync");
vSyncSwitch.updateValueAction = () =>
{
gameSettings.vSyncEnabled = vSyncSwitch.GetValue();
SettingsManager.instance.ApplyDisplaySettings();
};
displayModeDropdown.SetUp((int)gameSettings.displayMode, DisplayModeOptions,
"Menu UI/Settings_Display_Mode");
displayModeDropdown.updateValueAction = () =>
{
gameSettings.displayMode = (GraphicsDisplayMode)displayModeDropdown.selectedIndex;
SettingsManager.instance.ApplyDisplaySettings();
};
displayResolutionDropdown.SetUp(GetDisplayResolutionIndex(), _displayResolutionOptions,
"Menu UI/Settings_Display_Resolution");
displayResolutionDropdown.updateValueAction = () =>
{
Vector2Int resolution = _availableDisplayResolutions[displayResolutionDropdown.selectedIndex];
gameSettings.displayResolutionWidth = resolution.x;
gameSettings.displayResolutionHeight = resolution.y;
SettingsManager.instance.ApplyDisplaySettings();
};
}
private void BuildDisplayResolutionOptions()
{
_availableDisplayResolutions.Clear();
_displayResolutionOptions.Clear();
AddDisplayResolution(Screen.width, Screen.height);
foreach (Resolution resolution in Screen.resolutions)
{
AddDisplayResolution(resolution.width, resolution.height);
}
_availableDisplayResolutions.Sort((left, right) =>
{
int pixelCountComparison = (left.x * left.y).CompareTo(right.x * right.y);
return pixelCountComparison != 0 ? pixelCountComparison : left.x.CompareTo(right.x);
});
foreach (Vector2Int resolution in _availableDisplayResolutions)
{
_displayResolutionOptions.Add($"{resolution.x} x {resolution.y}");
}
}
private void AddDisplayResolution(int width, int height)
{
Vector2Int resolution = new Vector2Int(width, height);
if (!_availableDisplayResolutions.Contains(resolution))
{
_availableDisplayResolutions.Add(resolution);
}
}
private int GetDisplayResolutionIndex()
{
Vector2Int savedResolution = new Vector2Int(gameSettings.displayResolutionWidth,
gameSettings.displayResolutionHeight);
int savedResolutionIndex = _availableDisplayResolutions.IndexOf(savedResolution);
if (savedResolutionIndex >= 0)
{
return savedResolutionIndex;
}
return _availableDisplayResolutions.IndexOf(new Vector2Int(Screen.width, Screen.height));
}
#endif
}
}
}

View File

@@ -48,7 +48,7 @@ namespace Ichni.Menu
offsetModifier.SetPrefixAndSuffix("", " ms", true);
offsetModifier.updateValueAction = () =>
{
offset = SettingsManager.instance.gameSettings.beatmapOffset = offsetModifier.GetValue();
offset = SettingsManager.instance.settingsSaveData.beatmapOffset = offsetModifier.GetValue();
largeOffsetHintText.gameObject.SetActive(Mathf.Abs(offset) > 50);
};
@@ -71,7 +71,7 @@ namespace Ichni.Menu
(uint)AkCallbackType.AK_EnableGetMusicPlayPosition | (uint)AkCallbackType.AK_MusicSyncEntry,
OnMusicEvent, null);
offsetModifier.SetValue(SettingsManager.instance.gameSettings.beatmapOffset);
offsetModifier.SetValue(SettingsManager.instance.settingsSaveData.beatmapOffset);
}
void Update()
@@ -141,4 +141,4 @@ namespace Ichni.Menu
}
}
}
}
}

View File

@@ -5,7 +5,7 @@ namespace Ichni.Menu
{
public class SettingsUIPage : UIPageBase
{
GameSettings gameSettings => SettingsManager.instance.gameSettings;
SettingsSaveData gameSettings => SettingsManager.instance.settingsSaveData;
public Button backButton;
public SettingsWindowController settingsWindowController;
@@ -23,4 +23,4 @@ namespace Ichni.Menu
});
}
}
}
}

View File

@@ -7,7 +7,7 @@ namespace Ichni.Menu
{
public abstract class SettingsWindow : SerializedMonoBehaviour
{
public GameSettings gameSettings => SettingsManager.instance.gameSettings;
public SettingsSaveData gameSettings => SettingsManager.instance.settingsSaveData;
public abstract void Initialize();
@@ -18,4 +18,4 @@ namespace Ichni.Menu
}
}
}
}

View File

@@ -11,7 +11,7 @@ namespace Ichni.Menu
{
public partial class SettingsWindowController : MonoBehaviour
{
public GameSettings gameSettings => SettingsManager.instance.gameSettings;
public SettingsSaveData gameSettings => SettingsManager.instance.settingsSaveData;
public CanvasGroup canvasGroup;
@@ -70,4 +70,4 @@ namespace Ichni.Menu
{
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c826ec81b4d62334697e4620a4cb2474
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,143 @@
fileFormatVersion: 2
guid: 49526de8ef0d7374696f0e4fd4533142
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 25, y: 20, z: 25, w: 20}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 1537655665
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,143 @@
fileFormatVersion: 2
guid: 19ac3b816ac249d42962b597b4a5b9d5
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 42, z: 0, w: 42}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 1537655665
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,143 @@
fileFormatVersion: 2
guid: 6fa105ce9ef516d46879783dbfeb0e88
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,21 +1,21 @@
ManifestFileVersion: 0
UnityVersion: 6000.3.7f1
CRC: 3502675414
CRC: 4053260153
HashAppended: 0
AssetBundleManifest:
AssetBundleInfos:
Info_0:
Name: shapes
Name: unifiedraytracing
Dependencies: {}
Info_1:
Name: basic
Dependencies: {}
Info_2:
Name: metropolis_on_orbit
Dependencies: {}
Info_3:
Name: departure_to_multiverse
Dependencies: {}
Info_4:
Name: unifiedraytracing
Info_3:
Name: metropolis_on_orbit
Dependencies: {}
Info_4:
Name: shapes
Dependencies: {}

View File

@@ -1,16 +1,16 @@
ManifestFileVersion: 0
UnityVersion: 6000.3.7f1
CRC: 1054575820
CRC: 4187162310
Hashes:
AssetFileHash:
serializedVersion: 2
Hash: 007f20ee8efa9449fd672739395d8b8b
Hash: 041639c891c497f5cda773d76795250a
TypeTreeHash:
serializedVersion: 2
Hash: dfed41e3b38758e5f34df30221d719dc
IncrementalBuildHash:
serializedVersion: 2
Hash: 2fc84aafa10273fada9abacc94cc0ec8
Hash: 1175efd1b6efe2bebf239ab190c42566
HashAppended: 0
ClassTypes:
- Class: 1

View File

@@ -1,16 +1,16 @@
ManifestFileVersion: 0
UnityVersion: 6000.3.7f1
CRC: 3814763255
CRC: 12343729
Hashes:
AssetFileHash:
serializedVersion: 2
Hash: b67223887fe2f824117e4b27a9358f9c
Hash: 95c6613dec434b5e157eab1de94cfdfc
TypeTreeHash:
serializedVersion: 2
Hash: 8111ab10d600eaa1a60bd178b1bcf4cf
Hash: 6d9f9b8bc1074349261a0dbbf7f0f9f8
IncrementalBuildHash:
serializedVersion: 2
Hash: 85f89324f6067e3b6b212baf51e9b101
Hash: 0f6768f5833370c99987c209c7f083f1
HashAppended: 0
ClassTypes:
- Class: 1
@@ -57,6 +57,8 @@ ClassTypes:
Script: {fileID: 11500000, guid: 638fbd0e919667f47a93485dd230c71f, type: 3}
- Class: 114
Script: {fileID: 11500000, guid: eb907071d10d7324995ff3aefbc2f1df, type: 3}
- Class: 114
Script: {fileID: 11500000, guid: 922e341faf5784c41b3b0466e0468972, type: 3}
- Class: 115
Script: {instanceID: 0}
- Class: 135

View File

@@ -10,7 +10,7 @@ Hashes:
Hash: 8ab77e54d824a64c30bc20496c5f5926
IncrementalBuildHash:
serializedVersion: 2
Hash: cb0fdc66431f09d69236a0e556b74895
Hash: 204f3b93fddc5bbe213f57aa05799cdd
HashAppended: 0
ClassTypes:
- Class: 1

View File

@@ -10,7 +10,7 @@ Hashes:
Hash: bc9f87c7e64ff656997db0f2b2a91703
IncrementalBuildHash:
serializedVersion: 2
Hash: 612a23b0b40c16e3e1e3cbbf90636bd0
Hash: 98313c8eb74f89178044a9a994c30a81
HashAppended: 0
ClassTypes:
- Class: 28

View File

@@ -1,6 +1,6 @@
ManifestFileVersion: 0
UnityVersion: 6000.3.7f1
CRC: 803255327
CRC: 3304633615
HashAppended: 0
AssetBundleManifest:
AssetBundleInfos:

View File

@@ -1,16 +1,16 @@
ManifestFileVersion: 0
UnityVersion: 6000.3.7f1
CRC: 3067715901
CRC: 1012070008
Hashes:
AssetFileHash:
serializedVersion: 2
Hash: 6abcdfd9cae8dff022735fc0aa0fb26c
Hash: f038c8737dccdbbb922785ad13defdbe
TypeTreeHash:
serializedVersion: 2
Hash: dfed41e3b38758e5f34df30221d719dc
IncrementalBuildHash:
serializedVersion: 2
Hash: 5137fd1deb792cb6f62602cd7823e6fb
Hash: daff94e5b2902357c1b4f50d325b0bb4
HashAppended: 0
ClassTypes:
- Class: 1

View File

@@ -1,16 +1,16 @@
ManifestFileVersion: 0
UnityVersion: 6000.3.7f1
CRC: 1851616411
CRC: 3176997596
Hashes:
AssetFileHash:
serializedVersion: 2
Hash: e222936565bcc8f611c0cecb46e4de21
Hash: 2739e7db8a5127e5f4e38ed26a5dab24
TypeTreeHash:
serializedVersion: 2
Hash: 8111ab10d600eaa1a60bd178b1bcf4cf
Hash: 6d9f9b8bc1074349261a0dbbf7f0f9f8
IncrementalBuildHash:
serializedVersion: 2
Hash: 67609888e0ca1cd25ae3c8a70c9cc423
Hash: 087cd007fe584b08e6ef217eeaede36a
HashAppended: 0
ClassTypes:
- Class: 1
@@ -57,6 +57,8 @@ ClassTypes:
Script: {fileID: 11500000, guid: 638fbd0e919667f47a93485dd230c71f, type: 3}
- Class: 114
Script: {fileID: 11500000, guid: eb907071d10d7324995ff3aefbc2f1df, type: 3}
- Class: 114
Script: {fileID: 11500000, guid: 922e341faf5784c41b3b0466e0468972, type: 3}
- Class: 115
Script: {instanceID: 0}
- Class: 135

View File

@@ -10,7 +10,7 @@ Hashes:
Hash: 8ab77e54d824a64c30bc20496c5f5926
IncrementalBuildHash:
serializedVersion: 2
Hash: d112fcb376deb19d16966ce67cc858fe
Hash: 2b9d1caa8b3c01da7365d30bd4e1e291
HashAppended: 0
ClassTypes:
- Class: 1

View File

@@ -34636,6 +34636,7 @@ GameObject:
- component: {fileID: 6374922826736626084}
- component: {fileID: 8731137484130123864}
- component: {fileID: 6411650891525449585}
- component: {fileID: 7677400993941291652}
m_Layer: 0
m_Name: Flick_HitFX
m_TagString: Untagged
@@ -39468,3 +39469,38 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
--- !u!114 &7677400993941291652
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7916584198410465453}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 922e341faf5784c41b3b0466e0468972, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Ichni.VFXObject
serializationData:
SerializedFormat: 2
SerializedBytes:
ReferencedUnityObjects: []
SerializedBytesString:
Prefab: {fileID: 0}
PrefabModificationsReferencedUnityObjects: []
PrefabModifications: []
SerializationNodes:
- Name: onSpawnAction
Entry: 6
Data:
- Name: onDespawnAction
Entry: 6
Data:
isAutoDespawn: 1
autoDespawnTime: 0.4
despawnTimer: 0
disableAtMediumAndLower:
- {fileID: 4315719500094685406}
- {fileID: 7653901243468862591}
disableAtLowOnly:
- {fileID: 218248303542728390}

View File

@@ -19422,6 +19422,7 @@ GameObject:
- component: {fileID: 1036169766419486204}
- component: {fileID: 3711062127305343447}
- component: {fileID: 4188768693027638627}
- component: {fileID: 380390184437325210}
m_Layer: 0
m_Name: Hold_HitFX_B
m_TagString: Untagged
@@ -24253,6 +24254,41 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
--- !u!114 &380390184437325210
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2943020494385716766}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 922e341faf5784c41b3b0466e0468972, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Ichni.VFXObject
serializationData:
SerializedFormat: 2
SerializedBytes:
ReferencedUnityObjects: []
SerializedBytesString:
Prefab: {fileID: 0}
PrefabModificationsReferencedUnityObjects: []
PrefabModifications: []
SerializationNodes:
- Name: onSpawnAction
Entry: 6
Data:
- Name: onDespawnAction
Entry: 6
Data:
isAutoDespawn: 0
autoDespawnTime: 1
despawnTimer: 0
disableAtMediumAndLower:
- {fileID: 6573924625455929684}
disableAtLowOnly:
- {fileID: 6651680105352939543}
- {fileID: 2070592205952932412}
--- !u!1 &6573924625455929684
GameObject:
m_ObjectHideFlags: 0

View File

@@ -4847,6 +4847,7 @@ GameObject:
- component: {fileID: 6795338915013005225}
- component: {fileID: 7686838569397045560}
- component: {fileID: 8644577418426854476}
- component: {fileID: 786338997556789770}
m_Layer: 0
m_Name: Hold_HitFX_G
m_TagString: Untagged
@@ -9678,6 +9679,41 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
--- !u!114 &786338997556789770
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5199942171001745879}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 922e341faf5784c41b3b0466e0468972, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Ichni.VFXObject
serializationData:
SerializedFormat: 2
SerializedBytes:
ReferencedUnityObjects: []
SerializedBytesString:
Prefab: {fileID: 0}
PrefabModificationsReferencedUnityObjects: []
PrefabModifications: []
SerializationNodes:
- Name: onSpawnAction
Entry: 6
Data:
- Name: onDespawnAction
Entry: 6
Data:
isAutoDespawn: 0
autoDespawnTime: 1
despawnTimer: 0
disableAtMediumAndLower:
- {fileID: 8145642094151385667}
disableAtLowOnly:
- {fileID: 7142595897816035270}
- {fileID: 6969281223192018514}
--- !u!1 &6422879766326712376
GameObject:
m_ObjectHideFlags: 0

View File

@@ -29152,6 +29152,7 @@ GameObject:
- component: {fileID: 8304709085130353050}
- component: {fileID: 8543824802591545481}
- component: {fileID: 8940303714819021693}
- component: {fileID: 7045525186860897399}
m_Layer: 0
m_Name: Hold_HitFX_P
m_TagString: Untagged
@@ -33983,3 +33984,38 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
--- !u!114 &7045525186860897399
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8837549430712806196}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 922e341faf5784c41b3b0466e0468972, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Ichni.VFXObject
serializationData:
SerializedFormat: 2
SerializedBytes:
ReferencedUnityObjects: []
SerializedBytesString:
Prefab: {fileID: 0}
PrefabModificationsReferencedUnityObjects: []
PrefabModifications: []
SerializationNodes:
- Name: onSpawnAction
Entry: 6
Data:
- Name: onDespawnAction
Entry: 6
Data:
isAutoDespawn: 0
autoDespawnTime: 1
despawnTimer: 0
disableAtMediumAndLower:
- {fileID: 1807912427671244093}
disableAtLowOnly:
- {fileID: 4690891593166535979}
- {fileID: 5194171103924125613}

View File

@@ -4905,6 +4905,7 @@ GameObject:
- component: {fileID: 3798029571758690913}
- component: {fileID: 6512331645267871761}
- component: {fileID: 4138720391616017682}
- component: {fileID: 5571405091865508458}
m_Layer: 0
m_Name: Stay_HitFX
m_TagString: Untagged
@@ -9733,6 +9734,39 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
--- !u!114 &5571405091865508458
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5249990677744981946}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 922e341faf5784c41b3b0466e0468972, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Ichni.VFXObject
serializationData:
SerializedFormat: 2
SerializedBytes:
ReferencedUnityObjects: []
SerializedBytesString:
Prefab: {fileID: 0}
PrefabModificationsReferencedUnityObjects: []
PrefabModifications: []
SerializationNodes:
- Name: onSpawnAction
Entry: 6
Data:
- Name: onDespawnAction
Entry: 6
Data:
isAutoDespawn: 1
autoDespawnTime: 1
despawnTimer: 0
disableAtMediumAndLower:
- {fileID: 481741008711077494}
disableAtLowOnly: []
--- !u!1 &5931878544904699336
GameObject:
m_ObjectHideFlags: 0

View File

@@ -9826,6 +9826,7 @@ GameObject:
- component: {fileID: 8544422724010775666}
- component: {fileID: 8082031638419169081}
- component: {fileID: 1014060429300395170}
- component: {fileID: 4889441942532997180}
m_Layer: 0
m_Name: Tap_HiEffect_B
m_TagString: Untagged
@@ -14653,3 +14654,35 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
--- !u!114 &4889441942532997180
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8754511166712621810}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 922e341faf5784c41b3b0466e0468972, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Ichni.VFXObject
serializationData:
SerializedFormat: 2
SerializedBytes:
ReferencedUnityObjects: []
SerializedBytesString:
Prefab: {fileID: 0}
PrefabModificationsReferencedUnityObjects: []
PrefabModifications: []
SerializationNodes:
- Name: onSpawnAction
Entry: 6
Data:
- Name: onDespawnAction
Entry: 6
Data:
isAutoDespawn: 1
autoDespawnTime: 0.4
despawnTimer: 0
disableAtMediumAndLower: []
disableAtLowOnly: []

View File

@@ -9844,6 +9844,7 @@ GameObject:
- component: {fileID: 3263924656594740668}
- component: {fileID: 7038272264455304515}
- component: {fileID: 8700396741641032723}
- component: {fileID: 987397096316892202}
m_Layer: 0
m_Name: Tap_HitEffect_G
m_TagString: Untagged
@@ -14672,6 +14673,39 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
--- !u!114 &987397096316892202
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7671123070809976812}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 922e341faf5784c41b3b0466e0468972, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Ichni.VFXObject
serializationData:
SerializedFormat: 2
SerializedBytes:
ReferencedUnityObjects: []
SerializedBytesString:
Prefab: {fileID: 0}
PrefabModificationsReferencedUnityObjects: []
PrefabModifications: []
SerializationNodes:
- Name: onSpawnAction
Entry: 6
Data:
- Name: onDespawnAction
Entry: 6
Data:
isAutoDespawn: 1
autoDespawnTime: 0.4
despawnTimer: 0
disableAtMediumAndLower:
- {fileID: 7986537471941685033}
disableAtLowOnly: []
--- !u!1 &7986537471941685033
GameObject:
m_ObjectHideFlags: 0

View File

@@ -4923,6 +4923,7 @@ GameObject:
- component: {fileID: 5040270902117431550}
- component: {fileID: 447330800752594519}
- component: {fileID: 1390550520963138891}
- component: {fileID: 4762391414571929359}
m_Layer: 0
m_Name: Tap_HitEffect_P
m_TagString: Untagged
@@ -9753,6 +9754,41 @@ ParticleSystemRenderer:
m_MeshWeighting1: 1
m_MeshWeighting2: 1
m_MeshWeighting3: 1
--- !u!114 &4762391414571929359
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 976141841207214558}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 922e341faf5784c41b3b0466e0468972, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::SLSUtilities.LeanPoolAssistance.VFXObject
serializationData:
SerializedFormat: 2
SerializedBytes:
ReferencedUnityObjects: []
SerializedBytesString:
Prefab: {fileID: 0}
PrefabModificationsReferencedUnityObjects: []
PrefabModifications: []
SerializationNodes:
- Name: onSpawnAction
Entry: 6
Data:
- Name: onDespawnAction
Entry: 6
Data:
isAutoDespawn: 1
autoDespawnTime: 0.4
despawnTimer: 0
disableAtMediumAndLower:
- {fileID: 7286007667833831019}
- {fileID: 1526589185383174293}
disableAtLowOnly:
- {fileID: 587594120215132903}
--- !u!1 &1526589185383174293
GameObject:
m_ObjectHideFlags: 0

View File

@@ -35,8 +35,6 @@ namespace Ichni.RhythmGame.ThemeBundles.DepartureToMultiverse
{
noteVisual.extraPartList[0].gameObject.SetActive(false);
}
LeanPool.Despawn(effectParticle.gameObject, 1);
}
#endregion
}

Some files were not shown because too many files have changed in this diff Show More