技能主要更新,修复卡顿并加入动画,以及各种其他更新。
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
using UnityEngine;
|
||||
using TMPro;
|
||||
using System.Collections; // 保持对 Coroutine 的支持
|
||||
using System.Collections; // ���ֶ� Coroutine ��֧��
|
||||
using System.Collections.Generic; // For List
|
||||
|
||||
public class AnimationController : MonoBehaviour
|
||||
@@ -9,11 +9,11 @@ public class AnimationController : MonoBehaviour
|
||||
|
||||
public static AnimationController Global;
|
||||
|
||||
public GameObject redEffect; // 红色击打动画对象
|
||||
public GameObject greenEffect; // 绿色击打动画对象
|
||||
public GameObject yellowEffect; // 黄色击打动画对象
|
||||
public GameObject purpleEffect; // 紫色击打动画对象
|
||||
public GameObject blueEffect; // 蓝色击打动画对象
|
||||
public GameObject redEffect; // ��ɫ��������
|
||||
public GameObject greenEffect; // ��ɫ��������
|
||||
public GameObject yellowEffect; // ��ɫ��������
|
||||
public GameObject purpleEffect; // ��ɫ��������
|
||||
public GameObject blueEffect; // ��ɫ��������
|
||||
|
||||
private Animator redAnimator;
|
||||
private Animator greenAnimator;
|
||||
@@ -25,35 +25,33 @@ public class AnimationController : MonoBehaviour
|
||||
public GameObject hit_particular_object;
|
||||
public GameObject hit_ring_object;
|
||||
|
||||
[Header("轨道颜色 (Hex Color Codes)")]
|
||||
// F86F64 (浅红色/珊瑚色)
|
||||
public string redColorHex = "#F86F64";
|
||||
// 40EC90 (亮绿色)
|
||||
public string greenColorHex = "#40EC90";
|
||||
// F7CB6C (金黄色)
|
||||
public string yellowColorHex = "#F7CB6C";
|
||||
// F888E7 (亮粉色/洋红色)
|
||||
public string purpleColorHex = "#F888E7";
|
||||
// 4FACFE (天蓝色)
|
||||
public string blueColorHex = "#4FACFE";
|
||||
[Header("Hold 粒子配置")]
|
||||
[Header("�����ɫ (Hex Color Codes)")]
|
||||
public string redColorHex = "#FF9390";
|
||||
public string greenColorHex = "#38F6CA";
|
||||
public string yellowColorHex = "#FFE1A2";
|
||||
public string purpleColorHex = "#F083E4";
|
||||
public string blueColorHex = "#7AF9FF";
|
||||
[Header("Hold ��������")]
|
||||
public float holdParticleInterval = 0.2f;
|
||||
|
||||
private Coroutine holdParticleCoroutine;
|
||||
private bool holdActive = false;
|
||||
private string currentHoldColor;
|
||||
private bool isHolding = false;
|
||||
private WaitForSeconds cachedHoldWait;
|
||||
private float cachedHoldWaitSeconds = -1f;
|
||||
private static bool loggedMissingHitParticleSource;
|
||||
|
||||
// Track active particle instances for immediate cleanup
|
||||
private List<GameObject> activeParticles = new List<GameObject>();
|
||||
|
||||
[Header("判定飘字prefabs")]
|
||||
[Header("�ж�Ʈ��prefabs")]
|
||||
public GameObject perfect_judge_prefab;
|
||||
public GameObject great_judge_prefab;
|
||||
public GameObject good_judge_prefab;
|
||||
public GameObject miss_judge_prefab;
|
||||
[Header("轨道关联的判定文字 (Legacy Text)")]
|
||||
// 请在 Inspector 中将对应的 UI Text 拖入
|
||||
[Header("�������������� (Legacy Text)")]
|
||||
// ���� Inspector �н���Ӧ�� UI Text ����
|
||||
public TextMeshProUGUI red_track_judgementText;
|
||||
public TextMeshProUGUI green_track_judgementText;
|
||||
public TextMeshProUGUI yellow_track_judgementText;
|
||||
@@ -124,7 +122,13 @@ public class AnimationController : MonoBehaviour
|
||||
{
|
||||
while (holdActive)
|
||||
{
|
||||
yield return new WaitForSeconds(Mathf.Max(0.01f, holdParticleInterval));
|
||||
float waitSeconds = Mathf.Max(0.01f, holdParticleInterval);
|
||||
if (cachedHoldWait == null || !Mathf.Approximately(cachedHoldWaitSeconds, waitSeconds))
|
||||
{
|
||||
cachedHoldWaitSeconds = waitSeconds;
|
||||
cachedHoldWait = new WaitForSeconds(waitSeconds);
|
||||
}
|
||||
yield return cachedHoldWait;
|
||||
if (!holdActive) break;
|
||||
PlayDestroyAnimation(color);
|
||||
}
|
||||
@@ -143,7 +147,7 @@ public class AnimationController : MonoBehaviour
|
||||
|
||||
Global = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
Debug.Log("[AnimationController] Global AnimationController registered");
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log("[AnimationController] Global AnimationController registered");
|
||||
}
|
||||
|
||||
// Animator cache
|
||||
@@ -164,7 +168,7 @@ public class AnimationController : MonoBehaviour
|
||||
if (byTag != null)
|
||||
{
|
||||
hit_particular_object = byTag;
|
||||
Debug.Log("AnimationController: assigned hit_particular_object via tag 'HitParticleSource'.");
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log("AnimationController: assigned hit_particular_object via tag 'HitParticleSource'.");
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
@@ -176,15 +180,16 @@ public class AnimationController : MonoBehaviour
|
||||
if (byName != null)
|
||||
{
|
||||
hit_particular_object = byName;
|
||||
Debug.Log("AnimationController: assigned hit_particular_object via GameObject.Find by name.");
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log("AnimationController: assigned hit_particular_object via GameObject.Find by name.");
|
||||
}
|
||||
}
|
||||
|
||||
if (hit_particular_object == null)
|
||||
{
|
||||
Debug.LogWarning("AnimationController: hit_particular_object not assigned in Inspector and automatic lookup failed.\n" +
|
||||
"You can either assign a scene object to 'hit_particular_object' on the prefab instance in the scene,\n" +
|
||||
"or tag the scene particle source with 'HitParticleSource', or place a GameObject named 'HitParticleSource' in the scene.");
|
||||
if (JudgeManager.IsDebugEnabled)
|
||||
Debug.LogWarning("AnimationController: hit_particular_object not assigned in Inspector and automatic lookup failed.\n" +
|
||||
"You can either assign a scene object to 'hit_particular_object' on the prefab instance in the scene,\n" +
|
||||
"or tag the scene particle source with 'HitParticleSource', or place a GameObject named 'HitParticleSource' in the scene.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,7 +199,11 @@ public class AnimationController : MonoBehaviour
|
||||
// Only use the scene object as the template. No prefab fallback.
|
||||
if (hit_particular_object == null)
|
||||
{
|
||||
Debug.LogError("hit_particular_object is null. Assign a scene particle source to AnimationController.hit_particular_object or tag a GameObject 'HitParticleSource'.");
|
||||
if (!loggedMissingHitParticleSource)
|
||||
{
|
||||
loggedMissingHitParticleSource = true;
|
||||
Debug.LogError("hit_particular_object is null. Assign a scene particle source to AnimationController.hit_particular_object or tag a GameObject 'HitParticleSource'.");
|
||||
}
|
||||
// As a fallback, trigger the Animator-based effects
|
||||
TriggerAnimatorEffect(color);
|
||||
return;
|
||||
@@ -214,15 +223,15 @@ public class AnimationController : MonoBehaviour
|
||||
case "blue": if (blueEffect != null) { spawnPos = blueEffect.transform.position; parentTransform = blueEffect.transform; } break;
|
||||
}
|
||||
|
||||
// 获取并解析颜色
|
||||
// ��ȡ��������ɫ
|
||||
Color trackColor;
|
||||
string hexCode = GetHexCodeForColor(color);
|
||||
|
||||
// 尝试解析 16 进制颜色代码。如果失败,使用白色作为默认值。
|
||||
// ���Խ��� 16 ������ɫ���롣���ʧ�ܣ�ʹ�ð�ɫ��ΪĬ��ֵ��
|
||||
if (!ColorUtility.TryParseHtmlString(hexCode, out trackColor))
|
||||
{
|
||||
trackColor = Color.white;
|
||||
Debug.LogWarning($"Failed to parse hex color for '{color}' ({hexCode}). Using white.");
|
||||
if (JudgeManager.IsDebugEnabled) Debug.LogWarning($"Failed to parse hex color for '{color}' ({hexCode}). Using white.");
|
||||
}
|
||||
|
||||
SpawnJudgePrefabByTrackText(color);
|
||||
@@ -281,7 +290,7 @@ public class AnimationController : MonoBehaviour
|
||||
GameObject ringInstance = Instantiate(hit_ring_object, spawnPos, Quaternion.identity);
|
||||
if (ringInstance != null)
|
||||
{
|
||||
Debug.Log($"AnimationController: instantiated hit_ring_object '{ringInstance.name}' at {spawnPos}. parentTransform={(parentTransform != null ? parentTransform.name : "null")} ");
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"AnimationController: instantiated hit_ring_object '{ringInstance.name}' at {spawnPos}. parentTransform={(parentTransform != null ? parentTransform.name : "null")} ");
|
||||
|
||||
// Parent before activation to avoid transform surprises when Simulation Space = Local
|
||||
if (parentTransform != null)
|
||||
@@ -289,14 +298,14 @@ public class AnimationController : MonoBehaviour
|
||||
ringInstance.transform.SetParent(parentTransform, false);
|
||||
// keep world position at spawnPos
|
||||
ringInstance.transform.position = spawnPos;
|
||||
Debug.Log("AnimationController: ringInstance parent set to " + parentTransform.name);
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log("AnimationController: ringInstance parent set to " + parentTransform.name);
|
||||
}
|
||||
|
||||
// Activate after parenting
|
||||
ringInstance.SetActive(true);
|
||||
|
||||
var ringSystems = ringInstance.GetComponentsInChildren<ParticleSystem>(true);
|
||||
Debug.Log("AnimationController: ringSystems count=" + (ringSystems != null ? ringSystems.Length : 0));
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log("AnimationController: ringSystems count=" + (ringSystems != null ? ringSystems.Length : 0));
|
||||
if (ringSystems != null && ringSystems.Length > 0)
|
||||
{
|
||||
float ringMaxLife = 0f;
|
||||
@@ -304,7 +313,7 @@ public class AnimationController : MonoBehaviour
|
||||
{
|
||||
var rmain = rps.main;
|
||||
// log important runtime properties for debugging
|
||||
Debug.Log($"ring PS: {rps.gameObject.name} simulationSpace={rmain.simulationSpace} startLifetime={rmain.startLifetime.constant} startColor={rmain.startColor.color}");
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"ring PS: {rps.gameObject.name} simulationSpace={rmain.simulationSpace} startLifetime={rmain.startLifetime.constant} startColor={rmain.startColor.color}");
|
||||
|
||||
// reuse same track color
|
||||
rmain.startColor = trackColor;
|
||||
@@ -319,7 +328,7 @@ public class AnimationController : MonoBehaviour
|
||||
{
|
||||
var emission = rps.emission;
|
||||
var rate = emission.rateOverTime;
|
||||
Debug.Log($"ring PS emission rateOverTime.constant (approx) = {rate.constant}");
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"ring PS emission rateOverTime.constant (approx) = {rate.constant}");
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
@@ -331,7 +340,7 @@ public class AnimationController : MonoBehaviour
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("hit_ring_object has no ParticleSystem components");
|
||||
if (JudgeManager.IsDebugEnabled) Debug.LogWarning("hit_ring_object has no ParticleSystem components");
|
||||
Destroy(ringInstance);
|
||||
}
|
||||
}
|
||||
@@ -342,7 +351,7 @@ public class AnimationController : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
// 用于根据颜色名称获取对应的 16 进制代码
|
||||
// ���ڸ�����ɫ���ƻ�ȡ��Ӧ�� 16 ���ƴ���
|
||||
private string GetHexCodeForColor(string color)
|
||||
{
|
||||
switch (color)
|
||||
@@ -352,7 +361,7 @@ public class AnimationController : MonoBehaviour
|
||||
case "yellow": return yellowColorHex;
|
||||
case "purple": return purpleColorHex;
|
||||
case "blue": return blueColorHex;
|
||||
default: return "#FFFFFF"; // 默认返回白色
|
||||
default: return "#FFFFFF"; // Ĭ�Ϸ��ذ�ɫ
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,14 +408,14 @@ public class AnimationController : MonoBehaviour
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 根据传入的颜色频道,定位到对应的 UI Text 并读取其内容
|
||||
/// ���ݴ������ɫƵ������λ����Ӧ�� UI Text ����ȡ������
|
||||
/// </summary>
|
||||
private void SpawnJudgePrefabByTrackText(string color)
|
||||
{
|
||||
TextMeshProUGUI targetText = null;
|
||||
Transform spawnPoint = null;
|
||||
|
||||
// 1. 匹配对应的文本组件和位置
|
||||
// 1. ƥ���Ӧ���ı������λ��
|
||||
switch (color)
|
||||
{
|
||||
case "red": targetText = red_track_judgementText; spawnPoint = redEffect != null ? redEffect.transform : null; break;
|
||||
@@ -417,11 +426,11 @@ public class AnimationController : MonoBehaviour
|
||||
}
|
||||
if (targetText != null && spawnPoint != null)
|
||||
{
|
||||
// 打印调试信息,看程序到底读到了什么文字
|
||||
Debug.LogError($"轨道 {color} 当前读到的文字是: [{targetText.text}]");
|
||||
// 打印调试信息,确认判定到底读取到的是什么文字
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"判定 {color} 当前读取到的文字为: [{targetText.text}]");
|
||||
}
|
||||
|
||||
// 2. 如果找到了文本且文本不为空,则执行喷出逻辑
|
||||
// 2. ����ҵ����ı����ı���Ϊ�գ���ִ�������
|
||||
if (targetText != null && spawnPoint != null && !string.IsNullOrEmpty(targetText.text))
|
||||
{
|
||||
DoExecutePrefabSpawn(targetText.text, spawnPoint);
|
||||
@@ -429,13 +438,13 @@ public class AnimationController : MonoBehaviour
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 最终执行实例化的函数
|
||||
/// ����ִ��ʵ�����ĺ���
|
||||
/// </summary>
|
||||
private void DoExecutePrefabSpawn(string judgeResult, Transform spawnTransform)
|
||||
{
|
||||
GameObject prefabToUse = null;
|
||||
|
||||
// 字符串匹配(需确保与 InputManager 中传入的字符串一致)
|
||||
// �ַ���ƥ�䣨��ȷ���� InputManager �д�����ַ���һ�£�
|
||||
if (judgeResult.Contains("Perfect")) prefabToUse = perfect_judge_prefab;
|
||||
else if (judgeResult.Contains("Great")) prefabToUse = great_judge_prefab;
|
||||
else if (judgeResult.Contains("Good")) prefabToUse = good_judge_prefab;
|
||||
@@ -443,10 +452,10 @@ public class AnimationController : MonoBehaviour
|
||||
|
||||
if (prefabToUse != null)
|
||||
{
|
||||
// 在对应特效点生成判定 Prefab
|
||||
// �ڶ�Ӧ��Ч�������ж� Prefab
|
||||
GameObject instance = Instantiate(prefabToUse, spawnTransform.position, Quaternion.identity);
|
||||
|
||||
// 自动销毁,防止堆积
|
||||
// �Զ����٣���ֹ�ѻ�
|
||||
Destroy(instance, 1.0f);
|
||||
}
|
||||
}
|
||||
@@ -524,4 +533,4 @@ public class AnimationController : MonoBehaviour
|
||||
{
|
||||
yield return StartCoroutine(PrewarmCoroutine(perTemplate));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,33 +5,33 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
|
||||
{
|
||||
public static Animation_GenerateJudgementSituationPrefab Instance;
|
||||
|
||||
[Header("生成位置 (五个轨道)")]
|
||||
[Header("����� (������)")]
|
||||
public GameObject redEffect;
|
||||
public GameObject greenEffect;
|
||||
public GameObject yellowEffect;
|
||||
public GameObject purpleEffect;
|
||||
public GameObject blueEffect;
|
||||
|
||||
[Header("判定飘字 Prefabs")]
|
||||
[Header("�ж�Ʈ�� Prefabs")]
|
||||
public GameObject perfect_judge_prefab;
|
||||
public GameObject great_judge_prefab;
|
||||
public GameObject good_judge_prefab;
|
||||
public GameObject miss_judge_prefab;
|
||||
|
||||
[Header("缩放与随机")]
|
||||
[Header("���������")]
|
||||
public float baseScale = 1.0f;
|
||||
public bool useRandomScale = true;
|
||||
public float minScaleMultiplier = 0.8f;
|
||||
public float maxScaleMultiplier = 1.2f;
|
||||
|
||||
[Header("物理跳跃设置")]
|
||||
public float jumpForce = 5.0f; // 向上弹起的初始速度
|
||||
public float gravity = -12.0f; // 重力
|
||||
[Header("������Ծ����")]
|
||||
public float jumpForce = 5.0f; // ���ϵ���ij�ʼ�ٶ�
|
||||
public float gravity = -12.0f; // ����
|
||||
|
||||
[Header("时间与透明度控制 (秒)")]
|
||||
public float fadeInTime = 0.1f; // 生成后多少秒完成渐显 (0->1)
|
||||
public float fadeOutStartTime = 0.6f; // 生成后第几秒开始渐隐
|
||||
public float fadeOutDuration = 0.3f; // 渐隐动画持续时长
|
||||
[Header("ʱ�������ȿ��� (��)")]
|
||||
public float fadeInTime = 0.1f; // ���ɺ��������ɽ��� (0->1)
|
||||
public float fadeOutStartTime = 0.6f; // ���ɺ�ڼ��뿪ʼ����
|
||||
public float fadeOutDuration = 0.3f; // ������������ʱ��
|
||||
|
||||
// Cache spawn transforms to avoid accessing destroyed GameObject references.
|
||||
private Transform redSpawn;
|
||||
@@ -48,8 +48,8 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
|
||||
if (Instance == null)
|
||||
{
|
||||
Instance = this;
|
||||
// 注意:仅当第一次创建时保留,允许重新加载
|
||||
// DontDestroyOnLoad(gameObject); // ← 移除此行以允许场景重新加载时的清理
|
||||
// ע�⣺������һ�δ���ʱ�������������¼���
|
||||
// DontDestroyOnLoad(gameObject); // �� �Ƴ������������������¼���ʱ������
|
||||
}
|
||||
else if (Instance != this)
|
||||
{
|
||||
@@ -73,7 +73,7 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
|
||||
int currentSceneIndex = UnityEngine.SceneManagement.SceneManager.GetActiveScene().buildIndex;
|
||||
if (currentSceneIndex != lastSceneIndex)
|
||||
{
|
||||
if (GameConfig.verboseLogs) Debug.Log($"[Animation_Generate] Scene changed detected (from {lastSceneIndex} to {currentSceneIndex}), refreshing spawn point cache");
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[Animation_Generate] Scene changed detected (from {lastSceneIndex} to {currentSceneIndex}), refreshing spawn point cache");
|
||||
lastSceneIndex = currentSceneIndex;
|
||||
CacheSpawnPoints();
|
||||
}
|
||||
@@ -111,7 +111,7 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
|
||||
purpleSpawn = purpleEffect != null ? purpleEffect.transform : null;
|
||||
blueSpawn = blueEffect != null ? blueEffect.transform : null;
|
||||
|
||||
if (GameConfig.verboseLogs)
|
||||
if (JudgeManager.IsDebugEnabled)
|
||||
{
|
||||
Debug.Log($"[Animation_Generate] Cached spawn points: red={redSpawn!=null}, green={greenSpawn!=null}, yellow={yellowSpawn!=null}, purple={purpleSpawn!=null}, blue={blueSpawn!=null}");
|
||||
}
|
||||
@@ -145,17 +145,17 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameConfig.verboseLogs) Debug.Log($"[Animation_Generate] Spawning judge prefab: color={color}, result={judgeResult}, prefab={prefabToUse.name}");
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[Animation_Generate] Spawning judge prefab: color={color}, result={judgeResult}, prefab={prefabToUse.name}");
|
||||
|
||||
GameObject instance = Instantiate(prefabToUse, spawnPoint);
|
||||
instance.transform.localPosition = Vector3.zero;
|
||||
|
||||
// 处理缩放
|
||||
// ��������
|
||||
float finalScale = baseScale;
|
||||
if (useRandomScale) finalScale *= Random.Range(minScaleMultiplier, maxScaleMultiplier);
|
||||
instance.transform.localScale = new Vector3(finalScale, finalScale, 1f);
|
||||
|
||||
// 开启运动逻辑
|
||||
// �����˶���
|
||||
StartCoroutine(AnimateSprite(instance));
|
||||
}
|
||||
|
||||
@@ -168,10 +168,10 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
|
||||
float vVelocity = jumpForce;
|
||||
Vector3 currentLocalPos = Vector3.zero;
|
||||
|
||||
// 计算总生命周期:开始渐隐的时间 + 渐隐持续的时长
|
||||
// �������������ڣ���ʼ������ʱ�� + ����������ʱ��
|
||||
float totalLifeTime = fadeOutStartTime + fadeOutDuration;
|
||||
|
||||
// 初始透明度
|
||||
// ��ʼ����
|
||||
if (sr != null)
|
||||
{
|
||||
Color c = sr.color;
|
||||
@@ -179,7 +179,7 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
|
||||
sr.color = c;
|
||||
}
|
||||
|
||||
// 只要没到总寿命,就持续执行
|
||||
// ֻҪû�����������ͳ���ִ��
|
||||
while (elapsed < totalLifeTime)
|
||||
{
|
||||
if (obj == null) yield break;
|
||||
@@ -187,28 +187,28 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
|
||||
float dt = Time.deltaTime;
|
||||
elapsed += dt;
|
||||
|
||||
// --- 1. 物理位移 ---
|
||||
// --- 1. ����� ---
|
||||
vVelocity += gravity * dt;
|
||||
currentLocalPos.y += vVelocity * dt;
|
||||
obj.transform.localPosition = currentLocalPos;
|
||||
|
||||
// --- 2. 透明度逻辑 (基于秒数) ---
|
||||
// --- 2. ������ (��������) ---
|
||||
if (sr != null)
|
||||
{
|
||||
float alpha;
|
||||
|
||||
// 渐显阶段:当前时间 < fadeInTime
|
||||
// ���ԽΣ���ǰʱ�� < fadeInTime
|
||||
if (elapsed < fadeInTime)
|
||||
{
|
||||
alpha = Mathf.InverseLerp(0f, fadeInTime, elapsed);
|
||||
}
|
||||
// 渐隐阶段:当前时间 > fadeOutStartTime
|
||||
// �����Σ���ǰʱ�� > fadeOutStartTime
|
||||
else if (elapsed > fadeOutStartTime)
|
||||
{
|
||||
// 在 fadeOutStartTime 到 totalLifeTime 之间从 1 变到 0
|
||||
// �� fadeOutStartTime �� totalLifeTime ֮��� 1 �䵽 0
|
||||
alpha = Mathf.InverseLerp(totalLifeTime, fadeOutStartTime, elapsed);
|
||||
}
|
||||
// 中间完全显示阶段
|
||||
// �м���ȫ��ʾ��
|
||||
else
|
||||
{
|
||||
alpha = 1f;
|
||||
@@ -222,7 +222,7 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// --- 确保最后一帧是完全透明的 ---
|
||||
// --- ȷ�����һ֡����ȫ���� ---
|
||||
if (obj != null && sr != null)
|
||||
{
|
||||
Color c = sr.color;
|
||||
@@ -230,7 +230,7 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
|
||||
sr.color = c;
|
||||
}
|
||||
|
||||
// 等待渲染完成,防止闪删
|
||||
// �ȴ���Ⱦ��ɣ���ֹ��ɾ
|
||||
yield return new WaitForEndOfFrame();
|
||||
|
||||
if (obj != null) Destroy(obj);
|
||||
@@ -240,7 +240,7 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
|
||||
{
|
||||
if (string.IsNullOrEmpty(color)) return null;
|
||||
|
||||
// 在每次调用时检查是否需要重新缓存
|
||||
// ��ÿ�ε���ʱ����Ƿ���Ҫ���»���
|
||||
CacheSpawnPointsIfNeeded();
|
||||
|
||||
Transform point = null;
|
||||
|
||||
@@ -5,8 +5,10 @@ public abstract class BaseNote : MonoBehaviour
|
||||
public int TrackIndex { get; protected set; }
|
||||
public string NoteColor { get; protected set; }
|
||||
public float Speed { get; protected set; }
|
||||
protected float hitTime;
|
||||
|
||||
protected float hitTime;
|
||||
|
||||
public float HitTime => hitTime;
|
||||
|
||||
public virtual void Initialize(int trackIndex, string noteColor, float speed, float hitTime)
|
||||
{
|
||||
TrackIndex = trackIndex;
|
||||
|
||||
@@ -210,6 +210,8 @@ public class BeatmapManager : MonoBehaviour
|
||||
|
||||
// ����ͳһ�ĵ��˴����������� HP ��������ã�
|
||||
SetupEnemiesAndHP();
|
||||
ApplyTrackScoreCaps(beatmap);
|
||||
ApplyTrackScoreCaps(beatmap);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -221,6 +223,7 @@ public class BeatmapManager : MonoBehaviour
|
||||
Debug.Log("�����Ѽ��أ����� Beatmap ����" + (beatmap != null ? beatmap.title : "null"));
|
||||
// No globalDelaySeconds available in this path. Call NoteSpawner directly.
|
||||
noteSpawner.LoadBeatmap(beatmap);
|
||||
ApplyTrackScoreCaps(beatmap);
|
||||
}
|
||||
|
||||
// ��ȡ�����ص�ǰ���浽���������������ã�
|
||||
@@ -299,7 +302,7 @@ public class BeatmapManager : MonoBehaviour
|
||||
Debug.LogWarning("BeatmapManager.Start: chart parsed from SongData, attempting to assign audio and pause system");
|
||||
|
||||
// try to assign audio to GameManager.musicSource
|
||||
var gm = FindObjectOfType<GameManager>();
|
||||
var gm = FindAnyObjectByType<GameManager>();
|
||||
if (gm != null && gm.musicSource != null)
|
||||
{
|
||||
if (assignedSongData != null && assignedSongData.audioFile != null)
|
||||
@@ -369,7 +372,7 @@ public class BeatmapManager : MonoBehaviour
|
||||
}
|
||||
|
||||
// Pause the system to show pause overlay (PauseManager will enable overlayRoot)
|
||||
var pauseMgr = PauseManager.Instance ?? FindObjectOfType<PauseManager>();
|
||||
var pauseMgr = PauseManager.Instance ?? FindAnyObjectByType<PauseManager>();
|
||||
if (pauseMgr != null)
|
||||
{
|
||||
pauseMgr.Pause(true);
|
||||
@@ -575,7 +578,7 @@ public class BeatmapManager : MonoBehaviour
|
||||
if (parsedNoteAmount <= 0)
|
||||
{
|
||||
parsedNoteAmount = parsed.notes.Length;
|
||||
if (GameConfig.verboseLogs) Debug.Log($"[BeatmapManager] Calculated missing noteAmount: {parsedNoteAmount}");
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[BeatmapManager] Calculated missing noteAmount: {parsedNoteAmount}");
|
||||
}
|
||||
|
||||
// Ensure note statistics (counts by color) are populated
|
||||
@@ -596,10 +599,72 @@ public class BeatmapManager : MonoBehaviour
|
||||
{
|
||||
parsedNoteStatistics[i++] = new NoteStatistic { colorType = kvp.Key, count = kvp.Value };
|
||||
}
|
||||
if (GameConfig.verboseLogs) Debug.Log($"[BeatmapManager] Calculated missing noteStatistics for {counts.Count} colors");
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[BeatmapManager] Calculated missing noteStatistics for {counts.Count} colors");
|
||||
}
|
||||
}
|
||||
|
||||
// Compute per-track note counts and apply max score caps to allies
|
||||
private void ApplyTrackScoreCaps(Beatmap parsed)
|
||||
{
|
||||
if (parsed == null || parsed.notes == null) return;
|
||||
|
||||
const int trackCount = 5;
|
||||
var counts = new int[trackCount];
|
||||
for (int i = 0; i < parsed.notes.Length; i++)
|
||||
{
|
||||
var n = parsed.notes[i];
|
||||
int idx = n != null ? n.trackIndex : -1;
|
||||
if (idx < 0 || idx >= trackCount) continue;
|
||||
counts[idx]++;
|
||||
}
|
||||
|
||||
int per = perNoteScore;
|
||||
if (per <= 0 && parsedNoteAmount > 0)
|
||||
per = totalChartScore / parsedNoteAmount;
|
||||
|
||||
var ui = teamUIController.Instance;
|
||||
for (int i = 0; i < trackCount; i++)
|
||||
{
|
||||
int maxScore = Mathf.Max(0, per * counts[i]);
|
||||
var ally = ResolveAllyCombatant(i, ui);
|
||||
if (ally != null)
|
||||
{
|
||||
ally.SetMaxTrackScore(maxScore, true);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fallback: update UI text directly if combatant not found
|
||||
string value = $"0/{maxScore}";
|
||||
if (ui != null)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: if (ui.teammate01_current_scoreText != null) ui.teammate01_current_scoreText.text = value; break;
|
||||
case 1: if (ui.teammate02_current_scoreText != null) ui.teammate02_current_scoreText.text = value; break;
|
||||
case 2: if (ui.teammate03_current_scoreText != null) ui.teammate03_current_scoreText.text = value; break;
|
||||
case 3: if (ui.teammate04_current_scoreText != null) ui.teammate04_current_scoreText.text = value; break;
|
||||
case 4: if (ui.teammate05_current_scoreText != null) ui.teammate05_current_scoreText.text = value; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private AllyCombatant ResolveAllyCombatant(int slotIndex, teamUIController ui)
|
||||
{
|
||||
GameObject go = null;
|
||||
if (ui != null)
|
||||
go = ui.GetAllyObjectBySlot(slotIndex);
|
||||
if (go == null)
|
||||
{
|
||||
var byName = GameObject.Find($"ally_0{slotIndex + 1}");
|
||||
if (byName != null) go = byName;
|
||||
}
|
||||
if (go == null) return null;
|
||||
var ally = go.GetComponent<AllyCombatant>();
|
||||
if (ally != null) return ally;
|
||||
return go.GetComponentInChildren<AllyCombatant>(true);
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private class BeatmapExtra
|
||||
{
|
||||
@@ -633,4 +698,4 @@ public class BeatmapManager : MonoBehaviour
|
||||
public class TrackStates { public bool trackFading_active; public TrackState red; public TrackState green; public TrackState yellow; public TrackState purple; public TrackState blue; }
|
||||
[System.Serializable]
|
||||
public class TrackState { public bool isOn; public float fadeTime; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ public static class GameConfig
|
||||
// Toggle detailed logging for debugging. Keep false in production for performance.
|
||||
public static bool verboseLogs = false;
|
||||
|
||||
// Toggle skill effect logging.
|
||||
public static bool skillDebugMode = true;
|
||||
|
||||
// When true, gameplay will not automatically read JSON beatmaps on Start.
|
||||
public static bool testMode = false;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@ public static class HoldNoteJudgePool
|
||||
public object noteData; // store as object to avoid type dependency
|
||||
}
|
||||
|
||||
private static readonly Dictionary<string, HoldInfo> pool = new Dictionary<string, HoldInfo>();
|
||||
private static Dictionary<string, HoldInfo> pool = new Dictionary<string, HoldInfo>();
|
||||
|
||||
public static void RegisterStart(string noteID, float pressTime, float hitTime, float scheduledEnd, string color, int trackIndex, object noteData)
|
||||
{
|
||||
@@ -32,7 +32,7 @@ public static class HoldNoteJudgePool
|
||||
noteData = noteData
|
||||
};
|
||||
pool[noteID] = info;
|
||||
Debug.Log($"[HoldNoteJudgePool] Registered start for {noteID} press={pressTime:F3} hit={hitTime:F3} end={scheduledEnd:F3}");
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[HoldNoteJudgePool] Registered start for {noteID} press={pressTime:F3} hit={hitTime:F3} end={scheduledEnd:F3}");
|
||||
}
|
||||
|
||||
public static bool TryGet(string noteID, out HoldInfo info)
|
||||
|
||||
@@ -1,176 +1,190 @@
|
||||
using UnityEngine;
|
||||
using TMPro;
|
||||
using System;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class InputManager : MonoBehaviour
|
||||
{
|
||||
public static InputManager Instance { get; private set; }
|
||||
public static event Action<KeyCode> OnKeyPressed;
|
||||
public static event Action<KeyCode> OnKeyReleased;
|
||||
|
||||
[Header("轨道判定显示TMP对象(请在Inspector中连接)")]
|
||||
public TextMeshProUGUI[] trackJudgeTexts = new TextMeshProUGUI[5];
|
||||
|
||||
[Header("按键指示文本(独立于判定文本,五个分别对应按键:D,F,Space,J,K)")]
|
||||
public Text[] trackKeyTexts = new Text[5];
|
||||
|
||||
[Header("是否显示判定文字")]
|
||||
public bool showJudgeText = true;
|
||||
|
||||
// 按键激活/非激活颜色
|
||||
[Header("按键高亮颜色设置")]
|
||||
[Tooltip("按键被按下时的颜色")]
|
||||
public Color keyActiveColor = Color.yellow;
|
||||
[Tooltip("按键未按下时的颜色(默认黑色)")]
|
||||
public Color keyInactiveColor = Color.black;
|
||||
|
||||
// 判定结果对应颜色
|
||||
[Header("判定结果对应颜色")]
|
||||
public Color perfectColor = Color.yellow;
|
||||
public Color greatColor = Color.green;
|
||||
public Color goodColor = Color.cyan;
|
||||
public Color missColor = Color.red;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance == null)
|
||||
Instance = this;
|
||||
else
|
||||
Destroy(gameObject);
|
||||
|
||||
// 初始化按键文本为非激活颜色
|
||||
if (trackKeyTexts != null)
|
||||
{
|
||||
foreach (var t in trackKeyTexts)
|
||||
{
|
||||
if (t != null) t.color = keyInactiveColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// Ensure UI shows current bindings from KeyBindingManager/PlayerPrefs
|
||||
RefreshKeyLabels();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
foreach (string color in new string[] { "red", "green", "yellow", "purple", "blue" })
|
||||
{
|
||||
KeyCode key = KeyBindingManager.GetKeyForColor(color);
|
||||
if (key == KeyCode.None) continue;
|
||||
|
||||
int index = GetIndexForColor(color);
|
||||
|
||||
if (Input.GetKeyDown(key))
|
||||
{
|
||||
OnKeyPressed?.Invoke(key);
|
||||
// 调用 JudgeManager 统一判断最早的音符
|
||||
JudgeManager.Instance.JudgeEarliestNote(key);
|
||||
|
||||
// 按下时设置对应的按键文本颜色为激活色(如果已设置)
|
||||
if (trackKeyTexts != null && index >= 0 && index < trackKeyTexts.Length)
|
||||
{
|
||||
var txt = trackKeyTexts[index];
|
||||
if (txt != null)
|
||||
txt.color = keyActiveColor;
|
||||
}
|
||||
}
|
||||
if (Input.GetKeyUp(key))
|
||||
{
|
||||
OnKeyReleased?.Invoke(key);
|
||||
|
||||
// 松开时恢复为非激活颜色
|
||||
if (trackKeyTexts != null && index >= 0 && index < trackKeyTexts.Length)
|
||||
{
|
||||
var txt = trackKeyTexts[index];
|
||||
if (txt != null)
|
||||
txt.color = keyInactiveColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force a release event for all bound keys and reset UI indicators.
|
||||
/// Useful for external "clear input" operations where the system should treat
|
||||
/// all keys as released regardless of physical state.
|
||||
/// </summary>
|
||||
public void ForceReleaseAllKeys()
|
||||
{
|
||||
string[] colors = new string[] { "red", "green", "yellow", "purple", "blue" };
|
||||
foreach (var color in colors)
|
||||
{
|
||||
KeyCode key = KeyBindingManager.GetKeyForColor(color);
|
||||
if (key == KeyCode.None) continue;
|
||||
try { OnKeyReleased?.Invoke(key); } catch { }
|
||||
|
||||
int index = GetIndexForColor(color);
|
||||
if (trackKeyTexts != null && index >= 0 && index < trackKeyTexts.Length)
|
||||
{
|
||||
var txt = trackKeyTexts[index];
|
||||
if (txt != null) txt.color = keyInactiveColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh displayed labels for key bindings (called after rebind)
|
||||
public void RefreshKeyLabels()
|
||||
{
|
||||
string[] colors = new string[] { "red", "green", "yellow", "purple", "blue" };
|
||||
if (trackKeyTexts == null) return;
|
||||
for (int i = 0; i < colors.Length && i < trackKeyTexts.Length; i++)
|
||||
{
|
||||
var txt = trackKeyTexts[i];
|
||||
if (txt == null) continue;
|
||||
KeyCode k = KeyBindingManager.GetKeyForColor(colors[i]);
|
||||
txt.text = KeyBindingManager.GetDisplayName(k);
|
||||
}
|
||||
}
|
||||
|
||||
private int GetIndexForColor(string color)
|
||||
{
|
||||
// 保持与 trackJudgeTexts 相同的索引映射
|
||||
switch (color)
|
||||
{
|
||||
case "red": return 0;
|
||||
case "green": return 1;
|
||||
case "yellow": return 2;
|
||||
case "purple": return 3;
|
||||
case "blue": return 4;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新指定轨道的判定文本和颜色
|
||||
/// </summary>
|
||||
public void ShowJudgeResult(int trackIndex, string result)
|
||||
{
|
||||
if (!showJudgeText) return;
|
||||
if (trackJudgeTexts == null || trackIndex < 0 || trackIndex >= trackJudgeTexts.Length) return;
|
||||
var textObj = trackJudgeTexts[trackIndex];
|
||||
if (textObj == null) return;
|
||||
textObj.text = result;
|
||||
switch (result)
|
||||
{
|
||||
case "Perfect":
|
||||
textObj.color = perfectColor;
|
||||
break;
|
||||
case "Great":
|
||||
textObj.color = greatColor;
|
||||
break;
|
||||
case "Good":
|
||||
textObj.color = goodColor;
|
||||
break;
|
||||
case "Miss":
|
||||
textObj.color = missColor;
|
||||
break;
|
||||
default:
|
||||
textObj.color = Color.white;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
using UnityEngine;
|
||||
using TMPro;
|
||||
using System;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class InputManager : MonoBehaviour
|
||||
{
|
||||
public static InputManager Instance { get; private set; }
|
||||
public static event Action<KeyCode> OnKeyPressed;
|
||||
public static event Action<KeyCode> OnKeyReleased;
|
||||
|
||||
private static readonly string[] TrackColors = { "red", "green", "yellow", "purple", "blue" };
|
||||
|
||||
[Header("����ж���ʾTMP��������Inspector�����ӣ�")]
|
||||
public TextMeshProUGUI[] trackJudgeTexts = new TextMeshProUGUI[5];
|
||||
|
||||
[Header("����ָʾ�ı����������ж��ı�������ֱ��Ӧ������D,F,Space,J,K��")]
|
||||
public Text[] trackKeyTexts = new Text[5];
|
||||
|
||||
[Header("�Ƿ���ʾ�ж�����")]
|
||||
public bool showJudgeText = true;
|
||||
|
||||
// ��������/�Ǽ�����ɫ
|
||||
[Header("����������ɫ����")]
|
||||
[Tooltip("����������ʱ����ɫ")]
|
||||
public Color keyActiveColor = Color.yellow;
|
||||
[Tooltip("����δ����ʱ����ɫ��Ĭ�Ϻ�ɫ��")]
|
||||
public Color keyInactiveColor = Color.black;
|
||||
|
||||
// �ж������Ӧ��ɫ
|
||||
[Header("�ж������Ӧ��ɫ")]
|
||||
public Color perfectColor = Color.yellow;
|
||||
public Color greatColor = Color.green;
|
||||
public Color goodColor = Color.cyan;
|
||||
public Color missColor = Color.red;
|
||||
|
||||
private KeyCode[] cachedKeys = new KeyCode[5];
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance == null)
|
||||
Instance = this;
|
||||
else
|
||||
Destroy(gameObject);
|
||||
|
||||
// 初始化按键缓存
|
||||
RefreshKeyCache();
|
||||
|
||||
// ��ʼ�������ı�Ϊ�Ǽ�����ɫ
|
||||
if (trackKeyTexts != null)
|
||||
{
|
||||
foreach (var t in trackKeyTexts)
|
||||
{
|
||||
if (t != null) t.color = keyInactiveColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshKeyCache()
|
||||
{
|
||||
for (int i = 0; i < TrackColors.Length; i++)
|
||||
{
|
||||
cachedKeys[i] = KeyBindingManager.GetKeyForColor(TrackColors[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// Ensure UI shows current bindings from KeyBindingManager/PlayerPrefs
|
||||
RefreshKeyLabels();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
for (int i = 0; i < TrackColors.Length; i++)
|
||||
{
|
||||
KeyCode key = cachedKeys[i];
|
||||
if (key == KeyCode.None) continue;
|
||||
|
||||
int index = i; // TrackColors array index matches track index logic here
|
||||
|
||||
if (Input.GetKeyDown(key))
|
||||
{
|
||||
OnKeyPressed?.Invoke(key);
|
||||
// ���� JudgeManager ͳһ�ж����������
|
||||
JudgeManager.Instance.JudgeEarliestNote(key);
|
||||
|
||||
// ����ʱ���ö�Ӧ�İ����ı���ɫΪ����ɫ����������ã�
|
||||
if (trackKeyTexts != null && index >= 0 && index < trackKeyTexts.Length)
|
||||
{
|
||||
var txt = trackKeyTexts[index];
|
||||
if (txt != null)
|
||||
txt.color = keyActiveColor;
|
||||
}
|
||||
}
|
||||
if (Input.GetKeyUp(key))
|
||||
{
|
||||
OnKeyReleased?.Invoke(key);
|
||||
|
||||
// �ɿ�ʱ�ָ�Ϊ�Ǽ�����ɫ
|
||||
if (trackKeyTexts != null && index >= 0 && index < trackKeyTexts.Length)
|
||||
{
|
||||
var txt = trackKeyTexts[index];
|
||||
if (txt != null)
|
||||
txt.color = keyInactiveColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force a release event for all bound keys and reset UI indicators.
|
||||
/// Useful for external "clear input" operations where the system should treat
|
||||
/// all keys as released regardless of physical state.
|
||||
/// </summary>
|
||||
public void ForceReleaseAllKeys()
|
||||
{
|
||||
for (int i = 0; i < TrackColors.Length; i++)
|
||||
{
|
||||
string color = TrackColors[i];
|
||||
KeyCode key = KeyBindingManager.GetKeyForColor(color);
|
||||
if (key == KeyCode.None) continue;
|
||||
try { OnKeyReleased?.Invoke(key); } catch { }
|
||||
|
||||
int index = GetIndexForColor(color);
|
||||
if (trackKeyTexts != null && index >= 0 && index < trackKeyTexts.Length)
|
||||
{
|
||||
var txt = trackKeyTexts[index];
|
||||
if (txt != null) txt.color = keyInactiveColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh displayed labels for key bindings (called after rebind)
|
||||
public void RefreshKeyLabels()
|
||||
{
|
||||
if (trackKeyTexts == null) return;
|
||||
for (int i = 0; i < TrackColors.Length && i < trackKeyTexts.Length; i++)
|
||||
{
|
||||
var txt = trackKeyTexts[i];
|
||||
if (txt == null) continue;
|
||||
KeyCode k = KeyBindingManager.GetKeyForColor(TrackColors[i]);
|
||||
txt.text = KeyBindingManager.GetDisplayName(k);
|
||||
}
|
||||
}
|
||||
|
||||
private int GetIndexForColor(string color)
|
||||
{
|
||||
// ������ trackJudgeTexts ��ͬ������ӳ��
|
||||
switch (color)
|
||||
{
|
||||
case "red": return 0;
|
||||
case "green": return 1;
|
||||
case "yellow": return 2;
|
||||
case "purple": return 3;
|
||||
case "blue": return 4;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ����ָ��������ж��ı�����ɫ
|
||||
/// </summary>
|
||||
public void ShowJudgeResult(int trackIndex, string result)
|
||||
{
|
||||
if (!showJudgeText) return;
|
||||
if (trackJudgeTexts == null || trackIndex < 0 || trackIndex >= trackJudgeTexts.Length) return;
|
||||
var textObj = trackJudgeTexts[trackIndex];
|
||||
if (textObj == null) return;
|
||||
textObj.text = result;
|
||||
switch (result)
|
||||
{
|
||||
case "Perfect":
|
||||
textObj.color = perfectColor;
|
||||
break;
|
||||
case "Great":
|
||||
textObj.color = greatColor;
|
||||
break;
|
||||
case "Good":
|
||||
textObj.color = goodColor;
|
||||
break;
|
||||
case "Miss":
|
||||
textObj.color = missColor;
|
||||
break;
|
||||
default:
|
||||
textObj.color = Color.white;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,23 @@ public class JudgeManager : MonoBehaviour
|
||||
public settlementController sc;
|
||||
public NoteSpawner ns;
|
||||
|
||||
private bool _cachedIsDebugEnabled;
|
||||
private void UpdateDebugCache()
|
||||
{
|
||||
_cachedIsDebugEnabled = (_enableDebugLogs || GameConfig.verboseLogs);
|
||||
}
|
||||
|
||||
[Header("Debug Settings")]
|
||||
[SerializeField] private bool _enableDebugLogs = false;
|
||||
public bool EnableDebugLogs => _enableDebugLogs;
|
||||
public static bool IsDebugEnabled => (Instance != null && Instance._cachedIsDebugEnabled);
|
||||
|
||||
[Header("Global Note Settings")]
|
||||
[Tooltip("Global material applied to hold notes when they are judged.")]
|
||||
public Material globalJudgedMaterial;
|
||||
[Tooltip("Whether to enable material switching after a hold note is judged.")]
|
||||
public bool enableJudgedMaterial = false;
|
||||
|
||||
public static JudgeManager Instance { get; private set; }
|
||||
|
||||
// total/remaining notes tracking for end-of-song detection
|
||||
@@ -35,9 +52,12 @@ public class JudgeManager : MonoBehaviour
|
||||
|
||||
private HoldJudgeState GetHoldState(string noteID)
|
||||
{
|
||||
if (!holdStates.ContainsKey(noteID))
|
||||
holdStates[noteID] = new HoldJudgeState();
|
||||
return holdStates[noteID];
|
||||
if (!holdStates.TryGetValue(noteID, out var state))
|
||||
{
|
||||
state = new HoldJudgeState();
|
||||
holdStates[noteID] = state;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
// Public event and hook invoked when the entire beatmap's last note has been judged.
|
||||
@@ -52,7 +72,7 @@ public class JudgeManager : MonoBehaviour
|
||||
{
|
||||
if (settlement_go == null)
|
||||
{
|
||||
Debug.LogError("结算页面不存在!");
|
||||
if (IsDebugEnabled) Debug.LogError("结算页面不存在!");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -63,7 +83,7 @@ public class JudgeManager : MonoBehaviour
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[JudgeManager] Failed to activate settlement_go: {ex}");
|
||||
if (IsDebugEnabled) Debug.LogWarning($"[JudgeManager] Failed to activate settlement_go: {ex}");
|
||||
}
|
||||
|
||||
// Try to resolve settlementController reference if missing
|
||||
@@ -87,12 +107,12 @@ public class JudgeManager : MonoBehaviour
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[JudgeManager] Exception calling startSettlement_uiUpdate: {ex}");
|
||||
if (IsDebugEnabled) Debug.LogWarning($"[JudgeManager] Exception calling startSettlement_uiUpdate: {ex}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[JudgeManager] settlementController (sc) not found on settlement_go; cannot call startSettlement_uiUpdate().");
|
||||
if (IsDebugEnabled) Debug.LogWarning("[JudgeManager] settlementController (sc) not found on settlement_go; cannot call startSettlement_uiUpdate().");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +128,7 @@ public class JudgeManager : MonoBehaviour
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[JudgeManager] Exception while invoking AllNotesJudged event: {ex}");
|
||||
if (IsDebugEnabled) Debug.LogWarning($"[JudgeManager] Exception while invoking AllNotesJudged event: {ex}");
|
||||
}
|
||||
try
|
||||
{
|
||||
@@ -116,7 +136,7 @@ public class JudgeManager : MonoBehaviour
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[JudgeManager] Exception in OnAllNotesJudged hook: {ex}");
|
||||
if (IsDebugEnabled) Debug.LogWarning($"[JudgeManager] Exception in OnAllNotesJudged hook: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +145,7 @@ public class JudgeManager : MonoBehaviour
|
||||
if (Instance == null) Instance = this;
|
||||
else Destroy(gameObject);
|
||||
|
||||
UpdateDebugCache();
|
||||
if (settlement_go != null) settlement_go.SetActive(false);
|
||||
}
|
||||
|
||||
@@ -136,7 +157,7 @@ public class JudgeManager : MonoBehaviour
|
||||
{
|
||||
totalNotes = Mathf.Max(0, total);
|
||||
remainingNotes = totalNotes;
|
||||
Debug.Log($"[JudgeManager] SetTotalNotes: total={totalNotes}");
|
||||
if (IsDebugEnabled) Debug.Log($"[JudgeManager] SetTotalNotes: total={totalNotes}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -147,10 +168,10 @@ public class JudgeManager : MonoBehaviour
|
||||
{
|
||||
if (remainingNotes <= 0) return;
|
||||
remainingNotes = Mathf.Max(0, remainingNotes - 1);
|
||||
Debug.Log($"[JudgeManager] NotifyNoteJudged: remaining={remainingNotes}");
|
||||
if (IsDebugEnabled) Debug.Log($"[JudgeManager] NotifyNoteJudged: remaining={remainingNotes}");
|
||||
if (remainingNotes == 0)
|
||||
{
|
||||
Debug.Log("[JudgeManager] All notes judged -> Triggering AllNotesJudged");
|
||||
if (IsDebugEnabled) Debug.Log("[JudgeManager] All notes judged -> Triggering AllNotesJudged");
|
||||
TriggerAllNotesJudged();
|
||||
}
|
||||
}
|
||||
@@ -161,11 +182,11 @@ public class JudgeManager : MonoBehaviour
|
||||
var s = GetHoldState(noteID);
|
||||
if (s.startResolved)
|
||||
{
|
||||
Debug.LogWarning($"[JudgeManager] TryResolveStart: already resolved start for {noteID}");
|
||||
if (IsDebugEnabled) Debug.LogWarning($"[JudgeManager] TryResolveStart: already resolved start for {noteID}");
|
||||
return false;
|
||||
}
|
||||
s.startResolved = true;
|
||||
Debug.Log($"[JudgeManager] TryResolveStart: start resolved for {noteID} at time={Time.time:F3}");
|
||||
if (IsDebugEnabled) Debug.Log($"[JudgeManager] TryResolveStart: start resolved for {noteID} at time={Time.time:F3}");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -174,11 +195,11 @@ public class JudgeManager : MonoBehaviour
|
||||
var s = GetHoldState(noteID);
|
||||
if (s.endResolved)
|
||||
{
|
||||
Debug.LogWarning($"[JudgeManager] TryResolveEnd: already resolved end for {noteID}");
|
||||
if (IsDebugEnabled) Debug.LogWarning($"[JudgeManager] TryResolveEnd: already resolved end for {noteID}");
|
||||
return false;
|
||||
}
|
||||
s.endResolved = true;
|
||||
Debug.Log($"[JudgeManager] TryResolveEnd: end resolved for {noteID} at time={Time.time:F3}");
|
||||
if (IsDebugEnabled) Debug.Log($"[JudgeManager] TryResolveEnd: end resolved for {noteID} at time={Time.time:F3}");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -187,11 +208,11 @@ public class JudgeManager : MonoBehaviour
|
||||
var s = GetHoldState(noteID);
|
||||
if (s.skillTriggered)
|
||||
{
|
||||
Debug.LogWarning($"[JudgeManager] TryTriggerSkill: already triggered for {noteID}");
|
||||
if (IsDebugEnabled) Debug.LogWarning($"[JudgeManager] TryTriggerSkill: already triggered for {noteID}");
|
||||
return false;
|
||||
}
|
||||
s.skillTriggered = true;
|
||||
Debug.Log($"[JudgeManager] TryTriggerSkill: skill triggered for {noteID} at time={Time.time:F3}");
|
||||
if (IsDebugEnabled) Debug.Log($"[JudgeManager] TryTriggerSkill: skill triggered for {noteID} at time={Time.time:F3}");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -200,7 +221,7 @@ public class JudgeManager : MonoBehaviour
|
||||
if (holdStates.ContainsKey(noteID))
|
||||
{
|
||||
holdStates.Remove(noteID);
|
||||
Debug.Log($"[JudgeManager] ClearHoldState: cleared state for {noteID}");
|
||||
if (IsDebugEnabled) Debug.Log($"[JudgeManager] ClearHoldState: cleared state for {noteID}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,34 +230,34 @@ public class JudgeManager : MonoBehaviour
|
||||
public void RegisterScheduledEndTime(string noteID, float endTime)
|
||||
{
|
||||
noteEndTimes[noteID] = endTime;
|
||||
Debug.Log($"[JudgeManager] RegisterScheduledEndTime: {noteID} -> {endTime:F3}");
|
||||
if (IsDebugEnabled) Debug.Log($"[JudgeManager] RegisterScheduledEndTime: {noteID} -> {endTime:F3}");
|
||||
}
|
||||
|
||||
public float GetScheduledEndTime(string noteID)
|
||||
{
|
||||
return noteEndTimes.ContainsKey(noteID) ? noteEndTimes[noteID] : 0f;
|
||||
return noteEndTimes.TryGetValue(noteID, out var endTime) ? endTime : 0f;
|
||||
}
|
||||
|
||||
public void RegisterStartJudged(string noteID, bool state)
|
||||
{
|
||||
startJudgedNotes[noteID] = state;
|
||||
Debug.Log($"[JudgeManager] RegisterStartJudged: {noteID} = {state} at time={Time.time:F3}");
|
||||
if (IsDebugEnabled) Debug.Log($"[JudgeManager] RegisterStartJudged: {noteID} = {state} at time={Time.time:F3}");
|
||||
}
|
||||
|
||||
public bool IsStartJudged(string noteID)
|
||||
{
|
||||
return startJudgedNotes.ContainsKey(noteID) && startJudgedNotes[noteID];
|
||||
return startJudgedNotes.TryGetValue(noteID, out var state) && state;
|
||||
}
|
||||
|
||||
public void RegisterNoteReleased(string noteID, bool state)
|
||||
{
|
||||
releasedNotes[noteID] = state;
|
||||
Debug.Log($"[JudgeManager] RegisterNoteReleased: {noteID} = {state}");
|
||||
if (IsDebugEnabled) Debug.Log($"[JudgeManager] RegisterNoteReleased: {noteID} = {state}");
|
||||
}
|
||||
|
||||
public bool HasNoteReleased(string noteID)
|
||||
{
|
||||
return releasedNotes.ContainsKey(noteID) && releasedNotes[noteID];
|
||||
return releasedNotes.TryGetValue(noteID, out var state) && state;
|
||||
}
|
||||
|
||||
public void RegisterNote(KeyCode key, Note note)
|
||||
@@ -246,7 +267,7 @@ public class JudgeManager : MonoBehaviour
|
||||
|
||||
if (!judgeQueues[key].Contains(note))
|
||||
judgeQueues[key].Enqueue(note);
|
||||
Debug.Log($"[JudgeManager] RegisterNote: key={key} note={note.name} time={Time.time:F3} queueSize={judgeQueues[key].Count}");
|
||||
if (IsDebugEnabled) Debug.Log($"[JudgeManager] RegisterNote: key={key} note={note.name} time={Time.time:F3} queueSize={judgeQueues[key].Count}");
|
||||
}
|
||||
|
||||
public void UnregisterNote(KeyCode key, Note note)
|
||||
@@ -263,7 +284,7 @@ public class JudgeManager : MonoBehaviour
|
||||
n.JudgeMiss();
|
||||
}
|
||||
judgeQueues[key] = newQueue;
|
||||
Debug.Log($"[JudgeManager] UnregisterNote: key={key} removed {note.name}, newQueueSize={judgeQueues[key].Count}");
|
||||
if (IsDebugEnabled) Debug.Log($"[JudgeManager] UnregisterNote: key={key} removed {note.name}, newQueueSize={judgeQueues[key].Count}");
|
||||
}
|
||||
|
||||
public void JudgeEarliestNote(KeyCode key)
|
||||
@@ -273,7 +294,7 @@ public class JudgeManager : MonoBehaviour
|
||||
Note note = judgeQueues[key].Dequeue();
|
||||
if (note != null && !note.IsJudged())
|
||||
{
|
||||
Debug.Log($"[JudgeManager] JudgeEarliestNote: judging {note.name} for key={key} at time={Time.time:F3}");
|
||||
if (IsDebugEnabled) Debug.Log($"[JudgeManager] JudgeEarliestNote: judging {note.name} for key={key} at time={Time.time:F3}");
|
||||
note.Judge();
|
||||
}
|
||||
}
|
||||
@@ -281,15 +302,28 @@ public class JudgeManager : MonoBehaviour
|
||||
|
||||
public void RegisterMiddlePassed(string noteID)
|
||||
{
|
||||
if (!middlePassedCounts.ContainsKey(noteID))
|
||||
middlePassedCounts[noteID] = 0;
|
||||
middlePassedCounts[noteID]++;
|
||||
Debug.Log($"[JudgeManager] Middle passed for {noteID}, total={middlePassedCounts[noteID]}");
|
||||
if (middlePassedCounts.TryGetValue(noteID, out int count))
|
||||
{
|
||||
middlePassedCounts[noteID] = count + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
middlePassedCounts[noteID] = 1;
|
||||
}
|
||||
if (IsDebugEnabled) Debug.Log($"[JudgeManager] Middle passed for {noteID}, total={middlePassedCounts[noteID]}");
|
||||
}
|
||||
|
||||
// Event for syncing alpha across segments of a hold note
|
||||
public event Action<string, float> OnHoldAlphaSync;
|
||||
|
||||
public void SyncHoldAlpha(string noteID, float alpha)
|
||||
{
|
||||
OnHoldAlphaSync?.Invoke(noteID, alpha);
|
||||
}
|
||||
|
||||
public int GetMiddlePassedCount(string noteID)
|
||||
{
|
||||
return middlePassedCounts.ContainsKey(noteID) ? middlePassedCounts[noteID] : 0;
|
||||
return middlePassedCounts.TryGetValue(noteID, out var count) ? count : 0;
|
||||
}
|
||||
|
||||
public void ClearNoteRecord(string noteID)
|
||||
@@ -299,6 +333,6 @@ public class JudgeManager : MonoBehaviour
|
||||
noteEndTimes.Remove(noteID);
|
||||
middlePassedCounts.Remove(noteID);
|
||||
ClearHoldState(noteID);
|
||||
Debug.Log($"[JudgeManager] ClearNoteRecord: cleared records for {noteID}");
|
||||
if (IsDebugEnabled) Debug.Log($"[JudgeManager] ClearNoteRecord: cleared records for {noteID}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@ using UnityEngine;
|
||||
|
||||
public class Notes : MonoBehaviour
|
||||
{
|
||||
public int trackIndex; // 轨道索引
|
||||
public float hitTime; // 该音符应该被击中的时间
|
||||
private bool canBeJudged = false; // 是否进入判定区域
|
||||
private bool isHit = false; // 是否已被击中
|
||||
public int trackIndex; // �������
|
||||
public float hitTime; // ������Ӧ�ñ����е�ʱ��
|
||||
private bool canBeJudged = false; // �Ƿ�����ж�����
|
||||
private bool isHit = false; // �Ƿ��ѱ�����
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// 只有进入判定区后,才允许自动miss
|
||||
// ֻ�н����ж����������Զ�miss
|
||||
if (canBeJudged && !isHit && Time.timeSinceLevelLoad > hitTime + 0.3f)
|
||||
{
|
||||
JudgeMiss();
|
||||
@@ -34,7 +34,7 @@ public class Notes : MonoBehaviour
|
||||
|
||||
public void JudgeNote()
|
||||
{
|
||||
if (!canBeJudged || isHit) return; // 仅在进入判定区后才能被判定
|
||||
if (!canBeJudged || isHit) return; // ���ڽ����ж�������ܱ��ж�
|
||||
|
||||
float currentTime = Time.timeSinceLevelLoad;
|
||||
float timeDifference = Mathf.Abs(currentTime - hitTime);
|
||||
@@ -59,10 +59,10 @@ public class Notes : MonoBehaviour
|
||||
|
||||
private void Recycle()
|
||||
{
|
||||
// 如果存在 NotePool,优先归还池,否则销毁物体
|
||||
// ������� NotePool�����ȹ黹�أ�������������
|
||||
if (NotePool.Instance != null)
|
||||
{
|
||||
NotePool.Instance.ReturnNote(gameObject, "red"); // color 未保存时默认 red,建议上层调用时传入
|
||||
NotePool.Instance.ReturnNote(gameObject, "red"); // color δ����ʱĬ�� red�������ϲ����ʱ����
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,385 +1,390 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class Note : BaseNote
|
||||
{
|
||||
private KeyCode keyToPress;
|
||||
private string noteColor;
|
||||
private AnimationController anim;
|
||||
private NoteController controller;
|
||||
private bool isJudged = false;
|
||||
private bool hasLock = false; // track whether we hold the track lock
|
||||
|
||||
private NoteData noteData;
|
||||
|
||||
[Header("判定配置")]
|
||||
public NoteJudgeConfig judgeConfig; // 判定窗口配置,包含判定时间范围
|
||||
|
||||
// Cache allies per track to avoid GameObject.Find on every judge.
|
||||
private static AllyCombatant[] allyCache;
|
||||
|
||||
// Timeout handling - force miss if note isn't judged by this time
|
||||
private float missDeadlineTime = -1f;
|
||||
|
||||
private static AllyCombatant GetAllyForTrackCached(int trackIndex)
|
||||
{
|
||||
if (trackIndex < 0) return null;
|
||||
if (allyCache == null || allyCache.Length < 10) allyCache = new AllyCombatant[10];
|
||||
if (allyCache[trackIndex] != null) return allyCache[trackIndex];
|
||||
|
||||
var allyGo = GameObject.Find($"ally_0{trackIndex + 1}");
|
||||
if (allyGo != null)
|
||||
{
|
||||
allyCache[trackIndex] = allyGo.GetComponent<AllyCombatant>();
|
||||
}
|
||||
return allyCache[trackIndex];
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
anim = GetComponent<AnimationController>();
|
||||
controller = GetComponent<NoteController>();
|
||||
}
|
||||
|
||||
public void Setup(KeyCode key, int trackIndex, float speed, float hitTime, string color, NoteJudgeConfig judgeConfig, NoteData data)
|
||||
{
|
||||
keyToPress = key;
|
||||
noteColor = color;
|
||||
TrackIndex = trackIndex;
|
||||
Speed = speed;
|
||||
this.hitTime = hitTime;
|
||||
isJudged = false;
|
||||
hasLock = false;
|
||||
this.judgeConfig = judgeConfig;
|
||||
this.noteData = data;
|
||||
|
||||
// Set miss deadline: hitTime + missRange (the latest time to judge before auto-miss)
|
||||
float missRange = (judgeConfig?.missRange ?? 0.5f);
|
||||
this.missDeadlineTime = hitTime + missRange;
|
||||
|
||||
if (controller != null)
|
||||
{
|
||||
controller.SetSpeed(speed);
|
||||
}
|
||||
|
||||
InputManager.OnKeyPressed += HandlePress;
|
||||
|
||||
// 判定配置检查
|
||||
if (judgeConfig == null)
|
||||
{
|
||||
if (GameConfig.verboseLogs) Debug.LogError($"NoteJudgeConfig is null! 判定配置未赋值!track={trackIndex}");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GameConfig.verboseLogs) Debug.Log($"NoteJudgeConfig: perfect={judgeConfig.perfectRange}, great={judgeConfig.greatRange}, good={judgeConfig.goodRange}, miss={judgeConfig.missRange}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
InputManager.OnKeyPressed -= HandlePress;
|
||||
// Release lock if we still hold it
|
||||
if (hasLock)
|
||||
{
|
||||
TrackKeyManager.Instance?.UnlockTrackForJudge(TrackIndex, gameObject.GetInstanceID().ToString());
|
||||
hasLock = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// Force miss if we've passed the deadline without being judged
|
||||
if (!isJudged && Time.time > missDeadlineTime && gameObject.activeSelf)
|
||||
{
|
||||
if (GameConfig.verboseLogs) Debug.Log($"[Note] {noteColor} on track {TrackIndex} exceeded miss deadline at time {Time.time:F3}, forcing Miss");
|
||||
JudgeMiss();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandlePress(KeyCode key)
|
||||
{
|
||||
// only proceed for matching key and not already judged
|
||||
if (isJudged || key != keyToPress)
|
||||
return;
|
||||
|
||||
string myId = gameObject.GetInstanceID().ToString();
|
||||
|
||||
// Ensure only one note per track consumes this physical press per frame
|
||||
if (TrackKeyManager.Instance != null && !TrackKeyManager.Instance.TryConsumeTrackForFrame(TrackIndex))
|
||||
{
|
||||
if (GameConfig.verboseLogs) Debug.Log($"[Note] Press ignored due to frame consumption on track {TrackIndex}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure we are the front-most note on this track. If not, ignore this press so later press can hit the next note.
|
||||
var headId = TrackKeyManager.Instance?.GetCurrentNoteId(TrackIndex);
|
||||
if (headId != null && headId != myId)
|
||||
{
|
||||
if (GameConfig.verboseLogs) Debug.Log($"[Note] Press ignored because another note is ahead on track {TrackIndex}: head={headId} me={myId}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to acquire lock for this note (prevents multiple notes on same track being judged at once)
|
||||
if (TrackKeyManager.Instance != null && !TrackKeyManager.Instance.TryLockTrackForJudge(TrackIndex, myId))
|
||||
return;
|
||||
|
||||
hasLock = true;
|
||||
|
||||
float pressTime = Time.time;
|
||||
float maxWindow = (judgeConfig?.missRange ?? 0.5f);
|
||||
|
||||
// If there is a controller and the note is not inside judge zone, only allow judgment
|
||||
// if the press time is within the allowed window. Otherwise ignore the press.
|
||||
if (controller != null && !controller.IsInJudgeZone())
|
||||
{
|
||||
if (Mathf.Abs(pressTime - hitTime) > maxWindow)
|
||||
{
|
||||
// outside allowed window and not in judge zone -> release lock and ignore the press
|
||||
ReleaseTrackLock(myId);
|
||||
if (GameConfig.verboseLogs) Debug.Log($"[Note] {noteColor} press rejected: outside timing window and not in judge zone");
|
||||
return;
|
||||
}
|
||||
// otherwise within window: fall through to normal judging
|
||||
}
|
||||
|
||||
float timeDifference = Mathf.Abs(pressTime - hitTime);
|
||||
// raw offset ms: positive = note was early (hitTime > pressTime)
|
||||
float rawOffsetMs = (hitTime - pressTime) * 1000f;
|
||||
string judgeResult = null;
|
||||
|
||||
if (judgeConfig != null)
|
||||
{
|
||||
if (timeDifference <= judgeConfig.perfectRange)
|
||||
{
|
||||
if (GameConfig.verboseLogs) Debug.Log($"{keyToPress}: Perfect");
|
||||
judgeResult = "Perfect";
|
||||
ScoreManager.Instance.countPerfect += 1;
|
||||
if (TrackIndex >= 0 && TrackIndex < 5) ScoreManager.Instance.trackPerfectCounts[TrackIndex]++;
|
||||
if (noteData != null) noteData.judgeOffsetMs = rawOffsetMs;
|
||||
ScoreManager.Instance.RecordOffset(rawOffsetMs);
|
||||
}
|
||||
else if (timeDifference <= judgeConfig.greatRange)
|
||||
{
|
||||
if (GameConfig.verboseLogs) Debug.Log($"{keyToPress}: Great");
|
||||
judgeResult = "Great";
|
||||
ScoreManager.Instance.countGreat += 1;
|
||||
if (TrackIndex >= 0 && TrackIndex < 5) ScoreManager.Instance.trackGreatCounts[TrackIndex]++;
|
||||
if (noteData != null) noteData.judgeOffsetMs = rawOffsetMs;
|
||||
ScoreManager.Instance.RecordOffset(rawOffsetMs);
|
||||
}
|
||||
else if (timeDifference <= judgeConfig.goodRange)
|
||||
{
|
||||
if (GameConfig.verboseLogs) Debug.Log($"{keyToPress}: Good");
|
||||
judgeResult = "Good";
|
||||
ScoreManager.Instance.countGood += 1;
|
||||
if (TrackIndex >= 0 && TrackIndex < 5) ScoreManager.Instance.trackGoodCounts[TrackIndex]++;
|
||||
if (noteData != null) noteData.judgeOffsetMs = rawOffsetMs;
|
||||
ScoreManager.Instance.RecordOffset(rawOffsetMs);
|
||||
}
|
||||
else
|
||||
{
|
||||
JudgeMiss();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// fallback timing
|
||||
if (timeDifference <= 0.08f)
|
||||
{
|
||||
if (GameConfig.verboseLogs) Debug.Log($"{keyToPress}: Perfect");
|
||||
judgeResult = "Perfect";
|
||||
ScoreManager.Instance.countPerfect += 1;
|
||||
// Add missing offset record for fallback path too if possible
|
||||
ScoreManager.Instance.RecordOffset(0);
|
||||
}
|
||||
else if (timeDifference <= 0.15f)
|
||||
{
|
||||
if (GameConfig.verboseLogs) Debug.Log($"{keyToPress}: Great");
|
||||
judgeResult = "Great";
|
||||
ScoreManager.Instance.countGreat += 1;
|
||||
ScoreManager.Instance.RecordOffset(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
JudgeMiss();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(judgeResult))
|
||||
{
|
||||
InputManager.Instance?.ShowJudgeResult(TrackIndex, judgeResult);
|
||||
JudgeSoundManager.Instance?.PlayJudgeSound(judgeResult);
|
||||
teamUIController.Instance?.OnJudgeResult(judgeResult);
|
||||
|
||||
// Spawn judgment prefab - ensure noteColor is passed correctly
|
||||
if (GameConfig.verboseLogs) Debug.Log($"[Note] Spawning judge prefab: color={noteColor}, result={judgeResult}");
|
||||
Animation_GenerateJudgementSituationPrefab.Instance?.SpawnJudgePrefab(noteColor, judgeResult);
|
||||
|
||||
try
|
||||
{
|
||||
var ally = GetAllyForTrackCached(TrackIndex);
|
||||
if (ally != null)
|
||||
{
|
||||
int added = ally.AddScoreForJudge(judgeResult);
|
||||
float efficiency = ally.scoreEfficiency;
|
||||
ScoreManager.Instance?.AddPmScoreForTrack(TrackIndex, added, efficiency);
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[Note] Failed to add per-track score: {ex}");
|
||||
}
|
||||
|
||||
try { SkillBuilder.Instance?.NotifyNoteHit(TrackIndex, judgeResult, SkillDefinition.NoteTypeTrigger.Tap); } catch { }
|
||||
}
|
||||
|
||||
Judge();
|
||||
}
|
||||
|
||||
public bool IsJudged()
|
||||
{
|
||||
return isJudged;
|
||||
}
|
||||
|
||||
public void Judge()
|
||||
{
|
||||
if (isJudged)
|
||||
return;
|
||||
isJudged = true;
|
||||
|
||||
if (controller != null)
|
||||
{
|
||||
controller.StopMovement();
|
||||
controller.PlayHitEffect();
|
||||
}
|
||||
|
||||
// Remove from per-track queue immediately so following notes become head
|
||||
string myId = gameObject.GetInstanceID().ToString();
|
||||
TrackKeyManager.Instance?.UnregisterKey(TrackIndex, myId);
|
||||
|
||||
// Release lock if we held it
|
||||
if (hasLock)
|
||||
{
|
||||
TrackKeyManager.Instance?.UnlockTrackForJudge(TrackIndex, myId);
|
||||
hasLock = false;
|
||||
}
|
||||
|
||||
ReturnToPool();
|
||||
if (anim != null)
|
||||
{
|
||||
anim.PlayDestroyAnimation(noteColor);
|
||||
}
|
||||
|
||||
// notify global judge manager that this short note has been finally judged (hit)
|
||||
JudgeManager.Instance?.NotifyNoteJudged();
|
||||
}
|
||||
|
||||
public void JudgeMiss()
|
||||
{
|
||||
// idempotent: this can be called from multiple paths
|
||||
if (isJudged) return;
|
||||
|
||||
isJudged = true;
|
||||
ScoreManager.Instance.countMiss += 1;
|
||||
if (TrackIndex >= 0 && TrackIndex < 5) ScoreManager.Instance.trackMissCounts[TrackIndex]++;
|
||||
|
||||
// Record a 0 offset for Miss to ensure total Offset Count matches note count
|
||||
ScoreManager.Instance?.RecordOffset(0);
|
||||
|
||||
if (GameConfig.verboseLogs) Debug.Log($"{keyToPress} Miss");
|
||||
|
||||
InputManager.Instance?.ShowJudgeResult(TrackIndex, "Miss");
|
||||
JudgeSoundManager.Instance?.PlayJudgeSound("Miss");
|
||||
teamUIController.Instance?.OnJudgeResult("Miss");
|
||||
|
||||
// Spawn judgment prefab for Miss - ensure noteColor is passed correctly
|
||||
if (GameConfig.verboseLogs) Debug.Log($"[Note.JudgeMiss] Spawning miss judge prefab: color={noteColor}");
|
||||
Animation_GenerateJudgementSituationPrefab.Instance?.SpawnJudgePrefab(noteColor, "Miss");
|
||||
|
||||
try
|
||||
{
|
||||
var ally = GetAllyForTrackCached(TrackIndex);
|
||||
if (ally != null)
|
||||
{
|
||||
int added = ally.AddScoreForJudge("Miss");
|
||||
float efficiency = ally.scoreEfficiency;
|
||||
ScoreManager.Instance?.AddPmScoreForTrack(TrackIndex, added, efficiency);
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[Note] Failed to add per-track score for Miss: {ex}");
|
||||
}
|
||||
|
||||
try { SkillBuilder.Instance?.NotifyNoteHit(TrackIndex, "Miss", SkillDefinition.NoteTypeTrigger.Tap); } catch { }
|
||||
|
||||
// notify global judge manager that this note has been finally judged (miss)
|
||||
JudgeManager.Instance?.NotifyNoteJudged();
|
||||
|
||||
ReturnToPool();
|
||||
}
|
||||
|
||||
private void ReleaseTrackLock(string myId)
|
||||
{
|
||||
if (hasLock)
|
||||
{
|
||||
TrackKeyManager.Instance?.UnlockTrackForJudge(TrackIndex, myId);
|
||||
hasLock = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReturnToPool()
|
||||
{
|
||||
InputManager.OnKeyPressed -= HandlePress;
|
||||
|
||||
// CRITICAL: Always release lock and unregister before deactivating
|
||||
string myId = gameObject.GetInstanceID().ToString();
|
||||
|
||||
// Release lock if we still hold it
|
||||
ReleaseTrackLock(myId);
|
||||
|
||||
// Remove from per-track queue to avoid stale entries
|
||||
TrackKeyManager.Instance?.UnregisterKey(TrackIndex, myId);
|
||||
|
||||
if (controller != null)
|
||||
{
|
||||
controller.ResetState();
|
||||
}
|
||||
|
||||
gameObject.SetActive(false);
|
||||
NotePool.Instance.ReturnNote(gameObject, noteColor);
|
||||
}
|
||||
|
||||
public int GetTrackIndex()
|
||||
{
|
||||
return TrackIndex;
|
||||
}
|
||||
|
||||
public KeyCode GetKey()
|
||||
{
|
||||
return keyToPress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// From Controller notify Note whether it's inside judge zone
|
||||
/// </summary>
|
||||
public void SetJudgeZone(bool inZone)
|
||||
{
|
||||
// leaving judge zone
|
||||
if (!inZone)
|
||||
{
|
||||
// Only process if we haven't been judged yet
|
||||
if (!isJudged)
|
||||
{
|
||||
// IMPORTANT: Don't call JudgeMiss directly here because SetJudgeZone is called
|
||||
// from OnTriggerExit2D, which is in a physics callback.
|
||||
// Calling ReturnToPool (which deactivates the object) inside a physics callback
|
||||
// causes "GameObject is already being activated or deactivated" errors.
|
||||
// Instead, mark for miss and let Update handle it next frame.
|
||||
if (GameConfig.verboseLogs) Debug.Log($"[Note.SetJudgeZone] Note {noteColor} left judge zone on track {TrackIndex}, will force Miss next frame");
|
||||
missDeadlineTime = Time.time; // Force deadline to now so Update will handle it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using UnityEngine;
|
||||
|
||||
public class Note : BaseNote
|
||||
{
|
||||
private KeyCode keyToPress;
|
||||
private string noteColor;
|
||||
private AnimationController anim;
|
||||
private NoteController controller;
|
||||
private bool isJudged = false;
|
||||
private bool hasLock = false; // track whether we hold the track lock
|
||||
|
||||
private NoteData noteData;
|
||||
|
||||
[Header("判定配置")]
|
||||
public NoteJudgeConfig judgeConfig; // 判定窗口配置,包含判定时间范围
|
||||
|
||||
// Cache allies per track to avoid GameObject.Find on every judge.
|
||||
private static AllyCombatant[] allyCache;
|
||||
|
||||
// Timeout handling - force miss if note isn't judged by this time
|
||||
private float missDeadlineTime = -1f;
|
||||
|
||||
private static AllyCombatant GetAllyForTrackCached(int trackIndex)
|
||||
{
|
||||
if (trackIndex < 0) return null;
|
||||
if (allyCache == null || allyCache.Length < 10) allyCache = new AllyCombatant[10];
|
||||
if (allyCache[trackIndex] != null) return allyCache[trackIndex];
|
||||
|
||||
var allyGo = GameObject.Find($"ally_0{trackIndex + 1}");
|
||||
if (allyGo != null)
|
||||
{
|
||||
allyCache[trackIndex] = allyGo.GetComponent<AllyCombatant>();
|
||||
}
|
||||
return allyCache[trackIndex];
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
anim = GetComponent<AnimationController>();
|
||||
controller = GetComponent<NoteController>();
|
||||
}
|
||||
|
||||
public void Setup(KeyCode key, int trackIndex, float speed, float hitTime, string color, NoteJudgeConfig judgeConfig, NoteData data)
|
||||
{
|
||||
keyToPress = key;
|
||||
noteColor = color;
|
||||
TrackIndex = trackIndex;
|
||||
Speed = speed;
|
||||
this.hitTime = hitTime;
|
||||
isJudged = false;
|
||||
hasLock = false;
|
||||
this.judgeConfig = judgeConfig;
|
||||
this.noteData = data;
|
||||
|
||||
// Set miss deadline: hitTime + missRange (the latest time to judge before auto-miss)
|
||||
float missRange = (judgeConfig?.missRange ?? 0.5f);
|
||||
this.missDeadlineTime = hitTime + missRange;
|
||||
|
||||
if (controller != null)
|
||||
{
|
||||
controller.SetSpeed(speed);
|
||||
}
|
||||
|
||||
InputManager.OnKeyPressed += HandlePress;
|
||||
|
||||
// 判定配置检查
|
||||
if (judgeConfig == null)
|
||||
{
|
||||
if (JudgeManager.IsDebugEnabled) Debug.LogError($"NoteJudgeConfig is null! 判定配置未赋值!track={trackIndex}");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"NoteJudgeConfig: perfect={judgeConfig.perfectRange}, great={judgeConfig.greatRange}, good={judgeConfig.goodRange}, miss={judgeConfig.missRange}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
InputManager.OnKeyPressed -= HandlePress;
|
||||
// Release lock if we still hold it
|
||||
if (hasLock)
|
||||
{
|
||||
TrackKeyManager.Instance?.UnlockTrackForJudge(TrackIndex, gameObject.GetInstanceID().ToString());
|
||||
hasLock = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// Force miss if we've passed the deadline without being judged
|
||||
if (!isJudged && Time.time > missDeadlineTime && gameObject.activeSelf)
|
||||
{
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[Note] {noteColor} on track {TrackIndex} exceeded miss deadline at time {Time.time:F3}, forcing Miss");
|
||||
JudgeMiss();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandlePress(KeyCode key)
|
||||
{
|
||||
// only proceed for matching key and not already judged
|
||||
if (isJudged || key != keyToPress)
|
||||
return;
|
||||
|
||||
string myId = gameObject.GetInstanceID().ToString();
|
||||
|
||||
// Try to acquire lock for this note (prevents multiple notes on same track being judged at once)
|
||||
if (TrackKeyManager.Instance != null && !TrackKeyManager.Instance.TryLockTrackForJudge(TrackIndex, myId))
|
||||
return;
|
||||
|
||||
hasLock = true;
|
||||
|
||||
float pressTime = Time.time;
|
||||
float maxWindow = (judgeConfig?.missRange ?? 0.5f);
|
||||
|
||||
// If there is a controller and the note is not inside judge zone, only allow judgment
|
||||
// if the press time is within the allowed window. Otherwise ignore the press.
|
||||
if (controller != null && !controller.IsInJudgeZone())
|
||||
{
|
||||
if (Mathf.Abs(pressTime - hitTime) > maxWindow)
|
||||
{
|
||||
// outside allowed window and not in judge zone -> release lock and ignore the press
|
||||
ReleaseTrackLock(myId);
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[Note] {noteColor} press rejected: outside timing window and not in judge zone");
|
||||
return;
|
||||
}
|
||||
// otherwise within window: fall through to normal judging
|
||||
}
|
||||
|
||||
// Prefer the closest candidate in the judge zone for this press
|
||||
if (TrackKeyManager.Instance != null)
|
||||
{
|
||||
if (!TrackKeyManager.Instance.IsBestCandidate(TrackIndex, myId, pressTime, maxWindow))
|
||||
{
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[Note] Press ignored because another note is a better candidate on track {TrackIndex}");
|
||||
ReleaseTrackLock(myId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure only one note per track consumes this physical press per frame,
|
||||
// but only after we've confirmed this note is the best candidate.
|
||||
if (!TrackKeyManager.Instance.TryConsumeTrackForFrame(TrackIndex))
|
||||
{
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[Note] Press ignored due to frame consumption on track {TrackIndex}");
|
||||
ReleaseTrackLock(myId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
float timeDifference = Mathf.Abs(pressTime - hitTime);
|
||||
// raw offset ms: positive = note was early (hitTime > pressTime)
|
||||
float rawOffsetMs = (hitTime - pressTime) * 1000f;
|
||||
string judgeResult = null;
|
||||
|
||||
if (judgeConfig != null)
|
||||
{
|
||||
if (timeDifference <= judgeConfig.perfectRange)
|
||||
{
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"{keyToPress}: Perfect");
|
||||
judgeResult = "Perfect";
|
||||
ScoreManager.Instance.countPerfect += 1;
|
||||
if (TrackIndex >= 0 && TrackIndex < 5) ScoreManager.Instance.trackPerfectCounts[TrackIndex]++;
|
||||
if (noteData != null) noteData.judgeOffsetMs = rawOffsetMs;
|
||||
ScoreManager.Instance.RecordOffset(rawOffsetMs);
|
||||
}
|
||||
else if (timeDifference <= judgeConfig.greatRange)
|
||||
{
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"{keyToPress}: Great");
|
||||
judgeResult = "Great";
|
||||
ScoreManager.Instance.countGreat += 1;
|
||||
if (TrackIndex >= 0 && TrackIndex < 5) ScoreManager.Instance.trackGreatCounts[TrackIndex]++;
|
||||
if (noteData != null) noteData.judgeOffsetMs = rawOffsetMs;
|
||||
ScoreManager.Instance.RecordOffset(rawOffsetMs);
|
||||
}
|
||||
else if (timeDifference <= judgeConfig.goodRange)
|
||||
{
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"{keyToPress}: Good");
|
||||
judgeResult = "Good";
|
||||
ScoreManager.Instance.countGood += 1;
|
||||
if (TrackIndex >= 0 && TrackIndex < 5) ScoreManager.Instance.trackGoodCounts[TrackIndex]++;
|
||||
if (noteData != null) noteData.judgeOffsetMs = rawOffsetMs;
|
||||
ScoreManager.Instance.RecordOffset(rawOffsetMs);
|
||||
}
|
||||
else
|
||||
{
|
||||
JudgeMiss();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// fallback timing
|
||||
if (timeDifference <= 0.08f)
|
||||
{
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"{keyToPress}: Perfect");
|
||||
judgeResult = "Perfect";
|
||||
ScoreManager.Instance.countPerfect += 1;
|
||||
// Add missing offset record for fallback path too if possible
|
||||
ScoreManager.Instance.RecordOffset(0);
|
||||
}
|
||||
else if (timeDifference <= 0.15f)
|
||||
{
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"{keyToPress}: Great");
|
||||
judgeResult = "Great";
|
||||
ScoreManager.Instance.countGreat += 1;
|
||||
ScoreManager.Instance.RecordOffset(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
JudgeMiss();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(judgeResult))
|
||||
{
|
||||
InputManager.Instance?.ShowJudgeResult(TrackIndex, judgeResult);
|
||||
JudgeSoundManager.Instance?.PlayJudgeSound(judgeResult);
|
||||
teamUIController.Instance?.OnJudgeResult(judgeResult);
|
||||
|
||||
// Spawn judgment prefab - ensure noteColor is passed correctly
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[Note] Spawning judge prefab: color={noteColor}, result={judgeResult}");
|
||||
Animation_GenerateJudgementSituationPrefab.Instance?.SpawnJudgePrefab(noteColor, judgeResult);
|
||||
|
||||
try
|
||||
{
|
||||
var ally = GetAllyForTrackCached(TrackIndex);
|
||||
if (ally != null)
|
||||
{
|
||||
int added = ally.AddScoreForJudge(judgeResult);
|
||||
float efficiency = ally.scoreEfficiency;
|
||||
ScoreManager.Instance?.AddPmScoreForTrack(TrackIndex, added, efficiency);
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
if (JudgeManager.IsDebugEnabled) Debug.LogWarning($"[Note] Failed to add per-track score: {ex}");
|
||||
}
|
||||
|
||||
try { SkillBuilder.Instance?.NotifyNoteHit(TrackIndex, judgeResult, SkillDefinition.NoteTypeTrigger.Tap); } catch { }
|
||||
}
|
||||
|
||||
Judge();
|
||||
}
|
||||
|
||||
public bool IsJudged()
|
||||
{
|
||||
return isJudged;
|
||||
}
|
||||
|
||||
public void Judge()
|
||||
{
|
||||
if (isJudged)
|
||||
return;
|
||||
isJudged = true;
|
||||
|
||||
if (controller != null)
|
||||
{
|
||||
controller.StopMovement();
|
||||
controller.PlayHitEffect();
|
||||
}
|
||||
|
||||
// Remove from per-track queue immediately so following notes become head
|
||||
string myId = gameObject.GetInstanceID().ToString();
|
||||
TrackKeyManager.Instance?.UnregisterKey(TrackIndex, myId);
|
||||
|
||||
// Release lock if we held it
|
||||
if (hasLock)
|
||||
{
|
||||
TrackKeyManager.Instance?.UnlockTrackForJudge(TrackIndex, myId);
|
||||
hasLock = false;
|
||||
}
|
||||
|
||||
ReturnToPool();
|
||||
if (anim != null)
|
||||
{
|
||||
anim.PlayDestroyAnimation(noteColor);
|
||||
}
|
||||
|
||||
// notify global judge manager that this short note has been finally judged (hit)
|
||||
JudgeManager.Instance?.NotifyNoteJudged();
|
||||
}
|
||||
|
||||
public void JudgeMiss()
|
||||
{
|
||||
// idempotent: this can be called from multiple paths
|
||||
if (isJudged) return;
|
||||
|
||||
isJudged = true;
|
||||
ScoreManager.Instance.countMiss += 1;
|
||||
if (TrackIndex >= 0 && TrackIndex < 5) ScoreManager.Instance.trackMissCounts[TrackIndex]++;
|
||||
|
||||
// Record a 0 offset for Miss to ensure total Offset Count matches note count
|
||||
ScoreManager.Instance?.RecordOffset(0);
|
||||
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"{keyToPress} Miss");
|
||||
|
||||
InputManager.Instance?.ShowJudgeResult(TrackIndex, "Miss");
|
||||
JudgeSoundManager.Instance?.PlayJudgeSound("Miss");
|
||||
teamUIController.Instance?.OnJudgeResult("Miss");
|
||||
|
||||
// Spawn judgment prefab for Miss - ensure noteColor is passed correctly
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[Note.JudgeMiss] Spawning miss judge prefab: color={noteColor}");
|
||||
Animation_GenerateJudgementSituationPrefab.Instance?.SpawnJudgePrefab(noteColor, "Miss");
|
||||
|
||||
try
|
||||
{
|
||||
var ally = GetAllyForTrackCached(TrackIndex);
|
||||
if (ally != null)
|
||||
{
|
||||
int added = ally.AddScoreForJudge("Miss");
|
||||
float efficiency = ally.scoreEfficiency;
|
||||
ScoreManager.Instance?.AddPmScoreForTrack(TrackIndex, added, efficiency);
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
if (JudgeManager.IsDebugEnabled) Debug.LogWarning($"[Note] Failed to add per-track score for Miss: {ex}");
|
||||
}
|
||||
|
||||
try { SkillBuilder.Instance?.NotifyNoteHit(TrackIndex, "Miss", SkillDefinition.NoteTypeTrigger.Tap); } catch { }
|
||||
|
||||
// notify global judge manager that this note has been finally judged (miss)
|
||||
JudgeManager.Instance?.NotifyNoteJudged();
|
||||
|
||||
ReturnToPool();
|
||||
}
|
||||
|
||||
private void ReleaseTrackLock(string myId)
|
||||
{
|
||||
if (hasLock)
|
||||
{
|
||||
TrackKeyManager.Instance?.UnlockTrackForJudge(TrackIndex, myId);
|
||||
hasLock = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReturnToPool()
|
||||
{
|
||||
InputManager.OnKeyPressed -= HandlePress;
|
||||
|
||||
// CRITICAL: Always release lock and unregister before deactivating
|
||||
string myId = gameObject.GetInstanceID().ToString();
|
||||
|
||||
// Release lock if we still hold it
|
||||
ReleaseTrackLock(myId);
|
||||
|
||||
// Remove from per-track queue to avoid stale entries
|
||||
TrackKeyManager.Instance?.UnregisterKey(TrackIndex, myId);
|
||||
|
||||
if (controller != null)
|
||||
{
|
||||
controller.ResetState();
|
||||
}
|
||||
|
||||
gameObject.SetActive(false);
|
||||
NotePool.Instance.ReturnNote(gameObject, noteColor);
|
||||
}
|
||||
|
||||
public int GetTrackIndex()
|
||||
{
|
||||
return TrackIndex;
|
||||
}
|
||||
|
||||
public KeyCode GetKey()
|
||||
{
|
||||
return keyToPress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// From Controller notify Note whether it's inside judge zone
|
||||
/// </summary>
|
||||
public void SetJudgeZone(bool inZone)
|
||||
{
|
||||
// leaving judge zone
|
||||
if (!inZone)
|
||||
{
|
||||
// Only process if we haven't been judged yet
|
||||
if (!isJudged)
|
||||
{
|
||||
// IMPORTANT: Don't call JudgeMiss directly here because SetJudgeZone is called
|
||||
// from OnTriggerExit2D, which is in a physics callback.
|
||||
// Calling ReturnToPool (which deactivates the object) inside a physics callback
|
||||
// causes "GameObject is already being activated or deactivated" errors.
|
||||
// Instead, mark for miss and let Update handle it next frame.
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[Note.SetJudgeZone] Note {noteColor} left judge zone on track {TrackIndex}, will force Miss next frame");
|
||||
missDeadlineTime = Time.time; // Force deadline to now so Update will handle it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public class NoteController : MonoBehaviour
|
||||
activationTime = hitTime - travelTime;
|
||||
baseSpawnYOffset = initialYOffset;
|
||||
|
||||
if (GameConfig.verboseLogs)
|
||||
if (JudgeManager.IsDebugEnabled)
|
||||
Debug.Log($"[NoteController] Configured: spawnPos={spawnPosition}, activationTime={activationTime:F3}, initialYOffset={initialYOffset:F4}");
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ public class NoteController : MonoBehaviour
|
||||
isInJudgeZone = true;
|
||||
// register note by instance id so TrackKeyManager can prioritize
|
||||
// tap notes are short notes without hold
|
||||
TrackKeyManager.Instance.RegisterKey(linkedNote.GetTrackIndex(), linkedNote.gameObject.GetInstanceID().ToString(), "tap");
|
||||
TrackKeyManager.Instance.RegisterKey(linkedNote.GetTrackIndex(), linkedNote.gameObject.GetInstanceID().ToString(), "tap", linkedNote.HitTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ public class NotePool : MonoBehaviour
|
||||
}
|
||||
|
||||
// Also ensure particle and judge prefab warm-up if AnimationController exists in scene.
|
||||
var anim = AnimationController.Global ?? FindObjectOfType<AnimationController>();
|
||||
var anim = AnimationController.Global ?? FindAnyObjectByType<AnimationController>();
|
||||
if (anim != null)
|
||||
{
|
||||
anim.PrewarmParticles(2);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,11 @@ public class TrackKeyManager : MonoBehaviour
|
||||
private Dictionary<int, Queue<(string noteId, string noteType)>> trackKeyMappings =
|
||||
new Dictionary<int, Queue<(string, string)>>();
|
||||
|
||||
// Active notes currently inside judge zone per track
|
||||
private Dictionary<int, List<string>> trackActiveNotes = new Dictionary<int, List<string>>();
|
||||
// Hit times for notes (used to pick best candidate when multiple notes overlap)
|
||||
private readonly Dictionary<string, float> noteHitTimes = new Dictionary<string, float>();
|
||||
|
||||
// Track notes currently being judged to prevent double-judging the same note
|
||||
// Format: trackIndex -> HashSet of note IDs being judged
|
||||
private Dictionary<int, HashSet<string>> trackNotesBeingJudged = new Dictionary<int, HashSet<string>>();
|
||||
@@ -38,6 +43,12 @@ public class TrackKeyManager : MonoBehaviour
|
||||
public int pruneBatchSize = 5;
|
||||
|
||||
private Coroutine cleanupCoroutine;
|
||||
private readonly WaitForSecondsRealtime cleanupInterval = new WaitForSecondsRealtime(1f);
|
||||
private readonly List<int> trackKeysBuffer = new List<int>(16);
|
||||
private readonly List<int> trackLockKeysBuffer = new List<int>(16);
|
||||
private readonly List<string> noteIdsToRemoveBuffer = new List<string>(32);
|
||||
private readonly HashSet<int> allTracksBuffer = new HashSet<int>();
|
||||
private readonly HashSet<string> allQueuedNoteIdsBuffer = new HashSet<string>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
@@ -80,25 +91,40 @@ public class TrackKeyManager : MonoBehaviour
|
||||
while (true)
|
||||
{
|
||||
// run once per second (unscaled so it runs during pause)
|
||||
yield return new WaitForSecondsRealtime(1f);
|
||||
yield return cleanupInterval;
|
||||
|
||||
float now = Time.unscaledTime;
|
||||
|
||||
// 1) Prune head entries for all tracks (defensive)
|
||||
var trackKeys = new List<int>(trackKeyMappings.Keys);
|
||||
foreach (var ti in trackKeys)
|
||||
trackKeysBuffer.Clear();
|
||||
foreach (var ti in trackKeyMappings.Keys) trackKeysBuffer.Add(ti);
|
||||
for (int i = 0; i < trackKeysBuffer.Count; i++)
|
||||
{
|
||||
PruneStaleHeads(ti);
|
||||
PruneStaleHeads(trackKeysBuffer[i]);
|
||||
}
|
||||
|
||||
allQueuedNoteIdsBuffer.Clear();
|
||||
foreach (var qpair in trackKeyMappings)
|
||||
{
|
||||
var q = qpair.Value;
|
||||
if (q == null || q.Count == 0) continue;
|
||||
foreach (var item in q)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(item.noteId))
|
||||
allQueuedNoteIdsBuffer.Add(item.noteId);
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Inspect locks and release any that reference ids that are expired or not present in any queue
|
||||
var trackLockKeys = new List<int>(trackNotesBeingJudged.Keys);
|
||||
foreach (var t in trackLockKeys)
|
||||
trackLockKeysBuffer.Clear();
|
||||
foreach (var t in trackNotesBeingJudged.Keys) trackLockKeysBuffer.Add(t);
|
||||
for (int i = 0; i < trackLockKeysBuffer.Count; i++)
|
||||
{
|
||||
int t = trackLockKeysBuffer[i];
|
||||
var locks = trackNotesBeingJudged[t];
|
||||
if (locks == null || locks.Count == 0) continue;
|
||||
|
||||
var toRemove = new List<string>();
|
||||
noteIdsToRemoveBuffer.Clear();
|
||||
|
||||
foreach (var noteId in locks)
|
||||
{
|
||||
@@ -108,35 +134,23 @@ public class TrackKeyManager : MonoBehaviour
|
||||
// If expiry missing or expired, consider the id stale and remove lock
|
||||
if (!hasExpiry || expired)
|
||||
{
|
||||
toRemove.Add(noteId);
|
||||
noteIdsToRemoveBuffer.Add(noteId);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Also if the queues do not contain this id anywhere, it is likely stale
|
||||
bool foundInQueues = false;
|
||||
foreach (var qpair in trackKeyMappings)
|
||||
if (!allQueuedNoteIdsBuffer.Contains(noteId))
|
||||
{
|
||||
foreach (var item in qpair.Value)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(item.noteId) && item.noteId == noteId)
|
||||
{
|
||||
foundInQueues = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (foundInQueues) break;
|
||||
}
|
||||
if (!foundInQueues)
|
||||
{
|
||||
toRemove.Add(noteId);
|
||||
noteIdsToRemoveBuffer.Add(noteId);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var id in toRemove)
|
||||
for (int r = 0; r < noteIdsToRemoveBuffer.Count; r++)
|
||||
{
|
||||
var id = noteIdsToRemoveBuffer[r];
|
||||
locks.Remove(id);
|
||||
noteIdExpiry.Remove(id);
|
||||
if (GameConfig.verboseLogs) Debug.LogWarning($"[TrackKeyManager] Cleanup: removed stale lock id={id} on track {t}");
|
||||
if (JudgeManager.IsDebugEnabled) Debug.LogWarning($"[TrackKeyManager] Cleanup: removed stale lock id={id} on track {t}");
|
||||
}
|
||||
|
||||
// If locks become empty, remove dictionary entry to keep structure clean
|
||||
@@ -147,12 +161,12 @@ public class TrackKeyManager : MonoBehaviour
|
||||
}
|
||||
|
||||
// 3) Defensive: if a track has no queued ids but has consumed-frame or locks, clear them
|
||||
var allTracks = new HashSet<int>();
|
||||
foreach (var k in trackKeyMappings.Keys) allTracks.Add(k);
|
||||
foreach (var k in trackNotesBeingJudged.Keys) allTracks.Add(k);
|
||||
foreach (var k in trackConsumedFrame.Keys) allTracks.Add(k);
|
||||
allTracksBuffer.Clear();
|
||||
foreach (var k in trackKeyMappings.Keys) allTracksBuffer.Add(k);
|
||||
foreach (var k in trackNotesBeingJudged.Keys) allTracksBuffer.Add(k);
|
||||
foreach (var k in trackConsumedFrame.Keys) allTracksBuffer.Add(k);
|
||||
|
||||
foreach (var track in allTracks)
|
||||
foreach (var track in allTracksBuffer)
|
||||
{
|
||||
bool hasQueue = trackKeyMappings.ContainsKey(track) && trackKeyMappings[track].Count > 0;
|
||||
bool hasLocks = trackNotesBeingJudged.ContainsKey(track) && trackNotesBeingJudged[track].Count > 0;
|
||||
@@ -160,7 +174,7 @@ public class TrackKeyManager : MonoBehaviour
|
||||
{
|
||||
// clear locks for this track
|
||||
trackNotesBeingJudged.Remove(track);
|
||||
if (GameConfig.verboseLogs) Debug.LogWarning($"[TrackKeyManager] Cleanup: cleared locks for empty track {track}");
|
||||
if (JudgeManager.IsDebugEnabled) Debug.LogWarning($"[TrackKeyManager] Cleanup: cleared locks for empty track {track}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,14 +187,18 @@ public class TrackKeyManager : MonoBehaviour
|
||||
bool isFocused = Application.isFocused;
|
||||
|
||||
// If we've moved to a new frame or focus state changed, clear consumption tracking
|
||||
if (lastCheckedFrame != currentFrame || (isFocused && !lastFocusedState))
|
||||
if (lastCheckedFrame != currentFrame)
|
||||
{
|
||||
// Reset per-frame tracking for the new frame
|
||||
// In high-performance scenarios, we might avoid Clear() if the dict is small,
|
||||
// but for safety with multiple keys, we keep it.
|
||||
}
|
||||
|
||||
if (isFocused && !lastFocusedState)
|
||||
{
|
||||
// On focus regain, clear the consumed frame dictionary to allow input again
|
||||
if (isFocused && !lastFocusedState)
|
||||
{
|
||||
trackConsumedFrame.Clear();
|
||||
if (GameConfig.verboseLogs) Debug.Log("[TrackKeyManager] Focus regained, cleared track consumption");
|
||||
}
|
||||
trackConsumedFrame.Clear();
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log("[TrackKeyManager] Focus regained, cleared track consumption");
|
||||
}
|
||||
|
||||
lastCheckedFrame = currentFrame;
|
||||
@@ -214,8 +232,11 @@ public class TrackKeyManager : MonoBehaviour
|
||||
{
|
||||
q.Dequeue();
|
||||
noteIdExpiry.Remove(head.noteId);
|
||||
noteHitTimes.Remove(head.noteId);
|
||||
if (trackActiveNotes.ContainsKey(trackIndex))
|
||||
trackActiveNotes[trackIndex].Remove(head.noteId);
|
||||
pruned++;
|
||||
if (GameConfig.verboseLogs)
|
||||
if (JudgeManager.IsDebugEnabled)
|
||||
Debug.LogWarning($"[TrackKeyManager] Pruned stale head on track {trackIndex}: id={head.noteId} type={head.noteType} now={now:F2} expiry={expiry:F2}");
|
||||
continue;
|
||||
}
|
||||
@@ -226,16 +247,24 @@ public class TrackKeyManager : MonoBehaviour
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当音符进入判定区域时,添加音符 id 到队列(包含note类型信息)
|
||||
/// noteType: "tap" 或 "hold"
|
||||
/// �����������ж�����ʱ���������� id �����У�����note������Ϣ��
|
||||
/// noteType: "tap" �� "hold"
|
||||
/// </summary>
|
||||
public void RegisterKey(int trackIndex, string noteInstanceId, string noteType = "tap")
|
||||
public void RegisterKey(int trackIndex, string noteInstanceId, string noteType = "tap", float hitTime = float.NaN)
|
||||
{
|
||||
if (!trackKeyMappings.ContainsKey(trackIndex))
|
||||
trackKeyMappings[trackIndex] = new Queue<(string, string)>();
|
||||
|
||||
trackKeyMappings[trackIndex].Enqueue((noteInstanceId, noteType));
|
||||
|
||||
if (!trackActiveNotes.ContainsKey(trackIndex))
|
||||
trackActiveNotes[trackIndex] = new List<string>();
|
||||
if (!string.IsNullOrEmpty(noteInstanceId) && !trackActiveNotes[trackIndex].Contains(noteInstanceId))
|
||||
trackActiveNotes[trackIndex].Add(noteInstanceId);
|
||||
|
||||
if (!string.IsNullOrEmpty(noteInstanceId) && !float.IsNaN(hitTime))
|
||||
noteHitTimes[noteInstanceId] = hitTime;
|
||||
|
||||
// record/refresh TTL so the id can't block forever if exit/unregister is missed
|
||||
if (!string.IsNullOrEmpty(noteInstanceId))
|
||||
{
|
||||
@@ -244,14 +273,20 @@ public class TrackKeyManager : MonoBehaviour
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当音符离开判定区域时,移除音符 id
|
||||
/// �������뿪�ж�����ʱ���Ƴ����� id
|
||||
/// </summary>
|
||||
public void UnregisterKey(int trackIndex, string noteInstanceId)
|
||||
{
|
||||
if (!trackKeyMappings.ContainsKey(trackIndex) || trackKeyMappings[trackIndex].Count == 0)
|
||||
{
|
||||
// still clear TTL record
|
||||
if (!string.IsNullOrEmpty(noteInstanceId)) noteIdExpiry.Remove(noteInstanceId);
|
||||
if (!string.IsNullOrEmpty(noteInstanceId))
|
||||
{
|
||||
noteIdExpiry.Remove(noteInstanceId);
|
||||
noteHitTimes.Remove(noteInstanceId);
|
||||
}
|
||||
if (trackActiveNotes.ContainsKey(trackIndex))
|
||||
trackActiveNotes[trackIndex].Remove(noteInstanceId);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -259,7 +294,13 @@ public class TrackKeyManager : MonoBehaviour
|
||||
if (trackKeyMappings[trackIndex].Count > 0 && trackKeyMappings[trackIndex].Peek().noteId == noteInstanceId)
|
||||
{
|
||||
trackKeyMappings[trackIndex].Dequeue();
|
||||
if (!string.IsNullOrEmpty(noteInstanceId)) noteIdExpiry.Remove(noteInstanceId);
|
||||
if (!string.IsNullOrEmpty(noteInstanceId))
|
||||
{
|
||||
noteIdExpiry.Remove(noteInstanceId);
|
||||
noteHitTimes.Remove(noteInstanceId);
|
||||
}
|
||||
if (trackActiveNotes.ContainsKey(trackIndex))
|
||||
trackActiveNotes[trackIndex].Remove(noteInstanceId);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -276,11 +317,17 @@ public class TrackKeyManager : MonoBehaviour
|
||||
}
|
||||
trackKeyMappings[trackIndex] = temp;
|
||||
|
||||
if (!string.IsNullOrEmpty(noteInstanceId)) noteIdExpiry.Remove(noteInstanceId);
|
||||
if (!string.IsNullOrEmpty(noteInstanceId))
|
||||
{
|
||||
noteIdExpiry.Remove(noteInstanceId);
|
||||
noteHitTimes.Remove(noteInstanceId);
|
||||
}
|
||||
if (trackActiveNotes.ContainsKey(trackIndex))
|
||||
trackActiveNotes[trackIndex].Remove(noteInstanceId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取轨道当前排在队首的音符实例 id(队列头)
|
||||
/// ��ȡ�����ǰ���ڶ�������ʵ�� id������ͷ��
|
||||
/// </summary>
|
||||
public string GetCurrentNoteId(int trackIndex)
|
||||
{
|
||||
@@ -290,11 +337,11 @@ public class TrackKeyManager : MonoBehaviour
|
||||
{
|
||||
return trackKeyMappings[trackIndex].Peek().noteId;
|
||||
}
|
||||
return null; // 无音符
|
||||
return null; // ������
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前队头note的类型("tap" 或 "hold")
|
||||
/// ��ȡ��ǰ��ͷnote�����ͣ�"tap" �� "hold"��
|
||||
/// </summary>
|
||||
public string GetCurrentNoteType(int trackIndex)
|
||||
{
|
||||
@@ -385,6 +432,44 @@ public class TrackKeyManager : MonoBehaviour
|
||||
{
|
||||
trackConsumedFrame.Clear();
|
||||
}
|
||||
public bool IsBestCandidate(int trackIndex, string noteId, float pressTime, float maxWindow)
|
||||
{
|
||||
if (trackIndex < 0 || string.IsNullOrEmpty(noteId)) return true;
|
||||
if (!trackActiveNotes.TryGetValue(trackIndex, out var list) || list == null || list.Count == 0)
|
||||
return true;
|
||||
|
||||
string bestId = null;
|
||||
float bestDelta = float.MaxValue;
|
||||
float bestHit = float.MaxValue;
|
||||
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
var id = list[i];
|
||||
if (string.IsNullOrEmpty(id)) continue;
|
||||
if (!noteHitTimes.TryGetValue(id, out var ht)) continue;
|
||||
float delta = Mathf.Abs(pressTime - ht);
|
||||
if (delta > maxWindow) continue;
|
||||
|
||||
if (delta < bestDelta - 0.0001f)
|
||||
{
|
||||
bestDelta = delta;
|
||||
bestId = id;
|
||||
bestHit = ht;
|
||||
}
|
||||
else if (Mathf.Abs(delta - bestDelta) <= 0.0001f)
|
||||
{
|
||||
// tie-breaker: earlier hit time wins
|
||||
if (ht < bestHit)
|
||||
{
|
||||
bestId = id;
|
||||
bestHit = ht;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestId == null) return true;
|
||||
return bestId == noteId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force clear all track locks and per-frame consumption state.
|
||||
@@ -395,6 +480,16 @@ public class TrackKeyManager : MonoBehaviour
|
||||
trackNotesBeingJudged.Clear();
|
||||
trackConsumedFrame.Clear();
|
||||
noteIdExpiry.Clear();
|
||||
if (GameConfig.verboseLogs) Debug.Log("[TrackKeyManager] ClearAllLocks called: cleared locks and consumption state");
|
||||
noteHitTimes.Clear();
|
||||
trackActiveNotes.Clear();
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log("[TrackKeyManager] ClearAllLocks called: cleared locks and consumption state");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Serialization;
|
||||
@@ -9,22 +9,22 @@ using UnityEditor;
|
||||
|
||||
public class settlementController : MonoBehaviour
|
||||
{
|
||||
[Header("�������֮��")]
|
||||
[Header("管理器与基础引用")]
|
||||
[SerializeField] private BeatmapManager bmm;
|
||||
[SerializeField] private ScoreManager sm;
|
||||
public GameManager gm;
|
||||
private SongData thisSong_so;
|
||||
private int maxScore_sum = 2000000;
|
||||
[SerializeField] private Image thisSong_backPic;
|
||||
[Header("��ת֮��ť")]
|
||||
[Header("跳转控制按钮")]
|
||||
public Button exit_toSelectSongs;
|
||||
public Button replay_thisGame;
|
||||
public Button display_rankList;
|
||||
public Button share_toSocialMedia;
|
||||
|
||||
[Header("�ı��ͽ�����")]
|
||||
[Header("文本与进度显示")]
|
||||
public Text songName_Text;
|
||||
[Tooltip("��ǰ�ؿ��Ľ��Ȱٷֱ�")]
|
||||
[Tooltip("当前关卡的进度百分比")]
|
||||
public Text thisLevel_currentPercentage_Text;
|
||||
public Text finalScore_Text;
|
||||
public Text pmScoreSum_Text;
|
||||
@@ -33,26 +33,26 @@ public class settlementController : MonoBehaviour
|
||||
|
||||
public Image thisLevel_progressBar_Image;
|
||||
|
||||
[Header("ȷ���㷨")]
|
||||
[Header("准确度权重算法")]
|
||||
public float perfect_weight = 1f;
|
||||
public float great_weight = 0.6666667f;
|
||||
public float good_weight = 0.333333f;
|
||||
public float miss_weight = 0;
|
||||
|
||||
[Header("����������Ϣ")]
|
||||
[Header("结算奖励信息")]
|
||||
public Text reward_playerEXP_Text;
|
||||
public Text reward_money_Text;
|
||||
public Text reward_idolEXP_bottle_Text;
|
||||
|
||||
[Header("�Ŷӽ���")]
|
||||
[Header("队伍展示")]
|
||||
public loadSettlementTeamPrefab settlementTeamLoader;
|
||||
public Image mvp_hero_hd_image;
|
||||
|
||||
[Header("������Ǯ")]
|
||||
[Header("结算获得金钱")]
|
||||
[SerializeField] private long moneyToGive_thisLevel;
|
||||
|
||||
// �õ�����ͳ��
|
||||
[Header("�������ͳ��")]
|
||||
// 得到评分统计
|
||||
[Header("音符判定统计")]
|
||||
public Text perfectHitCount_Text;
|
||||
public Text perfectHitPercent_Text;
|
||||
public Image perfect_barFill_Image;
|
||||
@@ -76,27 +76,27 @@ public class settlementController : MonoBehaviour
|
||||
public Text lateHitCount_Text;
|
||||
public Text avgOffset_Text;
|
||||
|
||||
[Header("��������ϵͳ")]
|
||||
[Tooltip("�������ֲ��������½���")]
|
||||
[Header("结算音频控制")]
|
||||
[Tooltip("结算界面背景音乐,播放结算流程")]
|
||||
public AudioSource settlementAudioSource;
|
||||
|
||||
[Tooltip("��Ϸ���ֲ�������ԭ��Ϸ���֣�")]
|
||||
[Tooltip("游戏背景音乐(通常是原游戏曲目)")]
|
||||
public AudioSource oriGameMusicSource;
|
||||
|
||||
[Tooltip("��Ƶ�����������ڵ�ͨ�˲���������")]
|
||||
[Tooltip("音频混音器,用于低通滤波等效果")]
|
||||
public AudioMixer gameMusicMixer;
|
||||
|
||||
[Tooltip("��Ƶ�������е�ͨ�˲�����������")]
|
||||
[Tooltip("音频混音器中的低通滤波参数名")]
|
||||
public string lowpassParamName = "inGameMusic_lowpass";
|
||||
|
||||
[Tooltip("��ͨ�˲�����������ʱ�䣨�룩")]
|
||||
[Tooltip("低通滤波器渐变持续时间(秒)")]
|
||||
public float lowpassFadeDuration = 2f;
|
||||
|
||||
[Tooltip("������� CanvasGroup�����ڵ���Ч����")]
|
||||
[Tooltip("结算界面 CanvasGroup,用于淡入效果")]
|
||||
public CanvasGroup settlementCanvasGroup;
|
||||
|
||||
[Header("����������")]
|
||||
[Tooltip("������")]
|
||||
[Header("结算控制组件")]
|
||||
[Tooltip("结算控制管理器")]
|
||||
public GameObject cdm;
|
||||
|
||||
private Coroutine musicTransitionCoroutine;
|
||||
@@ -104,7 +104,7 @@ public class settlementController : MonoBehaviour
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// Setup button listeners
|
||||
// 设置按钮监听
|
||||
if (replay_thisGame != null)
|
||||
{
|
||||
replay_thisGame.onClick.AddListener(OnReplayButtonClicked);
|
||||
@@ -115,11 +115,11 @@ public class settlementController : MonoBehaviour
|
||||
exit_toSelectSongs.onClick.AddListener(OnExitButtonClicked);
|
||||
}
|
||||
|
||||
// ���� CDM �����ڿ�ʼʱ���ֽ���״̬��
|
||||
// 确保 CDM 对象在开始时处于关闭状态
|
||||
if (cdm != null)
|
||||
{
|
||||
cdm.SetActive(false);
|
||||
if (GameConfig.verboseLogs) Debug.Log("[SettlementController] CDM object disabled at startup");
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log("[SettlementController] CDM object disabled at startup");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -149,14 +149,14 @@ public class settlementController : MonoBehaviour
|
||||
exit_toSelectSongs.onClick.RemoveListener(OnExitButtonClicked);
|
||||
}
|
||||
|
||||
// ֹͣ���ֹ���Э��
|
||||
// 停止音乐过渡协程
|
||||
if (musicTransitionCoroutine != null)
|
||||
{
|
||||
StopCoroutine(musicTransitionCoroutine);
|
||||
musicTransitionCoroutine = null;
|
||||
}
|
||||
|
||||
// ֹͣ CanvasGroup ����Э��
|
||||
// 停止 CanvasGroup 渐变协程
|
||||
if (canvasFadeCoroutine != null)
|
||||
{
|
||||
StopCoroutine(canvasFadeCoroutine);
|
||||
@@ -175,10 +175,10 @@ public class settlementController : MonoBehaviour
|
||||
|
||||
public void startSettlement_uiUpdate()
|
||||
{
|
||||
// --- �������ڽ������̿�ʼʱ���� CanvasGroup Ϊ�� ---
|
||||
// --- 确保结算界面开始时 CanvasGroup 为透明 ---
|
||||
InitializeSettlementCanvas();
|
||||
|
||||
// --- ���������ý��������� ---
|
||||
// --- 启用结算控制管理器 ---
|
||||
if (cdm != null)
|
||||
{
|
||||
cdm.SetActive(true);
|
||||
@@ -189,9 +189,9 @@ public class settlementController : MonoBehaviour
|
||||
|
||||
// --- Statistics: Record total play time at settlement ---
|
||||
if (gm != null) gm.RecordTotalPlayTime();
|
||||
else { var activeGM = FindObjectOfType<GameManager>(); if (activeGM != null) activeGM.RecordTotalPlayTime(); }
|
||||
else { var activeGM = FindAnyObjectByType<GameManager>(); if (activeGM != null) activeGM.RecordTotalPlayTime(); }
|
||||
|
||||
// ���������֣���UI����֮ǰ��
|
||||
// 准备结算音乐(在UI更新之前)
|
||||
PrepareSettlementMusic();
|
||||
|
||||
if(sm != null)
|
||||
@@ -277,6 +277,7 @@ public class settlementController : MonoBehaviour
|
||||
InGamePerformanceManager.Instance.UpdateTotalCure(totalCure);
|
||||
InGamePerformanceManager.Instance.UpdateTotalDamage(totalDamage);
|
||||
InGamePerformanceManager.Instance.UpdateTotalManaRestored(totalMana);
|
||||
if (sm != null) InGamePerformanceManager.Instance.UpdateTotalScore(sm.allSum_pmScore + sm.allSum_idolScore);
|
||||
|
||||
// Load and display achievement prefabs
|
||||
InGamePerformanceManager.Instance.LoadArchievePrefab();
|
||||
@@ -326,7 +327,7 @@ public class settlementController : MonoBehaviour
|
||||
if (settlementTeamLoader == null)
|
||||
{
|
||||
// try to auto-locate loader in scene if not assigned in inspector
|
||||
settlementTeamLoader = FindObjectOfType<loadSettlementTeamPrefab>();
|
||||
settlementTeamLoader = FindAnyObjectByType<loadSettlementTeamPrefab>();
|
||||
}
|
||||
|
||||
if (settlementTeamLoader != null)
|
||||
@@ -334,22 +335,22 @@ public class settlementController : MonoBehaviour
|
||||
// ensure loader has key references
|
||||
if (settlementTeamLoader.sm == null) settlementTeamLoader.sm = sm ?? ScoreManager.Instance;
|
||||
if (settlementTeamLoader.tuic == null) settlementTeamLoader.tuic = teamUIController.Instance;
|
||||
if (settlementTeamLoader.gm == null) settlementTeamLoader.gm = gm ?? FindObjectOfType<GameManager>();
|
||||
if (settlementTeamLoader.gm == null) settlementTeamLoader.gm = gm ?? FindAnyObjectByType<GameManager>();
|
||||
try { settlementTeamLoader.PopulateSettlementCards(); }
|
||||
catch (System.Exception ex) { Debug.LogWarning("Failed to PopulateSettlementCards: " + ex); }
|
||||
}
|
||||
|
||||
// ��ʼ���ֹ��ɣ��ڽ���UI��ʾ��
|
||||
// ��ѡ�� MVP ������ͼƬ
|
||||
// 初始化结算完成,在界面UI显示后
|
||||
// 尝试选择 MVP 英雄的大图
|
||||
SetMvpHeroImageFromTopScorer();
|
||||
StartMusicTransition();
|
||||
|
||||
// --- �ģ�������ĩβ��ʼ CanvasGroup ���� ---
|
||||
// --- 修改:在所有逻辑末尾开始 CanvasGroup 渐变 ---
|
||||
StartCanvasFadeIn();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ��ʼ��������� CanvasGroup Ϊ��״̬
|
||||
/// 初始化结算界面的 CanvasGroup 为透明状态
|
||||
/// </summary>
|
||||
private void InitializeSettlementCanvas()
|
||||
{
|
||||
@@ -366,12 +367,15 @@ public class settlementController : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
// 缓存 AllyHero_SO 数组,避免在结算界面多次调用昂贵的 Resources.LoadAll
|
||||
private static AllyHero_SO[] _cachedAllyHeroSOs;
|
||||
|
||||
/// <summary>
|
||||
/// ������dlcData�з����Ұ�����ǰ������DLC��������������
|
||||
/// 从所有dlcData中查找并准备当前歌曲所属DLC的结算音乐
|
||||
/// </summary>
|
||||
private void PrepareSettlementMusic()
|
||||
{
|
||||
// --- �ģ��Ƴ� CanvasGroup ���ã������ڽ�����ĩβ�ŵ��� ---
|
||||
// --- 修改:移除 CanvasGroup 相关设置,已移动到结算逻辑末尾调用 ---
|
||||
|
||||
if (thisSong_so == null)
|
||||
{
|
||||
@@ -385,13 +389,20 @@ public class settlementController : MonoBehaviour
|
||||
return;
|
||||
}
|
||||
|
||||
// �������� dlcData ScriptableObjects
|
||||
// 使用异步或分帧逻辑来查找 DLC
|
||||
StartCoroutine(PrepareSettlementMusicRoutine());
|
||||
}
|
||||
|
||||
private IEnumerator PrepareSettlementMusicRoutine()
|
||||
{
|
||||
// 加载 dlcData ScriptableObjects
|
||||
dlcData[] allDlcs = Resources.LoadAll<dlcData>("");
|
||||
dlcData foundDlc = null;
|
||||
|
||||
// ��������DLC�����Ұ�����ǰ������DLC
|
||||
foreach (var dlc in allDlcs)
|
||||
// 遍历所有DLC数据以找到包含当前歌曲的DLC
|
||||
for (int i = 0; i < allDlcs.Length; i++)
|
||||
{
|
||||
var dlc = allDlcs[i];
|
||||
if (dlc == null || dlc.songList == null) continue;
|
||||
|
||||
if (dlc.songList.Contains(thisSong_so))
|
||||
@@ -399,37 +410,37 @@ public class settlementController : MonoBehaviour
|
||||
foundDlc = dlc;
|
||||
break;
|
||||
}
|
||||
// 每处理 20 个 DLC 等待一帧
|
||||
if (i > 0 && i % 20 == 0) yield return null;
|
||||
}
|
||||
|
||||
if (foundDlc == null)
|
||||
{
|
||||
Debug.LogWarning($"[SettlementController] No DLC found containing song '{thisSong_so.songName}'");
|
||||
return;
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (foundDlc.settlementMusic == null)
|
||||
{
|
||||
Debug.LogWarning($"[SettlementController] DLC '{foundDlc.dlcName}' does not have settlement music assigned");
|
||||
return;
|
||||
yield break;
|
||||
}
|
||||
|
||||
// ���ý������ֵ�AudioSource
|
||||
// 设置结算音乐的AudioSource
|
||||
settlementAudioSource.clip = foundDlc.settlementMusic;
|
||||
settlementAudioSource.loop = true;
|
||||
settlementAudioSource.playOnAwake = false;
|
||||
|
||||
// ��������ͣ���ȴ�����
|
||||
// 初始音量并静音等待播放
|
||||
settlementAudioSource.volume = 1f;
|
||||
settlementAudioSource.mute = true;
|
||||
settlementAudioSource.Stop();
|
||||
|
||||
Debug.Log($"[SettlementController] Prepared settlement music from DLC '{foundDlc.dlcName}': {foundDlc.settlementMusic.name}");
|
||||
|
||||
// --- �ģ��Ƴ� CanvasGroup ������ã������ڽ�����ĩβ�ŵ��� ---
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ��ʼ���ֹ��ɣ�������Ϸ���֣�ͨ����ͨ�˲�������Ȼ�Ž�������
|
||||
/// 开始音乐过渡,将原游戏背景音乐淡出,然后播放结算音乐
|
||||
/// </summary>
|
||||
private void StartMusicTransition()
|
||||
{
|
||||
@@ -441,9 +452,9 @@ public class settlementController : MonoBehaviour
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ���ֹ���Э�̣�
|
||||
/// 1. ����Ϸ���ֵĵ�ͨ�˲�����22000Hz����0Hz��ͬʱ��������0
|
||||
/// 2. ������ֹͣԭ���ֲ���ʼ���Ž�������
|
||||
/// 音乐过渡协程:
|
||||
/// 1. 将游戏音乐的低通滤波器从 22000Hz 逐渐降至 0Hz,同时音量降至 0
|
||||
/// 2. 渐变完成后停止原音轨并开始播放结算音乐
|
||||
/// </summary>
|
||||
private IEnumerator MusicTransitionRoutine()
|
||||
{
|
||||
@@ -451,10 +462,10 @@ public class settlementController : MonoBehaviour
|
||||
float startLowpass = 22000f;
|
||||
float endLowpass = 0f;
|
||||
|
||||
// --- ��������¼��ʼ���� ---
|
||||
// --- 获取当前音量作为起始点 ---
|
||||
float startVolume = (oriGameMusicSource != null) ? oriGameMusicSource.volume : 1f;
|
||||
|
||||
// ���ԭ��Ϸ����Դ�ͻ����������ڣ�����е�ͨ�˲�����������������
|
||||
// 如果原游戏音轨和混音器参数存在,执行低通滤波和音量淡出
|
||||
if (oriGameMusicSource != null && gameMusicMixer != null && !string.IsNullOrEmpty(lowpassParamName))
|
||||
{
|
||||
Debug.Log($"[SettlementController] Starting lowpass and volume fade over {lowpassFadeDuration}s");
|
||||
@@ -464,23 +475,23 @@ public class settlementController : MonoBehaviour
|
||||
elapsed += Time.unscaledDeltaTime;
|
||||
float t = Mathf.Clamp01(elapsed / Mathf.Max(0.0001f, lowpassFadeDuration));
|
||||
|
||||
// 1. ���õ�ͨ�˲���
|
||||
// 1. 设置低通滤波器
|
||||
float currentLowpass = Mathf.Lerp(startLowpass, endLowpass, t);
|
||||
try { gameMusicMixer.SetFloat(lowpassParamName, currentLowpass); }
|
||||
catch (System.Exception ex) { Debug.LogWarning($"Mixer error: {ex.Message}"); }
|
||||
|
||||
// 2. --- �ģ�ƽ���������� ---
|
||||
// 2. --- 修改:平滑降低音量 ---
|
||||
oriGameMusicSource.volume = Mathf.Lerp(startVolume, 0f, t);
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// ȷ������״̬
|
||||
// 确保最终状态
|
||||
try { gameMusicMixer.SetFloat(lowpassParamName, endLowpass); } catch { }
|
||||
|
||||
// --- �ģ��������㲢��ͣ/ֹͣ ---
|
||||
// --- 修改:音量归零并暂停/停止 ---
|
||||
oriGameMusicSource.volume = 0f;
|
||||
oriGameMusicSource.Pause(); // ����ʹ�� .Stop()
|
||||
oriGameMusicSource.Pause(); // 也可以使用 .Stop()
|
||||
|
||||
Debug.Log("[SettlementController] Lowpass and volume fade complete, game music paused.");
|
||||
}
|
||||
@@ -489,7 +500,7 @@ public class settlementController : MonoBehaviour
|
||||
Debug.LogWarning("[SettlementController] oriGameMusicSource or gameMusicMixer not assigned, skipping fade");
|
||||
}
|
||||
|
||||
// ������ɺ�ʼ���Ž�������
|
||||
// 过渡完成后,开始播放结算音乐
|
||||
if (settlementAudioSource != null && settlementAudioSource.clip != null)
|
||||
{
|
||||
try
|
||||
@@ -515,7 +526,7 @@ public class settlementController : MonoBehaviour
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ��ʼ CanvasGroup ����Ч��
|
||||
/// 开始 CanvasGroup 渐入效果
|
||||
/// </summary>
|
||||
private void StartCanvasFadeIn()
|
||||
{
|
||||
@@ -527,7 +538,7 @@ public class settlementController : MonoBehaviour
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CanvasGroup ����Э�̣�0.25���ڴ� alpha=0 ���뵽 alpha=1
|
||||
/// CanvasGroup 渐入协程:0.25秒内从 alpha=0 渐变到 alpha=1
|
||||
/// </summary>
|
||||
private IEnumerator CanvasFadeInRoutine()
|
||||
{
|
||||
@@ -563,13 +574,13 @@ public class settlementController : MonoBehaviour
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ����������ǰ��Ϸ - ���¼������桢���ֺͳ�ʼ��������Ϸ״̬
|
||||
/// 重新开始当前游戏 - 重新加载场景并重置游戏状态
|
||||
/// </summary>
|
||||
private void OnReplayButtonClicked()
|
||||
{
|
||||
Debug.Log("[SettlementController] Replay button clicked - restarting current game");
|
||||
|
||||
// ��֤��Ҫ������
|
||||
// 验证必要引用
|
||||
if (bmm == null)
|
||||
{
|
||||
Debug.LogError("[SettlementController] BeatmapManager is null, cannot replay");
|
||||
@@ -582,7 +593,7 @@ public class settlementController : MonoBehaviour
|
||||
return;
|
||||
}
|
||||
|
||||
// ���浱ǰ�ؿ���Ϣ���������¼��أ�
|
||||
// 保存当前关卡信息用于重新加载
|
||||
SongData songToReplay = bmm.assignedSongData;
|
||||
int difficultyToReplay = bmm.assignedDifficulty;
|
||||
|
||||
@@ -594,33 +605,42 @@ public class settlementController : MonoBehaviour
|
||||
|
||||
Debug.Log($"[SettlementController] Reloading song: {songToReplay.songName}, difficulty: {difficultyToReplay}");
|
||||
|
||||
// ֹͣ��������
|
||||
// 停止结算音乐
|
||||
if (settlementAudioSource != null && settlementAudioSource.isPlaying)
|
||||
{
|
||||
settlementAudioSource.Stop();
|
||||
}
|
||||
|
||||
// ���� BeatmapManager Ϊ��һ������������
|
||||
// 设置 BeatmapManager 为下一次加载准备
|
||||
BeatmapManager.SetPendingSong(songToReplay, difficultyToReplay);
|
||||
|
||||
// ���¼��ص�ǰ������GamePlay_gamePlay��
|
||||
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
|
||||
// 重新加载当前场景
|
||||
StartCoroutine(LoadSceneAsync(SceneManager.GetActiveScene().name));
|
||||
}
|
||||
|
||||
private IEnumerator LoadSceneAsync(string sceneName)
|
||||
{
|
||||
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
|
||||
while (!asyncLoad.isDone)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ����ѡ�����
|
||||
/// 返回选曲界面
|
||||
/// </summary>
|
||||
private void OnExitButtonClicked()
|
||||
{
|
||||
Debug.Log("[SettlementController] Exit button clicked - returning to song selection");
|
||||
|
||||
// ֹͣ��������
|
||||
// 停止结算音乐
|
||||
if (settlementAudioSource != null && settlementAudioSource.isPlaying)
|
||||
{
|
||||
settlementAudioSource.Stop();
|
||||
}
|
||||
|
||||
// ����κδ�����������
|
||||
// 清除任何待处理的歌曲信息
|
||||
BeatmapManager.pendingSongData = null;
|
||||
BeatmapManager.pendingDifficulty = -1;
|
||||
|
||||
@@ -634,7 +654,7 @@ public class settlementController : MonoBehaviour
|
||||
}
|
||||
else
|
||||
{
|
||||
SceneManager.LoadScene("selectYourSongFirst");
|
||||
StartCoroutine(LoadSceneAsync("selectYourSongFirst"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -643,7 +663,8 @@ public class settlementController : MonoBehaviour
|
||||
{
|
||||
if (gm == null || gm.blackMaskImage == null)
|
||||
{
|
||||
SceneManager.LoadScene(sceneName);
|
||||
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
|
||||
while (!asyncLoad.isDone) yield return null;
|
||||
yield break;
|
||||
}
|
||||
|
||||
@@ -673,12 +694,16 @@ public class settlementController : MonoBehaviour
|
||||
blackMask.color = fc;
|
||||
|
||||
// load target scene
|
||||
SceneManager.LoadScene(sceneName);
|
||||
AsyncOperation asyncOp = SceneManager.LoadSceneAsync(sceneName);
|
||||
while (!asyncOp.isDone)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ���ݸ���ɫ�������ѡ����߷ֵĽ�ɫ�������� AllyHero_SO �е� ally_hero_HD_image ��ֵ�� mvp_hero_hd_image
|
||||
/// ����ʹ�� ScoreManager �� per-track pm sums��������������˵����� AllyCombatant.currentScore
|
||||
/// 根据各角色(按槽位选出最高分的角色),设置 AllyHero_SO 中的 ally_hero_HD_image 赋值给 mvp_hero_hd_image
|
||||
/// 这里使用 ScoreManager 的 per-track pm sums,如果没有则从场景中的 AllyCombatant.currentScore 读取
|
||||
/// </summary>
|
||||
private void SetMvpHeroImageFromTopScorer()
|
||||
{
|
||||
@@ -750,8 +775,11 @@ public class settlementController : MonoBehaviour
|
||||
allyId = teamUIController.Instance.allySlotIds[topIndex];
|
||||
if (allyId > 0)
|
||||
{
|
||||
var arr = Resources.LoadAll<AllyHero_SO>("");
|
||||
foreach (var a in arr)
|
||||
if (_cachedAllyHeroSOs == null || _cachedAllyHeroSOs.Length == 0)
|
||||
{
|
||||
_cachedAllyHeroSOs = Resources.LoadAll<AllyHero_SO>("");
|
||||
}
|
||||
foreach (var a in _cachedAllyHeroSOs)
|
||||
{
|
||||
if (a != null && a.ally_heroID == allyId) { heroSO = a; break; }
|
||||
}
|
||||
@@ -761,11 +789,14 @@ public class settlementController : MonoBehaviour
|
||||
// final fallback: try direct Resources lookup by scanning all and picking first non-null for slot
|
||||
if (heroSO == null)
|
||||
{
|
||||
var arr = Resources.LoadAll<AllyHero_SO>("");
|
||||
if (arr != null && arr.Length > 0)
|
||||
if (_cachedAllyHeroSOs == null || _cachedAllyHeroSOs.Length == 0)
|
||||
{
|
||||
_cachedAllyHeroSOs = Resources.LoadAll<AllyHero_SO>("");
|
||||
}
|
||||
if (_cachedAllyHeroSOs != null && _cachedAllyHeroSOs.Length > 0)
|
||||
{
|
||||
// attempt to match by name or just pick first
|
||||
heroSO = arr[0] as AllyHero_SO;
|
||||
heroSO = _cachedAllyHeroSOs[0] as AllyHero_SO;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user