加入很多新内容,这波一块交了

This commit is contained in:
FloatGaming
2026-04-08 20:53:41 +08:00
parent d9376833b6
commit 85dfff28dd
128 changed files with 14546 additions and 474 deletions
@@ -3,6 +3,11 @@ using System.Collections;
public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
{
private sealed class PooledJudgeTag : MonoBehaviour
{
public GameObject prefabSource;
}
public static Animation_GenerateJudgementSituationPrefab Instance;
[Header("Inspector")]
@@ -42,6 +47,9 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
// Track last scene to detect scene changes
private int lastSceneIndex = -1;
private Coroutine prewarmJudgeCoroutine;
private readonly System.Collections.Generic.Dictionary<GameObject, System.Collections.Generic.Stack<GameObject>> judgePools
= new System.Collections.Generic.Dictionary<GameObject, System.Collections.Generic.Stack<GameObject>>();
private void Awake()
{
@@ -147,7 +155,10 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
if (JudgeManager.IsDebugEnabled) Debug.Log($"[Animation_Generate] Spawning judge prefab: color={color}, result={judgeResult}, prefab={prefabToUse.name}");
GameObject instance = Instantiate(prefabToUse, spawnPoint);
GameObject instance = GetPooledJudge(prefabToUse, spawnPoint);
if (instance == null)
return;
instance.transform.localPosition = Vector3.zero;
// Documentation text normalized.
@@ -159,6 +170,52 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
StartCoroutine(AnimateSprite(instance));
}
public void PrewarmJudgePrefabs(int perPrefab = 1)
{
if (!Application.isPlaying)
return;
if (prewarmJudgeCoroutine != null)
{
StopCoroutine(prewarmJudgeCoroutine);
}
prewarmJudgeCoroutine = StartCoroutine(PrewarmJudgePrefabsRoutine(Mathf.Max(1, perPrefab)));
}
public IEnumerator PrewarmJudgePrefabsRoutine(int perPrefab = 1)
{
GameObject[] prefabs =
{
perfect_judge_prefab,
great_judge_prefab,
good_judge_prefab,
miss_judge_prefab
};
int count = Mathf.Max(1, perPrefab);
foreach (GameObject prefab in prefabs)
{
if (prefab == null)
continue;
for (int i = 0; i < count; i++)
{
GameObject instance = null;
try
{
instance = CreatePooledJudge(prefab);
ReturnJudgeToPool(instance);
}
catch { }
yield return null;
}
}
prewarmJudgeCoroutine = null;
}
private IEnumerator AnimateSprite(GameObject obj)
{
if (obj == null) yield break;
@@ -232,7 +289,76 @@ public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour
// Documentation text normalized.
if (obj != null) Destroy(obj);
if (obj != null) ReturnJudgeToPool(obj);
}
private GameObject GetPooledJudge(GameObject prefab, Transform parent)
{
if (prefab == null || parent == null)
return null;
if (!judgePools.TryGetValue(prefab, out var pool))
{
pool = new System.Collections.Generic.Stack<GameObject>();
judgePools[prefab] = pool;
}
GameObject instance = null;
while (pool.Count > 0 && instance == null)
{
instance = pool.Pop();
}
if (instance == null)
{
instance = CreatePooledJudge(prefab);
}
if (instance == null)
return null;
instance.transform.SetParent(parent, false);
instance.SetActive(true);
return instance;
}
private GameObject CreatePooledJudge(GameObject prefab)
{
if (prefab == null)
return null;
GameObject instance = Instantiate(prefab, transform);
var tag = instance.GetComponent<PooledJudgeTag>();
if (tag == null)
tag = instance.AddComponent<PooledJudgeTag>();
tag.prefabSource = prefab;
instance.SetActive(false);
return instance;
}
private void ReturnJudgeToPool(GameObject instance)
{
if (instance == null)
return;
var tag = instance.GetComponent<PooledJudgeTag>();
if (tag == null || tag.prefabSource == null)
{
Destroy(instance);
return;
}
if (!judgePools.TryGetValue(tag.prefabSource, out var pool))
{
pool = new System.Collections.Generic.Stack<GameObject>();
judgePools[tag.prefabSource] = pool;
}
instance.transform.SetParent(transform, false);
instance.transform.localPosition = new Vector3(99999f, 99999f, 0f);
instance.transform.localRotation = Quaternion.identity;
instance.SetActive(false);
pool.Push(instance);
}
private Transform GetSpawnPoint(string color)
@@ -9,6 +9,7 @@ public class NoteData
public string color; // Documentation text normalized.
public string type; // Documentation text normalized.
public float length; // Documentation text normalized.
public string noteFunction;
// New fields for judgement recording
// judgeOffsetMs: signed offset in milliseconds recorded at judgement time
@@ -326,6 +326,9 @@ public class GameManager : MonoBehaviour
{
Debug.Log("GameManager Start() initialized");
// Move first-hit one-time setup cost into scene load instead of the first judged note.
StartCoroutine(PrewarmGameplayFirstHitPath());
// --- Initialize Statistics ---
currentSong = BeatmapManager.pendingSongData ?? SongDataHolder.SelectedSongData;
if (currentSong != null && !GameConfig.autoPlayEnabled)
@@ -489,6 +492,34 @@ public class GameManager : MonoBehaviour
}
}
private IEnumerator PrewarmGameplayFirstHitPath()
{
yield return null;
try
{
Note.PrewarmRuntimeCaches();
HoldNote.PrewarmRuntimeCaches();
var judgeFx = Animation_GenerateJudgementSituationPrefab.Instance
?? FindAnyObjectByType<Animation_GenerateJudgementSituationPrefab>();
judgeFx?.PrewarmJudgePrefabs(1);
var fxEvent = effectEventController.Instance ?? FindAnyObjectByType<effectEventController>();
if (fxEvent != null && fxEvent.isActiveAndEnabled)
{
// Cache target and pre-create SmoothShake via OnEnable/Awake path already present.
}
var globalFx = globalNoteEffect.Instance ?? FindAnyObjectByType<globalNoteEffect>();
globalFx?.PrewarmRuntime();
}
catch (System.Exception ex)
{
Debug.LogWarning($"[GameManager] Gameplay prewarm failed: {ex}");
}
}
private void Update()
{
// Detect Space or yellow note key to request start (only before gameplay starts)
+32 -1
View File
@@ -99,6 +99,18 @@ public class HoldNote : BaseNote
return beatmapManagerCache;
}
public static void PrewarmRuntimeCaches()
{
GetBeatmapManagerCached();
if (allyCache == null || allyCache.Length < 10)
allyCache = new AllyCombatant[10];
for (int i = 0; i < 5; i++)
{
GetAllyForTrackCached(i);
}
}
private static float JudgeToPmMultiplier(string judgeResult)
{
switch (judgeResult)
@@ -326,9 +338,10 @@ public class HoldNote : BaseNote
}
private float cachedTravelTime = 1.0f; // Cached travel time for distance optimization
private bool isSyncNote;
public void Setup(int id, int trackIndex, float speed, float time, float delay,
bool isEnd, float scheduledEnd, string color, KeyCode key, string type, float travelTimeSeconds, NoteJudgeConfig judgeConfig, NoteData noteData)
bool isEnd, float scheduledEnd, string color, KeyCode key, string type, float travelTimeSeconds, NoteJudgeConfig judgeConfig, NoteData noteData, bool isSync = false)
{
this.id = id;
// Ensure singletons are up to date if they were re-initialized (e.g. on scene reload)
@@ -347,6 +360,7 @@ public class HoldNote : BaseNote
this.key = key;
this.type = type;
this.judgeConfig = judgeConfig;
this.isSyncNote = isSync;
this.noteID = id.ToString();
this.keyToPress = key;
@@ -659,6 +673,13 @@ public class HoldNote : BaseNote
InputManager.Instance?.ShowJudgeResult(trackIndex, result);
teamUIController.Instance?.OnJudgeResult(result);
JudgeSoundManager.Instance?.PlayJudgeSound(result);
if (isSyncNote)
{
effectEventController.TryTriggerMultiNoteShake();
}
globalNoteEffect.TryTrigger(noteData != null ? noteData.noteFunction : null);
}
private void OnTriggerEnter2D(Collider2D collision)
@@ -975,6 +996,16 @@ public class HoldNote : BaseNote
{
ScheduleReturnToPool(0.2f);
}
if (segment == NoteSegment.Start && isSyncNote && result != "Miss")
{
effectEventController.TryTriggerMultiNoteShake();
}
if (segment == NoteSegment.Start && result != "Miss")
{
globalNoteEffect.TryTrigger(noteData != null ? noteData.noteFunction : null);
}
}
private IEnumerator DelayedReturn()
+29 -1
View File
@@ -11,6 +11,7 @@ public class Note : BaseNote
private bool hasLock = false; // track whether we hold the track lock
private NoteData noteData;
private bool isSyncNote;
[Header("Inspector")]
public NoteJudgeConfig judgeConfig; // Documentation text normalized.
@@ -42,6 +43,18 @@ public class Note : BaseNote
return beatmapManagerCache;
}
public static void PrewarmRuntimeCaches()
{
GetBeatmapManagerCached();
if (allyCache == null || allyCache.Length < 10)
allyCache = new AllyCombatant[10];
for (int i = 0; i < 5; i++)
{
GetAllyForTrackCached(i);
}
}
private static float JudgeToPmMultiplier(string judgeResult)
{
switch (judgeResult)
@@ -150,7 +163,7 @@ public class Note : BaseNote
controller = GetComponent<NoteController>();
}
public void Setup(KeyCode key, int trackIndex, float speed, float hitTime, string color, NoteJudgeConfig judgeConfig, NoteData data)
public void Setup(KeyCode key, int trackIndex, float speed, float hitTime, string color, NoteJudgeConfig judgeConfig, NoteData data, bool isSync = false)
{
keyToPress = key;
noteColor = color;
@@ -161,6 +174,7 @@ public class Note : BaseNote
hasLock = false;
this.judgeConfig = judgeConfig;
this.noteData = data;
this.isSyncNote = isSync;
// Set miss deadline: hitTime + missRange (the latest time to judge before auto-miss)
float missRange = (judgeConfig?.missRange ?? 0.5f);
@@ -279,6 +293,13 @@ public class Note : BaseNote
try { SkillBuilder.Instance?.NotifyNoteHit(TrackIndex, judgeResult, SkillDefinition.NoteTypeTrigger.Tap); } catch { }
LogTapJudge(judgeResult, 0f, false, true, pressTime);
if (isSyncNote)
{
effectEventController.TryTriggerMultiNoteShake();
}
globalNoteEffect.TryTrigger(noteData != null ? noteData.noteFunction : null);
Judge();
}
@@ -437,6 +458,13 @@ public class Note : BaseNote
try { SkillBuilder.Instance?.NotifyNoteHit(TrackIndex, judgeResult, SkillDefinition.NoteTypeTrigger.Tap); } catch { }
LogTapJudge(judgeResult, rawOffsetMs, rewrittenToPerfect, false, pressTime);
if (isSyncNote && judgeResult != "Miss")
{
effectEventController.TryTriggerMultiNoteShake();
}
globalNoteEffect.TryTrigger(noteData != null ? noteData.noteFunction : null);
}
Judge();
@@ -390,7 +390,7 @@ public class NoteSpawner : MonoBehaviour
if (noteScript != null)
{
// Setup note script with timing parameters
noteScript.Setup(key, noteData.trackIndex, noteSpeed, realtimeHit, noteData.color, judgeConfig, noteData);
noteScript.Setup(key, noteData.trackIndex, noteSpeed, realtimeHit, noteData.color, judgeConfig, noteData, isSync);
// Configure controller for absolute positioning (replaces relative Translate)
if (noteController != null)
@@ -476,7 +476,7 @@ public class NoteSpawner : MonoBehaviour
if (holdNote != null)
{
// pass realtime hit time (startTime + note.time) and delay 0, include globalHitDelay
holdNote.Setup(holdNoteId, noteData.trackIndex, noteSpeed, baseHit, 0f, false, scheduledEndTime, noteData.color, key, "start", travelTime, judgeConfig, noteData);
holdNote.Setup(holdNoteId, noteData.trackIndex, noteSpeed, baseHit, 0f, false, scheduledEndTime, noteData.color, key, "start", travelTime, judgeConfig, noteData, isSync);
// 使用流速倍率 sm 直接进行缩放,确保长条音符在 0.5-2.0 范围内依然能完美衔接
float visualScale = sm;
@@ -508,7 +508,7 @@ public class NoteSpawner : MonoBehaviour
if (holdSeg != null)
{
// Documentation text normalized.
holdSeg.Setup(holdNoteId, noteData.trackIndex, noteSpeed, baseHit, segmentDelay, false, scheduledEndTime, noteData.color, key, "middle", travelTime, judgeConfig, noteData);
holdSeg.Setup(holdNoteId, noteData.trackIndex, noteSpeed, baseHit, segmentDelay, false, scheduledEndTime, noteData.color, key, "middle", travelTime, judgeConfig, noteData, false);
// 应用与开始段一致的流速缩放
float visualScaleMid = sm;
@@ -540,7 +540,7 @@ public class NoteSpawner : MonoBehaviour
HoldNote holdEnd = endObj.GetComponent<HoldNote>();
if (holdEnd != null)
{
holdEnd.Setup(holdNoteId, noteData.trackIndex, noteSpeed, baseHit, endDelay, true, scheduledEndTime, noteData.color, key, "end", travelTime, judgeConfig, noteData);
holdEnd.Setup(holdNoteId, noteData.trackIndex, noteSpeed, baseHit, endDelay, true, scheduledEndTime, noteData.color, key, "end", travelTime, judgeConfig, noteData, false);
// 同样应用流速缩放
float visualScaleEnd = sm;
@@ -0,0 +1,314 @@
using SmoothShakeFree;
using UnityEngine;
public class effectEventController : MonoBehaviour
{
public enum CameraOffsetAxis
{
X,
Y,
Z
}
public static effectEventController Instance { get; private set; }
[Header("Multi Note Screen Shake")]
public bool enableMultiNoteScreenShake = true;
public float multiNoteScreenShakeAmplitude = 0.08f;
[Header("Camera Drift")]
public bool enableCameraDrift = false;
public Vector2 cameraDriftRange = new Vector2(0.15f, 0.08f);
public float cameraDriftSpeed = 0.3f;
[Header("Player Skill 10 Camera Offset")]
public float playerSkillCameraOffsetAmount = 0.6f;
public CameraOffsetAxis playerSkillCameraOffsetAxis = CameraOffsetAxis.Y;
private Transform cachedShakeTarget;
private Transform cachedCameraDriftPivot;
private Transform cachedCameraSkillPivot;
private Vector3 cameraDriftBaseLocalPosition;
private Vector3 cameraSkillBaseLocalPosition;
private float cameraDriftSeedX;
private float cameraDriftSeedY;
private void Awake()
{
if (Instance == null)
{
Instance = this;
cameraDriftSeedX = Random.Range(0f, 1000f);
cameraDriftSeedY = Random.Range(1000f, 2000f);
CacheShakeTarget();
return;
}
if (Instance != this)
{
Destroy(gameObject);
}
}
private void OnEnable()
{
CacheShakeTarget();
ResetCameraDriftIfNeeded();
}
private void Update()
{
UpdateCameraDrift();
UpdatePlayerSkillCameraOffset();
}
private void CacheShakeTarget()
{
if (Camera.main != null)
{
cachedShakeTarget = Camera.main.transform;
PrewarmShakeComponent();
EnsureCameraDriftPivot();
EnsureCameraSkillPivot();
}
}
private void PrewarmShakeComponent()
{
if (cachedShakeTarget == null)
return;
SmoothShake shake = cachedShakeTarget.GetComponent<SmoothShake>();
if (shake == null)
{
shake = cachedShakeTarget.gameObject.AddComponent<SmoothShake>();
}
if (shake.positionShake == null) shake.positionShake = new Shaker();
if (shake.rotationShake == null) shake.rotationShake = new Shaker();
if (shake.shakers == null || shake.shakers.Length != 2)
shake.shakers = new[] { shake.positionShake, shake.rotationShake };
if (shake.sum == null || shake.sum.Length != 2)
shake.sum = new Vector3[2];
}
private void EnsureCameraDriftPivot()
{
if (cachedShakeTarget == null)
return;
if (cachedCameraDriftPivot != null)
return;
Transform currentParent = cachedShakeTarget.parent;
if (currentParent != null && currentParent.name == "__effect_event_camera_drift_pivot")
{
cachedCameraDriftPivot = currentParent;
cameraDriftBaseLocalPosition = cachedCameraDriftPivot.localPosition;
return;
}
GameObject pivotObject = new GameObject("__effect_event_camera_drift_pivot");
Transform pivot = pivotObject.transform;
if (currentParent != null)
{
pivot.SetParent(currentParent, false);
pivot.localPosition = cachedShakeTarget.localPosition;
pivot.localRotation = cachedShakeTarget.localRotation;
pivot.localScale = Vector3.one;
}
else
{
pivot.position = cachedShakeTarget.position;
pivot.rotation = cachedShakeTarget.rotation;
}
cachedShakeTarget.SetParent(pivot, true);
cachedShakeTarget.localRotation = Quaternion.identity;
cachedCameraDriftPivot = pivot;
cameraDriftBaseLocalPosition = cachedCameraDriftPivot.localPosition;
}
private void EnsureCameraSkillPivot()
{
if (cachedShakeTarget == null)
return;
if (cachedCameraSkillPivot != null)
return;
Transform currentParent = cachedShakeTarget.parent;
if (currentParent != null && currentParent.name == "__effect_event_camera_skill_pivot")
{
cachedCameraSkillPivot = currentParent;
cameraSkillBaseLocalPosition = cachedCameraSkillPivot.localPosition;
return;
}
GameObject pivotObject = new GameObject("__effect_event_camera_skill_pivot");
Transform pivot = pivotObject.transform;
if (currentParent != null)
{
pivot.SetParent(currentParent, false);
pivot.localPosition = cachedShakeTarget.localPosition;
pivot.localRotation = cachedShakeTarget.localRotation;
pivot.localScale = Vector3.one;
}
else
{
pivot.position = cachedShakeTarget.position;
pivot.rotation = cachedShakeTarget.rotation;
pivot.localScale = Vector3.one;
}
cachedShakeTarget.SetParent(pivot, true);
cachedShakeTarget.localPosition = Vector3.zero;
cachedShakeTarget.localRotation = Quaternion.identity;
cachedCameraSkillPivot = pivot;
cameraSkillBaseLocalPosition = cachedCameraSkillPivot.localPosition;
}
private void UpdateCameraDrift()
{
if (cachedShakeTarget == null)
{
CacheShakeTarget();
}
if (cachedShakeTarget == null)
return;
EnsureCameraDriftPivot();
if (cachedCameraDriftPivot == null)
return;
if (!enableCameraDrift)
{
ResetCameraDriftIfNeeded();
return;
}
float speed = Mathf.Max(0f, cameraDriftSpeed);
float time = Time.time * speed;
float offsetX = (Mathf.PerlinNoise(cameraDriftSeedX, time) - 0.5f) * 2f * Mathf.Abs(cameraDriftRange.x);
float offsetY = (Mathf.PerlinNoise(cameraDriftSeedY, time) - 0.5f) * 2f * Mathf.Abs(cameraDriftRange.y);
cachedCameraDriftPivot.localPosition = cameraDriftBaseLocalPosition + new Vector3(offsetX, offsetY, 0f);
}
private void ResetCameraDriftIfNeeded()
{
if (cachedCameraDriftPivot == null)
return;
cachedCameraDriftPivot.localPosition = cameraDriftBaseLocalPosition;
}
private void UpdatePlayerSkillCameraOffset()
{
if (cachedShakeTarget == null)
{
CacheShakeTarget();
}
if (cachedShakeTarget == null)
return;
EnsureCameraSkillPivot();
if (cachedCameraSkillPivot == null)
return;
Vector3 targetLocalPosition = cameraSkillBaseLocalPosition;
if (PlayerSkillService.IsSkillEnabled(10))
{
float offset = playerSkillCameraOffsetAmount;
switch (playerSkillCameraOffsetAxis)
{
case CameraOffsetAxis.X:
targetLocalPosition.x += offset;
break;
case CameraOffsetAxis.Y:
targetLocalPosition.y += offset;
break;
case CameraOffsetAxis.Z:
targetLocalPosition.z += offset;
break;
}
}
if (cachedCameraSkillPivot.localPosition != targetLocalPosition)
{
cachedCameraSkillPivot.localPosition = targetLocalPosition;
}
}
public static void RefreshPlayerSkillCameraOffset()
{
effectEventController controller = Instance != null ? Instance : FindAnyObjectByType<effectEventController>();
if (controller == null)
return;
controller.CacheShakeTarget();
controller.UpdatePlayerSkillCameraOffset();
}
public static void TryTriggerMultiNoteShake()
{
effectEventController controller = Instance != null ? Instance : FindAnyObjectByType<effectEventController>();
if (controller == null)
return;
controller.TriggerMultiNoteShakeInternal();
}
private void TriggerMultiNoteShakeInternal()
{
if (!enableMultiNoteScreenShake)
return;
if (cachedShakeTarget == null)
{
CacheShakeTarget();
}
if (cachedShakeTarget == null)
return;
float amplitudeValue = Mathf.Max(0f, multiNoteScreenShakeAmplitude);
if (amplitudeValue <= 0f)
return;
SmoothShake shake = cachedShakeTarget.GetComponent<SmoothShake>();
if (shake == null)
{
shake = cachedShakeTarget.gameObject.AddComponent<SmoothShake>();
}
if (shake.positionShake == null) shake.positionShake = new Shaker();
if (shake.rotationShake == null) shake.rotationShake = new Shaker();
shake.positionShake.noiseType = Shaker.NoiseType.SineWave;
shake.positionShake.amplitude = new Vector3(amplitudeValue, amplitudeValue, 0f);
shake.positionShake.frequency = new Vector3(20f, 20f, 0f);
shake.rotationShake.amplitude = Vector3.zero;
shake.rotationShake.frequency = Vector3.zero;
shake.timeSettings.constantShake = false;
shake.timeSettings.fadeInDuration = 0.03f;
shake.timeSettings.holdDuration = 0.04f;
shake.timeSettings.fadeOutDuration = 0.05f;
if (shake.timeSettings.fadeInCurve == null || shake.timeSettings.fadeInCurve.length == 0)
shake.timeSettings.fadeInCurve = AnimationCurve.Linear(0f, 0f, 1f, 1f);
if (shake.timeSettings.fadeOutCurve == null || shake.timeSettings.fadeOutCurve.length == 0)
shake.timeSettings.fadeOutCurve = AnimationCurve.Linear(0f, 1f, 1f, 0f);
shake.shakers = new[] { shake.positionShake, shake.rotationShake };
shake.sum = new Vector3[2];
shake.StartShake();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f65edb710af433c479467d8c932e5d59
@@ -0,0 +1,361 @@
using SmoothShakeFree;
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
public class globalNoteEffect : MonoBehaviour
{
public static globalNoteEffect Instance { get; private set; }
[Header("Camera Shake Event")]
public float cameraShakeAmplitude = 0.12f;
[Header("Flash Blink Event")]
public Image flashBlinkImage;
public float flashBlinkFadeDuration = 0.2f;
[Header("Camera Dash Event")]
public float targetCameraZPosition = -6.5f;
public AnimationCurve cameraDashCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
public float cameraDashDuration = 0.12f;
[Header("Camera Wave FX")]
public float cameraWaveEnterDuration = 0.12f;
public float cameraWaveRecoverDuration = 0.12f;
[Header("Camera Glitch FX")]
public float cameraGlitchEnterDuration = 0.08f;
public float cameraGlitchRecoverDuration = 0.12f;
[Header("Camera Monochrome FX")]
public float cameraMonochromeEnterDuration = 0.1f;
public float cameraMonochromeRecoverDuration = 0.14f;
[Header("Screenshot Scale FX")]
public float screenshotScaleStartOpacity = 0.5f;
public float screenshotScaleFadeDuration = 0.22f;
public float screenshotScaleEndScale = 1.12f;
private Transform cachedCameraTransform;
private Coroutine flashBlinkCoroutine;
private Coroutine cameraDashCoroutine;
private float cameraBaseLocalZ;
private bool hasCameraBaseLocalZ;
private void Awake()
{
if (Instance == null)
{
Instance = this;
}
else if (Instance != this)
{
Destroy(gameObject);
return;
}
CacheCameraTransform();
PrewarmRuntime();
}
private void Start()
{
SetFlashImageAlpha(0f);
}
private void OnEnable()
{
CacheCameraTransform();
SetFlashImageAlpha(0f);
PrewarmRuntime();
}
private void LateUpdate()
{
if (cachedCameraTransform == null)
{
CacheCameraTransform();
}
if (cachedCameraTransform != null && cameraDashCoroutine == null)
{
cameraBaseLocalZ = cachedCameraTransform.localPosition.z;
hasCameraBaseLocalZ = true;
}
}
private void CacheCameraTransform()
{
if (Camera.main != null)
{
cachedCameraTransform = Camera.main.transform;
cameraBaseLocalZ = cachedCameraTransform.localPosition.z;
hasCameraBaseLocalZ = true;
}
}
public void PrewarmRuntime()
{
CacheCameraTransform();
if (cachedCameraTransform != null)
{
SmoothShake shake = cachedCameraTransform.GetComponent<SmoothShake>();
if (shake == null)
{
shake = cachedCameraTransform.gameObject.AddComponent<SmoothShake>();
}
if (shake.positionShake == null) shake.positionShake = new Shaker();
if (shake.rotationShake == null) shake.rotationShake = new Shaker();
if (shake.shakers == null || shake.shakers.Length != 2)
shake.shakers = new[] { shake.positionShake, shake.rotationShake };
if (shake.sum == null || shake.sum.Length != 2)
shake.sum = new Vector3[2];
}
GlobalDistortionRuntimeController.EnsureInstance();
GlobalGlitchRuntimeController.EnsureInstance();
GlobalMonochromeRuntimeController.EnsureInstance();
GlobalAfterimageRuntimeController.EnsureInstance();
}
public static void TryTrigger(string noteFunction)
{
if (string.IsNullOrWhiteSpace(noteFunction))
return;
globalNoteEffect controller = GetOrCreateInstance();
if (controller == null)
return;
controller.TriggerInternal(noteFunction.Trim());
}
private static globalNoteEffect GetOrCreateInstance()
{
if (Instance != null)
return Instance;
globalNoteEffect found = FindAnyObjectByType<globalNoteEffect>();
if (found != null)
{
Instance = found;
return found;
}
GameObject runtimeObject = new GameObject("__global_note_effect_runtime");
return runtimeObject.AddComponent<globalNoteEffect>();
}
private void TriggerInternal(string noteFunction)
{
switch (noteFunction)
{
case "CameraShakeEvent":
TriggerCameraShake();
break;
case "FlashBlinkEvent":
TriggerFlashBlink();
break;
case "CameraScaleEvent":
TriggerCameraDash();
break;
case "CameraWaveFX":
TriggerCameraWaveFx();
break;
case "CameraGlitchFX":
TriggerCameraGlitchFx();
break;
case "CameraMonochromeFX":
TriggerCameraMonochromeFx();
break;
case "ScreenshotScale":
TriggerScreenshotScaleFx();
break;
}
}
private void TriggerCameraShake()
{
CacheCameraTransform();
if (cachedCameraTransform == null)
return;
float amplitudeValue = Mathf.Max(0f, cameraShakeAmplitude);
if (amplitudeValue <= 0f)
return;
SmoothShake shake = cachedCameraTransform.GetComponent<SmoothShake>();
if (shake == null)
{
shake = cachedCameraTransform.gameObject.AddComponent<SmoothShake>();
}
if (shake.positionShake == null) shake.positionShake = new Shaker();
if (shake.rotationShake == null) shake.rotationShake = new Shaker();
shake.positionShake.noiseType = Shaker.NoiseType.SineWave;
shake.positionShake.amplitude = new Vector3(amplitudeValue, amplitudeValue, 0f);
shake.positionShake.frequency = new Vector3(18f, 18f, 0f);
shake.rotationShake.amplitude = Vector3.zero;
shake.rotationShake.frequency = Vector3.zero;
shake.timeSettings.constantShake = false;
shake.timeSettings.fadeInDuration = 0.02f;
shake.timeSettings.holdDuration = 0.04f;
shake.timeSettings.fadeOutDuration = 0.05f;
if (shake.timeSettings.fadeInCurve == null || shake.timeSettings.fadeInCurve.length == 0)
shake.timeSettings.fadeInCurve = AnimationCurve.Linear(0f, 0f, 1f, 1f);
if (shake.timeSettings.fadeOutCurve == null || shake.timeSettings.fadeOutCurve.length == 0)
shake.timeSettings.fadeOutCurve = AnimationCurve.Linear(0f, 1f, 1f, 0f);
shake.shakers = new[] { shake.positionShake, shake.rotationShake };
shake.sum = new Vector3[2];
shake.StartShake();
}
private void TriggerFlashBlink()
{
if (flashBlinkImage == null)
return;
if (flashBlinkCoroutine != null)
{
StopCoroutine(flashBlinkCoroutine);
}
flashBlinkCoroutine = StartCoroutine(FlashBlinkCoroutine());
}
private IEnumerator FlashBlinkCoroutine()
{
SetFlashImageAlpha(1f);
float duration = Mathf.Max(0.01f, flashBlinkFadeDuration);
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = Mathf.Clamp01(elapsed / duration);
SetFlashImageAlpha(1f - t);
yield return null;
}
SetFlashImageAlpha(0f);
flashBlinkCoroutine = null;
}
private void TriggerCameraDash()
{
CacheCameraTransform();
if (cachedCameraTransform == null)
return;
if (cameraDashCoroutine != null)
{
StopCoroutine(cameraDashCoroutine);
if (hasCameraBaseLocalZ)
{
Vector3 resetPosition = cachedCameraTransform.localPosition;
resetPosition.z = cameraBaseLocalZ;
cachedCameraTransform.localPosition = resetPosition;
}
}
cameraDashCoroutine = StartCoroutine(CameraDashCoroutine());
}
private IEnumerator CameraDashCoroutine()
{
if (!hasCameraBaseLocalZ)
{
cameraBaseLocalZ = cachedCameraTransform.localPosition.z;
hasCameraBaseLocalZ = true;
}
float baseLocalZ = cameraBaseLocalZ;
float targetZ = targetCameraZPosition;
if (Mathf.Approximately(targetZ, baseLocalZ))
{
targetZ = baseLocalZ + 1.5f;
}
float duration = Mathf.Max(0.01f, cameraDashDuration);
AnimationCurve curve = cameraDashCurve == null || cameraDashCurve.length == 0
? AnimationCurve.EaseInOut(0f, 0f, 1f, 1f)
: cameraDashCurve;
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = Mathf.Clamp01(elapsed / duration);
float curvedT = curve.Evaluate(t);
Vector3 position = cachedCameraTransform.localPosition;
position.z = Mathf.LerpUnclamped(baseLocalZ, targetZ, curvedT);
cachedCameraTransform.localPosition = position;
yield return null;
}
elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = Mathf.Clamp01(elapsed / duration);
float curvedT = curve.Evaluate(t);
Vector3 position = cachedCameraTransform.localPosition;
position.z = Mathf.LerpUnclamped(targetZ, baseLocalZ, curvedT);
cachedCameraTransform.localPosition = position;
yield return null;
}
Vector3 finalPosition = cachedCameraTransform.localPosition;
finalPosition.z = baseLocalZ;
cachedCameraTransform.localPosition = finalPosition;
cameraDashCoroutine = null;
}
private void TriggerCameraWaveFx()
{
GlobalDistortionRuntimeController.TriggerWaveEffect(
Mathf.Max(0.0001f, cameraWaveEnterDuration),
Mathf.Max(0.0001f, cameraWaveRecoverDuration)
);
}
private void TriggerCameraGlitchFx()
{
GlobalGlitchRuntimeController.TriggerWaveEffect(
Mathf.Max(0.0001f, cameraGlitchEnterDuration),
Mathf.Max(0.0001f, cameraGlitchRecoverDuration)
);
}
private void TriggerCameraMonochromeFx()
{
GlobalMonochromeRuntimeController.Trigger(
Mathf.Max(0.0001f, cameraMonochromeEnterDuration),
Mathf.Max(0.0001f, cameraMonochromeRecoverDuration)
);
}
private void TriggerScreenshotScaleFx()
{
GlobalAfterimageRuntimeController.Trigger(
Mathf.Clamp01(screenshotScaleStartOpacity),
Mathf.Max(0.0001f, screenshotScaleFadeDuration),
Mathf.Max(1f, screenshotScaleEndScale)
);
}
private void SetFlashImageAlpha(float alpha)
{
if (flashBlinkImage == null)
return;
Color color = flashBlinkImage.color;
color.a = Mathf.Clamp01(alpha);
flashBlinkImage.color = color;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7aad6dbb8ac9a574b832837614506af8
@@ -28,11 +28,19 @@ public class iNumberPrefabController : MonoBehaviour
private bool _warnedMissingPrefab;
private bool _warnedMissingParents;
private Transform pooledRoot;
private readonly System.Collections.Generic.Dictionary<GameObject, System.Collections.Generic.Stack<playerInstantNumbersPrefab>> pools
= new System.Collections.Generic.Dictionary<GameObject, System.Collections.Generic.Stack<playerInstantNumbersPrefab>>();
private readonly System.Collections.Generic.Dictionary<playerInstantNumbersPrefab, GameObject> sourcePrefabs
= new System.Collections.Generic.Dictionary<playerInstantNumbersPrefab, GameObject>();
private void Awake()
{
if (Instance == null) Instance = this;
else if (Instance != this) { Destroy(gameObject); return; }
pooledRoot = new GameObject("__instant_number_pool").transform;
pooledRoot.SetParent(transform, false);
}
private void Start()
@@ -150,19 +158,76 @@ public class iNumberPrefabController : MonoBehaviour
return;
}
var go = Instantiate(prefab, parent, false);
if (go == null) return;
var view = GetPooledInstance(prefab, parent);
if (view != null) view.Play(type, signedValue);
}
if (prefab.scene.IsValid())
private playerInstantNumbersPrefab GetPooledInstance(GameObject prefab, Transform parent)
{
if (prefab == null || parent == null)
return null;
if (!pools.TryGetValue(prefab, out var pool))
{
if (prefab.activeSelf) prefab.SetActive(false);
pool = new System.Collections.Generic.Stack<playerInstantNumbersPrefab>();
pools[prefab] = pool;
}
// Ensure the newly created object is active before playing animation
if (!go.activeInHierarchy) go.SetActive(true);
playerInstantNumbersPrefab view = null;
while (pool.Count > 0 && view == null)
{
view = pool.Pop();
}
var view = go.GetComponent<playerInstantNumbersPrefab>();
if (view != null) view.Play(type, signedValue);
if (view == null)
{
var go = Instantiate(prefab, parent, false);
if (go == null)
return null;
view = go.GetComponent<playerInstantNumbersPrefab>();
if (view == null)
return null;
sourcePrefabs[view] = prefab;
}
else
{
view.transform.SetParent(parent, false);
view.gameObject.SetActive(true);
}
view.ResetForReuse();
return view;
}
public static bool ReturnInstance(playerInstantNumbersPrefab view)
{
if (Instance == null || view == null)
return false;
return Instance.ReturnInstanceInternal(view);
}
private bool ReturnInstanceInternal(playerInstantNumbersPrefab view)
{
if (view == null)
return false;
if (!sourcePrefabs.TryGetValue(view, out var prefab) || prefab == null)
return false;
if (!pools.TryGetValue(prefab, out var pool))
{
pool = new System.Collections.Generic.Stack<playerInstantNumbersPrefab>();
pools[prefab] = pool;
}
view.transform.SetParent(pooledRoot, false);
view.ResetForReuse();
view.gameObject.SetActive(false);
pool.Push(view);
return true;
}
private void WarnMissingPrefabOnce()
@@ -51,14 +51,33 @@ public class playerInstantNumbersPrefab : MonoBehaviour
_canvasGroup.alpha = 1f;
}
private void OnDisable()
{
if (_animationCoroutine != null)
{
StopCoroutine(_animationCoroutine);
_animationCoroutine = null;
}
}
public void Play(iNumberPrefabController.InstantNumberType type, int signedValue)
{
ResetForReuse();
ApplyVisual(type, signedValue);
if (_animationCoroutine != null) StopCoroutine(_animationCoroutine);
_animationCoroutine = StartCoroutine(Animate());
}
public void ResetForReuse()
{
if (_canvasGroup != null) _canvasGroup.alpha = 1f;
transform.localRotation = Quaternion.identity;
transform.localScale = Vector3.one;
if (_rectTransform != null) _rectTransform.anchoredPosition = Vector2.zero;
else transform.localPosition = Vector3.zero;
}
private void ApplyVisual(iNumberPrefabController.InstantNumberType type, int signedValue)
{
if (numberText != null)
@@ -178,6 +197,10 @@ public class playerInstantNumbersPrefab : MonoBehaviour
yield return null;
}
Destroy(gameObject);
_animationCoroutine = null;
if (!iNumberPrefabController.ReturnInstance(this))
{
Destroy(gameObject);
}
}
}
@@ -524,6 +524,8 @@ public class settlementController : MonoBehaviour
targetTotalScore = targetPmScore + targetIdolScore;
targetTotalPercent = (float)targetTotalScore / Mathf.Max(1, _1000000) * 100f;
PlayerSkillService.NotifySettlementCompleted(targetIdolScore);
finalScore_Text.text = targetTotalScore.ToString();
pmScoreSum_Text.text = targetPmScore.ToString();