功能批量实现 优化 服务端 新浮动小游戏框架 新界面UI

This commit is contained in:
FloatGaming
2026-04-24 13:20:16 +08:00
parent e0bc0bbf08
commit 5eec879582
257 changed files with 38481 additions and 1288 deletions
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
public sealed class ExpBottleLedger : MonoBehaviour
@@ -219,8 +218,6 @@ public sealed class ExpBottleLedger : MonoBehaviour
InitializeIfNeeded();
bool changed = false;
var flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
var type = typeof(Player_SO);
for (int i = 0; i < ExpBottleCatalog.All.Count; i++)
{
@@ -229,14 +226,7 @@ public sealed class ExpBottleLedger : MonoBehaviour
{
continue;
}
var field = type.GetField(descriptor.LegacyPlayerFieldName, flags);
if (field == null || field.FieldType != typeof(int))
{
continue;
}
var legacyValue = Mathf.Max(0, (int)field.GetValue(playerData));
var legacyValue = Mathf.Max(0, playerData.GetLegacyExpBottleCount(descriptor.LegacyPlayerFieldName));
var currentValue = GetCountByKey(descriptor.Key);
if (!overwriteExistingCounts && currentValue > 0)
{
@@ -44,6 +44,7 @@ public sealed class PlayerEconomyLedger : MonoBehaviour
Instance = this;
DontDestroyOnLoad(gameObject);
InitializeIfNeeded();
TryAttachDefaultPlayerData();
}
private void OnApplicationPause(bool pauseStatus)
@@ -87,6 +88,15 @@ public sealed class PlayerEconomyLedger : MonoBehaviour
InitializeIfNeeded();
boundPlayerData = playerData;
if (!loadedFromSave)
{
payload.coins = Mathf.Max(0, playerData.Coins);
payload.material = Mathf.Max(0, playerData.Material);
SaveNow();
loadedFromSave = true;
return;
}
SyncToPlayerData();
GlobalAchievementService.EnsureInstance().ReportCurrentCoins(payload.coins);
NotifyEconomyChanged();
@@ -257,4 +267,14 @@ public sealed class PlayerEconomyLedger : MonoBehaviour
OnMaterialChanged(payload.material);
}
}
private void TryAttachDefaultPlayerData()
{
Player_SO player = RuntimeResourcesCache.LoadDefaultPlayerSo();
if (player != null)
{
AttachPlayerData(player);
}
}
}
@@ -85,7 +85,7 @@ public static class PlayerEconomyStorage
{
version = 1,
lastUpdatedUtcTicks = DateTime.UtcNow.Ticks,
coins = 999999,
coins = 0,
material = 0
};
}
@@ -0,0 +1,218 @@
using System;
using System.IO;
using UnityEngine;
public sealed class PlayerExperienceLedger : MonoBehaviour
{
[Serializable]
private class PlayerExperiencePayload
{
public int version = 1;
public long lastUpdatedUtcTicks;
public int playerExp;
}
public static PlayerExperienceLedger Instance { get; private set; }
public event Action<int> OnExperienceChanged;
private PlayerExperiencePayload payload;
private bool initialized;
private bool loadedFromSave;
private Player_SO boundPlayerData;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Bootstrap()
{
EnsureInstance();
}
public static PlayerExperienceLedger EnsureInstance()
{
if (Instance != null)
{
return Instance;
}
var host = new GameObject("__runtime_player_exp_bridge");
host.hideFlags = HideFlags.HideInHierarchy | HideFlags.HideInInspector;
DontDestroyOnLoad(host);
Instance = host.AddComponent<PlayerExperienceLedger>();
return Instance;
}
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
InitializeIfNeeded();
TryAttachDefaultPlayerData();
}
private void OnApplicationPause(bool pauseStatus)
{
if (pauseStatus)
{
SaveNow();
}
}
private void OnApplicationQuit()
{
SaveNow();
}
public void InitializeIfNeeded()
{
if (initialized)
{
return;
}
loadedFromSave = SecureSaveVault.TryLoadJson("player_experience", "runtime", out payload, GetLegacySavePath());
if (payload == null)
{
payload = CreateDefaultPayload();
}
initialized = true;
}
public void AttachPlayerData(Player_SO playerData)
{
if (playerData == null)
{
return;
}
InitializeIfNeeded();
boundPlayerData = playerData;
if (!loadedFromSave)
{
payload.playerExp = Mathf.Max(0, playerData.player_currentEXP);
SaveNow();
loadedFromSave = true;
return;
}
SyncToPlayerData();
NotifyExperienceChanged();
}
public int GetExperience()
{
InitializeIfNeeded();
return payload.playerExp;
}
public void AddExperience(int amount)
{
if (amount == 0)
{
return;
}
InitializeIfNeeded();
long next = (long)payload.playerExp + amount;
if (next < 0)
{
next = 0;
}
else if (next > int.MaxValue)
{
next = int.MaxValue;
}
payload.playerExp = (int)next;
SaveNow();
}
public bool TryConsumeExperience(int amount)
{
if (amount <= 0)
{
return true;
}
InitializeIfNeeded();
if (payload.playerExp < amount)
{
return false;
}
payload.playerExp -= amount;
SaveNow();
return true;
}
public void SaveNow()
{
if (!initialized)
{
InitializeIfNeeded();
}
if (payload == null)
{
payload = CreateDefaultPayload();
}
payload.lastUpdatedUtcTicks = DateTime.UtcNow.Ticks;
SecureSaveVault.SaveJson("player_experience", "runtime", payload, GetLegacySavePath());
SyncToPlayerData();
NotifyExperienceChanged();
}
private void SyncToPlayerData()
{
if (boundPlayerData == null || payload == null)
{
return;
}
boundPlayerData.player_currentEXP = Mathf.Max(0, payload.playerExp);
#if UNITY_EDITOR
if (!Application.isPlaying)
{
UnityEditor.EditorUtility.SetDirty(boundPlayerData);
}
#endif
}
private void NotifyExperienceChanged()
{
OnExperienceChanged?.Invoke(GetExperience());
}
private void TryAttachDefaultPlayerData()
{
Player_SO player = RuntimeResourcesCache.LoadDefaultPlayerSo();
if (player != null)
{
AttachPlayerData(player);
}
}
private static PlayerExperiencePayload CreateDefaultPayload()
{
return new PlayerExperiencePayload
{
version = 1,
lastUpdatedUtcTicks = DateTime.UtcNow.Ticks,
playerExp = 0,
};
}
private static string GetLegacySavePath()
{
return Path.Combine(Application.persistentDataPath, "player_experience.json");
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ea76e6465f93823418420efa7c62c0f0
@@ -97,7 +97,7 @@ public static class PlayerRksService
private static Player_SO LoadDefaultPlayerSo()
{
Player_SO[] players = Resources.LoadAll<Player_SO>(string.Empty);
Player_SO[] players = RuntimeResourcesCache.LoadAll<Player_SO>(string.Empty);
if (players == null)
{
return null;
@@ -116,7 +116,7 @@ public static class PlayerRksService
private static float CalculateOverallRksRaw()
{
SongData[] songs = Resources.LoadAll<SongData>(string.Empty);
SongData[] songs = RuntimeResourcesCache.LoadAllSongs();
if (songs == null || songs.Length == 0)
{
return 0f;
@@ -171,7 +171,7 @@ public static class PlayerRksService
private static float GetMaxChartConstant()
{
SongData[] songs = Resources.LoadAll<SongData>(string.Empty);
SongData[] songs = RuntimeResourcesCache.LoadAllSongs();
float maxConstant = 0f;
if (songs == null)
{
@@ -491,6 +491,35 @@ public sealed class PlayerSkillService : MonoBehaviour
return;
}
bool assetChanged = false;
if (saveData.selectedSkillIndex >= 0 && saveData.selectedSkillIndex < registeredSkillAsset.skills.Count)
{
for (int i = 0; i < registeredSkillAsset.skills.Count; i++)
{
userLevel_skills_SO.UserLevelSkillEntry entry = registeredSkillAsset.skills[i];
if (entry == null)
{
continue;
}
bool shouldEnable = i == saveData.selectedSkillIndex;
if (entry.isEnabled != shouldEnable)
{
entry.isEnabled = shouldEnable;
assetChanged = true;
}
}
#if UNITY_EDITOR
if (assetChanged && !Application.isPlaying)
{
UnityEditor.EditorUtility.SetDirty(registeredSkillAsset);
}
#endif
return;
}
int enabledIndex = -1;
for (int i = 0; i < registeredSkillAsset.skills.Count; i++)
{
@@ -595,7 +624,7 @@ public sealed class PlayerSkillService : MonoBehaviour
return cachedRandomMemoryRewardItem;
}
storeItemSO[] items = Resources.LoadAll<storeItemSO>("so/storeSO");
storeItemSO[] items = RuntimeResourcesCache.LoadAllStoreItems();
for (int i = 0; i < items.Length; i++)
{
if (items[i] != null && items[i].itemID == RandomMemoryStoreItemId)
@@ -611,7 +640,7 @@ public sealed class PlayerSkillService : MonoBehaviour
private int GetOwnedHeroCount()
{
HashSet<int> heroIds = new HashSet<int>();
AllyHero_SO[] heroes = Resources.LoadAll<AllyHero_SO>(string.Empty);
AllyHero_SO[] heroes = RuntimeResourcesCache.LoadAllAllyHeroes();
if (heroes == null)
{
return 0;
@@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using UnityEngine;
public static class RuntimeResourcesCache
{
private static readonly Dictionary<string, UnityEngine.Object[]> TypedLoadAllCache =
new Dictionary<string, UnityEngine.Object[]>(StringComparer.Ordinal);
private static readonly Dictionary<string, UnityEngine.Object> LoadCache =
new Dictionary<string, UnityEngine.Object>(StringComparer.Ordinal);
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void ResetStaticState()
{
TypedLoadAllCache.Clear();
LoadCache.Clear();
}
public static T[] LoadAll<T>(string path) where T : UnityEngine.Object
{
string normalizedPath = NormalizePath(path);
string cacheKey = typeof(T).FullName + "|" + normalizedPath;
if (TypedLoadAllCache.TryGetValue(cacheKey, out UnityEngine.Object[] cached))
{
return cached as T[] ?? Array.Empty<T>();
}
T[] loaded = Resources.LoadAll<T>(normalizedPath) ?? Array.Empty<T>();
TypedLoadAllCache[cacheKey] = loaded;
return loaded;
}
public static T Load<T>(string path) where T : UnityEngine.Object
{
string normalizedPath = NormalizePath(path);
string cacheKey = typeof(T).FullName + "|" + normalizedPath;
if (LoadCache.TryGetValue(cacheKey, out UnityEngine.Object cached))
{
return cached as T;
}
T loaded = Resources.Load<T>(normalizedPath);
LoadCache[cacheKey] = loaded;
return loaded;
}
public static Player_SO LoadDefaultPlayerSo()
{
Player_SO player = Load<Player_SO>("playerdata/playerData_uniqueData");
if (player != null)
{
return player;
}
Player_SO[] players = LoadAll<Player_SO>(string.Empty);
return players.Length > 0 ? players[0] : null;
}
public static SongData[] LoadAllSongs()
{
return LoadAll<SongData>(string.Empty);
}
public static SongData[] LoadSongIndex()
{
return LoadAll<SongData>("song_songIndex");
}
public static SongData[] LoadSongsFromPath(string path)
{
return LoadAll<SongData>(path);
}
public static AllyHero_SO[] LoadAllAllyHeroes()
{
return LoadAll<AllyHero_SO>(string.Empty);
}
public static storeItemSO[] LoadAllStoreItems()
{
return LoadAll<storeItemSO>("so/storeSO");
}
public static dlcData[] LoadAllDlcs()
{
return LoadAll<dlcData>(string.Empty);
}
public static expBottlesSO[] LoadAllExpBottles()
{
return LoadAll<expBottlesSO>(string.Empty);
}
public static growthMaterialSO[] LoadAllGrowthMaterials()
{
return LoadAll<growthMaterialSO>(string.Empty);
}
public static equipmentConsumableSO[] LoadAllEquipmentConsumables()
{
return LoadAll<equipmentConsumableSO>(string.Empty);
}
public static mail_so[] LoadAllMailDefinitions()
{
return LoadAll<mail_so>("so/mail_so");
}
public static void InvalidateAll()
{
TypedLoadAllCache.Clear();
LoadCache.Clear();
}
private static string NormalizePath(string path)
{
return string.IsNullOrWhiteSpace(path) ? string.Empty : path.Trim().Replace("\\", "/");
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 668b399541da6ff4db8f7894f36e5434
@@ -142,6 +142,44 @@ public sealed class StoreOwnershipLedger : MonoBehaviour
return true;
}
public bool TryRevokeOwnership(storeItemSO itemSO, out string failureMessage)
{
InitializeIfNeeded();
failureMessage = string.Empty;
if (itemSO == null)
{
failureMessage = "商品数据丢失";
return false;
}
StoreOwnershipEntry entry;
entriesByItemId.TryGetValue(itemSO.itemID, out entry);
if (entry == null && !IsOwned(itemSO))
{
return true;
}
if (entry != null)
{
entry.owned = false;
if (entry.unlockedStorySonIds != null)
{
entry.unlockedStorySonIds.Clear();
}
if (!entry.owned && (entry.unlockedStorySonIds == null || entry.unlockedStorySonIds.Count == 0))
{
entriesByItemId.Remove(itemSO.itemID);
}
}
SyncAllMirrorFlags();
GlobalAchievementService.EnsureInstance().RefreshDerivedMetrics();
SaveNow();
return true;
}
public bool TryGrantOwnershipPreview(storeItemSO itemSO, out string failureMessage)
{
InitializeIfNeeded();
@@ -203,7 +241,7 @@ public sealed class StoreOwnershipLedger : MonoBehaviour
private void LoadStoreItems()
{
cachedStoreItems.Clear();
var loadedItems = Resources.LoadAll<storeItemSO>(RuntimeStoreItemResourcesPath);
var loadedItems = RuntimeResourcesCache.LoadAllStoreItems();
var seen = new HashSet<int>();
for (int i = 0; i < loadedItems.Length; i++)
{