From 035a87e032237622a292aee9b8b0d2cd48a75343 Mon Sep 17 00:00:00 2001 From: dal4segno Date: Wed, 11 Mar 2026 17:51:52 +0900 Subject: [PATCH] =?UTF-8?q?[Enemy]=20=EC=A0=81=20=EC=95=A0=EB=8B=88?= =?UTF-8?q?=EB=A9=94=EC=9D=B4=EC=85=98=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EB=9F=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ultraworked with [Sisyphus](https://github.com/code-yeonggu/oh-my-opencode) Co-authored-by: Sisyphus --- .../EnemyAnimationController.controller | 257 ++++++++++++++++++ .../EnemyAnimationController.controller.meta | 8 + .../Scripts/Enemy/EnemyAnimationController.cs | 107 ++++++++ .../Enemy/EnemyAnimationController.cs.meta | 2 + 4 files changed, 374 insertions(+) create mode 100644 Assets/Animations/EnemyAnimationController.controller create mode 100644 Assets/Animations/EnemyAnimationController.controller.meta create mode 100644 Assets/Scripts/Enemy/EnemyAnimationController.cs create mode 100644 Assets/Scripts/Enemy/EnemyAnimationController.cs.meta diff --git a/Assets/Animations/EnemyAnimationController.controller b/Assets/Animations/EnemyAnimationController.controller new file mode 100644 index 00000000..20326bfa --- /dev/null +++ b/Assets/Animations/EnemyAnimationController.controller @@ -0,0 +1,257 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1102 &-7908033645098541312 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Idle + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: + - {fileID: 3552691702041008167} + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 1375766457120316241, guid: 12f6f354f8fc3c14a981342fc460cc7e, type: 3} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!1107 &-5279407469294999195 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Base Layer + m_ChildStates: + - serializedVersion: 1 + m_State: {fileID: -7908033645098541312} + m_Position: {x: 30, y: 180, z: 0} + - serializedVersion: 1 + m_State: {fileID: -4652545120162758660} + m_Position: {x: 290, y: 180, z: 0} + - serializedVersion: 1 + m_State: {fileID: -2487449162152911812} + m_Position: {x: -210, y: 350, z: 0} + m_ChildStateMachines: [] + m_AnyStateTransitions: [] + m_EntryTransitions: [] + m_StateMachineTransitions: {} + m_StateMachineBehaviours: [] + m_AnyStatePosition: {x: -190, y: 270, z: 0} + m_EntryPosition: {x: 50, y: 120, z: 0} + m_ExitPosition: {x: 800, y: 120, z: 0} + m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} + m_DefaultState: {fileID: -7908033645098541312} +--- !u!1102 &-4652545120162758660 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Move + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: + - {fileID: 8997152603305261974} + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: -1886843759694807048} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!1102 &-2487449162152911812 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Skill + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: [] + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: -7717634560727564301, guid: 079bd00af1b92964d8973dcbf2dcd21f, type: 3} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!206 &-1886843759694807048 +BlendTree: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Blend Tree + m_Childs: + - serializedVersion: 2 + m_Motion: {fileID: 4441030841997727119, guid: 005ddab128d7eb141b25099c0eb1ed39, type: 3} + m_Threshold: 0 + m_Position: {x: 0, y: 0} + m_TimeScale: 1 + m_CycleOffset: 0 + m_DirectBlendParameter: Speed + m_Mirror: 0 + - serializedVersion: 2 + m_Motion: {fileID: 2571081563578608591, guid: e6a2aa1552aac6f46b40192ec605d57e, type: 3} + m_Threshold: 0.5 + m_Position: {x: 0, y: 0} + m_TimeScale: 1 + m_CycleOffset: 0 + m_DirectBlendParameter: Speed + m_Mirror: 0 + - serializedVersion: 2 + m_Motion: {fileID: 1827226128182048838, guid: 60fb511bc6259b745b432aea6a222303, type: 3} + m_Threshold: 1 + m_Position: {x: 0, y: 0} + m_TimeScale: 1 + m_CycleOffset: 0 + m_DirectBlendParameter: Speed + m_Mirror: 0 + m_BlendParameter: Speed + m_BlendParameterY: Speed + m_MinThreshold: 0 + m_MaxThreshold: 1 + m_UseAutomaticThresholds: 1 + m_NormalizedBlendValues: 0 + m_BlendType: 0 +--- !u!91 &9100000 +AnimatorController: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: EnemyAnimationController + serializedVersion: 5 + m_AnimatorParameters: + - m_Name: Speed + m_Type: 1 + m_DefaultFloat: 0 + m_DefaultInt: 0 + m_DefaultBool: 0 + m_Controller: {fileID: 9100000} + - m_Name: IsGrounded + m_Type: 4 + m_DefaultFloat: 0 + m_DefaultInt: 0 + m_DefaultBool: 0 + m_Controller: {fileID: 9100000} + - m_Name: Jump + m_Type: 9 + m_DefaultFloat: 0 + m_DefaultInt: 0 + m_DefaultBool: 0 + m_Controller: {fileID: 9100000} + - m_Name: Land + m_Type: 9 + m_DefaultFloat: 0 + m_DefaultInt: 0 + m_DefaultBool: 0 + m_Controller: {fileID: 9100000} + - m_Name: Attack + m_Type: 9 + m_DefaultFloat: 0 + m_DefaultInt: 0 + m_DefaultBool: 0 + m_Controller: {fileID: 9100000} + - m_Name: Skill + m_Type: 9 + m_DefaultFloat: 0 + m_DefaultInt: 0 + m_DefaultBool: 0 + m_Controller: {fileID: 9100000} + m_AnimatorLayers: + - serializedVersion: 5 + m_Name: Base Layer + m_StateMachine: {fileID: -5279407469294999195} + m_Mask: {fileID: 0} + m_Motions: [] + m_Behaviours: [] + m_BlendingMode: 0 + m_SyncedLayerIndex: -1 + m_DefaultWeight: 0 + m_IKPass: 0 + m_SyncedLayerAffectsTiming: 0 + m_Controller: {fileID: 9100000} +--- !u!1101 &3552691702041008167 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 3 + m_ConditionEvent: Speed + m_EventTreshold: 0.01 + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: -4652545120162758660} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.25 + m_TransitionOffset: 0 + m_ExitTime: 0.75 + m_HasExitTime: 0 + m_HasFixedDuration: 1 + m_InterruptionSource: 0 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 +--- !u!1101 &8997152603305261974 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 4 + m_ConditionEvent: Speed + m_EventTreshold: 0.03 + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: -7908033645098541312} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.1 + m_TransitionOffset: 0 + m_ExitTime: 0.9977373 + m_HasExitTime: 0 + m_HasFixedDuration: 0 + m_InterruptionSource: 0 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 diff --git a/Assets/Animations/EnemyAnimationController.controller.meta b/Assets/Animations/EnemyAnimationController.controller.meta new file mode 100644 index 00000000..8cddabbb --- /dev/null +++ b/Assets/Animations/EnemyAnimationController.controller.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4bd980f1a222c5b468136f7e717925d5 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 9100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Enemy/EnemyAnimationController.cs b/Assets/Scripts/Enemy/EnemyAnimationController.cs new file mode 100644 index 00000000..0f260111 --- /dev/null +++ b/Assets/Scripts/Enemy/EnemyAnimationController.cs @@ -0,0 +1,107 @@ +using UnityEngine; +using Unity.Netcode; + +namespace Colosseum.Enemy +{ + /// + /// 적 캐릭터 애니메이션 컨트롤러 + /// NavMeshAgent 속도에 따라 Idle/Move 애니메이션 제어 + /// + public class EnemyAnimationController : NetworkBehaviour + { + [Header("Animation Parameters")] + [SerializeField] private string speedParam = "Speed"; + [SerializeField] private string attackTriggerParam = "Attack"; + [SerializeField] private string skillTriggerParam = "Skill"; + + [Header("Settings")] + [SerializeField] private float speedSmoothTime = 0.1f; + [SerializeField] private float stopThreshold = 0.05f; + + private Animator animator; + private UnityEngine.AI.NavMeshAgent navMeshAgent; + private float currentSpeed; + private float speedVelocity; + + private void Awake() + { + animator = GetComponent(); + navMeshAgent = GetComponent(); + } + + public override void OnNetworkSpawn() + { + if (!IsServer) + { + enabled = false; + } + } + + private void Update() + { + if (!IsServer) return; + + UpdateAnimationParameters(); + } + + /// + /// 애니메이션 파라미터 업데이트 + /// + private void UpdateAnimationParameters() + { + if (animator == null || navMeshAgent == null) + return; + + // NavMeshAgent의 속도를 기반으로 타겟 속도 계산 + float targetSpeed = navMeshAgent.velocity.magnitude; + + // 정지 임계값 처리 + if (targetSpeed < stopThreshold) + targetSpeed = 0f; + + // 부드러운 속도 변화 + currentSpeed = Mathf.SmoothDamp(currentSpeed, targetSpeed, ref speedVelocity, speedSmoothTime); + + // 애니메이터 파라미터 설정 + animator.SetFloat(speedParam, currentSpeed); + } + + /// + /// 공격 애니메이션 트리거 (외부에서 호출) + /// + public void PlayAttack() + { + if (IsServer && animator != null) + { + animator.SetTrigger(attackTriggerParam); + } + } + /// + /// 스킬 애니메이션 트리거 (외부에서 호출) + /// + public void PlaySkill() + { + if (IsServer && animator != null) + { + animator.SetTrigger(skillTriggerParam); + } + } + + /// + /// 특정 애니메이션 트리거 재생 + /// + /// 트리거 파라미터 이름 + public void PlayTrigger(string triggerName) + { + if (IsServer && animator != null) + { + animator.SetTrigger(triggerName); + } + } + + /// + /// 현재 애니메이션 속도 + /// + public float CurrentSpeed => currentSpeed; + } +} diff --git a/Assets/Scripts/Enemy/EnemyAnimationController.cs.meta b/Assets/Scripts/Enemy/EnemyAnimationController.cs.meta new file mode 100644 index 00000000..6d93f64f --- /dev/null +++ b/Assets/Scripts/Enemy/EnemyAnimationController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5443105bfa8e570438bb5dc62c58aca9 \ No newline at end of file