using System.Collections.Generic; using Cielonos.MainGame.Characters; using Cielonos.MainGame.FunctionalAnimation; using Opsive.BehaviorDesigner.Runtime.Tasks; using Opsive.BehaviorDesigner.Runtime.Tasks.Actions; using Opsive.GraphDesigner.Runtime; using Opsive.GraphDesigner.Runtime.Variables; using Opsive.Shared.Utility; using SLSUtilities.FunctionalAnimation; using UnityEngine; namespace Cielonos.MainGame.Characters.AI { public enum PlayLoopReturnMode { ReturnImmediately, WaitForDuration, LoopIndefinitely } [Description("播放一个循环类型的功能动画,并可选择是立刻返回成功,等待规定时间后再返回,还是无限循环直到被外部节点打断。")] [NodeIcon("Assets/Sprites/Icon/Play.png")] [Category("Cielonos")] public class PlayLoopFuncAnim : AutomataActionBase { protected CharacterBase target; [Tooltip("是否选择其他目标,默认目标为玩家。")] public bool isOtherTarget = false; [Tooltip("如果选择了其他目标,则需要指定目标对象。")] public SharedVariable targetGameObject; private AnimationSubcontrollerBase animationSc => self.animationSc; private FuncAnimSubmodule funcAnimSm; private RuntimeFuncAnim funcAnim; [Header("Animation Settings")] [Tooltip("要播放的功能动画名称。必须是循环动画。")] public string animationName; [Tooltip("是否选择其他功能动画子模块,默认子模块为全身动作子模块。")] public bool isOtherFuncAnimSm = false; [Tooltip("如果选择了其他功能动画子模块,则需要指定子模块名称。")] public string funcAnimSmName = ""; [Tooltip("动画过渡时间,单位秒。")] public float transitionDuration = 0.1f; [Header("Return Mode")] [Tooltip("节点返回模式:\nReturnImmediately: 播放动画后立即返回 Success。\nWaitForDuration: 在指定时间内返回 Running,结束后返回 Success。\nLoopIndefinitely: 永远返回 Running,由外部(如 Parallel 节点)负责中止它。")] public PlayLoopReturnMode returnMode = PlayLoopReturnMode.ReturnImmediately; [Tooltip("播放持续时间(秒)。仅在 WaitForDuration 模式下有效。")] public SharedVariable playDuration = 2f; [Header("Behaviors")] [Tooltip("是否在开始播放动画时转向目标。")] public bool willTurnToTargetAtStart = true; [Tooltip("是否在播放过程中持续转向目标。")] public bool willTurnToTargetDuringPlaying = false; [Tooltip("是否调整根吸附以贴近目标。")] public bool willAdjustAdsorption = true; [Tooltip("是否持续调整根吸附以贴近目标,直到动画结束。")] public bool willKeepAdjustingAdsorption = false; [Tooltip("根吸附调整的最小距离。")] public float adsorptionMinDistance = 2; [Tooltip("如果动画在外力干预下被打断,是否当作执行成功。")] public bool disruptionReturnSuccess = true; private bool successPlayed = false; private bool frameBuffer; private float startTime; public override void OnAwake() { base.OnAwake(); funcAnimSm = isOtherFuncAnimSm ? null : animationSc.fullBodyFuncAnimSm; target = isOtherTarget ? targetGameObject.Value.GetComponent() : MainGameManager.Player; } public override void OnStart() { successPlayed = funcAnimSm.Play(animationName, 1, transitionDuration); frameBuffer = false; startTime = Time.time; if (successPlayed) { funcAnim = funcAnimSm.currentRuntimeFuncAnim; if (willTurnToTargetAtStart) { self.movementSc.SmartTurnToTarget(target); } if (willAdjustAdsorption) { if (!willKeepAdjustingAdsorption) { funcAnim.AddUpdateUntilEvent(new SetRootAdsorptionAdjustment.Once(target, adsorptionMinDistance)); } else { funcAnim.AddUpdateEvent(new SetRootAdsorptionAdjustment.Keep(target, adsorptionMinDistance)); } } } } public override TaskStatus OnUpdate() { // 播放失败时的 1 帧缓冲返回 if (!successPlayed) { if (!frameBuffer) { frameBuffer = true; return TaskStatus.Running; } else { return TaskStatus.Failure; } } // 被外部干预打断或覆盖时的 1 帧缓冲返回 if (funcAnim.isDisrupted || funcAnimSm.currentRuntimeFuncAnim != funcAnim) { if (!frameBuffer) { frameBuffer = true; return TaskStatus.Running; } else { return disruptionReturnSuccess ? TaskStatus.Success : TaskStatus.Failure; } } if (willTurnToTargetDuringPlaying) { self.movementSc.TurnToTargetByAngle(target, 360f); } // 根据返回模式判定 if (returnMode == PlayLoopReturnMode.ReturnImmediately) { if (!frameBuffer) { frameBuffer = true; return TaskStatus.Running; } else { return TaskStatus.Success; } } else if (returnMode == PlayLoopReturnMode.WaitForDuration) { if (Time.time - startTime >= playDuration.Value) { if (!frameBuffer) { frameBuffer = true; return TaskStatus.Running; } else { return TaskStatus.Success; } } } return TaskStatus.Running; } } }