using System.Collections.Generic; using Sirenix.OdinInspector; using Spine; using Spine.Unity; using UnityEngine; using AnimationState = Spine.AnimationState; using Random = UnityEngine.Random; namespace DDD { public class SpineController : MonoBehaviour { // Variables #region Variables private SkeletonAnimation _skeletonAnimation; [SerializeField, ReadOnly] private Material _originalMaterial; [SerializeField] private Material _replacementMaterial; private AnimationState _animationState; // Variables [SerializeField] private bool _isSkinSet = true; [SerializeField, ShowIf("@_isSkinSet && !_isRandomSkin")] private string _initialSkinName = "default"; [SerializeField, ShowIf("@_isSkinSet")] private bool _isRandomSkin; [SerializeField, ShowIf("@_isSkinSet && _isRandomSkin")] private bool _isRandomRange; [SerializeField, ShowIf("@_isSkinSet && _isRandomSkin && _isRandomRange"), Tooltip("x <= 값 < y")] private Vector2 _randomRange; [SerializeField, ShowIf("@_isSkinSet && _isRandomSkin && !_isRandomRange")] private List _randomStrings; private bool _customMaterialEnabled; #endregion // Unity events #region Unity events private void Reset() { InitializeComponents(); } private void Awake() { InitializeComponents(); if (!_isSkinSet) return; if (_isRandomSkin) { if (_isRandomRange) { SetRandomSkin(); } else { SetRandomStringListSkin(); } } else { SetSkin(_initialSkinName); } } #endregion // Initialize methods #region Initialize methods private void InitializeComponents() { _skeletonAnimation = transform.GetComponentInChildren(); if (!_originalMaterial) { _originalMaterial = _skeletonAnimation.SkeletonDataAsset.atlasAssets[0].PrimaryMaterial; } _animationState = _skeletonAnimation.AnimationState; } #endregion // Methods #region Methods /// 스파인 애니메이션 이름 /// 반복 여부 /// 애니메이션 속도 양수값 /// true인 경우 자동으로 speed에 음수값(-1)을 넣음 /// /// public TrackEntry PlayAnimation(string animationName, bool isLoopActive, float speed = 1f, bool isReverse = false, int trackIndex = 0) { if (!_skeletonAnimation || _animationState == null) return null; if (string.IsNullOrEmpty(animationName)) { Debug.LogError($"{animationName}의 애니메이션은 존재하지 않습니다."); return null; } // 중복 체크 var currentTrackEntry = _animationState.GetCurrent(trackIndex); if (currentTrackEntry != null && currentTrackEntry.Animation.Name == animationName) { return currentTrackEntry; } _animationState.TimeScale = isReverse ? -Mathf.Abs(speed) : Mathf.Abs(speed); var trackEntry = _animationState.SetAnimation(trackIndex, animationName, isLoopActive); if (isReverse) { trackEntry.TrackTime = trackEntry.AnimationEnd; } return trackEntry; } public TrackEntry PlayAnimationDuration(string animationName, bool isLoopActive, float duration, bool isReverse = false, int trackIndex = 0) { if (!_skeletonAnimation || _animationState == null) return null; if (string.IsNullOrEmpty(animationName)) { Debug.LogError($"{animationName}의 애니메이션은 존재하지 않습니다."); return null; } // 중복 체크 var currentTrackEntry = _animationState.GetCurrent(trackIndex); if (currentTrackEntry != null && currentTrackEntry.Animation.Name == animationName) { return currentTrackEntry; } var findAnimation = _skeletonAnimation.Skeleton.Data.FindAnimation(animationName); if (findAnimation == null) { Debug.LogError($"{animationName} 애니메이션을 찾을 수 없습니다."); return null; } var speed = findAnimation.Duration / duration; _animationState.TimeScale = isReverse ? -Mathf.Abs(speed) : Mathf.Abs(speed); var trackEntry = _animationState.SetAnimation(trackIndex, animationName, isLoopActive); if (isReverse) { trackEntry.TrackTime = trackEntry.AnimationEnd; } return trackEntry; } public TrackEntry AddAnimation(string animationName, bool isLoopActive, int trackIndex = 0) { if (!_skeletonAnimation || _animationState == null) return null; if (string.IsNullOrEmpty(animationName)) { Debug.LogError($"{animationName} 애니메이션은 존재하지 않습니다."); return null; } var trackEntry = _animationState.AddAnimation(trackIndex, animationName, isLoopActive, 0); return trackEntry; } public string GetCurrentSkin() { if (_skeletonAnimation == null) return null; return _skeletonAnimation.Skeleton.Skin.ToString(); } public void SetSkin(string skinName) { if (_skeletonAnimation == null && _animationState == null) return; if (string.IsNullOrEmpty(skinName)) { Debug.LogError($"{skinName}의 스킨 이름은 존재하지 않습니다."); return; } _skeletonAnimation.Skeleton.SetSkin(skinName); _skeletonAnimation.Skeleton.SetSlotsToSetupPose(); _animationState.Apply(_skeletonAnimation.Skeleton); } public void SetRandomSkin() { if (_skeletonAnimation == null || _skeletonAnimation.Skeleton == null) return; var skins = _skeletonAnimation.skeleton.Data.Skins; var randomSkin = Random.Range((int)_randomRange.x, (int)_randomRange.y); var randomSkinName = skins.Items[randomSkin].Name; SetSkin(randomSkinName); } public void SetRandomStringListSkin() { if (_skeletonAnimation == null || _skeletonAnimation.Skeleton == null) return; if (_randomStrings == null || _randomStrings.Count <= 0) { Debug.LogError("_randomStrings 설정 오류"); return; } var randomSkin = Random.Range(0, _randomStrings.Count); var randomSkinName = _randomStrings[randomSkin]; SetSkin(randomSkinName); } public void EnableCustomMaterial() { if (_customMaterialEnabled) return; _skeletonAnimation.CustomMaterialOverride[_originalMaterial] = _replacementMaterial; _customMaterialEnabled = true; } public void DisableCustomMaterial() { if (!_customMaterialEnabled) return; _skeletonAnimation.CustomMaterialOverride.Remove(_originalMaterial); _customMaterialEnabled = false; } public bool IsAnimationComplete(int trackIndex = 0) { if (!_skeletonAnimation || _animationState == null) return false; var currentTrackEntry = _animationState.GetCurrent(trackIndex); if (currentTrackEntry == null) { Debug.LogWarning($"트랙 {trackIndex}에서 재생 중인 애니메이션이 없습니다."); return false; } return currentTrackEntry.IsComplete; } #endregion } }