반응형

 

 

 

 

보통 카메라 흔드는 기능에 대해 구글링을 해보면 Random.insideUnitCircle이나 Sphere로 카메라를 흔드는 코드를 많이 볼 수 있었다. 그런데 이런식으로 구성을하면 끊기면서 흔들리는(?) 느낌이 강하게 들어서 좀 더 찾아보니 관련된 에셋이 몇개 있었는데 받아서 뜯어보니 Mathf.PerlinNoise라는 노이즈 함수를 이용해서 카메라를 흔드는것을 봤다.

 

에셋에서는 위치 및 회전값까지 변경해서 건드리고있었는데, 회전값까지 변경하는 코드는 카메라 흔들림이 너무 정신없어서 전부 빼고 x, y 좌표만 변경하는 방법으로 코드를 바꿨다.

 

※ Perlin noise (펄린노이즈)

- 단계적 텍스처를 만들기 위해 개발된 노이즈 함수, 지형(마인크래프트 등)을 생성할 때 이용한다고한다.

- 위키 링크 (영어)

- 유니티 링크 (영어)

 

 

 

 

코드

using System.Collections;
using UnityEngine;

public class CamShake : MonoBehaviour
{
    [SerializeField]
    private float m_roughness;      //거칠기 정도
    [SerializeField]
    private float m_magnitude;      //움직임 범위

    private void Update()
    {
        if(Input.GetKeyDown(KeyCode.Space))
        {
            StartCoroutine(Shake(1f));
        }
    }

    IEnumerator Shake(float duration)
    {
        float halfDuration = duration / 2;
        float elapsed = 0f;
        float tick = Random.Range(-10f, 10f);

        while (elapsed < duration)
        {
            elapsed += Time.deltaTime / halfDuration;

            tick += Time.deltaTime * m_roughness;
            transform.position = new Vector3(
                Mathf.PerlinNoise(tick, 0) - .5f,
                Mathf.PerlinNoise(0, tick) - .5f,
                0f) * m_magnitude * Mathf.PingPong(elapsed, halfDuration);

            yield return null;
        }
    }
}

※ roughness : 거칠기 정도, 카메라가 움직이는 동안 카메라의 떨림을 제어하는 수치이다. 값이 높아질수록 카메라가 움직이는 시간동안 많이 떨린다.

※ magnitude : 카메라 움직임 범위, 카메라가 움직이는 범위 수치이다.

※ tick : 펄린노이즈 함수에 들어갈 값이다. 초반에 랜덤으로 값을 설정하는 이유는 노이즈 함수에 들어가는 값을 다르게하기 위해서이다. (항상 같은 움직임으로 흔들게 하고싶으면 틱값을 랜덤이 아닌 고정값으로 초기화하면 된다.)

 

 

 

 

결과

 

 

 

 

 

 

 

반응형
반응형

 

 

 

 

NavMeshAgent에 Angular Speed를 아무리 높게 줘도 회전이 늦게 일어난다. (해당 프로퍼티가 초당 회전각도로 알고있는데 아무리 수치를 높여도 회전이 느림)

위와 같이 회전이 몬스터 같은 유닛의 경우 어색할 가능성이 다분하다.

그래서 NavMeshAgent.updateRotation의 값을 false로 설정해준 뒤, 직접 각을 계산해서 넣어줘야한다.

 

 

 

공통

private NavMeshAgent m_agent;

void Start()
{
    m_agent = GetComponent<NavMeshAgent>();
    m_agent.updateRotation = false;
}

 

 

 

 

1. NavMeshAgent의 DesireVelocity값을 이용해 회전값 조정

- NavMeshAgent.DesireVelocity - 회피를 고려한 목표 속도

- Qaternion.LookRotation을 이용해서 회전값을 적용해주면 된다.

void Update()
{
    transform.rotation = Quaternion.LookRotation(m_agent.desireVelocity);
}

 

 

- 결과

그런데 한가지 문제가 있다면 커브길 주변에서 유닛이 떨리는 현상이 발견된다. (실제로 적용해보면 심각할정도로 떨린다.)

 

 

 

2. 직접 각을 계산해서 적용하기

- NavMeshAgent.steeringTarget값을 이용해 회전 값을 계산해서 적용하기

void Update()
{
    Vector2 forward = new Vector2(transform.position.z, transform.position.x);
    Vector2 steeringTarget = new Vector2(m_agent.steeringTarget.z, m_agent.steeringTarget.x);
    
    //방향을 구한 뒤, 역함수로 각을 구한다.
    Vector2 dir = steeringTarget - forward;
    float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
    
    //방향 적용
    transform.eulerAngles = Vector3.up * angle;
}

※ 3차원 공간에서는 전진 방향이 z축이니 Vector2의 x값을 transform.position.z값으로 설정한다.

※ steeringTarget : 경로상의 다음 목적지.

 

- 결과

1번과 다르게 떨리는 현상 없이 바로 회전하는것을 볼 수 있다.

 

 

 

 

 

반응형
반응형

 

 

 

 

Navigation Bake (Baked Agent)

  • Agent Radius : Agent의 반지름, Agent와 벽까지의 거리이다. 해당 반지름의 길이만큼 Agent가 벽에 붙어서 움직일 수 있다.
  • Agent Height : Agent의 높이, Agent의 높이를 지정해서 천장이 있는 지형의 통과 여부를 결정한다.
  • Max Slope : 이동 가능한 경사도
  • Step Height : 이동 가능한 단차의 한계 값, 설정한 수치만큼의 단차를 이동 할 수 있다.

※ Agent Height에 따른 네비메쉬 차이

 

 

NavMeshAgent Component

- A* PathFinding 알고리즘을 사용하여 길을 탐색한다.

- Bake된 네비메쉬의 정보를 바탕으로 목적지까지의 최단거리 이동을 계산하며, Agent간의 충돌을 회피할 수 도 있다.

 

AgentType : Navigation의 Agents 탭에서 지정한 정보

BaseOffset : NavMeshAgent는 Bake된 네비메쉬의 표면에 붙어있기 떄문에 실제 맵 오브젝트로부터 공중으로 살짝 떨어지게 되어있다. 해당 값으로 높이조절이 가능하다.

 

Steering

  • Speed : 최대 이동 속도
  • Angular Speed : 회전 속도(Degree/sec)
  • Acceleration : 최대 가속도
  • Stopping Distance : 목표점에 가까워졌을 때, 설정한 거리에 들어오면 감속
  • Auto Braking : 목적지에 도착하기 직전에 감속을 시작할 것인지 결정

 

Obstacle Avoidance

- 다른 Agent와 Obstacle을 어떻게 회피할 것인지 결정하는 프로퍼티

  • Radius : 지형과 관계없이 다른 Agent나 Obstacle의 충돌하는 영역의 두께가 조절된다.
  • Height : Agent끼리의 높이 충돌을 조절한다.
  • Quality : 회피 품질(None은 회피 무시)
  • Priority : Agent간의 회피 우선 순위, 낮은 수가 높은 우선 순위이며 자신보다 낮은 Agent는 회피 대상에서 제외된다.

 

Path Finding

  • Auto Traverse Off Mesh Link : 분리된 메쉬 간에 자동으로 링크를 생성하는 옵션. Off Mesh Link 컴포넌트를 사용할 시 해당 기능을 비활성화해야한다.
  • Auto Repath : 네비메쉬에 변동이 생기거나 길이 막혔을 때, 재탐색한다.
  • Area Mask : 네비메쉬의 영역별로 이동을 제한할 수 있는 기능

 

 

 

 

 

반응형
반응형

 

 

 

 

유니티 엔진의 Input 클래스에는 touch라는 구조체가 존재한다. 이 touch라는 구조체의 정보를 바탕으로 모바일 환경에서의 제스처기능을 적용해 카메라를 이동하거나 줌 인/아웃 기능을 구현할 수 있다.

 

 

카메라 이동

using UnityEngine

enum GESTURE
{
    MOVE = 1,
    ZOOM,
}

private void MoveCam()
{
    if (Input.touchCount == (int)GESTURE.MOVE)
    {
        Touch touch = Input.touches[0];
        Camera.main.transform.position = new Vector3(
            Camera.main.transform.position.x - touch.deltaPosition.x,
            Camera.main.transform.position.y - touch.deltaPosition.y,
            Camera.main.transform.position.z);
    }
}
  • Touch 구조체에는 deltaPosition이 존재하는데 이전 프레임과 현재 프레임 사이의 움직인 벡터 값을 받을 수 있다.
  • 이 벡터 값을 이용해서 카메라의 위치를 변경해주면 된다.
  • deltaPosition을 바로 적용해주면 카메라가 엄청 빠르게 움직이니 해당 값에 speed(float) 값을 곱해서 적용해준다.
  • 간단하게 보여주기 위해 작성된 코드로, Camera.main의 카메라 클래스는 캐싱해서 사용하는게 최적화에 도움이 된다.

 

 

 

카메라 줌 인/아웃

private void ZoomCam()
{
    if (Input.touchCount == (int)TOUCH.ZOOM)
    {
        Touch touch_1 = Input.touches[0];
        Touch touch_2 = Input.touches[1];

        //이전 프레임의 터치 좌표를 구한다.
        Vector2 t1PrevPos = touch_1.position - touch_1.deltaPosition;
        Vector2 t2PrevPos = touch_2.position - touch_2.deltaPosition;

        //이전 프레임과 현재 프레임 움직임 크기를 구함.
        float prevDeltaMag = (t1PrevPos - t2PrevPos).magnitude;
        float deltaMag = (touch_1.position - touch_2.position).magnitude;

        //두 크기값의 차를 구해 줌 인/아웃의 크기값을 구한다.
        float deltaMagDiff = prevDeltaMag - deltaMag;

        Camera.main.orthographicSize += deltaMagDiff;
        Camera.main.orthographicSize = Mathf.Clamp(Camera.main.orthographicSize, 7, 30);
    }
}
  • 줌 기능은 카메라 이동 코드와는 다르게 조금 복잡한데, 결론적으로 이전 프레임과 현제 프레임의 두 터치 사이의 벡터 크기(Maginitude) 값을 이용해서 deltaMagnitude 값을 구한다.
  • 이렇게 구해진 deltaMagnitude 값을 Camera의 orthographicSize에 더해주면 줌 인/아웃 기능 구현이 가능하다.
  • orthographicSize가 너무 작아져서 음수로 내려가면 화면이 뒤집어져 보이므로 Mathf.Clamp로 적절하게 최소, 최대값을 고정해준다.
  • deltaMagnitude 값을 바로 적용하면 줌이 빠르게 적용되니 해당 값에 speed(float) 값을 곱해서 사용해준다.

 

 

 

결과

 

 

 

 

 

 

반응형
반응형

 

 

 

 

SafeArea

- 해상도가 세로로 길어지면서 생겨난 개념이다.

- 초창기에는 iPhone의 노치라는 변태 해상도(?)를 출발로 현재는 안드로이드도 펀치홀 디스플레이가 적용되서 이런 비정상적인 화면에 맞춰 UI를 정상적으로 보이게 배치해줘야한다.

- 안드로이드같은 경우 유니티 내장 기능에서 펀치홀 라인을 레터박스로 날리는 옵션지 주어져있다. (하지만 이 포스팅에서 이 옵션을 사용하지 않는다.)

 

 

 

Screen.safeArea

- 단어 뜻 그대로 화면의 안전 영역의 픽셀 단위를 반환한다.

- 기기 해상도 값과 SafeArea 해상도 값을 계산해서 UI의 anchor의 위치값을 계산해서 UI Rect의 새로운 값을 만들어낸다.

 

 

 

Hierachy 세팅

- Canvas 자식으로 빈 UI 게임오브젝트를 넣어주고 자식으로 넣은 게임오브젝트의 RectTransform AnchorPreset을 화면 전체로 세팅해준다.

- 다른 UI 요소는 위에 자식으로 넣은 게임오브젝트의 자식으로 넣어주면 된다.

- 다음 코드로 스크립트를 생성해 Safe Area를 적용할 게임오브젝트에 붙여준다.

 

 

 

코드

using UnityEngine;

public class SafeArea : MonoBehaviour
{
    private void Awake()
    {
        ApplySafeArea();
    }

    private void ApplySafeArea()
    {
        Rect safeAreaRect = Screen.safeArea;
        Vector2 anchorMin = safeAreaRect.position;
        Vector2 anchorMax = safeAreaRect.position + safeAreaRect.size;

        //Calculate anchorMin
        anchorMin.x /= Screen.width;
        anchorMin.y /= Screen.height;

        //Calculate anchorMax
        anchorMax.x /= Screen.width;
        anchorMax.y /= Screen.height;

        var rectTr = GetComponent<RectTransform>();
        //Apply anchor
        rectTr.anchorMin = anchorMin;
        rectTr.anchorMax = anchorMax;

        rectTr.offsetMin = Vector2.zero;
        rectTr.offsetMax = Vector2.zero;
    }
}
  • 먼저 anchor의 Min, Max 값을 구한다음, offset을 각각 0으로 지정해주면 화면(Rect)이 SafeArea에 맞춰진다.
  • Screen.safeArea.position의 값을 포함시키는 이유는 iPhone의 경우 Safe Area가 화면 밑부분을 더 띄워주므로 다른 위치값이 나온다.
  • Screen.safeArea 실제 기기에서만 SafeArea에 맞는 값을 반환하기 때문에 에디터에서는 SafeArea가 적용 안되서 보이는게 정상이다.

 

 

 

결과 확인 (Safe Area 스크립트 적용 차이, 유니티 내장 Safe Area 적용)

 

 

 

 

 

반응형

+ Recent posts