568 lines
20 KiB
C#
568 lines
20 KiB
C#
using System;
|
||
using System.Collections;
|
||
using System.Linq;
|
||
using System.Threading.Tasks;
|
||
using asap.core;
|
||
using DataCenter;
|
||
using DG.Tweening;
|
||
using GameCore;
|
||
using UnityEngine;
|
||
using UnityEngine.AddressableAssets;
|
||
using UnityEngine.UI;
|
||
using Object = UnityEngine.Object;
|
||
|
||
namespace EventPartnerGather.Mining
|
||
{
|
||
|
||
public static class LegacyAnimExtensions
|
||
{
|
||
public static void GoToFrame(this Animation anim, int targetFrame)
|
||
{
|
||
var clipName = anim.clip.name;
|
||
// 获取动画状态
|
||
var state = anim[clipName];
|
||
float frameRate = state.clip.frameRate;
|
||
// 计算动画的总帧数(假设动画时长正确且帧率已知)
|
||
// 如果你知道确切的帧率,最好直接使用帧率,例如:float frameRate = 30f;
|
||
// totalFrames = state.length * frameRate;
|
||
// 这里是一种估算方式,实际帧数取决于制作时的帧率
|
||
var approximateFrames = state.length * 60f; // 假设60FPS制作
|
||
// 确保目标帧在有效范围内
|
||
targetFrame = Mathf.Clamp(targetFrame, 0, (int)approximateFrames);
|
||
// 计算目标时间 (时间 = 帧数 / 帧率)
|
||
// 使用相同的假设帧率进行计算
|
||
float targetTime = targetFrame / 60f;
|
||
// 设置动画时间
|
||
state.time = targetTime;
|
||
// 播放动画并立即采样到该位置
|
||
anim.Play(clipName);
|
||
anim.Sample(); // 强制立即更新到当前time指定的帧
|
||
anim.Stop(); // 停止,如果只想定位而不播放
|
||
}
|
||
|
||
public static Task PlayAnimFromToAsync(this Animation anim, MonoBehaviour runner, string clipName, int startFrame, int endFrame)
|
||
{
|
||
var tcs = new TaskCompletionSource<bool>();
|
||
runner.StartCoroutine(PlayAnimFromToCoroutine(anim, clipName, startFrame, endFrame, () =>
|
||
{
|
||
tcs.TrySetResult(true);
|
||
}));
|
||
return tcs.Task;
|
||
}
|
||
|
||
private static IEnumerator PlayAnimFromToCoroutine(Animation anim, string clipName, int startFrame, int endFrame, Action onComplete)
|
||
{
|
||
ELog($"PlayAnimFromToCoroutine startFrame={startFrame} endFrame={endFrame}");
|
||
AnimationState state = anim[clipName];
|
||
float frameRate = state.clip.frameRate;
|
||
float startTime = startFrame / frameRate;
|
||
float endTime = endFrame / frameRate;
|
||
// 设置起始时间并播放
|
||
// anim.Stop();
|
||
state.time = startTime;
|
||
anim.Play(clipName);
|
||
// 等待到目标时间,留浮点安全误差
|
||
while (state.time < endTime && anim.isPlaying)
|
||
{
|
||
yield return null;
|
||
}
|
||
|
||
// 强制采样到结束帧,避免漏采
|
||
state.time = endTime;
|
||
anim.Sample();
|
||
//state.speed = 0;
|
||
anim.Stop();
|
||
onComplete?.Invoke();
|
||
}
|
||
|
||
private static void ELog(string message)
|
||
{
|
||
#if UNITY_EDITOR
|
||
Debug.Log($"<color=cyan>LegacyAnimExtensions -> {message}</color>");
|
||
#endif
|
||
}
|
||
}
|
||
|
||
[Serializable]
|
||
public class TrainCar
|
||
{
|
||
public Transform carTransform;
|
||
public Transform oreContainer; // 矿石收集容器
|
||
// [HideInInspector]
|
||
public Transform collectPoint; // 收集点
|
||
[HideInInspector]
|
||
public Image bar;
|
||
[HideInInspector]
|
||
public Transform reward;
|
||
|
||
// [HideInInspector]
|
||
public int pointsRange; // 对应的点数范围 (如 0-10, 10-20, 20-30, 30-40, 40+)
|
||
// [HideInInspector]
|
||
public int maxCapacity = 10;
|
||
|
||
|
||
//
|
||
private int _collectPoint = 0;
|
||
// 检测获得点数是否可以收集
|
||
public bool CanCollect(int points)
|
||
{
|
||
ELog($"CanCollect points={points} maxCapacity={maxCapacity}");
|
||
return points <= maxCapacity;
|
||
}
|
||
|
||
//
|
||
// public int GetCollectedOresCount => _collectedOres.Count;
|
||
|
||
public void AddOre(GameObject ore)
|
||
{
|
||
// 消除 Ore
|
||
Object.Destroy(ore);
|
||
_collectPoint++;
|
||
|
||
|
||
// // Log("AddOre-> ");
|
||
// if (oreContainer && ore)
|
||
// {
|
||
// ore.transform.SetParent(oreContainer);
|
||
// // 简单的堆叠布局
|
||
// var count = _collectedOres.Count;
|
||
// ore.transform.localPosition = new Vector3(
|
||
// (count % 3 - 1) * 0.5f,
|
||
// (count / 3) * 0.3f,
|
||
// 0f
|
||
// );
|
||
// ore.transform.localScale = Vector3.one * 0.8f;
|
||
// _collectedOres.Add(ore);
|
||
// }
|
||
}
|
||
|
||
private static void ELog(string t)
|
||
{
|
||
#if UNITY_EDITOR
|
||
Debug.Log($"<color=cyan>TrainCar-> {t} </color>");
|
||
#endif
|
||
}
|
||
|
||
public void ClearOres()
|
||
{
|
||
// foreach (var ore in _collectedOres)
|
||
// {
|
||
// if (ore != null)
|
||
// {
|
||
// Object.DestroyImmediate(ore);
|
||
// }
|
||
// }
|
||
// _collectedOres.Clear();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 事件合作伙伴火车控制器
|
||
/// 管理火车车厢系统,包括矿石收集、车厢管理、矿石飞行动画等
|
||
/// 建议挂载在:Train容器对象上,确保包含所有车厢的Transform
|
||
///
|
||
/// 场景结构建议:
|
||
/// Train (挂载此脚本)
|
||
/// ├── TrainCar1 (第一节车厢)
|
||
/// │ └── OreContainer (矿石容器)
|
||
/// ├── TrainCar2 (第二节车厢)
|
||
/// │ └── OreContainer (矿石容器)
|
||
/// └── ... (更多车厢)
|
||
/// </summary>
|
||
public class EventPartnerTrainController : MonoBehaviour
|
||
{
|
||
[Header("火车车厢配置")]
|
||
public TrainCar[] trainCars = new TrainCar[5]; // 5节车厢
|
||
|
||
[Header("动画配置")]
|
||
// public float oreFlightDuration = 1f;
|
||
public AnimationCurve oreFlightCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
|
||
|
||
[Header("飞行偏移")]
|
||
public float flyOreOffsetX = 2f;
|
||
public float flyOreOffsetY = 2f;
|
||
|
||
[Header("移动目标")]
|
||
public Transform target; // 目标点(可选)
|
||
public Vector3 targetWorldPos; // 备用的世界坐标目标点(当 target 为空时使用)
|
||
public bool useTargetTransform = true;
|
||
[Header("移动参数")]
|
||
[Min(0.01f)]
|
||
public float duration = 1f; // 总时长(秒)
|
||
[Tooltip("位移归一化曲线:X=时间(0~1), Y=位移进度(0~1)。建议是S形:起点0,终点1,中间变快,末尾变慢。")]
|
||
public AnimationCurve positionCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
|
||
[Header("调试/外观")]
|
||
public Color gizmoColor = new Color(0.2f, 0.8f, 1f, 0.8f);
|
||
|
||
// 声音
|
||
private Sound soundFly;
|
||
private AudioClip audioFlyClip;
|
||
|
||
// 运行时
|
||
private Vector3 _startPos;
|
||
private Vector3 _endPos;
|
||
private float _t; // 0~1
|
||
private bool _isMoving;
|
||
// 周期
|
||
public float OreFlightDuration { get; set; } = 1.0f;
|
||
//
|
||
private EventPartnerGatherManager _manager;
|
||
//
|
||
private PlayerItemData _playerItemData;
|
||
|
||
private void Start()
|
||
{
|
||
//
|
||
_playerItemData = GContext.container.Resolve<PlayerItemData>();
|
||
InitializeTrainCars();
|
||
// 设一个合理的默认曲线(Slow -> Fast -> Stop),确保末尾Y=1停住
|
||
if (positionCurve == null || positionCurve.length < 2)
|
||
{
|
||
positionCurve = new AnimationCurve(
|
||
new Keyframe(0f, 0f, 0f, 2f), // 起步慢
|
||
new Keyframe(0.5f, 0.8f, 1.5f, 1.5f), // 中段加速
|
||
new Keyframe(1f, 1f, 0f, 0f) // 末尾速度收敛到0
|
||
);
|
||
}
|
||
LoadAudio();
|
||
}
|
||
|
||
private void Update()
|
||
{
|
||
if (!_isMoving) return;
|
||
_t += Time.deltaTime / Mathf.Max(0.0001f, duration);
|
||
float t01 = Mathf.Clamp01(_t);
|
||
float p = positionCurve.Evaluate(t01);
|
||
transform.position = Vector3.LerpUnclamped(_startPos, _endPos, p);
|
||
if (_t >= 1f)
|
||
{
|
||
// 确保精确落点
|
||
transform.position = _endPos;
|
||
_isMoving = false;
|
||
}
|
||
}
|
||
|
||
private void OnDisable()
|
||
{
|
||
StopFlyAudio();
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
StopFlyAudio();
|
||
AudioRelease();
|
||
}
|
||
|
||
|
||
// 开始从当前位置移动到目标世界坐标
|
||
public void StartMoveTo(Vector3 worldTarget)
|
||
{
|
||
_startPos = transform.position;
|
||
_endPos = worldTarget;
|
||
_t = 0f;
|
||
_isMoving = true;
|
||
}
|
||
//
|
||
public void StartMoveByDelta(Vector3 delta)
|
||
{
|
||
StartMoveTo(transform.position + delta);
|
||
}
|
||
|
||
// 重新设置目标(例如在运行时切换目标物体后调用)
|
||
public void SetTarget(Transform newTarget)
|
||
{
|
||
target = newTarget;
|
||
useTargetTransform = true;
|
||
}
|
||
|
||
|
||
void OnDrawGizmosSelected()
|
||
{
|
||
Vector3 end = useTargetTransform && target != null ? target.position : targetWorldPos;
|
||
Gizmos.color = gizmoColor;
|
||
// 预览当前配置下的终点
|
||
Gizmos.DrawWireSphere(end, 0.15f);
|
||
// 路径线
|
||
Gizmos.DrawLine(Application.isPlaying ? _startPos : transform.position, end);
|
||
}
|
||
|
||
private void InitializeTrainCars()
|
||
{
|
||
ELog($"InitTrainCars count={trainCars.Length}");
|
||
foreach (var train in trainCars)
|
||
{
|
||
var carTransform = train.carTransform;
|
||
if (!train.oreContainer)
|
||
{
|
||
train.oreContainer = carTransform.Find("OreContainer");
|
||
}
|
||
if (!train.collectPoint)
|
||
{
|
||
train.collectPoint = carTransform.Find("CollectPoint");
|
||
}
|
||
|
||
train.bar = carTransform.Find("ui/bg_bar/bar").GetComponent<Image>();
|
||
train.bar.fillAmount = 0;
|
||
train.reward = carTransform.Find("ui/bg_bar/reward");
|
||
|
||
// var anim = train.oreContainer.GetComponent<Animation>();
|
||
// anim.GoToFrame(0);
|
||
|
||
// 默认设置未 全部隐藏
|
||
var childAnim = train.oreContainer.GetComponent<OreIncreaseController>();
|
||
childAnim.Reset();
|
||
|
||
}
|
||
}
|
||
|
||
public void SetTrainCarCollectPoints(int index, int endPoints = -1)
|
||
{
|
||
var targetCar = trainCars[index];
|
||
var maxNum = targetCar.maxCapacity;
|
||
if (endPoints == -1)
|
||
{
|
||
endPoints = maxNum;
|
||
}
|
||
|
||
var fillAmount = (float)endPoints/maxNum;
|
||
targetCar.bar.fillAmount = fillAmount;
|
||
|
||
// var anim = targetCar.oreContainer.GetComponent<Animation>();
|
||
// anim.GoToFrame(Mathf.CeilToInt(fillAmount * 100));
|
||
|
||
var animController = targetCar.oreContainer.GetComponent<OreIncreaseController>();
|
||
animController.JumpTo(fillAmount);
|
||
|
||
ELog($"SetTrainCarCollectPoints index={index} endPoints={endPoints} fillAmount={fillAmount:F3}");
|
||
}
|
||
|
||
public async Task CollectOre(int step, GameObject ore)
|
||
{
|
||
// Log($"CollectOre-> {step}");
|
||
if (!ore) return;
|
||
if (step > trainCars.Length - 1)
|
||
{
|
||
return;
|
||
}
|
||
var targetCar = trainCars[step];
|
||
if (targetCar == null)
|
||
return;
|
||
await AnimateOreFlight(ore, targetCar);
|
||
targetCar.AddOre(ore);
|
||
}
|
||
|
||
#region Audio
|
||
private async void LoadAudio()
|
||
{
|
||
audioFlyClip = await Addressables.LoadAssetAsync<AudioClip>("audio_ui_partnermining_resource_fly").Task;
|
||
|
||
}
|
||
void AudioRelease()
|
||
{
|
||
Addressables.Release(audioFlyClip);
|
||
}
|
||
|
||
private void StopFlyAudio()
|
||
{
|
||
if (soundFly)
|
||
{
|
||
soundFly.audioSource.Stop();
|
||
soundFly.ReturnPool();
|
||
soundFly = null;
|
||
}
|
||
}
|
||
|
||
private void PlayFlyAudio()
|
||
{
|
||
StopFlyAudio();
|
||
soundFly = GContext.container.Resolve<ISoundService>().GetNewUISound(audioFlyClip);
|
||
soundFly.audioSource.Play();
|
||
soundFly.audioSource.loop = true;
|
||
}
|
||
|
||
#endregion
|
||
// TrainCar 都在一辆上面
|
||
public async Task RunOreIncreaseAnimation(int index, int startPoints,int endPoints)
|
||
{
|
||
ELog($"RunOreIncreaseAnimation ->{index} {startPoints} {endPoints}");
|
||
|
||
// GContext.Publish(new EventUISound("audio_ui_partnermining_resource_fly"));
|
||
|
||
PlayFlyAudio();
|
||
var targetCar = trainCars[index];
|
||
|
||
var maxNum = targetCar.maxCapacity;
|
||
var startFrame = startPoints * 100 / maxNum ;
|
||
var endFrame = endPoints * 100 / maxNum;
|
||
targetCar.bar.fillAmount = (float)startPoints/maxNum;
|
||
targetCar.bar.DOFillAmount((float)endPoints/maxNum,0.4f);
|
||
|
||
// var anim = targetCar.oreContainer.GetComponent<Animation>();
|
||
// var clipName = anim.clip.name;
|
||
// await anim.PlayAnimFromToAsync(this,clipName,startFrame,endFrame);
|
||
var animController = targetCar.oreContainer.GetComponent<OreIncreaseController>();
|
||
await animController.AsyncPlayFromTo(startFrame, endFrame);
|
||
StopFlyAudio();
|
||
}
|
||
|
||
#region 无用代码,整理删除掉
|
||
private TrainCar SelectTargetCar(int points)
|
||
{
|
||
// 根据点数选择对应的车厢
|
||
foreach (var car in trainCars)
|
||
{
|
||
if (car != null && car.CanCollect(points))
|
||
{
|
||
// 简单的分配逻辑:按点数范围分配
|
||
if (points <= car.pointsRange)
|
||
{
|
||
return car;
|
||
}
|
||
}
|
||
}
|
||
|
||
// // 如果没有合适的车厢,返回第一个有空间的车厢
|
||
// foreach (var car in trainCars)
|
||
// {
|
||
// if (car != null && car.CanCollect(points))
|
||
// {
|
||
// return car;
|
||
// }
|
||
// }
|
||
|
||
return null;
|
||
}
|
||
private async Task _AnimateOreFlight(GameObject ore, TrainCar targetCar)
|
||
{
|
||
if (!ore || !targetCar?.oreContainer) return;
|
||
|
||
var startPos = ore.transform.position;
|
||
var endPos = targetCar.collectPoint.position;
|
||
// 添加抛物线轨迹
|
||
var midPos = (startPos + endPos) * 0.5f;
|
||
midPos.y += flyOreOffsetY; // 抛物线高度
|
||
midPos.x -= flyOreOffsetX; // 偏移值
|
||
float t = 0f;
|
||
while (t < OreFlightDuration)
|
||
{
|
||
t += Time.deltaTime / OreFlightDuration;
|
||
var curveT = oreFlightCurve.Evaluate(t);
|
||
// 贝塞尔曲线插值
|
||
var pos = CalculateBezierPoint(curveT, startPos, midPos, endPos);
|
||
ore.transform.position = pos;
|
||
// 旋转效果
|
||
ore.transform.Rotate(0, 0, 180 * Time.deltaTime / OreFlightDuration);
|
||
await Awaiters.NextFrame;
|
||
}
|
||
|
||
ore.transform.position = endPos;
|
||
}
|
||
#endregion
|
||
private async Task AnimateOreFlight(GameObject ore, TrainCar targetCar)
|
||
{
|
||
if (!ore || !targetCar?.oreContainer) return;
|
||
|
||
Vector3 startPos = ore.transform.position;
|
||
Vector3 endPos = targetCar.collectPoint.position;
|
||
|
||
// 中间抛物点
|
||
Vector3 midPos = (startPos + endPos) * 0.5f;
|
||
midPos.y += flyOreOffsetY;
|
||
midPos.x -= flyOreOffsetX;
|
||
|
||
|
||
|
||
// 清理之前的 tween(如果有)
|
||
ore.transform.DOKill();
|
||
// 路径点(贝塞尔可近似成分段点,这里用三个点实现抛物)
|
||
Vector3[] path = { startPos, midPos, endPos };
|
||
// // 位移动画
|
||
// ore.transform.DOPath(path, oreFlightDuration, PathType.CatmullRom)
|
||
// .SetEase(oreFlightCurve)
|
||
// .OnComplete(() =>
|
||
// {
|
||
// // 动画结束确保位置对齐
|
||
// ore.transform.position = endPos;
|
||
// });
|
||
// 先清除旧的 tween
|
||
// ore.transform.DOKill();
|
||
var t = 0f;
|
||
Tween tween = DOTween.To(() => t, x =>
|
||
{
|
||
t = x;
|
||
float curveT = oreFlightCurve.Evaluate(t);
|
||
Vector3 pos = CalculateBezierPoint(curveT, startPos, midPos, endPos);
|
||
ore.transform.position = pos;
|
||
// ore.transform.Rotate(0, 0, 180 * Time.deltaTime / OreFlightDuration);
|
||
}, 1f, OreFlightDuration)
|
||
.SetEase(Ease.Linear)
|
||
.OnComplete(() => { ore.transform.position = endPos; });
|
||
|
||
// 如果要旋转,可以单独加一个旋转 tween
|
||
// ore.transform.DORotate(
|
||
// new Vector3(0, 0, 180), // 会被解释为目标角度(非增量)
|
||
// oreFlightDuration,
|
||
// RotateMode.FastBeyond360 // 表示可以旋转超过360度
|
||
// );
|
||
await tween.AsyncWaitForCompletion();
|
||
}
|
||
private static Vector3 CalculateBezierPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2)
|
||
{
|
||
var u = 1 - t;
|
||
var tt = t * t;
|
||
float uu = u * u;
|
||
return uu * p0 + 2 * u * t * p1 + tt * p2;
|
||
}
|
||
public void ResetTrain()
|
||
{
|
||
ELog($"ResetTrain count={trainCars.Length}");
|
||
foreach (var train in trainCars)
|
||
{
|
||
if (train == null) continue;
|
||
train?.ClearOres();
|
||
var carTransform = train.carTransform;
|
||
if (!train.oreContainer)
|
||
{
|
||
train.oreContainer = carTransform.Find("OreContainer");
|
||
}
|
||
if (!train.collectPoint)
|
||
{
|
||
train.collectPoint = carTransform.Find("CollectPoint");
|
||
}
|
||
train.bar = carTransform.Find("ui/bg_bar/bar").GetComponent<Image>();
|
||
train.bar.fillAmount = 0;
|
||
train.reward = carTransform.Find("ui/bg_bar/reward");
|
||
|
||
// var anim = train.oreContainer.GetComponent<Animation>();
|
||
// anim.GoToFrame(0);
|
||
|
||
var animController = train.oreContainer.GetComponent<OreIncreaseController>();
|
||
animController.Reset();
|
||
}
|
||
}
|
||
public void UpdateData(EventPartnerGatherManager manager)
|
||
{
|
||
ELog($"UpdateData-> {manager}");
|
||
_manager = manager;
|
||
var carNumber = trainCars.Length;
|
||
for (var i = 0; i < carNumber; ++i)
|
||
{
|
||
var trainCar = trainCars[i];
|
||
trainCar.maxCapacity = manager.NodeSteps[i];
|
||
trainCar.pointsRange = manager.NodeCumulativeSteps[i];
|
||
var rewardItemNew = trainCar.reward.GetComponent<RewardItemNew>();
|
||
var dropId = _manager.GetStageReward(i);
|
||
var listItems = _playerItemData.GetItemDataByDropIdLureInflation(_manager.GatherData.InflationRate, dropId);
|
||
rewardItemNew.SetData(listItems.FirstOrDefault());
|
||
}
|
||
}
|
||
//
|
||
private static void ELog(object t)
|
||
{
|
||
#if UNITY_EDITOR
|
||
Debug.Log($"<Color=cyan> EventPartnerTrainController {t} </color>");
|
||
#endif
|
||
}
|
||
|
||
}
|
||
} |