[2025/07/22]TIL

오수호·2025년 7월 22일

TIL

목록 보기
46/60

Timeline을 활용한 스킬 컷씬 생성 - 수정

이전에 Timeline을 활용하여 스킬 컷씬을 생성하는 포스트를 게시한 적이 있다.

이전 포스트

다만 현재시점에서 위의 포스트에서 잘못된 점들이 몇 가지 있어서 수정하려한다.

  1. 이전 포스트에서는 버추얼 카메라의 움직임과 이펙트가 발생할 오브젝트의 애니메이션트랙을 Apply Transform Offsets으로 두어야 한다고 생각했다.
    이는 Recorded Offset을 활용하기 위함이였다. 문제는, 내가 만든 타임라인에서 카메라와 이펙트가 움직이는 부분은 모두 clip이 아닌 타임라인 레코드 기능을 활용하여 키프레임을 통해서 움직이게 만들었다는 것이다. clip이 존재해야 clip offset을 변경하여, 클립이 재생되는 위치를 변경해 줄 수 있는데 clip이 존재하지 않기때문에 절대좌표위치를 변경할 방법이 없었다. 따라서 모든 애니메이션 트랙은 Apply Scene offsets로 변경해야한다.

  2. 모든 애니메이션의 시작 좌표는 0,0,0으로 해야한다. 앞선 포스팅을 보면 알 수 있다시피 Apply Scene Offsets방식의 Recorded Offsets은 에디터상에서 preview를 통해서만 정상적으로 적용이 되고, 실제로 플레이 모드에서는 적용되지 않는다. 따라서 이는 애니메이션의 키프레임을 통해서 시작위치를 초기화하는 방식으로 만들어야한다.

    하지만, 타임라인의 0프레임부터 시작좌표로 시작하면 이 변경사항을 유니티에서 감지하지 못한다. 따라서 0프레임에서 키프레임을 0,0,0으로 초기화해주고 (회전, 위치 값 모두) 1프레임부터 초기화 할 트랜스폼 값으로 초기화해주어야한다. 또한 타임라인을 플레이 하기 이전에 이를 적용해줄 함수를 콜해주어야한다.

    
    director.time = 0f;
    director.Evaluate();
    director.Play();
    

문제상황은 위와 같은데 이제부터는 새로 바꾼 방법으로 스킬 컷씬을 구현해보겠다.

  1. 애니메이션 트랙의 처음 시작위치가 Scene에서의 위치이므로, 타임라인이 재생되기 이전에 해당 오브젝트의 위치를 캐릭터 기준으로 초기화 해주어야한다.

       public void InitializeTimeline()
       {
           Unit attackerUnit = attacker as Unit;
           var pos = attackerUnit.transform.position;
           var rot = attackerUnit.transform.rotation;
           CurrentCameraController.Unfocus();
           CameraManager.Instance.followNextIEffectProvider = false;
           CurrentCameraController.transform.position = pos;
           CurrentCameraController.transform.rotation = rot;
           effectObject.transform.position = pos;
           effectObject.transform.rotation = rot;
       }

    위의 코드는 attacker를 받아온 상태에서 attacker의 위치를 기준으로 타임라인이 재생되기 이전에 초기화 해주는 구문이다. effectObject와 카메라의 위치를 모두 공격자를 기준으로 초기화해주는 것을 알 수 있다. 또한 현재 게임에서는 턴이 넘어갈때마다 자동으로 SkillCamera가 턴이 넘어가는 유닛을 자동으로 추적하기때문에 해당 기능을 꺼주기 위해서 CameraManager에 있는 followNextIEffectProvider를 false로 만들어준다.

  2. 스킬이펙트의 발생과 실제 효과적용을 분리

    기존에는 스킬이 사용하면 해당 스킬데이터의 VFX를 자동으로 재생시켜주었다. 하지만, Timeline에서 스킬이펙트가 발생하는 위치와 스킬의 효과가 적용되는 시점을 시그널로 타이밍을 잡아주려고 하니 합쳐져있는 기능이 발목을 잡았다. 따라서 이를 분리하는 작업을 실행했다.

    기존코드

    public class RangeSkillSO : RangeActionSo
    {
       public override AttackDistanceType DistanceType => AttackDistanceType.Range;
       public override CombatActionSo     ActionSo     => this;
    
       public override void Execute(IAttackable attacker, IDamageable target)
       {
           var skillController = attacker.SkillController;
           var currentSkill = skillController.CurrentSkillData;
           // PlayableAsset timeline = currentSkill.skillSo.skillTimeLine;
           // if (timeline != null)
           // {
           //     TimeLineManager.Instance.director.Play(timeline);
           // }
           
           foreach (var data in currentSkill.skillSo.effect.skillEffectDatas)
           {
               VFXController.VFXListPlay(data.skillVFX,VFXType.Cast,VFXSpawnReference.Target, target as IEffectProvider,true);
               VFXController.VFXListPlay(data.skillVFX,VFXType.Cast,VFXSpawnReference.Caster, attacker as IEffectProvider,true);
           }
           
           foreach (var effect in currentSkill.Effect.skillEffectDatas)
           {
               List<IDamageable> targets = skillController.SkillSubTargets[effect];
               foreach (IDamageable unit in targets)
               {
                   if (unit == null) continue;
                   if (effect.projectilePrefab != null)
                   {   
                       GameObject projectile = ObjectPoolManager.Instance.GetObject(effect.projectilePoolID);
                       if (projectile == null)
                           projectile = Instantiate(effect.projectilePrefab);
                       ProjectileComponent = projectile.GetComponent<PoolableProjectile>();
                       ProjectileComponent.Initialize(effect, attacker.Collider.bounds.center, unit.Collider.bounds.center, unit);
                   }
                   else
                   {
                       effect.AffectTargetWithSkill(unit);
                   }
               }
           }
    
           if (ProjectileComponent != null)
           {
               ProjectileComponent.trigger.OnTriggerTarget += ResetProjectile;
           }
       }

    VFX.Controller.VFXListPlay가 VFX를 재생해주는 코드이다. 이를 아래와 같이 바꾸었다.

    
    public abstract class CombatActionSo : ScriptableObject, IAttackAction
    {
       public abstract void               Execute(IAttackable attacker, IDamageable target);
       public abstract AttackDistanceType DistanceType { get; }
       public virtual  CombatActionSo     ActionSo     => this;
    
       public virtual void PlayVFX(IAttackable attacker, IDamageable target)
       {
           foreach (var data in attacker.SkillController.CurrentSkillData.skillSo.effect.skillEffectDatas)
           {
               VFXController.VFXListPlay(data.skillVFX,VFXType.Cast,VFXSpawnReference.Target, target as IEffectProvider,true);
               VFXController.VFXListPlay(data.skillVFX,VFXType.Cast,VFXSpawnReference.Caster, attacker as IEffectProvider,true);
           }
       }
    }
    public class RangeSkillSO : RangeActionSo
    {
      public override AttackDistanceType DistanceType => AttackDistanceType.Range;
      public override CombatActionSo     ActionSo     => this;
    
      public override void Execute(IAttackable attacker, IDamageable target)
      {
          var skillController = attacker.SkillController;
          var currentSkill = skillController.CurrentSkillData;
          // PlayableAsset timeline = currentSkill.skillSo.skillTimeLine;
          // if (timeline != null)
          // {
          //     TimeLineManager.Instance.director.Play(timeline);
          // }
          
          foreach (var effect in currentSkill.Effect.skillEffectDatas)
          {
              List<IDamageable> targets = skillController.SkillSubTargets[effect];
              foreach (IDamageable unit in targets)
              {
                  if (unit == null) continue;
                  if (effect.projectilePrefab != null)
                  {   
                      GameObject projectile = ObjectPoolManager.Instance.GetObject(effect.projectilePoolID);
                      if (projectile == null)
                          projectile = Instantiate(effect.projectilePrefab);
                      ProjectileComponent = projectile.GetComponent<PoolableProjectile>();
                      ProjectileComponent.Initialize(effect, attacker.Collider.bounds.center, unit.Collider.bounds.center, unit);
                  }
                  else
                  {
                      effect.AffectTargetWithSkill(unit);
                  }
              }
          }
    
          if (ProjectileComponent != null)
          {
              ProjectileComponent.trigger.OnTriggerTarget += ResetProjectile;
          }
      }
    
    

    RangeSkillSO는 원거리 스킬을 구현해놓은 SO클래스인데, 부모에 해당하는 클래스에 VFX재생 메서드를 추가하여 모든 SkillSO에서 공통적으로 사용할 수 있도록 만들었다.
    이를 시그널에서 호출하여 원하는 타이밍에 스킬이펙트를 생성하고, 원하는 타이밍에 스킬효과를 적용할 수 있게 만들었다.

profile
게임개발자 취준생입니다

0개의 댓글