Unity 최종 프로젝트 - 9

이준호·2024년 1월 24일
0

📌 Unity 최종 프로젝트



📌 추가사항

➔ 캐릭터 애니메이션 적용방법 Feat.Mixamo

좀비 에셋을 구매하였지만 애니메이션이 마땅치않아 고민하던 중에 Mixamo라는 사이트를 알게 되었다.
이 사이트는 캐릭터도 구할수 있지만 캐릭터 원본만 있다면 그 캐릭터에 사이트에 있는 애니메이션을 자동으로 적용이 되게 변환해주는 기능도 있다.

  • 일단 사이트에 들어가 검색창에 필요한 애니메이션 이름을 검색해준다.

  • 그 후 오른쪽에 있는 "UPLOAD CHARACTER" 를 눌러준다. (애니메이션이 아닌 캐릭터까지 필요하다면 그냥 다운로드를 하면 된다.)

  • 적용할 캐릭터를 넣는다. (.Zip도 가능은 하지만 안되는경우, .Fbx파일만 따로 추출하여 업로드시키면 된다.)

  • 업로드가 완료되면 Next를 눌러 적용을 시킨다.

  • 그 후 "DOWNLOAD" 버튼을 누르면 저런 창이 나온다.

  • "Format"은 "FBX for Unity(.fbx)"를 선택해준다.

  • "Skin"은 해당 캐릭터의 아바타까지 필요하다면 "With Skin"을 선택해주고, 애니메이션만 필요하다면 "With Out Skin"을 선택해주면 된다.

  • 그 후 다운로드된 파일은 유니티 프로젝트창에 드래그&드랍 으로 가져오면 된다.

  • 가져온 파일을 클릭하여 인스펙터 창을 확인하면 "Animation Type"이 있는데, 해당 캐릭터의 아바타에 맞는 타입을 골라서 Apply를 해주면 된다.

  • 만일 타입이 "Humanoid" 타입이라면, Animation카테고리에 들어가 밑을 보면 여러가지가 있을것이다.

  • 이동을 해야하는 애니메이션이라면, "Loop Time"을 체크해주고 나머지는 체크하지 않고, "Root Transform Position(Y)"에서 "Based Upon"을 "Feet"으로 바꿔준 뒤에 Apply를 눌러주면 된다.

  • 적용이 완료되면 애니메이션만 필요하다면, 애니메이션을 클릭 후에 "Ctrl + D"를 눌러 복제하여 사용하면 된다.

  • 캐릭터까지 전체 사용할 것이라면, 바로 하이라키창으로 전체 묶음을 옮겨서 사용하면 된다.






Root Motion

  • "Apply Root Motion"은 오브젝트의 위치와 회전을 애니메이션이 제어하도록 할 것이냐를 결정하는 곳이다.

  • true(체크) 한다면, 애니메이션이 해당 애니메이터가 적용된 오브젝트의 Transform에 영향이 갈 것이다. (캐릭터의 이동속도가 없어도 애니메이션에 의해서 앞으로 가긴 하지만, 직진으로만 가기에 매우 작은 이동속도를 부여해주면 될 것이다.)

    • 나의 경우 좀비의 걷기 애니메이션은 매우 느려서 애니메이션의 움직임에 맞춰서 움직이는 것이 훨씬 자연스럽기에 체크를 해주었다. (이동속도 : 0.1)
  • false 한다면, 애니메이션은 해당 오브젝트의 움직임 그 자체에는 영향을 주지 못한다. (캐릭터가 이동 속도가 없다면 제자리하는 모습이 나올것이다.)




Apply Root Motion

  • 움직이지 않는 애니메이션의 경우

  • 움직이는 애니메이션의 경우

Root Transform Rotation

오브젝트의 회전을 애니메이션이 제어하게 할 것인가를 정하는 카테고리

  • Bake Into Pose : 활성화를 한다면, 애니메이션에 관계없이 Transform의 좌표축에 고정을 시킨다. 게임 오브젝트가 애니메이션에 의해서 회전을 하지 않게 된다.

  • Based Upon : 오브젝트의 중심 좌표를 어디에 둘건지 정하는 것이다.

    • Body Orientation : 몸을 기준으로 한 앞 방향 벡터를 따른다. (대부분의 애니메이션에 효과적이지만, 해당 벡터와 수직이 되는 모션에 사용하면 이상하게 작동할 수도 있다.) (이 경우 Offset으로 조절을 해 줄수도 있다.)
    • Original : 해당 애니메이션을 만든 제작자가 설정한 중심으로 유지한다.

Root Transform Position (Y)

오브젝트의 Y값을 애니메이션이 제어하게 할 것인가를 정하는 카테고리

  • Feet(바닥)에 두는것이 보통 효과적이며, 움직이지 않는 애니메이션일 경우 "Bake Into Pose"를 체크해 주는것이 좋다.

Root Transform Position (XZ)

오브젝트의 X, Y값을 애니메이션이 제어하게 할 것인가를 정하는 카테고리

  • Origin(중심)에 두는것이 보통 효과적이며, 움직이지 않는 애니메이션일 경우 "Bake Into Pose"를 체크해 주는것이 좋다.












📌 트러블 슈팅

➔ Same Destination Bug

순찰쪽 노드 부분에서 대기시간이 끝나면 새로운 목적지로 이동하는 것이 아닌 살짝 움찔했다가 다시 대기시간에 들어가고 그 뒤에 다시 목적지가 설정되서 움직이는 버그가 있었다.

이유

    private INode.E_NodeState RandomPositionAssignment()
    {
        if (_detectedPlayer == null & _patrolRandomPosCheck == true)
        {
            _correctPos.x = Random.Range(_patrolMinPos.x, _patrolMaxPos.x);
            _correctPos.z = Random.Range(_patrolMinPos.y, _patrolMaxPos.y);
            
            AnimationWalkCheck();
            _agent.SetDestination(_correctPos);

            _patrolRandomPosCheck = false;
        }
   
        return INode.E_NodeState.ENS_Failure;
    }
    
    private INode.E_NodeState CorrectPathCheck()
    {
        // 경로가 유요하지 않거나 초기화되지 않은지 체크
        if (_agent.pathStatus == NavMeshPathStatus.PathInvalid)
        {
            DebugLogger.LogError("Agent Path is Invalid");
            
            _patrolRandomPosCheck = true;
            return INode.E_NodeState.ENS_Running;
        }
        
        return INode.E_NodeState.ENS_Failure;
    }
    
    private INode.E_NodeState CheckArrivalAtDestination()
    {
        if (_agent.pathPending)
        {
            return INode.E_NodeState.ENS_Running;
        }
        
        if (_agent.remainingDistance < Mathf.Epsilon)
        {
            return INode.E_NodeState.ENS_Success;
        }
        
        return INode.E_NodeState.ENS_Failure;
    }
    
    private INode.E_NodeState IdleWaitTimeCheck()
    {
        // 최초 한번 시간 할당.
        if (_idleWaitCheck == true)
        {
            AnimationIdleCheck();
            _idleDurationTime = Random.Range(3f, 5f);
            _idleStartTime = Time.time;
            _idleWaitCheck = false;
        }

        if (Time.time - _idleStartTime > _idleDurationTime)
        {
            _patrolRandomPosCheck = true;
            _idleWaitCheck = true;

            return INode.E_NodeState.ENS_Failure;
        }

        return INode.E_NodeState.ENS_Running;
    }
    
    private INode.E_NodeState MoveSetPosition()
    {
        AnimationWalkCheck();
        _agent.SetDestination(_correctPos);
        return INode.E_NodeState.ENS_Running;
    }
  • 대기시간이 다 지나야만 목적지를 할당해 줄 수 있는데, 그 대기시간이 끝나서 목적지를 할당해 주는 노드로 갈 때에는 새로운 좌표가 아닌 원래의 좌표 목적지를 가진 상태라 똑같은 목적지가 찍혀서 움직이려고 했다가 이미 도착해있는 것이니 다시 대기시간에 들어가 버리는 것이었다. 그래서 대기시간이 한 번 더 돌아야 새로운 목적지로 세팅이 되는 것이었다.


해결 방법

  • 목적지를 셋팅해주는 이동노드를 빼고 순찰셀렉토노드 가장 왼쪽(첫번째)노드 에서 랜덤 목적지를 할당해주면서 바로 새로운 목적지(SetDestination)을 할당해주어 해결하였다.

  • 랜덤 목적지는 최초 할당을 하고, 목적지에 도착하고 대기시간이 다 지나야만 새롭게 할당되기에 비효율적으로 계속 목적지를 반복해서 찍어주지 않아 효율적으로 노드를 순회할 수 있게 되었다.






➔ NavMeshAgent Spin In Place

NavMeshAgent가 목적지에 다다르면, 다음 행동을 준비하고 하려는것 이 아닌, 목적지를 인식하지 못하고 주의를 빙글빙글 도는 문제가 있었다.

이유

  • 애니메이션을 위에 설명한 방법처럼 적용을 하다가 애니메이션의 자연스러움을 위해 "Apply Root Motion"을 체크해주엇는데, 그것이 미세한 위치 오차를 불러와 인식하지 못한 것이었다.

  • 나의 경우 agnet의 StoppingDistance를 Mathf.Epsilon을 사용하여 정교하게 잡으려고 하였지만, 그 이유때문에 목적지를 인식하지 못하는 것이었다.



해결 방법

  • agnet.StoppingDistance를 0.1f로 바꿔주었다.

  • agent.remainingDistance 또한 0.1f로 변경하여 동일하게 제자리 대기 노드로 들어갈 수 있도록 하였다.

  • 자연스러운 움직임의 연속을 위해 "Atuo Braking"을 꺼주었다.

profile
No Easy Day

0개의 댓글