168 lines
6.6 KiB
C#
168 lines
6.6 KiB
C#
using asap.core;
|
||
using Assets.Scripts.UI.AnimationSimulator;
|
||
using Game;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Threading.Tasks;
|
||
using UnityEngine;
|
||
|
||
namespace UI.Wheel
|
||
{
|
||
public abstract class AbstractWheelRewardComponent : MonoBehaviour
|
||
{
|
||
#region Protected Fields
|
||
protected AbstractWheelRewardCell[] wheelRewardCells;
|
||
protected AnimationSimulatorComponent simulator;
|
||
protected Transform rewardContainer;
|
||
|
||
protected virtual string StartSound => string.Empty;
|
||
protected virtual string LoopedSound => string.Empty;
|
||
protected virtual string EndSound => string.Empty;
|
||
private EventUISound _startUISound;
|
||
private EventUISound _loopedUISound;
|
||
private EventUISound _endUISound;
|
||
#endregion
|
||
|
||
#region Serialized Fields
|
||
[SerializeField, Min(0f)]
|
||
protected float delayShowRewardPopup = 0.1f;
|
||
[SerializeField, Min(0f),Tooltip("指针旋转的角度范围(-20,20)")]
|
||
protected float pointerAngle = 20f;
|
||
[SerializeField, Min(0f)]
|
||
protected float wheelAngle = 14f;
|
||
[SerializeField]
|
||
protected float revertAngleTime = 0.01f;
|
||
[SerializeField, Min(0f)]
|
||
protected float cdEffectVelocity = 5f;
|
||
#endregion
|
||
|
||
#region Private Fields
|
||
|
||
protected const float FULL_CIRCLE = 360f;
|
||
protected float SegmentAngle => FULL_CIRCLE / wheelRewardCells?.Length??1 ;
|
||
protected int MaxSegment => (int)(FULL_CIRCLE / SegmentAngle) -1;
|
||
|
||
#endregion
|
||
|
||
#region Abstract Methods
|
||
public abstract void SetData(List<IWheelReward> rewards);
|
||
public abstract void ShowUI();
|
||
#endregion
|
||
|
||
#region Initialization
|
||
protected virtual void Awake()
|
||
{
|
||
simulator = this.GetComponent<AnimationSimulatorComponent>();
|
||
wheelRewardCells = this.GetComponentsInChildren<AbstractWheelRewardCell>();
|
||
rewardContainer = this.gameObject.FindChildGameObject(nameof(rewardContainer))?.transform;
|
||
}
|
||
protected virtual void InitUI()
|
||
{
|
||
}
|
||
protected virtual void Start()
|
||
{
|
||
InitUI();
|
||
}
|
||
#endregion
|
||
|
||
#region Wheel Animation
|
||
public virtual async Task PlayEffect(int rewardIndex, IWheelReward reward, Action endCallback)
|
||
{
|
||
EffectStart();
|
||
Debug.Log($"[Wheel] PlayEffect - rewardIndex: {rewardIndex}, wheelRewardCells.Length: {wheelRewardCells.Length}, SegmentAngle: {SegmentAngle}");
|
||
var param = new AnimationSimulatorComponent.Parameter();
|
||
param.scale = FULL_CIRCLE;
|
||
param.startValue = rewardContainer.eulerAngles.z;
|
||
param.offsetDirection = RandomOffsetDirection(reward);
|
||
param.AccelerationEndCallback = AccelerationEndCallback;
|
||
param.updateCallback = EffectUpdate;
|
||
param.revertTargetEndCallbcak = RevertTargetEndCallbcak;
|
||
param.uniformCompletionStartCallBack = (currentValue) => CalculateTargetAngle(rewardIndex, currentValue);
|
||
|
||
await simulator.PlayEffect(param);
|
||
|
||
await EffectEnd(reward);
|
||
endCallback?.Invoke();
|
||
}
|
||
|
||
protected virtual void AccelerationEndCallback()
|
||
{
|
||
if (string.IsNullOrEmpty(LoopedSound))
|
||
return;
|
||
if (_startUISound != null)
|
||
GContext.Publish(new FadeOutEvent(_startUISound.Sound));
|
||
|
||
_loopedUISound= new EventUISound(LoopedSound);
|
||
GContext.Publish(_loopedUISound);
|
||
}
|
||
|
||
private float CalculateTargetAngle(int rewardIndex, float currentValue)
|
||
{
|
||
int safeIndex = Mathf.Clamp(rewardIndex, 0, wheelRewardCells.Length - 1);
|
||
float cellAngle = SegmentAngle;
|
||
|
||
// 归一化到 [0, 360)
|
||
float normalizedCurrent = currentValue % FULL_CIRCLE;
|
||
if (normalizedCurrent < 0) normalizedCurrent += FULL_CIRCLE;
|
||
|
||
// 目标格子角度(0-360范围内)
|
||
float targetSlotAngle = safeIndex * cellAngle + cellAngle * 0.5f;
|
||
|
||
// 计算当前圈数(考虑负数圈)
|
||
float currentRevolutions = Mathf.Floor((currentValue - normalizedCurrent) / FULL_CIRCLE);
|
||
|
||
// 目标角度 = 当前圈数 × 360 + 目标格子角度
|
||
float targetAngle = currentRevolutions * FULL_CIRCLE + targetSlotAngle;
|
||
|
||
// 关键:如果targetAngle > currentValue,说明目标在"后面",需要减去360(多转一圈,继续顺时针)
|
||
// 这样确保targetAngle始终 <= currentValue(在顺时针旋转方向的前方)
|
||
while (targetAngle > currentValue)
|
||
{
|
||
targetAngle -= FULL_CIRCLE;
|
||
}
|
||
|
||
float normalizedResult = targetAngle % FULL_CIRCLE;
|
||
if (normalizedResult < 0) normalizedResult += FULL_CIRCLE;
|
||
Debug.Log($"[Wheel] CalculateTargetAngle - rewardIndex: {rewardIndex}, safeIndex: {safeIndex}, cellAngle: {cellAngle}, currentValue: {currentValue}, currentRevolutions: {currentRevolutions}, targetAngle: {targetAngle}, normalizedResult: {normalizedResult}°");
|
||
|
||
return targetAngle;
|
||
}
|
||
|
||
protected virtual void EffectStart()
|
||
{
|
||
_ = DelayShow();
|
||
if (string.IsNullOrEmpty(StartSound))
|
||
return;
|
||
_startUISound = new EventUISound(StartSound);
|
||
GContext.Publish(_startUISound);
|
||
}
|
||
protected virtual async Task DelayShow()
|
||
{
|
||
await Awaiters.Seconds(simulator.GetAccelertationTime() / 3);
|
||
}
|
||
protected virtual void EffectUpdate(float currentVelocity, float currentValue)
|
||
{
|
||
rewardContainer.rotation = Quaternion.Euler(0, 0, currentValue);
|
||
}
|
||
protected virtual async Task EffectEnd(IWheelReward reward)
|
||
{
|
||
Debug.Log($"[BossFight] EffectEnd - rewardContainer.eulerAngles.z: {rewardContainer.eulerAngles.z}, normalized: {((rewardContainer.eulerAngles.z % 360 + 360) % 360)}°");
|
||
await Awaiters.Seconds(delayShowRewardPopup);
|
||
}
|
||
protected virtual void RevertTargetEndCallbcak()
|
||
{
|
||
|
||
if (string.IsNullOrEmpty(EndSound))
|
||
return;
|
||
if (_loopedUISound!=null)
|
||
GContext.Publish(new FadeOutEvent(_loopedUISound.Sound));
|
||
GContext.Publish(new EventUISound(EndSound));
|
||
}
|
||
private bool RandomOffsetDirection(IWheelReward reward)
|
||
{
|
||
var random = UnityEngine.Random.Range(1, 101);
|
||
return random <= reward.LeftWeight;
|
||
}
|
||
#endregion
|
||
}
|
||
} |