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(); 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($"LegacyAnimExtensions -> {message}"); #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($"TrainCar-> {t} "); #endif } public void ClearOres() { // foreach (var ore in _collectedOres) // { // if (ore != null) // { // Object.DestroyImmediate(ore); // } // } // _collectedOres.Clear(); } } /// /// 事件合作伙伴火车控制器 /// 管理火车车厢系统,包括矿石收集、车厢管理、矿石飞行动画等 /// 建议挂载在:Train容器对象上,确保包含所有车厢的Transform /// /// 场景结构建议: /// Train (挂载此脚本) /// ├── TrainCar1 (第一节车厢) /// │ └── OreContainer (矿石容器) /// ├── TrainCar2 (第二节车厢) /// │ └── OreContainer (矿石容器) /// └── ... (更多车厢) /// 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(); 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(); train.bar.fillAmount = 0; train.reward = carTransform.Find("ui/bg_bar/reward"); // var anim = train.oreContainer.GetComponent(); // anim.GoToFrame(0); // 默认设置未 全部隐藏 var childAnim = train.oreContainer.GetComponent(); 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(); // anim.GoToFrame(Mathf.CeilToInt(fillAmount * 100)); var animController = targetCar.oreContainer.GetComponent(); 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("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().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(); // var clipName = anim.clip.name; // await anim.PlayAnimFromToAsync(this,clipName,startFrame,endFrame); var animController = targetCar.oreContainer.GetComponent(); 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(); train.bar.fillAmount = 0; train.reward = carTransform.Find("ui/bg_bar/reward"); // var anim = train.oreContainer.GetComponent(); // anim.GoToFrame(0); var animController = train.oreContainer.GetComponent(); 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(); 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($" EventPartnerTrainController {t} "); #endif } } }