Unity 피벗 포인트 문제와 해결 방법

CokeBear·2025년 8월 5일
0

Unity 피벗 포인트 문제와 해결 방법

Unity에서 3D 오브젝트를 다루다 보면 피벗 포인트(Pivot Point) 때문에 예상치 못한 문제들이 발생할 수 있습니다. 특히 레벨 디자인이나 조준 시스템을 구현할 때 피벗 포인트의 위치는 매우 중요한 요소가 됩니다. 이번 글에서는 피벗 포인트로 인한 문제점들과 이를 해결하는 다양한 방법을 알아보겠습니다.

피벗 포인트란?

피벗 포인트는 Unity에서 오브젝트의 기준점입니다. Transform.position이 바로 이 피벗 포인트의 위치를 나타내며, 오브젝트의 회전, 스케일링, 이동의 중심축 역할을 합니다.

Unity 에디터에서 피벗 포인트 확인하기

Scene 뷰 상단의 툴바에서 CenterPivot 사이를 전환할 수 있습니다:

  • Center: 오브젝트의 기하학적 중심점 표시
  • Pivot: 실제 피벗 포인트 위치 표시

피벗 포인트로 인한 주요 문제들

1. 레벨 디자인 시 오브젝트 배치 문제

가장 흔한 문제는 오브젝트 배치 시 발생하는 불편함입니다.

문제 상황

- Cube (Unity 기본 오브젝트): 피벗 포인트가 중심에 위치
- Barrel (외부 에셋): 피벗 포인트가 하단에 위치

Cube의 경우:

  • 바닥에 배치하면 절반이 땅속으로 들어감
  • 플랫폼 위에 놓으면 중간 높이에 떠있게 됨
  • 정확한 위치 조정을 위해 수동으로 Y값을 계속 조정해야 함

Barrel의 경우:

  • 피벗 포인트가 하단에 있어 바닥에 자연스럽게 배치됨
  • 레벨 디자인 시 훨씬 직관적

2. 조준 시스템에서의 부정확성

조준 시스템에서 Transform.position을 타겟으로 사용할 때:

// 현재 조준 시스템 (문제가 있는 코드)
aimPosition = target.transform.position;

결과:

  • 피벗이 중심에 있는 오브젝트: 중앙을 조준
  • 피벗이 하단에 있는 오브젝트: 바닥을 조준
  • 일관성 없는 조준으로 게임플레이 품질 저하

3. 회전 시 예상과 다른 동작

피벗 포인트가 잘못된 위치에 있으면:

  • 오브젝트가 이상한 축을 중심으로 회전
  • 바퀴나 기어 같은 회전 오브젝트에서 특히 문제 발생

해결 방법들

1. 3D 모델링 단계에서 해결 (근본적 해결)

Blender 등에서 피벗 포인트 조정:

  • 장점: 완전한 해결, 추가 작업 불필요
  • 단점: 에셋 수정 권한 필요, 기존 에셋 활용 어려움

2. 부모 오브젝트를 활용한 해결

Parent GameObject (빈 오브젝트)
└── Child GameObject (실제 모델)

구현 방법:
1. 빈 게임오브젝트 생성 (부모)
2. 실제 모델을 자식으로 배치
3. 자식 오브젝트의 위치를 조정하여 부모의 피벗을 원하는 위치로 설정
4. 타겟 컴포넌트, 콜라이더 등은 부모에 설정

장점:

  • 기존 에셋 수정 없이 해결
  • 완전한 제어 가능

단점:

  • 모든 오브젝트에 수동 작업 필요
  • 오브젝트 계층 복잡해짐

3. 스크립트를 통한 동적 해결 (권장)

가장 효율적인 해결 방법은 Renderer의 bounds.center를 활용하는 것입니다.

public class PlayerAim : MonoBehaviour
{
    private void UpdateAimPosition()
    {
        if (target != null)
        {
            // Renderer 컴포넌트가 있는지 확인
            Renderer targetRenderer = target.GetComponent<Renderer>();
            
            if (targetRenderer != null)
            {
                // 렌더러의 바운드 중심점을 조준점으로 설정
                aimPosition = targetRenderer.bounds.center;
            }
            else
            {
                // Renderer가 없다면 기존 방식 사용
                aimPosition = target.transform.position;
            }
        }
    }
}

이 방법의 장점:

  • 코드 한 줄로 해결
  • 모든 오브젝트에 자동 적용
  • 에셋 수정 불필요
  • 성능 영향 최소

4. 개선된 범용 타겟 시스템

더 정교한 시스템을 원한다면:

public class SmartTargetSystem : MonoBehaviour
{
    [System.Serializable]
    public class TargetInfo
    {
        public Transform target;
        public Vector3 customOffset = Vector3.zero;
        public bool useCustomOffset = false;
    }
    
    public Vector3 GetTargetPosition(Transform target)
    {
        // 1. 커스텀 타겟 포인트 확인
        TargetPoint customTarget = target.GetComponent<TargetPoint>();
        if (customTarget != null)
        {
            return customTarget.GetTargetPosition();
        }
        
        // 2. Renderer bounds 중심 사용
        Renderer renderer = target.GetComponent<Renderer>();
        if (renderer != null)
        {
            return renderer.bounds.center;
        }
        
        // 3. Collider 중심 사용
        Collider col = target.GetComponent<Collider>();
        if (col != null)
        {
            return col.bounds.center;
        }
        
        // 4. 기본값: Transform 위치
        return target.position;
    }
}

// 특별한 타겟 포인트가 필요한 오브젝트용 컴포넌트
public class TargetPoint : MonoBehaviour
{
    [SerializeField] private Vector3 localOffset = Vector3.zero;
    
    public Vector3 GetTargetPosition()
    {
        return transform.position + transform.TransformDirection(localOffset);
    }
    
    private void OnDrawGizmosSelected()
    {
        Gizmos.color = Color.red;
        Gizmos.DrawWireSphere(GetTargetPosition(), 0.1f);
    }
}

실제 적용 사례

레벨 디자인 워크플로우 개선

[System.Serializable]
public class PropPlacementHelper : MonoBehaviour
{
    [Header("Auto-Snap to Ground")]
    public bool snapToGround = true;
    public LayerMask groundLayer = 1;
    
    private void Start()
    {
        if (snapToGround)
        {
            SnapToGround();
        }
    }
    
    private void SnapToGround()
    {
        Renderer renderer = GetComponent<Renderer>();
        if (renderer != null)
        {
            Vector3 bottomPoint = renderer.bounds.min;
            
            if (Physics.Raycast(transform.position, Vector3.down, out RaycastHit hit, Mathf.Infinity, groundLayer))
            {
                float offset = transform.position.y - bottomPoint.y;
                transform.position = new Vector3(transform.position.x, hit.point.y + offset, transform.position.z);
            }
        }
    }
}

성능 고려사항

Renderer.bounds는 매 프레임 계산할 필요가 없으므로:

public class OptimizedTargetSystem : MonoBehaviour
{
    private Dictionary<Transform, Vector3> cachedTargetPositions = new Dictionary<Transform, Vector3>();
    
    public Vector3 GetCachedTargetPosition(Transform target)
    {
        if (!cachedTargetPositions.ContainsKey(target))
        {
            CacheTargetPosition(target);
        }
        
        return cachedTargetPositions[target];
    }
    
    private void CacheTargetPosition(Transform target)
    {
        Renderer renderer = target.GetComponent<Renderer>();
        Vector3 targetPos = renderer != null ? renderer.bounds.center : target.position;
        cachedTargetPositions[target] = targetPos;
    }
}

결론

피벗 포인트 문제는 Unity 개발에서 자주 마주치는 일반적인 이슈입니다. 가장 효율적인 해결책은 Renderer.bounds.center를 활용한 스크립트 기반 접근입니다. 이 방법은:

  • 최소한의 코드로 최대 효과
  • 기존 에셋 활용 가능
  • 자동화된 해결책
  • 확장 가능한 시스템

Unity 개발의 핵심은 문제 해결 능력입니다. 피벗 포인트 같은 기본적인 이슈부터 시작해서 점진적으로 복잡한 문제들을 해결하는 경험을 쌓아가시기 바랍니다. 이러한 문제 해결 스킬은 게임 개발뿐만 아니라 실생활에서도 큰 도움이 될 것입니다.

profile
back end developer

0개의 댓글