功能批量实现 优化 服务端 新浮动小游戏框架 新界面UI
This commit is contained in:
@@ -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++)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user