notebook实装功能,galgame继续,一些bug修复

This commit is contained in:
FloatGaming
2026-03-06 02:08:43 +08:00
parent 9011fa022e
commit 1f4077cf21
468 changed files with 50175 additions and 11402 deletions
+203 -15
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
@@ -132,6 +132,10 @@ public class AllyCombatant : MonoBehaviour, ICombatant
private HashSet<string> _activeAttackBelowSkills = new HashSet<string>();
private Dictionary<string, AppliedEffect> _appliedAttackBelowEffects = new Dictionary<string, AppliedEffect>();
// Track skills that are currently active because Attack is above a threshold (OnAttackAboveValue)
private HashSet<string> _activeAttackAboveSkills = new HashSet<string>();
private Dictionary<string, AppliedEffect> _appliedAttackAboveEffects = new Dictionary<string, AppliedEffect>();
// Prevent re-entrant mana-trigger loops, e.g. OnManaGained -> (skill adds mana) -> OnManaGained -> ...
private bool _isTriggeringManaGained = false;
private bool _isTriggeringManaLost = false;
@@ -179,6 +183,8 @@ public class AllyCombatant : MonoBehaviour, ICombatant
if (_appliedAttackZeroEffects == null) _appliedAttackZeroEffects = new Dictionary<string, AppliedEffect>();
if (_activeAttackBelowSkills == null) _activeAttackBelowSkills = new HashSet<string>();
if (_appliedAttackBelowEffects == null) _appliedAttackBelowEffects = new Dictionary<string, AppliedEffect>();
if (_activeAttackAboveSkills == null) _activeAttackAboveSkills = new HashSet<string>();
if (_appliedAttackAboveEffects == null) _appliedAttackAboveEffects = new Dictionary<string, AppliedEffect>();
}
private void LogVerbose(string message)
@@ -304,6 +310,7 @@ public class AllyCombatant : MonoBehaviour, ICombatant
EvaluateHPPercentageTriggers(currentHP, true);
EvaluateAttackZeroTriggers(true);
EvaluateAttackBelowTriggers(true);
EvaluateAttackAboveTriggers(true);
}
// Allow editing in inspector to immediately reflect on UI (editor only)
@@ -326,6 +333,7 @@ public class AllyCombatant : MonoBehaviour, ICombatant
EvaluateHPPercentageTriggers(currentHP);
EvaluateAttackZeroTriggers();
EvaluateAttackBelowTriggers();
EvaluateAttackAboveTriggers();
TryCastOnFullMana();
}
}
@@ -1263,10 +1271,14 @@ public class AllyCombatant : MonoBehaviour, ICombatant
{
if (isDead) return; // dead characters cannot change mana
int old = currentMana;
int requestedMana = mana;
bool incomingOverflowGainAtFull = mana > maxMana && old >= maxMana;
currentMana = Mathf.Clamp(mana, 0, maxMana);
UpdateManaVisuals(old, animateFade, flashVisuals);
// Only auto-cast when we actually reach full mana from below.
if (old < maxMana && currentMana >= maxMana)
// Also handle positive mana input while already full (e.g. maxMana=1 and receiving mana from adjacent skills):
// without this, mana can stay at 100% with no further "reach full from below" transition.
if ((old < maxMana && currentMana >= maxMana) || incomingOverflowGainAtFull)
{
TryCastOnFullMana();
}
@@ -1308,6 +1320,44 @@ public class AllyCombatant : MonoBehaviour, ICombatant
_isTriggeringManaLost = false;
}
}
int finalMana = currentMana;
if (requestedMana != old || incomingOverflowGainAtFull)
{
string manaEventType;
if (incomingOverflowGainAtFull && finalMana == old && old >= maxMana)
{
manaEventType = "ManaOverflowAtFull";
GameplaySkillLogger.RecordConflictHint(
"AllyMana",
"OverflowGainAtFull",
"ally=" + allyName + " | slot=" + slotIndex + " | requested=" + requestedMana + " | current=" + finalMana + " | max=" + maxMana);
}
else if (finalMana > old)
{
manaEventType = "ManaGain";
}
else if (finalMana < old)
{
manaEventType = "ManaSpend";
}
else
{
manaEventType = "ManaClampNoDelta";
}
GameplaySkillLogger.RecordAllyResourceEvent(
allyName,
slotIndex,
"Mana",
manaEventType,
requestedMana - old,
finalMana - old,
old,
finalMana,
maxMana,
"SetCurrentMana");
}
}
public void ModifyMana(int delta, bool animateFade = true, bool flashVisuals = true)
@@ -1398,6 +1448,7 @@ public class AllyCombatant : MonoBehaviour, ICombatant
{
EvaluateAttackZeroTriggers();
EvaluateAttackBelowTriggers();
EvaluateAttackAboveTriggers();
}
}
@@ -1597,6 +1648,18 @@ public class AllyCombatant : MonoBehaviour, ICombatant
ModifyHP(-delta, true, true);
int actual = oldHP - currentHP;
GameplaySkillLogger.RecordAllyResourceEvent(
allyName,
slotIndex,
"HP",
"DamageTaken",
amount,
effective,
oldHP,
currentHP,
maxHP,
source != null ? source.name : "None");
if (actual != 0)
{
if (deferPopup)
@@ -1618,6 +1681,18 @@ public class AllyCombatant : MonoBehaviour, ICombatant
int delta = Mathf.CeilToInt(amount * mult);
ModifyHP(delta, true, true);
int actual = currentHP - oldHP;
GameplaySkillLogger.RecordAllyResourceEvent(
allyName,
slotIndex,
"HP",
"HealTaken",
amount,
amount * mult,
oldHP,
currentHP,
maxHP,
source != null ? source.name : "None");
if (actual != 0)
{
if (deferPopup)
@@ -1936,6 +2011,7 @@ public class AllyCombatant : MonoBehaviour, ICombatant
vars["currentScore"] = currentScore;
vars["idolScore"] = currentScore;
vars["currentHP"] = currentHP;
vars["noteBaseScore"] = bmm != null ? Mathf.Max(0, bmm.perNoteScore) : Mathf.Max(0, baseTrackScore);
return vars;
}
// New: Evaluate HP-percent triggers and handle enter/leave transitions
@@ -2336,6 +2412,131 @@ public class AllyCombatant : MonoBehaviour, ICombatant
}
}
private void EvaluateAttackAboveTriggers(bool skipVFX = false)
{
if (!Application.isPlaying) return;
if (SkillBuilder.Instance == null) return;
var so = SkillBuilder.Instance.GetAllyHeroSOBySlot(slotIndex);
if (so == null) return;
int currAttack = attack;
var defs = _tmpSkillDefs;
defs.Clear();
if (so.equippedSkillGroupIDs != null && so.equippedSkillGroupIDs.Length > 0 && so.skillGroups != null)
{
foreach (var gid in so.equippedSkillGroupIDs)
{
if (gid == 0) continue;
SkillGroup group = null;
for (int k = 0; k < so.skillGroups.Length; k++)
{
var g = so.skillGroups[k];
if (g != null && g.skillGroupID == gid) { group = g; break; }
}
if (group == null || group.skills == null) continue;
for (int i = 0; i < group.skills.Length; i++)
{
var def = group.skills[i];
if (def != null) defs.Add(def);
}
}
}
else
{
var fallbackGroup = so.GetPrimarySkillGroup();
if (fallbackGroup != null && fallbackGroup.skills != null)
{
for (int i = 0; i < fallbackGroup.skills.Length; i++)
{
var def = fallbackGroup.skills[i];
if (def != null) defs.Add(def);
}
}
else if (so.availableSkills != null)
{
for (int i = 0; i < so.availableSkills.Length; i++)
{
var def = so.availableSkills[i];
if (def != null) defs.Add(def);
}
}
}
// Pick the closest satisfied threshold from below (largest attackTriggerValue such that attack > value).
string selected = null;
int selectedThreshold = int.MinValue;
for (int i = 0; i < defs.Count; i++)
{
var def = defs[i];
if (def == null) continue;
if (def.triggerCondition != SkillDefinition.SkillTrigger.OnAttackAboveValue) continue;
int thr = def.attackTriggerValue;
if (thr < 0) thr = 0;
if (currAttack > thr && thr >= selectedThreshold)
{
selectedThreshold = thr;
selected = def.skillId;
}
}
var vars = BuildFormulaVars(so);
for (int i = 0; i < defs.Count; i++)
{
var def = defs[i];
if (def == null) continue;
if (def.triggerCondition != SkillDefinition.SkillTrigger.OnAttackAboveValue) continue;
int thr = def.attackTriggerValue;
if (thr < 0) thr = 0;
bool wasActive = _activeAttackAboveSkills.Contains(def.skillId);
bool nowActive = (selected != null && def.skillId == selected) && (currAttack > thr);
float amount = 0f;
if (!string.IsNullOrWhiteSpace(def.formula))
{
if (SkillDefinition.TryEvaluateFormula(def.formula, vars, out float fresult))
amount = fresult;
}
bool isReversible = IsEffectReversible(def.effectType);
if (!isReversible)
{
if (!wasActive && nowActive)
{
_activeAttackAboveSkills.Add(def.skillId);
EffectSystem.Instance.ApplyEffect(def.defaultSelector, def.effectType, amount, 0f, this.gameObject, null, 1f, skipVFX);
}
else if (wasActive && !nowActive)
{
_activeAttackAboveSkills.Remove(def.skillId);
}
continue;
}
if (!wasActive && nowActive)
{
_activeAttackAboveSkills.Add(def.skillId);
EffectSystem.Instance.ApplyEffect(def.defaultSelector, def.effectType, amount, 0f, this.gameObject, null, 1f, skipVFX);
_appliedAttackAboveEffects[def.skillId] = new AppliedEffect(def.effectType, amount);
}
else if (wasActive && !nowActive)
{
_activeAttackAboveSkills.Remove(def.skillId);
if (_appliedAttackAboveEffects.TryGetValue(def.skillId, out var applied))
{
ApplyInverseEffect(def.defaultSelector, applied.effectType, applied.amount);
_appliedAttackAboveEffects.Remove(def.skillId);
}
}
}
}
public static void ActivateNextDamageRedirect(AllyCombatant redirector, float duration = 0f)
{
if (redirector == null) return;
@@ -2370,12 +2571,6 @@ public class AllyCombatant : MonoBehaviour, ICombatant
state.expireTime = expireTime;
state.activationOrder = ++s_nextDamageRedirectOrderSeed;
state.iconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(redirector, PlayerBudeffIconType.dmg_redirect_toSelf, 0f, duration);
// If adjacent allies already have buffs, transfer one now and consume this one-shot redirect.
if (redirector.TryTransferExistingBuffFromAdjacentToSelf())
{
ConsumeGlobalRedirect(redirector);
}
}
private static void CleanupGlobalRedirects()
@@ -3040,13 +3235,6 @@ public class AllyCombatant : MonoBehaviour, ICombatant
_selfDamageRedirectToAdjacentIconId = null;
}
_selfDamageRedirectToAdjacentIconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(this, PlayerBudeffIconType.dmg_redirect_toAdjacent, 0f, duration);
// "都给你!" rule: transfer to adjacent ally with the MOST buffs.
var adj = FindAdjacentAllyByBuffCount(pickMost: true, requireAtLeastOneBuff: false);
if (adj != null && TransferOneExistingBuffTo(adj))
{
ConsumeSelfDamageRedirectToAdjacent();
}
}
public void ActivateNonMissToPerfectRewrite(float duration)
+2
View File
@@ -1590,6 +1590,8 @@ public class EffectSystem : MonoBehaviour
case EffectType.DecreaseDamageResistance:
case EffectType.IncreaseAttack:
case EffectType.DecreaseAttack:
case EffectType.RedirectNextDamageToSelf:
case EffectType.RedirectSelfDamageToAdjacent:
case EffectType.RewriteNonMissToPerfect:
return true;
default:
+38
View File
@@ -70,6 +70,15 @@ public class EnemyCombatant : MonoBehaviour, ICombatant
currentMana = 0;
isDead = false;
GameplaySkillLogger.RecordEnemyLifeState(
gameObject.name,
"Spawned",
currentHP,
maxHP,
currentMana,
maxMana,
sourceData != null ? sourceData.name : "None");
iBudeffPrefabController.Instance?.RegisterEnemyBaseline(this);
// notify listeners (UI/manager) that enemy revived
@@ -150,6 +159,16 @@ public class EnemyCombatant : MonoBehaviour, ICombatant
ModifyHP(-delta, true);
int actual = before - currentHP;
GameplaySkillLogger.RecordEnemyHpEvent(
gameObject.name,
"Damage",
amount,
effective,
before,
currentHP,
damageResistance,
source != null ? source.name : "None");
if (actual != 0)
{
if (deferPopup)
@@ -184,6 +203,16 @@ public class EnemyCombatant : MonoBehaviour, ICombatant
int delta = Mathf.CeilToInt(amount * mult);
ModifyHP(delta, true);
int actual = currentHP - before;
GameplaySkillLogger.RecordEnemyHpEvent(
gameObject.name,
"Heal",
amount,
amount * mult,
before,
currentHP,
damageResistance,
source != null ? source.name : "None");
if (actual != 0)
{
if (deferPopup)
@@ -356,6 +385,15 @@ public class EnemyCombatant : MonoBehaviour, ICombatant
{
if (isDead) return;
GameplaySkillLogger.RecordEnemyLifeState(
gameObject.name,
"Dead",
currentHP,
maxHP,
currentMana,
maxMana,
_lastAttacker != null ? _lastAttacker.name : "None");
// award score to last attacker
if (sourceData != null)
{
@@ -0,0 +1,344 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using UnityEngine;
public static class GameplaySkillLogger
{
private static readonly object s_fileLock = new object();
private static readonly UTF8Encoding s_utf8NoBom = new UTF8Encoding(false);
private static readonly Dictionary<string, float> s_lastSkillReleaseByKey = new Dictionary<string, float>(256);
private static string s_logFilePath;
private static bool s_sessionActive;
private static float s_sessionStartRealtime;
private static int s_lastSecondBucket = -1;
private const float RapidDuplicateSkillThresholdSeconds = 0.05f;
public static string LogFilePath
{
get { return EnsureLogFilePath(); }
}
public static void BeginSession(string sessionLabel)
{
lock (s_fileLock)
{
BeginSessionInternal(sessionLabel);
}
}
public static void RecordSkillRelease(
string roleName,
string skillName,
string effectName,
string attributeSummary,
string targetSummary)
{
lock (s_fileLock)
{
EnsureSessionLocked();
string role = Sanitize(roleName);
string skill = Sanitize(skillName);
string effect = Sanitize(effectName);
string attr = Sanitize(attributeSummary);
string target = Sanitize(targetSummary);
string payload = "role=" + role
+ " | skill=" + skill
+ " | effect=" + effect
+ " | attr=" + attr
+ " | target=" + target;
float elapsed = AppendEventLineLocked("SKILL", payload);
string dedupeKey = role + "|" + skill + "|" + effect + "|" + target;
if (s_lastSkillReleaseByKey.TryGetValue(dedupeKey, out float lastElapsed))
{
float interval = elapsed - lastElapsed;
if (interval >= 0f && interval <= RapidDuplicateSkillThresholdSeconds)
{
AppendEventLineLocked(
"WARN",
"potential_duplicate_skill_trigger"
+ " | role=" + role
+ " | skill=" + skill
+ " | intervalSec=" + FormatFloat(interval));
}
}
s_lastSkillReleaseByKey[dedupeKey] = elapsed;
}
}
public static void RecordJudgeResult(
string noteType,
string noteSegment,
int trackIndex,
string noteId,
string judgeResult,
float rawOffsetMs,
bool rewrittenToPerfect,
bool autoplay,
float actionTime,
float hitTime,
float scheduledEndTime)
{
lock (s_fileLock)
{
EnsureSessionLocked();
string payload = "noteType=" + Sanitize(noteType)
+ " | segment=" + Sanitize(noteSegment)
+ " | track=" + trackIndex
+ " | noteId=" + Sanitize(noteId)
+ " | judge=" + Sanitize(judgeResult)
+ " | rawOffsetMs=" + FormatFloat(rawOffsetMs)
+ " | rewrittenToPerfect=" + (rewrittenToPerfect ? "1" : "0")
+ " | autoplay=" + (autoplay ? "1" : "0")
+ " | actionTime=" + FormatFloat(actionTime)
+ " | hitTime=" + FormatFloat(hitTime)
+ " | scheduledEndTime=" + FormatFloat(scheduledEndTime);
AppendEventLineLocked("JUDGE", payload);
}
}
public static void RecordScoreDelta(
string source,
int trackIndex,
int pmRequestedDelta,
int pmActualDelta,
int idolRequestedDelta,
int idolActualDelta,
float scoreEfficiency,
bool isSkillRelated,
int trackPmSum,
int trackIdolSum,
int allPm,
int allIdol,
int totalScore)
{
lock (s_fileLock)
{
EnsureSessionLocked();
string payload = "source=" + Sanitize(source)
+ " | track=" + trackIndex
+ " | pmReq=" + pmRequestedDelta
+ " | pmAct=" + pmActualDelta
+ " | idolReq=" + idolRequestedDelta
+ " | idolAct=" + idolActualDelta
+ " | efficiency=" + FormatFloat(scoreEfficiency)
+ " | skillRelated=" + (isSkillRelated ? "1" : "0")
+ " | trackPm=" + trackPmSum
+ " | trackIdol=" + trackIdolSum
+ " | allPm=" + allPm
+ " | allIdol=" + allIdol
+ " | total=" + totalScore;
AppendEventLineLocked("SCORE", payload);
}
}
public static void RecordEnemyHpEvent(
string enemyName,
string eventType,
float rawAmount,
float effectiveAmount,
int hpBefore,
int hpAfter,
float damageResistance,
string sourceName)
{
lock (s_fileLock)
{
EnsureSessionLocked();
string payload = "enemy=" + Sanitize(enemyName)
+ " | event=" + Sanitize(eventType)
+ " | raw=" + FormatFloat(rawAmount)
+ " | effective=" + FormatFloat(effectiveAmount)
+ " | hpBefore=" + hpBefore
+ " | hpAfter=" + hpAfter
+ " | hpDelta=" + (hpAfter - hpBefore)
+ " | resist=" + FormatFloat(damageResistance)
+ " | source=" + Sanitize(sourceName);
AppendEventLineLocked("ENEMY", payload);
}
}
public static void RecordEnemyLifeState(
string enemyName,
string state,
int hp,
int maxHp,
int mana,
int maxMana,
string source)
{
lock (s_fileLock)
{
EnsureSessionLocked();
string payload = "enemy=" + Sanitize(enemyName)
+ " | state=" + Sanitize(state)
+ " | hp=" + hp + "/" + maxHp
+ " | mana=" + mana + "/" + maxMana
+ " | source=" + Sanitize(source);
AppendEventLineLocked("ENEMY", payload);
}
}
public static void RecordAllyResourceEvent(
string allyName,
int slotIndex,
string resource,
string eventType,
float requestedAmount,
float effectiveAmount,
int before,
int after,
int maxValue,
string sourceName)
{
lock (s_fileLock)
{
EnsureSessionLocked();
string payload = "ally=" + Sanitize(allyName)
+ " | slot=" + slotIndex
+ " | resource=" + Sanitize(resource)
+ " | event=" + Sanitize(eventType)
+ " | requested=" + FormatFloat(requestedAmount)
+ " | effective=" + FormatFloat(effectiveAmount)
+ " | before=" + before
+ " | after=" + after
+ " | delta=" + (after - before)
+ " | max=" + maxValue
+ " | source=" + Sanitize(sourceName);
AppendEventLineLocked("ALLY", payload);
}
}
public static void RecordConflictHint(string subsystem, string issueType, string details)
{
lock (s_fileLock)
{
EnsureSessionLocked();
string payload = "subsystem=" + Sanitize(subsystem)
+ " | issue=" + Sanitize(issueType)
+ " | details=" + Sanitize(details);
AppendEventLineLocked("WARN", payload);
}
}
private static void BeginSessionInternal(string sessionLabel)
{
s_sessionStartRealtime = Time.realtimeSinceStartup;
s_lastSecondBucket = -1;
s_sessionActive = true;
s_lastSkillReleaseByKey.Clear();
string normalizedLabel = string.IsNullOrWhiteSpace(sessionLabel) ? "UnknownSession" : sessionLabel.Trim();
string path = EnsureLogFilePath();
string header =
"# Gameplay Skill Timeline Log" + Environment.NewLine +
"# Session: " + normalizedLabel + Environment.NewLine +
"# StartedAt: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture) + Environment.NewLine +
"# Format: [elapsedSec] [CATEGORY] key=value | key=value" + Environment.NewLine +
Environment.NewLine;
try
{
File.WriteAllText(path, header, s_utf8NoBom);
}
catch (Exception ex)
{
Debug.LogWarning("[GameplaySkillLogger] Failed to create session log: " + ex.Message);
}
}
private static float AppendEventLineLocked(string category, string payload)
{
float elapsed = Mathf.Max(0f, Time.realtimeSinceStartup - s_sessionStartRealtime);
int secondBucket = Mathf.FloorToInt(elapsed);
var builder = new StringBuilder(320);
if (secondBucket != s_lastSecondBucket)
{
if (s_lastSecondBucket >= 0) builder.AppendLine();
builder.AppendLine("=== T+" + secondBucket.ToString("D4", CultureInfo.InvariantCulture) + "s ===");
s_lastSecondBucket = secondBucket;
}
builder.Append("[")
.Append(elapsed.ToString("F3", CultureInfo.InvariantCulture))
.Append("s] [")
.Append(Sanitize(category))
.Append("] ")
.AppendLine(payload);
try
{
File.AppendAllText(EnsureLogFilePath(), builder.ToString(), s_utf8NoBom);
}
catch (Exception ex)
{
Debug.LogWarning("[GameplaySkillLogger] Failed to append log: " + ex.Message);
}
return elapsed;
}
private static void EnsureSessionLocked()
{
if (!s_sessionActive)
{
BeginSessionInternal("AutoSession");
}
}
private static string EnsureLogFilePath()
{
if (!string.IsNullOrEmpty(s_logFilePath))
{
return s_logFilePath;
}
try
{
string projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
string logsDir = Path.Combine(projectRoot, "Logs");
Directory.CreateDirectory(logsDir);
s_logFilePath = Path.Combine(logsDir, "GameplaySkillTimeline.log");
}
catch
{
s_logFilePath = Path.Combine(Application.persistentDataPath, "GameplaySkillTimeline.log");
}
return s_logFilePath;
}
private static string FormatFloat(float value)
{
if (float.IsNaN(value)) return "NaN";
if (float.IsPositiveInfinity(value)) return "Infinity";
if (float.IsNegativeInfinity(value)) return "-Infinity";
return value.ToString("0.###", CultureInfo.InvariantCulture);
}
private static string Sanitize(string value)
{
if (string.IsNullOrEmpty(value)) return "-";
return value.Replace("\r", " ").Replace("\n", " ").Trim();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 52e3eb7e0c41c7141b410f678c7ec701
+47
View File
@@ -269,6 +269,21 @@ public class ScoreManager : MonoBehaviour
}
RecalculateTotal();
GameplaySkillLogger.RecordScoreDelta(
"PerfectClearBonus",
-1,
perfectClearBonusPm,
perfectClearBonusPm,
0,
0,
float.NaN,
false,
-1,
-1,
allSum_pmScore,
allSum_idolScore,
totalScore);
}
private void EnsureIdolscoreKeys()
@@ -368,10 +383,12 @@ public class ScoreManager : MonoBehaviour
TryHookPerfectBonusEvent();
if (trackIndex < 0 || trackIndex >= pmScoreSums.Length) return;
if (pmDelta == 0) return;
int pmBefore = pmScoreSums[trackIndex];
pmScoreSums[trackIndex] += pmDelta;
// clamp at int.MaxValue-1 to avoid overflow
if (pmScoreSums[trackIndex] < 0) pmScoreSums[trackIndex] = 0;
if (pmScoreSums[trackIndex] > int.MaxValue - 1) pmScoreSums[trackIndex] = int.MaxValue - 1;
int pmActualDelta = pmScoreSums[trackIndex] - pmBefore;
int idolBefore = idolScoreSums[trackIndex];
int idolDelta = Mathf.FloorToInt(pmDelta * scoreEfficiency);
@@ -446,6 +463,21 @@ public class ScoreManager : MonoBehaviour
// Update per-track progress keys (bgSPRITE/idolscoreKey_*).
UpdateIdolscoreKeyScales();
GameplaySkillLogger.RecordScoreDelta(
"AddPmScoreForTrack",
trackIndex,
pmDelta,
pmActualDelta,
idolDelta,
idolActualDelta,
scoreEfficiency,
isSkillRelated,
pmScoreSums[trackIndex],
idolScoreSums[trackIndex],
allSum_pmScore,
allSum_idolScore,
totalScore);
}
/// <summary>
@@ -504,6 +536,21 @@ public class ScoreManager : MonoBehaviour
}
RecalculateTotal();
GameplaySkillLogger.RecordScoreDelta(
"AddIdolScoreForTrack",
trackIndex,
0,
0,
idolDelta,
actual,
float.NaN,
true,
pmScoreSums[trackIndex],
idolScoreSums[trackIndex],
allSum_pmScore,
allSum_idolScore,
totalScore);
}
public void RecalculateTotal()
+278 -1
View File
@@ -72,9 +72,19 @@ public class SkillBuilder : MonoBehaviour
private readonly Dictionary<string, Coroutine> _refreshOnlyTimedEffectCoroutines = new Dictionary<string, Coroutine>(32);
private readonly Dictionary<string, RefreshOnlyTimedState> _refreshOnlyTimedEffectStates = new Dictionary<string, RefreshOnlyTimedState>(32);
private readonly Dictionary<string, Coroutine> _refreshOnlyOverTimeCoroutines = new Dictionary<string, Coroutine>(16);
private readonly Dictionary<int, ManaFullLongingState> _manaFullLongingStateBySlot = new Dictionary<int, ManaFullLongingState>(8);
private int _cachedAlliesFrame = -1;
private readonly List<GameObject> _cachedAllies = new List<GameObject>(8);
private const string SkillIdYuetaoLongingDamage = "44109";
private const string SkillIdYuetaoLongingCost = "44109_2";
private struct ManaFullLongingState
{
public int frame;
public int consumedHp;
}
private static readonly HashSet<string> s_refreshOnlyTimedSkillIds = new HashSet<string>
{
// Lock
@@ -859,6 +869,7 @@ public class SkillBuilder : MonoBehaviour
case EffectType.IncreaseAttack:
case EffectType.DecreaseAttack:
case EffectType.RedirectNextDamageToSelf:
case EffectType.RedirectSelfDamageToAdjacent:
return true;
default:
return false;
@@ -1182,10 +1193,11 @@ public class SkillBuilder : MonoBehaviour
int groupId = 0;
string smallSkillName = null;
Sprite smallSkillIcon = null;
AllyHero_SO heroSoForName = null;
// Resolve group/icon metadata for the ally HUD skill icon queue.
try
{
var heroSoForName = GetAllyHeroSOBySlot(slotIndex);
heroSoForName = GetAllyHeroSOBySlot(slotIndex);
// Documentation text normalized.
if (heroSoForName != null && heroSoForName.skillGroups != null)
@@ -1308,6 +1320,11 @@ ResolvedGroup:
// Log useful debug info for diagnosing OnEnemyDead->Self issues
LogVerbose($"[SkillBuilder] UseSkillDefinition: casting skill {def.skillId} for slot {slotIndex} (caster={(caster?caster.name:"null")}) selector={def.defaultSelector} operateDirectly={def.operateDirectly} inputValue={inputValue} specificTarget={(specificTarget?specificTarget.name:"null")}");
if (TryHandleYuetaoLongingSkill(def, slotIndex, caster, heroSoForName, specificTarget))
{
return;
}
float amount = 0f;
// If caller provided explicit inputValue use it
if (inputValue != -1f)
@@ -1372,6 +1389,9 @@ ResolvedGroup:
vars["currentScore"] = casterAlly != null ? casterAlly.currentScore : 0f;
vars["idolScore"] = vars["currentScore"];
vars["currentHP"] = casterAlly != null ? casterAlly.currentHP : 0f;
vars["noteBaseScore"] = casterAlly != null
? (casterAlly.bmm != null ? Mathf.Max(0, casterAlly.bmm.perNoteScore) : Mathf.Max(0, casterAlly.baseTrackScore))
: 0f;
if (string.IsNullOrWhiteSpace(def.formula))
{
@@ -1438,6 +1458,26 @@ ResolvedGroup:
_lastSkillTriggerTime[repeatKey] = now;
}
try
{
string casterDisplayName = ResolveCasterDisplayNameForLog(slotIndex, heroSoForName, caster);
string skillDisplayNameForLog = string.IsNullOrWhiteSpace(smallSkillName)
? (!string.IsNullOrWhiteSpace(def.displayName) ? def.displayName : (!string.IsNullOrWhiteSpace(def.skillId) ? def.skillId : def.name))
: smallSkillName;
string effectSummary = BuildSkillEffectSummaryForLog(def, amountPerTick, amountTotal);
string targetSummary = BuildSkillTargetSummaryForLog(def.defaultSelector, specificTarget);
GameplaySkillLogger.RecordSkillRelease(
casterDisplayName,
skillDisplayNameForLog,
def.effectType.ToString(),
effectSummary,
targetSummary);
}
catch (System.Exception ex)
{
LogVerbose("[SkillBuilder] GameplaySkillLogger failed: " + ex.Message);
}
if (def.operateDirectly)
{
// Resolve targets as GameObjects but operate on their AllyCombatant / ICombatant data directly
@@ -1800,6 +1840,79 @@ ResolvedGroup:
}
}
private bool TryHandleYuetaoLongingSkill(SkillDefinition def, int slotIndex, GameObject caster, AllyHero_SO heroSoForName, GameObject specificTarget)
{
if (def == null || string.IsNullOrWhiteSpace(def.skillId)) return false;
if (def.skillId != SkillIdYuetaoLongingDamage && def.skillId != SkillIdYuetaoLongingCost) return false;
var casterAlly = caster != null ? caster.GetComponent<AllyCombatant>() : null;
if (casterAlly == null)
{
Debug.LogWarning($"[SkillBuilder] Yuetao Longing custom handler skipped: caster ally missing for slot {slotIndex}");
return false;
}
int consumedHp = 0;
int nowFrame = Time.frameCount;
if (_manaFullLongingStateBySlot.TryGetValue(slotIndex, out var state) && state.frame == nowFrame)
{
consumedHp = state.consumedHp;
}
else
{
consumedHp = ConsumeYuetaoLongingHpCost(casterAlly);
_manaFullLongingStateBySlot[slotIndex] = new ManaFullLongingState { frame = nowFrame, consumedHp = consumedHp };
}
string casterDisplayName = ResolveCasterDisplayNameForLog(slotIndex, heroSoForName, caster);
string skillDisplayName = !string.IsNullOrWhiteSpace(def.displayName) ? def.displayName : (!string.IsNullOrWhiteSpace(def.skillId) ? def.skillId : def.name);
string targetSummary = BuildSkillTargetSummaryForLog(def.defaultSelector, specificTarget);
if (def.skillId == SkillIdYuetaoLongingCost)
{
try
{
string effectSummary = "生命值" + FormatSignedIntForLog(-Mathf.Abs(consumedHp)) + " (上限3%, 保底>20%)";
GameplaySkillLogger.RecordSkillRelease(casterDisplayName, skillDisplayName, def.effectType.ToString(), effectSummary, targetSummary);
}
catch { }
return true;
}
float attackBonus = Mathf.Max(0f, casterAlly.attack) * 0.1f;
float damage = consumedHp + attackBonus;
if (damage > 0f)
{
ExecuteSkill(def.displayName ?? def.skillId, def.effectType, damage, def.defaultSelector, caster, specificTarget, def.defaultDuration, def.defaultTickInterval);
}
try
{
string effectSummary = "重击伤害=" + damage.ToString("0.###") + " (耗血" + consumedHp + "+攻击10%:" + attackBonus.ToString("0.###") + ")";
GameplaySkillLogger.RecordSkillRelease(casterDisplayName, skillDisplayName, def.effectType.ToString(), effectSummary, targetSummary);
}
catch { }
return true;
}
private static int ConsumeYuetaoLongingHpCost(AllyCombatant ally)
{
if (ally == null || ally.maxHP <= 0 || ally.currentHP <= 0) return 0;
int rawCost = Mathf.Max(0, Mathf.CeilToInt(ally.maxHP * 0.03f));
if (rawCost <= 0) return 0;
// "cannot drop to 20% max HP or below" => keep HP strictly greater than 20%.
int minRemainExclusive = Mathf.FloorToInt(ally.maxHP * 0.2f) + 1;
int maxAllowedCost = Mathf.Max(0, ally.currentHP - minRemainExclusive);
int finalCost = Mathf.Clamp(rawCost, 0, maxAllowedCost);
if (finalCost <= 0) return 0;
ally.ModifyHP(-finalCost, true);
return finalCost;
}
// Use the selected skill index from an AllyHero_SO for a given slot (calls UseSkillDefinition)
public void UseSelectedSkillForSlot(int slotIndex, int selectedSkillIndex, float inputValue = -1f, GameObject specificTarget = null)
{
@@ -2584,6 +2697,170 @@ ResolvedGroup:
}
}
private static string ResolveCasterDisplayNameForLog(int slotIndex, AllyHero_SO heroSo, GameObject caster)
{
if (heroSo != null && !string.IsNullOrWhiteSpace(heroSo.ally_heroName))
{
return heroSo.ally_heroName;
}
if (caster != null)
{
var ally = caster.GetComponent<AllyCombatant>() ?? caster.GetComponentInChildren<AllyCombatant>(true);
if (ally != null && !string.IsNullOrWhiteSpace(ally.allyName))
{
return ally.allyName;
}
if (!string.IsNullOrWhiteSpace(caster.name))
{
return caster.name;
}
}
return "slot" + (slotIndex + 1);
}
private static string BuildSkillTargetSummaryForLog(Selector selector, GameObject specificTarget)
{
if (specificTarget != null)
{
return selector + " -> " + specificTarget.name;
}
return selector.ToString();
}
private static string BuildSkillEffectSummaryForLog(SkillDefinition def, float amountPerTick, float amountTotal)
{
if (def == null) return "-";
bool isOverTime = IsOverTimeEffectForLog(def.effectType) && def.defaultDuration > 0f;
string summary;
switch (def.effectType)
{
case EffectType.DamageSingleEnemy:
case EffectType.DamageSingleAlly:
summary = "生命值" + FormatSignedFloatForLog(-Mathf.Abs(amountTotal));
break;
case EffectType.DamageOverTimeEnemy:
case EffectType.DamageOverTimeAlly:
if (isOverTime) summary = "生命值" + FormatSignedFloatForLog(-Mathf.Abs(amountPerTick)) + "/tick, 总计" + FormatSignedFloatForLog(-Mathf.Abs(amountTotal));
else summary = "生命值" + FormatSignedFloatForLog(-Mathf.Abs(amountTotal));
break;
case EffectType.HealSingleEnemy:
case EffectType.HealSingleSelf:
case EffectType.HealGroupSingle:
summary = "生命值" + FormatSignedFloatForLog(Mathf.Abs(amountTotal));
break;
case EffectType.HealOverTimeEnemy:
case EffectType.HealOverTimeSelf:
case EffectType.HealGroupOverTime:
if (isOverTime) summary = "生命值" + FormatSignedFloatForLog(Mathf.Abs(amountPerTick)) + "/tick, 总计" + FormatSignedFloatForLog(Mathf.Abs(amountTotal));
else summary = "生命值" + FormatSignedFloatForLog(Mathf.Abs(amountTotal));
break;
case EffectType.IncreaseManaOverTime:
if (isOverTime) summary = "法力值" + FormatSignedFloatForLog(amountPerTick) + "/tick, 总计" + FormatSignedFloatForLog(amountTotal);
else summary = "法力值" + FormatSignedFloatForLog(amountTotal);
break;
case EffectType.ReduceEnemyHealOverTime:
summary = "受疗倍率-" + Mathf.RoundToInt(Mathf.Abs(amountPerTick) * 100f) + "%";
break;
case EffectType.ScoreMultiplier:
summary = "得分倍率x" + amountTotal.ToString("0.###");
break;
case EffectType.AddScore:
summary = "偶像分数" + FormatSignedFloatForLog(amountTotal);
break;
case EffectType.IncreaseMaxHP:
summary = "最大生命值" + FormatSignedIntForLog(Mathf.CeilToInt(Mathf.Abs(amountTotal)));
break;
case EffectType.DecreaseMaxHP:
summary = "最大生命值" + FormatSignedIntForLog(-Mathf.CeilToInt(Mathf.Abs(amountTotal)));
break;
case EffectType.IncreaseMaxMana:
summary = "最大法力值" + FormatSignedIntForLog(Mathf.CeilToInt(Mathf.Abs(amountTotal)));
break;
case EffectType.DecreaseMaxMana:
summary = "最大法力值" + FormatSignedIntForLog(-Mathf.CeilToInt(Mathf.Abs(amountTotal)));
break;
case EffectType.IncreaseScoreEfficiency:
summary = "得分效率" + FormatSignedFloatForLog(Mathf.Abs(amountTotal));
break;
case EffectType.DecreaseScoreEfficiency:
summary = "得分效率" + FormatSignedFloatForLog(-Mathf.Abs(amountTotal));
break;
case EffectType.IncreaseDamageResistance:
summary = "伤害抗性" + FormatSignedFloatForLog(Mathf.Abs(amountTotal));
break;
case EffectType.DecreaseDamageResistance:
summary = "伤害抗性" + FormatSignedFloatForLog(-Mathf.Abs(amountTotal));
break;
case EffectType.IncreaseAttack:
summary = "攻击力" + FormatSignedIntForLog(Mathf.CeilToInt(Mathf.Abs(amountTotal)));
break;
case EffectType.DecreaseAttack:
summary = "攻击力" + FormatSignedIntForLog(-Mathf.CeilToInt(Mathf.Abs(amountTotal)));
break;
case EffectType.RedirectNextDamageToSelf:
summary = "下一次承伤转移到自身";
break;
case EffectType.RedirectSelfDamageToAdjacent:
summary = "下一次承伤转移到相邻偶像";
break;
case EffectType.GrantExtraPerfect:
summary = "额外Perfect判定+" + Mathf.Max(1, Mathf.RoundToInt(Mathf.Abs(amountTotal)));
break;
case EffectType.RewriteNonMissToPerfect:
summary = "非Miss判定改写为Perfect";
break;
case EffectType.BuffDuration:
summary = "增益效果倍率x" + amountPerTick.ToString("0.###");
break;
case EffectType.DebuffDuration:
summary = "减益效果倍率x" + amountPerTick.ToString("0.###");
break;
default:
summary = "数值" + FormatSignedFloatForLog(amountTotal);
break;
}
if (def.defaultDuration > 0f)
{
summary += ",持续" + def.defaultDuration.ToString("0.##") + "s";
if (isOverTime)
{
float tick = Mathf.Max(0.01f, def.GetEffectiveTickInterval());
summary += ",间隔" + tick.ToString("0.##") + "s";
}
}
return summary;
}
private static bool IsOverTimeEffectForLog(EffectType effectType)
{
return effectType == EffectType.DamageOverTimeEnemy
|| effectType == EffectType.DamageOverTimeAlly
|| effectType == EffectType.HealOverTimeEnemy
|| effectType == EffectType.HealOverTimeSelf
|| effectType == EffectType.HealGroupOverTime
|| effectType == EffectType.IncreaseManaOverTime;
}
private static string FormatSignedFloatForLog(float value)
{
if (value >= 0f) return "+" + value.ToString("0.###");
return value.ToString("0.###");
}
private static string FormatSignedIntForLog(int value)
{
if (value >= 0) return "+" + value.ToString();
return value.ToString();
}
// Helper: map judge string to numeric quality
private int JudgeQualityFromString(string judge)
{
+4 -2
View File
@@ -37,7 +37,9 @@ public class SkillDefinition : ScriptableObject
// New: triggers once when this ally is defeated (HP reaches 0 from above).
OnSelfDefeated,
// New: triggers on this ally when an adjacent ally releases a skill (casts on mana full).
OnAdjacentAllySkillCast
OnAdjacentAllySkillCast,
// New: attack threshold trigger (attack > attackTriggerValue)
OnAttackAboveValue
}
[Header("Inspector")]
@@ -98,7 +100,7 @@ public class SkillDefinition : ScriptableObject
[Tooltip("Optional: for HP percent triggers, evaluate this formula (0..1) to get the threshold instead of hpTriggerPercent. Useful for per-level thresholds like 90/80/75/70.")]
public string hpTriggerPercentFormula = "";
[Tooltip("For OnAttackBelowValue: triggers when attack < attackTriggerValue.")]
[Tooltip("For OnAttackBelowValue/OnAttackAboveValue: threshold for attack comparison.")]
public int attackTriggerValue = 0;
[Tooltip("Optional: if > 0 and repeatValue != 0, when the skill triggers again within this window, the computed amount is overridden by repeatValue.")]
+4 -4
View File
@@ -1,15 +1,15 @@
using UnityEngine;
using UnityEngine;
[CreateAssetMenu(fileName = "PlayerDefaultSO", menuName = "SO_Data/PlayerSO")]
public class Player_SO : ScriptableObject
{
[Header("Inspector")]
public Sprite playerProfile;
public string player_name;
public int player_UID; // steam ID
public int player_currentEXP;
public int player_currentLevel;
[Header("economics")]
[SerializeField] private int player_money;
[SerializeField] private int player_material;
[Header("Device Records")]
public string firstLaunchDate;
}
+5
View File
@@ -0,0 +1,5 @@
fileFormatVersion: 2
guid: 62ddefd95a62cbc4ea1a2aabc009a378
folderAsset: yes
DefaultImporter:
userData:
@@ -0,0 +1,182 @@
// The SteamManager is designed to work with Steamworks.NET
// This file is released into the public domain.
// Where that dedication is not recognized you are granted a perpetual,
// irrevocable license to copy and modify this file as you see fit.
//
// Version: 1.0.13
#if !(UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_STANDALONE_OSX || STEAMWORKS_WIN || STEAMWORKS_LIN_OSX)
#define DISABLESTEAMWORKS
#endif
using UnityEngine;
#if !DISABLESTEAMWORKS
using System.Collections;
using Steamworks;
#endif
//
// The SteamManager provides a base implementation of Steamworks.NET on which you can build upon.
// It handles the basics of starting up and shutting down the SteamAPI for use.
//
[DisallowMultipleComponent]
public class SteamManager : MonoBehaviour {
#if !DISABLESTEAMWORKS
protected static bool s_EverInitialized = false;
protected static SteamManager s_instance;
protected static SteamManager Instance {
get {
if (s_instance == null) {
return new GameObject("SteamManager").AddComponent<SteamManager>();
}
else {
return s_instance;
}
}
}
protected bool m_bInitialized = false;
public static bool Initialized {
get {
return Instance.m_bInitialized;
}
}
protected SteamAPIWarningMessageHook_t m_SteamAPIWarningMessageHook;
[AOT.MonoPInvokeCallback(typeof(SteamAPIWarningMessageHook_t))]
protected static void SteamAPIDebugTextHook(int nSeverity, System.Text.StringBuilder pchDebugText) {
Debug.LogWarning(pchDebugText);
}
#if UNITY_2019_3_OR_NEWER
// In case of disabled Domain Reload, reset static members before entering Play Mode.
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void InitOnPlayMode()
{
s_EverInitialized = false;
s_instance = null;
}
#endif
protected virtual void Awake() {
// Only one instance of SteamManager at a time!
if (s_instance != null) {
Destroy(gameObject);
return;
}
s_instance = this;
if(s_EverInitialized) {
// This is almost always an error.
// The most common case where this happens is when SteamManager gets destroyed because of Application.Quit(),
// and then some Steamworks code in some other OnDestroy gets called afterwards, creating a new SteamManager.
// You should never call Steamworks functions in OnDestroy, always prefer OnDisable if possible.
throw new System.Exception("Tried to Initialize the SteamAPI twice in one session!");
}
// We want our SteamManager Instance to persist across scenes.
DontDestroyOnLoad(gameObject);
if (!Packsize.Test()) {
Debug.LogError("[Steamworks.NET] Packsize Test returned false, the wrong version of Steamworks.NET is being run in this platform.", this);
}
if (!DllCheck.Test()) {
Debug.LogError("[Steamworks.NET] DllCheck Test returned false, One or more of the Steamworks binaries seems to be the wrong version.", this);
}
try {
// If Steam is not running or the game wasn't started through Steam, SteamAPI_RestartAppIfNecessary starts the
// Steam client and also launches this game again if the User owns it. This can act as a rudimentary form of DRM.
// Note that this will run which ever version you have installed in steam. Which may not be the precise executable
// we were currently running.
// Once you get a Steam AppID assigned by Valve, you need to replace AppId_t.Invalid with it and
// remove steam_appid.txt from the game depot. eg: "(AppId_t)480" or "new AppId_t(480)".
// See the Valve documentation for more information: https://partner.steamgames.com/doc/sdk/api#initialization_and_shutdown
if (SteamAPI.RestartAppIfNecessary(AppId_t.Invalid)) {
Debug.Log("[Steamworks.NET] Shutting down because RestartAppIfNecessary returned true. Steam will restart the application.");
Application.Quit();
return;
}
}
catch (System.DllNotFoundException e) { // We catch this exception here, as it will be the first occurrence of it.
Debug.LogError("[Steamworks.NET] Could not load [lib]steam_api.dll/so/dylib. It's likely not in the correct location. Refer to the README for more details.\n" + e, this);
Application.Quit();
return;
}
// Initializes the Steamworks API.
// If this returns false then this indicates one of the following conditions:
// [*] The Steam client isn't running. A running Steam client is required to provide implementations of the various Steamworks interfaces.
// [*] The Steam client couldn't determine the App ID of game. If you're running your application from the executable or debugger directly then you must have a [code-inline]steam_appid.txt[/code-inline] in your game directory next to the executable, with your app ID in it and nothing else. Steam will look for this file in the current working directory. If you are running your executable from a different directory you may need to relocate the [code-inline]steam_appid.txt[/code-inline] file.
// [*] Your application is not running under the same OS user context as the Steam client, such as a different user or administration access level.
// [*] Ensure that you own a license for the App ID on the currently active Steam account. Your game must show up in your Steam library.
// [*] Your App ID is not completely set up, i.e. in Release State: Unavailable, or it's missing default packages.
// Valve's documentation for this is located here:
// https://partner.steamgames.com/doc/sdk/api#initialization_and_shutdown
m_bInitialized = SteamAPI.Init();
if (!m_bInitialized) {
Debug.LogError("[Steamworks.NET] SteamAPI_Init() failed. Refer to Valve's documentation or the comment above this line for more information.", this);
return;
}
s_EverInitialized = true;
}
// This should only ever get called on first load and after an Assembly reload, You should never Disable the Steamworks Manager yourself.
protected virtual void OnEnable() {
if (s_instance == null) {
s_instance = this;
}
if (!m_bInitialized) {
return;
}
if (m_SteamAPIWarningMessageHook == null) {
// Set up our callback to receive warning messages from Steam.
// You must launch with "-debug_steamapi" in the launch args to receive warnings.
m_SteamAPIWarningMessageHook = new SteamAPIWarningMessageHook_t(SteamAPIDebugTextHook);
SteamClient.SetWarningMessageHook(m_SteamAPIWarningMessageHook);
}
}
// OnApplicationQuit gets called too early to shutdown the SteamAPI.
// Because the SteamManager should be persistent and never disabled or destroyed we can shutdown the SteamAPI here.
// Thus it is not recommended to perform any Steamworks work in other OnDestroy functions as the order of execution can not be garenteed upon Shutdown. Prefer OnDisable().
protected virtual void OnDestroy() {
if (s_instance != this) {
return;
}
s_instance = null;
if (!m_bInitialized) {
return;
}
SteamAPI.Shutdown();
}
protected virtual void Update() {
if (!m_bInitialized) {
return;
}
// Run Steam client callbacks
SteamAPI.RunCallbacks();
}
#else
public static bool Initialized {
get {
return false;
}
}
#endif // !DISABLESTEAMWORKS
}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ef4bffeda13d7a748973ff9204401c07
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
@@ -26,7 +26,7 @@ RectTransform:
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_ConstrainProportionsScale: 1
m_Children:
- {fileID: 3426810894855294520}
- {fileID: 4787860189400675959}
@@ -34,6 +34,7 @@ RectTransform:
- {fileID: 7481797042088762975}
- {fileID: 1351079257240028571}
- {fileID: 7655426131176574856}
- {fileID: 6114414043575968418}
m_Father: {fileID: 2613971938742376913}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
@@ -97,7 +98,7 @@ MonoBehaviour:
m_Top: 0
m_Bottom: 0
m_ChildAlignment: 3
m_Spacing: 3
m_Spacing: 9.55
m_ChildForceExpandWidth: 1
m_ChildForceExpandHeight: 1
m_ChildControlWidth: 0
@@ -141,7 +142,7 @@ RectTransform:
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 25, y: 25}
m_SizeDelta: {x: 35, y: 25}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &4734727163133092417
CanvasRenderer:
@@ -248,11 +249,11 @@ MonoBehaviour:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: dfa10de3e2846524089b5073175cd151, type: 3}
m_FontSize: 20
m_FontSize: 25
m_FontStyle: 0
m_BestFit: 1
m_MinSize: 0
m_MaxSize: 20
m_MaxSize: 25
m_Alignment: 5
m_AlignByGeometry: 0
m_RichText: 1
@@ -327,11 +328,11 @@ MonoBehaviour:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: dfa10de3e2846524089b5073175cd151, type: 3}
m_FontSize: 20
m_FontSize: 25
m_FontStyle: 0
m_BestFit: 1
m_MinSize: 0
m_MaxSize: 20
m_MaxSize: 25
m_Alignment: 5
m_AlignByGeometry: 0
m_RichText: 1
@@ -365,7 +366,7 @@ RectTransform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1300686028162242880}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalPosition: {x: 0, y: 0, z: -0.8}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
@@ -373,8 +374,8 @@ RectTransform:
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: -2.5252686, y: 4.807846}
m_SizeDelta: {x: 128.8411, y: 125.2335}
m_AnchoredPosition: {x: -3.1, y: 3.8}
m_SizeDelta: {x: 145.324, y: 141.255}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &2417251892043802145
CanvasRenderer:
@@ -450,7 +451,7 @@ RectTransform:
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 25, y: 25}
m_SizeDelta: {x: 35, y: 25}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &4210288393527682798
CanvasRenderer:
@@ -524,7 +525,7 @@ RectTransform:
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: -80, y: 0}
m_AnchoredPosition: {x: -65, y: 0}
m_SizeDelta: {x: 90, y: 30}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &8683684367698790289
@@ -557,7 +558,7 @@ MonoBehaviour:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: dfa10de3e2846524089b5073175cd151, type: 3}
m_FontSize: 18
m_FontSize: 22
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
@@ -636,11 +637,11 @@ MonoBehaviour:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: dfa10de3e2846524089b5073175cd151, type: 3}
m_FontSize: 20
m_FontSize: 25
m_FontStyle: 0
m_BestFit: 1
m_MinSize: 0
m_MaxSize: 20
m_MaxSize: 25
m_Alignment: 5
m_AlignByGeometry: 0
m_RichText: 1
@@ -674,7 +675,7 @@ RectTransform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1803836327666263803}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalPosition: {x: 0, y: 0, z: -1}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
@@ -682,8 +683,8 @@ RectTransform:
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: -2.5252686, y: 5.411278}
m_SizeDelta: {x: 103.0728, y: 103.0728}
m_AnchoredPosition: {x: -3, y: 4.3}
m_SizeDelta: {x: 123.437, y: 123.437}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &1406633831322811249
CanvasRenderer:
@@ -758,7 +759,7 @@ RectTransform:
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0.011796877, y: -0.0034751892}
m_SizeDelta: {x: 240.82, y: 30.11}
m_SizeDelta: {x: 301.5, y: 30.11}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3017584836910545941
CanvasRenderer:
@@ -929,11 +930,11 @@ MonoBehaviour:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: dfa10de3e2846524089b5073175cd151, type: 3}
m_FontSize: 20
m_FontSize: 25
m_FontStyle: 0
m_BestFit: 1
m_MinSize: 0
m_MaxSize: 20
m_MaxSize: 25
m_Alignment: 5
m_AlignByGeometry: 0
m_RichText: 1
@@ -1031,7 +1032,7 @@ RectTransform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2879873275385921024}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalPosition: {x: 0, y: 0, z: -4.84}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
@@ -1039,8 +1040,8 @@ RectTransform:
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: -220.10004, y: 60.999985}
m_SizeDelta: {x: 486.0001, y: 162}
m_AnchoredPosition: {x: -249.586, y: 55.236}
m_SizeDelta: {x: 544.972, y: 181.657}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &5280582095696078567
CanvasRenderer:
@@ -1116,7 +1117,7 @@ RectTransform:
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 25, y: 25}
m_SizeDelta: {x: 35, y: 25}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &7316102496420409225
CanvasRenderer:
@@ -1267,7 +1268,7 @@ RectTransform:
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: 68, y: 15}
m_AnchoredPosition: {x: 65.8, y: 15}
m_SizeDelta: {x: 100, y: 100}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &7982449552540773536
@@ -1439,18 +1440,18 @@ MonoBehaviour:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: dfa10de3e2846524089b5073175cd151, type: 3}
m_FontSize: 20
m_FontSize: 22
m_FontStyle: 0
m_BestFit: 1
m_MinSize: 0
m_MaxSize: 20
m_MaxSize: 22
m_Alignment: 5
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: 350234
m_Text: 2255023
--- !u!1 &3373680310273385780
GameObject:
m_ObjectHideFlags: 0
@@ -1485,7 +1486,7 @@ RectTransform:
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: -80, y: 0}
m_AnchoredPosition: {x: -65, y: 0}
m_SizeDelta: {x: 90, y: 30}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &8439903389052874367
@@ -1518,7 +1519,7 @@ MonoBehaviour:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: dfa10de3e2846524089b5073175cd151, type: 3}
m_FontSize: 18
m_FontSize: 22
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
@@ -1628,8 +1629,8 @@ RectTransform:
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: -80, y: 0}
m_SizeDelta: {x: 90, y: 30}
m_AnchoredPosition: {x: -77.114, y: 0}
m_SizeDelta: {x: 95.773, y: 30}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3533238549364255986
CanvasRenderer:
@@ -1661,7 +1662,7 @@ MonoBehaviour:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: dfa10de3e2846524089b5073175cd151, type: 3}
m_FontSize: 18
m_FontSize: 22
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
@@ -1744,8 +1745,8 @@ RectTransform:
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: -80, y: 0}
m_SizeDelta: {x: 90, y: 30}
m_AnchoredPosition: {x: -77.114, y: 0}
m_SizeDelta: {x: 95.773, y: 30}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &1513130722049452815
CanvasRenderer:
@@ -1777,7 +1778,7 @@ MonoBehaviour:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: dfa10de3e2846524089b5073175cd151, type: 3}
m_FontSize: 18
m_FontSize: 22
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
@@ -1935,7 +1936,7 @@ RectTransform:
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: -313.46063, y: 50}
m_AnchoredPosition: {x: -324.5, y: 45.2}
m_SizeDelta: {x: 233, y: 36}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &4531879398774622215
@@ -1980,7 +1981,7 @@ RectTransform:
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: -284.47562, y: 95.52118}
m_AnchoredPosition: {x: -326.8, y: 95.52118}
m_SizeDelta: {x: 290.97, y: 40}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3659676079848674504
@@ -2315,7 +2316,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 1}
m_AnchorMax: {x: 0.5, y: 1}
m_AnchoredPosition: {x: -251.8, y: -8.57}
m_AnchoredPosition: {x: -293.1, y: -8.57}
m_SizeDelta: {x: 200, y: 90}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &685427306910703188
@@ -2336,7 +2337,7 @@ MonoBehaviour:
m_Top: 0
m_Bottom: 0
m_ChildAlignment: 1
m_Spacing: -28.09
m_Spacing: -21.34
m_ChildForceExpandWidth: 1
m_ChildForceExpandHeight: 0
m_ChildControlWidth: 0
@@ -2400,7 +2401,7 @@ MonoBehaviour:
m_Top: 0
m_Bottom: 0
m_ChildAlignment: 5
m_Spacing: 3
m_Spacing: 9.55
m_ChildForceExpandWidth: 1
m_ChildForceExpandHeight: 1
m_ChildControlWidth: 0
@@ -2438,7 +2439,6 @@ RectTransform:
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 4148755058004053777}
- {fileID: 6494845825576441926}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
@@ -2496,7 +2496,7 @@ RectTransform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6682492808549074205}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalPosition: {x: 0, y: 0, z: -4.402}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
@@ -2506,8 +2506,8 @@ RectTransform:
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: -58.0542, y: 61.1}
m_SizeDelta: {x: 128.8411, y: 125.2335}
m_AnchoredPosition: {x: -67.251, y: 53.9}
m_SizeDelta: {x: 147.235, y: 143.112}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &2643565460332953122
CanvasRenderer:
@@ -2547,6 +2547,42 @@ MonoBehaviour:
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1.5
--- !u!1 &6853683024340712328
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6114414043575968418}
m_Layer: 0
m_Name: detail
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &6114414043575968418
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6853683024340712328}
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: 6494845825576441926}
m_Father: {fileID: 4148755058004053777}
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: -177.5, y: -5.2}
m_SizeDelta: {x: 100, y: 100}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &7151601246212580972
GameObject:
m_ObjectHideFlags: 0
@@ -2657,7 +2693,7 @@ RectTransform:
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0.17, y: -38.91}
m_SizeDelta: {x: 240.82, y: 22.17}
m_SizeDelta: {x: 301.5, y: 22.17}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &7024596005729876765
CanvasRenderer:
@@ -2764,11 +2800,11 @@ MonoBehaviour:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: dfa10de3e2846524089b5073175cd151, type: 3}
m_FontSize: 20
m_FontSize: 22
m_FontStyle: 0
m_BestFit: 1
m_MinSize: 0
m_MaxSize: 20
m_MaxSize: 22
m_Alignment: 5
m_AlignByGeometry: 0
m_RichText: 1
@@ -2850,7 +2886,7 @@ RectTransform:
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0.01, y: -38.91}
m_SizeDelta: {x: 240.58, y: 22.17}
m_SizeDelta: {x: 301.5, y: 22.17}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &4552223981125847129
CanvasRenderer:
@@ -2929,7 +2965,7 @@ RectTransform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7749677393578589965}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalPosition: {x: 0, y: 0, z: -5.087}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
@@ -2938,8 +2974,8 @@ RectTransform:
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: -597.3, y: 60.999985}
m_SizeDelta: {x: 271.5, y: 162}
m_AnchoredPosition: {x: -670.5, y: 54.942}
m_SizeDelta: {x: 302.667, y: 182.657}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &810551395488659022
CanvasRenderer:
@@ -3005,13 +3041,13 @@ RectTransform:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7889622464149315732}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
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: 4489091791280738956}
m_Father: {fileID: 2613971938742376913}
m_Father: {fileID: 6114414043575968418}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
@@ -3247,8 +3283,8 @@ RectTransform:
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: -80, y: 0}
m_SizeDelta: {x: 90, y: 30}
m_AnchoredPosition: {x: -77.114, y: 0}
m_SizeDelta: {x: 95.773, y: 30}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &492154790085150397
CanvasRenderer:
@@ -3280,7 +3316,7 @@ MonoBehaviour:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: dfa10de3e2846524089b5073175cd151, type: 3}
m_FontSize: 18
m_FontSize: 22
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
@@ -3478,7 +3514,7 @@ RectTransform:
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 25, y: 25}
m_SizeDelta: {x: 35, y: 25}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &6314649216240834215
CanvasRenderer:
@@ -3627,8 +3663,8 @@ RectTransform:
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: -80, y: 0}
m_SizeDelta: {x: 90, y: 30}
m_AnchoredPosition: {x: -77.114, y: 0}
m_SizeDelta: {x: 95.773, y: 30}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &1247215313835421715
CanvasRenderer:
@@ -3660,7 +3696,7 @@ MonoBehaviour:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: dfa10de3e2846524089b5073175cd151, type: 3}
m_FontSize: 18
m_FontSize: 22
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
@@ -1,5 +1,8 @@
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
public class loadSettlementTeamPrefab : MonoBehaviour
{
@@ -11,6 +14,18 @@ public class loadSettlementTeamPrefab : MonoBehaviour
public GameObject settleTeamCardsPrefab;
public GameObject objectToPutdown;
[Header("Settlement Entry Animation")]
[SerializeField] private bool enableAllySlotEntryTween = true;
[SerializeField] private float allySlotEntryOffsetX = 240f;
[SerializeField] private float allySlotEntryDuration = 0.35f;
[SerializeField] private float allySlotEntryStagger = 0.06f;
[SerializeField] private Ease allySlotEntryEase = Ease.OutCubic;
[SerializeField] private bool allySlotEntryUseUnscaledTime = true;
private readonly List<RectTransform> spawnedCardRoots = new List<RectTransform>();
private readonly List<Vector2> spawnedCardBasePositions = new List<Vector2>();
private Sequence allySlotEntrySequence;
/// <summary>
/// Instantiate five settlement cards under objectToPutdown and populate their fields.
/// Uses tuic.GetCurrentAllySOs() for profile/name when available, falls back to AllyHero_SO via allySlotIds.
@@ -18,6 +33,10 @@ public class loadSettlementTeamPrefab : MonoBehaviour
/// </summary>
public void PopulateSettlementCards()
{
KillAllySlotEntrySequence();
spawnedCardRoots.Clear();
spawnedCardBasePositions.Clear();
if (settleTeamCardsPrefab == null)
{
Debug.LogWarning("loadSettlementTeamPrefab: prefab not assigned");
@@ -86,6 +105,7 @@ public class loadSettlementTeamPrefab : MonoBehaviour
{
rectTransform.anchoredPosition = Vector2.zero;
rectTransform.sizeDelta = Vector2.zero;
spawnedCardRoots.Add(rectTransform);
}
// Diagnostic: verify parenting applied
@@ -244,6 +264,81 @@ public class loadSettlementTeamPrefab : MonoBehaviour
if (GameConfig.verboseLogs) Debug.Log($"[loadSettlementTeamPrefab] Created card for slot {i+1}");
}
CaptureSpawnedCardBasePositions();
if (enableAllySlotEntryTween)
SetSpawnedCardsAlpha(0f);
}
public IEnumerator PlayAllySlotEntryBeforeMask()
{
if (!enableAllySlotEntryTween)
yield break;
CaptureSpawnedCardBasePositions();
if (spawnedCardRoots.Count == 0)
yield break;
KillAllySlotEntrySequence();
float duration = Mathf.Max(0.01f, allySlotEntryDuration);
float stagger = Mathf.Max(0f, allySlotEntryStagger);
float offsetX = allySlotEntryOffsetX;
allySlotEntrySequence = DOTween.Sequence().SetUpdate(allySlotEntryUseUnscaledTime);
for (int i = 0; i < spawnedCardRoots.Count; i++)
{
RectTransform rt = spawnedCardRoots[i];
if (rt == null) continue;
Vector2 basePos = spawnedCardBasePositions[i];
CanvasGroup group = GetOrAddCanvasGroup(rt.transform);
if (group != null) group.alpha = 0f;
rt.anchoredPosition = new Vector2(basePos.x + offsetX, basePos.y);
float startAt = i * stagger; // top -> bottom (sibling order)
allySlotEntrySequence.Insert(startAt, rt.DOAnchorPos(basePos, duration).SetEase(allySlotEntryEase).SetUpdate(allySlotEntryUseUnscaledTime));
if (group != null)
allySlotEntrySequence.Insert(startAt, group.DOFade(1f, duration).SetEase(allySlotEntryEase).SetUpdate(allySlotEntryUseUnscaledTime));
}
if (allySlotEntrySequence != null && allySlotEntrySequence.active)
yield return allySlotEntrySequence.WaitForCompletion();
allySlotEntrySequence = null;
}
public bool HasSpawnedCards()
{
if (!enableAllySlotEntryTween)
return false;
if (spawnedCardRoots.Count > 0)
return true;
return objectToPutdown != null && objectToPutdown.transform.childCount > 0;
}
public void ShowSpawnedCardsInstantly()
{
CaptureSpawnedCardBasePositions();
SetSpawnedCardsAlpha(1f);
}
public void CompleteAllySlotEntryImmediately()
{
CaptureSpawnedCardBasePositions();
KillAllySlotEntrySequence();
for (int i = 0; i < spawnedCardRoots.Count; i++)
{
RectTransform rt = spawnedCardRoots[i];
if (rt == null) continue;
if (i < spawnedCardBasePositions.Count)
rt.anchoredPosition = spawnedCardBasePositions[i];
SetCanvasAlpha(rt.transform, 1f);
}
}
/// <summary>
@@ -264,4 +359,90 @@ public class loadSettlementTeamPrefab : MonoBehaviour
}
return $"{designation} {name}";
}
private void OnDisable()
{
KillAllySlotEntrySequence();
}
private void CaptureSpawnedCardBasePositions()
{
if (objectToPutdown == null)
return;
Canvas.ForceUpdateCanvases();
RectTransform parentRt = objectToPutdown.transform as RectTransform;
if (parentRt != null)
LayoutRebuilder.ForceRebuildLayoutImmediate(parentRt);
List<RectTransform> validCards = new List<RectTransform>();
// Prefer freshly instantiated references from this run.
for (int i = 0; i < spawnedCardRoots.Count; i++)
{
RectTransform rt = spawnedCardRoots[i];
if (rt == null) continue;
if (rt.parent != objectToPutdown.transform) continue;
validCards.Add(rt);
}
// Fallback scan if cache is empty.
if (validCards.Count == 0)
{
Transform parent = objectToPutdown.transform;
for (int i = 0; i < parent.childCount; i++)
{
RectTransform rt = parent.GetChild(i) as RectTransform;
if (rt == null) continue;
validCards.Add(rt);
}
}
validCards.Sort((a, b) => a.GetSiblingIndex().CompareTo(b.GetSiblingIndex()));
spawnedCardRoots.Clear();
spawnedCardBasePositions.Clear();
for (int i = 0; i < validCards.Count; i++)
{
RectTransform rt = validCards[i];
spawnedCardRoots.Add(rt);
spawnedCardBasePositions.Add(rt.anchoredPosition);
SetCanvasAlpha(rt.transform, 1f);
}
}
private void SetSpawnedCardsAlpha(float alpha)
{
float clamped = Mathf.Clamp01(alpha);
for (int i = 0; i < spawnedCardRoots.Count; i++)
{
RectTransform rt = spawnedCardRoots[i];
if (rt == null) continue;
SetCanvasAlpha(rt.transform, clamped);
}
}
private void KillAllySlotEntrySequence()
{
if (allySlotEntrySequence == null)
return;
if (allySlotEntrySequence.active)
allySlotEntrySequence.Kill(false);
allySlotEntrySequence = null;
}
private static CanvasGroup GetOrAddCanvasGroup(Transform root)
{
if (root == null) return null;
CanvasGroup cg = root.GetComponent<CanvasGroup>();
if (cg == null) cg = root.gameObject.AddComponent<CanvasGroup>();
return cg;
}
private static void SetCanvasAlpha(Transform root, float alpha)
{
CanvasGroup cg = GetOrAddCanvasGroup(root);
if (cg == null) return;
cg.alpha = Mathf.Clamp01(alpha);
}
}
+56 -2
View File
@@ -28,6 +28,7 @@ public class UI_Panel_Main : Singleton_Mono<UI_Panel_Main>
[SerializeField] GameObject ui_Panel_Character;
[SerializeField] Button button_Encyclopendia;
[SerializeField] GameObject ui_Panel_Encyclopendia;
[SerializeField] Transform notebook_father;
[SerializeField] Image image_Character;
[SerializeField] Button button_Mail;
[SerializeField] GameObject ui_Panel_Mail;
@@ -41,6 +42,9 @@ public class UI_Panel_Main : Singleton_Mono<UI_Panel_Main>
[SerializeField] GameObject ui_Panel_Story;
[SerializeField] Button button_Setting;
[SerializeField] GameObject ui_Panel_Setting;
[SerializeField] float prefab_Enter_Time = 0.35f;
[SerializeField] float prefab_Enter_Scale_From = 0.92f;
[SerializeField] Ease prefab_Enter_Ease = Ease.OutCubic;
[SerializeField] Button button_Idol;
[SerializeField] Button button_Select_Music;
@@ -240,6 +244,7 @@ public class UI_Panel_Main : Singleton_Mono<UI_Panel_Main>
readonly List<Tween> jiantou_Flash_Tweens = new();
readonly List<UI_ButtonTween> button_Tweeners = new();
bool selectScene_Loading;
GameObject encyclopedia_Instance;
protected override void In_Init()
{
@@ -302,8 +307,7 @@ public class UI_Panel_Main : Singleton_Mono<UI_Panel_Main>
button_Encyclopendia.onClick.AddListener(
() =>
{
gNotice.warning.display("此版本未开放该功能");
//Try_Open_Panel(ui_Panel_Encyclopendia);
Try_Open_Prefab(ui_Panel_Encyclopendia, ref encyclopedia_Instance);
});
if (button_Story != null)
button_Story.onClick.AddListener(
@@ -350,6 +354,56 @@ public class UI_Panel_Main : Singleton_Mono<UI_Panel_Main>
panel.SetActive(true);
return true;
}
GameObject Try_Open_Prefab(GameObject prefab, ref GameObject instance)
{
if (prefab == null) return null;
if (instance == null)
{
var parent = notebook_father != null ? notebook_father : (transform.parent != null ? transform.parent : transform);
instance = Instantiate(prefab, parent);
}
AssignPrefabCanvasCamera(instance);
if (!instance.activeSelf) instance.SetActive(true);
Play_Prefab_Enter(instance);
return instance;
}
void AssignPrefabCanvasCamera(GameObject instance)
{
if (instance == null) return;
var canvases = instance.GetComponentsInChildren<Canvas>(true);
if (canvases == null || canvases.Length == 0) return;
Camera targetCamera = Camera.main;
if (targetCamera == null)
{
var cams = Camera.allCameras;
if (cams != null && cams.Length > 0) targetCamera = cams[0];
}
if (targetCamera == null) return;
for (int i = 0; i < canvases.Length; i++)
{
var canvas = canvases[i];
if (canvas == null) continue;
canvas.worldCamera = targetCamera;
}
}
void Play_Prefab_Enter(GameObject panel)
{
if (panel == null) return;
var rect = panel.transform as RectTransform;
if (rect == null) rect = panel.GetComponent<RectTransform>();
if (rect == null) return;
rect.DOKill();
var cg = panel.GetComponent<CanvasGroup>();
if (cg == null) cg = panel.AddComponent<CanvasGroup>();
cg.DOKill();
rect.localScale = Vector3.one * prefab_Enter_Scale_From;
cg.alpha = 0f;
rect.DOScale(Vector3.one, prefab_Enter_Time).SetEase(prefab_Enter_Ease);
cg.DOFade(1f, prefab_Enter_Time).SetEase(prefab_Enter_Ease);
}
void Try_Load_Select_Scene()
{
if (selectScene_Loading)
@@ -782,6 +782,7 @@ RectTransform:
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1348388220439716234}
- {fileID: 426449599693635662}
- {fileID: 61400346345022494}
- {fileID: 6615900629953045265}
- {fileID: 2643255568127914845}
@@ -1555,7 +1556,7 @@ GameObject:
- component: {fileID: 5251759494279331112}
- component: {fileID: 162749160094985193}
m_Layer: 5
m_Name: UI outline
m_Name: steamID
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
@@ -1577,8 +1578,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 11.061996, y: -6.6999993}
m_SizeDelta: {x: -22.123108, y: 10.828098}
m_AnchoredPosition: {x: 0.00010681152, y: -0.023700237}
m_SizeDelta: {x: 0.0005, y: 3.0473}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &5251759494279331112
CanvasRenderer:
@@ -1609,19 +1610,19 @@ MonoBehaviour:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: dfa10de3e2846524089b5073175cd151, type: 3}
m_Font: {fileID: 12800000, guid: 2dfc162c344875b4da01e6a15073dce5, type: 3}
m_FontSize: 9
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 6
m_MaxSize: 40
m_Alignment: 0
m_Alignment: 4
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: SteamID_1145141919810
m_Text: 'UID: 76561199232899044'
--- !u!1 &1510303709083861045
GameObject:
m_ObjectHideFlags: 0
@@ -3668,7 +3669,7 @@ GameObject:
- component: {fileID: 4939770655549592904}
- component: {fileID: 2825520287440328854}
m_Layer: 5
m_Name: UI outline
m_Name: online status
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
@@ -3690,8 +3691,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: -5}
m_SizeDelta: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: -0.0000019073}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &4939770655549592904
CanvasRenderer:
@@ -3722,13 +3723,13 @@ MonoBehaviour:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: dfa10de3e2846524089b5073175cd151, type: 3}
m_FontSize: 11
m_Font: {fileID: 12800000, guid: 0f48904603cd9d74a8012b17a278cecc, type: 3}
m_FontSize: 12
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 7
m_MinSize: 1
m_MaxSize: 40
m_Alignment: 1
m_Alignment: 4
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
@@ -4886,6 +4887,11 @@ MonoBehaviour:
- sceneName:
backTargetScene:
depth: 4
steamMaterial: {fileID: 2100000, guid: 7701c15f96b443d408ac83222c9b869a, type: 2}
steamUserNameText: {fileID: 1863233853179500241}
steamUserID64Text: {fileID: 162749160094985193}
steamStatusText: {fileID: 2825520287440328854}
steamUserAvatarImage: {fileID: 8705733007173679835}
--- !u!114 &3093056817030257919
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -5523,8 +5529,8 @@ RectTransform:
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: -68.0999, y: -29.900002}
m_SizeDelta: {x: 222.8, y: 50}
m_AnchoredPosition: {x: -93.44745, y: -31.391098}
m_SizeDelta: {x: 273.4951, y: 26.8889}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &6137481741208887361
CanvasRenderer:
@@ -5555,19 +5561,19 @@ MonoBehaviour:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: bc54bd51ea8b84448ba1b65311872862, type: 3}
m_Font: {fileID: 12800000, guid: 2dfc162c344875b4da01e6a15073dce5, type: 3}
m_FontSize: 24
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
m_MaxSize: 40
m_BestFit: 1
m_MinSize: 12
m_MaxSize: 24
m_Alignment: 5
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: username
m_Text: USERNAME
--- !u!1 &5909026714256705340
GameObject:
m_ObjectHideFlags: 0
@@ -6308,8 +6314,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 1, y: 1}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: -39.499794, y: -39.500084}
m_SizeDelta: {x: 79, y: 79}
m_AnchoredPosition: {x: -42.5, y: -42.5}
m_SizeDelta: {x: 73, y: 73}
m_Pivot: {x: 1, y: 1}
--- !u!222 &1811290883119729690
CanvasRenderer:
@@ -6642,6 +6648,81 @@ MonoBehaviour:
m_Spacing: {x: 20, y: 20}
m_Constraint: 1
m_ConstraintCount: 4
--- !u!1 &7164294745953147232
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 426449599693635662}
- component: {fileID: 5634685933390596799}
- component: {fileID: 8500724824802082929}
m_Layer: 5
m_Name: boarder
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &426449599693635662
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7164294745953147232}
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: 3166511394318740470}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 1, y: 1}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: -39.499794, y: -39.500084}
m_SizeDelta: {x: 79, y: 79}
m_Pivot: {x: 1, y: 1}
--- !u!222 &5634685933390596799
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7164294745953147232}
m_CullTransparentMesh: 1
--- !u!114 &8500724824802082929
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7164294745953147232}
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: b118510512d10e94a87b7abe45f5af8e, 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 &7302066207285333480
GameObject:
m_ObjectHideFlags: 0
@@ -6,6 +6,7 @@ using UnityEngine.EventSystems;
using System.Collections.Generic;
using UnityEngine.Audio;
using Bansonic;
using Steamworks;
public class btmandtopController : MonoBehaviour, ICancelHandler
{
@@ -75,6 +76,13 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
public int depth;
}
[Header("Steam Integration")]
public Material steamMaterial;
public Text steamUserNameText;
public Text steamUserID64Text;
public Text steamStatusText;
public Image steamUserAvatarImage;
private UnityAction instantiateSettings;
private UnityAction instantiateUserInfo;
private UnityAction instantiateStore;
@@ -164,6 +172,8 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
if (button_Music != null)
button_Music.onClick.AddListener(ToggleMusicPicLocal);
UpdateSteamUserInfo();
var binder = BgmUiBinder.Instance != null ? BgmUiBinder.Instance : Object.FindAnyObjectByType<BgmUiBinder>();
if (binder != null && musicPicRoot != null)
{
@@ -869,4 +879,74 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
}
navSceneLoading = false;
}
public void UpdateSteamUserInfo()
{
if (!SteamManager.Initialized)
{
Debug.LogWarning("[Steam] SteamManager not initialized.");
if (steamStatusText != null)
steamStatusText.text = "Unknown Server";
return;
}
try
{
CSteamID steamID = SteamUser.GetSteamID();
if (steamUserNameText != null)
steamUserNameText.text = SteamFriends.GetPersonaName();
if (steamUserID64Text != null)
steamUserID64Text.text = "Uid : " + steamID.m_SteamID.ToString();
// Online status check
EPersonaState state = SteamFriends.GetPersonaState();
bool isOffline = (state == EPersonaState.k_EPersonaStateOffline);
if (steamStatusText != null)
{
steamStatusText.text = isOffline ? "Steam Offline" : "Steam Online";
}
if (steamUserAvatarImage != null)
{
// Assign material if offline, otherwise set to null (default)
steamUserAvatarImage.material = isOffline ? steamMaterial : null;
int iImage = SteamFriends.GetMediumFriendAvatar(steamID);
if (iImage != -1)
{
uint width, height;
if (SteamUtils.GetImageSize(iImage, out width, out height))
{
byte[] imageBuffer = new byte[width * height * 4];
if (SteamUtils.GetImageRGBA(iImage, imageBuffer, (int)(width * height * 4)))
{
Texture2D texture = new Texture2D((int)width, (int)height, TextureFormat.RGBA32, false);
texture.LoadRawTextureData(imageBuffer);
texture.Apply();
// Flip the texture vertically because Steam returns it upside down
Texture2D flipped = new Texture2D((int)width, (int)height, TextureFormat.RGBA32, false);
for (int y = 0; y < (int)height; y++)
{
Color[] pixels = texture.GetPixels(0, y, (int)width, 1);
flipped.SetPixels(0, (int)height - 1 - y, (int)width, 1, pixels);
}
flipped.Apply();
steamUserAvatarImage.sprite = Sprite.Create(flipped, new Rect(0, 0, width, height), new Vector2(0.5f, 0.5f));
}
}
}
}
}
catch (System.Exception e)
{
Debug.LogError("[Steam] Error updating steam info: " + e);
if (steamStatusText != null)
steamStatusText.text = "Unknown Server";
}
}
}
+468 -1
View File
@@ -23,10 +23,26 @@ public class GameManager : MonoBehaviour
[Header("canvas")]
public GameObject startCanvas;
public CanvasGroup cg_startCanvas;
public GameObject extraStartObject;
private CanvasGroup extraStartCG;
public float canvasFadeTime;
private Coroutine fade_canvasGroup;
public CanvasGroup settleCG;
[Header("Start Canvas Anim")]
[SerializeField] private bool enableStartCanvasDetailedAnim = true;
[SerializeField] private float startCanvasBtmFadeIn = 0.28f;
[SerializeField] private float startCanvasBtmFadeOut = 0.22f;
[SerializeField] private int startCanvasFlashCount = 2;
[SerializeField] private float startCanvasFlashUnit = 0.055f;
[SerializeField] private float startCanvasImageFadeIn = 0.2f;
[SerializeField] private float startCanvasMoveInDuration = 0.3f;
[SerializeField] private float startCanvasMoveOutDuration = 0.22f;
[SerializeField] private float startCanvasMoveStagger = 0.06f;
[SerializeField] private float startCanvasMoveOffsetY = 80f;
[SerializeField] private float startCanvasPanelExitOffsetY = 120f;
[SerializeField] private float startCanvasEntryGap = 0.04f;
[Header("buttons")]
public Button startGame;
public Button backtoSelectingPage;
@@ -67,6 +83,35 @@ public class GameManager : MonoBehaviour
private SongData currentSong;
private bool timeRecorded = false;
private Coroutine startCanvasIntroRoutine;
private bool startCanvasIntroCompleted = false;
private bool startCanvasExitStarted = false;
private RectTransform startupCanvasRoot;
private RectTransform startup_btm;
private RectTransform startup_boarder;
private RectTransform startup_image;
private RectTransform startup_text;
private RectTransform startup_start;
private RectTransform startup_escape;
private RectTransform startup_tips;
private RectTransform startup_uiBack;
private CanvasGroup startup_btmCg;
private CanvasGroup startup_boarderCg;
private CanvasGroup startup_imageCg;
private CanvasGroup startup_textCg;
private CanvasGroup startup_startCg;
private CanvasGroup startup_escapeCg;
private CanvasGroup startup_tipsCg;
private CanvasGroup startup_uiBackCg;
private Vector2 startup_boarderBasePos;
private Vector2 startup_imageBasePos;
private Vector2 startup_textBasePos;
private Vector2 startup_startBasePos;
private Vector2 startup_escapeBasePos;
private void SubscribeToPauseManager()
{
if (pauseSubscribed) return;
@@ -299,8 +344,15 @@ public class GameManager : MonoBehaviour
Debug.LogWarning("[GameManager] No SongData found for statistics tracking");
}
// Start a new per-level skill timeline log (overwrites previous level log).
string skillLogSessionName = currentSong != null ? currentSong.songName : "UnknownSong";
GameplaySkillLogger.BeginSession(skillLogSessionName);
Debug.Log($"[GameManager] Skill timeline log reset: {GameplaySkillLogger.LogFilePath}");
// Ensure start canvas and its CanvasGroup are active and visible at scene start
if (startCanvas != null && !startCanvas.activeSelf) startCanvas.SetActive(true);
if (startCanvas != null && startCanvas.transform.localScale.sqrMagnitude < 0.0001f)
startCanvas.transform.localScale = Vector3.one;
if (cg_startCanvas != null)
{
cg_startCanvas.alpha = 1f;
@@ -308,6 +360,18 @@ public class GameManager : MonoBehaviour
cg_startCanvas.blocksRaycasts = true;
}
// Initialize extra start object and its CanvasGroup
if (extraStartObject != null)
{
extraStartCG = extraStartObject.GetComponent<CanvasGroup>();
}
startCanvasExitStarted = false;
startCanvasIntroCompleted = false;
InitializeStartCanvasAnimRefs();
PrepareStartCanvasEntryState();
StartStartCanvasIntro();
// Try to apply assigned SongData (if any) to background and UI after scene load
StartCoroutine(WaitAndApplyAssignedSong(2f));
@@ -477,12 +541,28 @@ public class GameManager : MonoBehaviour
StopCoroutine(fade_canvasGroup);
fade_canvasGroup = null;
}
if (startCanvasIntroRoutine != null)
{
StopCoroutine(startCanvasIntroRoutine);
startCanvasIntroRoutine = null;
}
}
// Start the fade coroutine for the start canvas group (safe to call multiple times)
private void StartFadeStartCanvas()
{
if (cg_startCanvas == null || startCanvas == null) return;
if (startCanvasExitStarted) return;
startCanvasExitStarted = true;
if (startCanvasIntroRoutine != null)
{
StopCoroutine(startCanvasIntroRoutine);
startCanvasIntroRoutine = null;
}
SetStartButtonsInteractable(false);
if (fade_canvasGroup != null) StopCoroutine(fade_canvasGroup);
fade_canvasGroup = StartCoroutine(FadeStartCanvasCoroutine(canvasFadeTime));
}
@@ -491,13 +571,70 @@ public class GameManager : MonoBehaviour
{
if (cg_startCanvas == null || startCanvas == null) yield break;
if (enableStartCanvasDetailedAnim)
{
ApplyStartCanvasEntryFinalState();
// text / START / escape: sequential move down + fade out
yield return StartCoroutine(AnimateMoveAndFade(startup_text, startup_textCg, startup_textBasePos, startup_textBasePos + new Vector2(0f, -startCanvasMoveOffsetY), 1f, 0f, startCanvasMoveOutDuration, false));
yield return WaitRealtime(startCanvasMoveStagger);
yield return StartCoroutine(AnimateMoveAndFade(startup_start, startup_startCg, startup_startBasePos, startup_startBasePos + new Vector2(0f, -startCanvasMoveOffsetY), 1f, 0f, startCanvasMoveOutDuration, false));
yield return WaitRealtime(startCanvasMoveStagger);
yield return StartCoroutine(AnimateMoveAndFade(startup_escape, startup_escapeCg, startup_escapeBasePos, startup_escapeBasePos + new Vector2(0f, -startCanvasMoveOffsetY), 1f, 0f, startCanvasMoveOutDuration, false));
// TIPS / UIback: flicker out
Coroutine tipsOut = null;
Coroutine uiBackOut = null;
if (startup_tipsCg != null) tipsOut = StartCoroutine(FlashOut(startup_tipsCg, startCanvasFlashCount, startCanvasFlashUnit));
if (startup_uiBackCg != null) uiBackOut = StartCoroutine(FlashOut(startup_uiBackCg, startCanvasFlashCount, startCanvasFlashUnit));
// boarder + Image: wait for text/START/escape, then move down + fade
Coroutine boarderOut = StartCoroutine(AnimateMoveAndFade(startup_boarder, startup_boarderCg, startup_boarderBasePos, startup_boarderBasePos + new Vector2(0f, -startCanvasPanelExitOffsetY), 1f, 0f, startCanvasMoveOutDuration, false));
Coroutine imageOut = StartCoroutine(AnimateMoveAndFade(startup_image, startup_imageCg, startup_imageBasePos, startup_imageBasePos + new Vector2(0f, -startCanvasPanelExitOffsetY), 1f, 0f, startCanvasMoveOutDuration, false));
if (boarderOut != null || imageOut != null)
yield return WaitRealtime(startCanvasMoveOutDuration + 0.01f);
if (tipsOut != null || uiBackOut != null)
yield return WaitRealtime(startCanvasFlashUnit * Mathf.Max(2, startCanvasFlashCount * 2) + 0.01f);
// btm fade out at last
yield return StartCoroutine(FadeCanvasGroupAlpha(startup_btmCg, 1f, 0f, startCanvasBtmFadeOut, false));
cg_startCanvas.alpha = 0f;
cg_startCanvas.interactable = false;
cg_startCanvas.blocksRaycasts = false;
startCanvas.SetActive(false);
if (extraStartCG != null)
{
extraStartCG.alpha = 0f;
extraStartCG.interactable = false;
extraStartCG.blocksRaycasts = false;
if (extraStartObject != null) extraStartObject.SetActive(false);
}
fade_canvasGroup = null;
yield break;
}
float elapsed = 0f;
float startA = cg_startCanvas.alpha;
while (elapsed < duration)
{
elapsed += Time.unscaledDeltaTime;
float frac = Mathf.Clamp01(elapsed / Mathf.Max(0.0001f, duration));
cg_startCanvas.alpha = Mathf.Lerp(startA, 0f, frac);
float currentAlpha = Mathf.Lerp(startA, 0f, frac);
cg_startCanvas.alpha = currentAlpha;
if (extraStartCG != null)
{
extraStartCG.alpha = currentAlpha;
// Sync raycast state with alpha
bool visible = currentAlpha > 0.001f;
extraStartCG.interactable = visible;
extraStartCG.blocksRaycasts = visible;
}
yield return null;
}
@@ -505,9 +642,339 @@ public class GameManager : MonoBehaviour
cg_startCanvas.interactable = false;
cg_startCanvas.blocksRaycasts = false;
startCanvas.SetActive(false);
if (extraStartCG != null)
{
extraStartCG.alpha = 0f;
extraStartCG.interactable = false;
extraStartCG.blocksRaycasts = false;
if (extraStartObject != null) extraStartObject.SetActive(false);
}
fade_canvasGroup = null;
}
private void StartStartCanvasIntro()
{
if (!enableStartCanvasDetailedAnim)
{
ApplyStartCanvasEntryFinalState();
startCanvasIntroCompleted = true;
SetStartButtonsInteractable(true);
return;
}
if (startCanvasIntroRoutine != null)
{
StopCoroutine(startCanvasIntroRoutine);
startCanvasIntroRoutine = null;
}
startCanvasIntroRoutine = StartCoroutine(StartCanvasIntroCoroutine());
}
private IEnumerator StartCanvasIntroCoroutine()
{
startCanvasIntroCompleted = false;
SetStartButtonsInteractable(false);
yield return StartCoroutine(FadeCanvasGroupAlpha(startup_btmCg, 0f, 1f, startCanvasBtmFadeIn, false));
float flashDuration = startCanvasFlashUnit * Mathf.Max(2, startCanvasFlashCount * 2);
Coroutine boarderIn = null;
Coroutine tipsIn = null;
Coroutine uiBackIn = null;
if (startup_boarderCg != null) boarderIn = StartCoroutine(FlashIn(startup_boarderCg, startCanvasFlashCount, startCanvasFlashUnit, false));
if (startup_tipsCg != null) tipsIn = StartCoroutine(FlashIn(startup_tipsCg, startCanvasFlashCount, startCanvasFlashUnit, false));
if (startup_uiBackCg != null) uiBackIn = StartCoroutine(FlashIn(startup_uiBackCg, startCanvasFlashCount, startCanvasFlashUnit, false));
if (boarderIn != null || tipsIn != null || uiBackIn != null)
yield return WaitRealtime(flashDuration + 0.01f);
yield return WaitRealtime(startCanvasEntryGap);
yield return StartCoroutine(FadeCanvasGroupAlpha(startup_imageCg, 0f, 1f, startCanvasImageFadeIn, false));
Vector2 textFrom = startup_textBasePos + new Vector2(0f, -startCanvasMoveOffsetY);
Vector2 startFrom = startup_startBasePos + new Vector2(0f, -startCanvasMoveOffsetY);
Vector2 escapeFrom = startup_escapeBasePos + new Vector2(0f, -startCanvasMoveOffsetY);
yield return StartCoroutine(AnimateMoveAndFade(startup_text, startup_textCg, textFrom, startup_textBasePos, 0f, 1f, startCanvasMoveInDuration, false));
yield return WaitRealtime(startCanvasMoveStagger);
yield return StartCoroutine(AnimateMoveAndFade(startup_start, startup_startCg, startFrom, startup_startBasePos, 0f, 1f, startCanvasMoveInDuration, true));
yield return WaitRealtime(startCanvasMoveStagger);
yield return StartCoroutine(AnimateMoveAndFade(startup_escape, startup_escapeCg, escapeFrom, startup_escapeBasePos, 0f, 1f, startCanvasMoveInDuration, true));
startCanvasIntroCompleted = true;
SetStartButtonsInteractable(true);
startCanvasIntroRoutine = null;
}
private void InitializeStartCanvasAnimRefs()
{
startupCanvasRoot = null;
startup_btm = null;
startup_boarder = null;
startup_image = null;
startup_text = null;
startup_start = null;
startup_escape = null;
startup_tips = null;
startup_uiBack = null;
startup_btmCg = null;
startup_boarderCg = null;
startup_imageCg = null;
startup_textCg = null;
startup_startCg = null;
startup_escapeCg = null;
startup_tipsCg = null;
startup_uiBackCg = null;
if (startCanvas == null) return;
Transform root = startCanvas.transform;
startupCanvasRoot = FindRectByName(root, "startupCanvas");
if (startupCanvasRoot == null) startupCanvasRoot = root as RectTransform;
if (startupCanvasRoot == null) return;
startup_btm = FindDirectChildRectByName(startupCanvasRoot, "btm");
startup_boarder = FindDirectChildRectByName(startupCanvasRoot, "boarder");
startup_image = FindDirectChildRectByName(startupCanvasRoot, "Image");
startup_text = FindDirectChildRectByName(startupCanvasRoot, "text");
startup_start = FindDirectChildRectByName(startupCanvasRoot, "START");
startup_escape = FindDirectChildRectByName(startupCanvasRoot, "escape");
startup_tips = FindDirectChildRectByName(startupCanvasRoot, "TIPS");
startup_uiBack = FindDirectChildRectByName(startupCanvasRoot, "UIback");
if (startup_tips == null) startup_tips = FindRectByName(startupCanvasRoot, "TIPS");
if (startup_uiBack == null) startup_uiBack = FindRectByName(startupCanvasRoot, "UIback");
startup_btmCg = GetOrAddCanvasGroup(startup_btm);
startup_boarderCg = GetOrAddCanvasGroup(startup_boarder);
startup_imageCg = GetOrAddCanvasGroup(startup_image);
startup_textCg = GetOrAddCanvasGroup(startup_text);
startup_startCg = GetOrAddCanvasGroup(startup_start);
startup_escapeCg = GetOrAddCanvasGroup(startup_escape);
startup_tipsCg = GetOrAddCanvasGroup(startup_tips);
startup_uiBackCg = GetOrAddCanvasGroup(startup_uiBack);
if (startup_boarder != null) startup_boarderBasePos = startup_boarder.anchoredPosition;
if (startup_image != null) startup_imageBasePos = startup_image.anchoredPosition;
if (startup_text != null) startup_textBasePos = startup_text.anchoredPosition;
if (startup_start != null) startup_startBasePos = startup_start.anchoredPosition;
if (startup_escape != null) startup_escapeBasePos = startup_escape.anchoredPosition;
}
private void PrepareStartCanvasEntryState()
{
if (cg_startCanvas == null || startCanvas == null) return;
if (!startCanvas.activeSelf) startCanvas.SetActive(true);
cg_startCanvas.alpha = 1f;
cg_startCanvas.interactable = true;
cg_startCanvas.blocksRaycasts = true;
if (extraStartCG != null)
{
if (extraStartObject != null && !extraStartObject.activeSelf) extraStartObject.SetActive(true);
extraStartCG.alpha = 1f;
extraStartCG.interactable = true;
extraStartCG.blocksRaycasts = true;
}
if (!enableStartCanvasDetailedAnim)
{
ApplyStartCanvasEntryFinalState();
return;
}
SetCanvasGroupAlpha(startup_btmCg, 0f, false);
SetCanvasGroupAlpha(startup_boarderCg, 0f, false);
SetCanvasGroupAlpha(startup_imageCg, 0f, false);
SetCanvasGroupAlpha(startup_textCg, 0f, false);
SetCanvasGroupAlpha(startup_startCg, 0f, true);
SetCanvasGroupAlpha(startup_escapeCg, 0f, true);
SetCanvasGroupAlpha(startup_tipsCg, 0f, false);
SetCanvasGroupAlpha(startup_uiBackCg, 0f, false);
if (startup_boarder != null) startup_boarder.anchoredPosition = startup_boarderBasePos;
if (startup_image != null) startup_image.anchoredPosition = startup_imageBasePos;
if (startup_text != null) startup_text.anchoredPosition = startup_textBasePos + new Vector2(0f, -startCanvasMoveOffsetY);
if (startup_start != null) startup_start.anchoredPosition = startup_startBasePos + new Vector2(0f, -startCanvasMoveOffsetY);
if (startup_escape != null) startup_escape.anchoredPosition = startup_escapeBasePos + new Vector2(0f, -startCanvasMoveOffsetY);
}
private void ApplyStartCanvasEntryFinalState()
{
if (startup_boarder != null) startup_boarder.anchoredPosition = startup_boarderBasePos;
if (startup_image != null) startup_image.anchoredPosition = startup_imageBasePos;
if (startup_text != null) startup_text.anchoredPosition = startup_textBasePos;
if (startup_start != null) startup_start.anchoredPosition = startup_startBasePos;
if (startup_escape != null) startup_escape.anchoredPosition = startup_escapeBasePos;
SetCanvasGroupAlpha(startup_btmCg, 1f, false);
SetCanvasGroupAlpha(startup_boarderCg, 1f, false);
SetCanvasGroupAlpha(startup_imageCg, 1f, false);
SetCanvasGroupAlpha(startup_textCg, 1f, false);
SetCanvasGroupAlpha(startup_startCg, 1f, true);
SetCanvasGroupAlpha(startup_escapeCg, 1f, true);
SetCanvasGroupAlpha(startup_tipsCg, 1f, false);
SetCanvasGroupAlpha(startup_uiBackCg, 1f, false);
}
private void SetStartButtonsInteractable(bool interactable)
{
if (startGame != null) startGame.interactable = interactable;
if (backtoSelectingPage != null) backtoSelectingPage.interactable = interactable;
}
private IEnumerator AnimateMoveAndFade(RectTransform rt, CanvasGroup cg, Vector2 from, Vector2 to, float fromAlpha, float toAlpha, float duration, bool allowRaycastWhenVisible)
{
if (rt == null || cg == null) yield break;
float dur = Mathf.Max(0.01f, duration);
float elapsed = 0f;
rt.anchoredPosition = from;
SetCanvasGroupAlpha(cg, fromAlpha, allowRaycastWhenVisible);
while (elapsed < dur)
{
elapsed += Time.unscaledDeltaTime;
float t = EaseOutCubic(Mathf.Clamp01(elapsed / dur));
rt.anchoredPosition = Vector2.LerpUnclamped(from, to, t);
float a = Mathf.LerpUnclamped(fromAlpha, toAlpha, t);
SetCanvasGroupAlpha(cg, a, allowRaycastWhenVisible);
yield return null;
}
rt.anchoredPosition = to;
SetCanvasGroupAlpha(cg, toAlpha, allowRaycastWhenVisible);
}
private IEnumerator FadeCanvasGroupAlpha(CanvasGroup cg, float fromAlpha, float toAlpha, float duration, bool allowRaycastWhenVisible)
{
if (cg == null) yield break;
float dur = Mathf.Max(0.01f, duration);
float elapsed = 0f;
SetCanvasGroupAlpha(cg, fromAlpha, allowRaycastWhenVisible);
while (elapsed < dur)
{
elapsed += Time.unscaledDeltaTime;
float t = EaseOutCubic(Mathf.Clamp01(elapsed / dur));
float a = Mathf.LerpUnclamped(fromAlpha, toAlpha, t);
SetCanvasGroupAlpha(cg, a, allowRaycastWhenVisible);
yield return null;
}
SetCanvasGroupAlpha(cg, toAlpha, allowRaycastWhenVisible);
}
private IEnumerator FlashIn(CanvasGroup cg, int flashes, float unitDuration, bool allowRaycastWhenVisible)
{
if (cg == null) yield break;
int count = Mathf.Max(1, flashes);
float step = Mathf.Max(0.01f, unitDuration);
SetCanvasGroupAlpha(cg, 0f, allowRaycastWhenVisible);
for (int i = 0; i < count; i++)
{
SetCanvasGroupAlpha(cg, 1f, allowRaycastWhenVisible);
yield return WaitRealtime(step);
if (i < count - 1)
{
SetCanvasGroupAlpha(cg, 0f, allowRaycastWhenVisible);
yield return WaitRealtime(step);
}
}
SetCanvasGroupAlpha(cg, 1f, allowRaycastWhenVisible);
}
private IEnumerator FlashOut(CanvasGroup cg, int flashes, float unitDuration)
{
if (cg == null) yield break;
int count = Mathf.Max(1, flashes);
float step = Mathf.Max(0.01f, unitDuration);
SetCanvasGroupAlpha(cg, 1f, false);
for (int i = 0; i < count; i++)
{
SetCanvasGroupAlpha(cg, 0f, false);
yield return WaitRealtime(step);
if (i < count - 1)
{
SetCanvasGroupAlpha(cg, 1f, false);
yield return WaitRealtime(step);
}
}
SetCanvasGroupAlpha(cg, 0f, false);
}
private IEnumerator WaitRealtime(float duration)
{
float elapsed = 0f;
float dur = Mathf.Max(0f, duration);
while (elapsed < dur)
{
elapsed += Time.unscaledDeltaTime;
yield return null;
}
}
private static float EaseOutCubic(float t)
{
float inv = 1f - t;
return 1f - inv * inv * inv;
}
private static CanvasGroup GetOrAddCanvasGroup(RectTransform rt)
{
if (rt == null) return null;
CanvasGroup cg = rt.GetComponent<CanvasGroup>();
if (cg == null) cg = rt.gameObject.AddComponent<CanvasGroup>();
return cg;
}
private static void SetCanvasGroupAlpha(CanvasGroup cg, float alpha, bool allowRaycastWhenVisible)
{
if (cg == null) return;
float a = Mathf.Clamp01(alpha);
cg.alpha = a;
bool visible = a > 0.001f;
cg.interactable = visible && allowRaycastWhenVisible;
cg.blocksRaycasts = visible && allowRaycastWhenVisible;
}
private static RectTransform FindDirectChildRectByName(Transform root, string exactName)
{
if (root == null || string.IsNullOrEmpty(exactName)) return null;
for (int i = 0; i < root.childCount; i++)
{
Transform c = root.GetChild(i);
if (c == null) continue;
if (string.Equals(c.name, exactName, System.StringComparison.OrdinalIgnoreCase))
return c as RectTransform;
}
return null;
}
private static RectTransform FindRectByName(Transform root, string exactName)
{
Transform t = FindDescendantByName(root, exactName);
return t as RectTransform;
}
private static Transform FindDescendantByName(Transform root, string exactName)
{
if (root == null || string.IsNullOrEmpty(exactName)) return null;
Transform[] all = root.GetComponentsInChildren<Transform>(true);
for (int i = 0; i < all.Length; i++)
{
Transform t = all[i];
if (t == null) continue;
if (string.Equals(t.name, exactName, System.StringComparison.OrdinalIgnoreCase))
return t;
}
return null;
}
private void OnAllNotesSpawned()
{
// All notes have been spawned - trigger settlement decision logic
+10 -68
View File
@@ -60,11 +60,6 @@ public class HoldNote : BaseNote
[Range(1f, 2f)]
public float holdWindowMultiplier = 1.3f;
[Header("Custom Animation")]
public GameObject holdAnimationPrefab;
private GameObject activeHoldAnimationInstance;
private Coroutine delayedHoldAnimationCoroutine;
public float visualSpeedMultiplier = 1f; // injected from NoteSpawner to adapt windows when visual speed changes
// track when the hold actually started (real-time) so we can compute held fraction
@@ -121,6 +116,7 @@ public class HoldNote : BaseNote
int baseScore = 0;
var bmm = ally != null ? ally.bmm : null;
if (bmm == null) bmm = BeatmapManager.Instance;
if (bmm == null) bmm = GetBeatmapManagerCached();
if (bmm != null) baseScore = Mathf.Max(0, bmm.perNoteScore);
@@ -322,58 +318,6 @@ public class HoldNote : BaseNote
}
}
private void StartHoldEffects()
{
StopHoldEffects(); // cleanup any existing
if (gameObject.activeInHierarchy)
{
delayedHoldAnimationCoroutine = StartCoroutine(DelayedStartHoldEffects());
}
}
private void StopHoldEffects()
{
if (delayedHoldAnimationCoroutine != null)
{
StopCoroutine(delayedHoldAnimationCoroutine);
delayedHoldAnimationCoroutine = null;
}
if (activeHoldAnimationInstance != null)
{
Destroy(activeHoldAnimationInstance);
activeHoldAnimationInstance = null;
}
AnimationController.Global?.StopHoldParticles();
}
private IEnumerator DelayedStartHoldEffects()
{
yield return new WaitForSeconds(0.1f);
// Start standard particles
AnimationController.Global?.StartHoldParticles(noteColor);
// Instantiate custom prefab if assigned
if (holdAnimationPrefab != null)
{
// find spawn position: prefer the track effect position if available from AnimationController
Vector3 spawnPos = transform.position;
var ac = AnimationController.Global;
if (ac != null)
{
// Logic to find slot position (simplified: use ac's position or the note's)
// In AnimationController.PlayDestroyAnimation, it uses redEffect.transform.position etc.
// We don't have direct access to those private fields, but we can assume
// the note is at the judge line when this starts.
}
activeHoldAnimationInstance = Instantiate(holdAnimationPrefab, spawnPos, Quaternion.identity);
// Optionally parent to the note or track
// activeHoldAnimationInstance.transform.SetParent(this.transform, false);
}
}
private IEnumerator DelayedDisableAnimation(GameObject animationObject, float delay)
{
// No-op: we used to disable the shared AnimationController here which stopped coroutines.
@@ -393,6 +337,7 @@ public class HoldNote : BaseNote
this.trackIndex = trackIndex;
// also set BaseNote.TrackIndex so other systems using TrackIndex property work consistently
this.TrackIndex = trackIndex;
this.speed = speed;
this.startTime = time;
this.delay = delay;
@@ -497,7 +442,7 @@ public class HoldNote : BaseNote
if (!isHoldActive && jm != null && jm.IsStartJudged(noteID) && !jm.HasNoteReleased(noteID) && keyHeld)
{
isHoldActive = true;
StartHoldEffects();
AnimationController.Global?.StartHoldParticles(noteColor);
if (debugEnabled) Debug.Log($"[HoldNote] Start particles started for {noteID} color={noteColor} at time={now:F3}");
}
@@ -531,7 +476,7 @@ public class HoldNote : BaseNote
if (isHoldActive && keyUp)
{
isHoldActive = false;
StopHoldEffects();
AnimationController.Global?.StopHoldParticles();
if (debugEnabled) Debug.Log($"[HoldNote] KeyUp {keyToPress} detected. NoteID: {noteID}, Segment: {segment}, Type: {type}");
if (!hasReleased)
@@ -706,7 +651,7 @@ public class HoldNote : BaseNote
JudgeManager.Instance.RegisterStartJudged(noteID, true);
isHoldActive = true;
PlayHitAnimation();
StartHoldEffects();
AnimationController.Global?.StartHoldParticles(noteColor);
holdStartTime = pressTime;
HoldNoteJudgePool.RegisterStart(noteID, pressTime, hitTime, scheduledEndTime, noteColor, trackIndex, (object)noteData);
@@ -762,7 +707,7 @@ public class HoldNote : BaseNote
isJudged = true;
// Terminate input - mark key as released
isHoldActive = false;
StopHoldEffects();
AnimationController.Global?.StopHoldParticles();
return;
}
@@ -932,7 +877,7 @@ public class HoldNote : BaseNote
JudgeManager.Instance.RegisterStartJudged(noteID, true);
isHoldActive = true;
PlayHitAnimation();
StartHoldEffects();
AnimationController.Global?.StartHoldParticles(noteColor);
// record when the hold started so we can compute held fraction later
holdStartTime = pressTime;
@@ -948,7 +893,7 @@ public class HoldNote : BaseNote
JudgeManager.Instance.RegisterStartJudged(noteID, true);
isHoldActive = true;
PlayHitAnimation();
StartHoldEffects();
AnimationController.Global?.StartHoldParticles(noteColor);
holdStartTime = pressTime;
@@ -962,7 +907,7 @@ public class HoldNote : BaseNote
JudgeManager.Instance.RegisterStartJudged(noteID, true);
isHoldActive = true;
PlayHitAnimation();
StartHoldEffects();
AnimationController.Global?.StartHoldParticles(noteColor);
holdStartTime = pressTime;
@@ -1250,7 +1195,7 @@ public class HoldNote : BaseNote
if (JudgeManager.IsDebugEnabled) Debug.Log($"[HoldNote] Hold end NotifyNoteHit called: TrackIndex={TrackIndex} trackIndexField={trackIndex} result={result} triggeredHold={triggeredHold} triggeredTap={triggeredTap}");
}
StopHoldEffects();
AnimationController.Global?.StopHoldParticles();
// Release the track lock immediately after end evaluation so the next hold can start on time
ReleaseTrackJudgeLockSafe();
@@ -1372,9 +1317,6 @@ public class HoldNote : BaseNote
// Restore materials before returning to pool/reusing
RestoreOriginalMaterials();
// Stop any active hold animations or delayed starts
StopHoldEffects();
hasReleased = false;
segment = NoteSegment.None;
keyToPress = KeyCode.None;
@@ -13,6 +13,7 @@ public class HoldNoteController : MonoBehaviour
private bool useAbsolutePositioning = false;
private Vector3 spawnPosition;
private float baseSpawnYOffset = 0f; // can be negative to offset upward for delayed segments
private float visualOffset = 0f; // additional Y offset for visual compensation
private void Update()
{
@@ -44,6 +45,7 @@ public class HoldNoteController : MonoBehaviour
activationTime = hitTime - travelTime;
isMoving = false;
useAbsolutePositioning = false;
visualOffset = 0f;
}
/// <summary>
@@ -54,6 +56,7 @@ public class HoldNoteController : MonoBehaviour
activationTime = Time.time + segmentDelay;
isMoving = false;
useAbsolutePositioning = false;
visualOffset = 0f;
}
public void SetSpeed(float newSpeed)
@@ -66,11 +69,12 @@ public class HoldNoteController : MonoBehaviour
/// spawnPoint should be the original spawn point for the lane.
/// baseSpawnYOffset is applied to keep delayed segments offset upward (can be negative).
/// </summary>
public void ConfigureAbsolutePositioning(Vector3 spawnPoint, float newActivationTime, float spawnYOffset)
public void ConfigureAbsolutePositioning(Vector3 spawnPoint, float newActivationTime, float spawnYOffset, float newVisualOffset = 0f)
{
spawnPosition = spawnPoint;
activationTime = newActivationTime;
baseSpawnYOffset = spawnYOffset;
visualOffset = newVisualOffset;
useAbsolutePositioning = true;
isMoving = true;
@@ -81,6 +85,7 @@ public class HoldNoteController : MonoBehaviour
public void StopMovement()
{
isMoving = false;
visualOffset = 0f;
}
private void OnTriggerEnter2D(Collider2D collision)
@@ -109,6 +114,7 @@ public class HoldNoteController : MonoBehaviour
public bool UsesAbsolutePositioning => useAbsolutePositioning;
public float BaseSpawnYOffset => baseSpawnYOffset;
public Vector3 SpawnPosition => spawnPosition;
public float VisualOffset => visualOffset;
private void ApplyAbsolutePosition(float time)
{
@@ -116,7 +122,8 @@ public class HoldNoteController : MonoBehaviour
float travelDistance = speed * elapsed;
Vector3 newPos = spawnPosition;
newPos.y -= (baseSpawnYOffset + travelDistance);
// Apply visual offset (positive moves the note down its path earlier)
newPos.y -= (baseSpawnYOffset + travelDistance + visualOffset);
transform.position = newPos;
}
}
@@ -12,7 +12,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 088ed1ed6b6731f43ad3166781e3e931, type: 3}
m_Name: NoteJudgeConfig
m_EditorClassIdentifier:
perfectRange: 0.05
greatRange: 0.1
goodRange: 0.15
missRange: 0.2
perfectRange: 0.1
greatRange: 0.15
goodRange: 0.2
missRange: 0.25
+46 -3
View File
@@ -1,3 +1,4 @@
using System.Globalization;
using UnityEngine;
public class Note : BaseNote
@@ -55,12 +56,10 @@ public class Note : BaseNote
private static int ComputePmDeltaFromJudge(string judgeResult, AllyCombatant ally)
{
// If current character slot has no ally or is dead, then this corresponding track will also not get idol score
if (ally == null || ally.IsDead) return 0;
int baseScore = 0;
var bmm = ally != null ? ally.bmm : null;
if (bmm == null) bmm = BeatmapManager.Instance;
if (bmm == null) bmm = GetBeatmapManagerCached();
if (bmm != null) baseScore = Mathf.Max(0, bmm.perNoteScore);
@@ -109,6 +108,42 @@ public class Note : BaseNote
sm.trackPerfectCounts[trackIndex] += 1;
}
private string BuildNoteLogId()
{
if (noteData != null)
{
return "tap:"
+ TrackIndex
+ ":"
+ noteData.time.ToString("0.###", CultureInfo.InvariantCulture)
+ ":"
+ gameObject.GetInstanceID();
}
return "tap:"
+ TrackIndex
+ ":"
+ hitTime.ToString("0.###", CultureInfo.InvariantCulture)
+ ":"
+ gameObject.GetInstanceID();
}
private void LogTapJudge(string judgeResult, float rawOffsetMs, bool rewrittenToPerfect, bool autoplay, float actionTime)
{
GameplaySkillLogger.RecordJudgeResult(
"Tap",
"Single",
TrackIndex,
BuildNoteLogId(),
judgeResult,
rawOffsetMs,
rewrittenToPerfect,
autoplay,
actionTime,
hitTime,
float.NaN);
}
private void Awake()
{
anim = GetComponent<AnimationController>();
@@ -218,6 +253,7 @@ public class Note : BaseNote
ScoreManager.Instance.RecordOffset(0f);
const string judgeResult = "Perfect";
if (noteData != null) noteData.judgeResult = judgeResult;
InputManager.Instance?.ShowJudgeResult(TrackIndex, judgeResult);
JudgeSoundManager.Instance?.PlayJudgeSound(judgeResult);
@@ -241,6 +277,7 @@ public class Note : BaseNote
}
try { SkillBuilder.Instance?.NotifyNoteHit(TrackIndex, judgeResult, SkillDefinition.NoteTypeTrigger.Tap); } catch { }
LogTapJudge(judgeResult, 0f, false, true, pressTime);
Judge();
}
@@ -368,10 +405,13 @@ public class Note : BaseNote
if (!string.IsNullOrEmpty(judgeResult))
{
string originalJudge = judgeResult;
bool rewrittenToPerfect = false;
if (TryRewriteNonMissJudgeToPerfect(TrackIndex, ref judgeResult))
{
rewrittenToPerfect = true;
AdjustCountsAfterRewriteToPerfect(TrackIndex, originalJudge);
}
if (noteData != null) noteData.judgeResult = judgeResult;
InputManager.Instance?.ShowJudgeResult(TrackIndex, judgeResult);
JudgeSoundManager.Instance?.PlayJudgeSound(judgeResult);
@@ -396,6 +436,7 @@ public class Note : BaseNote
}
try { SkillBuilder.Instance?.NotifyNoteHit(TrackIndex, judgeResult, SkillDefinition.NoteTypeTrigger.Tap); } catch { }
LogTapJudge(judgeResult, rawOffsetMs, rewrittenToPerfect, false, pressTime);
}
Judge();
@@ -452,6 +493,7 @@ public class Note : BaseNote
ScoreManager.Instance?.RecordOffset(0);
if (JudgeManager.IsDebugEnabled) Debug.Log($"{keyToPress} Miss");
if (noteData != null) noteData.judgeResult = "Miss";
InputManager.Instance?.ShowJudgeResult(TrackIndex, "Miss");
JudgeSoundManager.Instance?.PlayJudgeSound("Miss");
@@ -476,6 +518,7 @@ public class Note : BaseNote
}
try { SkillBuilder.Instance?.NotifyNoteHit(TrackIndex, "Miss", SkillDefinition.NoteTypeTrigger.Tap); } catch { }
LogTapJudge("Miss", 0f, false, false, Time.time);
// notify global judge manager that this note has been finally judged (miss)
JudgeManager.Instance?.NotifyNoteJudged();
@@ -5,6 +5,7 @@ using UnityEngine.Serialization;
using UnityEngine.SceneManagement;
using System.Collections;
using UnityEngine.Audio;
using DG.Tweening;
public class settlementController : MonoBehaviour
{
@@ -124,6 +125,10 @@ public class settlementController : MonoBehaviour
[SerializeField] private float introMoveDuration = 0.42f;
[SerializeField] private float introNumberDuration = 0.48f;
[SerializeField] private float introStepGap = 0.06f;
[SerializeField] private float introMvpEnterYOffset = 120f;
[SerializeField] private float introMvpEnterDuration = 0.42f;
[SerializeField] private Ease introMvpEnterEase = Ease.OutCubic;
[SerializeField] private bool introTweenUseUnscaledTime = true;
private int targetPmScore;
private int targetIdolScore;
@@ -139,6 +144,11 @@ public class settlementController : MonoBehaviour
private float targetGreatPercent;
private float targetGoodPercent;
private float targetMissPercent;
private Tween mvpEntryTween;
private bool skipIntroRequested;
private Transform cachedIntroMvpTransform;
private Vector3 cachedIntroMvpBaseLocalPos;
private bool hasCachedIntroMvpBaseLocalPos;
private void Awake()
{
@@ -175,6 +185,16 @@ public class settlementController : MonoBehaviour
}
}
private void Update()
{
if (settlementIntroCoroutine == null) return;
if (!enableDetailedSettlementIntro) return;
if (Input.GetMouseButtonDown(0))
{
SkipSettlementIntroAnimations();
}
}
private void OnDestroy()
{
if (replay_thisGame != null)
@@ -206,6 +226,12 @@ public class settlementController : MonoBehaviour
StopCoroutine(settlementIntroCoroutine);
settlementIntroCoroutine = null;
}
if (mvpEntryTween != null)
{
if (mvpEntryTween.active) mvpEntryTween.Kill(false);
mvpEntryTween = null;
}
}
// Start is called once before the first execution of Update after the MonoBehaviour is created
@@ -420,8 +446,12 @@ public class settlementController : MonoBehaviour
private void StartSettlementIntro()
{
skipIntroRequested = false;
if (!enableDetailedSettlementIntro)
{
if (settlementTeamLoader != null)
settlementTeamLoader.ShowSpawnedCardsInstantly();
StartCanvasFadeIn();
return;
}
@@ -463,7 +493,16 @@ public class settlementController : MonoBehaviour
Transform heroDApicTr = FindDescendantByName(rightRoot != null ? rightRoot : settleRoot, "heroDApic");
Transform heroMaskTr = FindDescendantByName(heroDApicTr != null ? heroDApicTr : settleRoot, "MASK");
Transform lihuiTr = FindDescendantByName(heroMaskTr != null ? heroMaskTr : settleRoot, "lihui");
RectTransform teamDisplayingRt = FindRectByName(rightRoot != null ? rightRoot : settleRoot, "teamDisplaying");
Transform teamDisplayingTr = FindDescendantByName(rightRoot != null ? rightRoot : settleRoot, "teamDisplaying");
RectTransform teamDisplayingRt = teamDisplayingTr as RectTransform;
Transform mvpTr = (mvp_object != null) ? mvp_object.transform : FindDescendantByName(heroDApicTr != null ? heroDApicTr : settleRoot, "mvp");
bool hasAllyCardEntry = settlementTeamLoader != null && settlementTeamLoader.HasSpawnedCards();
bool shouldAnimateMvp = mvpTr != null && mvpTr.gameObject.activeInHierarchy;
Vector3 mvpBaseLocalPos = shouldAnimateMvp ? mvpTr.localPosition : Vector3.zero;
cachedIntroMvpTransform = mvpTr;
hasCachedIntroMvpBaseLocalPos = shouldAnimateMvp;
if (shouldAnimateMvp)
cachedIntroMvpBaseLocalPos = mvpBaseLocalPos;
Transform buttonsRoot = FindDescendantByName(rightRoot != null ? rightRoot : settleRoot, "buttons");
List<Transform> rightButtons = CollectDirectChildren(buttonsRoot);
@@ -496,7 +535,25 @@ public class settlementController : MonoBehaviour
if (rewardRt != null) { SetAnchoredY(rewardRt, -494.3f); SetCanvasAlpha(rewardRt, 0f); }
if (lihuiTr != null) SetCanvasAlpha(lihuiTr, 0f);
if (teamDisplayingRt != null) { SetAnchoredX(teamDisplayingRt, 2265f); SetCanvasAlpha(teamDisplayingRt, 0f); }
if (teamDisplayingRt != null)
{
if (hasAllyCardEntry)
{
SetAnchoredX(teamDisplayingRt, 1490.09f);
SetCanvasAlpha(teamDisplayingRt, 1f);
}
else
{
SetAnchoredX(teamDisplayingRt, 2265f);
SetCanvasAlpha(teamDisplayingRt, 0f);
}
}
if (shouldAnimateMvp)
{
SetCanvasAlpha(mvpTr, 0f);
mvpTr.localPosition = new Vector3(mvpBaseLocalPos.x, mvpBaseLocalPos.y + introMvpEnterYOffset, mvpBaseLocalPos.z);
}
for (int i = 0; i < rightButtons.Count; i++)
{
@@ -566,10 +623,20 @@ public class settlementController : MonoBehaviour
yield return StartCoroutine(PlayStatusGroupIn(goodGroup, goodHitCount_Text, targetGoodCount, goodHitPercent_Text, targetGoodPercent));
yield return StartCoroutine(PlayStatusGroupIn(missGroup, missHitCount_Text, targetMissCount, missHitPercent_Text, targetMissPercent));
// heroDApic/MASK/lihui flash + teamDisplaying move/fade in
// ally cards enter before MASK.
if (hasAllyCardEntry)
yield return StartCoroutine(settlementTeamLoader.PlayAllySlotEntryBeforeMask());
// heroDApic/MASK/lihui flash
if (lihuiTr != null)
yield return StartCoroutine(FlashInTransform(lihuiTr, introFlashCount, introFlashDuration));
if (teamDisplayingRt != null)
// MVP shows after MASK.
if (shouldAnimateMvp)
yield return StartCoroutine(AnimateMvpEntryAfterMask(mvpTr, mvpBaseLocalPos));
// Fallback for scenes still using container slide-in instead of per-slot tween.
if (!hasAllyCardEntry && teamDisplayingRt != null)
yield return StartCoroutine(AnimateAnchoredXAndFade(teamDisplayingRt, 2265f, 1490.09f, introMoveDuration, true));
// right/buttons: each button flashes in sequence
@@ -584,9 +651,125 @@ public class settlementController : MonoBehaviour
settlementCanvasGroup.alpha = 1f;
settlementCanvasGroup.interactable = true;
settlementCanvasGroup.blocksRaycasts = true;
hasCachedIntroMvpBaseLocalPos = false;
cachedIntroMvpTransform = null;
settlementIntroCoroutine = null;
}
private void SkipSettlementIntroAnimations()
{
if (skipIntroRequested) return;
skipIntroRequested = true;
if (settlementIntroCoroutine != null)
{
StopCoroutine(settlementIntroCoroutine);
settlementIntroCoroutine = null;
}
if (mvpEntryTween != null)
{
if (mvpEntryTween.active) mvpEntryTween.Kill(false);
mvpEntryTween = null;
}
if (settlementTeamLoader != null)
settlementTeamLoader.CompleteAllySlotEntryImmediately();
ApplySettlementIntroFinalState();
if (settlementCanvasGroup != null)
{
settlementCanvasGroup.alpha = 1f;
settlementCanvasGroup.interactable = true;
settlementCanvasGroup.blocksRaycasts = true;
}
hasCachedIntroMvpBaseLocalPos = false;
cachedIntroMvpTransform = null;
}
private void ApplySettlementIntroFinalState()
{
Transform settleRoot = settlementCanvasGroup != null ? settlementCanvasGroup.transform : transform;
Transform rightRoot = FindDescendantByName(settleRoot, "right");
Transform leftRoot = FindDescendantByName(settleRoot, "left");
Transform picsRoot = FindDescendantByName(leftRoot != null ? leftRoot : settleRoot, "pics");
RectTransform rightBottomRt = FindRectByName(rightRoot != null ? rightRoot : settleRoot, "right_bottom");
Transform quhuiImageTr = FindDescendantByName(picsRoot != null ? picsRoot : settleRoot, "quhuiImage");
RectTransform scoreBottomRt = FindRectByName(settleRoot, "score_bottom");
RectTransform shenstarsRt = FindRectByName(settleRoot, "shenstars");
RectTransform rewardRt = FindRectByName(settleRoot, "reward");
RectTransform teamDisplayingRt = FindRectByName(rightRoot != null ? rightRoot : settleRoot, "teamDisplaying");
Transform buttonsRoot = FindDescendantByName(rightRoot != null ? rightRoot : settleRoot, "buttons");
List<Transform> rightButtons = CollectDirectChildren(buttonsRoot);
Transform heroDApicTr = FindDescendantByName(rightRoot != null ? rightRoot : settleRoot, "heroDApic");
Transform heroMaskTr = FindDescendantByName(heroDApicTr != null ? heroDApicTr : settleRoot, "MASK");
Transform lihuiTr = FindDescendantByName(heroMaskTr != null ? heroMaskTr : settleRoot, "lihui");
Transform mvpTr = (mvp_object != null) ? mvp_object.transform : FindDescendantByName(heroDApicTr != null ? heroDApicTr : settleRoot, "mvp");
if (rightBottomRt != null) { SetAnchoredX(rightBottomRt, 1677.75f); SetCanvasAlpha(rightBottomRt, 1f); }
if (quhuiImageTr != null) SetCanvasAlpha(quhuiImageTr, 1f);
if (scoreBottomRt != null) { SetAnchoredX(scoreBottomRt, -432.4f); SetCanvasAlpha(scoreBottomRt, 1f); }
if (shenstarsRt != null) { SetAnchoredX(shenstarsRt, -437.5785f); SetCanvasAlpha(shenstarsRt, 1f); }
if (rewardRt != null) { SetAnchoredY(rewardRt, 0f); SetCanvasAlpha(rewardRt, 1f); }
if (lihuiTr != null) SetCanvasAlpha(lihuiTr, 1f);
if (teamDisplayingRt != null) { SetAnchoredX(teamDisplayingRt, 1490.09f); SetCanvasAlpha(teamDisplayingRt, 1f); }
for (int i = 0; i < rightButtons.Count; i++)
{
if (rightButtons[i] != null) SetCanvasAlpha(rightButtons[i], 1f);
}
Transform perfectGroup = FindGroupRoot(perfectHitCount_Text, perfectHitPercent_Text);
Transform greatGroup = FindGroupRoot(greatHitCount_Text, greatHitPercent_Text);
Transform goodGroup = FindGroupRoot(goodHitCount_Text, goodHitPercent_Text);
Transform missGroup = FindGroupRoot(missHitCount_Text, missHitPercent_Text);
if (perfectGroup != null) SetCanvasAlpha(perfectGroup, 1f);
if (greatGroup != null) SetCanvasAlpha(greatGroup, 1f);
if (goodGroup != null) SetCanvasAlpha(goodGroup, 1f);
if (missGroup != null) SetCanvasAlpha(missGroup, 1f);
int noteCountSum = Mathf.Max(1, targetPerfectCount + targetGreatCount + targetGoodCount + targetMissCount);
if (pmScoreSum_Text != null) { pmScoreSum_Text.text = targetPmScore.ToString(); SetCanvasAlpha(pmScoreSum_Text.transform, 1f); }
if (idolScoreSum_Text != null) { idolScoreSum_Text.text = targetIdolScore.ToString(); SetCanvasAlpha(idolScoreSum_Text.transform, 1f); }
if (accuracy_Text != null) { accuracy_Text.text = targetAccuracyPercent.ToString("F3") + "%"; SetCanvasAlpha(accuracy_Text.transform, 1f); }
if (finalScore_Text != null) { finalScore_Text.text = targetTotalScore.ToString(); SetCanvasAlpha(finalScore_Text.transform, 1f); }
if (thisLevel_currentPercentage_Text != null) { thisLevel_currentPercentage_Text.text = targetTotalPercent.ToString("F2") + "%"; SetCanvasAlpha(thisLevel_currentPercentage_Text.transform, 1f); }
if (perfectHitCount_Text != null) { perfectHitCount_Text.text = targetPerfectCount.ToString(); SetCanvasAlpha(perfectHitCount_Text.transform, 1f); }
if (greatHitCount_Text != null) { greatHitCount_Text.text = targetGreatCount.ToString(); SetCanvasAlpha(greatHitCount_Text.transform, 1f); }
if (goodHitCount_Text != null) { goodHitCount_Text.text = targetGoodCount.ToString(); SetCanvasAlpha(goodHitCount_Text.transform, 1f); }
if (missHitCount_Text != null) { missHitCount_Text.text = targetMissCount.ToString(); SetCanvasAlpha(missHitCount_Text.transform, 1f); }
if (perfectHitPercent_Text != null) { perfectHitPercent_Text.text = targetPerfectPercent.ToString("F1") + "%"; SetCanvasAlpha(perfectHitPercent_Text.transform, 1f); }
if (greatHitPercent_Text != null) { greatHitPercent_Text.text = targetGreatPercent.ToString("F1") + "%"; SetCanvasAlpha(greatHitPercent_Text.transform, 1f); }
if (goodHitPercent_Text != null) { goodHitPercent_Text.text = targetGoodPercent.ToString("F1") + "%"; SetCanvasAlpha(goodHitPercent_Text.transform, 1f); }
if (missHitPercent_Text != null) { missHitPercent_Text.text = targetMissPercent.ToString("F1") + "%"; SetCanvasAlpha(missHitPercent_Text.transform, 1f); }
if (thisLevel_progressBar_Image != null)
thisLevel_progressBar_Image.fillAmount = (float)targetTotalScore / Mathf.Max(1, _1000000);
if (perfect_barFill_Image != null) perfect_barFill_Image.fillAmount = (float)targetPerfectCount / noteCountSum;
if (great_barFill_Image != null) great_barFill_Image.fillAmount = (float)targetGreatCount / noteCountSum;
if (good_barFill_Image != null) good_barFill_Image.fillAmount = (float)targetGoodCount / noteCountSum;
if (miss_barFill_Image != null) miss_barFill_Image.fillAmount = (float)targetMissCount / noteCountSum;
if (mvpTr != null && mvpTr.gameObject.activeInHierarchy)
{
Vector3 targetPos;
if (hasCachedIntroMvpBaseLocalPos && cachedIntroMvpTransform == mvpTr)
targetPos = cachedIntroMvpBaseLocalPos;
else
targetPos = new Vector3(mvpTr.localPosition.x, mvpTr.localPosition.y - introMvpEnterYOffset, mvpTr.localPosition.z);
mvpTr.localPosition = targetPos;
SetCanvasAlpha(mvpTr, 1f);
}
}
/// <summary>
/// Documentation text normalized.
/// </summary>
@@ -823,6 +1006,32 @@ public class settlementController : MonoBehaviour
yield return WaitRealtime(introNumberDuration + 0.02f);
}
private IEnumerator AnimateMvpEntryAfterMask(Transform mvpTransform, Vector3 baseLocalPosition)
{
if (mvpTransform == null || !mvpTransform.gameObject.activeInHierarchy)
yield break;
if (mvpEntryTween != null)
{
if (mvpEntryTween.active) mvpEntryTween.Kill(false);
mvpEntryTween = null;
}
CanvasGroup group = GetOrAddCanvasGroup(mvpTransform);
if (group != null) group.alpha = 0f;
mvpTransform.localPosition = new Vector3(baseLocalPosition.x, baseLocalPosition.y + introMvpEnterYOffset, baseLocalPosition.z);
float duration = Mathf.Max(0.01f, introMvpEnterDuration);
Sequence seq = DOTween.Sequence().SetUpdate(introTweenUseUnscaledTime);
seq.Join(mvpTransform.DOLocalMoveY(baseLocalPosition.y, duration).SetEase(introMvpEnterEase).SetUpdate(introTweenUseUnscaledTime));
if (group != null)
seq.Join(group.DOFade(1f, duration).SetEase(introMvpEnterEase).SetUpdate(introTweenUseUnscaledTime));
mvpEntryTween = seq;
yield return seq.WaitForCompletion();
mvpEntryTween = null;
}
private IEnumerator AnimateIntText(Text target, int toValue, float duration)
{
if (target == null) yield break;
@@ -833,6 +1042,7 @@ public class settlementController : MonoBehaviour
while (elapsed < dur)
{
if (skipIntroRequested) break;
elapsed += Time.unscaledDeltaTime;
float t = EaseOutCubic(Mathf.Clamp01(elapsed / dur));
int value = Mathf.RoundToInt(Mathf.Lerp(0f, toValue, t));
@@ -853,6 +1063,7 @@ public class settlementController : MonoBehaviour
while (elapsed < dur)
{
if (skipIntroRequested) break;
elapsed += Time.unscaledDeltaTime;
float t = EaseOutCubic(Mathf.Clamp01(elapsed / dur));
float value = Mathf.Lerp(0f, toValue, t);
@@ -874,6 +1085,7 @@ public class settlementController : MonoBehaviour
float dur = Mathf.Max(0.01f, duration);
while (elapsed < dur)
{
if (skipIntroRequested) break;
elapsed += Time.unscaledDeltaTime;
float t = EaseOutCubic(Mathf.Clamp01(elapsed / dur));
SetAnchoredX(rt, Mathf.LerpUnclamped(fromX, toX, t));
@@ -896,6 +1108,7 @@ public class settlementController : MonoBehaviour
float dur = Mathf.Max(0.01f, duration);
while (elapsed < dur)
{
if (skipIntroRequested) break;
elapsed += Time.unscaledDeltaTime;
float t = EaseOutCubic(Mathf.Clamp01(elapsed / dur));
SetAnchoredY(rt, Mathf.LerpUnclamped(fromY, toY, t));
@@ -920,6 +1133,7 @@ public class settlementController : MonoBehaviour
float elapsed = 0f;
while (elapsed < total)
{
if (skipIntroRequested) break;
elapsed += Time.unscaledDeltaTime;
float p = Mathf.Clamp01(elapsed / total);
@@ -990,12 +1204,13 @@ public class settlementController : MonoBehaviour
return 1f - inv * inv * inv;
}
private static IEnumerator WaitRealtime(float duration)
private IEnumerator WaitRealtime(float duration)
{
float elapsed = 0f;
float dur = Mathf.Max(0f, duration);
while (elapsed < dur)
{
if (skipIntroRequested) yield break;
elapsed += Time.unscaledDeltaTime;
yield return null;
}
@@ -1347,4 +1562,3 @@ public class settlementController : MonoBehaviour
}
}
}
+41
View File
@@ -4,6 +4,7 @@ using UnityEngine;
using UnityEngine.UI;
using Bansonic;
using UnityEngine.SceneManagement;
using Steamworks;
#if UNITY_EDITOR
using UnityEditor;
#endif
@@ -38,6 +39,46 @@ public class userSettings : MonoBehaviour
private int clearupClickCount = 0;
private float clearupExpireTime;
private void Start()
{
UpdateUserInfoDisplay();
}
private void UpdateUserInfoDisplay()
{
if (SteamManager.Initialized)
{
if (user_login_source_text != null)
{
// 获取 Steam 的测试分支名 (Beta branch)
string branch;
if (SteamApps.GetCurrentBetaName(out branch, 64))
{
user_login_source_text.text = "(Steam) " + branch;
}
else
{
user_login_source_text.text = "(Steam) public"; // 默认分支
}
}
if (user_source_uid_text != null)
{
CSteamID steamID = SteamUser.GetSteamID();
string personaName = SteamFriends.GetPersonaName();
user_source_uid_text.text = string.Format("{0} ({1})", personaName, steamID.m_SteamID.ToString());
}
}
else
{
if (user_login_source_text != null)
user_login_source_text.text = "Unknown Server";
if (user_source_uid_text != null)
user_source_uid_text.text = "Unknown User";
}
}
private void OnEnable()
{
if (clearup_saveData_button != null)