Work, Study/Unity

241224 회전하는 화살 구현하기

Waltwaez 2024. 12. 24. 22:37

https://www.youtube.com/watch?v=R4XsJ645l6E

 

어제 위 예제를 따라 VFX 그래프를 공부해서 화살 오브젝트를 만들었다.

이 예제에서는 VFX 그래프에서 자체적으로 Angle 값들을 설정하는데,

실제로 그대로 사용하려면 무리가 있었다.

 

이 글에서 구현한 것은 위 동영상의 12분 41초를 보면 나오는

자체적인 회전을 가진 화살 오브젝트인데 (롤의 애쉬 궁이 대표적)

 

내 프로젝트에서 사용하기 위해 아래 2가지를 구현해야 했다.

  1. 목표를 향한 3D 오브젝트의 회전
  2. 화살 오브젝트의 회전 유지

 

1. VFX 그래프

 

우선 VFX의 Output Particle Mesh 부분이다.

Angle 부분만 주목하면 된다. Add로 넣었는데 Set(Overwrite)으로 해도 상관 없을듯?

 

처음에는 외부의 방향 벡터를 받아온 다음에 VFX 그래프 내부에서

회전 축을 계산하는 방식으로 사용하려고 했는데, "방향 벡터"라는 개념과 "회전" 이라는 개념을 헷갈렸다.

 

VFX 그래프에서 받는 Angle은 Vector3이며, 단위는 오일러 각이다.

90도를 수직이라고 하는 그 단위가 맞다.

 

하지만 스크립트 단위에서 일반적으로 사용되는 Rotation은 방향 벡터를 사용한다.

따라서 이 둘의 변환이 필요해지는데 이런 역할은 스크립트에서 진행하는 게 훨씬 편했다.

VFX 그래프는 Quaternion 개념을 사용하지 않는데, (최소한 나는 발견하지 못했음)

스크립트 단위에선 사용할 수 있고 오일러 각으로 변환하는 것도 편-안하기 때문이다.

 

따라서 VFX 그래프에서 프로퍼티로 EulerAngle을 노출시켰다.

오일러 각에 따라 3D 오브젝트의 커서는 날아가는 방향을 가리키도록 구현할 것이며,

방향 벡터 -> 오일러 각 변환 계산은 스크립트 단위에서 수행된다.

 

추가로, 여기서 사용하는 3D 오브젝트는 비대칭이고,

아무 조건도 주지 않았을 때 어떤 방향을 기본적으로 가리키는지에 대한 설정이 필요하다.

이거는 3D 모델을 어떻게 만들었냐에 따라 달라지므로, 모델을 씬에 띄워보고 어떤 방향을 가리키는지 봐야 한다.

 

 

나 같은 경우는 VFX 그래프에서 프로퍼티를 설정하고, 아무런 조작도 주지 않은 화살표의 방향을 보고

0, -1, 0으로 설정한 뒤 프로퍼티 BaseDirection으로 노출시켰다.

 

추가로 EulerAngle이라는 것도 구현을 해 뒀는데

스크립트 단위에서 방향 벡터들로 계산된 오일러 각이 들어오게 된다.

 

2. 스크립트

내 프로젝트의 Projectile.cs 부분이다.

target 변수를 가지며, 마지막으로 알려진 타겟의 위치로 방향을 바꿔가면서 이동하는 로직을 갖고 있다.

 

초기화나 할당 같은 부분은 다 생략하고, 어떤 식으로 계산이 되는 지만 보겠다.

  /// <summary>
      /// 방향이 있는 VFX의 초기 방향을 설정합니다.
      /// </summary>
      private void InitializeVFXDirection() 
      {
          if (vfx != null && vfx.HasVector3("BaseDirection"))
          {
              vfxBaseDirection = vfx.GetVector3("BaseDirection").normalized;
  
              // 초기 방향 계산
              Vector3 initialDirection = (lastKnownPosition - transform.position).normalized;
  
              // 기본 -> 목표 방향으로의 회전 계산해서 VFX에 전달
              Quaternion rotation = Quaternion.FromToRotation(vfxBaseDirection, initialDirection);
              Vector3 eulerAngles = rotation.eulerAngles;
  
              // VFX에 초기 회전 적용
              if (vfx.HasVector3("EulerAngle"))
              {
                  vfx.SetVector3("EulerAngle", eulerAngles);
              }
              if (vfx.HasVector3("FlyingDirection"))
              {
                  vfx.SetVector3("FlyingDirection", initialDirection);
              }
          }
      }

 

초기화 부분에서 동작하는 메서드로, VFX 그래프에서 BaseDirection 을 가져온 뒤 타겟의 알려진 마지막 위치 lastKnownPosition 으로 회전시키고, 그 값을 오일러 각으로 변환하는 로직을 갖고 있다.

 
    private void Update()
    {
...
        // 타겟이 살아 있다면 위치 갱신
        if (target != null)
        {
            lastKnownPosition = target.transform.position;
        }

        // 방향 계산 및 이동
        Vector3 direction = (lastKnownPosition - transform.position).normalized;
        transform.position += direction * speed * Time.deltaTime;

        // VFX의 방향 업데이트
        UpdateVFXDirection(direction);
...
    }

    /// <summary>
    /// 방향 벡터를 받아 VFX에 오일러 각으로 변환해 전달합니다.
    /// </summary>
    private void UpdateVFXDirection(Vector3 directionVector)
    {
        if (vfx != null)
        {
            if (vfx.HasVector3("FlyingDirection"))
            {
                vfx.SetVector3("FlyingDirection", directionVector);
            }

            // 이펙트의 방향에 따른 회전
            if (vfxBaseDirection != null)
            {
                Quaternion directionRotation = Quaternion.FromToRotation(vfxBaseDirection, directionVector);
                Vector3 eulerAngles = directionRotation.eulerAngles;

                // 자체적인 회전을 갖는 이펙트라면
                if (vfx.HasBool("SelfRotation"))
                {
                    currentRotation += rotationSpeed * Time.deltaTime;
                    currentRotation %= 360f; // 360도를 넘지 않는 정규화

                    // 진행 방향을 축으로 하는 자체 회전
                    Quaternion axialRotation = Quaternion.AngleAxis(currentRotation, directionVector);

                    // 회전 결합 (결합 순서가 중요함)
                    Quaternion finalRotation = axialRotation * directionRotation;
                    eulerAngles = finalRotation.eulerAngles;
                }


                if (vfx.HasVector3("EulerAngle"))
                {
                    vfx.SetVector3("EulerAngle", eulerAngles);
                }
            }
        }
    }

 

Update 문에서는 대상의 위치를 파악하고 해당 방향으로 direction 을 수정한 뒤 해당 방향으로 조금 이동한다.

UpdateVFXDirection 문에서

  1. 화살표의 방향 자체는 초기화 로직과 크게 다르지 않다.
  2. selfRotation 이라는 프로퍼티를 설정, 오브젝트가 자체적인 회전을 갖는 경우에는 오일러 각 구현에 추가적인 연산이 들어간다.

 

여기서 사용되는 currentRotation, rotationSpeed 는 모두 float 타입.

 

3. 결과

 

 

1. 타겟이 이동함에 따라 화살의 꼭짓점이 해당 타겟을 가리키고 있는 구현

2. 화살 자체의 회전

 

모두 정상적으로 동작하는 것을 볼 수 있다. 사진 2장이라 직관적으로 보이진 않지만..