168 lines
5.0 KiB
C#
168 lines
5.0 KiB
C#
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);
|
|
}
|
|
}
|