拼ui 一些业务逻辑x实现
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 18f4d2367ef2dff429599f1580fd6bad
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a61efc08d9ba71e4ead0ef17331c2cf2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: afec8d3d486f3574d83ade6fc9dc4b82
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
[Serializable]
|
||||
public class GlobalAchievementMetricEntry
|
||||
{
|
||||
public GlobalAchievementMetricType metricType;
|
||||
public float value;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class GlobalAchievementStateEntry
|
||||
{
|
||||
public int achievementId;
|
||||
public bool unlocked;
|
||||
public long unlockedUtcTicks;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class GlobalAchievementSaveData
|
||||
{
|
||||
public int version = 1;
|
||||
public string lastLoginDateStamp;
|
||||
public string trustedDateStamp;
|
||||
public List<GlobalAchievementMetricEntry> metrics = new List<GlobalAchievementMetricEntry>();
|
||||
public List<GlobalAchievementStateEntry> states = new List<GlobalAchievementStateEntry>();
|
||||
}
|
||||
|
||||
public struct GlobalAchievementSnapshot
|
||||
{
|
||||
public GlobalAchievementSO definition;
|
||||
public float currentValue;
|
||||
public float normalizedProgress;
|
||||
public bool unlocked;
|
||||
public DateTime unlockedUtc;
|
||||
public string unlockedDateTimeText;
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f9bc8ea38a77534a9d329e1992fb750
|
||||
@@ -0,0 +1,71 @@
|
||||
using UnityEngine;
|
||||
|
||||
public enum GlobalAchievementMetricType
|
||||
{
|
||||
TotalCoinsEarned = 0,
|
||||
OwnedHeroCount = 1,
|
||||
OwnedSongCount = 2,
|
||||
BestSingleSongTotalScore = 3,
|
||||
TotalSongBestScoreSum = 4,
|
||||
LoginDays = 5,
|
||||
}
|
||||
|
||||
[CreateAssetMenu(fileName = "GA_", menuName = "SO_Data/GlobalAchievement")]
|
||||
public class GlobalAchievementSO : ScriptableObject
|
||||
{
|
||||
[Header("identity")]
|
||||
public int achievementId;
|
||||
public string displayName;
|
||||
public string achievementSeriesKey;
|
||||
[Range(0, 6)] public int achievementLevel;
|
||||
|
||||
[TextArea(2, 5)]
|
||||
public string description;
|
||||
|
||||
[Header("display")]
|
||||
public Sprite icon;
|
||||
public int sortOrder;
|
||||
public bool hiddenUntilUnlocked;
|
||||
|
||||
[Header("runtime mirror")]
|
||||
[SerializeField] private bool isUnlocked;
|
||||
[SerializeField] private string unlockedDateTime;
|
||||
|
||||
[Header("trigger")]
|
||||
public GlobalAchievementMetricType metricType = GlobalAchievementMetricType.LoginDays;
|
||||
public float targetValue = 1f;
|
||||
|
||||
[Header("steam")]
|
||||
public bool syncWithSteam;
|
||||
public string steamAchievementApiName;
|
||||
|
||||
public bool IsUnlocked
|
||||
{
|
||||
get { return isUnlocked; }
|
||||
}
|
||||
|
||||
public string UnlockedDateTime
|
||||
{
|
||||
get { return unlockedDateTime; }
|
||||
}
|
||||
|
||||
public void SetUnlockedState(bool value, long unlockedUtcTicks = 0L)
|
||||
{
|
||||
isUnlocked = value;
|
||||
if (value && unlockedUtcTicks > 0L)
|
||||
{
|
||||
unlockedDateTime = new System.DateTime(unlockedUtcTicks, System.DateTimeKind.Utc).ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss");
|
||||
}
|
||||
else if (!value)
|
||||
{
|
||||
unlockedDateTime = string.Empty;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
UnityEditor.EditorUtility.SetDirty(this);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c385da50177c22f468bf5b7012e70372
|
||||
@@ -0,0 +1,571 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public sealed class GlobalAchievementService : MonoBehaviour
|
||||
{
|
||||
private const string SaveCategory = "global_achievement";
|
||||
private const string SaveKey = "runtime_state";
|
||||
public const string DefaultResourcesPath = "so/globalachievments";
|
||||
public const string DefaultEditorAssetFolder = "Assets/Resources/so/globalachievments";
|
||||
|
||||
public static GlobalAchievementService Instance { get; private set; }
|
||||
|
||||
public event Action<GlobalAchievementSO> OnAchievementUnlocked;
|
||||
|
||||
private readonly List<GlobalAchievementSO> definitions = new List<GlobalAchievementSO>();
|
||||
private readonly Dictionary<int, GlobalAchievementStateEntry> stateById = new Dictionary<int, GlobalAchievementStateEntry>();
|
||||
private readonly Dictionary<GlobalAchievementMetricType, GlobalAchievementMetricEntry> metricByType = new Dictionary<GlobalAchievementMetricType, GlobalAchievementMetricEntry>();
|
||||
|
||||
private GlobalAchievementSaveData saveData;
|
||||
private bool initialized;
|
||||
private bool loginReportedThisSession;
|
||||
private int lastSaveFrame = -1;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
private static void Bootstrap()
|
||||
{
|
||||
EnsureInstance();
|
||||
}
|
||||
|
||||
public static GlobalAchievementService EnsureInstance()
|
||||
{
|
||||
if (Instance != null)
|
||||
{
|
||||
return Instance;
|
||||
}
|
||||
|
||||
var host = new GameObject("__global_achievement_service");
|
||||
host.hideFlags = HideFlags.HideInHierarchy | HideFlags.HideInInspector;
|
||||
DontDestroyOnLoad(host);
|
||||
Instance = host.AddComponent<GlobalAchievementService>();
|
||||
return Instance;
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
InitializeIfNeeded();
|
||||
}
|
||||
|
||||
private void OnApplicationPause(bool pauseStatus)
|
||||
{
|
||||
if (pauseStatus)
|
||||
{
|
||||
SaveNow();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnApplicationQuit()
|
||||
{
|
||||
SaveNow();
|
||||
}
|
||||
|
||||
public void InitializeIfNeeded()
|
||||
{
|
||||
if (initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
GlobalAchievementSaveData loaded;
|
||||
if (!SecureSaveVault.TryLoadJson(SaveCategory, SaveKey, out loaded))
|
||||
{
|
||||
loaded = new GlobalAchievementSaveData();
|
||||
}
|
||||
|
||||
saveData = loaded ?? new GlobalAchievementSaveData();
|
||||
RebuildCaches();
|
||||
LoadDefinitionsFromResources();
|
||||
ReportLoginSessionStart();
|
||||
RefreshDerivedMetrics();
|
||||
SaveNow();
|
||||
}
|
||||
|
||||
public void LoadDefinitionsFromResources(string resourcesPath = DefaultResourcesPath)
|
||||
{
|
||||
InitializeIfNeeded();
|
||||
|
||||
definitions.Clear();
|
||||
GlobalAchievementSO[] loadedDefinitions = Resources.LoadAll<GlobalAchievementSO>(resourcesPath);
|
||||
if (loadedDefinitions != null && loadedDefinitions.Length > 0)
|
||||
{
|
||||
definitions.AddRange(loadedDefinitions);
|
||||
definitions.Sort(CompareDefinitions);
|
||||
}
|
||||
|
||||
SyncMirrorFlagsToDefinitions();
|
||||
EvaluateAll();
|
||||
}
|
||||
|
||||
public IReadOnlyList<GlobalAchievementSO> GetDefinitions()
|
||||
{
|
||||
InitializeIfNeeded();
|
||||
return definitions;
|
||||
}
|
||||
|
||||
public List<GlobalAchievementSnapshot> GetSnapshots()
|
||||
{
|
||||
InitializeIfNeeded();
|
||||
var snapshots = new List<GlobalAchievementSnapshot>(definitions.Count);
|
||||
for (int i = 0; i < definitions.Count; i++)
|
||||
{
|
||||
GlobalAchievementSO definition = definitions[i];
|
||||
if (definition == null || definition.achievementId <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GlobalAchievementStateEntry state = GetOrCreateState(definition.achievementId);
|
||||
float currentValue = GetMetric(definition.metricType);
|
||||
float targetValue = Mathf.Max(1f, definition.targetValue);
|
||||
snapshots.Add(new GlobalAchievementSnapshot
|
||||
{
|
||||
definition = definition,
|
||||
currentValue = currentValue,
|
||||
normalizedProgress = Mathf.Clamp01(currentValue / targetValue),
|
||||
unlocked = state.unlocked,
|
||||
unlockedUtc = state.unlockedUtcTicks > 0 ? new DateTime(state.unlockedUtcTicks, DateTimeKind.Utc) : default(DateTime),
|
||||
unlockedDateTimeText = state.unlockedUtcTicks > 0 ? new DateTime(state.unlockedUtcTicks, DateTimeKind.Utc).ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss") : string.Empty,
|
||||
});
|
||||
}
|
||||
|
||||
return snapshots;
|
||||
}
|
||||
|
||||
public float GetMetric(GlobalAchievementMetricType metricType)
|
||||
{
|
||||
InitializeIfNeeded();
|
||||
GlobalAchievementMetricEntry metric;
|
||||
if (metricByType.TryGetValue(metricType, out metric))
|
||||
{
|
||||
return Mathf.Max(0f, metric.value);
|
||||
}
|
||||
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public void AddMetric(GlobalAchievementMetricType metricType, float delta)
|
||||
{
|
||||
if (Mathf.Approximately(delta, 0f))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InitializeIfNeeded();
|
||||
GlobalAchievementMetricEntry metric = GetOrCreateMetric(metricType);
|
||||
float nextValue = Mathf.Max(0f, metric.value + delta);
|
||||
if (Mathf.Approximately(metric.value, nextValue))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
metric.value = nextValue;
|
||||
PersistMetricsIfChanged(true);
|
||||
}
|
||||
|
||||
public void SetMetric(GlobalAchievementMetricType metricType, float value)
|
||||
{
|
||||
InitializeIfNeeded();
|
||||
PersistMetricsIfChanged(SetMetricValue(metricType, value));
|
||||
}
|
||||
|
||||
public void SetMetricAtLeast(GlobalAchievementMetricType metricType, float value)
|
||||
{
|
||||
InitializeIfNeeded();
|
||||
GlobalAchievementMetricEntry metric = GetOrCreateMetric(metricType);
|
||||
float nextValue = Mathf.Max(0f, value);
|
||||
if (nextValue <= metric.value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
metric.value = nextValue;
|
||||
PersistMetricsIfChanged(true);
|
||||
}
|
||||
|
||||
public void ReportLoginSessionStart()
|
||||
{
|
||||
InitializeIfNeeded();
|
||||
if (loginReportedThisSession)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
loginReportedThisSession = true;
|
||||
string currentDateStamp = DateTime.Now.ToString("yyyy-MM-dd");
|
||||
if (!string.IsNullOrEmpty(saveData.trustedDateStamp) && string.CompareOrdinal(currentDateStamp, saveData.trustedDateStamp) < 0)
|
||||
{
|
||||
currentDateStamp = saveData.trustedDateStamp;
|
||||
}
|
||||
else
|
||||
{
|
||||
saveData.trustedDateStamp = currentDateStamp;
|
||||
}
|
||||
|
||||
if (!string.Equals(saveData.lastLoginDateStamp, currentDateStamp, StringComparison.Ordinal))
|
||||
{
|
||||
saveData.lastLoginDateStamp = currentDateStamp;
|
||||
AddMetric(GlobalAchievementMetricType.LoginDays, 1f);
|
||||
return;
|
||||
}
|
||||
|
||||
SaveNow();
|
||||
}
|
||||
|
||||
public void ReportCurrentCoins(int currentCoins)
|
||||
{
|
||||
// No current-coin achievement type now, but this remains available as a central probe point
|
||||
// for future expansion without changing economy call sites.
|
||||
}
|
||||
|
||||
public void ReportCoinsEarned(int amount)
|
||||
{
|
||||
if (amount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AddMetric(GlobalAchievementMetricType.TotalCoinsEarned, amount);
|
||||
}
|
||||
|
||||
public void RefreshOwnedHeroCount()
|
||||
{
|
||||
InitializeIfNeeded();
|
||||
PersistMetricsIfChanged(RefreshOwnedHeroCountInternal(true));
|
||||
}
|
||||
|
||||
public void RefreshOwnedSongCountAndScores()
|
||||
{
|
||||
InitializeIfNeeded();
|
||||
PersistMetricsIfChanged(RefreshOwnedSongCountAndScoresInternal(true));
|
||||
}
|
||||
|
||||
public void RefreshDerivedMetrics()
|
||||
{
|
||||
InitializeIfNeeded();
|
||||
StoreOwnershipLedger.EnsureInstance().ForceSyncMirrorFlags();
|
||||
|
||||
bool changed = false;
|
||||
changed |= RefreshOwnedHeroCountInternal(false);
|
||||
changed |= RefreshOwnedSongCountAndScoresInternal(false);
|
||||
PersistMetricsIfChanged(changed);
|
||||
}
|
||||
|
||||
public void SaveNow()
|
||||
{
|
||||
if (!initialized || saveData == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Application.isPlaying && lastSaveFrame == Time.frameCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lastSaveFrame = Application.isPlaying ? Time.frameCount : -1;
|
||||
SecureSaveVault.SaveJson(SaveCategory, SaveKey, saveData);
|
||||
}
|
||||
|
||||
private void EvaluateAll()
|
||||
{
|
||||
for (int i = 0; i < definitions.Count; i++)
|
||||
{
|
||||
GlobalAchievementSO definition = definitions[i];
|
||||
if (definition == null || definition.achievementId <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GlobalAchievementStateEntry state = GetOrCreateState(definition.achievementId);
|
||||
if (state.unlocked)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float currentValue = GetMetric(definition.metricType);
|
||||
if (currentValue + 0.0001f < Mathf.Max(0f, definition.targetValue))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Unlock(definition, state);
|
||||
}
|
||||
|
||||
SyncMirrorFlagsToDefinitions();
|
||||
}
|
||||
|
||||
private bool RefreshOwnedHeroCountInternal(bool syncOwnershipFirst)
|
||||
{
|
||||
if (syncOwnershipFirst)
|
||||
{
|
||||
StoreOwnershipLedger.EnsureInstance().ForceSyncMirrorFlags();
|
||||
}
|
||||
|
||||
AllyHero_SO[] heroes = Resources.LoadAll<AllyHero_SO>("so/ally");
|
||||
if (heroes == null || heroes.Length == 0)
|
||||
{
|
||||
return SetMetricValue(GlobalAchievementMetricType.OwnedHeroCount, 0f);
|
||||
}
|
||||
|
||||
var uniqueIds = new HashSet<int>();
|
||||
int unlockedCount = 0;
|
||||
for (int i = 0; i < heroes.Length; i++)
|
||||
{
|
||||
AllyHero_SO hero = heroes[i];
|
||||
if (hero == null || hero.ally_heroID <= 0 || !uniqueIds.Add(hero.ally_heroID))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hero.isUnlocked)
|
||||
{
|
||||
unlockedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return SetMetricValue(GlobalAchievementMetricType.OwnedHeroCount, unlockedCount);
|
||||
}
|
||||
|
||||
private bool RefreshOwnedSongCountAndScoresInternal(bool syncOwnershipFirst)
|
||||
{
|
||||
if (syncOwnershipFirst)
|
||||
{
|
||||
StoreOwnershipLedger.EnsureInstance().ForceSyncMirrorFlags();
|
||||
}
|
||||
|
||||
SongData[] songs = Resources.LoadAll<SongData>("song_songIndex");
|
||||
if (songs == null || songs.Length == 0)
|
||||
{
|
||||
bool emptyChanged = false;
|
||||
emptyChanged |= SetMetricValue(GlobalAchievementMetricType.OwnedSongCount, 0f);
|
||||
emptyChanged |= SetMetricValue(GlobalAchievementMetricType.BestSingleSongTotalScore, 0f);
|
||||
emptyChanged |= SetMetricValue(GlobalAchievementMetricType.TotalSongBestScoreSum, 0f);
|
||||
return emptyChanged;
|
||||
}
|
||||
|
||||
var uniqueIds = new HashSet<int>();
|
||||
int ownedSongCount = 0;
|
||||
int bestSingleSongScore = 0;
|
||||
long totalSongBestScore = 0L;
|
||||
|
||||
for (int i = 0; i < songs.Length; i++)
|
||||
{
|
||||
SongData song = songs[i];
|
||||
if (song == null || song.songID <= 0 || !uniqueIds.Add(song.songID))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
song.LoadPersistent();
|
||||
|
||||
if (song.isUnlocked)
|
||||
{
|
||||
ownedSongCount++;
|
||||
}
|
||||
|
||||
int highestScore;
|
||||
int bestDifficulty;
|
||||
string bestDifficultyName;
|
||||
song.GetAbsoluteHighestScore(out highestScore, out bestDifficulty, out bestDifficultyName);
|
||||
if (highestScore > bestSingleSongScore)
|
||||
{
|
||||
bestSingleSongScore = highestScore;
|
||||
}
|
||||
|
||||
totalSongBestScore += Mathf.Max(0, highestScore);
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
changed |= SetMetricValue(GlobalAchievementMetricType.OwnedSongCount, ownedSongCount);
|
||||
changed |= SetMetricValue(GlobalAchievementMetricType.BestSingleSongTotalScore, bestSingleSongScore);
|
||||
changed |= SetMetricValue(GlobalAchievementMetricType.TotalSongBestScoreSum, totalSongBestScore > float.MaxValue ? float.MaxValue : totalSongBestScore);
|
||||
return changed;
|
||||
}
|
||||
|
||||
private bool SetMetricValue(GlobalAchievementMetricType metricType, float value)
|
||||
{
|
||||
GlobalAchievementMetricEntry metric = GetOrCreateMetric(metricType);
|
||||
float nextValue = Mathf.Max(0f, value);
|
||||
if (Mathf.Approximately(metric.value, nextValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
metric.value = nextValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void PersistMetricsIfChanged(bool changed)
|
||||
{
|
||||
if (!changed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EvaluateAll();
|
||||
SaveNow();
|
||||
}
|
||||
|
||||
private void Unlock(GlobalAchievementSO definition, GlobalAchievementStateEntry state)
|
||||
{
|
||||
state.unlocked = true;
|
||||
state.unlockedUtcTicks = DateTime.UtcNow.Ticks;
|
||||
definition.SetUnlockedState(true, state.unlockedUtcTicks);
|
||||
GlobalAchievementSteamBridge.TryUnlock(definition);
|
||||
if (OnAchievementUnlocked != null)
|
||||
{
|
||||
OnAchievementUnlocked(definition);
|
||||
}
|
||||
}
|
||||
|
||||
private GlobalAchievementMetricEntry GetOrCreateMetric(GlobalAchievementMetricType metricType)
|
||||
{
|
||||
GlobalAchievementMetricEntry metric;
|
||||
if (metricByType.TryGetValue(metricType, out metric))
|
||||
{
|
||||
return metric;
|
||||
}
|
||||
|
||||
metric = new GlobalAchievementMetricEntry
|
||||
{
|
||||
metricType = metricType,
|
||||
value = 0f
|
||||
};
|
||||
metricByType.Add(metricType, metric);
|
||||
saveData.metrics.Add(metric);
|
||||
return metric;
|
||||
}
|
||||
|
||||
private GlobalAchievementStateEntry GetOrCreateState(int achievementId)
|
||||
{
|
||||
GlobalAchievementStateEntry state;
|
||||
if (stateById.TryGetValue(achievementId, out state))
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
state = new GlobalAchievementStateEntry
|
||||
{
|
||||
achievementId = achievementId,
|
||||
unlocked = false,
|
||||
unlockedUtcTicks = 0L
|
||||
};
|
||||
stateById.Add(achievementId, state);
|
||||
saveData.states.Add(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
private void RebuildCaches()
|
||||
{
|
||||
stateById.Clear();
|
||||
metricByType.Clear();
|
||||
|
||||
if (saveData.metrics == null)
|
||||
{
|
||||
saveData.metrics = new List<GlobalAchievementMetricEntry>();
|
||||
}
|
||||
|
||||
if (saveData.states == null)
|
||||
{
|
||||
saveData.states = new List<GlobalAchievementStateEntry>();
|
||||
}
|
||||
|
||||
for (int i = 0; i < saveData.metrics.Count; i++)
|
||||
{
|
||||
GlobalAchievementMetricEntry entry = saveData.metrics[i];
|
||||
if (entry == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
metricByType[entry.metricType] = entry;
|
||||
}
|
||||
|
||||
for (int i = 0; i < saveData.states.Count; i++)
|
||||
{
|
||||
GlobalAchievementStateEntry entry = saveData.states[i];
|
||||
if (entry == null || entry.achievementId <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
stateById[entry.achievementId] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
private static int CompareDefinitions(GlobalAchievementSO left, GlobalAchievementSO right)
|
||||
{
|
||||
if (left == null && right == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (left == null)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (right == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int seriesCompare = string.CompareOrdinal(left.achievementSeriesKey, right.achievementSeriesKey);
|
||||
if (seriesCompare != 0)
|
||||
{
|
||||
return seriesCompare;
|
||||
}
|
||||
|
||||
int levelCompare = left.achievementLevel.CompareTo(right.achievementLevel);
|
||||
if (levelCompare != 0)
|
||||
{
|
||||
return levelCompare;
|
||||
}
|
||||
|
||||
int sortCompare = left.sortOrder.CompareTo(right.sortOrder);
|
||||
if (sortCompare != 0)
|
||||
{
|
||||
return sortCompare;
|
||||
}
|
||||
|
||||
int metricCompare = left.metricType.CompareTo(right.metricType);
|
||||
if (metricCompare != 0)
|
||||
{
|
||||
return metricCompare;
|
||||
}
|
||||
|
||||
int targetCompare = left.targetValue.CompareTo(right.targetValue);
|
||||
if (targetCompare != 0)
|
||||
{
|
||||
return targetCompare;
|
||||
}
|
||||
|
||||
return left.achievementId.CompareTo(right.achievementId);
|
||||
}
|
||||
|
||||
private void SyncMirrorFlagsToDefinitions()
|
||||
{
|
||||
for (int i = 0; i < definitions.Count; i++)
|
||||
{
|
||||
GlobalAchievementSO definition = definitions[i];
|
||||
if (definition == null || definition.achievementId <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GlobalAchievementStateEntry state;
|
||||
bool unlocked = stateById.TryGetValue(definition.achievementId, out state) && state != null && state.unlocked;
|
||||
definition.SetUnlockedState(unlocked, unlocked && state != null ? state.unlockedUtcTicks : 0L);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a2d505205caa8e044a45b0e2f9742128
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
#if !(UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_STANDALONE_OSX || STEAMWORKS_WIN || STEAMWORKS_LIN_OSX)
|
||||
#define DISABLESTEAMWORKS
|
||||
#endif
|
||||
|
||||
#if !DISABLESTEAMWORKS
|
||||
using Steamworks;
|
||||
#endif
|
||||
|
||||
public static class GlobalAchievementSteamBridge
|
||||
{
|
||||
public static bool TryUnlock(GlobalAchievementSO definition)
|
||||
{
|
||||
if (definition == null || !definition.syncWithSteam || string.IsNullOrWhiteSpace(definition.steamAchievementApiName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#if !DISABLESTEAMWORKS
|
||||
if (!SteamManager.Initialized)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = SteamUserStats.SetAchievement(definition.steamAchievementApiName);
|
||||
if (success)
|
||||
{
|
||||
SteamUserStats.StoreStats();
|
||||
}
|
||||
|
||||
return success;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bf76c26d20df2d74b962e90d0718e503
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f434937296e6d7d4a8b4d68734976ec1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d01a922a7463b36418c80900c9ef668d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+1
@@ -0,0 +1 @@
|
||||
Deprecated. Place GlobalAchievementSO assets in Assets/Resources/so/globalachievments instead.
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f049e141d4f4444a9e650c5f9bbe2ea
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,217 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
public class uAchievementDisplayController : MonoBehaviour
|
||||
{
|
||||
[Header("prefabs")]
|
||||
public GameObject uAchievePrefab;
|
||||
public Transform contentParent;
|
||||
|
||||
[Header("sprite list")]
|
||||
public Sprite[] achievementSprites;
|
||||
|
||||
[Header("global achievement")]
|
||||
public bool initializeGlobalAchievementServiceOnEnable = true;
|
||||
public string runtimeResourcesPath = GlobalAchievementService.DefaultResourcesPath;
|
||||
public string editorAssetFolder = GlobalAchievementService.DefaultEditorAssetFolder;
|
||||
public List<GlobalAchievementSO> scannedGlobalAchievements = new List<GlobalAchievementSO>();
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (initializeGlobalAchievementServiceOnEnable)
|
||||
{
|
||||
GlobalAchievementService.EnsureInstance().LoadDefinitionsFromResources(runtimeResourcesPath);
|
||||
}
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
RefreshAchievementInstances();
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnValidate()
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ScanGlobalAchievementsInEditor();
|
||||
}
|
||||
#endif
|
||||
|
||||
public List<GlobalAchievementSnapshot> GetGlobalAchievementSnapshots()
|
||||
{
|
||||
return GlobalAchievementService.EnsureInstance().GetSnapshots();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
RefreshAchievementInstances();
|
||||
}
|
||||
}
|
||||
|
||||
[ContextMenu("Scan Global Achievements")]
|
||||
public void ScanGlobalAchievements()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
ScanGlobalAchievementsInEditor();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
scannedGlobalAchievements.Clear();
|
||||
GlobalAchievementSO[] loaded = Resources.LoadAll<GlobalAchievementSO>(runtimeResourcesPath);
|
||||
if (loaded == null || loaded.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
scannedGlobalAchievements.AddRange(loaded);
|
||||
}
|
||||
|
||||
public void RefreshAchievementInstances()
|
||||
{
|
||||
if (uAchievePrefab == null || contentParent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ClearAchievementInstances();
|
||||
|
||||
List<GlobalAchievementSnapshot> snapshots = GetGlobalAchievementSnapshots();
|
||||
List<GlobalAchievementSnapshot> displaySnapshots = BuildHighestUnlockedSnapshotsPerSeries(snapshots);
|
||||
displaySnapshots.Sort(CompareSnapshots);
|
||||
for (int i = 0; i < displaySnapshots.Count; i++)
|
||||
{
|
||||
GlobalAchievementSnapshot snapshot = displaySnapshots[i];
|
||||
if (!snapshot.unlocked || snapshot.definition == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GameObject instance = Instantiate(uAchievePrefab, contentParent);
|
||||
uAchievementPrefab controller = instance.GetComponent<uAchievementPrefab>();
|
||||
if (controller == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
controller.Setup(snapshot, GetBottomSprite(snapshot.definition.achievementLevel));
|
||||
}
|
||||
}
|
||||
|
||||
private static List<GlobalAchievementSnapshot> BuildHighestUnlockedSnapshotsPerSeries(List<GlobalAchievementSnapshot> snapshots)
|
||||
{
|
||||
var result = new List<GlobalAchievementSnapshot>();
|
||||
var bestBySeries = new Dictionary<string, GlobalAchievementSnapshot>();
|
||||
|
||||
for (int i = 0; i < snapshots.Count; i++)
|
||||
{
|
||||
GlobalAchievementSnapshot snapshot = snapshots[i];
|
||||
if (!snapshot.unlocked || snapshot.definition == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string seriesKey = !string.IsNullOrWhiteSpace(snapshot.definition.achievementSeriesKey)
|
||||
? snapshot.definition.achievementSeriesKey
|
||||
: snapshot.definition.metricType.ToString();
|
||||
|
||||
GlobalAchievementSnapshot currentBest;
|
||||
if (!bestBySeries.TryGetValue(seriesKey, out currentBest))
|
||||
{
|
||||
bestBySeries[seriesKey] = snapshot;
|
||||
continue;
|
||||
}
|
||||
|
||||
int levelCompare = snapshot.definition.achievementLevel.CompareTo(currentBest.definition.achievementLevel);
|
||||
if (levelCompare > 0)
|
||||
{
|
||||
bestBySeries[seriesKey] = snapshot;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (levelCompare == 0 && snapshot.unlockedUtc < currentBest.unlockedUtc)
|
||||
{
|
||||
bestBySeries[seriesKey] = snapshot;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var pair in bestBySeries)
|
||||
{
|
||||
result.Add(pair.Value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void ClearAchievementInstances()
|
||||
{
|
||||
for (int i = contentParent.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
Transform child = contentParent.GetChild(i);
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
DestroyImmediate(child.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Sprite GetBottomSprite(int level)
|
||||
{
|
||||
if (achievementSprites == null || achievementSprites.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int safeIndex = Mathf.Clamp(level, 0, achievementSprites.Length - 1);
|
||||
return achievementSprites[safeIndex];
|
||||
}
|
||||
|
||||
private static int CompareSnapshots(GlobalAchievementSnapshot left, GlobalAchievementSnapshot right)
|
||||
{
|
||||
int levelCompare = right.definition.achievementLevel.CompareTo(left.definition.achievementLevel);
|
||||
if (levelCompare != 0)
|
||||
{
|
||||
return levelCompare;
|
||||
}
|
||||
|
||||
return left.unlockedUtc.CompareTo(right.unlockedUtc);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void ScanGlobalAchievementsInEditor()
|
||||
{
|
||||
scannedGlobalAchievements.Clear();
|
||||
if (string.IsNullOrWhiteSpace(editorAssetFolder))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string[] guids = AssetDatabase.FindAssets("t:GlobalAchievementSO", new[] { editorAssetFolder });
|
||||
for (int i = 0; i < guids.Length; i++)
|
||||
{
|
||||
string assetPath = AssetDatabase.GUIDToAssetPath(guids[i]);
|
||||
GlobalAchievementSO definition = AssetDatabase.LoadAssetAtPath<GlobalAchievementSO>(assetPath);
|
||||
if (definition != null)
|
||||
{
|
||||
scannedGlobalAchievements.Add(definition);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 987b38d0b9b55094a812dc91a025b9ec
|
||||
@@ -0,0 +1,48 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class uAchievementPrefab : MonoBehaviour
|
||||
{
|
||||
[Header("infos")]
|
||||
public Image aBottomImage;
|
||||
public Image aIcon;
|
||||
public Text aName;
|
||||
public Text aGetDate;
|
||||
public Text aDescription;
|
||||
|
||||
public void Setup(GlobalAchievementSnapshot snapshot, Sprite bottomSprite)
|
||||
{
|
||||
GlobalAchievementSO definition = snapshot.definition;
|
||||
if (definition == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (aName != null)
|
||||
{
|
||||
aName.text = definition.displayName;
|
||||
}
|
||||
|
||||
if (aGetDate != null)
|
||||
{
|
||||
aGetDate.text = snapshot.unlockedDateTimeText;
|
||||
}
|
||||
|
||||
if (aDescription != null)
|
||||
{
|
||||
aDescription.text = definition.description;
|
||||
}
|
||||
|
||||
if (aIcon != null)
|
||||
{
|
||||
aIcon.sprite = definition.icon;
|
||||
aIcon.enabled = definition.icon != null;
|
||||
}
|
||||
|
||||
if (aBottomImage != null)
|
||||
{
|
||||
aBottomImage.sprite = bottomSprite;
|
||||
aBottomImage.enabled = bottomSprite != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d89a2975d23b4e34b9a47d7d51cc583f
|
||||
@@ -0,0 +1,613 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &1549067601785280663
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1807029293904587218}
|
||||
- component: {fileID: 8601725193883887585}
|
||||
- component: {fileID: 7760466027318539489}
|
||||
m_Layer: 5
|
||||
m_Name: mskimg
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &1807029293904587218
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1549067601785280663}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 4286190042205812935}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 220, y: 51}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &8601725193883887585
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1549067601785280663}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &7760466027318539489
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1549067601785280663}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 21300000, guid: 4f084763b23303b4783af7665db60a33, type: 3}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!1 &2057447381607439721
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 6945428158373363852}
|
||||
- component: {fileID: 6954052181346252657}
|
||||
- component: {fileID: 1893507208843142754}
|
||||
m_Layer: 5
|
||||
m_Name: des
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &6945428158373363852
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2057447381607439721}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 452491335617731858}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 17, y: -13.212}
|
||||
m_SizeDelta: {x: 130.172, y: 13.576}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &6954052181346252657
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2057447381607439721}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &1893507208843142754
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2057447381607439721}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_FontData:
|
||||
m_Font: {fileID: 12800000, guid: bc54bd51ea8b84448ba1b65311872862, type: 3}
|
||||
m_FontSize: 13
|
||||
m_FontStyle: 0
|
||||
m_BestFit: 0
|
||||
m_MinSize: 1
|
||||
m_MaxSize: 40
|
||||
m_Alignment: 3
|
||||
m_AlignByGeometry: 0
|
||||
m_RichText: 1
|
||||
m_HorizontalOverflow: 0
|
||||
m_VerticalOverflow: 0
|
||||
m_LineSpacing: 1
|
||||
m_Text: "\u7B80\u4ECB\u6700\u591A\u4E5F\u5C31\u5341\u4E2A\u5B57\u6574"
|
||||
--- !u!1 &3853073308254161695
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 6155334586977510247}
|
||||
- component: {fileID: 4151510886845045432}
|
||||
m_Layer: 5
|
||||
m_Name: uAchievementPrefab
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &6155334586977510247
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3853073308254161695}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 452491335617731858}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 205, y: 55}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &4151510886845045432
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3853073308254161695}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: d89a2975d23b4e34b9a47d7d51cc583f, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
aBottomImage: {fileID: 7760466027318539489}
|
||||
aIcon: {fileID: 408771293922604001}
|
||||
aName: {fileID: 6612220277252403227}
|
||||
aGetDate: {fileID: 8035948722274830026}
|
||||
aDescription: {fileID: 1893507208843142754}
|
||||
--- !u!1 &4178773801772122773
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1685732796308453693}
|
||||
- component: {fileID: 2917781354974279582}
|
||||
- component: {fileID: 6612220277252403227}
|
||||
m_Layer: 5
|
||||
m_Name: title
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &1685732796308453693
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4178773801772122773}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 452491335617731858}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 17, y: 11.528}
|
||||
m_SizeDelta: {x: 130.172, y: 16.944}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &2917781354974279582
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4178773801772122773}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &6612220277252403227
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4178773801772122773}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_FontData:
|
||||
m_Font: {fileID: 12800000, guid: dfa10de3e2846524089b5073175cd151, type: 3}
|
||||
m_FontSize: 16
|
||||
m_FontStyle: 0
|
||||
m_BestFit: 0
|
||||
m_MinSize: 1
|
||||
m_MaxSize: 40
|
||||
m_Alignment: 0
|
||||
m_AlignByGeometry: 0
|
||||
m_RichText: 1
|
||||
m_HorizontalOverflow: 0
|
||||
m_VerticalOverflow: 0
|
||||
m_LineSpacing: 1
|
||||
m_Text: "\u6210\u5C31\u540D\u5B57\u6700\u591A\u516B\u5B57"
|
||||
--- !u!1 &4442388964663260224
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 9126304612977524600}
|
||||
- component: {fileID: 3597828589541724163}
|
||||
- component: {fileID: 408771293922604001}
|
||||
m_Layer: 5
|
||||
m_Name: prof
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &9126304612977524600
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4442388964663260224}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 452491335617731858}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: -74.9, y: 0}
|
||||
m_SizeDelta: {x: 40, y: 40}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &3597828589541724163
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4442388964663260224}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &408771293922604001
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4442388964663260224}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 21300000, guid: 1a075b65c796bfc4aa14b8e5b412d38a, type: 3}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!1 &6911097261879657878
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 452491335617731858}
|
||||
- component: {fileID: 9016916423687245873}
|
||||
- component: {fileID: 661185216835996425}
|
||||
m_Layer: 5
|
||||
m_Name: boarder
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &452491335617731858
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6911097261879657878}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 4286190042205812935}
|
||||
- {fileID: 9126304612977524600}
|
||||
- {fileID: 1685732796308453693}
|
||||
- {fileID: 3458573061627051884}
|
||||
- {fileID: 6945428158373363852}
|
||||
m_Father: {fileID: 6155334586977510247}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 205, y: 55}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &9016916423687245873
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6911097261879657878}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &661185216835996425
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6911097261879657878}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 0}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!1 &8456038322340636303
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 4286190042205812935}
|
||||
- component: {fileID: 7137554977806279391}
|
||||
- component: {fileID: 9201936408420364057}
|
||||
- component: {fileID: 6566656222579036843}
|
||||
m_Layer: 5
|
||||
m_Name: btm
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &4286190042205812935
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8456038322340636303}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 1807029293904587218}
|
||||
m_Father: {fileID: 452491335617731858}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 200, y: 50}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &7137554977806279391
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8456038322340636303}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &9201936408420364057
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8456038322340636303}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 0}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!114 &6566656222579036843
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8456038322340636303}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 31a19414c41e5ae4aae2af33fee712f6, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_ShowMaskGraphic: 1
|
||||
--- !u!1 &8804789641353309784
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 3458573061627051884}
|
||||
- component: {fileID: 7917957300619336252}
|
||||
- component: {fileID: 8035948722274830026}
|
||||
m_Layer: 5
|
||||
m_Name: date
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &3458573061627051884
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8804789641353309784}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 452491335617731858}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 17, y: -6.3760014}
|
||||
m_SizeDelta: {x: 130.172, y: 18.864}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &7917957300619336252
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8804789641353309784}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &8035948722274830026
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8804789641353309784}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_FontData:
|
||||
m_Font: {fileID: 12800000, guid: bc54bd51ea8b84448ba1b65311872862, type: 3}
|
||||
m_FontSize: 10
|
||||
m_FontStyle: 0
|
||||
m_BestFit: 0
|
||||
m_MinSize: 1
|
||||
m_MaxSize: 40
|
||||
m_Alignment: 0
|
||||
m_AlignByGeometry: 0
|
||||
m_RichText: 1
|
||||
m_HorizontalOverflow: 0
|
||||
m_VerticalOverflow: 0
|
||||
m_LineSpacing: 1
|
||||
m_Text: '2026-03-11 20: 20'
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c42cf5fea198c5c408a5c7a8f17809b0
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,6 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class uHonor : MonoBehaviour
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: afa19287d1c47e74b84c21d4eacd0502
|
||||
@@ -0,0 +1,13 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class uLevel : MonoBehaviour
|
||||
{
|
||||
public Text uLevelText;
|
||||
public Slider uLevelSlider;
|
||||
public Text uLProgress;
|
||||
public Text uL_curr;
|
||||
public Text uL_post;
|
||||
|
||||
public Button view_levelDetail;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 274c488de7ad5e14194f988f670c8f06
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 65ffb3ca4910d0b459f6dddc6d8837c3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,431 @@
|
||||
using System.Collections.Generic;
|
||||
using EasyChart;
|
||||
using EasyChart.UGUI;
|
||||
using UnityEngine;
|
||||
|
||||
public class URadarChartController : MonoBehaviour
|
||||
{
|
||||
private const int AxisCount = 6;
|
||||
|
||||
[Header("Chart")]
|
||||
public UGUIChartBridge chartBridge;
|
||||
public bool rebuildOnEnable = true;
|
||||
public bool showReferenceSeries = false;
|
||||
public int enforcedSortOrder = 0;
|
||||
|
||||
[Header("Series Names")]
|
||||
public string mainSeriesName = "Player";
|
||||
public string referenceSeriesName = "Reference";
|
||||
|
||||
[Header("Axes")]
|
||||
public List<URadarAxisEntry> axes = new List<URadarAxisEntry>
|
||||
{
|
||||
new URadarAxisEntry { label = "A", value = 72f, referenceValue = 60f },
|
||||
new URadarAxisEntry { label = "B", value = 55f, referenceValue = 60f },
|
||||
new URadarAxisEntry { label = "C", value = 68f, referenceValue = 60f },
|
||||
new URadarAxisEntry { label = "D", value = 49f, referenceValue = 60f },
|
||||
new URadarAxisEntry { label = "E", value = 80f, referenceValue = 60f },
|
||||
new URadarAxisEntry { label = "F", value = 61f, referenceValue = 60f }
|
||||
};
|
||||
|
||||
[Header("Range")]
|
||||
public float minValue = 0f;
|
||||
public float maxValue = 100f;
|
||||
public int splitCount = 5;
|
||||
|
||||
[Header("Grid")]
|
||||
public bool overrideGridStyle = false;
|
||||
public Color gridLineColor = new Color32(255, 255, 255, 180);
|
||||
public Color outerGridLineColor = Color.white;
|
||||
public float gridLineWidth = 2f;
|
||||
|
||||
[Header("Labels")]
|
||||
public bool overrideLabelStyle = false;
|
||||
public bool overrideLabelFont = false;
|
||||
public Font axisLabelFont;
|
||||
public int axisLabelFontSize = 18;
|
||||
public Color axisLabelColor = Color.white;
|
||||
public Vector2 axisLabelOffset = Vector2.zero;
|
||||
|
||||
[Header("Style")]
|
||||
public bool overrideSeriesStyle = true;
|
||||
public Color mainStrokeColor = new Color32(82, 126, 255, 255);
|
||||
public Color mainFillColor = new Color(82f / 255f, 126f / 255f, 255f / 255f, 0.28f);
|
||||
public Color mainPointColor = Color.white;
|
||||
public Color referenceStrokeColor = new Color32(72, 229, 229, 255);
|
||||
public Color referenceFillColor = new Color(72f / 255f, 229f / 255f, 229f / 255f, 0.22f);
|
||||
public Color referencePointColor = Color.white;
|
||||
public float radarPlotPadding = 8f;
|
||||
|
||||
private ChartProfile sourceProfile;
|
||||
private ChartProfile runtimeProfile;
|
||||
private ChartTheme runtimeTheme;
|
||||
private bool rebuildQueued;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
showReferenceSeries = false;
|
||||
NormalizeAxes();
|
||||
QueueRebuild();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
showReferenceSeries = false;
|
||||
NormalizeAxes();
|
||||
if (rebuildOnEnable)
|
||||
{
|
||||
QueueRebuild();
|
||||
}
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (rebuildQueued)
|
||||
{
|
||||
TryRebuild();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
showReferenceSeries = false;
|
||||
NormalizeAxes();
|
||||
axisLabelFontSize = Mathf.Max(1, axisLabelFontSize);
|
||||
gridLineWidth = Mathf.Max(0.1f, gridLineWidth);
|
||||
QueueRebuild();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (runtimeProfile != null)
|
||||
{
|
||||
DestroyImmediate(runtimeProfile);
|
||||
runtimeProfile = null;
|
||||
}
|
||||
|
||||
if (runtimeTheme != null)
|
||||
{
|
||||
DestroyImmediate(runtimeTheme);
|
||||
runtimeTheme = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void QueueRebuild()
|
||||
{
|
||||
rebuildQueued = true;
|
||||
}
|
||||
|
||||
[ContextMenu("Rebuild Radar Chart")]
|
||||
public void RebuildNow()
|
||||
{
|
||||
QueueRebuild();
|
||||
TryRebuild();
|
||||
}
|
||||
|
||||
public void SetAxisLabel(int index, string label)
|
||||
{
|
||||
if (!TryGetAxis(index, out URadarAxisEntry axis))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
axis.label = string.IsNullOrWhiteSpace(label) ? $"Axis {index + 1}" : label.Trim();
|
||||
QueueRebuild();
|
||||
}
|
||||
|
||||
public void SetAxisValue(int index, float value)
|
||||
{
|
||||
if (!TryGetAxis(index, out URadarAxisEntry axis))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
axis.value = Mathf.Clamp(value, minValue, maxValue);
|
||||
QueueRebuild();
|
||||
}
|
||||
|
||||
public void SetReferenceAxisValue(int index, float value)
|
||||
{
|
||||
if (!TryGetAxis(index, out URadarAxisEntry axis))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
axis.referenceValue = Mathf.Clamp(value, minValue, maxValue);
|
||||
QueueRebuild();
|
||||
}
|
||||
|
||||
private bool TryGetAxis(int index, out URadarAxisEntry axis)
|
||||
{
|
||||
NormalizeAxes();
|
||||
if (index < 0 || index >= axes.Count)
|
||||
{
|
||||
axis = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
axis = axes[index];
|
||||
return axis != null;
|
||||
}
|
||||
|
||||
private void TryRebuild()
|
||||
{
|
||||
if (chartBridge == null || chartBridge.Profile == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (sourceProfile == null)
|
||||
{
|
||||
sourceProfile = chartBridge.Profile;
|
||||
}
|
||||
|
||||
if (sourceProfile == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureRuntimeProfile();
|
||||
EnsureBridgePriority();
|
||||
ConfigureRuntimeProfile();
|
||||
chartBridge.Refresh();
|
||||
ApplyRuntimeTheme();
|
||||
rebuildQueued = false;
|
||||
}
|
||||
|
||||
private void EnsureRuntimeProfile()
|
||||
{
|
||||
if (runtimeProfile != null)
|
||||
{
|
||||
if (!ReferenceEquals(chartBridge.Profile, runtimeProfile))
|
||||
{
|
||||
chartBridge.Profile = runtimeProfile;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
runtimeProfile = Instantiate(sourceProfile);
|
||||
runtimeProfile.name = sourceProfile.name + "_URadar_Runtime";
|
||||
runtimeProfile.hideFlags = HideFlags.DontSave;
|
||||
chartBridge.Profile = runtimeProfile;
|
||||
}
|
||||
|
||||
private void EnsureBridgePriority()
|
||||
{
|
||||
if (chartBridge != null && chartBridge.SortOrder < enforcedSortOrder)
|
||||
{
|
||||
chartBridge.SortOrder = enforcedSortOrder;
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureRuntimeProfile()
|
||||
{
|
||||
NormalizeAxes();
|
||||
|
||||
runtimeProfile.coordinateSystem = CoordinateSystemType.Polar2D;
|
||||
runtimeProfile.EnsureRuntimeData();
|
||||
|
||||
runtimeProfile.polarAxes.angleAxis.labels = BuildLabels();
|
||||
runtimeProfile.polarAxes.angleAxis.visible = true;
|
||||
runtimeProfile.polarAxes.angleAxis.showLabels = true;
|
||||
if (runtimeProfile.polarAxes.angleAxis.labelStyle == null)
|
||||
{
|
||||
runtimeProfile.polarAxes.angleAxis.labelStyle = new LabelStyleSettings();
|
||||
}
|
||||
runtimeProfile.polarAxes.angleAxis.labelStyle.enabled = true;
|
||||
|
||||
runtimeProfile.polarAxes.radiusAxis.visible = true;
|
||||
runtimeProfile.polarAxes.radiusAxis.autoRangeMin = false;
|
||||
runtimeProfile.polarAxes.radiusAxis.autoRangeMax = false;
|
||||
runtimeProfile.polarAxes.radiusAxis.minValue = minValue;
|
||||
runtimeProfile.polarAxes.radiusAxis.maxValue = Mathf.Max(minValue + 1f, maxValue);
|
||||
runtimeProfile.polarAxes.radiusAxis.splitCount = Mathf.Max(2, splitCount);
|
||||
if (runtimeProfile.polarAxes.radiusAxis.labelStyle == null)
|
||||
{
|
||||
runtimeProfile.polarAxes.radiusAxis.labelStyle = new LabelStyleSettings();
|
||||
}
|
||||
runtimeProfile.polarAxes.radiusAxis.labelStyle.enabled = runtimeProfile.polarAxes.radiusAxis.showLabels;
|
||||
|
||||
if (overrideGridStyle)
|
||||
{
|
||||
runtimeProfile.polarAxes.angleAxis.color = gridLineColor;
|
||||
runtimeProfile.polarAxes.angleAxis.width = gridLineWidth;
|
||||
runtimeProfile.polarAxes.radiusAxis.color = gridLineColor;
|
||||
runtimeProfile.polarAxes.radiusAxis.edgeColor = outerGridLineColor;
|
||||
runtimeProfile.polarAxes.radiusAxis.width = gridLineWidth;
|
||||
}
|
||||
|
||||
if (overrideLabelStyle)
|
||||
{
|
||||
runtimeProfile.polarAxes.angleAxis.labelColor = axisLabelColor;
|
||||
runtimeProfile.polarAxes.angleAxis.fontSize = axisLabelFontSize;
|
||||
runtimeProfile.polarAxes.angleAxis.labelOffset = axisLabelOffset;
|
||||
runtimeProfile.polarAxes.angleAxis.labelStyle.fontSize = axisLabelFontSize;
|
||||
runtimeProfile.polarAxes.angleAxis.labelStyle.color = axisLabelColor;
|
||||
runtimeProfile.polarAxes.angleAxis.labelStyle.offset = axisLabelOffset;
|
||||
|
||||
runtimeProfile.polarAxes.radiusAxis.labelColor = axisLabelColor;
|
||||
runtimeProfile.polarAxes.radiusAxis.fontSize = axisLabelFontSize;
|
||||
runtimeProfile.polarAxes.radiusAxis.labelStyle.fontSize = axisLabelFontSize;
|
||||
runtimeProfile.polarAxes.radiusAxis.labelStyle.color = axisLabelColor;
|
||||
}
|
||||
|
||||
if (runtimeProfile.series == null)
|
||||
{
|
||||
runtimeProfile.series = new List<Serie>();
|
||||
}
|
||||
|
||||
int targetSeriesCount = 1;
|
||||
while (runtimeProfile.series.Count < targetSeriesCount)
|
||||
{
|
||||
runtimeProfile.series.Add(new Serie());
|
||||
}
|
||||
|
||||
while (runtimeProfile.series.Count > targetSeriesCount)
|
||||
{
|
||||
runtimeProfile.series.RemoveAt(runtimeProfile.series.Count - 1);
|
||||
}
|
||||
|
||||
ConfigureSeries(runtimeProfile.series[0], mainSeriesName, true);
|
||||
|
||||
runtimeProfile.EnsureRuntimeData();
|
||||
}
|
||||
|
||||
private void ApplyRuntimeTheme()
|
||||
{
|
||||
if (chartBridge == null || chartBridge.ChartElement == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!overrideLabelFont || axisLabelFont == null)
|
||||
{
|
||||
chartBridge.ChartElement.Theme = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (runtimeTheme == null)
|
||||
{
|
||||
runtimeTheme = ScriptableObject.CreateInstance<ChartTheme>();
|
||||
runtimeTheme.name = "URadar_RuntimeTheme";
|
||||
runtimeTheme.hideFlags = HideFlags.DontSave;
|
||||
}
|
||||
|
||||
runtimeTheme.primaryFont = axisLabelFont;
|
||||
runtimeTheme.axisFontSize = axisLabelFontSize;
|
||||
chartBridge.ChartElement.Theme = runtimeTheme;
|
||||
}
|
||||
|
||||
private void ConfigureSeries(Serie serie, string serieName, bool isMainSeries)
|
||||
{
|
||||
if (serie == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
serie.name = string.IsNullOrWhiteSpace(serieName) ? (isMainSeries ? "Player" : "Reference") : serieName.Trim();
|
||||
serie.visible = true;
|
||||
if (serie.type != SerieType.Radar)
|
||||
{
|
||||
serie.SetType(SerieType.Radar);
|
||||
}
|
||||
|
||||
if (!(serie.settings is RadarSettings radarSettings))
|
||||
{
|
||||
radarSettings = new RadarSettings();
|
||||
serie.settings = radarSettings;
|
||||
}
|
||||
|
||||
radarSettings.radar.innerRadius = 0f;
|
||||
radarSettings.radar.outerRadius = 0f;
|
||||
radarSettings.radar.plot.padding = radarPlotPadding;
|
||||
|
||||
radarSettings.area.show = true;
|
||||
radarSettings.point.show = true;
|
||||
|
||||
if (overrideSeriesStyle)
|
||||
{
|
||||
radarSettings.stroke.color = isMainSeries ? mainStrokeColor : referenceStrokeColor;
|
||||
radarSettings.stroke.width = isMainSeries ? 2.5f : 2f;
|
||||
radarSettings.area.textureFill.color = isMainSeries ? mainFillColor : referenceFillColor;
|
||||
radarSettings.point.textureFill.color = isMainSeries ? mainPointColor : referencePointColor;
|
||||
radarSettings.point.size = isMainSeries ? 8f : 7f;
|
||||
}
|
||||
|
||||
if (serie.labelSettings != null)
|
||||
{
|
||||
serie.labelSettings.enabled = false;
|
||||
}
|
||||
|
||||
if (serie.seriesData == null)
|
||||
{
|
||||
serie.seriesData = new List<SeriesData>();
|
||||
}
|
||||
else
|
||||
{
|
||||
serie.seriesData.Clear();
|
||||
}
|
||||
|
||||
for (int i = 0; i < AxisCount; i++)
|
||||
{
|
||||
URadarAxisEntry axis = axes[i];
|
||||
float value = isMainSeries ? axis.value : axis.referenceValue;
|
||||
serie.seriesData.Add(new SeriesData
|
||||
{
|
||||
id = (isMainSeries ? "main_" : "ref_") + i,
|
||||
name = axis.label,
|
||||
x = i,
|
||||
value = Mathf.Clamp(value, minValue, maxValue)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private List<string> BuildLabels()
|
||||
{
|
||||
List<string> labels = new List<string>(AxisCount);
|
||||
for (int i = 0; i < AxisCount; i++)
|
||||
{
|
||||
string label = axes[i] != null ? axes[i].label : null;
|
||||
labels.Add(string.IsNullOrWhiteSpace(label) ? $"Axis {i + 1}" : label.Trim());
|
||||
}
|
||||
|
||||
return labels;
|
||||
}
|
||||
|
||||
private void NormalizeAxes()
|
||||
{
|
||||
if (axes == null)
|
||||
{
|
||||
axes = new List<URadarAxisEntry>();
|
||||
}
|
||||
|
||||
while (axes.Count < AxisCount)
|
||||
{
|
||||
axes.Add(new URadarAxisEntry
|
||||
{
|
||||
label = $"Axis {axes.Count + 1}",
|
||||
value = 50f,
|
||||
referenceValue = 60f
|
||||
});
|
||||
}
|
||||
|
||||
if (axes.Count > AxisCount)
|
||||
{
|
||||
axes.RemoveRange(AxisCount, axes.Count - AxisCount);
|
||||
}
|
||||
|
||||
for (int i = 0; i < axes.Count; i++)
|
||||
{
|
||||
if (axes[i] == null)
|
||||
{
|
||||
axes[i] = new URadarAxisEntry
|
||||
{
|
||||
label = $"Axis {i + 1}",
|
||||
value = 50f,
|
||||
referenceValue = 60f
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4fbf5b991cb51dd459154eacb5c2a0c0
|
||||
@@ -0,0 +1,9 @@
|
||||
using UnityEngine;
|
||||
|
||||
[System.Serializable]
|
||||
public class URadarAxisEntry
|
||||
{
|
||||
public string label = "Axis";
|
||||
[Range(0f, 100f)] public float value = 50f;
|
||||
[Range(0f, 100f)] public float referenceValue = 60f;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4ba52b8221926d44ea14998106dd8bcb
|
||||
@@ -0,0 +1,579 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using EasyChart.UGUI;
|
||||
using UnityEngine;
|
||||
|
||||
public class uBehaviourRaderController : MonoBehaviour
|
||||
{
|
||||
private const int AxisActive = 0;
|
||||
private const int AxisChart = 1;
|
||||
private const int AxisFormation = 2;
|
||||
private const int AxisAcc = 3;
|
||||
private const int AxisRks = 4;
|
||||
private const int AxisRank = 5;
|
||||
|
||||
[Header("Objects")]
|
||||
public GameObject uRaderObj;
|
||||
public URadarChartController radarChartController;
|
||||
public UGUIChartBridge chartBridge;
|
||||
public RankConfig rankConfig;
|
||||
|
||||
[Header("Async")]
|
||||
public bool reloadOnEnable = true;
|
||||
public int maxInitAttempts = 20;
|
||||
public float retryDelaySeconds = 0.15f;
|
||||
|
||||
[Header("Radar Baseline")]
|
||||
[Range(0f, 100f)] public float neutralAxisValue = 50f;
|
||||
[Range(0f, 100f)] public float referenceAxisValue = 60f;
|
||||
|
||||
[Header("Score Targets")]
|
||||
public float chartScoreTarget = 1000000f;
|
||||
public float idolScoreTarget = 1000000f;
|
||||
|
||||
[Header("Activity Tuning")]
|
||||
public float activeRecencyHalfLifeDays = 7f;
|
||||
public float activeGapScaleDays = 2f;
|
||||
public int activeConfidenceFullCount = 12;
|
||||
|
||||
[Header("Performance Tuning")]
|
||||
public int topSampleCount = 10;
|
||||
public int performanceConfidenceFullCount = 20;
|
||||
public float normalizedSpreadPenalty = 0.30f;
|
||||
|
||||
private Coroutine loadRoutine;
|
||||
private bool settingsSubscribed;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
BeginLoad();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
BindSettingsVisibility();
|
||||
if (reloadOnEnable && Application.isPlaying)
|
||||
{
|
||||
BeginLoad();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
UnsubscribeSettingsVisibility();
|
||||
if (loadRoutine != null)
|
||||
{
|
||||
StopCoroutine(loadRoutine);
|
||||
loadRoutine = null;
|
||||
}
|
||||
}
|
||||
|
||||
[ContextMenu("Refresh Behaviour Radar")]
|
||||
public void RefreshNow()
|
||||
{
|
||||
BeginLoad();
|
||||
}
|
||||
|
||||
private void BeginLoad()
|
||||
{
|
||||
if (!isActiveAndEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadRoutine != null)
|
||||
{
|
||||
StopCoroutine(loadRoutine);
|
||||
}
|
||||
|
||||
loadRoutine = StartCoroutine(LoadAndApplyRoutine());
|
||||
}
|
||||
|
||||
private IEnumerator LoadAndApplyRoutine()
|
||||
{
|
||||
for (int attempt = 0; attempt < maxInitAttempts; attempt++)
|
||||
{
|
||||
ResolveDependencies();
|
||||
|
||||
if (radarChartController != null &&
|
||||
chartBridge != null &&
|
||||
chartBridge.Profile != null)
|
||||
{
|
||||
ApplySettingsVisibility(btmandtopController.CurrentSettingsVisible);
|
||||
ApplySummary(BuildSummary(RecentPlayHistoryStore.GetRecords()));
|
||||
loadRoutine = null;
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return new WaitForSecondsRealtime(retryDelaySeconds);
|
||||
}
|
||||
|
||||
ApplySummary(BuildEmptySummary());
|
||||
loadRoutine = null;
|
||||
}
|
||||
|
||||
private void ResolveDependencies()
|
||||
{
|
||||
if (uRaderObj == null)
|
||||
{
|
||||
uRaderObj = gameObject;
|
||||
}
|
||||
|
||||
if (radarChartController == null)
|
||||
{
|
||||
radarChartController = GetComponent<URadarChartController>();
|
||||
if (radarChartController == null)
|
||||
{
|
||||
radarChartController = GetComponentInChildren<URadarChartController>(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (chartBridge == null && radarChartController != null)
|
||||
{
|
||||
chartBridge = radarChartController.chartBridge;
|
||||
}
|
||||
|
||||
if (chartBridge == null)
|
||||
{
|
||||
chartBridge = GetComponent<UGUIChartBridge>();
|
||||
if (chartBridge == null)
|
||||
{
|
||||
chartBridge = GetComponentInChildren<UGUIChartBridge>(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (rankConfig == null)
|
||||
{
|
||||
RankConfig[] configs = Resources.LoadAll<RankConfig>(string.Empty);
|
||||
if (configs != null && configs.Length > 0)
|
||||
{
|
||||
rankConfig = configs[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void BindSettingsVisibility()
|
||||
{
|
||||
if (settingsSubscribed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
btmandtopController.GlobalSettingsVisibilityChanged += HandleSettingsVisibilityChanged;
|
||||
settingsSubscribed = true;
|
||||
}
|
||||
|
||||
private void UnsubscribeSettingsVisibility()
|
||||
{
|
||||
if (!settingsSubscribed)
|
||||
{
|
||||
settingsSubscribed = false;
|
||||
return;
|
||||
}
|
||||
|
||||
btmandtopController.GlobalSettingsVisibilityChanged -= HandleSettingsVisibilityChanged;
|
||||
settingsSubscribed = false;
|
||||
}
|
||||
|
||||
private void HandleSettingsVisibilityChanged(bool visible)
|
||||
{
|
||||
ApplySettingsVisibility(visible);
|
||||
}
|
||||
|
||||
private void ApplySettingsVisibility(bool settingsVisible)
|
||||
{
|
||||
if (uRaderObj == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (uRaderObj == gameObject)
|
||||
{
|
||||
Debug.LogWarning("[uBehaviourRaderController] uRaderObj should not be the same object as the controller host. Assign the radar display root instead.");
|
||||
return;
|
||||
}
|
||||
|
||||
uRaderObj.SetActive(!settingsVisible);
|
||||
}
|
||||
|
||||
private void ApplySummary(BehaviourRadarSummary summary)
|
||||
{
|
||||
if (radarChartController == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
radarChartController.SetAxisLabel(AxisActive, "\u6d3b\u8dc3");
|
||||
radarChartController.SetAxisLabel(AxisChart, "\u8c31\u9762");
|
||||
radarChartController.SetAxisLabel(AxisFormation, "\u7f16\u961f");
|
||||
radarChartController.SetAxisLabel(AxisAcc, "ACC");
|
||||
radarChartController.SetAxisLabel(AxisRks, "RKS");
|
||||
radarChartController.SetAxisLabel(AxisRank, "\u8bc4\u7ea7");
|
||||
|
||||
radarChartController.SetAxisValue(AxisActive, summary.active);
|
||||
radarChartController.SetAxisValue(AxisChart, summary.chart);
|
||||
radarChartController.SetAxisValue(AxisFormation, summary.formation);
|
||||
radarChartController.SetAxisValue(AxisAcc, summary.acc);
|
||||
radarChartController.SetAxisValue(AxisRks, summary.rks);
|
||||
radarChartController.SetAxisValue(AxisRank, summary.rating);
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
radarChartController.SetReferenceAxisValue(i, referenceAxisValue);
|
||||
}
|
||||
|
||||
radarChartController.RebuildNow();
|
||||
}
|
||||
|
||||
private BehaviourRadarSummary BuildSummary(IReadOnlyList<RecentPlayRecord> records)
|
||||
{
|
||||
BehaviourRadarSummary summary = BuildEmptySummary();
|
||||
if (records == null || records.Count == 0)
|
||||
{
|
||||
return summary;
|
||||
}
|
||||
|
||||
List<RecordSample> samples = BuildSamples(records);
|
||||
if (samples.Count == 0)
|
||||
{
|
||||
return summary;
|
||||
}
|
||||
|
||||
summary.active = ComputeActivityScore(samples);
|
||||
summary.chart = ComputeChartScore(samples);
|
||||
summary.formation = ComputeFormationScore(samples);
|
||||
summary.acc = ComputeAccuracyScore(samples);
|
||||
summary.rks = ComputeRksScore(samples);
|
||||
summary.rating = ComputeRatingScore(samples);
|
||||
return summary;
|
||||
}
|
||||
|
||||
private BehaviourRadarSummary BuildEmptySummary()
|
||||
{
|
||||
return new BehaviourRadarSummary
|
||||
{
|
||||
active = neutralAxisValue,
|
||||
chart = neutralAxisValue,
|
||||
formation = neutralAxisValue,
|
||||
acc = neutralAxisValue,
|
||||
rks = neutralAxisValue,
|
||||
rating = neutralAxisValue
|
||||
};
|
||||
}
|
||||
|
||||
private List<RecordSample> BuildSamples(IReadOnlyList<RecentPlayRecord> records)
|
||||
{
|
||||
List<RecordSample> samples = new List<RecordSample>(records.Count);
|
||||
DateTime now = DateTime.Now;
|
||||
|
||||
for (int i = 0; i < records.Count; i++)
|
||||
{
|
||||
RecentPlayRecord record = records[i];
|
||||
if (record == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
RecordSample sample = new RecordSample();
|
||||
sample.weight = Mathf.Exp(-i / 24f);
|
||||
sample.playedAtLocal = TryParseLocalTime(record, now, out sample.hasPlayableTime);
|
||||
sample.accuracy = Mathf.Clamp(record.accuracy, 0f, 100f);
|
||||
sample.rks = Mathf.Clamp(record.srks, 0f, 100f);
|
||||
|
||||
if (record.hasScoreBreakdown)
|
||||
{
|
||||
sample.chartScore = Mathf.Max(0, record.chartScore);
|
||||
sample.idolScore = Mathf.Max(0, record.idolScore);
|
||||
sample.hasScoreBreakdown = true;
|
||||
}
|
||||
|
||||
if (record.hasRankMarker && record.rankTierCount > 0)
|
||||
{
|
||||
sample.rankTierCount = record.rankTierCount;
|
||||
sample.rankIndex = Mathf.Clamp(record.rankIndex, 0, sample.rankTierCount);
|
||||
sample.hasRankMarker = true;
|
||||
}
|
||||
else if (record.scoreReadable)
|
||||
{
|
||||
sample.rankTierCount = GetRankTierCount();
|
||||
sample.rankIndex = CalculateRankIndex(record.totalScore);
|
||||
sample.hasRankMarker = sample.rankTierCount > 0;
|
||||
}
|
||||
|
||||
samples.Add(sample);
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
private DateTime TryParseLocalTime(RecentPlayRecord record, DateTime now, out bool hasValue)
|
||||
{
|
||||
hasValue = false;
|
||||
|
||||
if (record != null && !string.IsNullOrWhiteSpace(record.playedAtUtc))
|
||||
{
|
||||
if (DateTime.TryParse(record.playedAtUtc, null, System.Globalization.DateTimeStyles.RoundtripKind, out DateTime utcTime))
|
||||
{
|
||||
hasValue = true;
|
||||
return utcTime.ToLocalTime();
|
||||
}
|
||||
}
|
||||
|
||||
if (record != null && !string.IsNullOrWhiteSpace(record.playedAt))
|
||||
{
|
||||
if (DateTime.TryParseExact(
|
||||
record.playedAt,
|
||||
"MM-dd, HH:mm",
|
||||
System.Globalization.CultureInfo.InvariantCulture,
|
||||
System.Globalization.DateTimeStyles.None,
|
||||
out DateTime partial))
|
||||
{
|
||||
DateTime candidate = new DateTime(now.Year, partial.Month, partial.Day, partial.Hour, partial.Minute, 0);
|
||||
if (candidate > now.AddDays(1))
|
||||
{
|
||||
candidate = candidate.AddYears(-1);
|
||||
}
|
||||
|
||||
hasValue = true;
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return now;
|
||||
}
|
||||
|
||||
private float ComputeActivityScore(List<RecordSample> samples)
|
||||
{
|
||||
List<DateTime> playedDays = new List<DateTime>();
|
||||
for (int i = 0; i < samples.Count; i++)
|
||||
{
|
||||
if (!samples[i].hasPlayableTime)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
playedDays.Add(samples[i].playedAtLocal.Date);
|
||||
}
|
||||
|
||||
if (playedDays.Count == 0)
|
||||
{
|
||||
return neutralAxisValue;
|
||||
}
|
||||
|
||||
playedDays.Sort((a, b) => b.CompareTo(a));
|
||||
|
||||
int uniqueDays = 0;
|
||||
DateTime previousDay = DateTime.MinValue;
|
||||
float totalGapDays = 0f;
|
||||
int gapCount = 0;
|
||||
DateTime newest = playedDays[0];
|
||||
DateTime oldest = playedDays[playedDays.Count - 1];
|
||||
|
||||
for (int i = 0; i < playedDays.Count; i++)
|
||||
{
|
||||
DateTime day = playedDays[i];
|
||||
if (day == previousDay)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (previousDay != DateTime.MinValue)
|
||||
{
|
||||
totalGapDays += Mathf.Max(0f, (float)(previousDay - day).TotalDays);
|
||||
gapCount++;
|
||||
}
|
||||
|
||||
previousDay = day;
|
||||
uniqueDays++;
|
||||
}
|
||||
|
||||
float spanDays = Mathf.Max(1f, (float)(newest - oldest).TotalDays + 1f);
|
||||
float density = Mathf.Clamp01(uniqueDays / spanDays);
|
||||
float averageGap = gapCount > 0 ? totalGapDays / gapCount : 0f;
|
||||
float cadence = Mathf.Exp(-averageGap / Mathf.Max(0.25f, activeGapScaleDays));
|
||||
float recencyDays = Mathf.Max(0f, (float)(DateTime.Now - newest).TotalDays);
|
||||
float recency = Mathf.Exp(-recencyDays / Mathf.Max(0.25f, activeRecencyHalfLifeDays));
|
||||
|
||||
float raw = (density * 0.5f + cadence * 0.3f + recency * 0.2f) * 100f;
|
||||
return BlendWithNeutral(raw, uniqueDays, activeConfidenceFullCount);
|
||||
}
|
||||
|
||||
private float ComputeChartScore(List<RecordSample> samples)
|
||||
{
|
||||
return ComputeNormalizedPerformanceScore(
|
||||
samples,
|
||||
sample => sample.hasScoreBreakdown,
|
||||
sample => Mathf.Clamp01(sample.chartScore / Mathf.Max(1f, chartScoreTarget)));
|
||||
}
|
||||
|
||||
private float ComputeFormationScore(List<RecordSample> samples)
|
||||
{
|
||||
return ComputeNormalizedPerformanceScore(
|
||||
samples,
|
||||
sample => sample.hasScoreBreakdown,
|
||||
sample => Mathf.Clamp01(sample.idolScore / Mathf.Max(1f, idolScoreTarget)));
|
||||
}
|
||||
|
||||
private float ComputeAccuracyScore(List<RecordSample> samples)
|
||||
{
|
||||
return ComputeNormalizedPerformanceScore(
|
||||
samples,
|
||||
sample => sample.accuracy > 0f,
|
||||
sample => Mathf.Clamp01(sample.accuracy / 100f));
|
||||
}
|
||||
|
||||
private float ComputeRksScore(List<RecordSample> samples)
|
||||
{
|
||||
return ComputeNormalizedPerformanceScore(
|
||||
samples,
|
||||
sample => sample.rks > 0f,
|
||||
sample => Mathf.Clamp01(sample.rks / 100f));
|
||||
}
|
||||
|
||||
private float ComputeRatingScore(List<RecordSample> samples)
|
||||
{
|
||||
int tierCount = GetRankTierCount();
|
||||
if (tierCount <= 0)
|
||||
{
|
||||
return neutralAxisValue;
|
||||
}
|
||||
|
||||
return ComputeNormalizedPerformanceScore(
|
||||
samples,
|
||||
sample => sample.hasRankMarker && sample.rankTierCount > 0,
|
||||
sample =>
|
||||
{
|
||||
int count = Mathf.Max(1, sample.rankTierCount);
|
||||
return Mathf.Clamp01(sample.rankIndex / (float)count);
|
||||
});
|
||||
}
|
||||
|
||||
private float ComputeNormalizedPerformanceScore(
|
||||
List<RecordSample> samples,
|
||||
Func<RecordSample, bool> predicate,
|
||||
Func<RecordSample, float> selector)
|
||||
{
|
||||
List<float> values = new List<float>();
|
||||
float weightedSum = 0f;
|
||||
float totalWeight = 0f;
|
||||
|
||||
for (int i = 0; i < samples.Count; i++)
|
||||
{
|
||||
RecordSample sample = samples[i];
|
||||
if (!predicate(sample))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float value = Mathf.Clamp01(selector(sample));
|
||||
values.Add(value);
|
||||
weightedSum += value * sample.weight;
|
||||
totalWeight += sample.weight;
|
||||
}
|
||||
|
||||
if (values.Count == 0 || totalWeight <= 0f)
|
||||
{
|
||||
return neutralAxisValue;
|
||||
}
|
||||
|
||||
float weightedAverage = weightedSum / totalWeight;
|
||||
List<float> sorted = new List<float>(values);
|
||||
sorted.Sort((a, b) => b.CompareTo(a));
|
||||
|
||||
int topCount = Mathf.Min(topSampleCount, sorted.Count);
|
||||
float topAverage = 0f;
|
||||
for (int i = 0; i < topCount; i++)
|
||||
{
|
||||
topAverage += sorted[i];
|
||||
}
|
||||
topAverage /= Mathf.Max(1, topCount);
|
||||
|
||||
float variance = 0f;
|
||||
for (int i = 0; i < values.Count; i++)
|
||||
{
|
||||
float delta = values[i] - weightedAverage;
|
||||
variance += delta * delta;
|
||||
}
|
||||
variance /= Mathf.Max(1, values.Count);
|
||||
|
||||
float spread = Mathf.Sqrt(variance);
|
||||
float stability = 1f - Mathf.Clamp01(spread / Mathf.Max(0.05f, normalizedSpreadPenalty));
|
||||
float raw = (weightedAverage * 0.55f + topAverage * 0.25f + stability * 0.20f) * 100f;
|
||||
return BlendWithNeutral(raw, values.Count, performanceConfidenceFullCount);
|
||||
}
|
||||
|
||||
private float BlendWithNeutral(float raw, int sampleCount, int fullConfidenceCount)
|
||||
{
|
||||
float confidence = Mathf.Clamp01(sampleCount / Mathf.Max(1f, fullConfidenceCount));
|
||||
return Mathf.Lerp(neutralAxisValue, Mathf.Clamp(raw, 0f, 100f), confidence);
|
||||
}
|
||||
|
||||
private int GetRankTierCount()
|
||||
{
|
||||
if (rankConfig == null || rankConfig.thresholds == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Mathf.Max(0, rankConfig.thresholds.Count);
|
||||
}
|
||||
|
||||
private int CalculateRankIndex(int totalScore)
|
||||
{
|
||||
if (rankConfig == null || rankConfig.thresholds == null || rankConfig.thresholds.Count == 0 || totalScore <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bestLevel = 0;
|
||||
float highestMatchingPercent = -1f;
|
||||
int thresholdCount = rankConfig.thresholds.Count;
|
||||
|
||||
for (int i = 0; i < thresholdCount; i++)
|
||||
{
|
||||
RankThreshold threshold = rankConfig.thresholds[i];
|
||||
if (threshold == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float requiredScore = rankConfig.baseScore * threshold.thresholdPercent;
|
||||
if (totalScore < requiredScore || threshold.thresholdPercent <= highestMatchingPercent)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
highestMatchingPercent = threshold.thresholdPercent;
|
||||
bestLevel = thresholdCount - i;
|
||||
}
|
||||
|
||||
return bestLevel;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private struct BehaviourRadarSummary
|
||||
{
|
||||
public float active;
|
||||
public float chart;
|
||||
public float formation;
|
||||
public float acc;
|
||||
public float rks;
|
||||
public float rating;
|
||||
}
|
||||
|
||||
private struct RecordSample
|
||||
{
|
||||
public float weight;
|
||||
public bool hasPlayableTime;
|
||||
public DateTime playedAtLocal;
|
||||
public int chartScore;
|
||||
public int idolScore;
|
||||
public bool hasScoreBreakdown;
|
||||
public float accuracy;
|
||||
public float rks;
|
||||
public int rankIndex;
|
||||
public int rankTierCount;
|
||||
public bool hasRankMarker;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0343fabebe70383449bc7c9e6af02d7f
|
||||
@@ -0,0 +1,9 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class uRks : MonoBehaviour
|
||||
{
|
||||
[Header("slider")]
|
||||
public Slider rks_slider;
|
||||
public Text rks_scoreText;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 158b192458701974e87cb49b35e29475
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class uServerStatus : MonoBehaviour
|
||||
{
|
||||
[Header("UI References")]
|
||||
public Image statusIcon;
|
||||
public Text statusText;
|
||||
|
||||
[Header("Status Configurations")]
|
||||
public StatusConfig[] statusConfigs;
|
||||
|
||||
[Serializable]
|
||||
public class StatusConfig
|
||||
{
|
||||
public ServerStatus status;
|
||||
public Sprite icon;
|
||||
public string displayText;
|
||||
}
|
||||
|
||||
public enum ServerStatus
|
||||
{
|
||||
Good,
|
||||
Fair,
|
||||
Unreachable,
|
||||
Unknown,
|
||||
Offline,
|
||||
Test
|
||||
}
|
||||
|
||||
public void SetStatus(ServerStatus newStatus)
|
||||
{
|
||||
foreach (var config in statusConfigs)
|
||||
{
|
||||
if (config.status == newStatus)
|
||||
{
|
||||
if (statusIcon != null) statusIcon.sprite = config.icon;
|
||||
if (statusText != null) statusText.text = string.IsNullOrEmpty(config.displayText) ? newStatus.ToString() : config.displayText;
|
||||
return;
|
||||
}
|
||||
}
|
||||
Debug.LogWarning($"No configuration found for status {newStatus}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9fd7c838e5489749a0edc9fccacf71e
|
||||
Reference in New Issue
Block a user