hold音符补丁 备份
This commit is contained in:
@@ -60,6 +60,11 @@ public class HoldNote : BaseNote
|
||||
[Range(1f, 2f)]
|
||||
public float holdWindowMultiplier = 1.3f;
|
||||
|
||||
[Header("Custom Animation")]
|
||||
public GameObject holdAnimationPrefab;
|
||||
private GameObject activeHoldAnimationInstance;
|
||||
private Coroutine delayedHoldAnimationCoroutine;
|
||||
|
||||
public float visualSpeedMultiplier = 1f; // injected from NoteSpawner to adapt windows when visual speed changes
|
||||
|
||||
// track when the hold actually started (real-time) so we can compute held fraction
|
||||
@@ -317,6 +322,58 @@ public class HoldNote : BaseNote
|
||||
}
|
||||
}
|
||||
|
||||
private void StartHoldEffects()
|
||||
{
|
||||
StopHoldEffects(); // cleanup any existing
|
||||
if (gameObject.activeInHierarchy)
|
||||
{
|
||||
delayedHoldAnimationCoroutine = StartCoroutine(DelayedStartHoldEffects());
|
||||
}
|
||||
}
|
||||
|
||||
private void StopHoldEffects()
|
||||
{
|
||||
if (delayedHoldAnimationCoroutine != null)
|
||||
{
|
||||
StopCoroutine(delayedHoldAnimationCoroutine);
|
||||
delayedHoldAnimationCoroutine = null;
|
||||
}
|
||||
if (activeHoldAnimationInstance != null)
|
||||
{
|
||||
Destroy(activeHoldAnimationInstance);
|
||||
activeHoldAnimationInstance = null;
|
||||
}
|
||||
AnimationController.Global?.StopHoldParticles();
|
||||
}
|
||||
|
||||
private IEnumerator DelayedStartHoldEffects()
|
||||
{
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
|
||||
// Start standard particles
|
||||
AnimationController.Global?.StartHoldParticles(noteColor);
|
||||
|
||||
// Instantiate custom prefab if assigned
|
||||
if (holdAnimationPrefab != null)
|
||||
{
|
||||
// find spawn position: prefer the track effect position if available from AnimationController
|
||||
Vector3 spawnPos = transform.position;
|
||||
var ac = AnimationController.Global;
|
||||
if (ac != null)
|
||||
{
|
||||
// Logic to find slot position (simplified: use ac's position or the note's)
|
||||
// In AnimationController.PlayDestroyAnimation, it uses redEffect.transform.position etc.
|
||||
// We don't have direct access to those private fields, but we can assume
|
||||
// the note is at the judge line when this starts.
|
||||
}
|
||||
|
||||
activeHoldAnimationInstance = Instantiate(holdAnimationPrefab, spawnPos, Quaternion.identity);
|
||||
|
||||
// Optionally parent to the note or track
|
||||
// activeHoldAnimationInstance.transform.SetParent(this.transform, false);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator DelayedDisableAnimation(GameObject animationObject, float delay)
|
||||
{
|
||||
// No-op: we used to disable the shared AnimationController here which stopped coroutines.
|
||||
@@ -440,7 +497,7 @@ public class HoldNote : BaseNote
|
||||
if (!isHoldActive && jm != null && jm.IsStartJudged(noteID) && !jm.HasNoteReleased(noteID) && keyHeld)
|
||||
{
|
||||
isHoldActive = true;
|
||||
AnimationController.Global?.StartHoldParticles(noteColor);
|
||||
StartHoldEffects();
|
||||
if (debugEnabled) Debug.Log($"[HoldNote] Start particles started for {noteID} color={noteColor} at time={now:F3}");
|
||||
}
|
||||
|
||||
@@ -474,7 +531,7 @@ public class HoldNote : BaseNote
|
||||
if (isHoldActive && keyUp)
|
||||
{
|
||||
isHoldActive = false;
|
||||
AnimationController.Global?.StopHoldParticles();
|
||||
StopHoldEffects();
|
||||
if (debugEnabled) Debug.Log($"[HoldNote] KeyUp {keyToPress} detected. NoteID: {noteID}, Segment: {segment}, Type: {type}");
|
||||
|
||||
if (!hasReleased)
|
||||
@@ -649,7 +706,7 @@ public class HoldNote : BaseNote
|
||||
JudgeManager.Instance.RegisterStartJudged(noteID, true);
|
||||
isHoldActive = true;
|
||||
PlayHitAnimation();
|
||||
AnimationController.Global?.StartHoldParticles(noteColor);
|
||||
StartHoldEffects();
|
||||
|
||||
holdStartTime = pressTime;
|
||||
HoldNoteJudgePool.RegisterStart(noteID, pressTime, hitTime, scheduledEndTime, noteColor, trackIndex, (object)noteData);
|
||||
@@ -695,17 +752,17 @@ public class HoldNote : BaseNote
|
||||
// Autoplay must ignore physical input to guarantee Perfect at scheduledEndTime.
|
||||
if (!GameConfig.autoPlayEnabled && Input.GetKey(keyToPress) && JudgeManager.Instance.IsStartJudged(noteID) && !isJudged)
|
||||
{
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[HoldNote] End entered while key still held: {noteColor}, calculating result immediately");
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[HoldNote] End entered while key still held: {noteColor}, forcing Perfect");
|
||||
// Record release time as current time (player is still holding)
|
||||
releaseTime = Time.time;
|
||||
hasReleased = true;
|
||||
JudgeManager.Instance?.RegisterNoteReleased(noteID, true);
|
||||
// Evaluate and judge the hold end
|
||||
EvaluateHoldEnd(releaseTime, false);
|
||||
// Evaluate and judge the hold end - force Perfect since we reached the end while holding
|
||||
EvaluateHoldEnd(releaseTime, false, true);
|
||||
isJudged = true;
|
||||
// Terminate input - mark key as released
|
||||
isHoldActive = false;
|
||||
AnimationController.Global?.StopHoldParticles();
|
||||
StopHoldEffects();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -875,7 +932,7 @@ public class HoldNote : BaseNote
|
||||
JudgeManager.Instance.RegisterStartJudged(noteID, true);
|
||||
isHoldActive = true;
|
||||
PlayHitAnimation();
|
||||
AnimationController.Global?.StartHoldParticles(noteColor);
|
||||
StartHoldEffects();
|
||||
|
||||
// record when the hold started so we can compute held fraction later
|
||||
holdStartTime = pressTime;
|
||||
@@ -891,7 +948,7 @@ public class HoldNote : BaseNote
|
||||
JudgeManager.Instance.RegisterStartJudged(noteID, true);
|
||||
isHoldActive = true;
|
||||
PlayHitAnimation();
|
||||
AnimationController.Global?.StartHoldParticles(noteColor);
|
||||
StartHoldEffects();
|
||||
|
||||
holdStartTime = pressTime;
|
||||
|
||||
@@ -905,7 +962,7 @@ public class HoldNote : BaseNote
|
||||
JudgeManager.Instance.RegisterStartJudged(noteID, true);
|
||||
isHoldActive = true;
|
||||
PlayHitAnimation();
|
||||
AnimationController.Global?.StartHoldParticles(noteColor);
|
||||
StartHoldEffects();
|
||||
|
||||
holdStartTime = pressTime;
|
||||
|
||||
@@ -986,7 +1043,7 @@ public class HoldNote : BaseNote
|
||||
}
|
||||
|
||||
// central evaluation for hold end, given an actual releaseTime (real-time) or forceMiss
|
||||
private void EvaluateHoldEnd(float actualReleaseTime, bool forceMiss)
|
||||
private void EvaluateHoldEnd(float actualReleaseTime, bool forceMiss, bool forcePerfect = false)
|
||||
{
|
||||
if (isJudged) return; // already evaluated
|
||||
|
||||
@@ -1001,7 +1058,7 @@ public class HoldNote : BaseNote
|
||||
hasReleased = true;
|
||||
JudgeManager.Instance.RegisterNoteReleased(noteID, true);
|
||||
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[HoldNote] EvaluateHoldEnd: noteID={noteID} releaseTime={releaseTime:F3} scheduledEnd={scheduledEndTime:F3} forceMiss={forceMiss}");
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[HoldNote] EvaluateHoldEnd: noteID={noteID} releaseTime={releaseTime:F3} scheduledEnd={scheduledEndTime:F3} forceMiss={forceMiss} forcePerfect={forcePerfect}");
|
||||
|
||||
string result;
|
||||
|
||||
@@ -1028,13 +1085,13 @@ public class HoldNote : BaseNote
|
||||
float playerHeld = Mathf.Clamp(releaseTime - info.pressTime, 0f, info.length);
|
||||
float frac = info.length <= 0f ? 0f : (playerHeld / info.length);
|
||||
|
||||
if (frac > 0.8f)
|
||||
if (forcePerfect || frac > 0.7f)
|
||||
{
|
||||
result = "Perfect";
|
||||
ScoreManager.Instance.countPerfect += 1;
|
||||
if (trackIndex >= 0 && trackIndex < 5) ScoreManager.Instance.trackPerfectCounts[trackIndex]++;
|
||||
}
|
||||
else if (frac > 0.5f)
|
||||
else if (frac > 0.4f)
|
||||
{
|
||||
result = "Great";
|
||||
ScoreManager.Instance.countGreat += 1;
|
||||
@@ -1069,13 +1126,13 @@ public class HoldNote : BaseNote
|
||||
float held = Mathf.Clamp(actualReleaseTime - hitTime, 0f, holdRequired);
|
||||
float frac = holdRequired <= 0f ? 0f : (held / holdRequired);
|
||||
|
||||
if (frac > 0.8f)
|
||||
if (forcePerfect || frac > 0.7f)
|
||||
{
|
||||
result = "Perfect";
|
||||
ScoreManager.Instance.countPerfect += 1;
|
||||
if (trackIndex >= 0 && trackIndex < 5) ScoreManager.Instance.trackPerfectCounts[trackIndex]++;
|
||||
}
|
||||
else if (frac > 0.5f)
|
||||
else if (frac > 0.4f)
|
||||
{
|
||||
result = "Great";
|
||||
ScoreManager.Instance.countGreat += 1;
|
||||
@@ -1193,7 +1250,7 @@ public class HoldNote : BaseNote
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[HoldNote] Hold end NotifyNoteHit called: TrackIndex={TrackIndex} trackIndexField={trackIndex} result={result} triggeredHold={triggeredHold} triggeredTap={triggeredTap}");
|
||||
}
|
||||
|
||||
AnimationController.Global?.StopHoldParticles();
|
||||
StopHoldEffects();
|
||||
|
||||
// Release the track lock immediately after end evaluation so the next hold can start on time
|
||||
ReleaseTrackJudgeLockSafe();
|
||||
@@ -1315,6 +1372,9 @@ public class HoldNote : BaseNote
|
||||
// Restore materials before returning to pool/reusing
|
||||
RestoreOriginalMaterials();
|
||||
|
||||
// Stop any active hold animations or delayed starts
|
||||
StopHoldEffects();
|
||||
|
||||
hasReleased = false;
|
||||
segment = NoteSegment.None;
|
||||
keyToPress = KeyCode.None;
|
||||
|
||||
@@ -12,7 +12,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 088ed1ed6b6731f43ad3166781e3e931, type: 3}
|
||||
m_Name: NoteJudgeConfig
|
||||
m_EditorClassIdentifier:
|
||||
perfectRange: 0.15
|
||||
greatRange: 0.2
|
||||
goodRange: 0.25
|
||||
missRange: 0.3
|
||||
perfectRange: 0.05
|
||||
greatRange: 0.1
|
||||
goodRange: 0.15
|
||||
missRange: 0.2
|
||||
|
||||
@@ -55,6 +55,9 @@ public class Note : BaseNote
|
||||
|
||||
private static int ComputePmDeltaFromJudge(string judgeResult, AllyCombatant ally)
|
||||
{
|
||||
// If current character slot has no ally or is dead, then this corresponding track will also not get idol score
|
||||
if (ally == null || ally.IsDead) return 0;
|
||||
|
||||
int baseScore = 0;
|
||||
|
||||
var bmm = ally != null ? ally.bmm : null;
|
||||
|
||||
@@ -27,6 +27,12 @@ public class NoteSpawner : MonoBehaviour
|
||||
[Tooltip("Global additional realtime offset (seconds) added to hit times for all notes. Use to test input latency or adjust judgement timing. Default 0. Can be negative to make notes arrive earlier.")]
|
||||
public float globalHitDelay = 0f;
|
||||
|
||||
[Header("Sync Note Detection")]
|
||||
[Tooltip("Whether to spawn a special prefab for notes that appear within 0.05s of each other.")]
|
||||
public bool enableSyncNotePrefab = false;
|
||||
[Tooltip("The prefab to instantiate under the note when a sync is detected.")]
|
||||
public GameObject syncNotePrefab;
|
||||
|
||||
// Documentation text normalized.
|
||||
[Tooltip("Multiplier applied to visual fall speed. Changing this will automatically adjust spawn timing so notes still arrive at their original beat times.")]
|
||||
[Range(0.5f, 2f)]
|
||||
@@ -131,6 +137,10 @@ public class NoteSpawner : MonoBehaviour
|
||||
spawnOffset = savedDelay + static_value_add_to_spawnoffset;
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[NoteSpawner] Applied spawnOffset={spawnOffset} (Saved={savedDelay} + Static={static_value_add_to_spawnoffset})");
|
||||
|
||||
// 读取 PlayerPrefs 中的同步音符开关状态
|
||||
enableSyncNotePrefab = PlayerPrefs.GetInt("EnableSyncNotePrefab", 1) == 1;
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[NoteSpawner] Applied enableSyncNotePrefab={enableSyncNotePrefab}");
|
||||
|
||||
// restore animations active state on Start
|
||||
if (animations != null)
|
||||
{
|
||||
@@ -254,6 +264,27 @@ public class NoteSpawner : MonoBehaviour
|
||||
|
||||
NoteData note = beatmap.notes[i];
|
||||
|
||||
// Check if this is a sync note (within 0.05s of any other note)
|
||||
bool isSync = false;
|
||||
if (enableSyncNotePrefab)
|
||||
{
|
||||
// Check previous notes (optimized assuming sorted beatmap)
|
||||
for (int j = i - 1; j >= 0; j--)
|
||||
{
|
||||
if (Mathf.Abs(note.time - beatmap.notes[j].time) <= 0.05f) { isSync = true; break; }
|
||||
if (note.time - beatmap.notes[j].time > 0.05f) break;
|
||||
}
|
||||
if (!isSync)
|
||||
{
|
||||
// Check next notes (optimized assuming sorted beatmap)
|
||||
for (int j = i + 1; j < beatmap.notes.Length; j++)
|
||||
{
|
||||
if (Mathf.Abs(note.time - beatmap.notes[j].time) <= 0.05f) { isSync = true; break; }
|
||||
if (beatmap.notes[j].time - note.time > 0.05f) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float travelTime = GetLaneTravelTimeSeconds(laneTravelTimes, note.trackIndex);
|
||||
float spawnTime = note.time - travelTime;
|
||||
float delay = spawnTime - (Time.time - startTime) + spawnOffset;
|
||||
@@ -272,12 +303,12 @@ public class NoteSpawner : MonoBehaviour
|
||||
if (note.type == "hold")
|
||||
{
|
||||
// create the hold note once and record its id mapping
|
||||
int hid = SpawnHoldNote(note, sm, travelTime, noteSpeed, segmentInterval);
|
||||
int hid = SpawnHoldNote(note, sm, travelTime, noteSpeed, segmentInterval, isSync);
|
||||
noteIndexToHoldId[i] = hid;
|
||||
}
|
||||
else
|
||||
{
|
||||
SpawnNote(note, sm, travelTime, noteSpeed);
|
||||
SpawnNote(note, sm, travelTime, noteSpeed, isSync);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,7 +330,7 @@ public class NoteSpawner : MonoBehaviour
|
||||
return key;
|
||||
}
|
||||
|
||||
public void SpawnNote(NoteData noteData, float sm, float travelTime, float noteSpeed)
|
||||
public void SpawnNote(NoteData noteData, float sm, float travelTime, float noteSpeed, bool isSync = false)
|
||||
{
|
||||
if (noteData.trackIndex < 0 || noteData.trackIndex >= spawnPoints.Length)
|
||||
{
|
||||
@@ -321,6 +352,23 @@ public class NoteSpawner : MonoBehaviour
|
||||
return;
|
||||
}
|
||||
|
||||
if (enableSyncNotePrefab && syncNotePrefab != null)
|
||||
{
|
||||
// Clean up any existing sync prefab from previous use in pool
|
||||
foreach (Transform child in note.transform)
|
||||
{
|
||||
if (child.name.StartsWith(syncNotePrefab.name))
|
||||
{
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
if (isSync)
|
||||
{
|
||||
Instantiate(syncNotePrefab, note.transform);
|
||||
}
|
||||
}
|
||||
|
||||
Transform spawnPoint = spawnPoints[noteData.trackIndex];
|
||||
|
||||
// Calculate hit time (realtime when note should be judged)
|
||||
@@ -354,7 +402,7 @@ public class NoteSpawner : MonoBehaviour
|
||||
}
|
||||
|
||||
// Modified: return generated holdNoteId so callers can map notes to ids
|
||||
public int SpawnHoldNote(NoteData noteData, float sm, float travelTime, float noteSpeed, float segmentInterval)
|
||||
public int SpawnHoldNote(NoteData noteData, float sm, float travelTime, float noteSpeed, float segmentInterval, bool isSync = false)
|
||||
{
|
||||
if (noteData.trackIndex < 0 || noteData.trackIndex >= spawnPoints.Length)
|
||||
{
|
||||
@@ -398,6 +446,23 @@ public class NoteSpawner : MonoBehaviour
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (enableSyncNotePrefab && syncNotePrefab != null)
|
||||
{
|
||||
// Clean up any existing sync prefab from previous use in pool
|
||||
foreach (Transform child in startObj.transform)
|
||||
{
|
||||
if (child.name.StartsWith(syncNotePrefab.name))
|
||||
{
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
if (isSync)
|
||||
{
|
||||
Instantiate(syncNotePrefab, startObj.transform);
|
||||
}
|
||||
}
|
||||
|
||||
// Hold segments use absolute positioning; spawn at the lane origin.
|
||||
startObj.transform.position = spawnPoint.position;
|
||||
startObj.transform.rotation = Quaternion.identity;
|
||||
|
||||
@@ -1228,11 +1228,11 @@ public class settlementController : MonoBehaviour
|
||||
|
||||
if (sm != null)
|
||||
{
|
||||
scores[0] = sm.red_pmScore_sum;
|
||||
scores[1] = sm.green_pmScore_sum;
|
||||
scores[2] = sm.yellow_pmScore_sum;
|
||||
scores[3] = sm.purple_pmScore_sum;
|
||||
scores[4] = sm.blue_pmScore_sum;
|
||||
scores[0] = sm.red_idolScore_sum;
|
||||
scores[1] = sm.green_idolScore_sum;
|
||||
scores[2] = sm.yellow_idolScore_sum;
|
||||
scores[3] = sm.purple_idolScore_sum;
|
||||
scores[4] = sm.blue_idolScore_sum;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1262,9 +1262,13 @@ public class settlementController : MonoBehaviour
|
||||
if (topIndex < 0 || max <= 0)
|
||||
{
|
||||
Debug.Log("[SettlementController] No top scorer found or scores are all zero");
|
||||
// hide image
|
||||
mvp_hero_hd_image.sprite = null;
|
||||
mvp_hero_hd_image.color = new Color(mvp_hero_hd_image.color.r, mvp_hero_hd_image.color.g, mvp_hero_hd_image.color.b, 0f);
|
||||
// hide image and mvp object
|
||||
if (mvp_object != null) mvp_object.SetActive(false);
|
||||
if (mvp_hero_hd_image != null)
|
||||
{
|
||||
mvp_hero_hd_image.sprite = null;
|
||||
mvp_hero_hd_image.color = new Color(mvp_hero_hd_image.color.r, mvp_hero_hd_image.color.g, mvp_hero_hd_image.color.b, 0f);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1297,6 +1301,8 @@ public class settlementController : MonoBehaviour
|
||||
}
|
||||
|
||||
// final fallback: try direct Resources lookup by scanning all and picking first non-null for slot
|
||||
// REMOVED: This fallback causes a default character to appear when no hero is selected.
|
||||
/*
|
||||
if (heroSO == null)
|
||||
{
|
||||
if (_cachedAllyHeroSOs == null || _cachedAllyHeroSOs.Length == 0)
|
||||
@@ -1309,6 +1315,7 @@ public class settlementController : MonoBehaviour
|
||||
heroSO = _cachedAllyHeroSOs[0] as AllyHero_SO;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if (heroSO != null)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user