using System; using System.Collections; using System.Collections.Generic; using Ichni.RhythmGame.Beatmap; using UniRx; using UnityEngine; namespace Ichni.RhythmGame { /// /// 将物体的z轴指向目标物体,注意,LookAt的启用期间,物体的旋转将被锁定 /// public partial class LookAt : AnimationBase { #region [暴露属性字段与关联] Exposed Fields & References public GameElement targetGameElement; public TransformSubmodule targetTransformSubmodule; public FlexibleBool enabling; #endregion #region [生命周期与工厂] Lifecycle & Factory public static LookAt GenerateElement(string elementName, Guid id, List tags, bool isFirstGenerated, GameElement animatedObject, GameElement lookAtTarget, FlexibleBool enabling) { LookAt look = Instantiate(GameManager.Instance.basePrefabs.emptyObject).AddComponent(); look.Initialize(elementName, id, tags, isFirstGenerated, animatedObject); look.animatedObject = animatedObject; look.enabling = enabling; look.animationReturnType = FlexibleReturnType.Before; look.targetGameElement = lookAtTarget; look.targetTransformSubmodule = (animatedObject as IHaveTransformSubmodule).transformSubmodule; //look.timeDurationSubmodule.SetDuration(-999f, 999f); //TODO: 换为(-delay, songLength) return look; } public override void SetDefaultSubmodules() { timeDurationSubmodule = new TimeDurationSubmodule(this); } public override void AfterInitialize() { base.AfterInitialize(); // 注册 Phase 7(Effect),确保在 Track/TrackFollower(Phase 4/5)和 Note(Phase 6) // 全部更新完毕后再计算旋转覆盖,避免因位置尚未就绪导致的旋转抖动 CoreServices.UpdateScheduler.Register(UpdatePhase.Effect, this); } public override void OnDelete() { base.OnDelete(); CoreServices.UpdateScheduler.Unregister(UpdatePhase.Effect, this); } public override void ManualUpdate(float songTime, bool forceUpdate = false) { base.ManualUpdate(songTime, forceUpdate); // 动画结束时同步注销 Phase 7 if (timeDurationSubmodule.CheckAfterEndTime(songTime)) { CoreServices.UpdateScheduler.Unregister(UpdatePhase.Effect, this); } } #endregion #region [核心动画逻辑] Core Animation Logic public override void ScheduledUpdate(UpdatePhase phase, float songTime) { switch (phase) { case UpdatePhase.Animation: base.ScheduledUpdate(phase, songTime); break; case UpdatePhase.Effect: // 在所有 Track/TrackFollower/Note 位置更新完毕后覆盖旋转 if (enabling.value) { (animatedObject as IHaveTransformSubmodule)?.UpdateLookAt(this); } break; } } protected override void UpdateAnimation(float songTime, bool forceUpdate) { if (targetGameElement is null) return; enabling.UpdateFlexibleBool(songTime); if (!targetTransformSubmodule.eulerAnglesOffsetLock || enabling.value) { targetTransformSubmodule.eulerAnglesOffsetLock = enabling.value; } if (enabling.value) { animationReturnType = FlexibleReturnType.MiddleExecuting; targetTransformSubmodule.eulerAnglesDirtyMark = true; } else if (animationReturnType != FlexibleReturnType.MiddleInterval) { animationReturnType = FlexibleReturnType.MiddleInterval; targetTransformSubmodule.eulerAnglesDirtyMark = true; } } public override void ApplyTimeOffset(float offset) { base.ApplyTimeOffset(offset); enabling.animations.ForEach(anim => anim.ApplyTimeOffset(offset)); } #endregion } }