加入用户最近100场战绩记录并实现展示

This commit is contained in:
2026-03-16 23:18:58 +08:00
parent ec42f2e34b
commit f5c6f143c0
106 changed files with 12120 additions and 642 deletions
+41 -35
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using UnityEngine;
@@ -58,9 +58,11 @@ public class ChartFileEntry
[CreateAssetMenu(fileName = "NewSongData", menuName = "EaseOut/SongData")]
public class SongData : ScriptableObject
{
private const string SecureSaveCategory = "song_runtime";
public string usernameID = "user_TestUser@FloatGaming";
public string songName;
public int songID;
public bool isUnlocked;
public int songLength_second;
public string artistName;
public string painter;
@@ -289,7 +291,7 @@ public class SongData : ScriptableObject
if (chartFileMap == null) chartFileMap = new Dictionary<int, TextAsset>();
if (chartFiles == null) chartFiles = new List<ChartFileEntry>();
// 尝试从持久化存储加载数据
// 灏濊瘯浠庢寔涔呭寲瀛樺偍鍔犺浇鏁版嵁
LoadPersistent();
SyncRecordsFromChartFiles();
@@ -317,8 +319,7 @@ public class SongData : ScriptableObject
}
/// <summary>
/// 将数据保存到持久化存储,并附加 MD5 校验和防止篡改。
/// </summary>
/// 灏嗘暟鎹繚瀛樺埌鎸佷箙鍖栧瓨鍌紝骞堕檮鍔?MD5 鏍¢獙鍜岄槻姝㈢鏀广€? /// </summary>
public void SavePersistent()
{
if (songID == 0)
@@ -328,28 +329,23 @@ public class SongData : ScriptableObject
try
{
// 在保存前确保 chartFiles 已同步,这样在编辑器中查看 SO 时数据也是最新的
// 鍦ㄤ繚瀛樺墠纭繚 chartFiles 宸插悓姝ワ紝杩欐牱鍦ㄧ紪杈戝櫒涓煡鐪?SO 鏃舵暟鎹篃鏄渶鏂扮殑
SyncEntriesFromMaps();
string path = System.IO.Path.Combine(Application.persistentDataPath, $"SongData_{songID}.json");
SongDataSerializable serializable = new SongDataSerializable(this);
// 1. 序列化基础数据
// 1. 搴忓垪鍖栧熀纭€鏁版嵁
string jsonWithoutHash = JsonUtility.ToJson(serializable);
// 2. 计算 MD5 (使用自定义盐值提高安全性)
// 2. 璁$畻 MD5 (浣跨敤鑷畾涔夌洂鍊兼彁楂樺畨鍏ㄦ€?
serializable.dataHash = CalculateMD5(jsonWithoutHash + "bansonic2026@fudongyouxi");
// 3. 再次序列化包含 Hash 的最终数据
string finalJson = JsonUtility.ToJson(serializable, true);
// 确保目录存在
string dir = System.IO.Path.GetDirectoryName(path);
if (!System.IO.Directory.Exists(dir)) System.IO.Directory.CreateDirectory(dir);
SecureSaveVault.SaveRawJson(SecureSaveCategory, songID.ToString(), finalJson, GetLegacySavePath());
System.IO.File.WriteAllText(path, finalJson);
Debug.Log($"[SongData] {songName} (ID:{songID}) saved to {path}");
Debug.Log($"[SongData] {songName} (ID:{songID}) saved to secure vault.");
#if UNITY_EDITOR
if (!Application.isPlaying)
@@ -365,21 +361,32 @@ public class SongData : ScriptableObject
}
}
public void SetUnlocked(bool value)
{
if (isUnlocked == value)
{
return;
}
isUnlocked = value;
#if UNITY_EDITOR
UnityEditor.EditorUtility.SetDirty(this);
#endif
}
/// <summary>
/// 从持久化 JSON 恢复数据。
/// 校验逻辑已迁移至 DataCheck 脚本,此处仅执行基础加载。
/// </summary>
/// 浠庢寔涔呭寲 JSON 鎭㈠鏁版嵁銆? /// 鏍¢獙閫昏緫宸茶縼绉昏嚦 DataCheck 鑴氭湰锛屾澶勪粎鎵ц鍩虹鍔犺浇銆? /// </summary>
public void LoadPersistent()
{
if (_isPersistentDataLoaded) return;
string path = System.IO.Path.Combine(Application.persistentDataPath, $"SongData_{songID}.json");
if (System.IO.File.Exists(path))
string json;
if (SecureSaveVault.TryLoadRawJson(SecureSaveCategory, songID.ToString(), out json, GetLegacySavePath()))
{
try
{
string json = System.IO.File.ReadAllText(path);
// 尝试解析数据
// 灏濊瘯瑙f瀽鏁版嵁
SongDataSerializable serializable = JsonUtility.FromJson<SongDataSerializable>(json);
if (serializable != null)
{
@@ -391,7 +398,7 @@ public class SongData : ScriptableObject
}
catch (System.Exception e)
{
Debug.LogWarning($"[SongData] {songName} 基础加载失败: {e.Message}");
Debug.LogWarning($"[SongData] {songName} 鍩虹鍔犺浇澶辫触: {e.Message}");
}
}
else
@@ -456,11 +463,7 @@ public class SongData : ScriptableObject
UpdateOverallRecordsFromEntries();
string path = System.IO.Path.Combine(Application.persistentDataPath, $"SongData_{songID}.json");
if (System.IO.File.Exists(path))
{
try { System.IO.File.Delete(path); } catch { }
}
SecureSaveVault.Delete(SecureSaveCategory, songID.ToString(), GetLegacySavePath());
SavePersistent();
@@ -470,8 +473,12 @@ public class SongData : ScriptableObject
}
/// <summary>
/// 当检测到篡改或文件损坏时,重置所有动态记录并立即重新保存文件以恢复合法性。
/// </summary>
/// 褰撴娴嬪埌绡℃敼鎴栨枃浠舵崯鍧忔椂锛岄噸缃墍鏈夊姩鎬佽褰曞苟绔嬪嵆閲嶆柊淇濆瓨鏂囦欢浠ユ仮澶嶅悎娉曟€с€? /// </summary>
private string GetLegacySavePath()
{
return System.IO.Path.Combine(Application.persistentDataPath, $"SongData_{songID}.json");
}
private void ResetToZeroAndSave()
{
// 1. 重置所有动态数值字段
@@ -498,15 +505,14 @@ public class SongData : ScriptableObject
if (levelProgressMap == null) levelProgressMap = new Dictionary<int, float>();
else levelProgressMap.Clear();
// 3. 立即调用保存方法,生成一个新的、带有正确哈希值的合法文件
// 3. 绔嬪嵆璋冪敤淇濆瓨鏂规硶锛岀敓鎴愪竴涓柊鐨勩€佸甫鏈夋纭搱甯屽€肩殑鍚堟硶鏂囦欢
SavePersistent();
Debug.LogWarning($"[SongData] {songName} has been fully reset to zero due to data integrity failure.");
}
/// <summary>
/// 静态校验方法,可在异步线程调用,判断 JSON 内容是否合法。
/// </summary>
/// 闈欐€佹牎楠屾柟娉曪紝鍙湪寮傛绾跨▼璋冪敤锛屽垽鏂?JSON 鍐呭鏄惁鍚堟硶銆? /// </summary>
public static bool VerifyJsonIntegrity(string json, string salt, out SongDataSerializable result)
{
result = null;
@@ -519,7 +525,7 @@ public class SongData : ScriptableObject
serializable.dataHash = "";
string currentJson = JsonUtility.ToJson(serializable);
// 使用静态 MD5 计算
// 浣跨敤闈欐€?MD5 璁$畻
string calculatedHash = StaticCalculateMD5(currentJson + salt);
result = serializable;
@@ -620,8 +626,7 @@ public class SongData : ScriptableObject
}
/// <summary>
/// Dictionary 中的数据同步回 ChartFileEntry 列表,确保 SO 内存数据与持久化数据一致。
/// </summary>
/// 灏?Dictionary 涓殑鏁版嵁鍚屾鍥?ChartFileEntry 鍒楄〃锛岀‘淇?SO 鍐呭瓨鏁版嵁涓庢寔涔呭寲鏁版嵁涓€鑷淬€? /// </summary>
public void SyncEntriesFromMaps()
{
if (chartFiles == null) return;
@@ -898,3 +903,4 @@ public class SongData : ScriptableObject
CalculateHighestPersonalRecord();
}
}
+10 -8
View File
@@ -1,10 +1,11 @@
using System.Collections;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public class SongDataLoader : MonoBehaviour
{
private const string SecureCategory = "song_export";
private string jsonDirectory;
public List<SongDataSerializable> loadedSongs = new List<SongDataSerializable>();
[SerializeField] private int _yieldEveryNFiles = 5;
@@ -17,26 +18,27 @@ public class SongDataLoader : MonoBehaviour
IEnumerator LoadAllSongsFromJsonRoutine()
{
if (!Directory.Exists(jsonDirectory))
List<string> loadedJsonList = SecureSaveVault.LoadAllRawJson(SecureCategory, jsonDirectory);
if (loadedJsonList == null || loadedJsonList.Count == 0)
{
Debug.LogError("JSON ļвڣ");
Debug.LogError("JSON 持久化数据不存在");
yield break;
}
loadedSongs.Clear();
IEnumerable<string> jsonFiles = Directory.EnumerateFiles(jsonDirectory, "*.json");
int counter = 0;
foreach (string file in jsonFiles)
for (int i = 0; i < loadedJsonList.Count; i++)
{
string json = File.ReadAllText(file);
string json = loadedJsonList[i];
SongDataSerializable jsonData = JsonUtility.FromJson<SongDataSerializable>(json);
loadedSongs.Add(jsonData);
// 由于 SongDataSerializable 现在只存储动态分数,不再包含 songName
Debug.Log($"Loaded persistent song data from: {Path.GetFileName(file)}");
Debug.Log("Loaded persistent song data from secure vault.");
counter++;
if (_yieldEveryNFiles > 0 && counter % _yieldEveryNFiles == 0)
{
yield return null;
}
}
}
}
+8 -14
View File
@@ -1,8 +1,9 @@
using System.IO;
using System.IO;
using UnityEngine;
public class SongDataManager : MonoBehaviour
{
private const string SecureCategory = "song_export";
private string jsonDirectory;
[Header("Settings")]
@@ -12,12 +13,6 @@ public class SongDataManager : MonoBehaviour
{
jsonDirectory = Path.Combine(Application.persistentDataPath, "song_json_export");
if (!Directory.Exists(jsonDirectory))
{
Directory.CreateDirectory(jsonDirectory);
Debug.Log("Created song JSON directory: " + jsonDirectory);
}
if (autoSaveOnStart)
{
StartCoroutine(SaveAllSongDataToJsonRoutine());
@@ -39,7 +34,7 @@ public class SongDataManager : MonoBehaviour
yield break;
}
Debug.Log($"Start exporting {allSongData.Length} songs to JSON...");
Debug.Log($"Start exporting {allSongData.Length} songs to secure vault...");
for (int i = 0; i < allSongData.Length; i++)
{
@@ -47,14 +42,13 @@ public class SongDataManager : MonoBehaviour
if (song == null) continue;
SongDataSerializable jsonData = new SongDataSerializable(song);
string json = JsonUtility.ToJson(jsonData, true);
string filePath = Path.Combine(jsonDirectory, song.songID + ".json");
File.WriteAllText(filePath, json);
string json = JsonUtility.ToJson(jsonData, false);
string legacyPath = Path.Combine(jsonDirectory, song.songID + ".json");
SecureSaveVault.SaveRawJson(SecureCategory, song.songID.ToString(), json, legacyPath);
yield return null;
}
Debug.Log("All song JSON files exported.");
Debug.Log("All song JSON files exported to secure vault.");
}
}