167
Assets/Scripts/UI/SongSelection/SongDotAnimationUI.cs
Normal file
167
Assets/Scripts/UI/SongSelection/SongDotAnimationUI.cs
Normal file
@@ -0,0 +1,167 @@
|
||||
using System.Collections.Generic;
|
||||
using Ichni.Menu;
|
||||
using UnityEngine;
|
||||
|
||||
public class SongDotAnimationUI : MonoBehaviour
|
||||
{
|
||||
[Header("引用")]
|
||||
public SongListControllerUI controllerUI;
|
||||
|
||||
[Header("预制体")]
|
||||
public GameObject DotPfab;
|
||||
|
||||
[Header("布局参数")]
|
||||
[SerializeField] private float innerOffset = 400f;
|
||||
[SerializeField] private float outerOffset = 600f;
|
||||
[SerializeField] [Min(1f)] private float visibleSongRadius = 2f;
|
||||
[SerializeField] [Min(1)] private int visibleDotCount = 5;
|
||||
[SerializeField] [Range(0f, 1f)] private float outerScale = 0f;
|
||||
[SerializeField] [Range(0f, 1f)] private float midScale = 0.2f;
|
||||
[SerializeField] [Range(0f, 1f)] private float outerAlpha = 0f;
|
||||
[SerializeField] [Range(0f, 1f)] private float midAlpha = 0.45f;
|
||||
|
||||
private readonly List<RectTransform> dots = new();
|
||||
private readonly List<CanvasGroup> dotCanvasGroups = new();
|
||||
private int displayedCount = -1;
|
||||
|
||||
public int Count => controllerUI != null ? controllerUI.SongCount : 0;
|
||||
public float ScrollProgress => controllerUI != null ? controllerUI.ScrollProgress : 0f;
|
||||
|
||||
void Start()
|
||||
{
|
||||
RebuildDotsIfNeeded();
|
||||
}
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
RebuildDotsIfNeeded();
|
||||
|
||||
if (controllerUI == null || dots.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float centerIndex = controllerUI.ContinuousSongIndex;
|
||||
int firstVisibleSongIndex = Mathf.FloorToInt(centerIndex) - visibleDotCount / 2;
|
||||
for (int i = 0; i < dots.Count; i++)
|
||||
{
|
||||
RectTransform dot = dots[i];
|
||||
int songIndex = firstVisibleSongIndex + i;
|
||||
if (songIndex < 0 || songIndex >= Count)
|
||||
{
|
||||
SetDotVisible(i, false);
|
||||
continue;
|
||||
}
|
||||
|
||||
float relativeIndex = songIndex - centerIndex;
|
||||
float absoluteRelativeIndex = Mathf.Abs(relativeIndex);
|
||||
bool isVisible = absoluteRelativeIndex <= visibleSongRadius;
|
||||
|
||||
SetDotVisible(i, isVisible);
|
||||
if (!isVisible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float y = GetDotY(relativeIndex);
|
||||
dots[i].anchoredPosition = new Vector2(0f, y);
|
||||
|
||||
float scale = GetDotScale(absoluteRelativeIndex);
|
||||
dots[i].localScale = Vector3.one * Mathf.Clamp01(scale);
|
||||
|
||||
if (i < dotCanvasGroups.Count && dotCanvasGroups[i] != null)
|
||||
{
|
||||
dotCanvasGroups[i].alpha = GetDotAlpha(absoluteRelativeIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RebuildDotsIfNeeded()
|
||||
{
|
||||
int targetCount = Mathf.Min(Count, visibleDotCount);
|
||||
if (displayedCount == targetCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = dots.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (dots[i] != null)
|
||||
{
|
||||
Destroy(dots[i].gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
dots.Clear();
|
||||
dotCanvasGroups.Clear();
|
||||
|
||||
if (DotPfab == null)
|
||||
{
|
||||
displayedCount = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < targetCount; i++)
|
||||
{
|
||||
GameObject go = Instantiate(DotPfab, transform);
|
||||
dots.Add(go.GetComponent<RectTransform>());
|
||||
|
||||
CanvasGroup canvasGroup = go.GetComponent<CanvasGroup>();
|
||||
if (canvasGroup == null)
|
||||
{
|
||||
canvasGroup = go.AddComponent<CanvasGroup>();
|
||||
}
|
||||
|
||||
dotCanvasGroups.Add(canvasGroup);
|
||||
}
|
||||
|
||||
displayedCount = targetCount;
|
||||
}
|
||||
|
||||
private void SetDotVisible(int dotIndex, bool isVisible)
|
||||
{
|
||||
RectTransform dot = dots[dotIndex];
|
||||
if (dot.gameObject.activeSelf != isVisible)
|
||||
{
|
||||
dot.gameObject.SetActive(isVisible);
|
||||
}
|
||||
}
|
||||
|
||||
private float GetDotY(float relativeIndex)
|
||||
{
|
||||
float absRelativeIndex = Mathf.Abs(relativeIndex);
|
||||
if (absRelativeIndex <= 1f)
|
||||
{
|
||||
return -relativeIndex * innerOffset;
|
||||
}
|
||||
|
||||
float scaleRange = Mathf.Max(visibleSongRadius - 1f, 0.0001f);
|
||||
float outerT = Mathf.Clamp01((absRelativeIndex - 1f) / scaleRange);
|
||||
float distance = Mathf.Lerp(innerOffset, outerOffset, outerT);
|
||||
return -Mathf.Sign(relativeIndex) * distance;
|
||||
}
|
||||
|
||||
private float GetDotScale(float absoluteRelativeIndex)
|
||||
{
|
||||
if (absoluteRelativeIndex <= 1f)
|
||||
{
|
||||
return Mathf.Lerp(1f, midScale, absoluteRelativeIndex);
|
||||
}
|
||||
|
||||
float scaleRange = Mathf.Max(visibleSongRadius - 1f, 0.0001f);
|
||||
float outerT = Mathf.Clamp01((absoluteRelativeIndex - 1f) / scaleRange);
|
||||
return Mathf.Lerp(midScale, outerScale, outerT);
|
||||
}
|
||||
|
||||
private float GetDotAlpha(float absoluteRelativeIndex)
|
||||
{
|
||||
if (absoluteRelativeIndex <= 1f)
|
||||
{
|
||||
return Mathf.Lerp(1f, midAlpha, absoluteRelativeIndex);
|
||||
}
|
||||
|
||||
float scaleRange = Mathf.Max(visibleSongRadius - 1f, 0.0001f);
|
||||
float outerT = Mathf.Clamp01((absoluteRelativeIndex - 1f) / scaleRange);
|
||||
return Mathf.Lerp(midAlpha, outerAlpha, outerT);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user