技能主要更新,修复卡顿并加入动画,以及各种其他更新。

This commit is contained in:
FloatGaming
2026-02-07 21:05:17 +08:00
parent abeca51be5
commit 1ac3cd0104
1349 changed files with 1526749 additions and 24850 deletions
+59
View File
@@ -0,0 +1,59 @@
using UnityEngine;
public class BgmGyroRotator : MonoBehaviour
{
public RectTransform target;
public float maxRotation = 3f;
public float rotX = 1f;
public float rotY = 1f;
public float rotZ = 0.35f;
public float smooth = 8f;
public bool useUnscaled = true;
private Quaternion baseRotation;
private bool baseCaptured = false;
private void Awake()
{
if (target == null) target = transform as RectTransform;
CaptureBase();
}
private void OnEnable()
{
if (target == null) target = transform as RectTransform;
CaptureBase();
}
private void CaptureBase()
{
if (target == null) return;
baseRotation = target.localRotation;
baseCaptured = true;
}
private void Update()
{
if (target == null) return;
if (!baseCaptured) CaptureBase();
Vector2 norm = GetMouseNormalized();
Vector3 delta = new Vector3(-norm.y * maxRotation * rotX,
norm.x * maxRotation * rotY,
norm.x * maxRotation * rotZ);
Quaternion targetRot = baseRotation * Quaternion.Euler(delta);
float dt = useUnscaled ? Time.unscaledDeltaTime : Time.deltaTime;
float t = 1f - Mathf.Exp(-Mathf.Max(0.01f, smooth) * dt);
target.localRotation = Quaternion.Slerp(target.localRotation, targetRot, t);
}
private static Vector2 GetMouseNormalized()
{
Vector2 pos = Input.mousePosition;
if (Screen.width <= 1 || Screen.height <= 1)
return Vector2.zero;
float x = (pos.x / Screen.width) * 2f - 1f;
float y = (pos.y / Screen.height) * 2f - 1f;
return new Vector2(Mathf.Clamp(x, -1f, 1f), Mathf.Clamp(y, -1f, 1f));
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 17e6aaba9c8c0c84cbbeaedff83ef6f2
+430
View File
@@ -0,0 +1,430 @@
using UnityEngine;
using UnityEngine.UI;
using System.IO;
public class BgmParticleEmitter : MonoBehaviour
{
[Header("Audio")]
public AudioSource source;
public FFTWindow fftWindow = FFTWindow.BlackmanHarris;
[Header("Rendering")]
public Material particleMaterial;
public Texture2D particleTexture;
public string particleTextureRelativePath = "artworks/UI_UI/新版主界面 (2)/矩形 2-1.png";
public string fallbackTextureRelativePath = "artworks/UI_UI/新版主界面 (2)/矩形 2-1.png";
public bool useTextureAlphaOnly = true;
[Header("Emission")]
public int spectrumSize = 256;
public float minRate = 2f;
public float maxRate = 30f;
public float minSpeed = 25f;
public float maxSpeed = 80f;
public float minSize = 4f;
public float maxSize = 8f;
public float energyScale = 50f;
public float startLifetime = 34.2f;
[Header("Rotation Drift")]
public Vector2 startRotationRange = new Vector2(0f, 360f);
public Vector2 angularVelocityRange = new Vector2(3f, 12f);
[Header("Spawn Area")]
public float spawnMargin = 40f;
public float spawnHeight = 10f;
private ParticleSystem mainPs;
private float[] spectrum;
private RectTransform canvasRect;
private static Material defaultParticleMat;
private static Texture2D circleTex;
private Material runtimeMat;
private Vector2 lastCanvasSize;
private bool hasLastCanvasSize;
private float lastSpawnMargin;
private float lastSpawnHeight;
private bool spawnAreaInitialized;
private void Awake()
{
BuildIfNeeded();
}
private void OnEnable()
{
BuildIfNeeded();
}
private void BuildIfNeeded()
{
if (mainPs == null)
mainPs = GetComponent<ParticleSystem>();
if (mainPs == null)
{
mainPs = gameObject.AddComponent<ParticleSystem>();
}
if (particleTexture == null)
particleTexture = LoadTextureFromFile();
#if UNITY_EDITOR
if (particleTexture == null)
particleTexture = LoadTextureFromAsset();
#endif
if (particleTexture != null && useTextureAlphaOnly)
particleTexture = ConvertTextureToWhiteAlpha(particleTexture);
if (particleTexture == null)
particleTexture = GetCircleTexture();
ConfigureSystem(mainPs, Color.white, 10);
var shadow = transform.Find("Shadow");
if (shadow != null)
{
if (Application.isPlaying)
Destroy(shadow.gameObject);
else
DestroyImmediate(shadow.gameObject);
}
if (canvasRect == null)
{
var canvas = GetComponentInParent<Canvas>();
if (canvas != null)
canvasRect = canvas.GetComponent<RectTransform>();
}
if (spectrum == null || spectrum.Length != spectrumSize)
spectrum = new float[spectrumSize];
}
private void Update()
{
UpdateSpawnArea();
if (!Application.isPlaying)
return;
if (source == null)
{
var mgr = BgmPlaybackManager.Instance;
if (mgr != null) source = mgr.audioSource;
}
if (source == null || source.clip == null)
return;
if (spectrum == null || spectrum.Length != spectrumSize)
spectrum = new float[spectrumSize];
source.GetSpectrumData(spectrum, 0, fftWindow);
float energy = 0f;
int start = 2;
int end = Mathf.Min(64, spectrumSize - 1);
for (int i = start; i <= end; i++)
energy += spectrum[i];
energy /= Mathf.Max(1, end - start + 1);
float level = Mathf.Clamp01(energy * Mathf.Max(1f, energyScale));
float rate = Mathf.Lerp(minRate, maxRate, level);
float speed = Mathf.Lerp(minSpeed, maxSpeed, level);
float size = Mathf.Lerp(minSize, maxSize, level);
ApplyRuntimeParams(mainPs, rate, speed, size);
}
private void ConfigureSystem(ParticleSystem ps, Color color, int order)
{
var main = ps.main;
main.loop = true;
main.playOnAwake = true;
main.startLifetime = startLifetime;
main.startSpeed = minSpeed;
main.startSize = minSize;
main.startColor = color;
main.simulationSpace = ParticleSystemSimulationSpace.Local;
main.scalingMode = ParticleSystemScalingMode.Hierarchy;
main.maxParticles = 2000;
main.startRotation3D = true;
main.startRotationX = new ParticleSystem.MinMaxCurve(startRotationRange.x * Mathf.Deg2Rad, startRotationRange.y * Mathf.Deg2Rad);
main.startRotationY = new ParticleSystem.MinMaxCurve(startRotationRange.x * Mathf.Deg2Rad, startRotationRange.y * Mathf.Deg2Rad);
main.startRotationZ = new ParticleSystem.MinMaxCurve(startRotationRange.x * Mathf.Deg2Rad, startRotationRange.y * Mathf.Deg2Rad);
var emission = ps.emission;
emission.rateOverTime = minRate;
var shape = ps.shape;
shape.enabled = true;
shape.shapeType = ParticleSystemShapeType.Box;
shape.scale = new Vector3(200f, spawnHeight, 0f);
var vel = ps.velocityOverLifetime;
vel.enabled = true;
vel.space = ParticleSystemSimulationSpace.Local;
vel.x = new ParticleSystem.MinMaxCurve(-5f, 5f);
vel.y = new ParticleSystem.MinMaxCurve(-minSpeed, -minSpeed * 1.2f);
vel.z = new ParticleSystem.MinMaxCurve(0f, 0f);
var renderer = ps.GetComponent<ParticleSystemRenderer>();
renderer.sortingOrder = order;
renderer.renderMode = ParticleSystemRenderMode.Billboard;
var mat = GetParticleMaterial();
renderer.material = mat;
renderer.sharedMaterial = mat;
var rot = ps.rotationOverLifetime;
rot.enabled = true;
rot.separateAxes = true;
rot.x = new ParticleSystem.MinMaxCurve(angularVelocityRange.x * Mathf.Deg2Rad, angularVelocityRange.y * Mathf.Deg2Rad);
rot.y = new ParticleSystem.MinMaxCurve(angularVelocityRange.x * Mathf.Deg2Rad, angularVelocityRange.y * Mathf.Deg2Rad);
rot.z = new ParticleSystem.MinMaxCurve(angularVelocityRange.x * Mathf.Deg2Rad, angularVelocityRange.y * Mathf.Deg2Rad);
}
private void ApplyRuntimeParams(ParticleSystem ps, float rate, float speed, float size)
{
if (ps == null) return;
var main = ps.main;
main.startSpeed = speed;
main.startSize = size;
main.startLifetime = startLifetime;
var emission = ps.emission;
emission.rateOverTime = rate;
var vel = ps.velocityOverLifetime;
vel.x = new ParticleSystem.MinMaxCurve(-5f, 5f);
vel.y = new ParticleSystem.MinMaxCurve(-speed, -speed * 1.2f);
vel.z = new ParticleSystem.MinMaxCurve(0f, 0f);
}
private void UpdateSpawnArea()
{
var canvas = GetComponentInParent<Canvas>();
if (canvas != null)
{
var rt = canvas.GetComponent<RectTransform>();
if (canvasRect == null || canvasRect != rt)
canvasRect = rt;
}
Vector2 size = canvasRect != null ? canvasRect.rect.size : new Vector2(Screen.width, Screen.height);
bool sizeChanged = !hasLastCanvasSize || size != lastCanvasSize;
bool marginChanged = !spawnAreaInitialized || !Mathf.Approximately(lastSpawnMargin, spawnMargin);
bool heightChanged = !spawnAreaInitialized || !Mathf.Approximately(lastSpawnHeight, spawnHeight);
if (!sizeChanged && !marginChanged && !heightChanged)
return;
hasLastCanvasSize = true;
lastCanvasSize = size;
lastSpawnMargin = spawnMargin;
lastSpawnHeight = spawnHeight;
spawnAreaInitialized = true;
var shape = mainPs.shape;
shape.scale = new Vector3(size.x, spawnHeight, 0f);
var rect = transform as RectTransform;
if (rect != null)
{
if (rect.anchorMin != new Vector2(0.5f, 1f)) rect.anchorMin = new Vector2(0.5f, 1f);
if (rect.anchorMax != new Vector2(0.5f, 1f)) rect.anchorMax = new Vector2(0.5f, 1f);
if (rect.pivot != new Vector2(0.5f, 0.5f)) rect.pivot = new Vector2(0.5f, 0.5f);
if (rect.anchoredPosition != new Vector2(0f, spawnMargin)) rect.anchoredPosition = new Vector2(0f, spawnMargin);
if (rect.localScale != Vector3.one) rect.localScale = Vector3.one;
}
else
{
var targetPos = new Vector3(0f, size.y * 0.5f + spawnMargin, 0f);
if (transform.localPosition != targetPos) transform.localPosition = targetPos;
if (transform.localScale != Vector3.one) transform.localScale = Vector3.one;
}
}
public void ForceLayoutUpdate()
{
UpdateSpawnArea();
}
private static Material GetDefaultParticleMaterial()
{
if (defaultParticleMat == null)
{
defaultParticleMat = Resources.GetBuiltinResource<Material>("Default-Particle.mat");
if (defaultParticleMat == null || defaultParticleMat.shader == null || !defaultParticleMat.shader.isSupported)
{
Shader shader = Shader.Find("Particles/Standard Unlit");
if (shader == null) shader = Shader.Find("Universal Render Pipeline/Particles/Unlit");
if (shader == null) shader = Shader.Find("Particles/Standard");
if (shader == null) shader = Shader.Find("Unlit/Texture");
if (shader != null)
defaultParticleMat = new Material(shader);
}
if (defaultParticleMat != null && defaultParticleMat.mainTexture == null)
defaultParticleMat.mainTexture = GetCircleTexture();
}
return defaultParticleMat;
}
private Material GetParticleMaterial()
{
Material baseMat = GetSafeParticleMaterial();
if (baseMat == null)
baseMat = CreateFallbackMaterial();
if (baseMat == null)
baseMat = GetDefaultParticleMaterial();
if (baseMat == null)
baseMat = CreateFallbackMaterial();
if (baseMat != null && baseMat.mainTexture == null && particleTexture == null)
baseMat = CreateFallbackMaterial();
if (runtimeMat == null || runtimeMat.shader != baseMat.shader)
runtimeMat = new Material(baseMat);
if (particleTexture != null)
runtimeMat.mainTexture = particleTexture;
else if (runtimeMat.mainTexture == null)
runtimeMat.mainTexture = GetCircleTexture();
if (runtimeMat.HasProperty("_Color"))
runtimeMat.SetColor("_Color", Color.white);
if (runtimeMat.HasProperty("_TintColor"))
runtimeMat.SetColor("_TintColor", Color.white);
if (runtimeMat.HasProperty("_BaseColor"))
runtimeMat.SetColor("_BaseColor", Color.white);
if (runtimeMat.HasProperty("_MainTex"))
runtimeMat.SetTexture("_MainTex", runtimeMat.mainTexture);
if (runtimeMat.HasProperty("_BaseMap"))
runtimeMat.SetTexture("_BaseMap", runtimeMat.mainTexture);
return runtimeMat;
}
private Material CreateFallbackMaterial()
{
Shader shader = Shader.Find("Universal Render Pipeline/Particles/Unlit");
if (shader == null) shader = Shader.Find("Particles/Standard Unlit");
if (shader == null) shader = Shader.Find("Particles/Standard");
if (shader == null) shader = Shader.Find("Unlit/Transparent");
if (shader == null) shader = Shader.Find("Unlit/Texture");
if (shader == null) return null;
return new Material(shader);
}
private Material GetSafeParticleMaterial()
{
if (particleMaterial == null) return null;
Shader shader = particleMaterial.shader;
if (shader == null) return null;
string name = shader.name;
if (string.IsNullOrEmpty(name)) return null;
string lower = name.ToLowerInvariant();
if (lower.Contains("particle"))
return particleMaterial;
return null;
}
private static Texture2D GetCircleTexture()
{
if (circleTex != null)
return circleTex;
const int size = 32;
circleTex = new Texture2D(size, size, TextureFormat.ARGB32, false);
circleTex.wrapMode = TextureWrapMode.Clamp;
circleTex.filterMode = FilterMode.Bilinear;
Vector2 center = new Vector2((size - 1) * 0.5f, (size - 1) * 0.5f);
float radius = size * 0.5f * 0.9f;
for (int y = 0; y < size; y++)
{
for (int x = 0; x < size; x++)
{
float dist = Vector2.Distance(new Vector2(x, y), center);
float t = Mathf.InverseLerp(radius * 0.9f, radius, dist);
float alpha = Mathf.Clamp01(1f - t);
circleTex.SetPixel(x, y, new Color(1f, 1f, 1f, alpha));
}
}
circleTex.Apply();
return circleTex;
}
private Texture2D LoadTextureFromFile()
{
string relPath = particleTextureRelativePath;
if (string.IsNullOrEmpty(relPath))
relPath = fallbackTextureRelativePath;
if (string.IsNullOrEmpty(relPath))
return null;
string path = Path.Combine(Application.dataPath, relPath);
if (!File.Exists(path) && !string.IsNullOrEmpty(fallbackTextureRelativePath))
{
string fallback = Path.Combine(Application.dataPath, fallbackTextureRelativePath);
if (File.Exists(fallback))
{
particleTextureRelativePath = fallbackTextureRelativePath;
path = fallback;
}
}
if (!File.Exists(path))
return null;
var data = File.ReadAllBytes(path);
var tex = new Texture2D(2, 2, TextureFormat.ARGB32, false);
if (tex.LoadImage(data))
return tex;
return null;
}
private static Texture2D ConvertTextureToWhiteAlpha(Texture2D src)
{
if (src == null) return null;
try
{
var pixels = src.GetPixels32();
if (pixels == null || pixels.Length == 0) return src;
var tex = new Texture2D(src.width, src.height, TextureFormat.ARGB32, false);
for (int i = 0; i < pixels.Length; i++)
{
byte a = pixels[i].a;
pixels[i] = new Color32(255, 255, 255, a);
}
tex.SetPixels32(pixels);
tex.Apply();
tex.wrapMode = TextureWrapMode.Clamp;
tex.filterMode = FilterMode.Bilinear;
return tex;
}
catch
{
return src;
}
}
#if UNITY_EDITOR
private Texture2D LoadTextureFromAsset()
{
string relPath = particleTextureRelativePath;
if (string.IsNullOrEmpty(relPath))
relPath = fallbackTextureRelativePath;
if (string.IsNullOrEmpty(relPath))
return null;
string assetPath = "Assets/" + relPath.TrimStart('/', '\\');
return UnityEditor.AssetDatabase.LoadAssetAtPath<Texture2D>(assetPath);
}
#endif
#if UNITY_EDITOR
private void OnValidate()
{
if (particleTexture == null)
{
const string path = "Assets/artworks/UI_UI/新版主界面 (2)/矩形 2-1.png";
particleTexture = UnityEditor.AssetDatabase.LoadAssetAtPath<Texture2D>(path);
}
if (runtimeMat != null && particleTexture != null)
runtimeMat.mainTexture = particleTexture;
}
#endif
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c4276f055c7596a4b8edd5b90b320148
+231
View File
@@ -0,0 +1,231 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;
public class BgmPlaybackManager : MonoBehaviour
{
public static BgmPlaybackManager Instance { get; private set; }
[Header("Audio")]
public AudioSource audioSource;
public AudioClip clip;
[Tooltip("Optional playlist. If empty, will load all clips in Resources/BGM.")]
public List<AudioClip> playlist = new List<AudioClip>();
public int startIndex = 0;
[Tooltip("Resources path without extension. Example: BGM/Bansonic OST")]
public string resourcesPath = "BGM/Bansonic OST";
[Tooltip("Optional absolute file path to mp3/wav (fallback).")]
public string fallbackFilePath = "";
public bool loop = true;
public bool autoPlay = true;
public string gameplaySceneName = "gamePlay_gamePlay";
public bool stopOnGameplayScene = true;
private bool isLoading = false;
private int currentIndex = 0;
public static BgmPlaybackManager EnsureInstance()
{
if (Instance != null) return Instance;
var go = new GameObject("BGM_PlaybackManager");
Instance = go.AddComponent<BgmPlaybackManager>();
return Instance;
}
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
if (audioSource == null)
audioSource = gameObject.AddComponent<AudioSource>();
audioSource.playOnAwake = false;
audioSource.loop = loop;
if (clip == null)
clip = audioSource.clip;
EnsurePlaylist();
if (playlist.Count > 0)
{
currentIndex = Mathf.Clamp(startIndex, 0, playlist.Count - 1);
clip = playlist[currentIndex];
}
if (clip != null)
audioSource.clip = clip;
if (autoPlay)
EnsurePlaying();
}
private void OnEnable()
{
SceneManager.sceneLoaded += OnSceneLoaded;
}
private void OnDisable()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if (!stopOnGameplayScene)
return;
if (scene.name == gameplaySceneName)
StopLobbyAudioAndParticles();
}
public void EnsurePlaying()
{
if (audioSource == null)
audioSource = gameObject.AddComponent<AudioSource>();
audioSource.loop = loop;
if (audioSource.clip == null && clip != null)
audioSource.clip = clip;
if (audioSource.clip == null)
{
EnsurePlaylist();
if (playlist.Count > 0)
{
currentIndex = Mathf.Clamp(currentIndex, 0, playlist.Count - 1);
audioSource.clip = playlist[currentIndex];
}
}
if (audioSource.clip == null && !isLoading)
{
StartCoroutine(LoadClipRoutine());
return;
}
if (audioSource.clip != null && !audioSource.isPlaying)
audioSource.Play();
}
private IEnumerator LoadClipRoutine()
{
isLoading = true;
// Try Resources first
if (audioSource.clip == null && !string.IsNullOrEmpty(resourcesPath))
{
var res = Resources.Load<AudioClip>(resourcesPath);
if (res != null)
{
audioSource.clip = res;
}
}
// Fallback to file path if still missing
if (audioSource.clip == null)
{
string path = fallbackFilePath;
if (string.IsNullOrEmpty(path))
{
// Default to project root /??/Bansonic OST.mp3
var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
path = Path.Combine(projectRoot, "??", "Bansonic OST.mp3");
}
if (File.Exists(path))
{
string uri = new System.Uri(path).AbsoluteUri;
using (var req = UnityWebRequestMultimedia.GetAudioClip(uri, AudioType.MPEG))
{
yield return req.SendWebRequest();
if (req.result == UnityWebRequest.Result.Success)
{
audioSource.clip = DownloadHandlerAudioClip.GetContent(req);
}
}
}
}
isLoading = false;
if (autoPlay && audioSource.clip != null && !audioSource.isPlaying)
audioSource.Play();
}
private void EnsurePlaylist()
{
if (playlist == null)
playlist = new List<AudioClip>();
if (playlist.Count == 0)
{
var clips = Resources.LoadAll<AudioClip>("BGM");
if (clips != null && clips.Length > 0)
{
Array.Sort(clips, (a, b) => string.CompareOrdinal(a.name, b.name));
playlist.AddRange(clips);
}
}
}
public void PlayIndex(int index)
{
EnsurePlaylist();
if (playlist == null || playlist.Count == 0)
return;
currentIndex = (index % playlist.Count + playlist.Count) % playlist.Count;
audioSource.clip = playlist[currentIndex];
clip = audioSource.clip;
audioSource.loop = loop;
audioSource.time = 0f;
audioSource.Play();
}
public void Next()
{
EnsurePlaylist();
if (playlist == null || playlist.Count == 0)
return;
PlayIndex(currentIndex + 1);
}
public void Previous()
{
EnsurePlaylist();
if (playlist == null || playlist.Count == 0)
return;
PlayIndex(currentIndex - 1);
}
public void TogglePause()
{
if (audioSource == null || audioSource.clip == null)
return;
if (audioSource.isPlaying)
audioSource.Pause();
else
audioSource.Play();
}
private void StopLobbyAudioAndParticles()
{
if (audioSource != null)
audioSource.Stop();
}
public float CurrentTime => audioSource != null ? audioSource.time : 0f;
public float TotalTime => (audioSource != null && audioSource.clip != null) ? audioSource.clip.length : 0f;
public bool IsReady => audioSource != null && audioSource.clip != null;
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 305b5ad12645e8545a89e36bc600b124
@@ -0,0 +1,51 @@
using UnityEngine;
using UnityEngine.SceneManagement;
public static class BgmRuntimeBootstrap
{
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
private static void Bootstrap()
{
var mgr = BgmPlaybackManager.EnsureInstance();
mgr.EnsurePlaying();
// Ensure UI binder exists
if (Object.FindAnyObjectByType<BgmUiBinder>() == null)
{
var go = new GameObject("BGM_UI_Binder");
go.AddComponent<BgmUiBinder>();
Object.DontDestroyOnLoad(go);
}
RemoveBgmParticlesInLoadedScenes();
SceneManager.sceneLoaded -= OnSceneLoaded;
SceneManager.sceneLoaded += OnSceneLoaded;
}
private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
RemoveBgmParticlesInLoadedScenes();
}
private static void RemoveBgmParticlesInLoadedScenes()
{
var emitters = Object.FindObjectsByType<BgmParticleEmitter>(FindObjectsInactive.Include, FindObjectsSortMode.None);
for (int i = 0; i < emitters.Length; i++)
{
var e = emitters[i];
if (e == null) continue;
if (!e.gameObject.scene.IsValid() || !e.gameObject.scene.isLoaded) continue;
Object.Destroy(e.gameObject);
}
var all = Resources.FindObjectsOfTypeAll<Transform>();
for (int i = 0; i < all.Length; i++)
{
var t = all[i];
if (t == null) continue;
if (!t.gameObject.scene.IsValid() || !t.gameObject.scene.isLoaded) continue;
if (t.name.Equals("BgmParticles", System.StringComparison.OrdinalIgnoreCase))
Object.Destroy(t.gameObject);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a470f0e76d5042a43b2919b3c552a5e9
@@ -0,0 +1,245 @@
using UnityEngine;
using UnityEngine.UI;
#if UNITY_EDITOR
using UnityEditor;
#endif
[ExecuteAlways]
public class BgmSpectrumVisualizer : MonoBehaviour
{
[Header("Audio")]
public AudioSource source;
public FFTWindow fftWindow = FFTWindow.BlackmanHarris;
[Header("Bars")]
public RectTransform barPrefab;
public int bars = 64;
public float barWidth = 4.8f;
public float barSpacing = 2f;
public float maxHeight = 100f;
[Header("Spectrum")]
public int spectrumSize = 512;
public float amplitude = 60f;
public float smooth = 12f;
public float noiseFloor = 0.002f;
[Header("Wave")]
public float waveAmplitude = 6f;
public float waveSpeed = 1.6f;
public float waveFrequency = 0.45f;
[Tooltip("If true, bars expand upward from baseline only.")]
public bool growUpOnly = true;
[Header("Edit Preview")]
public bool previewInEditMode = true;
public float previewAmplitude = 0.6f;
public float previewNoiseSpeed = 0.25f;
private float[] spectrum;
private RectTransform[] barRects;
private float[] barHeights;
private Vector2[] barBasePos;
private bool rebuildQueued;
private void Awake()
{
if (Application.isPlaying)
{
Init();
}
else
{
ScheduleRebuild();
}
}
private void OnEnable()
{
if (barRects == null || barRects.Length == 0)
{
if (Application.isPlaying) Init();
else ScheduleRebuild();
}
}
private void OnValidate()
{
if (bars < 1) bars = 1;
if (barWidth < 1f) barWidth = 1f;
if (barSpacing < 0f) barSpacing = 0f;
if (maxHeight < 1f) maxHeight = 1f;
ScheduleRebuild();
}
private void Init()
{
RebuildBars();
}
private void ScheduleRebuild()
{
if (rebuildQueued) return;
rebuildQueued = true;
#if UNITY_EDITOR
EditorApplication.delayCall -= RebuildIfQueued;
EditorApplication.delayCall += RebuildIfQueued;
#endif
}
#if UNITY_EDITOR
private void RebuildIfQueued()
{
if (!rebuildQueued) return;
rebuildQueued = false;
if (this == null) return;
RebuildBars();
if (!Application.isPlaying && previewInEditMode)
UpdateBarsPreview();
}
#endif
private void Update()
{
if (!Application.isPlaying)
{
if (previewInEditMode)
UpdateBarsPreview();
return;
}
if (source == null)
{
var mgr = BgmPlaybackManager.Instance;
if (mgr != null) source = mgr.audioSource;
}
if (source == null || source.clip == null)
return;
if (spectrum == null || spectrum.Length != spectrumSize)
spectrum = new float[spectrumSize];
source.GetSpectrumData(spectrum, 0, fftWindow);
for (int i = 0; i < bars; i++)
{
float t = (float)i / (bars - 1);
int idx = Mathf.Clamp(Mathf.RoundToInt(Mathf.Lerp(1, spectrumSize - 1, t * t)), 1, spectrumSize - 1);
float v = Mathf.Max(0f, spectrum[idx] - noiseFloor) * amplitude;
float baseHeight = Mathf.Clamp01(v) * maxHeight;
float wave = Mathf.Sin(Time.time * waveSpeed + i * waveFrequency) * waveAmplitude;
float target = Mathf.Clamp(baseHeight + wave, 0f, maxHeight);
barHeights[i] = Mathf.Lerp(barHeights[i], target, Time.deltaTime * smooth);
var rt = barRects[i];
float h = Mathf.Max(1f, barHeights[i]);
var sd = rt.sizeDelta;
if (sd.x != barWidth || sd.y != h)
rt.sizeDelta = new Vector2(barWidth, h);
if (growUpOnly && barBasePos != null && i < barBasePos.Length)
{
var ap = rt.anchoredPosition;
float nx = barBasePos[i].x;
float ny = barBasePos[i].y + h * 0.5f;
if (ap.x != nx || ap.y != ny)
rt.anchoredPosition = new Vector2(nx, ny);
}
}
}
private void RebuildBars()
{
if (barPrefab == null)
{
var go = new GameObject("Bar", typeof(RectTransform), typeof(Image), typeof(Outline));
var img = go.GetComponent<Image>();
img.color = Color.white;
var outline = go.GetComponent<Outline>();
outline.effectColor = new Color(0f, 0f, 0f, 1f);
outline.effectDistance = new Vector2(1f, 1f);
outline.useGraphicAlpha = true;
barPrefab = go.GetComponent<RectTransform>();
barPrefab.sizeDelta = new Vector2(barWidth, 10f);
go.SetActive(false);
}
// destroy existing bars
for (int i = transform.childCount - 1; i >= 0; i--)
{
var child = transform.GetChild(i);
if (Application.isPlaying)
Destroy(child.gameObject);
else
DestroyImmediate(child.gameObject);
}
spectrum = new float[Mathf.Max(64, spectrumSize)];
barRects = new RectTransform[bars];
barHeights = new float[bars];
barBasePos = new Vector2[bars];
for (int i = 0; i < bars; i++)
{
var bar = Instantiate(barPrefab, transform);
bar.gameObject.SetActive(true);
barRects[i] = bar;
var outline = bar.GetComponent<Outline>();
if (outline == null)
{
outline = bar.gameObject.AddComponent<Outline>();
outline.effectColor = new Color(0f, 0f, 0f, 1f);
outline.effectDistance = new Vector2(1f, 1f);
outline.useGraphicAlpha = true;
}
barRects[i].sizeDelta = new Vector2(barWidth, 1f);
var basePos = new Vector2(i * (barWidth + barSpacing), 0f);
barRects[i].anchoredPosition = basePos;
barBasePos[i] = basePos;
}
}
private void UpdateBarsPreview()
{
if (barRects == null || barRects.Length == 0)
return;
float time = GetEditorTime();
for (int i = 0; i < bars; i++)
{
float t = (float)i / (bars - 1);
float noise = Mathf.PerlinNoise(t * 2.5f, time * previewNoiseSpeed);
float baseHeight = Mathf.Lerp(0.2f, 1f, noise) * maxHeight * previewAmplitude;
float wave = Mathf.Sin(time * waveSpeed + i * waveFrequency) * waveAmplitude;
float target = Mathf.Clamp(baseHeight + wave, 0f, maxHeight);
barHeights[i] = Mathf.Lerp(barHeights[i], target, 0.2f);
var rt = barRects[i];
float h = Mathf.Max(1f, barHeights[i]);
var sd = rt.sizeDelta;
if (sd.x != barWidth || sd.y != h)
rt.sizeDelta = new Vector2(barWidth, h);
if (growUpOnly && barBasePos != null && i < barBasePos.Length)
{
var ap = rt.anchoredPosition;
float nx = barBasePos[i].x;
float ny = barBasePos[i].y + h * 0.5f;
if (ap.x != nx || ap.y != ny)
rt.anchoredPosition = new Vector2(nx, ny);
}
}
}
private float GetEditorTime()
{
#if UNITY_EDITOR
return (float)EditorApplication.timeSinceStartup;
#else
return Time.realtimeSinceStartup;
#endif
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0e9b030cf9fb62746885cdd04004e33a
+941
View File
@@ -0,0 +1,941 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;
[ExecuteAlways]
public class BgmUiBinder : MonoBehaviour
{
public static BgmUiBinder Instance { get; private set; }
[Header("Lookup Names")]
public string musicRootName = "MusicPic";
public string timeRootName = "TIME";
public string progressName = "BGM_Progress";
public string pointerName = "Zhizhen";
public string spectrumRootName = "SpectrumRoot";
public string musicPicCanvasName = "MusicPicCanvas";
[Header("Buttons")]
public string prevButtonName = "Previous_Song";
public string nextButtonName = "Next_Song";
public string pauseButtonName = "pause_Song";
public string musicToggleButtonName = "Button_Music";
public Button prevButton;
public Button nextButton;
public Button pauseButton;
public Button musicToggleButton;
public bool useManualMusicToggle = true;
public bool bindMusicToggleButton = false;
[Header("Bindings (optional)")]
public Image progressImage;
public Slider progressSlider;
public TMP_Text timeTmp;
public Text timeText;
public RectTransform pointerRect;
public Vector2 pointerOffset = Vector2.zero;
public bool enableMusicPicGyro = false;
[Header("MusicPic Toggle")]
public bool keepMusicPicAcrossScenes = false;
public bool showMusicPicInLobby = false;
public bool forceHideOnSceneLoad = true;
public string lobbySceneName = "UI_UI";
public string gameplaySceneName = "gamePlay_gamePlay";
public float musicPicFadeDuration = 0.3f;
public bool useUnscaledTime = true;
[Header("Auto Create")]
public bool autoCreateProgress = false;
public Vector2 defaultProgressSize = new Vector2(320f, 8f);
public Vector2 defaultProgressPosition = Vector2.zero;
public Color progressColor = Color.white;
public Vector2 pointerStartPos = new Vector2(-190.98f, -87.05f);
public Vector2 pointerEndPos = new Vector2(195.3f, -87.05f);
public Vector2 spectrumPosition = new Vector2(-238.9f, -111.5f);
private readonly List<TMP_Text> timeTmps = new List<TMP_Text>();
private readonly List<Text> timeTexts = new List<Text>();
private System.Text.StringBuilder _sb = new System.Text.StringBuilder(32);
private int _lastSeconds = -1;
private int _lastTotalSeconds = -1;
private RectTransform musicRoot;
private bool hasExplicitMusicRoot = false;
private RectTransform progressRect;
private float pointerBaseY = 0f;
private bool musicPicVisible = true;
private Coroutine musicPicTween;
private Coroutine lobbyShowRoutine;
private CanvasGroup musicPicGroup;
private RectTransform spectrumRoot;
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
}
private string _cachedGameplaySceneName;
private bool _isGameplayScene;
private bool _spectrumHiddenInGameplay = false;
private void OnEnable()
{
SceneManager.sceneLoaded += OnSceneLoaded;
_cachedGameplaySceneName = gameplaySceneName;
_isGameplayScene = SceneManager.GetActiveScene().name == _cachedGameplaySceneName;
_spectrumHiddenInGameplay = false;
_buttonsBound = false;
Bind();
ApplyDefaultMusicPicVisibility(SceneManager.GetActiveScene().name);
if (Application.isPlaying)
{
var mgr = BgmPlaybackManager.EnsureInstance();
mgr.EnsurePlaying();
}
}
private void OnDisable()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
_isGameplayScene = scene.name == _cachedGameplaySceneName;
_spectrumHiddenInGameplay = false;
_buttonsBound = false;
Bind();
ApplyDefaultMusicPicVisibility(scene.name);
}
public void Bind()
{
musicRoot = null;
hasExplicitMusicRoot = false;
Canvas sceneCanvas = FindSceneCanvas();
var controller = Object.FindAnyObjectByType<btmandtopController>();
if (controller != null && controller.musicPicRoot != null)
{
SetMusicRoot(controller.musicPicRoot, sceneCanvas);
return;
}
RectTransform persistentMusic = null;
var persistentCanvasGo = GameObject.Find(musicPicCanvasName);
if (!keepMusicPicAcrossScenes && persistentCanvasGo != null)
{
if (Application.isPlaying)
Destroy(persistentCanvasGo);
else
DestroyImmediate(persistentCanvasGo);
persistentCanvasGo = null;
}
if (persistentCanvasGo != null)
{
var candidate = persistentCanvasGo.transform.Find(musicRootName);
if (candidate != null)
persistentMusic = candidate as RectTransform;
}
var candidates = FindAllMusicPic();
RectTransform sceneMusic = null;
var activeScene = SceneManager.GetActiveScene();
for (int i = 0; i < candidates.Count; i++)
{
var c = candidates[i];
if (c == null || c.gameObject.scene != activeScene)
continue;
if (sceneMusic == null)
sceneMusic = c;
if (c.GetComponentInParent<btmandtopController>(true) != null)
{
sceneMusic = c;
break;
}
}
if (persistentMusic != null)
musicRoot = persistentMusic;
else
musicRoot = sceneMusic;
hasExplicitMusicRoot = musicRoot != null;
if (candidates.Count > 1)
{
for (int i = 0; i < candidates.Count; i++)
{
var c = candidates[i];
if (c == null) continue;
if (c == musicRoot) continue;
if (Application.isPlaying)
Destroy(c.gameObject);
else
DestroyImmediate(c.gameObject);
}
}
if (musicRoot == null)
{
var canvas = Object.FindAnyObjectByType<Canvas>();
if (canvas != null)
musicRoot = canvas.GetComponent<RectTransform>();
}
if (hasExplicitMusicRoot)
{
EnsureMusicPicCanvas(sceneCanvas);
EnsureMusicPicCanvasGroup();
if (Application.isPlaying && forceHideOnSceneLoad)
SetMusicPicVisible(false, true);
}
if (progressSlider == null && progressImage == null)
{
if (musicRoot != null)
{
var target = musicRoot.Find(progressName);
if (target != null)
{
progressSlider = target.GetComponent<Slider>();
progressImage = target.GetComponent<Image>();
}
}
}
if (autoCreateProgress && progressSlider == null && progressImage == null && musicRoot != null)
{
var go = new GameObject(progressName, typeof(RectTransform), typeof(Image));
var rt = go.GetComponent<RectTransform>();
rt.SetParent(musicRoot, false);
rt.sizeDelta = defaultProgressSize;
rt.anchoredPosition = defaultProgressPosition;
var img = go.GetComponent<Image>();
img.type = Image.Type.Filled;
img.fillMethod = Image.FillMethod.Horizontal;
img.fillOrigin = 0;
img.fillAmount = 0f;
img.color = progressColor;
progressImage = img;
}
// If a progress bar exists outside MusicPic, ignore it and let cleanup remove it.
if (musicRoot != null)
{
if (progressSlider != null && !progressSlider.transform.IsChildOf(musicRoot))
progressSlider = null;
if (progressImage != null && !progressImage.transform.IsChildOf(musicRoot))
progressImage = null;
}
// Cleanup any BGM_Progress that isn't under MusicPic
CleanupProgressOutsideMusicRoot();
progressRect = null;
if (progressSlider != null) progressRect = progressSlider.GetComponent<RectTransform>();
if (progressRect == null && progressImage != null) progressRect = progressImage.rectTransform;
if (pointerRect == null)
{
Transform pointer = null;
if (musicRoot != null)
pointer = musicRoot.Find(pointerName);
if (pointer == null)
{
var go = FindByNameContains(pointerName);
if (go != null) pointer = go.transform;
}
if (pointer != null)
pointerRect = pointer.GetComponent<RectTransform>();
}
if (pointerRect != null)
{
pointerBaseY = pointerRect.anchoredPosition.y;
pointerRect.anchoredPosition = pointerStartPos;
pointerBaseY = pointerStartPos.y;
}
if (enableMusicPicGyro && musicRoot != null)
{
var gyro = musicRoot.GetComponent<BgmGyroRotator>();
if (gyro == null) gyro = musicRoot.gameObject.AddComponent<BgmGyroRotator>();
gyro.target = musicRoot;
}
BindButtons();
EnsureSpectrum();
CacheTimeTargets();
HideSpectrumInGameplay(SceneManager.GetActiveScene().name);
}
public void SetMusicRoot(RectTransform root, Canvas sceneCanvas = null)
{
musicRoot = root;
hasExplicitMusicRoot = musicRoot != null;
if (!hasExplicitMusicRoot)
return;
EnsureMusicPicCanvas(sceneCanvas);
EnsureMusicPicCanvasGroup();
EnsureSpectrum();
CacheTimeTargets();
}
private void CacheTimeTargets()
{
timeTmps.Clear();
timeTexts.Clear();
if (timeTmp != null) timeTmps.Add(timeTmp);
if (timeText != null) timeTexts.Add(timeText);
if (timeTmps.Count == 0 && timeTexts.Count == 0)
{
Transform timeRoot = null;
if (musicRoot != null)
{
var all = musicRoot.GetComponentsInChildren<Transform>(true);
for (int i = 0; i < all.Length; i++)
{
var t = all[i];
if (t == null) continue;
if (string.Equals(t.name, timeRootName, StringComparison.OrdinalIgnoreCase))
{
timeRoot = t;
break;
}
}
}
if (timeRoot == null)
{
var timeGo = GameObject.Find(timeRootName);
if (timeGo != null) timeRoot = timeGo.transform;
}
if (timeRoot != null)
{
timeTmps.AddRange(timeRoot.GetComponentsInChildren<TMP_Text>(true));
timeTexts.AddRange(timeRoot.GetComponentsInChildren<Text>(true));
if (!timeRoot.gameObject.activeSelf)
timeRoot.gameObject.SetActive(true);
for (int i = 0; i < timeTmps.Count; i++)
{
if (timeTmps[i] != null && !timeTmps[i].gameObject.activeSelf)
timeTmps[i].gameObject.SetActive(true);
}
for (int i = 0; i < timeTexts.Count; i++)
{
if (timeTexts[i] != null && !timeTexts[i].gameObject.activeSelf)
timeTexts[i].gameObject.SetActive(true);
}
}
}
if (timeTmps.Count == 0 && timeTexts.Count == 0)
{
var allTmps = Object.FindObjectsByType<TMP_Text>(FindObjectsInactive.Include, FindObjectsSortMode.None);
foreach (var t in allTmps)
{
if (t == null) continue;
if (t.name.ToUpper().Contains("TIME") || (t.text != null && t.text.ToUpper().Contains("TIME")))
{
timeTmps.Add(t);
break;
}
}
}
if (timeTmps.Count == 0 && timeTexts.Count == 0)
{
var allTexts = Object.FindObjectsByType<Text>(FindObjectsInactive.Include, FindObjectsSortMode.None);
foreach (var t in allTexts)
{
if (t == null) continue;
if (t.name.ToUpper().Contains("TIME") || (t.text != null && t.text.ToUpper().Contains("TIME")))
{
timeTexts.Add(t);
break;
}
}
}
}
private bool _buttonsBound = false;
private void Update()
{
if (!Application.isPlaying)
return;
if (_isGameplayScene && !_spectrumHiddenInGameplay)
{
HideSpectrumInGameplay(_cachedGameplaySceneName);
_spectrumHiddenInGameplay = true;
}
if (!_buttonsBound)
{
BindButtons();
_buttonsBound = true;
}
if (useManualMusicToggle && musicToggleButton != null && Input.GetMouseButtonDown(0))
{
if (IsPointerOverButton(musicToggleButton))
ToggleMusicPic();
}
// keep spectrum alive if MusicPic gets rebuilt or moved
if (spectrumRoot == null && musicRoot != null)
{
EnsureSpectrum();
}
var mgr = BgmPlaybackManager.Instance;
if (mgr == null || !mgr.IsReady)
return;
float total = mgr.TotalTime;
float current = mgr.CurrentTime;
if (total <= 0.01f) return;
float t = Mathf.Clamp01(current / total);
if (progressSlider != null)
progressSlider.value = t;
if (progressImage != null)
progressImage.fillAmount = t;
UpdatePointer(t);
int curS = Mathf.FloorToInt(current);
int totS = Mathf.FloorToInt(total);
if (curS != _lastSeconds || totS != _lastTotalSeconds)
{
_lastSeconds = curS;
_lastTotalSeconds = totS;
_sb.Clear();
AppendTime(_sb, curS);
_sb.Append(" / ");
AppendTime(_sb, totS);
string timeStr = _sb.ToString();
for (int i = 0; i < timeTmps.Count; i++)
{
if (timeTmps[i] != null) timeTmps[i].text = timeStr;
}
for (int i = 0; i < timeTexts.Count; i++)
{
if (timeTexts[i] != null) timeTexts[i].text = timeStr;
}
}
}
private void AppendTime(System.Text.StringBuilder sb, int totalSeconds)
{
int m = totalSeconds / 60;
int s = totalSeconds % 60;
if (m < 10) sb.Append('0');
sb.Append(m);
sb.Append(':');
if (s < 10) sb.Append('0');
sb.Append(s);
}
private void LateUpdate()
{
if (musicRoot != null && spectrumRoot != null && enableMusicPicGyro)
spectrumRoot.localRotation = musicRoot.localRotation;
}
private void UpdatePointer(float t)
{
if (pointerRect == null || progressRect == null) return;
var parent = pointerRect.parent as RectTransform;
if (parent == null) return;
float x = Mathf.Lerp(pointerStartPos.x, pointerEndPos.x, t);
pointerRect.anchoredPosition = new Vector2(x + pointerOffset.x, pointerStartPos.y + pointerOffset.y);
}
private void CleanupProgressOutsideMusicRoot()
{
var all = Resources.FindObjectsOfTypeAll<RectTransform>();
for (int i = 0; i < all.Length; i++)
{
var rt = all[i];
if (rt == null) continue;
if (!string.Equals(rt.name, progressName, StringComparison.OrdinalIgnoreCase)) continue;
if (musicRoot != null && rt.transform.IsChildOf(musicRoot)) continue;
if (Application.isPlaying)
Destroy(rt.gameObject);
else
DestroyImmediate(rt.gameObject);
}
}
private void BindButtons()
{
if (prevButton == null) prevButton = FindButton(prevButtonName);
if (nextButton == null) nextButton = FindButton(nextButtonName);
if (pauseButton == null) pauseButton = FindButton(pauseButtonName);
if (musicToggleButton == null) musicToggleButton = FindButton(musicToggleButtonName);
useManualMusicToggle = musicToggleButton == null;
var mgr = BgmPlaybackManager.Instance ?? BgmPlaybackManager.EnsureInstance();
if (prevButton != null)
{
prevButton.onClick.RemoveAllListeners();
prevButton.onClick.AddListener(mgr.Previous);
}
if (nextButton != null)
{
nextButton.onClick.RemoveAllListeners();
nextButton.onClick.AddListener(mgr.Next);
}
if (pauseButton != null)
{
pauseButton.onClick.RemoveAllListeners();
pauseButton.onClick.AddListener(mgr.TogglePause);
}
if (musicToggleButton != null)
{
if (bindMusicToggleButton)
{
musicToggleButton.onClick.RemoveAllListeners();
musicToggleButton.onClick.AddListener(ToggleMusicPic);
}
}
}
private Button FindButton(string name)
{
if (string.IsNullOrEmpty(name)) return null;
var go = FindByNameContains(name);
if (go == null) return null;
return go.GetComponent<Button>();
}
private bool IsPointerOverButton(Button button)
{
if (button == null) return false;
RectTransform rect = button.GetComponent<RectTransform>();
if (rect == null)
rect = button.targetGraphic != null ? button.targetGraphic.rectTransform : null;
if (rect == null)
{
var graphics = button.GetComponentsInChildren<Graphic>(true);
rect = GetLargestRect(graphics);
}
if (rect == null) return false;
var canvas = rect.GetComponentInParent<Canvas>();
Camera cam = (canvas != null && canvas.renderMode != RenderMode.ScreenSpaceOverlay) ? canvas.worldCamera : null;
return RectTransformUtility.RectangleContainsScreenPoint(rect, Input.mousePosition, cam);
}
private RectTransform GetLargestRect(Graphic[] graphics)
{
if (graphics == null || graphics.Length == 0) return null;
RectTransform best = null;
float bestArea = 0f;
for (int i = 0; i < graphics.Length; i++)
{
var g = graphics[i];
if (g == null) continue;
var rt = g.rectTransform;
float area = Mathf.Abs(rt.rect.width * rt.rect.height);
if (area > bestArea)
{
bestArea = area;
best = rt;
}
}
return best;
}
private GameObject FindByNameContains(string name)
{
if (string.IsNullOrEmpty(name)) return null;
var all = Object.FindObjectsByType<Transform>(FindObjectsInactive.Include, FindObjectsSortMode.None);
for (int i = 0; i < all.Length; i++)
{
var t = all[i];
if (t == null) continue;
if (!t.gameObject.scene.IsValid() || !t.gameObject.scene.isLoaded) continue;
var n = t.name.Replace(" ", "");
var target = name.Replace(" ", "");
if (n.Contains(target))
return t.gameObject;
}
return null;
}
private void EnsureSpectrum()
{
if (musicRoot == null)
{
var foundMusic = FindByNameContains(musicRootName);
if (foundMusic != null)
musicRoot = foundMusic.GetComponent<RectTransform>();
}
if (musicRoot == null)
{
CleanupSpectrumRoots(true);
return;
}
RectTransform existing = musicRoot.Find(spectrumRootName) as RectTransform;
var activeScene = SceneManager.GetActiveScene();
var all = Resources.FindObjectsOfTypeAll<RectTransform>();
List<RectTransform> found = new List<RectTransform>();
for (int i = 0; i < all.Length; i++)
{
var rt = all[i];
if (rt == null) continue;
if (!rt.gameObject.scene.IsValid() || rt.gameObject.scene != activeScene) continue;
if (!string.Equals(rt.name, spectrumRootName, StringComparison.OrdinalIgnoreCase)) continue;
found.Add(rt);
}
RectTransform preferred = null;
for (int i = 0; i < found.Count; i++)
{
var rt = found[i];
if (rt != null && rt.transform.IsChildOf(musicRoot))
{
preferred = rt;
break;
}
}
if (preferred == null && found.Count > 0)
preferred = found[0];
if (preferred != null)
{
if (preferred.parent != musicRoot)
preferred.SetParent(musicRoot, false);
existing = preferred;
}
if (existing == null)
{
var any = FindByNameContains(spectrumRootName);
if (any != null)
existing = any.transform as RectTransform;
}
if (existing == null)
{
var go = new GameObject(spectrumRootName, typeof(RectTransform));
var rt = go.GetComponent<RectTransform>();
rt.SetParent(musicRoot, false);
rt.sizeDelta = new Vector2(420f, 120f);
rt.anchoredPosition = spectrumPosition;
existing = rt;
}
else if (existing.parent != musicRoot)
{
existing.SetParent(musicRoot, false);
}
spectrumRoot = existing as RectTransform;
if (spectrumRoot != null)
{
spectrumRoot.localRotation = Quaternion.identity;
spectrumRoot.localScale = Vector3.one;
spectrumRoot.anchoredPosition = spectrumPosition;
spectrumRoot.gameObject.SetActive(true);
}
var viz = existing.GetComponent<BgmSpectrumVisualizer>();
if (viz == null)
viz = existing.gameObject.AddComponent<BgmSpectrumVisualizer>();
if (viz != null)
viz.enabled = true;
CleanupSpectrumRoots(false);
}
private void EnsureMusicPicCanvas()
{
EnsureMusicPicCanvas(null);
}
private void EnsureMusicPicCanvas(Canvas sceneCanvas)
{
if (!keepMusicPicAcrossScenes || musicRoot == null)
return;
var currentCanvas = musicRoot.GetComponentInParent<Canvas>();
var persistent = GameObject.Find(musicPicCanvasName);
Canvas persistentCanvas = null;
if (persistent == null)
{
var go = new GameObject(musicPicCanvasName, typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
persistentCanvas = go.GetComponent<Canvas>();
persistentCanvas.renderMode = RenderMode.ScreenSpaceOverlay;
var scaler = go.GetComponent<CanvasScaler>();
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(1920f, 1080f);
scaler.matchWidthOrHeight = 0.5f;
DontDestroyOnLoad(go);
persistent = go;
}
else
{
persistentCanvas = persistent.GetComponent<Canvas>();
}
var sourceCanvas = sceneCanvas != null ? sceneCanvas : currentCanvas;
if (sourceCanvas != null && persistentCanvas != null)
{
persistentCanvas.renderMode = sourceCanvas.renderMode;
persistentCanvas.worldCamera = sourceCanvas.worldCamera;
persistentCanvas.planeDistance = sourceCanvas.planeDistance;
persistentCanvas.sortingLayerID = sourceCanvas.sortingLayerID;
persistentCanvas.overrideSorting = true;
persistentCanvas.sortingOrder = sourceCanvas.sortingOrder + 50;
var srcScaler = sourceCanvas.GetComponent<CanvasScaler>();
var dstScaler = persistentCanvas.GetComponent<CanvasScaler>();
if (srcScaler != null && dstScaler != null)
{
dstScaler.uiScaleMode = srcScaler.uiScaleMode;
dstScaler.referenceResolution = srcScaler.referenceResolution;
dstScaler.matchWidthOrHeight = srcScaler.matchWidthOrHeight;
dstScaler.referencePixelsPerUnit = srcScaler.referencePixelsPerUnit;
}
}
if (musicRoot != null && musicRoot.transform.parent != persistent.transform)
musicRoot.SetParent(persistent.transform, false);
}
private Canvas FindSceneCanvas()
{
var canvases = Object.FindObjectsByType<Canvas>(FindObjectsInactive.Include, FindObjectsSortMode.None);
var activeScene = SceneManager.GetActiveScene();
for (int i = 0; i < canvases.Length; i++)
{
var c = canvases[i];
if (c == null) continue;
if (c.gameObject.scene != activeScene) continue;
if (c.name == musicPicCanvasName) continue;
return c;
}
return null;
}
private void EnsureMusicPicCanvasGroup()
{
if (musicRoot == null)
return;
if (musicPicGroup == null)
musicPicGroup = musicRoot.GetComponent<CanvasGroup>();
if (musicPicGroup == null)
musicPicGroup = musicRoot.gameObject.AddComponent<CanvasGroup>();
musicRoot.SetAsLastSibling();
}
private void ApplyDefaultMusicPicVisibility(string sceneName)
{
bool shouldShow = false;
if (lobbyShowRoutine != null)
{
StopCoroutine(lobbyShowRoutine);
lobbyShowRoutine = null;
}
if (musicRoot != null && hasExplicitMusicRoot)
SetMusicPicVisible(shouldShow, true);
if (sceneName == "gamePlay_gamePlay")
{
CleanupSpectrumRoots(true);
HideSpectrumInGameplay(sceneName);
}
}
public void ToggleMusicPic()
{
if (musicRoot == null || !hasExplicitMusicRoot)
Bind();
if (musicRoot == null || !hasExplicitMusicRoot)
{
var go = FindByNameContains(musicRootName);
if (go != null)
{
musicRoot = go.GetComponent<RectTransform>();
hasExplicitMusicRoot = musicRoot != null;
}
}
if (musicRoot == null || !hasExplicitMusicRoot) return;
SetMusicPicVisible(!musicPicVisible, false);
}
public RectTransform GetSpectrumRoot()
{
return spectrumRoot;
}
private void SetMusicPicVisible(bool visible, bool instant)
{
if (musicRoot == null) return;
EnsureMusicPicCanvasGroup();
if (musicPicTween != null)
{
StopCoroutine(musicPicTween);
musicPicTween = null;
}
musicPicVisible = visible;
if (instant)
{
if (musicPicGroup != null)
{
musicPicGroup.alpha = visible ? 1f : 0f;
musicPicGroup.interactable = visible;
musicPicGroup.blocksRaycasts = visible;
}
musicRoot.gameObject.SetActive(visible);
if (!visible)
CleanupSpectrumRoots(true);
return;
}
musicRoot.gameObject.SetActive(true);
musicPicTween = StartCoroutine(FadeMusicPic(visible));
}
private System.Collections.IEnumerator FadeMusicPic(bool visibleAtEnd)
{
if (musicPicGroup == null)
yield break;
float start = musicPicGroup.alpha;
float target = visibleAtEnd ? 1f : 0f;
float t = 0f;
while (t < 1f)
{
float dt = useUnscaledTime ? Time.unscaledDeltaTime : Time.deltaTime;
t += dt / Mathf.Max(0.0001f, musicPicFadeDuration);
float eased = Mathf.SmoothStep(0f, 1f, t);
musicPicGroup.alpha = Mathf.LerpUnclamped(start, target, eased);
yield return null;
}
musicPicGroup.alpha = target;
musicPicGroup.interactable = visibleAtEnd;
musicPicGroup.blocksRaycasts = visibleAtEnd;
if (!visibleAtEnd && musicRoot != null)
musicRoot.gameObject.SetActive(false);
if (!visibleAtEnd)
CleanupSpectrumRoots(true);
}
private void CleanupSpectrumRoots(bool disableOnly)
{
var all = Resources.FindObjectsOfTypeAll<RectTransform>();
for (int i = 0; i < all.Length; i++)
{
var rt = all[i];
if (rt == null) continue;
if (!string.Equals(rt.name, spectrumRootName, StringComparison.OrdinalIgnoreCase)) continue;
if (musicRoot != null && rt.transform.IsChildOf(musicRoot)) continue;
if (disableOnly)
{
rt.gameObject.SetActive(false);
}
else
{
if (Application.isPlaying)
Destroy(rt.gameObject);
else
DestroyImmediate(rt.gameObject);
}
}
}
private void HideSpectrumInGameplay(string sceneName)
{
if (sceneName != gameplaySceneName)
return;
var all = Resources.FindObjectsOfTypeAll<RectTransform>();
for (int i = 0; i < all.Length; i++)
{
var rt = all[i];
if (rt == null) continue;
if (!string.Equals(rt.name, spectrumRootName, StringComparison.OrdinalIgnoreCase)) continue;
if (!rt.gameObject.scene.IsValid() || !rt.gameObject.scene.isLoaded) continue;
var viz = rt.GetComponent<BgmSpectrumVisualizer>();
if (viz != null) viz.enabled = false;
rt.gameObject.SetActive(false);
}
if (spectrumRoot != null)
{
var viz = spectrumRoot.GetComponent<BgmSpectrumVisualizer>();
if (viz != null) viz.enabled = false;
spectrumRoot.gameObject.SetActive(false);
}
}
private System.Collections.IEnumerator ShowLobbyMusicPicNextFrame()
{
int tries = 0;
while (tries < 3)
{
yield return null;
if (musicRoot == null || !hasExplicitMusicRoot)
Bind();
if (musicRoot != null && hasExplicitMusicRoot)
break;
tries++;
}
if (musicRoot == null || !hasExplicitMusicRoot)
yield break;
EnsureSpectrum();
EnsureMusicPicCanvasGroup();
if (musicPicGroup != null)
musicPicGroup.alpha = 0f;
SetMusicPicVisible(true, false);
}
private List<RectTransform> FindAllMusicPic()
{
var list = new List<RectTransform>();
var all = Resources.FindObjectsOfTypeAll<RectTransform>();
for (int i = 0; i < all.Length; i++)
{
var rt = all[i];
if (rt == null) continue;
if (!rt.gameObject.scene.IsValid() || !rt.gameObject.scene.isLoaded) continue;
var n = rt.name.Replace(" ", "");
var target = musicRootName.Replace(" ", "");
if (n.IndexOf("Canvas", StringComparison.OrdinalIgnoreCase) >= 0)
continue;
bool match = n.Equals(target, StringComparison.OrdinalIgnoreCase) ||
n.StartsWith(target + "(", StringComparison.OrdinalIgnoreCase) ||
n.StartsWith(target + "_", StringComparison.OrdinalIgnoreCase);
if (match)
list.Add(rt);
}
return list;
}
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ea68bc988222e7f4e89b5d312fb18c49