UI update 02

This commit is contained in:
FloatGaming
2026-06-30 21:21:30 +08:00
parent b2a1e307a4
commit f62f643cfc
1666 changed files with 211081 additions and 22799 deletions
+27 -2
View File
@@ -2,6 +2,7 @@ using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using Bansonic;
public class BootLoader : MonoBehaviour
{
@@ -19,6 +20,8 @@ public class BootLoader : MonoBehaviour
[Tooltip("Optional Text to show loading percentage.")]
[SerializeField] private Text progressText;
private bool transitionStarted;
private void Start()
{
// Start the asynchronous loading process
@@ -61,8 +64,16 @@ public class BootLoader : MonoBehaviour
float elapsedTime = Time.time - startTime;
if (elapsedTime >= minSplashTime)
{
// Allow the scene to activate
asyncLoad.allowSceneActivation = true;
if (!transitionStarted)
{
transitionStarted = true;
if (gTransition.Run(AllowSceneActivationRoutine(asyncLoad)))
{
yield break;
}
asyncLoad.allowSceneActivation = true;
}
}
}
@@ -72,4 +83,18 @@ public class BootLoader : MonoBehaviour
// Restore screen sleep timeout
Screen.sleepTimeout = SleepTimeout.SystemSetting;
}
private IEnumerator AllowSceneActivationRoutine(AsyncOperation asyncLoad)
{
if (asyncLoad == null)
{
yield break;
}
asyncLoad.allowSceneActivation = true;
while (!asyncLoad.isDone)
{
yield return null;
}
}
}
+2 -149
View File
@@ -1925,30 +1925,6 @@ public class AllyCombatant : MonoBehaviour, ICombatant
}
return;
}
// Fallback: check primary group
var fallbackGroup = so.GetPrimarySkillGroup();
if (fallbackGroup != null)
{
foreach (var def in fallbackGroup.skills)
{
if (def == null) continue;
if (def.triggerCondition != when) continue;
SkillBuilder.Instance.UseSkillDefinition(def, slotIndex, -1f, null);
}
return;
}
// Final fallback: iterate availableSkills
if (so.availableSkills != null)
{
foreach (var def in so.availableSkills)
{
if (def == null) continue;
if (def.triggerCondition != when) continue;
SkillBuilder.Instance.UseSkillDefinition(def, slotIndex, -1f, null);
}
}
}
private void TryCastOnFullMana()
@@ -2005,10 +1981,8 @@ public class AllyCombatant : MonoBehaviour, ICombatant
var so = SkillBuilder.Instance.GetAllyHeroSOBySlot(slotIndex);
if (so == null)
{
// Can't locate SO for this slot; attempt best-effort: call UsePrimarySkillForSlot which will log details
Debug.LogWarning($"[AllyCombatant] Slot {slotIndex + 1}: AllyHero_SO not found for slot. Falling back to UsePrimarySkillForSlot.");
SkillBuilder.Instance.UsePrimarySkillForSlot(slotIndex, -1f, null);
return true;
Debug.LogWarning($"[AllyCombatant] Slot {slotIndex + 1}: AllyHero_SO not found for slot. Skip OnManaFull cast.");
return false;
}
bool anyTriggered = false;
@@ -2033,47 +2007,6 @@ public class AllyCombatant : MonoBehaviour, ICombatant
}
}
// Second: primary group
if (!anyTriggered)
{
var fallbackGroup = so.GetPrimarySkillGroup();
if (fallbackGroup != null)
{
foreach (var skill in fallbackGroup.skills)
{
if (skill == null) continue;
if (skill.triggerCondition != SkillDefinition.SkillTrigger.OnManaFull) continue;
SkillBuilder.Instance.UseSkillDefinition(skill, slotIndex, -1f, null);
LogVerbose($"[AllyCombatant] Slot {slotIndex + 1} cast skill from primary group: {skill.skillId} (OnManaFull)");
anyTriggered = true;
}
}
}
// Third: availableSkills / primary skill fallback
if (!anyTriggered)
{
// try primary skill specifically
var def = so.GetPrimarySkill();
if (def != null && def.triggerCondition == SkillDefinition.SkillTrigger.OnManaFull)
{
SkillBuilder.Instance.UseSkillDefinition(def, slotIndex, -1f, null);
LogVerbose($"[AllyCombatant] Slot {slotIndex + 1} cast primary skill '{def.skillId}' due to ManaFull.");
anyTriggered = true;
}
else if (so.availableSkills != null)
{
foreach (var skill in so.availableSkills)
{
if (skill == null) continue;
if (skill.triggerCondition != SkillDefinition.SkillTrigger.OnManaFull) continue;
SkillBuilder.Instance.UseSkillDefinition(skill, slotIndex, -1f, null);
LogVerbose($"[AllyCombatant] Slot {slotIndex + 1} cast available skill: {skill.skillId} (OnManaFull)");
anyTriggered = true;
}
}
}
if (!anyTriggered)
{
LogVerbose($"[AllyCombatant] Slot {slotIndex + 1}: no skills configured for OnManaFull.");
@@ -2184,26 +2117,6 @@ public class AllyCombatant : MonoBehaviour, ICombatant
}
}
}
else
{
var fallbackGroup = so.GetPrimarySkillGroup();
if (fallbackGroup != null && fallbackGroup.skills != null)
{
for (int i = 0; i < fallbackGroup.skills.Length; i++)
{
var def = fallbackGroup.skills[i];
if (def != null) defs.Add(def);
}
}
else if (so.availableSkills != null)
{
for (int i = 0; i < so.availableSkills.Length; i++)
{
var def = so.availableSkills[i];
if (def != null) defs.Add(def);
}
}
}
var vars = BuildFormulaVars(so);
@@ -2344,26 +2257,6 @@ public class AllyCombatant : MonoBehaviour, ICombatant
}
}
}
else
{
var fallbackGroup = so.GetPrimarySkillGroup();
if (fallbackGroup != null && fallbackGroup.skills != null)
{
for (int i = 0; i < fallbackGroup.skills.Length; i++)
{
var def = fallbackGroup.skills[i];
if (def != null) defs.Add(def);
}
}
else if (so.availableSkills != null)
{
for (int i = 0; i < so.availableSkills.Length; i++)
{
var def = so.availableSkills[i];
if (def != null) defs.Add(def);
}
}
}
var vars = BuildFormulaVars(so);
@@ -2446,26 +2339,6 @@ public class AllyCombatant : MonoBehaviour, ICombatant
}
}
}
else
{
var fallbackGroup = so.GetPrimarySkillGroup();
if (fallbackGroup != null && fallbackGroup.skills != null)
{
for (int i = 0; i < fallbackGroup.skills.Length; i++)
{
var def = fallbackGroup.skills[i];
if (def != null) defs.Add(def);
}
}
else if (so.availableSkills != null)
{
for (int i = 0; i < so.availableSkills.Length; i++)
{
var def = so.availableSkills[i];
if (def != null) defs.Add(def);
}
}
}
// Pick the most restrictive threshold that is still satisfied (smallest attackTriggerValue such that attack < value).
string selected = null;
@@ -2567,26 +2440,6 @@ public class AllyCombatant : MonoBehaviour, ICombatant
}
}
}
else
{
var fallbackGroup = so.GetPrimarySkillGroup();
if (fallbackGroup != null && fallbackGroup.skills != null)
{
for (int i = 0; i < fallbackGroup.skills.Length; i++)
{
var def = fallbackGroup.skills[i];
if (def != null) defs.Add(def);
}
}
else if (so.availableSkills != null)
{
for (int i = 0; i < so.availableSkills.Length; i++)
{
var def = so.availableSkills[i];
if (def != null) defs.Add(def);
}
}
}
// Pick the closest satisfied threshold from below (largest attackTriggerValue such that attack > value).
string selected = null;
+2 -168
View File
@@ -3104,18 +3104,7 @@ ResolvedGroup:
}
return;
}
// Fallback: prefer any defined primary group (SO-level) via GetPrimarySkillGroup(), otherwise use primarySkillIndex
var fallbackGroup = so.GetPrimarySkillGroup();
if (fallbackGroup != null)
{
UseSkillGroupForSlot(fallbackGroup, slotIndex, inputValue, specificTarget);
return;
}
int idx = so.primarySkillIndex;
if (idx < 0) { Debug.LogWarning($"UsePrimarySkillForSlot: primarySkillIndex not set for slot {slotIndex}"); return; }
UseSelectedSkillForSlot(slotIndex, idx, inputValue, specificTarget);
Debug.LogWarning($"UsePrimarySkillForSlot: no equipped skill groups for slot {slotIndex}");
}
// Cast all non-null skills in a SkillGroup for a given slotIndex. Each skill is invoked via UseSkillDefinition.
@@ -3219,30 +3208,6 @@ ResolvedGroup:
}
continue;
}
// Fallback: check SO-level primary group via GetPrimarySkillGroup()
var fallbackGroup = so.GetPrimarySkillGroup();
if (fallbackGroup != null)
{
foreach (var sk in fallbackGroup.skills)
{
if (sk == null) continue;
if (sk.triggerCondition == SkillDefinition.SkillTrigger.OnGameStart)
{
LogVerbose($"[SkillBuilder] TriggerOnGameStart: casting group skill {sk.skillId} for slot {i}");
UseSkillDefinition(sk, i, -1f, null);
}
}
continue;
}
var def = so.GetPrimarySkill();
if (def == null) continue;
if (def.triggerCondition == SkillDefinition.SkillTrigger.OnGameStart)
{
LogVerbose($"[SkillBuilder] TriggerOnGameStart: casting primary skill {def.skillId} for slot {i}");
UsePrimarySkillForSlot(i, -1f, null);
}
}
}
@@ -3283,31 +3248,6 @@ ResolvedGroup:
}
continue;
}
var fallbackGroup = so.GetPrimarySkillGroup();
if (fallbackGroup != null)
{
foreach (var def in fallbackGroup.skills)
{
if (def == null) continue;
if (def.triggerCondition != SkillDefinition.SkillTrigger.OnEnemyDead) continue;
LogVerbose($"[SkillBuilder] TriggerOnEnemyDead: slot {i+1} casting skill {def.skillId}");
GameObject ctxTarget = null;
if (deadEnemy != null && (def.requiresSpecificTarget || def.defaultSelector == Selector.CurrentEnemies)) ctxTarget = deadEnemy.gameObject;
UseSkillDefinition(def, i, -1f, ctxTarget);
}
continue;
}
var primary = so.GetPrimarySkill();
if (primary != null && primary.triggerCondition == SkillDefinition.SkillTrigger.OnEnemyDead)
{
LogVerbose($"[SkillBuilder] TriggerOnEnemyDead: slot {i+1} casting primary skill {primary.skillId}");
// Only pass deadEnemy as specificTarget when the skill truly expects a specific target (e.g. targets CurrentEnemies)
GameObject ctxTarget = null;
if (deadEnemy != null && (primary.requiresSpecificTarget || primary.defaultSelector == Selector.CurrentEnemies)) ctxTarget = deadEnemy.gameObject;
UsePrimarySkillForSlot(i, -1f, ctxTarget);
}
}
}
@@ -3344,26 +3284,6 @@ ResolvedGroup:
}
continue;
}
var fallbackGroup2 = so.GetPrimarySkillGroup();
if (fallbackGroup2 != null)
{
foreach (var def in fallbackGroup2.skills)
{
if (def == null) continue;
if (def.triggerCondition != SkillDefinition.SkillTrigger.OnEnemyRevive) continue;
LogVerbose($"[SkillBuilder] TriggerOnEnemyRevive: slot {i+1} casting skill {def.skillId} (from primary group)");
UseSkillDefinition(def, i, -1f, enemy != null ? enemy.gameObject : null);
}
continue;
}
var primary2 = so.GetPrimarySkill();
if (primary2 != null && primary2.triggerCondition == SkillDefinition.SkillTrigger.OnEnemyRevive)
{
LogVerbose($"[SkillBuilder] TriggerOnEnemyRevive: slot {i+1} casting primary skill {primary2.skillId}");
UsePrimarySkillForSlot(i, -1f, enemy != null ? enemy.gameObject : null);
}
}
}
@@ -3395,24 +3315,6 @@ ResolvedGroup:
}
continue;
}
var fallbackGroup3 = so.GetPrimarySkillGroup();
if (fallbackGroup3 != null)
{
foreach (var def in fallbackGroup3.skills)
{
if (def == null) continue;
if (def.triggerCondition != SkillDefinition.SkillTrigger.OnAllEnemiesDefeated) continue;
UseSkillDefinition(def, i, -1f, null);
}
continue;
}
var primary3 = so.GetPrimarySkill();
if (primary3 != null && primary3.triggerCondition == SkillDefinition.SkillTrigger.OnAllEnemiesDefeated)
{
UsePrimarySkillForSlot(i, -1f, null);
}
}
}
@@ -3474,24 +3376,6 @@ ResolvedGroup:
}
continue;
}
var fallbackGroup = so.GetPrimarySkillGroup();
if (fallbackGroup != null && fallbackGroup.skills != null)
{
foreach (var def in fallbackGroup.skills)
{
if (def == null) continue;
if (def.triggerCondition != SkillDefinition.SkillTrigger.OnAdjacentAllySkillCast) continue;
UseSkillDefinition(def, i, -1f, null);
}
continue;
}
var primary = so.GetPrimarySkill();
if (primary != null && primary.triggerCondition == SkillDefinition.SkillTrigger.OnAdjacentAllySkillCast)
{
UsePrimarySkillForSlot(i, -1f, null);
}
}
}
@@ -3644,57 +3528,7 @@ ResolvedGroup:
}
return anyTriggered;
}
// Fallback to previous behavior using primary skill (availableSkills)
var defPrimary = so.GetPrimarySkill();
if (defPrimary == null) { LogVerbose($"[SkillBuilder] NotifyNoteHit: no primary skill for slot {trackIndex}"); return false; }
if (defPrimary.triggerCondition != SkillDefinition.SkillTrigger.OnNoteHit)
{
LogVerbose($"[SkillBuilder] NotifyNoteHit: primary skill {defPrimary.skillId} for slot {trackIndex} is not set to OnNoteHit (actual={defPrimary.triggerCondition}), rejecting");
return false;
}
// previous checks preserved
var effectiveNoteTypePrimary = noteType;
if (noteType == SkillDefinition.NoteTypeTrigger.Hold) effectiveNoteTypePrimary = SkillDefinition.NoteTypeTrigger.Tap;
switch (defPrimary.noteTriggerType)
{
case SkillDefinition.NoteTypeTrigger.Tap:
if (effectiveNoteTypePrimary != SkillDefinition.NoteTypeTrigger.Tap) { LogVerbose($"[SkillBuilder] NotifyNoteHit: skill {defPrimary.skillId} requires Tap but event is {noteType}, rejecting"); return false; }
break;
case SkillDefinition.NoteTypeTrigger.Hold:
if (noteType != SkillDefinition.NoteTypeTrigger.Hold) { LogVerbose($"[SkillBuilder] NotifyNoteHit: skill {defPrimary.skillId} requires Hold but event is {noteType}, rejecting"); return false; }
break;
case SkillDefinition.NoteTypeTrigger.Either:
break;
}
int qualityPrimary = JudgeQualityFromString(judgeResult);
if (defPrimary.onNoteHitMinThreshold == SkillDefinition.NoteTriggerThreshold.Miss)
{
if (qualityPrimary != 0) { LogVerbose($"[SkillBuilder] NotifyNoteHit: skill {defPrimary.skillId} is Miss-only but quality={qualityPrimary}, rejecting"); return false; }
}
else
{
int required = (int)defPrimary.onNoteHitMinThreshold;
if (qualityPrimary < required) { LogVerbose($"[SkillBuilder] NotifyNoteHit: skill {defPrimary.skillId} requires quality>={required} but got {qualityPrimary}, rejecting"); return false; }
}
string keyPrimary = $"{trackIndex}:{defPrimary.skillId}";
float nowPrimary = Time.time;
if (defPrimary.onNoteHitCooldown > 0f && _lastOnNoteHitTriggerTime.TryGetValue(keyPrimary, out float lastPrimary))
{
if (nowPrimary - lastPrimary < defPrimary.onNoteHitCooldown)
{
LogVerbose($"[SkillBuilder] NotifyNoteHit: skill {defPrimary.skillId} on cooldown for slot {trackIndex}");
return false;
}
}
LogVerbose($"[SkillBuilder] NotifyNoteHit: triggering primary skill {defPrimary.skillId} for slot {trackIndex} due to note hit {judgeResult} (noteType={noteType})");
UsePrimarySkillForSlot(trackIndex, -1f, null);
_lastOnNoteHitTriggerTime[keyPrimary] = nowPrimary;
return true;
return false;
}
/*
+101 -12
View File
@@ -32,6 +32,10 @@ public class AllyHero_SO : ScriptableObject
[Tooltip("Optional obsession tag used by memory skills such as 30011012. Leave empty to ignore mismatch checks.")]
public string obsessionTag;
[Header("DLC")]
[Tooltip("DLC key that owns this hero. Leave empty for base-game heroes that are always available. When set, the hero is only accessible when the matching DLC is owned.")]
public string sourceDlcId;
[Header("Inspector")]
public Sprite ally_heroImage;
[Header("Inspector")]
@@ -120,6 +124,8 @@ public class AllyHero_SO : ScriptableObject
[Header("Inspector")]
public int ally_currentEXP;
public int ally_growthUnlockedTierIndex;
[Tooltip("When enabled, level display/effective level resolution is clamped to the previous tier while this hero is breakthrough-locked, even if EXP has already reached the next tier threshold.")]
public bool level_lock;
public bool ally_autoBreakthroughEnabled;
public int ally_battleDeployCount;
public int ally_finishCount;
@@ -165,14 +171,6 @@ public class AllyHero_SO : ScriptableObject
}
}
if (skillGroups != null)
{
foreach (SkillGroup group in skillGroups)
{
if (group != null) return group;
}
}
return null;
}
@@ -365,11 +363,53 @@ public class AllyHero_SO : ScriptableObject
result.Add(groupId);
}
public int GetUnlockedLevelIndex()
{
List<AllyLevelInfo> sorted = BuildSortedLevelStats();
return ResolveUnlockedLevelIndex(sorted);
}
public int GetExpQualifiedLevelIndex()
{
List<AllyLevelInfo> sorted = BuildSortedLevelStats();
return ResolveExpQualifiedLevelIndex(sorted);
}
public int GetDisplayLevelIndex()
{
List<AllyLevelInfo> sorted = BuildSortedLevelStats();
return ResolveDisplayLevelIndex(sorted);
}
public string GetDisplayLevelRatingKey()
{
int displayIndex = GetDisplayLevelIndex();
if (displayIndex <= 0) return "C";
if (displayIndex == 1) return "B";
if (displayIndex == 2) return "A";
return "S";
}
public AllyLevelInfo GetEffectiveLevelForCurrentEXP()
{
if (levelStats == null || levelStats.Count == 0) return null;
List<AllyLevelInfo> sorted = BuildSortedLevelStats();
int unlockedIndex = ResolveUnlockedLevelIndex(sorted);
if (unlockedIndex < 0 || unlockedIndex >= sorted.Count)
{
return null;
}
return sorted[unlockedIndex];
}
private List<AllyLevelInfo> BuildSortedLevelStats()
{
var sorted = new List<AllyLevelInfo>();
if (levelStats == null || levelStats.Count == 0)
{
return sorted;
}
List<AllyLevelInfo> sorted = new List<AllyLevelInfo>();
for (int i = 0; i < levelStats.Count; i++)
{
if (levelStats[i] != null)
@@ -384,8 +424,57 @@ public class AllyHero_SO : ScriptableObject
}
sorted.Sort((left, right) => left.requiredEXP.CompareTo(right.requiredEXP));
int unlockedTierIndex = Mathf.Clamp(ally_growthUnlockedTierIndex, 0, sorted.Count - 1);
return sorted[unlockedTierIndex];
return sorted;
}
private int ResolveUnlockedLevelIndex(List<AllyLevelInfo> sorted)
{
if (sorted == null || sorted.Count == 0)
{
return -1;
}
return Mathf.Clamp(ally_growthUnlockedTierIndex, 0, sorted.Count - 1);
}
private int ResolveExpQualifiedLevelIndex(List<AllyLevelInfo> sorted)
{
if (sorted == null || sorted.Count == 0)
{
return -1;
}
int expQualifiedIndex = 0;
for (int i = 0; i < sorted.Count; i++)
{
if (ally_currentEXP >= sorted[i].requiredEXP)
{
expQualifiedIndex = i;
}
else
{
break;
}
}
return Mathf.Clamp(expQualifiedIndex, 0, sorted.Count - 1);
}
private int ResolveDisplayLevelIndex(List<AllyLevelInfo> sorted)
{
if (sorted == null || sorted.Count == 0)
{
return -1;
}
int expQualifiedIndex = ResolveExpQualifiedLevelIndex(sorted);
if (!level_lock)
{
return expQualifiedIndex;
}
int unlockedIndex = ResolveUnlockedLevelIndex(sorted);
return Mathf.Clamp(Mathf.Min(expQualifiedIndex, unlockedIndex), 0, sorted.Count - 1);
}
private AllyLevelInfo CloneLevelInfo(AllyLevelInfo source)
@@ -404,26 +404,7 @@ class AllyHeroGraphView : GraphView
private int GetCurrentLevelIndex(AllyHero_SO so)
{
if (so.levelStats == null || so.levelStats.Count == 0) return -1;
int bestIndex = -1;
int currentExp = so.ally_currentEXP;
for (int i = 0; i < so.levelStats.Count; i++)
{
var lvl = so.levelStats[i];
if (lvl == null) continue;
if (bestIndex < 0)
{
bestIndex = i;
if (currentExp < lvl.requiredEXP) continue;
}
var best = so.levelStats[bestIndex];
if (best == null) continue;
if (currentExp >= lvl.requiredEXP && lvl.requiredEXP >= best.requiredEXP)
{
bestIndex = i;
}
}
return bestIndex < 0 ? 0 : bestIndex;
return so != null ? so.GetDisplayLevelIndex() : -1;
}
private string FormatNumber(float value)
+1 -7
View File
@@ -337,12 +337,6 @@ public static class HeroSkinResolver
}
#endif
HeroSkinSO[] loaded = RuntimeResourcesCache.LoadAllHeroSkins();
if (loaded != null && loaded.Length > 0)
{
return loaded;
}
return Resources.LoadAll<HeroSkinSO>(string.Empty) ?? Array.Empty<HeroSkinSO>();
return RuntimeResourcesCache.LoadAllHeroSkins() ?? Array.Empty<HeroSkinSO>();
}
}
+25 -3
View File
@@ -9,6 +9,7 @@ using UnityEngine.UI;
using TMPro;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using Bansonic;
public class pressStart : MonoBehaviour
{
@@ -301,7 +302,10 @@ public class pressStart : MonoBehaviour
// 3. 切换场景
Debug.Log("[pressStart] Transition sequence complete. Loading UI_UI scene.");
Time.timeScale = 1f;
SceneManager.LoadScene("UI_UI");
if (!gTransition.LoadScene("UI_UI", LoadSceneMode.Single))
{
SceneManager.LoadScene("UI_UI");
}
}
private IEnumerator PollForBanflagAndProceed()
@@ -316,8 +320,17 @@ public class pressStart : MonoBehaviour
UpdateStatus(LocalizationService.Get("press_start.loading_test_mode", "Banflag detected. Loading test mode..."), Color.green);
// small delay to show status
yield return new WaitForSeconds(0.5f);
if (gTransition.LoadScene(testModeSceneName, LoadSceneMode.Single))
{
while (gTransition.IsBusy)
{
yield return null;
}
yield break;
}
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(testModeSceneName);
while (!asyncLoad.isDone)
while (asyncLoad != null && !asyncLoad.isDone)
{
yield return null;
}
@@ -334,8 +347,17 @@ public class pressStart : MonoBehaviour
private IEnumerator LoadSceneAsync(string sceneName)
{
if (gTransition.LoadScene(sceneName, LoadSceneMode.Single))
{
while (gTransition.IsBusy)
{
yield return null;
}
yield break;
}
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
while (!asyncLoad.isDone)
while (asyncLoad != null && !asyncLoad.isDone)
{
yield return null;
}
@@ -340,7 +340,10 @@ public class UI_SongsSelect_RuntimeFeatures : MonoBehaviour
BeatmapManager.SetPendingSong(song, difficulty);
Time.timeScale = 1f;
SceneManager.LoadScene(gameplaySceneName, LoadSceneMode.Single);
if (!gTransition.LoadScene(gameplaySceneName, LoadSceneMode.Single))
{
SceneManager.LoadScene(gameplaySceneName, LoadSceneMode.Single);
}
}
private SongData ResolveSelectedSong()
@@ -2,6 +2,7 @@ using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using Bansonic;
public class back_toPreviousPage : MonoBehaviour
{
@@ -32,8 +33,17 @@ public class back_toPreviousPage : MonoBehaviour
private IEnumerator LoadSceneAsync(string sceneName)
{
if (gTransition.LoadScene(sceneName, LoadSceneMode.Single))
{
while (gTransition.IsBusy)
{
yield return null;
}
yield break;
}
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
while (!asyncLoad.isDone)
while (asyncLoad != null && !asyncLoad.isDone)
{
yield return null;
}
+1 -1
View File
@@ -160,7 +160,7 @@ public class CharacterCardView : MonoBehaviour, IBeginDragHandler, IDragHandler,
return false;
}
AllyHero_SO[] heroes = Resources.LoadAll<AllyHero_SO>("");
AllyHero_SO[] heroes = RuntimeResourcesCache.LoadAllAllyHeroes();
for (int i = 0; i < heroes.Length; i++)
{
AllyHero_SO hero = heroes[i];
+1 -1
View File
@@ -1035,7 +1035,7 @@ public static class TeamShareCodec
return;
}
cachedHeroes = Resources.LoadAll<AllyHero_SO>(string.Empty);
cachedHeroes = RuntimeResourcesCache.LoadAllAllyHeroes();
cachedHeroById = new Dictionary<int, AllyHero_SO>();
if (cachedHeroes == null)
{
+2 -28
View File
@@ -494,9 +494,7 @@ public class newTeamSelector : MonoBehaviour
private static void RebuildHeroCache()
{
cachedHeroes = Resources.LoadAll<AllyHero_SO>("so/ally");
if (cachedHeroes == null || cachedHeroes.Length == 0)
cachedHeroes = Resources.LoadAll<AllyHero_SO>("");
cachedHeroes = RuntimeResourcesCache.LoadAllAllyHeroes();
cachedHeroesById = new Dictionary<int, AllyHero_SO>();
if (cachedHeroes == null) return;
@@ -511,30 +509,6 @@ public class newTeamSelector : MonoBehaviour
private string GetRatingFromSO(AllyHero_SO so)
{
if (so == null || so.levelStats == null || so.levelStats.Count == 0) return "C";
List<AllyHero_SO.AllyLevelInfo> sorted = new List<AllyHero_SO.AllyLevelInfo>();
foreach (var level in so.levelStats)
{
if (level != null)
sorted.Add(level);
}
sorted.Sort((left, right) => left.requiredEXP.CompareTo(right.requiredEXP));
int currentExp = so.ally_currentEXP;
int selectedIndex = 0;
for (int i = 0; i < sorted.Count; i++)
{
if (currentExp >= sorted[i].requiredEXP)
selectedIndex = i;
else
break;
}
if (selectedIndex <= 0) return "C";
if (selectedIndex == 1) return "B";
if (selectedIndex == 2) return "A";
return "S";
return so != null ? so.GetDisplayLevelRatingKey() : "C";
}
}
@@ -144,7 +144,7 @@ public class loadSettlementTeamPrefab : MonoBehaviour
AllyHero_SO resolvedHeroSO = null;
if (allyId > 0)
{
var arr = Resources.LoadAll<AllyHero_SO>("");
var arr = RuntimeResourcesCache.LoadAllAllyHeroes();
foreach (var a in arr)
{
if (a != null && a.ally_heroID == allyId) { resolvedHeroSO = a; break; }
+1 -1
View File
@@ -264,7 +264,7 @@ public class teamSettingPanel : MonoBehaviour
return false;
}
AllyHero_SO[] heroes = Resources.LoadAll<AllyHero_SO>("");
AllyHero_SO[] heroes = RuntimeResourcesCache.LoadAllAllyHeroes();
for (int i = 0; i < heroes.Length; i++)
{
AllyHero_SO hero = heroes[i];
+234
View File
@@ -0,0 +1,234 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
[AddComponentMenu("Layout/Flow Layout Group")]
public class FlowLayoutGroup : LayoutGroup
{
[SerializeField] private float spacingX = 10f;
[SerializeField] private float spacingY = 10f;
[SerializeField] private bool expandChildHeight = false;
[SerializeField] private float forcedChildHeight = 120f;
private readonly List<RowInfo> rows = new List<RowInfo>();
private float calculatedPreferredHeight;
private struct RowInfo
{
public int startIndex;
public int endIndex;
public float width;
public float height;
}
public float SpacingX
{
get => spacingX;
set => SetProperty(ref spacingX, value);
}
public float SpacingY
{
get => spacingY;
set => SetProperty(ref spacingY, value);
}
public bool ExpandChildHeight
{
get => expandChildHeight;
set => SetProperty(ref expandChildHeight, value);
}
public float ForcedChildHeight
{
get => forcedChildHeight;
set => SetProperty(ref forcedChildHeight, value);
}
public override void CalculateLayoutInputHorizontal()
{
base.CalculateLayoutInputHorizontal();
CalculateRows();
float minWidth = padding.horizontal;
float preferredWidth = rectTransform.rect.width > 0f ? rectTransform.rect.width : minWidth;
SetLayoutInputForAxis(minWidth, preferredWidth, -1f, 0);
}
public override void CalculateLayoutInputVertical()
{
CalculateRows();
SetLayoutInputForAxis(calculatedPreferredHeight, calculatedPreferredHeight, -1f, 1);
}
public override void SetLayoutHorizontal()
{
CalculateRows();
SetChildrenAlongAxis();
}
public override void SetLayoutVertical()
{
CalculateRows();
SetChildrenAlongAxis();
}
private void CalculateRows()
{
rows.Clear();
float availableWidth = GetAvailableWidth();
float currentRowWidth = 0f;
float currentRowHeight = 0f;
int currentRowStart = 0;
bool hasRow = false;
for (int i = 0; i < rectChildren.Count; i++)
{
RectTransform child = rectChildren[i];
if (child == null) continue;
float childWidth = Mathf.Max(LayoutUtility.GetPreferredSize(child, 0), LayoutUtility.GetMinSize(child, 0), child.rect.width);
float childHeight = expandChildHeight
? forcedChildHeight
: Mathf.Max(LayoutUtility.GetPreferredSize(child, 1), LayoutUtility.GetMinSize(child, 1), child.rect.height);
float requiredWidth = hasRow ? currentRowWidth + spacingX + childWidth : childWidth;
bool shouldWrap = hasRow && requiredWidth > availableWidth;
if (shouldWrap)
{
rows.Add(new RowInfo
{
startIndex = currentRowStart,
endIndex = i - 1,
width = currentRowWidth,
height = currentRowHeight
});
currentRowStart = i;
currentRowWidth = childWidth;
currentRowHeight = childHeight;
}
else
{
currentRowWidth = hasRow ? requiredWidth : childWidth;
currentRowHeight = Mathf.Max(currentRowHeight, childHeight);
hasRow = true;
}
}
if (hasRow)
{
rows.Add(new RowInfo
{
startIndex = currentRowStart,
endIndex = rectChildren.Count - 1,
width = currentRowWidth,
height = currentRowHeight
});
}
calculatedPreferredHeight = padding.vertical;
for (int i = 0; i < rows.Count; i++)
{
calculatedPreferredHeight += rows[i].height;
if (i < rows.Count - 1)
{
calculatedPreferredHeight += spacingY;
}
}
}
private void SetChildrenAlongAxis()
{
float availableWidth = GetAvailableWidth();
float y = padding.top;
for (int rowIndex = 0; rowIndex < rows.Count; rowIndex++)
{
RowInfo row = rows[rowIndex];
float startX = GetRowStartX(availableWidth, row.width);
float x = startX;
for (int i = row.startIndex; i <= row.endIndex; i++)
{
RectTransform child = rectChildren[i];
if (child == null) continue;
float childWidth = Mathf.Max(LayoutUtility.GetPreferredSize(child, 0), LayoutUtility.GetMinSize(child, 0), child.rect.width);
float childHeight = expandChildHeight
? forcedChildHeight
: Mathf.Max(LayoutUtility.GetPreferredSize(child, 1), LayoutUtility.GetMinSize(child, 1), child.rect.height);
float offsetY = GetChildVerticalOffset(row.height, childHeight);
SetChildAlongAxis(child, 0, x, childWidth);
SetChildAlongAxis(child, 1, y + offsetY, childHeight);
x += childWidth + spacingX;
}
y += row.height + spacingY;
}
}
private float GetRowStartX(float availableWidth, float rowWidth)
{
TextAnchor anchor = childAlignment;
switch (anchor)
{
case TextAnchor.UpperCenter:
case TextAnchor.MiddleCenter:
case TextAnchor.LowerCenter:
return padding.left + Mathf.Max(0f, (availableWidth - rowWidth) * 0.5f);
case TextAnchor.UpperRight:
case TextAnchor.MiddleRight:
case TextAnchor.LowerRight:
return padding.left + Mathf.Max(0f, availableWidth - rowWidth);
default:
return padding.left;
}
}
private float GetChildVerticalOffset(float rowHeight, float childHeight)
{
TextAnchor anchor = childAlignment;
switch (anchor)
{
case TextAnchor.MiddleLeft:
case TextAnchor.MiddleCenter:
case TextAnchor.MiddleRight:
return Mathf.Max(0f, (rowHeight - childHeight) * 0.5f);
case TextAnchor.LowerLeft:
case TextAnchor.LowerCenter:
case TextAnchor.LowerRight:
return Mathf.Max(0f, rowHeight - childHeight);
default:
return 0f;
}
}
private float GetAvailableWidth()
{
float availableWidth = rectTransform.rect.width - padding.horizontal;
if (availableWidth > 0f)
{
return availableWidth;
}
RectTransform parentRect = rectTransform.parent as RectTransform;
if (parentRect != null)
{
availableWidth = parentRect.rect.width - padding.horizontal;
if (availableWidth > 0f)
{
return availableWidth;
}
}
return float.PositiveInfinity;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 24c5b6ddd04e710409080eb450a8a928
+138 -88
View File
@@ -7,9 +7,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using DG.Tweening;
using JetBrains.Annotations;
using UnityEngine;
using UnityEngine.UI;
using Bansonic;
@@ -17,26 +15,43 @@ using Bansonic;
class UI_Panel_Character : MonoBehaviour
{
[SerializeField] float anim_Time = 0.5f;
[SerializeField, Range(0f, 1f)] float illustrationFadeStartAlpha = 0.25f;
float Anim_Speed => 1f / anim_Time;
[SerializeField] List<Animator> ui_Anim;
[SerializeField] Transform content_Character_Slot;
[SerializeField] Button button_Character_Slot_Prefab;
[SerializeField] GameObject button_Character_Slot_Prefab;
[SerializeField] Text text_Character_Name;
[SerializeField] Text text_char_name;
[SerializeField] Image Image_Character_Illustration;
[SerializeField] Image Image_Character_Illustration_BG;
//[Header("")]
[Header("buttons")]
public Button change_thisHero_skin;
public Button thisHero_detail;
public Button confirm_thisHero;
[Header("quit")]
[SerializeField] Button quitButton;
[Header("Data Paths")]
[Tooltip("Path relative to Resources folder for Editor mode")]
public string editorResourcePath = "so/ally";
[Tooltip("Path relative to Resources folder for Runtime mode")]
public string runtimeResourcePath = "so/ally";
private List<AllyHero_SO> allyHeroList = new List<AllyHero_SO>();
private readonly List<AllyHero_SO> allyHeroList = new List<AllyHero_SO>();
private readonly List<uiui_character_displayPrefab> heroDisplayPrefabs = new List<uiui_character_displayPrefab>();
private Vector2 originalIllustrationPos;
private Vector2 originalIllustrationBGPos;
private bool hasCachedPositions;
private Coroutine c_Character_Illustration_Anim;
private int previewCharacterIndex = -1;
private CanvasGroup illustrationCanvasGroup;
private const string SAVED_HERO_ID_KEY = "SelectedMainHeroID";
private void Update()
{
@@ -45,135 +60,129 @@ class UI_Panel_Character : MonoBehaviour
gameObject.SetActive(false);
}
}
private Vector2 originalIllustrationPos;
private Vector2 originalIllustrationBGPos;
private bool hasCachedPositions = false;
private const string SAVED_HERO_ID_KEY = "SelectedMainHeroID";
void Start()
{
UnityEngine.Debug.Log($"[UI_Panel_Character] Start called. Illustration: {Image_Character_Illustration}");
// 初始化按钮监听
if (change_thisHero_skin != null)
change_thisHero_skin.onClick.AddListener(() => gNotice.warning.display("此版本未开放皮肤切换功能"));
change_thisHero_skin.onClick.AddListener(() => gNotice.warning.display("\u6b64\u7248\u672c\u672a\u5f00\u653e\u76ae\u80a4\u5207\u6362\u529f\u80fd"));
if (thisHero_detail != null)
thisHero_detail.onClick.AddListener(() => gNotice.warning.display("此版本未开放详情查看功能"));
thisHero_detail.onClick.AddListener(() => gNotice.warning.display("\u6b64\u7248\u672c\u672a\u5f00\u653e\u8be6\u60c5\u67e5\u770b\u529f\u80fd"));
if (confirm_thisHero != null)
confirm_thisHero.onClick.AddListener(OnConfirmHeroClicked);
if (quitButton != null)
quitButton.onClick.AddListener(() => gameObject.SetActive(false));
if (Image_Character_Illustration != null)
{
// 设置为图示的数值:Pos X: 376, Pos Y: -306
Image_Character_Illustration.rectTransform.anchoredPosition = new Vector2(376, -306);
originalIllustrationPos = Image_Character_Illustration.rectTransform.anchoredPosition;
if (Image_Character_Illustration.transform.parent.TryGetComponent(out CanvasGroup canvasGroup))
if (Image_Character_Illustration.transform.parent != null &&
Image_Character_Illustration.transform.parent.TryGetComponent(out CanvasGroup canvasGroup))
{
canvasGroup.alpha = 0;
illustrationCanvasGroup = canvasGroup;
illustrationCanvasGroup.alpha = 0;
}
}
if (Image_Character_Illustration_BG != null)
{
// 设置运行时 X 值为 376
Vector2 pos = Image_Character_Illustration_BG.rectTransform.anchoredPosition;
pos.x = 376;
Image_Character_Illustration_BG.rectTransform.anchoredPosition = pos;
originalIllustrationBGPos = Image_Character_Illustration_BG.rectTransform.anchoredPosition;
}
hasCachedPositions = true;
foreach (var item in ui_Anim)
{
if (item != null) item.speed = Anim_Speed;
if (item != null)
item.speed = Anim_Speed;
}
if (content_Character_Slot != null)
content_Character_Slot.Get_Childrens_Component<UI_Button_Character_Head_Slot>(true, true);
// Load AllyHero_SO data
string path = Application.isEditor ? editorResourcePath : runtimeResourcePath;
var loadedData = Resources.LoadAll<AllyHero_SO>(path);
allyHeroList = new List<AllyHero_SO>(loadedData);
UnityEngine.Debug.Log($"[UI_Panel_Character] Loaded {allyHeroList.Count} heroes from {path}");
var loadedData = RuntimeResourcesCache.LoadAllAllyHeroes();
allyHeroList.Clear();
allyHeroList.AddRange(loadedData);
heroDisplayPrefabs.Clear();
UnityEngine.Debug.Log($"[UI_Panel_Character] Loaded {allyHeroList.Count} heroes");
// Ensure consistent order with UI_Panel_Main
allyHeroList.Sort((a, b) => a.ally_heroID.CompareTo(b.ally_heroID));
// 读取保存的角色 ID
int savedHeroID = PlayerPrefs.GetInt(SAVED_HERO_ID_KEY, -1);
int initialIndex = 0;
for (int i = 0; i < allyHeroList.Count; i++)
{
var obj = Instantiate(button_Character_Slot_Prefab, content_Character_Slot);
// Register UI sounds for the newly instantiated button
UISystemBootstrap.RegisterHierarchy(obj.gameObject);
var slotObject = Instantiate(button_Character_Slot_Prefab, content_Character_Slot);
var displayPrefab = slotObject.GetComponent<uiui_character_displayPrefab>();
var slotButton = displayPrefab != null ? displayPrefab.characterProfileButton : slotObject.GetComponent<Button>();
if (slotButton == null && slotObject.transform.parent != null)
{
slotButton = slotObject.transform.parent.GetComponent<Button>();
}
UISystemBootstrap.RegisterHierarchy(slotObject);
var data = allyHeroList[i];
// 如果 ID 匹配,设置初始索引
if (savedHeroID != -1 && data.ally_heroID == savedHeroID)
{
initialIndex = i;
}
if (obj.image != null)
if (displayPrefab != null)
{
obj.image.sprite = data.ally_heroSelectIcon;
obj.image.preserveAspect = true; // 保持比例,防止拉伸变形
obj.image.type = Image.Type.Simple; // 确保是普通显示模式
// 确保 RectTransform 撑满父物体并居中
obj.image.rectTransform.anchorMin = Vector2.zero;
obj.image.rectTransform.anchorMax = Vector2.one;
obj.image.rectTransform.sizeDelta = Vector2.zero;
obj.image.rectTransform.anchoredPosition = Vector2.zero;
displayPrefab.SetDisplay(data.ally_heroSelectIcon, data.ally_heroID);
}
heroDisplayPrefabs.Add(displayPrefab);
if (obj.TryGetComponent(out UI_Button_Character_Head_Slot head_Slot))
if (slotObject.TryGetComponent(out UI_Button_Character_Head_Slot head_Slot))
{
head_Slot.index = i;
int capturedIndex = i;
obj.onClick.AddListener(() => Set_Character_Index(capturedIndex));
if (slotButton != null)
{
slotButton.onClick.AddListener(() => Set_Character_Index(capturedIndex));
}
}
}
// 应用初始索引(来自保存的 ID 或默认 0)
previewCharacterIndex = initialIndex;
UI_Panel_Main.Singleton.Character_Index = initialIndex;
// Initial character display
Set_Character();
RefreshSelectedState(false);
}
public void Set_Character_Index(int index)
{
if (UI_Panel_Main.Singleton.Character_Index == index) return;
UI_Panel_Main.Singleton.Character_Index = index;
if (previewCharacterIndex == index)
return;
previewCharacterIndex = index;
Set_Character();
// 人性化逻辑:点击头像即自动设为看板并保存,并显示提示内容
SaveCurrentCharacter(true);
RefreshSelectedState(true);
}
private void OnConfirmHeroClicked()
{
if (SaveCurrentCharacter(true))
{
// 确认按钮逻辑:保存后禁用自身物体(隐藏面板)
gameObject.SetActive(false);
}
}
private bool SaveCurrentCharacter(bool showNotice)
{
int currentIndex = UI_Panel_Main.Singleton.Character_Index;
int currentIndex = GetCurrentPreviewIndex();
if (allyHeroList != null && currentIndex >= 0 && currentIndex < allyHeroList.Count)
{
int heroID = allyHeroList[currentIndex].ally_heroID;
@@ -182,57 +191,104 @@ class UI_Panel_Character : MonoBehaviour
if (showNotice)
{
gNotice.alarm.display($"已将 {allyHeroList[currentIndex].ally_heroName} 设置为记录对象。");
gNotice.alarm.display($"\u5df2\u5c06 {allyHeroList[currentIndex].ally_heroName} \u8bbe\u7f6e\u4e3a\u770b\u677f\u5bf9\u8c61\u3002");
}
// 同步更新主面板的看板图
previewCharacterIndex = currentIndex;
UI_Panel_Main.Singleton.Character_Index = currentIndex;
return true;
}
return false;
}
private void OnEnable()
{
previewCharacterIndex = UI_Panel_Main.Singleton != null ? UI_Panel_Main.Singleton.Character_Index : previewCharacterIndex;
Set_Character();
RefreshSelectedState(false);
}
Coroutine c_Character_Illustration_Anim;
private void RefreshSelectedState(bool animated)
{
int selectedIndex = GetCurrentPreviewIndex();
for (int i = 0; i < heroDisplayPrefabs.Count; i++)
{
if (heroDisplayPrefabs[i] != null)
{
heroDisplayPrefabs[i].SetSelected(i == selectedIndex, animated);
}
}
}
private int GetCurrentPreviewIndex()
{
if (previewCharacterIndex >= 0 && previewCharacterIndex < allyHeroList.Count)
{
return previewCharacterIndex;
}
if (UI_Panel_Main.Singleton != null)
{
return UI_Panel_Main.Singleton.Character_Index;
}
return -1;
}
public void Set_Character()
{
if (Image_Character_Illustration == null || illustrationCanvasGroup == null)
{
return;
}
if (c_Character_Illustration_Anim != null)
{
StopCoroutine(c_Character_Illustration_Anim);
c_Character_Illustration_Anim = null;
}
if (Image_Character_Illustration.transform.parent.TryGetComponent(out CanvasGroup canvasGroup))
{
c_Character_Illustration_Anim = StartCoroutine(C_Character_Illustration_Anim(canvasGroup, Set_Character_Show));
}
illustrationCanvasGroup.DOKill();
Set_Character_Show();
illustrationCanvasGroup.alpha = illustrationFadeStartAlpha;
illustrationCanvasGroup.DOFade(1f, anim_Time * 0.6f)
.SetEase(Ease.OutCubic)
.SetLink(illustrationCanvasGroup.gameObject);
}
public void Set_Character_Show()
{
int index = UI_Panel_Main.Singleton.Character_Index;
if (allyHeroList == null || index < 0 || index >= allyHeroList.Count) return;
int index = GetCurrentPreviewIndex();
if (allyHeroList.Count == 0 || index < 0 || index >= allyHeroList.Count)
return;
// 停止之前的 Tween 以防冲突
Image_Character_Illustration.rectTransform.DOKill();
Image_Character_Illustration_BG.rectTransform.DOKill();
text_Character_Name.DOKill();
if (text_char_name != null)
{
text_char_name.DOKill();
}
var data = allyHeroList[index];
// 使用初始缓存的坐标,防止频繁点击导致的偏移累积
Vector2 targetPos1 = hasCachedPositions ? originalIllustrationPos : Image_Character_Illustration.rectTransform.anchoredPosition;
Vector2 targetPos1 = hasCachedPositions
? originalIllustrationPos
: Image_Character_Illustration.rectTransform.anchoredPosition;
Image_Character_Illustration.sprite = data.ally_hero_HD_image;
// 动画:从左侧 200 像素滑入到目标位置
float startX1 = targetPos1.x - 200;
Image_Character_Illustration.rectTransform.anchoredPosition = new Vector2(startX1, targetPos1.y);
Image_Character_Illustration.rectTransform.DOAnchorPos(targetPos1, anim_Time)
.SetEase(Ease.OutCubic)
.SetLink(Image_Character_Illustration.gameObject);
Vector2 targetPosBG = hasCachedPositions ? originalIllustrationBGPos : Image_Character_Illustration_BG.rectTransform.anchoredPosition;
Vector2 targetPosBG = hasCachedPositions
? originalIllustrationBGPos
: Image_Character_Illustration_BG.rectTransform.anchoredPosition;
Image_Character_Illustration_BG.sprite = data.ally_hero_HD_image;
float startXBG = targetPosBG.x - 200;
Image_Character_Illustration_BG.rectTransform.anchoredPosition = new Vector2(startXBG, targetPosBG.y);
Image_Character_Illustration_BG.rectTransform.DOAnchorPos(targetPosBG, anim_Time)
@@ -246,19 +302,13 @@ class UI_Panel_Character : MonoBehaviour
text_Character_Name.DOText(text, anim_Time)
.SetEase(Ease.Linear)
.SetLink(text_Character_Name.gameObject);
}
IEnumerator C_Character_Illustration_Anim(CanvasGroup canvasGroup, Action onHide)
{
while (canvasGroup.alpha > 0)
if (text_char_name != null)
{
canvasGroup.alpha -= Anim_Speed * Time.deltaTime;
yield return null;
}
onHide?.Invoke();
while (canvasGroup.alpha < 1)
{
canvasGroup.alpha += Anim_Speed * Time.deltaTime;
yield return null;
text_char_name.text = string.Empty;
text_char_name.DOText(text, anim_Time)
.SetEase(Ease.Linear)
.SetLink(text_char_name.gameObject);
}
}
}
+91 -6
View File
@@ -160,7 +160,12 @@ public class UI_Panel_Mail : MonoBehaviour
if (slot.mailTitle != null) slot.mailTitle.text = mail.mail_title;
if (slot.mailSender != null) slot.mailSender.text = mail.mail_sender;
if (slot.mailTime != null) slot.mailTime.text = mail.mail_date;
if (slot.mailImage != null) slot.mailImage.sprite = mail.mail_image;
if (slot.mailImage != null)
{
slot.mailImage.sprite = mail.mail_image;
}
slot.RefreshRewardsWithMailText(mail);
slot.RefreshRewardPreviews(mail);
TryBindServerMailImage(mail, slot);
UpdateSlotVisuals(slot, mail);
SetSelectedState(slot, false);
@@ -295,7 +300,8 @@ public class UI_Panel_Mail : MonoBehaviour
reward_description = rewardEntry.reward_description ?? string.Empty,
reward_key = rewardEntry.reward_key ?? string.Empty,
reward_store_item_id = rewardEntry.reward_store_item_id,
reward_image = null
reward_image = null,
reward_icon_url = rewardEntry.reward_icon_url ?? string.Empty
};
MailRewardGrantService.PopulateRewardDisplay(reward);
mail.rewardList.Add(reward);
@@ -418,10 +424,7 @@ public class UI_Panel_Mail : MonoBehaviour
var go = Instantiate(rewardSlotPrefab, content_Reward_Slot);
var slot = go.GetComponent<rewardSlotPrefab>();
if (slot == null) continue;
if (slot.rewardName != null) slot.rewardName.text = reward.rewardName;
if (slot.rewardAmount != null) slot.rewardAmount.text = reward.reward_ammount == 1 ? string.Empty : reward.reward_ammount.ToString();
if (slot.rewardIcon != null) slot.rewardIcon.sprite = reward.reward_image;
if (slot.detailText != null) slot.detailText.text = reward.reward_description;
slot.BindReward(reward);
if (slot.detailBtm != null) slot.detailBtm.SetActive(false);
}
@@ -486,17 +489,20 @@ public class UI_Panel_Mail : MonoBehaviour
if (mail.mail_image != null)
{
slot.mailImage.sprite = mail.mail_image;
SetMailImageVisible(slot, true);
return;
}
if (!_serverMailImageUrls.TryGetValue(mail.mail_id, out string url) || string.IsNullOrWhiteSpace(url))
{
SetMailImageVisible(slot, false);
return;
}
string normalizedUrl = NormalizeMailImageUrl(url);
if (string.IsNullOrWhiteSpace(normalizedUrl))
{
SetMailImageVisible(slot, false);
return;
}
@@ -504,6 +510,7 @@ public class UI_Panel_Mail : MonoBehaviour
{
mail.mail_image = cachedSprite;
slot.mailImage.sprite = cachedSprite;
SetMailImageVisible(slot, true);
return;
}
@@ -512,6 +519,7 @@ public class UI_Panel_Mail : MonoBehaviour
return;
}
SetMailImageVisible(slot, false);
StartCoroutine(LoadServerMailImageRoutine(normalizedUrl, mail, slot));
}
@@ -547,6 +555,7 @@ public class UI_Panel_Mail : MonoBehaviour
if (slot != null && slot.mailImage != null)
{
slot.mailImage.sprite = sprite;
SetMailImageVisible(slot, true);
}
}
finally
@@ -556,6 +565,20 @@ public class UI_Panel_Mail : MonoBehaviour
}
}
private static void SetMailImageVisible(mailSlotPrefab slot, bool visible)
{
if (slot == null || slot.mailImage == null)
{
return;
}
GameObject imageObject = slot.mailImage.gameObject;
if (imageObject != null && imageObject.activeSelf != visible)
{
imageObject.SetActive(visible);
}
}
string NormalizeMailImageUrl(string imageUrl)
{
string trimmed = imageUrl?.Trim() ?? string.Empty;
@@ -1287,6 +1310,53 @@ public static class MailRewardGrantService
}
}
public static bool TryGetRewardRarity(mail_so.rewardItem reward, out ItemRarity rarity)
{
rarity = ItemRarity.None;
if (reward == null)
{
return false;
}
EnsureAssetsLoaded();
int amount = Mathf.Max(1, reward.reward_ammount);
string rewardKey = string.IsNullOrWhiteSpace(reward.reward_key) ? string.Empty : reward.reward_key.Trim();
string rewardName = string.IsNullOrWhiteSpace(reward.rewardName) ? string.Empty : reward.rewardName.Trim();
if (TryFindStoreItemReference(reward, rewardKey, rewardName, out storeItemSO storeItem) && storeItem != null)
{
rarity = storeItem.itemRarity;
return true;
}
if (reward.reward_Type == mail_so.reward_type.expBottles_allies)
{
if (TryResolveExpBottle(reward, rewardKey, rewardName, amount, out ResolvedReward resolved) && resolved != null && resolved.ExpBottleAsset != null)
{
rarity = resolved.ExpBottleAsset.itemRarity;
return true;
}
}
else if (reward.reward_Type == mail_so.reward_type.growth_material)
{
if (TryResolveGrowthMaterial(reward, rewardKey, rewardName, amount, out ResolvedReward resolved) && resolved != null && resolved.GrowthMaterialAsset != null)
{
rarity = resolved.GrowthMaterialAsset.itemRarity;
return true;
}
}
else if (reward.reward_Type == mail_so.reward_type.equipment_consumable)
{
if (TryResolveEquipmentConsumable(reward, rewardKey, rewardName, amount, out ResolvedReward resolved) && resolved != null && resolved.EquipmentConsumableAsset != null)
{
rarity = resolved.EquipmentConsumableAsset.itemRarity;
return true;
}
}
return false;
}
public static bool TryGrantAll(mail_so mail, out string failureMessage)
{
failureMessage = string.Empty;
@@ -1482,6 +1552,21 @@ public static class MailRewardGrantService
{
StoreItemsByName[item.name] = item;
}
// expBottlesSO / growthMaterialSO / equipmentConsumableSO live outside Resources,
// so Resources.LoadAll returns nothing. Backfill from storeItemSO direct references instead.
if (item.associatedExpBottle != null && !ExpBottleAssets.ContainsKey(item.associatedExpBottle.bottleKind))
{
ExpBottleAssets[item.associatedExpBottle.bottleKind] = item.associatedExpBottle;
}
if (item.associatedGrowthMaterial != null && !GrowthMaterialAssets.ContainsKey(item.associatedGrowthMaterial.materialKind))
{
GrowthMaterialAssets[item.associatedGrowthMaterial.materialKind] = item.associatedGrowthMaterial;
}
if (item.associatedEquipmentConsumable != null && !EquipmentConsumableAssets.ContainsKey(item.associatedEquipmentConsumable.consumableKind))
{
EquipmentConsumableAssets[item.associatedEquipmentConsumable.consumableKind] = item.associatedEquipmentConsumable;
}
}
}
+12 -3
View File
@@ -272,7 +272,7 @@ public class UI_Panel_Main : Singleton_Mono<UI_Panel_Main>
private IEnumerator InitDataRoutine()
{
var rawData = Resources.LoadAll<AllyHero_SO>("so/ally");
var rawData = RuntimeResourcesCache.LoadAllAllyHeroes();
data_List = new List<AllyHero_SO>(rawData);
data_List.Sort((a, b) => a.ally_heroID.CompareTo(b.ally_heroID));
@@ -318,7 +318,7 @@ public class UI_Panel_Main : Singleton_Mono<UI_Panel_Main>
button_Story.onClick.AddListener(
() =>
{
gNotice.warning.display("此版本未开放该功能");
gNotice.error.display("功能未开放");
//Try_Open_Panel(ui_Panel_Story);
});
if (button_Idol != null)
@@ -486,10 +486,19 @@ public class UI_Panel_Main : Singleton_Mono<UI_Panel_Main>
IEnumerator LoadSelectSceneAsync()
{
if (gTransition.LoadScene(ui_Select_Music_Scene_Name, LoadSceneMode.Single))
{
while (gTransition.IsBusy)
{
yield return null;
}
yield break;
}
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(ui_Select_Music_Scene_Name, LoadSceneMode.Single);
// Documentation text normalized.
while (!asyncLoad.isDone)
while (asyncLoad != null && !asyncLoad.isDone)
{
yield return null;
}
@@ -1538,7 +1538,7 @@ MonoBehaviour:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 8cdc81ed9eb4f4a41bdad444afbea4c2, type: 3}
m_Type: 3
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 1
@@ -5956,7 +5956,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3}
m_Name:
m_EditorClassIdentifier:
m_IgnoreReversedGraphics: 1
m_IgnoreReversedGraphics: 0
m_BlockingObjects: 0
m_BlockingMask:
serializedVersion: 2
@@ -124,12 +124,15 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
private Coroutine musicPicFade;
private bool musicPicVisible = true;
private bool navSceneLoading = false;
private Coroutine deferredUiRefreshRoutine;
private bool lastSettingsVisibilityState;
private bool lastOverlayPanelsVisibilityState;
private readonly Dictionary<string, int> guideIndexByScene = new Dictionary<string, int>();
private string currentGuideScene = string.Empty;
private static btmandtopController activeInstance;
private RectTransform topNavigationRoot;
private bool topNavigationGeometryDirty = true;
private int lastTopNavigationParentChildCount = -1;
private static readonly List<string> sceneHistory = new List<string>();
private static bool sceneHistoryHooked = false;
@@ -203,6 +206,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
BindNewBackButtons();
EnsureTopNavigationFront();
ScheduleDeferredUiRefresh();
EnsureMusicPicRoot();
SetupMusicPicDefault();
@@ -279,14 +283,11 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
if (userInfoInstance == null)
{
userInfoInstance = Instantiate(userInfo_prefab, putPrefabsHere.transform);
TryAssignCanvasCamera(userInfoInstance);
PlacePanelBelowSettings(userInfoInstance);
userInfoInstance.SetActive(false);
}
else
{
userInfoInstance.transform.SetParent(putPrefabsHere.transform, false);
PlacePanelBelowSettings(userInfoInstance);
userInfoInstance.SetActive(false);
}
}
@@ -874,43 +875,16 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
private void ToggleUserInfoPrefab()
{
if (userInfo_prefab == null || putPrefabsHere == null) return;
if (userInfoInstance == null)
if (ToggleIfAlreadyOpen(ref userInfoInstance))
{
userInfoInstance = Instantiate(userInfo_prefab, putPrefabsHere.transform);
TryAssignCanvasCamera(userInfoInstance);
PlacePanelBelowSettings(userInfoInstance);
userInfoInstance.SetActive(true);
BroadcastOverlayPanelsVisibility();
RegisterManagedPanel(userInfoInstance, () =>
{
if (userInfoInstance != null)
{
userInfoInstance.SetActive(false);
BroadcastOverlayPanelsVisibility();
}
});
EnsureTopNavigationFront();
return;
}
userInfoInstance.transform.SetParent(putPrefabsHere.transform, false);
userInfoInstance.SetActive(!userInfoInstance.activeSelf);
if (userInfoInstance.activeSelf)
if (OpenPrefab(userInfo_prefab, ref userInfoInstance))
{
PlacePanelBelowSettings(userInfoInstance);
RegisterManagedPanel(userInfoInstance, () =>
{
if (userInfoInstance != null)
{
userInfoInstance.SetActive(false);
BroadcastOverlayPanelsVisibility();
}
});
CloseInfoPanels(userInfoInstance);
RegisterManagedPanel(userInfoInstance, () => CloseManagedOverlay(userInfoInstance));
}
BroadcastOverlayPanelsVisibility();
EnsureTopNavigationFront();
}
private void BroadcastSettingsVisibility(bool visible)
@@ -951,6 +925,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
lastOverlayPanelsVisibilityState = visible;
CurrentOverlayPanelsVisible = visible;
GlobalOverlayPanelVisibilityChanged?.Invoke(visible);
topNavigationGeometryDirty = true;
EnsureTopNavigationFront();
}
@@ -1079,7 +1054,6 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
if (instance == null)
{
instance = Instantiate(prefab, putPrefabsHere.transform);
TryAssignCanvasCamera(instance);
}
else
{
@@ -1097,7 +1071,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
instance.SetActive(true);
}
PlacePanelBelowSettings(instance);
TryAssignCanvasCamera(instance);
BroadcastOverlayPanelsVisibility();
EnsureTopNavigationFront();
return true;
@@ -1114,30 +1088,9 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
settingsInstance.transform.SetAsLastSibling();
}
private void PlacePanelBelowSettings(GameObject instance)
{
if (instance == null || putPrefabsHere == null)
{
return;
}
instance.transform.SetParent(putPrefabsHere.transform, false);
if (settingsInstance == null || instance == settingsInstance)
{
instance.transform.SetAsLastSibling();
return;
}
EnsureSettingsLastSibling();
int settingsIndex = settingsInstance.transform.GetSiblingIndex();
int targetIndex = Mathf.Clamp(settingsIndex, 0, putPrefabsHere.transform.childCount - 1);
instance.transform.SetSiblingIndex(targetIndex);
EnsureSettingsLastSibling();
}
private void CloseInfoPanels(GameObject keep)
{
CloseInstance(ref userInfoInstance, keep);
CloseInstance(ref storeInstance, keep);
CloseInstance(ref showLevelInstance, keep);
CloseInstance(ref emailInstance, keep);
@@ -1292,9 +1245,27 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
if (topNavigationRoot.GetSiblingIndex() != parent.childCount - 1)
{
topNavigationRoot.SetAsLastSibling();
topNavigationGeometryDirty = true;
}
RefreshTopNavigationGeometry();
// The parent's child count changing means a panel was shown/hidden, which can
// shift the top-bar layout. Use it as a cheap heuristic to re-settle geometry.
if (parent.childCount != lastTopNavigationParentChildCount)
{
lastTopNavigationParentChildCount = parent.childCount;
topNavigationGeometryDirty = true;
}
if (topNavigationGeometryDirty)
{
topNavigationGeometryDirty = false;
RefreshTopNavigationGeometry();
}
}
public void MarkTopNavigationGeometryDirty()
{
topNavigationGeometryDirty = true;
}
private void ResolveTopNavigationRoot()
@@ -1411,6 +1382,40 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
}
}
trackedCurrentScene = incoming;
if (activeInstance != null)
{
activeInstance.topNavigationGeometryDirty = true;
activeInstance.EnsureTopNavigationFront();
activeInstance.ScheduleDeferredUiRefresh();
}
}
private void ScheduleDeferredUiRefresh()
{
if (!isActiveAndEnabled)
{
return;
}
if (deferredUiRefreshRoutine != null)
{
StopCoroutine(deferredUiRefreshRoutine);
}
deferredUiRefreshRoutine = StartCoroutine(DeferredUiRefreshRoutine());
}
private System.Collections.IEnumerator DeferredUiRefreshRoutine()
{
for (int i = 0; i < 3; i++)
{
yield return null;
topNavigationGeometryDirty = true;
EnsureTopNavigationFront();
}
deferredUiRefreshRoutine = null;
}
private static string PopPreviousSceneName(string currentScene)
@@ -1490,11 +1495,23 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
navSceneLoading = true;
Time.timeScale = 1f;
if (gTransition.LoadScene(sceneName, LoadSceneMode.Single))
{
while (gTransition.IsBusy)
{
yield return null;
}
navSceneLoading = false;
yield break;
}
AsyncOperation op = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single);
while (op != null && !op.isDone)
{
yield return null;
}
navSceneLoading = false;
}
@@ -1570,4 +1587,3 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
}
}
}
+9 -1
View File
@@ -4,6 +4,7 @@ using System.Collections;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using GameServer.Client;
using Bansonic;
#if UNITY_EDITOR
using UnityEditor;
#endif
@@ -613,8 +614,15 @@ public class PauseManager : MonoBehaviour
private IEnumerator LoadSceneAsync(string sceneName)
{
if (gTransition.LoadScene(sceneName, LoadSceneMode.Single))
{
while (gTransition.IsBusy)
yield return null;
yield break;
}
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
while (!asyncLoad.isDone)
while (asyncLoad != null && !asyncLoad.isDone)
yield return null;
}
@@ -12,6 +12,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
private readonly Dictionary<int, int> currentExpByHeroId = new Dictionary<int, int>();
private readonly Dictionary<int, int> unlockedTierByHeroId = new Dictionary<int, int>();
private readonly Dictionary<int, bool> levelLockByHeroId = new Dictionary<int, bool>();
private readonly Dictionary<int, bool> autoBreakthroughEnabledByHeroId = new Dictionary<int, bool>();
private readonly Dictionary<int, int> deployCountsByHeroId = new Dictionary<int, int>();
private readonly Dictionary<int, int> finishCountsByHeroId = new Dictionary<int, int>();
@@ -153,6 +154,13 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
return autoBreakthroughEnabledByHeroId.TryGetValue(heroId, out value) && value;
}
public bool IsLevelLockEnabled(int heroId)
{
InitializeIfNeeded();
bool value;
return levelLockByHeroId.TryGetValue(heroId, out value) && value;
}
public void SetCurrentExp(AllyHero_SO hero, int value)
{
if (hero == null || hero.ally_heroID <= 0)
@@ -164,6 +172,11 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
int safeValue = Mathf.Max(0, value);
currentExpByHeroId[hero.ally_heroID] = safeValue;
hero.ally_currentEXP = safeValue;
int unlockedTier = GetUnlockedTierIndex(hero.ally_heroID);
bool levelLock = ResolveLevelLockState(hero, safeValue, unlockedTier);
levelLockByHeroId[hero.ally_heroID] = levelLock;
hero.level_lock = levelLock;
SyncLegacySelectedSlotExpKeys(hero.ally_heroID, safeValue);
MarkDirty(hero);
SaveNow();
if (OnHeroGrowthChanged != null)
@@ -183,7 +196,11 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
int safeValue = Mathf.Clamp(value, 0, 3);
unlockedTierByHeroId[hero.ally_heroID] = safeValue;
hero.ally_growthUnlockedTierIndex = safeValue;
hero.ally_currentEXP = GetCurrentExp(hero.ally_heroID);
int currentExp = GetCurrentExp(hero.ally_heroID);
hero.ally_currentEXP = currentExp;
bool levelLock = ResolveLevelLockState(hero, currentExp, safeValue);
levelLockByHeroId[hero.ally_heroID] = levelLock;
hero.level_lock = levelLock;
MarkDirty(hero);
SaveNow();
if (OnHeroGrowthChanged != null)
@@ -292,6 +309,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
}
SyncAllMirrorFlags();
SyncAllLegacySelectedSlotExpKeys();
AllyHeroDeployLedgerStorage.TrySave(BuildPayload());
}
@@ -300,6 +318,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
InitializeIfNeeded();
currentExpByHeroId.Clear();
unlockedTierByHeroId.Clear();
levelLockByHeroId.Clear();
autoBreakthroughEnabledByHeroId.Clear();
ResetGrowthStateToDefaults();
ClearAllPendingDebtKeys();
@@ -311,6 +330,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
{
currentExpByHeroId.Clear();
unlockedTierByHeroId.Clear();
levelLockByHeroId.Clear();
autoBreakthroughEnabledByHeroId.Clear();
deployCountsByHeroId.Clear();
finishCountsByHeroId.Clear();
@@ -341,6 +361,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
currentExpByHeroId[entry.heroId] = currentExp;
unlockedTierByHeroId[entry.heroId] = unlockedTier;
levelLockByHeroId[entry.heroId] = entry.levelLock;
autoBreakthroughEnabledByHeroId[entry.heroId] = entry.autoBreakthroughEnabled;
deployCountsByHeroId[entry.heroId] = Mathf.Max(0, entry.deployCount);
finishCountsByHeroId[entry.heroId] = Mathf.Max(0, entry.finishCount);
@@ -356,8 +377,9 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
{
currentExpByHeroId.Clear();
unlockedTierByHeroId.Clear();
levelLockByHeroId.Clear();
autoBreakthroughEnabledByHeroId.Clear();
AllyHero_SO[] heroes = Resources.LoadAll<AllyHero_SO>(string.Empty);
AllyHero_SO[] heroes = RuntimeResourcesCache.LoadAllAllyHeroes();
for (int i = 0; i < heroes.Length; i++)
{
AllyHero_SO hero = heroes[i];
@@ -368,6 +390,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
currentExpByHeroId[hero.ally_heroID] = 0;
unlockedTierByHeroId[hero.ally_heroID] = 0;
levelLockByHeroId[hero.ally_heroID] = false;
autoBreakthroughEnabledByHeroId[hero.ally_heroID] = false;
if (hero.ally_currentEXP != 0)
@@ -387,12 +410,18 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
hero.ally_autoBreakthroughEnabled = false;
MarkDirty(hero);
}
if (hero.level_lock)
{
hero.level_lock = false;
MarkDirty(hero);
}
}
}
private void SyncAllMirrorFlags()
{
AllyHero_SO[] heroes = Resources.LoadAll<AllyHero_SO>(string.Empty);
AllyHero_SO[] heroes = RuntimeResourcesCache.LoadAllAllyHeroes();
for (int i = 0; i < heroes.Length; i++)
{
AllyHero_SO hero = heroes[i];
@@ -421,6 +450,12 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
unlockedTierByHeroId[hero.ally_heroID] = unlockedTier;
}
bool levelLock;
if (!levelLockByHeroId.TryGetValue(hero.ally_heroID, out levelLock))
{
levelLock = false;
}
bool autoBreakthroughEnabled;
if (!autoBreakthroughEnabledByHeroId.TryGetValue(hero.ally_heroID, out autoBreakthroughEnabled))
{
@@ -431,6 +466,8 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
NormalizeGrowthState(hero, ref currentExp, ref unlockedTier);
currentExpByHeroId[hero.ally_heroID] = currentExp;
unlockedTierByHeroId[hero.ally_heroID] = unlockedTier;
levelLock = ResolveLevelLockState(hero, currentExp, unlockedTier);
levelLockByHeroId[hero.ally_heroID] = levelLock;
int finishCount;
if (!finishCountsByHeroId.TryGetValue(hero.ally_heroID, out finishCount))
@@ -469,6 +506,12 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
changed = true;
}
if (hero.level_lock != levelLock)
{
hero.level_lock = levelLock;
changed = true;
}
if (hero.ally_battleDeployCount != count)
{
hero.ally_battleDeployCount = count;
@@ -503,7 +546,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
private static Dictionary<int, AllyHero_SO> BuildHeroLookup()
{
Dictionary<int, AllyHero_SO> heroById = new Dictionary<int, AllyHero_SO>();
AllyHero_SO[] heroes = Resources.LoadAll<AllyHero_SO>(string.Empty);
AllyHero_SO[] heroes = RuntimeResourcesCache.LoadAllAllyHeroes();
for (int i = 0; i < heroes.Length; i++)
{
AllyHero_SO hero = heroes[i];
@@ -564,9 +607,41 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
unlockedTier = Mathf.Min(unlockedTier, reachableTier);
}
private static bool ResolveLevelLockState(AllyHero_SO hero, int currentExp, int unlockedTier)
{
if (hero == null || hero.levelStats == null || hero.levelStats.Count == 0)
{
return false;
}
List<AllyHero_SO.AllyLevelInfo> levels = new List<AllyHero_SO.AllyLevelInfo>();
for (int i = 0; i < hero.levelStats.Count; i++)
{
if (hero.levelStats[i] != null)
{
levels.Add(hero.levelStats[i]);
}
}
if (levels.Count < 2)
{
return false;
}
levels.Sort((left, right) => left.requiredEXP.CompareTo(right.requiredEXP));
int clampedTier = Mathf.Clamp(unlockedTier, 0, levels.Count - 1);
if (clampedTier >= levels.Count - 1)
{
return false;
}
int currentCap = Mathf.Max(levels[clampedTier].requiredEXP, levels[clampedTier + 1].requiredEXP);
return Mathf.Max(0, currentExp) >= currentCap;
}
private static void ClearAllPendingDebtKeys()
{
AllyHero_SO[] heroes = Resources.LoadAll<AllyHero_SO>(string.Empty);
AllyHero_SO[] heroes = RuntimeResourcesCache.LoadAllAllyHeroes();
for (int i = 0; i < heroes.Length; i++)
{
AllyHero_SO hero = heroes[i];
@@ -581,6 +656,63 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
PlayerPrefs.Save();
}
private static void SyncLegacySelectedSlotExpKeys(int heroId, int expValue)
{
if (heroId <= 0)
{
return;
}
int safeExp = Mathf.Max(0, expValue);
bool changed = false;
for (int slot = 1; slot <= 5; slot++)
{
string heroKey = $"selected_heroSlot0{slot}_heroID";
if (PlayerPrefs.GetInt(heroKey, 0) != heroId)
{
continue;
}
string expKey = $"selected_heroSlot0{slot}_exp";
if (PlayerPrefs.GetInt(expKey, int.MinValue) == safeExp)
{
continue;
}
PlayerPrefs.SetInt(expKey, safeExp);
changed = true;
}
if (changed)
{
PlayerPrefs.Save();
}
}
private void SyncAllLegacySelectedSlotExpKeys()
{
bool changed = false;
for (int slot = 1; slot <= 5; slot++)
{
string heroKey = $"selected_heroSlot0{slot}_heroID";
int heroId = PlayerPrefs.GetInt(heroKey, 0);
string expKey = $"selected_heroSlot0{slot}_exp";
int expValue = heroId > 0 ? GetCurrentExp(heroId) : 0;
if (PlayerPrefs.GetInt(expKey, int.MinValue) == expValue)
{
continue;
}
PlayerPrefs.SetInt(expKey, expValue);
changed = true;
}
if (changed)
{
PlayerPrefs.Save();
}
}
private AllyHeroDeployLedgerPayload BuildPayload()
{
AllyHeroDeployLedgerPayload payload = AllyHeroDeployLedgerStorage.CreateDefaultPayload();
@@ -595,6 +727,11 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
allHeroIds.Add(pair.Key);
}
foreach (KeyValuePair<int, bool> pair in levelLockByHeroId)
{
allHeroIds.Add(pair.Key);
}
foreach (KeyValuePair<int, bool> pair in autoBreakthroughEnabledByHeroId)
{
allHeroIds.Add(pair.Key);
@@ -627,6 +764,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
heroId = heroId,
currentExp = Mathf.Max(0, GetCurrentExp(heroId)),
unlockedTierIndex = Mathf.Clamp(GetUnlockedTierIndex(heroId), 0, 3),
levelLock = IsLevelLockEnabled(heroId),
autoBreakthroughEnabled = IsAutoBreakthroughEnabled(heroId),
deployCount = Mathf.Max(0, GetDeployCount(heroId)),
finishCount = Mathf.Max(0, GetFinishCount(heroId)),
@@ -7,6 +7,7 @@ public class AllyHeroDeployEntry
public int heroId;
public int currentExp;
public int unlockedTierIndex;
public bool levelLock;
public bool autoBreakthroughEnabled;
public int deployCount;
public int finishCount;
@@ -14,6 +14,21 @@ public static class DlcContentAccess
return owningDlc == null || DlcOwnershipService.IsDlcOwned(owningDlc);
}
public static bool IsHeroAccessible(AllyHero_SO hero)
{
if (hero == null)
{
return false;
}
if (string.IsNullOrWhiteSpace(hero.sourceDlcId))
{
return true;
}
return DlcOwnershipService.IsDlcOwned(hero.sourceDlcId);
}
public static bool IsSkinAccessible(HeroSkinResolvedData skin)
{
if (skin == null)
@@ -24,6 +24,7 @@ public class DlcManifestEntry
public string[] songLabels;
public string[] songContentLabels;
public string[] heroSkinLabels;
public string[] heroLabels;
public string GetSafeDlcKey()
{
@@ -50,6 +51,11 @@ public class DlcManifestEntry
return GetResolvedLabels(heroSkinLabels, "dlc:" + GetSafeDlcKey() + ":heroskins");
}
public string[] GetHeroLabels()
{
return GetResolvedLabels(heroLabels, "dlc:" + GetSafeDlcKey() + ":heroes");
}
public string[] GetDependencyKeys()
{
List<string> result = new List<string>();
@@ -58,6 +64,7 @@ public class DlcManifestEntry
AppendUnique(result, GetSongLabels());
AppendUnique(result, GetSongContentLabels());
AppendUnique(result, GetHeroSkinLabels());
AppendUnique(result, GetHeroLabels());
return result.ToArray();
}
@@ -15,6 +15,8 @@ public static class DlcManifestService
public static List<DlcManifestRuntimeEntry> LoadInstalledEntries()
{
DlcPackageArchiveService.PrepareInstalledPackages();
List<DlcManifestRuntimeEntry> result = new List<DlcManifestRuntimeEntry>();
HashSet<string> visitedFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
string[] roots = GetManifestSearchRoots();
@@ -0,0 +1,310 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
public static class DlcPackageArchiveService
{
[Serializable]
private sealed class InstallState
{
public string sourcePath;
public long sourceFileLength;
public long sourceWriteTicksUtc;
}
private const string RuntimeFolderName = "DLC";
private const string PackageFolderName = "packages";
private const string InstalledFolderName = "installed_packages";
private const string PackageExtension = ".bsnkdlc";
private const string StateFileName = ".bsnkdlc.installstate.json";
private static readonly HashSet<string> PreparedPackagesThisSession =
new HashSet<string>(StringComparer.OrdinalIgnoreCase);
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void ResetRuntimeState()
{
PreparedPackagesThisSession.Clear();
}
public static void PrepareInstalledPackages()
{
string[] searchRoots = GetPackageSearchRoots();
HashSet<string> visited = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < searchRoots.Length; i++)
{
string root = searchRoots[i];
if (string.IsNullOrWhiteSpace(root) || !Directory.Exists(root))
{
continue;
}
string[] packageFiles;
try
{
packageFiles = Directory.GetFiles(root, "*" + PackageExtension, SearchOption.AllDirectories);
}
catch (Exception ex)
{
Debug.LogWarning("[DLC] Failed to enumerate package files in '" + root + "': " + ex.Message);
continue;
}
for (int packageIndex = 0; packageIndex < packageFiles.Length; packageIndex++)
{
string packagePath = packageFiles[packageIndex];
if (string.IsNullOrWhiteSpace(packagePath))
{
continue;
}
string fullPackagePath;
try
{
fullPackagePath = Path.GetFullPath(packagePath);
}
catch
{
continue;
}
if (!visited.Add(fullPackagePath))
{
continue;
}
TryInstallPackage(fullPackagePath);
}
}
}
public static string[] GetPackageSearchRoots()
{
List<string> result = new List<string>();
AppendIfValid(result, Path.Combine(Application.streamingAssetsPath, RuntimeFolderName, PackageFolderName));
AppendIfValid(result, Path.Combine(Application.streamingAssetsPath, RuntimeFolderName));
AppendIfValid(result, Path.Combine(Application.persistentDataPath, RuntimeFolderName, PackageFolderName));
AppendIfValid(result, Path.Combine(Application.persistentDataPath, RuntimeFolderName));
string playerRoot = GetPlayerRootDirectory();
AppendIfValid(result, Path.Combine(playerRoot, RuntimeFolderName, PackageFolderName));
AppendIfValid(result, Path.Combine(playerRoot, RuntimeFolderName));
return result.ToArray();
}
private static void TryInstallPackage(string packagePath)
{
if (string.IsNullOrWhiteSpace(packagePath) || !File.Exists(packagePath))
{
return;
}
string installDirectory = GetInstallDirectory(packagePath);
if (string.IsNullOrWhiteSpace(installDirectory))
{
return;
}
bool needsInstall = NeedsInstall(packagePath, installDirectory);
if (!needsInstall && PreparedPackagesThisSession.Contains(packagePath))
{
return;
}
try
{
if (!needsInstall)
{
PreparedPackagesThisSession.Add(packagePath);
return;
}
string installRoot = Path.GetDirectoryName(installDirectory) ?? string.Empty;
string stagingDirectory = installDirectory + ".staging";
if (!string.IsNullOrWhiteSpace(installRoot))
{
Directory.CreateDirectory(installRoot);
}
if (Directory.Exists(stagingDirectory))
{
Directory.Delete(stagingDirectory, true);
}
Directory.CreateDirectory(stagingDirectory);
ZipFile.ExtractToDirectory(packagePath, stagingDirectory);
WriteInstallState(packagePath, stagingDirectory);
if (Directory.Exists(installDirectory))
{
Directory.Delete(installDirectory, true);
}
Directory.Move(stagingDirectory, installDirectory);
PreparedPackagesThisSession.Add(packagePath);
}
catch (Exception ex)
{
Debug.LogWarning("[DLC] Failed to install package '" + packagePath + "': " + ex.Message);
}
}
private static bool NeedsInstall(string packagePath, string installDirectory)
{
if (!Directory.Exists(installDirectory))
{
return true;
}
InstallState state = ReadInstallState(installDirectory);
if (state == null)
{
return true;
}
FileInfo info;
try
{
info = new FileInfo(packagePath);
}
catch
{
return true;
}
return !string.Equals(state.sourcePath ?? string.Empty, packagePath, StringComparison.OrdinalIgnoreCase)
|| state.sourceFileLength != info.Length
|| state.sourceWriteTicksUtc != info.LastWriteTimeUtc.Ticks;
}
private static string GetInstallDirectory(string packagePath)
{
if (string.IsNullOrWhiteSpace(packagePath))
{
return string.Empty;
}
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(packagePath) ?? "package";
string safeName = SanitizeFileName(fileNameWithoutExtension);
string hash = ComputeShortHash(packagePath);
string installRoot = Path.Combine(Application.persistentDataPath, RuntimeFolderName, InstalledFolderName);
return Path.Combine(installRoot, safeName + "_" + hash);
}
private static void WriteInstallState(string packagePath, string directory)
{
FileInfo info = new FileInfo(packagePath);
InstallState state = new InstallState
{
sourcePath = packagePath,
sourceFileLength = info.Exists ? info.Length : 0L,
sourceWriteTicksUtc = info.Exists ? info.LastWriteTimeUtc.Ticks : 0L
};
string json = JsonUtility.ToJson(state, true);
File.WriteAllText(Path.Combine(directory, StateFileName), json, Encoding.UTF8);
}
private static InstallState ReadInstallState(string directory)
{
if (string.IsNullOrWhiteSpace(directory))
{
return null;
}
string statePath = Path.Combine(directory, StateFileName);
if (!File.Exists(statePath))
{
return null;
}
try
{
string json = File.ReadAllText(statePath, Encoding.UTF8);
return JsonUtility.FromJson<InstallState>(json);
}
catch
{
return null;
}
}
private static string GetPlayerRootDirectory()
{
try
{
string dataPath = Application.dataPath;
if (string.IsNullOrWhiteSpace(dataPath))
{
return string.Empty;
}
DirectoryInfo parent = Directory.GetParent(dataPath);
return parent != null ? parent.FullName : string.Empty;
}
catch
{
return string.Empty;
}
}
private static void AppendIfValid(List<string> result, string path)
{
if (result == null || string.IsNullOrWhiteSpace(path))
{
return;
}
try
{
string fullPath = Path.GetFullPath(path);
if (!result.Contains(fullPath))
{
result.Add(fullPath);
}
}
catch
{
}
}
private static string ComputeShortHash(string value)
{
byte[] bytes = Encoding.UTF8.GetBytes(value ?? string.Empty);
using (SHA1 sha1 = SHA1.Create())
{
byte[] hash = sha1.ComputeHash(bytes);
StringBuilder builder = new StringBuilder(8);
for (int i = 0; i < 4 && i < hash.Length; i++)
{
builder.Append(hash[i].ToString("x2"));
}
return builder.ToString();
}
}
private static string SanitizeFileName(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return "package";
}
char[] invalidChars = Path.GetInvalidFileNameChars();
StringBuilder builder = new StringBuilder(value.Length);
for (int i = 0; i < value.Length; i++)
{
char current = value[i];
builder.Append(Array.IndexOf(invalidChars, current) >= 0 ? '_' : current);
}
return builder.ToString().Trim();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0bf72431ab28c4640b1b960e8ca98168
@@ -19,6 +19,11 @@ public static class DlcRemoteContentService
{
private void Start()
{
if (DlcRemoteManifestSyncService.ShouldDeferInitialRefresh())
{
return;
}
if (DlcRemoteContentService.autoRefreshOnStart)
{
DlcRemoteContentService.RefreshInstalledDlc();
@@ -144,42 +149,56 @@ public static class DlcRemoteContentService
state.retainedCatalogHandles.Add(catalogHandle);
if (manifest.entry.autoDownloadDependencies)
{
string[] dependencyKeys = manifest.entry.GetDependencyKeys();
for (int i = 0; i < dependencyKeys.Length; i++)
{
string dependencyKey = dependencyKeys[i];
if (string.IsNullOrWhiteSpace(dependencyKey))
{
continue;
}
AsyncOperationHandle downloadHandle = Addressables.DownloadDependenciesAsync(dependencyKey, false);
yield return downloadHandle;
if (downloadHandle.Status == AsyncOperationStatus.Succeeded)
{
state.retainedAssetHandles.Add(downloadHandle);
}
else
{
localSuccess = false;
Debug.LogWarning("[DLC] Failed to download dependencies for key '" + dependencyKey + "'.");
Addressables.Release(downloadHandle);
}
}
}
List<dlcData> loadedDlcs = new List<dlcData>();
List<SongData> loadedSongs = new List<SongData>();
List<SongDlcContentSO> loadedSongContents = new List<SongDlcContentSO>();
List<HeroSkinSO> loadedHeroSkins = new List<HeroSkinSO>();
List<AllyHero_SO> loadedHeroes = new List<AllyHero_SO>();
// Load the lightweight DLC metadata first so we can decide ownership
// before pulling (or even downloading) any heavy content.
yield return LoadAssetsByLabels(manifest.entry.GetDlcDataLabels(), loadedDlcs, state.retainedAssetHandles, result => localSuccess &= result);
yield return LoadAssetsByLabels(manifest.entry.GetSongLabels(), loadedSongs, state.retainedAssetHandles, result => localSuccess &= result);
yield return LoadAssetsByLabels(manifest.entry.GetSongContentLabels(), loadedSongContents, state.retainedAssetHandles, result => localSuccess &= result);
yield return LoadAssetsByLabels(manifest.entry.GetHeroSkinLabels(), loadedHeroSkins, state.retainedAssetHandles, result => localSuccess &= result);
bool owned = IsManifestEntryOwned(manifest, loadedDlcs);
if (owned)
{
if (manifest.entry.autoDownloadDependencies)
{
string[] dependencyKeys = manifest.entry.GetDependencyKeys();
for (int i = 0; i < dependencyKeys.Length; i++)
{
string dependencyKey = dependencyKeys[i];
if (string.IsNullOrWhiteSpace(dependencyKey))
{
continue;
}
AsyncOperationHandle downloadHandle = Addressables.DownloadDependenciesAsync(dependencyKey, false);
yield return downloadHandle;
if (downloadHandle.Status == AsyncOperationStatus.Succeeded)
{
state.retainedAssetHandles.Add(downloadHandle);
}
else
{
localSuccess = false;
Debug.LogWarning("[DLC] Failed to download dependencies for key '" + dependencyKey + "'.");
Addressables.Release(downloadHandle);
}
}
}
yield return LoadAssetsByLabels(manifest.entry.GetSongLabels(), loadedSongs, state.retainedAssetHandles, result => localSuccess &= result);
yield return LoadAssetsByLabels(manifest.entry.GetSongContentLabels(), loadedSongContents, state.retainedAssetHandles, result => localSuccess &= result);
yield return LoadAssetsByLabels(manifest.entry.GetHeroSkinLabels(), loadedHeroSkins, state.retainedAssetHandles, result => localSuccess &= result);
yield return LoadAssetsByLabels(manifest.entry.GetHeroLabels(), loadedHeroes, state.retainedAssetHandles, result => localSuccess &= result);
}
else
{
Debug.Log("[DLC] Skipping content load for unowned DLC '" + manifest.entry.GetSafeDlcKey() + "'. Only metadata is registered.");
}
DlcRuntimePackage package = new DlcRuntimePackage
{
@@ -190,13 +209,54 @@ public static class DlcRemoteContentService
dlcs = loadedDlcs.ToArray(),
songs = loadedSongs.ToArray(),
songContents = loadedSongContents.ToArray(),
heroSkins = loadedHeroSkins.ToArray()
heroSkins = loadedHeroSkins.ToArray(),
heroes = loadedHeroes.ToArray()
};
state.packages.Add(package);
reportResult?.Invoke(localSuccess);
}
private static bool IsManifestEntryOwned(DlcManifestRuntimeEntry manifest, List<dlcData> loadedDlcs)
{
// If any loaded dlcData asset reports owned, the package is owned. A DLC that
// does not enforce entitlement (builtInContent / requiresOwnership == false)
// is always owned via DlcOwnershipService.
if (loadedDlcs != null)
{
bool anyEnforced = false;
for (int i = 0; i < loadedDlcs.Count; i++)
{
dlcData dlc = loadedDlcs[i];
if (dlc == null)
{
continue;
}
anyEnforced = true;
if (DlcOwnershipService.IsDlcOwned(dlc))
{
return true;
}
}
if (anyEnforced)
{
return false;
}
}
// No dlcData asset was found for this manifest entry. Fall back to the manifest
// key so a remote/local ownership flag can still gate the content.
string key = manifest != null && manifest.entry != null ? manifest.entry.GetSafeDlcKey() : null;
if (string.IsNullOrWhiteSpace(key))
{
return true;
}
return DlcOwnershipService.IsDlcOwned(key);
}
private static IEnumerator LoadAssetsByLabels<T>(
string[] labels,
List<T> output,
@@ -0,0 +1,270 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using GameServer.Client;
using UnityEngine;
using UnityEngine.Networking;
public static class DlcRemoteManifestSyncService
{
[Serializable]
private sealed class RemoteManifestEnvelope
{
public bool success;
public string message;
public RemoteManifestItem[] dlcs;
}
[Serializable]
private sealed class RemoteManifestItem
{
public string dlc_key;
public string display_name;
public string version;
public string manifest_file_name;
public string manifest_json;
public string uploaded_at;
public string updated_at;
}
private sealed class DlcRemoteManifestSyncRunner : MonoBehaviour
{
private void Start()
{
if (autoSyncOnStart)
{
SyncPublishedDlcs();
}
}
}
private const string DefaultServerUrl = "http://47.112.187.172:8080";
private const string RemoteManifestFilePrefix = "remote_";
private const string ManifestApiPath = "/api/dlcs/manifests";
private static DlcRemoteManifestSyncRunner runner;
private static bool autoSyncOnStart = true;
private static bool syncRequestedWhileBusy;
private static bool initialSyncPending = true;
public static bool IsSyncing { get; private set; }
public static event Action<bool> SyncCompleted;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void EnsureRunner()
{
if (runner != null)
{
return;
}
GameObject go = new GameObject(nameof(DlcRemoteManifestSyncService));
UnityEngine.Object.DontDestroyOnLoad(go);
runner = go.AddComponent<DlcRemoteManifestSyncRunner>();
}
public static void SetAutoSyncOnStart(bool enabled)
{
autoSyncOnStart = enabled;
if (!enabled)
{
initialSyncPending = false;
}
}
public static bool ShouldDeferInitialRefresh()
{
return autoSyncOnStart && initialSyncPending && !OnlineModeSettings.IsLocalOnlyMode;
}
public static void SyncPublishedDlcs()
{
EnsureRunner();
if (runner == null)
{
return;
}
if (IsSyncing)
{
syncRequestedWhileBusy = true;
return;
}
runner.StartCoroutine(SyncPublishedDlcsRoutine());
}
private static IEnumerator SyncPublishedDlcsRoutine()
{
if (IsSyncing)
{
yield break;
}
if (OnlineModeSettings.IsLocalOnlyMode)
{
initialSyncPending = false;
DlcRemoteContentService.RefreshInstalledDlc();
SyncCompleted?.Invoke(false);
yield break;
}
string url = BuildManifestApiUrl();
if (string.IsNullOrWhiteSpace(url))
{
initialSyncPending = false;
DlcRemoteContentService.RefreshInstalledDlc();
SyncCompleted?.Invoke(false);
yield break;
}
IsSyncing = true;
syncRequestedWhileBusy = false;
bool success = false;
using (UnityWebRequest request = UnityWebRequest.Get(url))
{
request.timeout = 10;
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
string responseText = request.downloadHandler != null ? request.downloadHandler.text : string.Empty;
success = TryApplyRemoteManifestPayload(responseText);
}
else
{
Debug.LogWarning("[DLC] Remote manifest sync failed: " + request.error);
}
}
initialSyncPending = false;
if (success)
{
DlcRemoteContentService.RefreshInstalledDlc();
}
else
{
DlcRemoteContentService.RefreshInstalledDlc();
}
IsSyncing = false;
SyncCompleted?.Invoke(success);
if (syncRequestedWhileBusy)
{
SyncPublishedDlcs();
}
}
private static bool TryApplyRemoteManifestPayload(string json)
{
if (string.IsNullOrWhiteSpace(json))
{
return false;
}
RemoteManifestEnvelope envelope;
try
{
envelope = JsonUtility.FromJson<RemoteManifestEnvelope>(json);
}
catch (Exception ex)
{
Debug.LogWarning("[DLC] Failed to parse remote manifest payload: " + ex.Message);
return false;
}
if (envelope == null || !envelope.success)
{
Debug.LogWarning("[DLC] Remote manifest payload reported failure.");
return false;
}
string manifestRoot = Path.Combine(Application.persistentDataPath, "DLC", "manifests");
try
{
Directory.CreateDirectory(manifestRoot);
DeleteExistingRemoteManifestFiles(manifestRoot);
RemoteManifestItem[] items = envelope.dlcs ?? Array.Empty<RemoteManifestItem>();
for (int i = 0; i < items.Length; i++)
{
RemoteManifestItem item = items[i];
if (item == null || string.IsNullOrWhiteSpace(item.manifest_json))
{
continue;
}
string safeKey = SanitizeFileName(item.dlc_key);
if (string.IsNullOrWhiteSpace(safeKey))
{
safeKey = "package_" + i;
}
string fileName = RemoteManifestFilePrefix + safeKey + ".json";
string path = Path.Combine(manifestRoot, fileName);
File.WriteAllText(path, item.manifest_json);
}
return true;
}
catch (Exception ex)
{
Debug.LogWarning("[DLC] Failed to write remote manifests: " + ex.Message);
return false;
}
}
private static void DeleteExistingRemoteManifestFiles(string manifestRoot)
{
string[] files = Directory.GetFiles(manifestRoot, RemoteManifestFilePrefix + "*.json", SearchOption.TopDirectoryOnly);
for (int i = 0; i < files.Length; i++)
{
try
{
File.Delete(files[i]);
}
catch (Exception ex)
{
Debug.LogWarning("[DLC] Failed to delete old remote manifest '" + files[i] + "': " + ex.Message);
}
}
}
private static string BuildManifestApiUrl()
{
string baseUrl = DefaultServerUrl;
NetworkManager manager = NetworkManager.Instance;
if (manager != null && !string.IsNullOrWhiteSpace(manager.ServerUrl))
{
baseUrl = manager.ServerUrl.Trim();
}
if (string.IsNullOrWhiteSpace(baseUrl))
{
return string.Empty;
}
return baseUrl.TrimEnd('/') + ManifestApiPath;
}
private static string SanitizeFileName(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return string.Empty;
}
char[] invalidChars = Path.GetInvalidFileNameChars();
string result = value.Trim();
for (int i = 0; i < invalidChars.Length; i++)
{
result = result.Replace(invalidChars[i], '_');
}
return result.Replace(' ', '_');
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 08474a42ea631b04b982f1e7e67fd96a
@@ -12,6 +12,7 @@ public sealed class DlcRuntimePackage
public SongData[] songs = Array.Empty<SongData>();
public SongDlcContentSO[] songContents = Array.Empty<SongDlcContentSO>();
public HeroSkinSO[] heroSkins = Array.Empty<HeroSkinSO>();
public AllyHero_SO[] heroes = Array.Empty<AllyHero_SO>();
}
public static class DlcRuntimeRegistry
@@ -25,11 +26,14 @@ public static class DlcRuntimeRegistry
new Dictionary<string, SongDlcContentSO>(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary<string, HeroSkinSO> HeroSkinsById =
new Dictionary<string, HeroSkinSO>(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary<int, AllyHero_SO> HeroesById =
new Dictionary<int, AllyHero_SO>();
private static dlcData[] cachedDlcs = Array.Empty<dlcData>();
private static SongData[] cachedSongs = Array.Empty<SongData>();
private static SongDlcContentSO[] cachedSongContents = Array.Empty<SongDlcContentSO>();
private static HeroSkinSO[] cachedHeroSkins = Array.Empty<HeroSkinSO>();
private static AllyHero_SO[] cachedHeroes = Array.Empty<AllyHero_SO>();
public static event Action RuntimeContentChanged;
@@ -69,6 +73,11 @@ public static class DlcRuntimeRegistry
return cachedHeroSkins;
}
public static AllyHero_SO[] GetAllHeroes()
{
return cachedHeroes;
}
public static void ReplaceAll(IList<DlcRuntimePackage> packages)
{
ClearSilently();
@@ -97,10 +106,12 @@ public static class DlcRuntimeRegistry
SongsById.Clear();
SongContentsById.Clear();
HeroSkinsById.Clear();
HeroesById.Clear();
cachedDlcs = Array.Empty<dlcData>();
cachedSongs = Array.Empty<SongData>();
cachedSongContents = Array.Empty<SongDlcContentSO>();
cachedHeroSkins = Array.Empty<HeroSkinSO>();
cachedHeroes = Array.Empty<AllyHero_SO>();
}
private static void RegisterPackageInternal(DlcRuntimePackage package)
@@ -185,6 +196,20 @@ public static class DlcRuntimeRegistry
HeroSkinsById[skinId] = skin;
}
}
if (package.heroes != null)
{
for (int i = 0; i < package.heroes.Length; i++)
{
AllyHero_SO hero = package.heroes[i];
if (hero == null || hero.ally_heroID <= 0)
{
continue;
}
HeroesById[hero.ally_heroID] = hero;
}
}
}
private static void RebuildSnapshots()
@@ -200,5 +225,8 @@ public static class DlcRuntimeRegistry
cachedHeroSkins = new HeroSkinSO[HeroSkinsById.Count];
HeroSkinsById.Values.CopyTo(cachedHeroSkins, 0);
cachedHeroes = new AllyHero_SO[HeroesById.Count];
HeroesById.Values.CopyTo(cachedHeroes, 0);
}
}
@@ -74,7 +74,7 @@ public static class RuntimeResourcesCache
public static AllyHero_SO[] LoadAllAllyHeroes()
{
return LoadAll<AllyHero_SO>(string.Empty);
return MergeArrays(LoadAll<AllyHero_SO>(string.Empty), DlcRuntimeRegistry.GetAllHeroes());
}
public static storeItemSO[] LoadAllStoreItems()
@@ -16,6 +16,7 @@ public class DailyTaskSaveData
public string dateKey;
public int dateStamp;
public int refreshUsedCount;
public bool loginReportedToday;
public int trustedDateStamp;
public int lastRefreshDateStamp;
public long lastRefreshLocalTicks;
+71 -2
View File
@@ -17,6 +17,8 @@ public sealed class DailyTaskService : MonoBehaviour
}
private const float OnlineDurationFlushStepSeconds = 5f;
private const string GameplaySceneName = "gameplay_gameplay";
private const string MainUiSceneName = "UI_UI";
private readonly Queue<DailyTaskEventData> pendingEvents = new Queue<DailyTaskEventData>();
private readonly Dictionary<string, userTasksPool.TaskDefinition> definitionById = new Dictionary<string, userTasksPool.TaskDefinition>(StringComparer.Ordinal);
@@ -28,6 +30,8 @@ public sealed class DailyTaskService : MonoBehaviour
private bool initialized;
private bool appFocused = true;
private float pendingOnlineDurationSeconds;
private bool isGameplayDurationTracking;
private float gameplayDurationRealtimeStart;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Bootstrap()
@@ -90,6 +94,7 @@ public sealed class DailyTaskService : MonoBehaviour
if (!hasFocus)
{
FlushPendingOnlineDuration();
FlushGameplayDuration();
}
}
@@ -98,12 +103,14 @@ public sealed class DailyTaskService : MonoBehaviour
if (pauseStatus)
{
FlushPendingOnlineDuration();
FlushGameplayDuration();
}
}
private void OnApplicationQuit()
{
FlushPendingOnlineDuration();
FlushGameplayDuration();
}
private void OnDestroy()
@@ -111,6 +118,7 @@ public sealed class DailyTaskService : MonoBehaviour
if (Instance == this)
{
FlushPendingOnlineDuration();
FlushGameplayDuration();
SceneManager.sceneLoaded -= OnSceneLoaded;
}
}
@@ -327,9 +335,18 @@ public sealed class DailyTaskService : MonoBehaviour
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if (string.Equals(scene.name, "UI_UI", StringComparison.OrdinalIgnoreCase))
if (string.Equals(scene.name, MainUiSceneName, StringComparison.OrdinalIgnoreCase))
{
DailyTaskEventHub.ReportLogin();
TryReportDailyLogin();
}
if (string.Equals(scene.name, GameplaySceneName, StringComparison.OrdinalIgnoreCase))
{
BeginGameplayDurationTracking();
}
else
{
FlushGameplayDuration();
}
}
@@ -382,6 +399,7 @@ public sealed class DailyTaskService : MonoBehaviour
saveData.dateKey = todayKey;
saveData.dateStamp = effectiveTodayStamp;
saveData.refreshUsedCount = 0;
saveData.loginReportedToday = false;
saveData.accumulatedProgress = new List<DailyTaskAccumulatedProgress>();
saveData.uniqueIntProgress = new List<DailyTaskUniqueIntProgress>();
GenerateDailyTasks();
@@ -524,6 +542,57 @@ public sealed class DailyTaskService : MonoBehaviour
}
}
private void TryReportDailyLogin()
{
EnsureTodayTasks();
if (saveData == null || saveData.loginReportedToday)
{
return;
}
saveData.loginReportedToday = true;
ReportEvent(new DailyTaskEventData
{
taskType = userTasksPool.TaskType.Login,
amount = 1f
});
}
private void BeginGameplayDurationTracking()
{
if (isGameplayDurationTracking)
{
return;
}
isGameplayDurationTracking = true;
gameplayDurationRealtimeStart = Time.realtimeSinceStartup;
}
private void FlushGameplayDuration()
{
if (!isGameplayDurationTracking)
{
return;
}
float now = Time.realtimeSinceStartup;
float elapsed = Mathf.Max(0f, now - gameplayDurationRealtimeStart);
isGameplayDurationTracking = false;
gameplayDurationRealtimeStart = 0f;
if (elapsed <= 0f)
{
return;
}
ReportEvent(new DailyTaskEventData
{
taskType = userTasksPool.TaskType.GameDuration,
amount = elapsed
});
}
private bool ApplyEventToTrackedProgress(DailyTaskEventData eventData)
{
switch (eventData.taskType)
+2 -1
View File
@@ -284,7 +284,7 @@ public class dailyTaskManager : MonoBehaviour
{
if (taskService.GetRemainingRefreshCount() <= 0)
{
gNotice.warning.display(LocalizationService.Get("daily.refresh_limit_reached", "已达到今日刷新上限"));
gNotice.warning.display("今日刷新次数已用尽");
}
else
{
@@ -295,6 +295,7 @@ public class dailyTaskManager : MonoBehaviour
return;
}
gNotice.message.display($"刷新成功,剩余刷新次数:{taskService.GetRemainingRefreshCount()}");
RefreshTaskUi();
}
+13 -4
View File
@@ -262,19 +262,28 @@ public class eula_and_warnings : MonoBehaviour
{
if (_mainScenePreloadReady)
{
StartCoroutine(ActivatePreloadedMainSceneRoutine());
if (!gTransition.Run(ActivatePreloadedMainSceneRoutineWithoutFade()))
{
StartCoroutine(ActivatePreloadedMainSceneRoutineWithoutFade());
}
return;
}
SceneManager.LoadScene(MainSceneName, LoadSceneMode.Single);
if (!gTransition.LoadScene(MainSceneName, LoadSceneMode.Single))
{
SceneManager.LoadScene(MainSceneName, LoadSceneMode.Single);
}
}
private System.Collections.IEnumerator ActivatePreloadedMainSceneRoutine()
private System.Collections.IEnumerator ActivatePreloadedMainSceneRoutineWithoutFade()
{
Scene sourceScene = gameObject.scene;
if (_mainScenePreloadOperation == null)
{
SceneManager.LoadScene(MainSceneName, LoadSceneMode.Single);
if (!gTransition.LoadScene(MainSceneName, LoadSceneMode.Single))
{
SceneManager.LoadScene(MainSceneName, LoadSceneMode.Single);
}
yield break;
}
+114
View File
@@ -0,0 +1,114 @@
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class gTransBlack : MonoBehaviour
{
[SerializeField] private Canvas b_Canvas;
[SerializeField] private CanvasGroup b_CG;
public Canvas Canvas => b_Canvas;
public CanvasGroup CanvasGroup => b_CG;
private void Awake()
{
EnsureReferences();
ApplySceneCamera();
}
private void OnEnable()
{
SceneManager.sceneLoaded -= HandleSceneLoaded;
SceneManager.sceneLoaded += HandleSceneLoaded;
ApplySceneCamera();
}
private void OnDisable()
{
SceneManager.sceneLoaded -= HandleSceneLoaded;
}
private void HandleSceneLoaded(Scene scene, LoadSceneMode mode)
{
ApplySceneCamera();
}
public void RefreshCanvasCamera()
{
ApplySceneCamera();
}
private void EnsureReferences()
{
if (b_Canvas == null)
{
b_Canvas = GetComponentInChildren<Canvas>(true);
}
if (b_CG == null)
{
b_CG = GetComponent<CanvasGroup>();
if (b_CG == null)
{
b_CG = GetComponentInChildren<CanvasGroup>(true);
}
}
}
private void ApplySceneCamera()
{
EnsureReferences();
if (b_Canvas == null)
{
return;
}
if (b_Canvas.renderMode == RenderMode.ScreenSpaceOverlay)
{
b_Canvas.worldCamera = null;
return;
}
if (b_Canvas.transform.localScale == Vector3.zero)
{
b_Canvas.transform.localScale = Vector3.one;
}
Camera targetCamera = ResolveFirstSceneCamera();
if (targetCamera == null)
{
return;
}
b_Canvas.worldCamera = targetCamera;
}
private static Camera ResolveFirstSceneCamera()
{
Camera mainCam = Camera.main;
if (mainCam != null && mainCam.gameObject.scene.IsValid() && mainCam.gameObject.scene.isLoaded)
{
return mainCam;
}
Camera[] cameras = Object.FindObjectsByType<Camera>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
for (int i = 0; i < cameras.Length; i++)
{
Camera camera = cameras[i];
if (camera == null || !camera.isActiveAndEnabled)
{
continue;
}
if (!camera.gameObject.scene.IsValid() || !camera.gameObject.scene.isLoaded)
{
continue;
}
return camera;
}
return null;
}
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6e8dcec2fcbc0a24e966c83902aad73a
@@ -1578,8 +1578,17 @@ public class GameManager : MonoBehaviour
private IEnumerator LoadSceneAsync(string sceneName)
{
if (gTransition.LoadScene(sceneName, LoadSceneMode.Single))
{
while (gTransition.IsBusy)
{
yield return null;
}
yield break;
}
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
while (!asyncLoad.isDone)
while (asyncLoad != null && !asyncLoad.isDone)
{
yield return null;
}
@@ -1588,10 +1597,19 @@ public class GameManager : MonoBehaviour
private IEnumerator FadeToBlackAndLoad(string sceneName, float duration)
{
RecordTotalPlayTime();
if (gTransition.LoadScene(sceneName, LoadSceneMode.Single, duration, duration))
{
while (gTransition.IsBusy)
{
yield return null;
}
yield break;
}
if (blackMaskImage == null)
{
AsyncOperation asyncOp = SceneManager.LoadSceneAsync(sceneName);
while (!asyncOp.isDone) yield return null;
while (asyncOp != null && !asyncOp.isDone) yield return null;
yield break;
}
@@ -1620,7 +1638,7 @@ public class GameManager : MonoBehaviour
// load scene
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
while (!asyncLoad.isDone)
while (asyncLoad != null && !asyncLoad.isDone)
{
yield return null;
}
@@ -3787,7 +3787,10 @@ namespace GameServer.Client
}
Time.timeScale = 1f;
SceneManager.LoadScene(GameplaySceneName, LoadSceneMode.Single);
if (!gTransition.LoadScene(GameplaySceneName, LoadSceneMode.Single))
{
SceneManager.LoadScene(GameplaySceneName, LoadSceneMode.Single);
}
}
private void EnsureRoomAutoPlayDisabled()
@@ -363,6 +363,7 @@ namespace GameServer.Client
[JsonProperty("reward_description")] public string reward_description;
[JsonProperty("reward_key")] public string reward_key;
[JsonProperty("reward_store_item_id")] public int reward_store_item_id;
[JsonProperty("reward_icon_url")] public string reward_icon_url;
}
[Serializable]
@@ -456,4 +457,46 @@ namespace GameServer.Client
[JsonProperty("requests")] public List<SocialFriendRequestEntry> requests;
}
/// <summary>
/// 邮件附件发放参考常量。
/// 服务器配置 rewards 时按此填写 reward_type + reward_key(或 reward_store_item_id)。
/// </summary>
public static class MailRewardKeys
{
// ── reward_type 字符串 ──────────────────────────────────────────────────
public const string TypeCoins = "money";
public const string TypePlayerExp = "exp_user";
public const string TypeMaterial = "metarial";
public const string TypeExpBottle = "exp_bottle";
public const string TypeGrowthMaterial = "growth_material";
public const string TypeEquipConsumable = "equipment_consumable";
public const string TypeStoreItem = "store_item";
// ── 经验瓶 reward_key ─────────────────────────────────────────────────
public const string ExpBottleCommon = "exp_common"; // 凡品经验瓶 (itemID 78001)
public const string ExpBottleMedium = "exp_medium"; // 中品经验瓶 (78002)
public const string ExpBottleSuperior = "exp_superior"; // 上品经验瓶 (78003)
public const string ExpBottleSupreme = "exp_supreme"; // 极品经验瓶 (78004)
public const string ExpBottleExtraordinary = "exp_extraordinary"; // 绝品经验瓶 (78005)
public const string ExpBottleCelestial = "exp_celestial"; // 仙品经验瓶 (78006)
public const string ExpBottleRainAll = "exp_rain_all"; // 雨露均沾 (78011)
public const string ExpBottleRainAllAdv = "exp_rain_all_advanced"; // 高级雨露均沾 (78012)
public const string ExpBottleRainAllSuper = "exp_rain_all_super"; // 超级雨露均沾 (78013)
// ── 突破材料 reward_key ────────────────────────────────────────────────
public const string GrowthMaterial78021 = "dush_78021";
public const string GrowthMaterial78022 = "dush_78022";
public const string GrowthMaterial78023 = "dush_78023";
public const string GrowthMaterial78024 = "dush_78024";
// ── 装备消耗品 reward_key ─────────────────────────────────────────────
public const string EquipUpgrade = "eqc_78101"; // 装备升级材料
public const string EquipBreakthrough = "eqc_78111"; // 装备突破材料
public const string EquipTransfer = "eqc_78121"; // 装备洗炼材料
public const string EquipFinal = "eqc_78131"; // 装备登顶材料
// ── 商店物品:直接用 reward_store_item_id 填 itemID,或 reward_key 填 itemID 字符串 ──
// itemID 77001 = 角色 3020677003 = 歌曲177004 = 歌曲2(见 Resources/so/storeSO/
}
}
@@ -8,6 +8,7 @@ using System.Collections;
using UnityEngine.Audio;
using DG.Tweening;
using GameServer.Client;
using Bansonic;
public class settlementController : MonoBehaviour
{
@@ -1912,8 +1913,17 @@ public class settlementController : MonoBehaviour
private IEnumerator LoadSceneAsync(string sceneName)
{
if (gTransition.LoadScene(sceneName, LoadSceneMode.Single))
{
while (gTransition.IsBusy)
{
yield return null;
}
yield break;
}
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
while (!asyncLoad.isDone)
while (asyncLoad != null && !asyncLoad.isDone)
{
yield return null;
}
@@ -2006,10 +2016,19 @@ public class settlementController : MonoBehaviour
// Coroutine that will be started on the GameManager instance so that the gm's MonoBehaviour runs it
private IEnumerator FadeToBlackAndLoadOnGM(string sceneName, float duration)
{
if (gTransition.LoadScene(sceneName, LoadSceneMode.Single, duration, duration))
{
while (gTransition.IsBusy)
{
yield return null;
}
yield break;
}
if (gm == null || gm.blackMaskImage == null)
{
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
while (!asyncLoad.isDone) yield return null;
while (asyncLoad != null && !asyncLoad.isDone) yield return null;
yield break;
}
@@ -2040,7 +2059,7 @@ public class settlementController : MonoBehaviour
// load target scene
AsyncOperation asyncOp = SceneManager.LoadSceneAsync(sceneName);
while (!asyncOp.isDone)
while (asyncOp != null && !asyncOp.isDone)
{
yield return null;
}
@@ -0,0 +1,176 @@
#if UNITY_EDITOR
using System.IO;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
[InitializeOnLoad]
public static class UIBlurBehindInstaller
{
private const string RendererDataPath = "Assets/Settings/Renderer2D.asset";
private const string MaterialFolderPath = "Assets/Materials";
private const string UIMaterialFolderPath = "Assets/Materials/UI";
private const string MaterialPath = "Assets/Materials/UI/Bansonic_UIBlurBehind.mat";
private const string ShaderName = "UI/Bansonic/Blur Behind";
private static bool installScheduled;
static UIBlurBehindInstaller()
{
ScheduleInstall();
}
[MenuItem("Bansonic/Rendering/Install UI Blur Behind")]
public static void EnsureInstalledMenu()
{
EnsureInstalled();
}
private static void ScheduleInstall()
{
if (installScheduled)
{
return;
}
installScheduled = true;
EditorApplication.delayCall += RunScheduledInstall;
}
private static void RunScheduledInstall()
{
installScheduled = false;
EnsureInstalled();
}
private static void EnsureInstalled()
{
if (EditorApplication.isCompiling || BuildPipeline.isBuildingPlayer)
{
ScheduleInstall();
return;
}
if (!EnsureRendererFeatureInstalled())
{
ScheduleInstall();
return;
}
if (!EnsureMaterialAsset())
{
ScheduleInstall();
}
}
private static bool EnsureRendererFeatureInstalled()
{
ScriptableRendererData rendererData = AssetDatabase.LoadAssetAtPath<ScriptableRendererData>(RendererDataPath);
if (rendererData == null)
{
return false;
}
if (!rendererData.TryGetRendererFeature<UIBlurBehindRendererFeature>(out UIBlurBehindRendererFeature feature))
{
feature = ScriptableObject.CreateInstance<UIBlurBehindRendererFeature>();
feature.name = "UI Blur Behind Renderer Feature";
feature.renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
feature.captureDownsample = 2;
AssetDatabase.AddObjectToAsset(feature, rendererData);
rendererData.rendererFeatures.Add(feature);
rendererData.SetDirty();
EditorUtility.SetDirty(feature);
EditorUtility.SetDirty(rendererData);
TryValidateRendererFeatures(rendererData);
AssetDatabase.SaveAssets();
AssetDatabase.ImportAsset(RendererDataPath);
return true;
}
bool changed = false;
if (feature.renderPassEvent != RenderPassEvent.AfterRenderingPostProcessing)
{
feature.renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
changed = true;
}
if (feature.captureDownsample != 2)
{
feature.captureDownsample = 2;
changed = true;
}
if (changed)
{
rendererData.SetDirty();
EditorUtility.SetDirty(feature);
EditorUtility.SetDirty(rendererData);
AssetDatabase.SaveAssets();
}
return true;
}
private static bool EnsureMaterialAsset()
{
Shader shader = Shader.Find(ShaderName);
if (shader == null)
{
return false;
}
EnsureFolder("Assets", "Materials");
EnsureFolder(MaterialFolderPath, "UI");
Material material = AssetDatabase.LoadAssetAtPath<Material>(MaterialPath);
if (material == null)
{
material = new Material(shader);
material.name = "Bansonic_UIBlurBehind";
material.SetFloat("_BlurRadius", 0.95f);
material.SetFloat("_BlurSpread", 0.28f);
material.SetFloat("_BackgroundOpacity", 1.0f);
material.SetFloat("_TintStrength", 0.0f);
AssetDatabase.CreateAsset(material, MaterialPath);
AssetDatabase.SaveAssets();
return true;
}
bool changed = false;
if (material.shader != shader)
{
material.shader = shader;
changed = true;
}
if (changed)
{
EditorUtility.SetDirty(material);
AssetDatabase.SaveAssets();
}
return true;
}
private static void EnsureFolder(string parentPath, string folderName)
{
string combined = Path.Combine(parentPath, folderName).Replace("\\", "/");
if (AssetDatabase.IsValidFolder(combined))
{
return;
}
AssetDatabase.CreateFolder(parentPath, folderName);
}
private static void TryValidateRendererFeatures(ScriptableRendererData rendererData)
{
MethodInfo validateMethod = typeof(ScriptableRendererData).GetMethod("ValidateRendererFeatures", BindingFlags.Instance | BindingFlags.NonPublic);
validateMethod?.Invoke(rendererData, null);
}
}
#endif
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6e36a19a876cc674aa18f8f1f76d423d
@@ -0,0 +1,162 @@
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.RenderGraphModule.Util;
using UnityEngine.Rendering.Universal;
public class UIBlurBehindRendererFeature : ScriptableRendererFeature
{
public const string GlobalTextureName = "_BansonicUIBlurSourceTex";
private static readonly int GlobalTextureId = Shader.PropertyToID(GlobalTextureName);
[Tooltip("When the UI blur background texture should be captured.")]
public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
[Tooltip("Downsample factor for the captured screen texture. Higher values are cheaper and blurrier.")]
[Range(1, 4)]
public int captureDownsample = 2;
private UIBlurBehindPass pass;
public override void Create()
{
if (pass == null)
{
pass = new UIBlurBehindPass();
}
pass.renderPassEvent = renderPassEvent;
pass.SetDownsample(captureDownsample);
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (pass == null)
{
return;
}
Camera camera = renderingData.cameraData.camera;
if (camera == null)
{
return;
}
CameraType cameraType = renderingData.cameraData.cameraType;
if (cameraType == CameraType.Preview || cameraType == CameraType.Reflection)
{
return;
}
pass.SetDownsample(captureDownsample);
renderer.EnqueuePass(pass);
}
protected override void Dispose(bool disposing)
{
if (pass != null)
{
pass.Dispose();
pass = null;
}
}
private sealed class UIBlurBehindPass : ScriptableRenderPass
{
private int downsample = 2;
private RTHandle blurSourceTexture;
public void SetDownsample(int value)
{
downsample = Mathf.Clamp(value, 1, 4);
requiresIntermediateTexture = true;
}
public void Dispose()
{
blurSourceTexture?.Release();
blurSourceTexture = null;
}
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();
UniversalCameraData cameraData = frameData.Get<UniversalCameraData>();
if (resourceData.isActiveTargetBackBuffer)
{
return;
}
RenderTextureDescriptor descriptor = cameraData.cameraTargetDescriptor;
descriptor.msaaSamples = 1;
descriptor.depthBufferBits = 0;
descriptor.width = Mathf.Max(1, descriptor.width / downsample);
descriptor.height = Mathf.Max(1, descriptor.height / downsample);
descriptor.useMipMap = false;
descriptor.autoGenerateMips = false;
RenderingUtils.ReAllocateHandleIfNeeded(
ref blurSourceTexture,
descriptor,
FilterMode.Bilinear,
TextureWrapMode.Clamp,
name: GlobalTextureName);
if (blurSourceTexture == null)
{
return;
}
Shader.SetGlobalTexture(GlobalTextureId, blurSourceTexture);
TextureHandle source = resourceData.activeColorTexture;
TextureHandle destination = renderGraph.ImportTexture(blurSourceTexture);
if (!source.IsValid() || !destination.IsValid())
{
return;
}
RenderGraphUtils.BlitMaterialParameters parameters = new(
source,
destination,
Blitter.GetBlitMaterial(TextureDimension.Tex2D),
0);
renderGraph.AddBlitPass(parameters, "UI Blur Behind Capture Copy");
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
RenderTextureDescriptor descriptor = renderingData.cameraData.cameraTargetDescriptor;
descriptor.msaaSamples = 1;
descriptor.depthBufferBits = 0;
descriptor.width = Mathf.Max(1, descriptor.width / downsample);
descriptor.height = Mathf.Max(1, descriptor.height / downsample);
descriptor.useMipMap = false;
descriptor.autoGenerateMips = false;
RenderingUtils.ReAllocateHandleIfNeeded(
ref blurSourceTexture,
descriptor,
FilterMode.Bilinear,
TextureWrapMode.Clamp,
name: GlobalTextureName);
CommandBuffer cmd = CommandBufferPool.Get("UI Blur Behind Capture");
try
{
if (blurSourceTexture == null)
{
return;
}
Blitter.BlitCameraTexture(cmd, renderingData.cameraData.renderer.cameraColorTargetHandle, blurSourceTexture);
cmd.SetGlobalTexture(GlobalTextureId, blurSourceTexture.nameID);
context.ExecuteCommandBuffer(cmd);
}
finally
{
cmd.Clear();
CommandBufferPool.Release(cmd);
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f9ac8248c066e1440a110a8b1cf5b019
+83 -1
View File
@@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using DG.Tweening;
using GameServer.Client;
using UnityEngine;
using UnityEngine.EventSystems;
@@ -62,6 +63,7 @@ public class globalChatSystem : MonoBehaviour
[SerializeField] Button back_to_worldwideChatButton;
[SerializeField] Toggle globalChatToggle;
[SerializeField] Toggle friendChatToggle;
[SerializeField] private CanvasGroup chatCanvasGroup;
private ArenaRoomService _service;
private ScrollRect _scrollRect;
@@ -94,6 +96,7 @@ public class globalChatSystem : MonoBehaviour
private bool _suppressModeToggleEvents;
private bool _modeTogglesAutoCreated;
private ToggleGroup _chatModeToggleGroup;
private Tween _visibilityTween;
public static globalChatSystem Instance { get; private set; }
public static event Action<string> ActivePrivateConversationChanged;
@@ -148,6 +151,7 @@ public class globalChatSystem : MonoBehaviour
{
Instance = this;
_lastPrivatePartnerId = PlayerPrefs.GetString(LastPrivatePartnerPrefKey, string.Empty);
EnsureCanvasGroupReference();
}
private void Start()
@@ -212,6 +216,9 @@ public class globalChatSystem : MonoBehaviour
private void OnEnable()
{
EnsureCanvasGroupReference();
PlayOpenFadeIfNeeded();
if (_service == null)
{
_service = ArenaRoomService.Instance;
@@ -283,6 +290,8 @@ public class globalChatSystem : MonoBehaviour
private void OnDestroy()
{
KillVisibilityTween();
if (closeButton != null)
{
closeButton.onClick.RemoveListener(CloseSelf);
@@ -1200,7 +1209,80 @@ public class globalChatSystem : MonoBehaviour
private void CloseSelf()
{
gameObject.SetActive(false);
HideWithFade();
}
private void EnsureCanvasGroupReference()
{
if (chatCanvasGroup == null)
{
chatCanvasGroup = GetComponent<CanvasGroup>();
}
}
private void KillVisibilityTween()
{
if (_visibilityTween != null && _visibilityTween.IsActive())
{
_visibilityTween.Kill(false);
}
_visibilityTween = null;
}
private void PlayOpenFadeIfNeeded()
{
if (chatCanvasGroup == null)
{
return;
}
KillVisibilityTween();
chatCanvasGroup.gameObject.SetActive(true);
chatCanvasGroup.alpha = 0f;
chatCanvasGroup.interactable = false;
chatCanvasGroup.blocksRaycasts = false;
_visibilityTween = chatCanvasGroup.DOFade(1f, 0.25f)
.SetEase(Ease.Linear)
.SetUpdate(true)
.OnComplete(() =>
{
if (chatCanvasGroup == null)
{
return;
}
chatCanvasGroup.alpha = 1f;
chatCanvasGroup.interactable = true;
chatCanvasGroup.blocksRaycasts = true;
_visibilityTween = null;
});
}
private void HideWithFade()
{
if (chatCanvasGroup == null)
{
gameObject.SetActive(false);
return;
}
KillVisibilityTween();
chatCanvasGroup.interactable = false;
chatCanvasGroup.blocksRaycasts = false;
_visibilityTween = chatCanvasGroup.DOFade(0f, 0.25f)
.SetEase(Ease.Linear)
.SetUpdate(true)
.OnComplete(() =>
{
if (chatCanvasGroup != null)
{
chatCanvasGroup.alpha = 0f;
}
gameObject.SetActive(false);
_visibilityTween = null;
});
}
public static bool IsViewingPrivateConversation(string steamId)
@@ -13,102 +13,30 @@ public class UI_SelectSong_AutoPlayToggle : MonoBehaviour
[Header("Tween")]
[SerializeField] private float tweenDuration = 0.25f;
[SerializeField] private Ease tweenEase = Ease.OutCubic;
private static readonly Color32 OnBg = new Color32(0x3A, 0x3A, 0x3A, 0xFF);
private static readonly Color32 OffBg = new Color32(0xFF, 0xFF, 0xFF, 0xFF);
private static readonly Color32 OnText = new Color32(0xFF, 0xFF, 0xFF, 0xFF);
private static readonly Color32 OffText = new Color32(0x3A, 0x3A, 0x3A, 0xFF);
private Color initialButtonColor = Color.white;
private Color initialTextColor = Color.white;
private bool hasInitialButtonColor;
private bool hasInitialTextColor;
private void Awake()
{
TryAutoWire();
}
private void OnEnable()
{
TryAutoWire();
if (button != null)
{
button.onClick.RemoveListener(OnClick);
button.onClick.AddListener(OnClick);
}
if (toggle != null)
{
toggle.onValueChanged.RemoveListener(OnToggleChanged);
toggle.onValueChanged.AddListener(OnToggleChanged);
// Keep UI state consistent with global setting.
toggle.SetIsOnWithoutNotify(GameConfig.autoPlayEnabled);
}
ApplyVisual(GameConfig.autoPlayEnabled, instant: true);
}
private void OnDisable()
{
if (button != null)
{
button.onClick.RemoveListener(OnClick);
}
if (toggle != null)
{
toggle.onValueChanged.RemoveListener(OnToggleChanged);
}
KillTweens();
}
public void TryAutoWire()
{
button = button != null ? button : GetComponent<Button>();
toggle = toggle != null ? toggle : GetComponent<Toggle>();
buttonImage = buttonImage != null ? buttonImage : GetComponent<Image>();
if (buttonImage == null && toggle != null && toggle.targetGraphic is Image tgImage)
{
buttonImage = tgImage;
}
labelText = labelText != null ? labelText : GetComponentInChildren<Text>(true);
}
private void OnClick()
{
bool next = !GameConfig.autoPlayEnabled;
GameConfig.SetAutoPlayEnabled(next);
if (toggle != null)
{
toggle.SetIsOnWithoutNotify(next);
}
ApplyVisual(next, instant: false);
}
private void OnToggleChanged(bool isOn)
{
GameConfig.SetAutoPlayEnabled(isOn);
ApplyVisual(isOn, instant: false);
}
private void ApplyVisual(bool enabled, bool instant)
{
Color targetBg = enabled ? OnBg : OffBg;
Color targetText = enabled ? OnText : OffText;
if (instant || !Application.isPlaying || tweenDuration <= 0f)
{
if (buttonImage != null) buttonImage.color = targetBg;
if (labelText != null) labelText.color = targetText;
return;
}
KillTweens();
if (buttonImage != null)
buttonImage.DOColor(targetBg, tweenDuration).SetEase(tweenEase);
if (labelText != null)
labelText.DOColor(targetText, tweenDuration).SetEase(tweenEase);
// Legacy component retained only for scene compatibility.
// Autoplay UI is now driven exclusively by selected_songInfo.autoplayButton.
}
private void KillTweens()
{
if (buttonImage != null) buttonImage.DOKill();
if (labelText != null) labelText.DOKill();
}
}
@@ -3,59 +3,10 @@ using UnityEngine.SceneManagement;
public static class UI_SelectSong_AutoPlayToggleBinder
{
private const string TargetSceneName = "selectYourSongFirst";
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Init()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
SceneManager.sceneLoaded += OnSceneLoaded;
}
private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if (scene.name != TargetSceneName)
return;
TryBind(scene);
}
private static void TryBind(Scene scene)
{
// Ensure we have the latest persisted value (important when entering from editor play).
GameConfig.LoadPrefs();
// Prefer the user's specified hierarchy if present (active only).
GameObject go =
SceneObjectLookupCache.Find("rightInfos/difficultyBtnInfos/difficultyBtnInfos/select_Auto") ??
SceneObjectLookupCache.Find("rightInfos/difficultyBtnInfos/select_Auto");
if (go == null)
{
// Fallback: search by name in this scene (includes inactive objects).
var transforms = Object.FindObjectsByType<Transform>(FindObjectsInactive.Include, FindObjectsSortMode.None);
for (int i = 0; i < transforms.Length; i++)
{
var t = transforms[i];
if (t == null) continue;
if (t.gameObject.scene != scene) continue;
if (t.name != "select_Auto") continue;
go = t.gameObject;
break;
}
}
if (go == null)
{
if (GameConfig.verboseLogs)
Debug.LogWarning("[AutoPlay] select_Auto button not found in selectYourSongFirst scene. Autoplay toggle UI will be unavailable.");
return;
}
var toggle = go.GetComponent<UI_SelectSong_AutoPlayToggle>();
if (toggle == null)
toggle = go.AddComponent<UI_SelectSong_AutoPlayToggle>();
toggle.TryAutoWire();
// Legacy autoplay binding has been retired.
// selectYourSongFirst now uses selected_songInfo.autoplayButton as the single source of truth.
}
}
@@ -102,7 +102,7 @@ public class UI_SelectSong_EnterAnim : MonoBehaviour
for (int i = 0; i < difficultyGroup.childCount; i++)
{
RectTransform child = difficultyGroup.GetChild(i) as RectTransform;
if (child != null)
if (child != null && child.gameObject.activeSelf)
{
difficultyItems.Add(child);
}
@@ -317,7 +317,7 @@ public class UI_SelectSong_EnterAnim : MonoBehaviour
void SnapVisible(RectTransform rect, bool applyScale)
{
if (rect == null) return;
if (!rect.gameObject.activeSelf) rect.gameObject.SetActive(true);
if (!rect.gameObject.activeSelf) return;
CacheBase(rect);
if (basePos.TryGetValue(rect, out Vector2 pos))
@@ -1,5 +1,6 @@
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;
using System.Collections.Generic;
@@ -23,11 +24,20 @@ public class load_teammatesProfile : MonoBehaviour
public Image teammate_profile_boarder_04;
public Image teammate_profile_boarder_05;
[Header("Level Colors")]
public Color levelColor_C;
public Color levelColor_B;
public Color levelColor_A;
public Color levelColor_S;
[Header("Level Border Sprites")]
public Sprite[] levelBorderSprites;
[Header("Hover Details")]
public loadDetailsPrefab hoverDetailsLoader;
public GameObject hoverDetailsPrefab;
public GameObject hoverDetailsParent;
private bool hoverTargetsBound;
private void Awake()
{
EnsureHoverTargetsBound();
}
// Start is called once before the first execution of Update after the MonoBehaviour is created
IEnumerator Start()
@@ -36,6 +46,38 @@ public class load_teammatesProfile : MonoBehaviour
update_teammates_profile();
}
private void EnsureHoverTargetsBound()
{
if (hoverTargetsBound)
{
return;
}
BindHoverTarget(teammate_profile_01, 1);
BindHoverTarget(teammate_profile_02, 2);
BindHoverTarget(teammate_profile_03, 3);
BindHoverTarget(teammate_profile_04, 4);
BindHoverTarget(teammate_profile_05, 5);
hoverTargetsBound = true;
}
private void BindHoverTarget(Image targetImage, int slotIndex)
{
if (targetImage == null)
{
return;
}
TeammateProfileHoverTarget hoverTarget = targetImage.GetComponent<TeammateProfileHoverTarget>();
if (hoverTarget == null)
{
hoverTarget = targetImage.gameObject.AddComponent<TeammateProfileHoverTarget>();
}
hoverTarget.Bind(this, slotIndex);
}
private IEnumerator EnsureHeroCache()
{
if (heroCacheReady) yield break;
@@ -49,7 +91,7 @@ public class load_teammatesProfile : MonoBehaviour
}
heroCacheBuilding = true;
var allHeroes = Resources.LoadAll<AllyHero_SO>("");
var allHeroes = RuntimeResourcesCache.LoadAllAllyHeroes();
heroById = new Dictionary<int, AllyHero_SO>();
if (allHeroes != null)
{
@@ -79,6 +121,8 @@ public class load_teammatesProfile : MonoBehaviour
public void update_teammates_profile()
{
EnsureHoverTargetsBound();
if (!heroCacheReady)
{
StartCoroutine(EnsureHeroCacheThenUpdate());
@@ -98,12 +142,12 @@ public class load_teammatesProfile : MonoBehaviour
teammate_profile_04.sprite = GetHeroSquareProfile(heroId4);
teammate_profile_05.sprite = GetHeroSquareProfile(heroId5);
// Set border colors based on hero level
teammate_profile_boarder_01.color = GetBorderColor(GetHeroLevel(heroId1, 1));
teammate_profile_boarder_02.color = GetBorderColor(GetHeroLevel(heroId2, 2));
teammate_profile_boarder_03.color = GetBorderColor(GetHeroLevel(heroId3, 3));
teammate_profile_boarder_04.color = GetBorderColor(GetHeroLevel(heroId4, 4));
teammate_profile_boarder_05.color = GetBorderColor(GetHeroLevel(heroId5, 5));
// Set border sprites based on hero level
ApplyBorderSprite(teammate_profile_boarder_01, GetHeroLevelKey(heroId1, 1));
ApplyBorderSprite(teammate_profile_boarder_02, GetHeroLevelKey(heroId2, 2));
ApplyBorderSprite(teammate_profile_boarder_03, GetHeroLevelKey(heroId3, 3));
ApplyBorderSprite(teammate_profile_boarder_04, GetHeroLevelKey(heroId4, 4));
ApplyBorderSprite(teammate_profile_boarder_05, GetHeroLevelKey(heroId5, 5));
}
private Sprite GetHeroSquareProfile(int heroId)
@@ -117,58 +161,153 @@ public class load_teammatesProfile : MonoBehaviour
return null;
}
private string GetHeroLevel(int heroId, int slotIndex)
private string GetHeroLevelKey(int heroId, int slotIndex)
{
if (heroId == 0) return "C";
if (heroById == null) return "C";
if (heroId == 0) return "Fallback";
if (heroById == null) return "Fallback";
AllyHero_SO hero;
if (!heroById.TryGetValue(heroId, out hero) || hero == null) return "C";
int exp = hero.ally_currentEXP;
int keySlot = Mathf.Clamp(slotIndex, 1, 5);
string expKey = $"selected_heroSlot0{keySlot}_exp";
if (PlayerPrefs.HasKey(expKey))
exp = PlayerPrefs.GetInt(expKey, exp);
return GetRatingFromSO(hero, exp);
if (!heroById.TryGetValue(heroId, out hero) || hero == null) return "Fallback";
return GetRatingKeyFromSO(hero);
}
private string GetRatingFromSO(AllyHero_SO so, int currentExp)
private string GetRatingKeyFromSO(AllyHero_SO so)
{
if (so == null || so.levelStats == null || so.levelStats.Count == 0) return "C";
if (so == null || so.levelStats == null || so.levelStats.Count == 0) return "Fallback";
return so.GetDisplayLevelRatingKey();
}
List<AllyHero_SO.AllyLevelInfo> sorted = new List<AllyHero_SO.AllyLevelInfo>();
foreach (var l in so.levelStats) if (l != null) sorted.Add(l);
sorted.Sort((a, b) => a.requiredEXP.CompareTo(b.requiredEXP));
int selectedIndex = 0;
for (int i = 0; i < sorted.Count; i++)
private void ApplyBorderSprite(Image targetImage, string level)
{
if (targetImage == null)
{
if (currentExp >= sorted[i].requiredEXP)
{
selectedIndex = i;
}
else
{
break;
}
return;
}
if (selectedIndex <= 0) return "C";
if (selectedIndex == 1) return "B";
if (selectedIndex == 2) return "A";
return "S";
Sprite borderSprite = GetBorderSprite(level);
targetImage.sprite = borderSprite;
targetImage.color = Color.white;
}
private Color GetBorderColor(string level)
private Sprite GetBorderSprite(string level)
{
Color color;
if (levelBorderSprites == null || levelBorderSprites.Length == 0)
{
return null;
}
int index;
switch (level)
{
case "C": color = levelColor_C; break;
case "B": color = levelColor_B; break;
case "A": color = levelColor_A; break;
case "S": color = levelColor_S; break;
default: color = Color.white; break;
case "Fallback": index = 4; break;
case "C": index = 0; break;
case "B": index = 1; break;
case "A": index = 2; break;
case "S": index = 3; break;
default: index = 4; break;
}
if (index < 0 || index >= levelBorderSprites.Length)
{
return null;
}
return levelBorderSprites[index];
}
public void ShowHeroDetailsForSlot(int slotIndex)
{
loadDetailsPrefab detailsLoader = ResolveDetailsLoader();
if (detailsLoader == null)
{
return;
}
if (!heroCacheReady)
{
return;
}
int heroId = GetSelectedHeroId(slotIndex);
if (heroId == 0 || heroById == null)
{
return;
}
AllyHero_SO hero;
if (!heroById.TryGetValue(heroId, out hero) || hero == null)
{
return;
}
string type = "\u89d2\u8272";
string name = string.IsNullOrEmpty(hero.ally_heroName) ? "\u672a\u77e5\u89d2\u8272" : hero.ally_heroName;
string status = "\u53ef\u7528";
string description = string.IsNullOrEmpty(hero.ally_heroDescription) ? "\u65e0\u63cf\u8ff0" : hero.ally_heroDescription;
if (hoverDetailsPrefab != null)
{
detailsLoader.detailsPrefab = hoverDetailsPrefab;
}
if (hoverDetailsParent != null)
{
detailsLoader.detailsParent = hoverDetailsParent;
}
detailsLoader.ShowDetails(type, name, status, description, Input.mousePosition);
}
public void HideHeroDetails()
{
loadDetailsPrefab detailsLoader = ResolveDetailsLoader();
if (detailsLoader != null)
{
detailsLoader.HideDetails();
}
}
private loadDetailsPrefab ResolveDetailsLoader()
{
if (hoverDetailsLoader != null)
{
return hoverDetailsLoader;
}
return loadDetailsPrefab.Instance;
}
private int GetSelectedHeroId(int slotIndex)
{
int keySlot = Mathf.Clamp(slotIndex, 1, 5);
return PlayerPrefs.GetInt($"selected_heroSlot0{keySlot}_heroID", 0);
}
private sealed class TeammateProfileHoverTarget : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
private load_teammatesProfile owner;
private int slotIndex;
public void Bind(load_teammatesProfile targetOwner, int targetSlotIndex)
{
owner = targetOwner;
slotIndex = targetSlotIndex;
}
public void OnPointerEnter(PointerEventData eventData)
{
if (owner != null)
{
owner.ShowHeroDetailsForSlot(slotIndex);
}
}
public void OnPointerExit(PointerEventData eventData)
{
if (owner != null)
{
owner.HideHeroDetails();
}
}
color.a = 1f;
return color;
}
}
@@ -2,6 +2,7 @@ using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using Bansonic;
public class returnMainMenu : MonoBehaviour
{
@@ -27,8 +28,17 @@ public class returnMainMenu : MonoBehaviour
private IEnumerator LoadSceneAsync(string sceneName)
{
if (gTransition.LoadScene(sceneName, LoadSceneMode.Single))
{
while (gTransition.IsBusy)
{
yield return null;
}
yield break;
}
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
while (!asyncLoad.isDone)
while (asyncLoad != null && !asyncLoad.isDone)
{
yield return null;
}
+5 -2
View File
@@ -276,7 +276,7 @@ public class userSettings : MonoBehaviour
{
Dictionary<int, SongData> songs = new Dictionary<int, SongData>();
SongData[] runtimeSongs = Resources.LoadAll<SongData>(RuntimeResourcesPath);
SongData[] runtimeSongs = RuntimeResourcesCache.LoadSongsFromPath(RuntimeResourcesPath);
if (runtimeSongs != null)
{
for (int i = 0; i < runtimeSongs.Length; i++)
@@ -350,6 +350,9 @@ public class userSettings : MonoBehaviour
AssetDatabase.SaveAssets();
#endif
SceneManager.LoadScene("Main_main");
if (!gTransition.LoadScene("Main_main", LoadSceneMode.Single))
{
SceneManager.LoadScene("Main_main");
}
}
}