Tutorial (4)

 

오브젝트 다루기 기초

 

작성 기준 버전 :: 2018.3.1f1

[본 튜토리얼의 내용은 유튜브 영상으로도 시청하실 수 있습니다]

 

이번 섹션에서는 유니티 에디터에서 오브젝트를 씬에 배치하고 이동하는 등의 오브젝트를 다루는 방법에 대해서 배워보자.

 

 

게임 오브젝트(Game Object)

 

 

게임 오브젝트는 씬에 배치되는 가장 기본 단위인 오브젝트로써 기본적으로 위치, 회전, 크기를 나타내는 트랜스폼 컴포넌트만을 가진 빈 오브젝트이다. 여기에 어떤 컴포넌트가 추가되느냐에 따라 천차만별로 달라질 수 있는 백지와 같은 오브젝트이다.

 

 

빈 게임 오브젝트에 Cube 메시를 가진 메시 필터 컴포넌트와, 메시를 그리기 위한 메시 렌더러 컴포넌트, 물리적인 실체를 가지기 위한 박스 콜라이더 컴포넌트를 추가함으로써 눈에 보이는 큐브 모양을 가진 게임 오브젝트가 되었다. 이처럼 게임 오브젝트에 어떤 컴포넌트를 붙이냐에 따라서, 그 컴포넌트는 플레이어가 조종하는 캐릭터가 될 수도 있고, 플레이어의 길을 막는 장애물이 되거나 플레이어와 싸우는 몬스터가 될 수도 있는 것이다.

 

 

오브젝트 생성하기

 

첫 번째로 알아볼 것은 오브젝트를 씬에 생성하는 방법이다.

 

 

하이어라키 탭 바로 아래에 있는 Create 드롭다운 메뉴를 누르면 생성할 수 있는 오브젝트들의 종류를 볼 수 있다. Create Empty 항목을 선택하면 앞에서 본 빈 게임 오브젝트를 생성한다.

 

 

이번에는 다음 과정들을 수행하기 위해서 Create>3D Object>Cube를 선택해서 눈에 보이는 상자 오브젝트를 추가하자.

 

 

Cube를 선택하면 씬 안에 새로운 큐브가 생겨난 것을 볼 수 있다. 이 외에도 몇 가지의 3D 오브젝트들을 생성해보자.

 

 

 

 

오브젝트 선택하기

 

이번에는 오브젝트를 선택해보자.

 

단일 오브젝트 선택하기

 

씬 뷰에서 오브젝트를 클릭하면 해당 오브젝트를 선택할 수 있고 선택된 오브젝튼느 테두리에 주황색 하이라이트가 표시된다.

 

 

오브젝트를 선택할 때 주의할 점이 있는데 씬 뷰에서 오브젝트를 선택하려고 할 때, 핸드툴 모드가 활성화된 상태에서는 오브젝트를 선택할 수 없다. W E R 등을 눌러서 다른 모드로 전환한 다음에 선택하도록 한다.

 

 

단일 오브젝트를 선택하는 또 다른 방법으로는 하이어라키 창에서 선택하고자 하는 오브젝트를 클릭하는 것이다.

 

 

하이어라키 창에서 오브젝트를 선택하면 다른 오브젝트를 잘못 선택할 확률이 줄어든다는 장점이 있다. 선택하고자 하는 오브젝트를 빨리 찾고 싶다면 오브젝트의 이름을 적절하게 정해서 보기 쉽게 정리하는게 좋다.

 

 

여러 오브젝트 선택하기

 

오브젝트 하나를 선택하는 방법 이외에도 여러 오브젝트를 한꺼번에 선택하는 방법도 있다.

 

전략 시뮬레이션 게임에서 여러 유닛을 한꺼번에 선택하듯이 선택하고자하는 오브젝트 근처에서 클릭하고 드래그하면 반투명한 사각형이 표시되는데 이 사각형 영역에 선택하고자 하는 오브젝트가 들어오게 만들면 여러 오브젝트가 동시에 선택된다. 이 방법은 손쉽게 여러 오브젝트를 선택할 수 있다는 장점이 있지만, 선택하고자하는 오브젝트 가까이에 선택하지 않으려고하는 다른 오브젝트가 함께 있다면 이 역시도 같이 선택될 수 있다는 단점이 있다.

 

 

여러 오브젝트를 선택하는 다른 방법으로는 역시 하이어라키 창에서 하는 방법이 있다. Shift 키를 사용하면 첫 번째 선택한 오브젝트와 두 번째 선택한 오브젝트 사이에 있는 모든 오브젝트가 선택되고, Ctrl 키를 사용하면 떨어져 있는 오브젝트를 하나씩 선택된 오브젝트에 추가시키며 선택할 수 있다.

 

 

 

 

 

 

 

오브젝트 이동시키기

 

배치한 오브젝트를 원하는 위치에 배치하기 위해서 오브젝트를 이동시키는 방법을 알아보자.

 

기즈모로 이동시키기

 

 

상단 버튼 중에 이동 툴 버튼 선택하거나 단축키 W키를 누르면 씬 뷰에서 오브젝트를 이동시킬 수 있게 된다.

 

이동 툴을 활성화시킨 채로 오브젝트를 선택하면 세 방향으로 뻗어나가는 화살표 모양의 기즈모가 오브젝트의 중앙에 생기는 것을 볼 수 있다.

 

 

세 화살표는 각 축 방향을 의미하며, 이 화살표를 클릭하고 드래그하면 그 축의 방향으로 오브젝트를 움직이게 된다. 빨간 화살표는 X축 방향, 초록 화살표는 Y축 방향, 파란 화살표는 Z축 방향이다.

 

 

각 축 화살표 사이를 보면 작은 면들이 보인다. 이것을 클릭하고 드래그하면 해당 색상의 축만 고정하고 나머지 축 방향으로 이동한다는 뜻이다.

 

 

즉 파란 면을 잡고 움직이면 오브젝트는 Z축은 고정한 채로 X축과 Y축에서만 움직이게 된다.

 

 

트랜스폼 컴포넌트로 이동시키기

 

오브젝트를 무브 툴과 기즈모가 아닌 인스펙터 창의 트랜스폼 컴포넌트를 이용해서 이동시킬 수도 있다. 기즈모를 통해 대강의 위치로 이동시키는 것이 아니라 정확히 원하는 좌표에 오브젝트를 가져다 놓고 싶다면 인스펙터 창의 트랜스폼 컴포넌트를 이용해서 오브젝트를 이동시키는게 더 좋다.

 

 

인스펙터 창에서 오브젝트를 이동시키는 다른 방법도 있는데 포지션의 각 축 이름에 마우스 커서를 가져다 대면 커서 앞에 양방향 화살표가 뜨는데 이 때 좌클릭 드래그를 하면 그 축의 값을 조절할 수도 있다.

 

 

 

오브젝트 회전시키기

 

오브젝트를 잘 배치하기 위해서는 위치뿐만 아니라 적절하게 회전시키는 것 역시 중요하다.

 

기즈모로 회전시키기

 

 

상단 버튼 중에 회전 툴 버튼을 클릭하거나 단축키 E를 누르면 씬 뷰에서 오브젝트를 회전시킬 수 있게 된다.

 

회전 툴을 활성화시킨 채로 오브젝트를 선택하면 구형의 회전 기즈모가 생긴다.

 

 

회전 기즈모에는 흰 원 안쪽으로 빨간 원, 초록 원, 파란 원이 보이는데 이것은 그 색상의 원 평면과 직교하는 축을 기준으로 회전한다는 의미이다.

 

 

기즈모의 축을 잡고 마우스를 움직이면 그 축을 기준으로만 회전한다. 아래의 이미지를 보면 순서대로 각각 X축, Y축, Z축을 잡고 오브젝트를 회전시키고 있다.

 

 

기즈모에서 축이 아닌 영역을 잡고 움직이면 오브젝트를 자유롭게 회전시킬 수 있다.

 

 

 

트랜스폼 컴포넌트로 회전시키기

 

오브젝트 이동과 마찬가지로 오브젝트 회전 역시 트랜스폼 컴포넌트를 통해서 할 수 있다. 인스펙터 창에서 트랜스폼 컴포넌트 값 중에 Rotation 값을 원하는 만큼 수정해주면 원하는 각도만큼 정확하게 오브젝트를 회전시킬 수 있다.

 

 

Rotation 값 중에 원하는 값의 이름에 마우스 커서를 대고 좌클릭 드래그하는 것으로도 오브젝트를 회전시킬 수 있다.

 

 

 

 

 

 

오브젝트 크기 조절하기

 

오브젝트의 적절한 크기 역시 자연스러운 오브젝트 배치에 있어서 중요한 요소 중에 하나이다. 의도되지 않을 잘못된 크기의 물체는 플레이어에게 큰 위화감을 주고 몰입을 방해하기 때문이다.

 

기즈모로 크기 조절하기

 

 

상단 버튼 중에 스케일 툴을 선택하거나 단축키 R키를 누르면 오브젝트의 크기를 조절할 수 있다.

 

스케일 툴을 활성화한 채로 오브젝트를 선택하면 이동 기즈모와 같은 형태지만 끝이 직육면체 모양인 기즈모가 생성된다.

 

 

빨간 색 막대는 오브젝트를 X축 방향으로 확대/축소하며, 초록 색 막대는 Y축 방향으로, 파란색 막대는 Z축 방향으로 오브젝트를 확대/축소한다.

 

 

가운데 있는 흰 색 정육면체를 잡고 마우스를 움직이면 모든 축 방향으로 오브젝트가 커졌다가 작아진다.

 

 

트랜스폼 컴포넌트로 크기 조절하기

 

인스펙터 창에서 트랜스폼 컴포넌트의 Scale 값을 통해서 오브젝트의 크기를 정확히 원하는 크기로 조절할 수 있다.

 

 

Scale 값 중에 원하는 값의 이름에 마우스 커서를 대고 좌클릭 드래그하는 것으로도 오브젝트의 크기를 조절할 수 있다.

 

 

 

오브젝트를 다른 오브젝트의 하위 오브젝트로 만들기

 

유니티에서는 단순히 하나의 오브젝트만을 사용하는 것이 아니라 오브젝트를 다른 오브젝트의 하위 오브젝트로 만들어서 사용하는 경우가 많다. 예를 들자면 캐릭터 오브젝트의 손에 무기 오브젝트를 붙여서 캐릭터가 무기를 들게 하거나, 차량 오브젝트를 만들 때, 차량의 몸체 오브젝트와 바퀴 오브젝트를 따로 만들어서 차량 몸체 오브젝트 하위에 바퀴 오브젝트를 붙이는 등의 방식을 사용한다.

 

이렇게 하면 어떤 장점이 있냐면 만약 캐릭터가 손에 들고 있는 무기를 다른 무기로 바꾸면 손에 무기 오브젝트를 다른 무기 오브젝트로 바꾸거나, 차량이 데미지를 입거나 폭발할 때, 차량의 바퀴가 차량에서 떨어져나가는 연출 등을 사용할 수 있게 된다. 만약 무기와 캐릭터가 통짜로 된 하나의 오브젝트라면 무기를 교체할 때마다, 캐릭터의 모델링마저 교체해야 될 것이고, 차량 몸체와 바퀴가 통짜로 된 하나의 오브젝트라면 차량에서 바퀴가 떨어져나가게 하기 위해서 바퀴가 떨어져 나가는 애니메이션을 만들어야 할 것이다.

 

이렇게 오브젝트를 다른 오브젝트의 하위 오브젝트로 만드는 것은 여러 곳에서 사용될 수 있는 좋은 기법으로 어떤 곳에 사용될 수 있는지 생각해보고 많이 활용해보는 것이 좋다.

 

한 오브젝트를 다른 오브젝트의 하위 오브젝트로 만드는 방법은 아주 간단하다. 하이어라키 뷰에서 다른 오브젝트의 하위 오브젝트로 만들고자 하는 오브젝트를 드래그해서 상위 오브젝트가 되고자 하는 오브젝트에 끌어다 놓으면 된다.

 

 

이렇게 해서 상위 오브젝트가 된 오브젝트를 부모 오브젝트라고 하며, 하위 오브젝트가 된 오브젝트를 자식 오브젝트라고 한다.

 

이렇게 다른 오브젝트의 하위 오브젝트가 된 자식 오브젝트는 부모 오브젝트의 이동, 회전, 스케일의 영향을 함께 받는다.

 

 

반응형

유니티에서 애니메이션 블랜드 사용하기

 

작성 기준 버전 :: 5.6 - 2019.4

 

[이 포스트의 내용은 유튜브 영상으로도 시청하실 수 있습니다]

 

게임에서 자연스러운 애니메이션은, 캐릭터에 생명을 불어넣어 주는 요소 중에 하나이다. 다양한 종류의 애니메이션을 제작하면 캐릭터의 움직임은 더욱 자연스러워 질 것이다. 예를 들어 캐릭터가 움직이는 방향에 따라서 정면으로 움직일 때는 바르게 걷고 정면을 본체로 왼쪽으로 움직이면 왼쪽으로 옆걸음을 하는 방식으로 제작한다면 만약 왼쪽 정면 대각선 방향을 향해서 이동하면 어색한 움직임을 보일 것이다. 그것을 자연스럽게 처리하려면 왼쪽 정면 대각선 방향으로 움직이는 애니메이션을 만들어주어야 하고, 또 거기서 더 자연스러운 애니메이션을 만들고자 한다면 그 정면과 왼쪽 정면 대각선 사이, 왼쪽과 왼쪽 정면 대각선 사이의 애니메이션을 만들어서 추가해주어야 하는 것이다. 하지만 그렇다고 해서 게임 제작자가 모든 경우의 애니메이션을 일일이 다 만들 수는 없다. 그런 작업은 쉽지 않을 뿐만 아니라 게임제작에 들어가는 자원과 시간이 부족하다.

 

이러한 문제를 해결하기 위해서 사용되는 기능이 바로 애니메이션 블랜드이다.

 

이번 예시를 설명하기 위해 간단한 박스맨을 제작했고, 걷기 모션과 달리기 모션을 만들었다. 이동속도에 따른 걷기/달리기 애니메이션 블랜드는 가장 대표적이고 유용한 예제이다. 캐릭터의 이동 속도에 따라서 캐릭터의 보폭이나 발걸음 이동속도 역시 유동적으로 변하는 편이 자연스럽게 보일 것이다.

 

 

 

간단하게 걷는 모션과 달리는 모션의 차이를 주기 위해서 걷는 모션은 팔다리를 가볍게 움직이고 달리는 모션은 팔다리를 격하게 흔들도록 만들어 보았다.

 

 

애니메이션 블랜드를 사용하기 위해서는 우선 캐릭터의 애니메이션을 관리할 애니메이터 컨트롤러(Animator Controller)를 생성해야 한다.

 

 

애니메이터를 생성해서 열어보면 텅 빈 애니메이터 화면이 보일 것이다.

 

 

빈 화면에 우클릭해서 Create State > From New Blend Tree 항목을 선택하면 다음과 같이 Blend Tree라는 이름의 애니메이션 스테이트가 생성되고 Parameters에는 Blend라는 이름의 float 형식의 매개변수가 생성될 것이다 :

 

 

이 Blend라는 이름의 매개변수는 애니메이션 블랜드에서 두 여러 애니메이션이 섞일 때, 어느 애니메이션으로 치우쳐서 출력될 것인지를 결정하는데 도움을 줄 것이다. 현재는 캐릭터의 이동 모션에 따라 걷기/달리기 모션 블랜드를 할 것이기 때문에 이 변수의 이름을 "MoveSpeed"로 변경하도록 하자.

 

 

그리고 애니메이터 창에서 Blend Tree 스테이트를 더블 클릭해보면 다음과 같이 블랜드 트리로 내려가게 될 것이다.

 

 

위의 그림에서처럼 블랜드 트리를 선택하고 인스펙터 창(Inspector View)을 보면 Blend Type과 Parameter 그리고 Motion을 볼 수 있다. 우선 블랜드 타입은 파라메타의 변화에 따라서 어떻게 애니메이션이 블랜드 될 것인지를 정하는 것이다. 그리고 파라메터는 현재 애니메이션의 파라메터 중에 이 블랜드 트리에서 사용할 값을 정하는 것이며, 모션은 이 블랜드 트리에서 섞을 애니메이션을 정하는 것이다.

 

 

지금은 이동 속도의 변화에 따라서 모션을 블랜드할 것이기 때문에 가만히 정지 > 걷기 > 달리기 이 세 가지 모션을 추가하겠다. 모션 밑에 있는 + 버튼을 누르고 Add Motion Field 항목을 선택하면 다음과 같이 화면이 변한다. 이제 모션들을 추가해보자.

 

 

각 모션에 애니메이션들을 추가해주면 위의 그림처럼 각 애니메이션 노드가 생성되서 블랜드 트리와 연결되는 것을 볼 수 있다. 여기서 또 조정할 수 있는 옵션은 Threshold이다. Threshold는 한계점이나 역치라는 의미로 여기서는 각 애니메이션의 그래프 상의 위치를 의미한다. 위의 그림에서는 Stand의 Threshold는 0, Walk의 threshold는 0.5, Run의 threshold는 1이다. 이 의미는 MoveSpeed의 값이 0~1사이에서만 움직일 것이고 0~0.5 값이면 Stand와 Walk 모션이 블랜드가 되고 0.5~1 값이면 Walk와 Run 모션이 블랜드된다는 것이다. 하지만 일반적으로 게임에서는 이동 속도 값을 0~1로 사용하지 않기 때문에 그럴 때는 Automate Thresholds 체크를 풀어주면 값을 적절하게 조절해줄 수 있게 된다.

 

만약에 최대 이동 속도가 50이고 걷는 속도가 10이라고 한다면 그 값을 아래와 같이 입력해주면 된다. 이렇게 설정해주고 캐릭터의 스크립트에서 이동 모션을 출력하면서 이동 속도에 따라서 animator.SetFloat("MoveSpeed", moveSpeed);를 호출해주면 애니메이터는 자동으로 변화하는 이동 속도에 따라 어느 애니메이션들을 얼마나 출력할지 결정하고 출력해줄 것이다.

 

 

 

 

애니메이션 블랜드를 사용할 때, 주의할 점은 블랜드된 애니메이션이 자연스럽게 보이기 위해서는 블랜드 되는 모션들이 비슷한 동작을 취해야하고 타이밍이 비슷해야 한다는 것이다. 하지만 예외적으로 애니메이션의 길이가 다른 것은 블랜드의 결과에 영향을 미치지 않는다. 그 이유는 정확한 블랜드를 위해서 두 애니메이션의 길이를 똑같게 맞추는 정규화 작업을 한 이후에 블랜드를 하기 때문이다.

 

간단한 위의 예제는 다음 링크에서 다운로드 받아서 실행해볼 수 있다.

 

AnimationBlendPractice.zip
다운로드

 

반응형
  1. 차우차우 2019.12.08 07:01

    와... 책에서 볼때는 정말 이해안가는 데.. 여기서는 정말 직빵으로 이해가 됬네요 정말 감사합니다.ㅜㅜ

    • wergia 2019.12.08 22:27 신고

      블로그에 쓰는 이유가 나중에 제가 까먹어서 다시 봤을 때도 쉽게 이해되도록 쓰는게 목표입니다.
      이해하기 쉬우셨다니 아주 기분이 좋네요.

유니티에서 XML 사용하기


게임을 제작할 때에, 여러 가지 데이터들을 저장하고 불러와야하는 경우가 많다. 게임 진행 상황을 저장하는 경우도 있을 수 있고, 맵이 랜덤으로 생성되는 게임의 경우에는 현재 생성된 맵의 데이터를 저장해뒀다가 다음에 다시 불러와야 할 수도 있다. 또는 게임의 아이템 정보들의 목록을 저장해둬야 할 수도 있다. 위와 같은 경우에 XML을 사용하면 저런 기능들을 쉽게 구현할 수 있게 된다.


XML은 마크업 언어를 정의하기 위한 확장성 마크업 언어라고 하는데, 여기서는 조작하기 쉬운 일종의 로컬 데이터베이스나 데이터를 저장하기 위한 파일로 사용될 것이다.


이번 섹션의 예시에서는 게임의 캐릭터 정보를 저장하기 위해서 XML을 사용한다고 가정하고 진행 해보겠다.


1. XML 파일 생성하여 저장하기

첫 번째로 알아볼 것은 XML 파일을 생성하는 것이다. 만약 게임을 시작하고 처음으로 저장을 하는 경우라면 아직 데이터를 저장하기 위한 파일이 생성되어 있지 않을 것이다. 물론 미리 XML 파일을 만들어두고 사용하는 것도 가능하지만, XML 파일을 생성하는 법에 대해서 알아두는 것도 나쁘지 않다.


다음이 XML을 생성하여 저장하는 코드의 전체이다 :


public class XmlTest : MonoBehaviour
{
    void Start()
    {
        CreateXml();
    }

    void CreateXml()
    {
        XmlDocument xmlDoc = new XmlDocument();

        // Xml을 선언한다(xml의 버전과 인코딩 방식을 정해준다.)
        xmlDoc.AppendChild(xmlDoc.CreateXmlDeclaration("1.0", "utf-8", "yes"));

        // 루트 노드 생성
        XmlNode root = xmlDoc.CreateNode(XmlNodeType.Element, "CharacterInfo", string.Empty);
        xmlDoc.AppendChild(root);

        // 자식 노드 생성
        XmlNode child = xmlDoc.CreateNode(XmlNodeType.Element, "Character", string.Empty);
        root.AppendChild(child);

        // 자식 노드에 들어갈 속성 생성
        XmlElement name = xmlDoc.CreateElement("Name");
        name.InnerText = "wergia";
        child.AppendChild(name);

        XmlElement lv = xmlDoc.CreateElement("Level");
        lv.InnerText = "1";
        child.AppendChild(lv);

        XmlElement exp = xmlDoc.CreateElement("Experience");
        exp.InnerText = "45";
        child.AppendChild(exp);

        xmlDoc.Save("./Assets/Resources/Character.xml");
    }
}


저장할 캐릭터 데이터의 내용은 캐릭터의 이름과 레벨, 경험치라고 가정하고 진행했다. 위의 코드를 실행하면 지정된 경로에 Character.xml 파일이 생성된다.



생성된 xml 파일은 다음과 같은 내용이다 :


<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<CharacterInfo>
  <Character>
    <Name>wergia</Name>
    <Level>1</Level>
    <Experience>45</Experience>
  </Character>
</CharacterInfo>


작성한 대로 캐릭터의 이름, 레벨, 경험치 값이 저장되었다.



2. XML 파일에서 데이터 불러와서 사용하기

데이터를 저장하는데 성공했다면 이 다음에는 저장된 데이터를 불러와서 사용해야할 것이다. 다음의 코드가 저장된 XML 데이터를 불러오는 것이다.


using System.Xml;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class XmlTest : MonoBehaviour
{
    void Start()
    {
        LoadXml();
    }

    void LoadXml()
    {
        TextAsset textAsset = (TextAsset)Resources.Load("Character");
        Debug.Log(textAsset);
        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.LoadXml(textAsset.text);

        XmlNodeList nodes = xmlDoc.SelectNodes("CharacterInfo/Character");

        foreach (XmlNode node in nodes)
        {
            Debug.Log("Name :: " + node.SelectSingleNode("Name").InnerText);
            Debug.Log("Level :: " + node.SelectSingleNode("Level").InnerText);
            Debug.Log("Exp :: " + node.SelectSingleNode("Experience").InnerText);
        }
    }

}


위의 코드를 실행해보면 XML에 저장해둔 캐릭터 데이터가 잘 불러와졌음을 알 수 있다.



불러온 데이터를 캐릭터 오브젝트를 생성해서 그 값을 다시 넣어준다면 캐릭터는 저장되기 직전의 캐릭터와 같은 상태를 가지게 될 것이다.



3. 이미 존재하는 XML 파일에 데이터 덮어씌워 저장하기

게임에서 이미 저장하기 기능을 사용해서 XML 파일이 생성되어 있고, 그 XML파일에 덮어 씌워 저장하는 경우도 있을 수 있다. 그럴 땐 다음 코드와 같이 이미 존재하는 XML 파일을 불러온 후에 그 안의 값을 수정하고 저장해주면 된다 :


using System.Xml;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class XmlTest : MonoBehaviour
{
    void Start()
    {
        SaveOverlapXml();
    }

    void SaveOverlapXml()
    {
        TextAsset textAsset = (TextAsset)Resources.Load("Character");
        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.LoadXml(textAsset.text);

        XmlNodeList nodes = xmlDoc.SelectNodes("CharacterInfo/Character");
        XmlNode character = nodes[0];

        character.SelectSingleNode("Name").InnerText = "wergia";
        character.SelectSingleNode("Level").InnerText = "5";
        character.SelectSingleNode("Experience").InnerText = "180";

        xmlDoc.Save("./Assets/Resources/Character.xml");
    }

}


위 코드를 실행해보면 다음과 같이 XML 파일이 변경되어 있는 것을 볼 수 있다 :


<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<CharacterInfo>
  <Character>
    <Name>wergia</Name>
    <Level>5</Level>
    <Experience>180</Experience>
  </Character>
</CharacterInfo>


위의 예시처럼 덮어씌워 저장하는 방법 이외에도 새로 노드를 덧붙여서 추가적인 내용을 저장한다던가, 아니면 여러 개의 저장 슬롯을 만들어서 저장하는 방법을 사용할 수 있다.



4. 취약점

저장 방식으로 사용하기 굉장히 편리한 XML이지만 이것도 굉장한 단점이 하나 있다. 이 XML을 그대로 사용하면 그냥 텍스트 편집기로도 열리기 때문에 사용자들이 쉽게 그 값을 변조할 수 있게 된다. 이것은 하나의 에디터나 다름없는 것이기 때문에 만약 제작하는 게임이 데이터가 변조되면 안되는 경우에는 심각한 문제가 될 수 있다. 만약 XML에 게임 데이터를 저장하고 불러와야 되는 경우에는 별도의 암호화/복호화 기능을 덧붙여서 기능을 만드는 것을 추천한다.

반응형
  1. ㅇㅇ 2017.11.20 10:35

    공부하는데 만은 참고가 됏어요 감사합니다

  2. 마망 2018.09.11 10:44

    깔끔한 레퍼런스 글 감사합니다.

    다만 이대로 적용해보니, save 후에 바로 load하는데 있어서 바로 저장되지 않는 현상이 발생하더라구요

    어떤 식으로 해결하면 좋을까요?

    • wergia 2018.09.13 17:08 신고

      간단한 방법으로는 저장한 후에 어느 정도의 더미 로드 시간을 주는 방법이 있겠습니다.

      그리고 게임 데이터를 저장하는 용도로 사용하는 것이라면 이미 게임 중에 메모리에 다 들어가 있는 정보를 다시 불러올 필요가 없을 것이라고 생각하기 때문에 Save직후에 바로 다시 Load하지 않아도 된다고 생각합니다.

5.6 문서 초안 : 이 초안 문서는 유니티 5.6에서 업데이트 된 기능에 대한 것이다. 따라서 이 문서의 정보는 최종 릴리즈 전에 변경될 수 있다.


                                                                                                                                                                                        

[5.6 초안] 에셋 번들

 

섹션 1 : 개요

 

에셋 번들은 실행 시 로드 할 수 있는 플랫폼 별 에셋(모델, 텍스쳐, 프리팹, 오디오 클립 및 전체 장면까지 포함)을 포함하는 아카이브 파일이다. 에셋 번들은 서로간의 종속성을 나타낼 수 있다. 예를 들어 에셋 번들 A의 머티리얼은 에셋 번들 B의 텍스처를 참조할 수 있다. 네트워크를 통한 효율적인 전달을 위해서, 에셋 번들을 유스 케이스 요구 사항(LZMA 및 LZ4)에 따라 내장된 알고리즘을 선택해서 압축할 수 있다.


에셋 번들은 다운로드 할 수 있는 콘텐츠(DLC)의 구성, 초기 설치 용량 감소, 최종 사용자의 플랫폼에 최적화된 에셋 로딩, 런타임 메모리 부족을 해소하는데 사용할 수 있다.


에셋 번들에는 무엇이 있는가?

좋은 질문이다. 실제로 "에셋 번들"은 다르지만 관련있는 두가지를 참조한다.


첫 번째는 디스크에 있는 실제 파일이다. 이것은 에셋 번들 아카이브를 호출하거나, 이 문서의 짧은 보관용으로만 사용된다. 아카이브는 폴더와 마찬가지로 컨테이너 내부에 추가 파일을 보유하는 컨테이너를 생각할 수 있다. 이러한 추가 파일은 직렬화된 파일(Serialized file)과 리소스 파일, 이 두가지 유형으로 구성된다. 직렬화된 파일에는 에셋이 각각의 객체로 분리되어 이 하나의 파일에 포함된다. 리소스 파일은 특정 에셋(텍스처 및 오디오)에 대해 별도로 저장된 이진 데이터 청크이므로 다른 스레드의 디스크에서 효율적으로 불러올 수 있다.


두 번째는 특정 아카이브에서 에셋을 불러오기 위해 코드를 통해 상호작용하는 실제 에셋 번들 객체이다. 이 개체에는 에셋 번들에 추가한 에셋의 모든 파일 경로에 대한 맵이 포함되어 있다. 이 파일의 경로는 파일을 요청했을 때 로드해야되는 해당 에셋에 속한 개체에 대한 것이다.


섹션 2 : 에셋 번들 워크플로우


에셋 번들을 사용하려면 다음의 순서를 따르라. 각 워크플로우에 대한 자세한 내용은 다음 섹션에서 확인할 수 있다.


에셋 번들에 에셋 등록

주어진 에셋을 등록하려면 다음 단계를 따라하면 된다.


1. 프로젝트 뷰에서 번들에 등록할 에셋을 선택하라.

2. 인스펙터 창에서 개체를 확인하라.

3. 인스펙터 창의 가장 아래쪽에 에셋 번들 및 배리언트을 등록하는 섹션을 볼 수 있다.


4. 왼쪽 드롭 다운은 에셋 번들을 등록하고, 오른쪽 드롭 다운은 배리언트(Variants, 변형)을 지정한다.

ex) 하나의 오브젝트가 있는데 이것이 작동하는 방식이 iOS와 안드로이드가 달라서 에셋 번들을 다르게 빌드해야한다던지, 두 장의 텍스쳐를 하나는 고해상도 배리언트로 묶고 하나는 저해상도 배리언트로 묶는다던지 하는 방식으로 사용된다. 단어의 뜻이 명확하게 드러나는 번역 단어가 없다고 생각하기 때문에 이후로는 그냥 배리언트라고 이야기하겠다.

5. 왼쪽 드롭 다운을 선택한다. "None"이라고 표시된 것은 현재 등록된 에셋 번들의 이름을 의미한다.

6. 아직 생성된 애셋 번들이 없다면 위 이미지와 같은 목록을 볼 수 있다.

7. "New..."를 클릭해서 새로운 에셋 번들을 생성한다.

8. 원하는 에셋 번들 이름을 입력한다.

- 애셋 번들 이름은 입력하는 내용에 따라 폴더 구조를 지원한다. 하위 폴더를 추가하려면 폴더 이름에 "/"로 나누어 표시한다.

- 예 : 에셋 번들 이름 "environment/forest"는 envionment 폴더 아래에 forest라는 번들을 생성한다.

9. 에셋 번들 이름을 선택하거나 생성한 후에는 원하는 경우 오른쪽 드롭다운에 대해 이 작업을 반복하면 배리언트 이름을 지정하거나 생성할 수 있다. 배리언트 이름은 에셋 번들을 빌드하는데 필요하지 않다.


에셋 번들 등록 및 그에 수반되는 전략에 대한 자세한 내용은 섹션 3을 참조하라.


에셋 번들 빌드

Assets 폴더에 Editor 폴더를 만들고 폴더에 다음 내용이 포함된 스크립트를 생성한다.


using UnityEditor;

public class CreateAssetBundles

{

[MenuItem("Assets/Build Asset Bundles")]

static void BuildAllAssetBundles()

{

string assetBundleDirectory = "Assets/AssetBundles";

if(!Directory.Exists(assetBundleDirectory))

{

Directory.CreateDirectory(assetBundleDirectory);

}

// 유니티 5.6에서 BuildTarget.Standalone은

// BuildTarget.StandaloneLinux;
// BuildTarget.StandaloneLinux64;
// BuildTarget.StandaloneLinuxUniversal;
// BuildTarget.StandaloneOSXIntel;
// BuildTarget.StandaloneOSXIntel64;
// BuildTarget.StandaloneOSXUniversal;
// BuildTarget.StandaloneWindows;
// BuildTarget.StandaloneWindows64;

// 등으로 세분화되었다.

BuildPipeline.BuildAssetBundles(assetBundleDirectory, BuildAssetBundleOptions.None, BuildTarget.Standalone);

}

}


이 스크립트는 Assets 메뉴 맨 아래에 해당 태그와 관련된 함수에서 코드를 실행할 "Build Asset Bundles"라는 메뉴 항목을 만든다. "Build Asset Bundles"를 클릭하면 진행 막대(progress bar)와 함께 빌드 대화 상자가 나타난다. 이렇게 하면 에셋 번들의 이름으로 레이블된 모든 에셋을 가져와서 assetBundleDirectory에 정의된 경로의 폴더에 넣는다.


이 코드의 기능에 대해서 더 자세한 내용은 섹션 4에서 볼 수 있다.


별도의 저장소에 번들을 업로드

이 단계는 각 사용자에게 고유한 단계이며 Unity 측에서 수행 방법을 알려줄 수는 없다. 에셋 번들을 타사 호스팅 사이트에 업로드하려는 경우 이 단계에서 수행하면 된다. 엄격하게 지역 개발을 수행하고 에셋 번들을 모두 디스크에 저장하려면 다음 단계로 건너뛴다.


에셋과 에셋 번들 로딩

로컬 저장소에서 에셋을 로드하려는 사용자는 AssetBundles.LoadFromFile API에 관심을 가질 것이다. 다음을 보라 :


using System.IO;

public class LoadFromFileExample : MonoBehaviour
{
    void Start()
    {
        var myLoadedAssetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "myassetBundle"));
        if (myLoadedAssetBundle == null)
        {
            Debug.Log("Failed to load AssetBundle!");
            return;
        }
        var prefab = myLoadedAssetBundle.LoadAsset<GameObject> ("MyObject");
        Instantiate(prefab);
    }
}


여기서 LoadFromFile은 번들 파일의 경로를 사용한다.


에셋 번들을 직접 호스팅하고 게임에 다운로드 해야하는 경우 UnityWebRequest API를 사용해야 한다. 다음은 그 예시이다 :


using UnityEngine.Networking;


IEnumerator InstantiateObject()
{
      string uri = "file:///" + Application.dataPath + "/AssetBundles/" + assetBundleName;

UnityEngine.Networking.UnityWebRequest request = UnityEngine.Networking.UnityWebRequest.GetAssetBundle(uri, 0);
yield return request.Send();
       
AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);

GameObject cube = bundle.LoadAsset<GameObject>("Cube");
GameObject sprite = bundle.LoadAsset<GameObject>("Sprite");

Instantiate(cube);
Instantiate(sprite);

}


여기서 GetAssetBundle(string, int)은 에셋 번들의 위치와 다운로드하려는 번들의 버전에 대한 URI를 가져온다. 이 예제에서는 로컬 파일을 가리키고 있지만 URI 문자열은 호스팅된 에셋 번들이 있는 URL을 가리킬 수 있다.


UnityWebRequest는 요청으로부터 에셋 번들을 얻는 Asset Bundles, DownloadHandlerAssetBundle을 다루기 위한 특정 핸들을 가지고 있다.


사용하는 방법에 관계없이 이제 AssetBundle 객체에 액세스할 수 있다. 이 객체에서 LoadAsset<T>(string)을 사용해야 한다. LoadAsset<T>(string)은 로드하려는 에셋의 Type T와 번들 안에 있는 문자열을 통해서 객체의 이름을 가져온다. 이렇게하면 에셋 번들에서 로드하는 객체가 반환되는데 이 객체는 Unity 내부의 객체처럼 사용할 수 있다. 예를 들어 장면에서 만들려는 GameObject인 경우 Instantiate(gameObjectFromAssetBundle)를 호출하면 된다.


에셋 번들을 로드하는 API에 대한 자세한 내용은 섹션 6를 확인하면 된다.


섹션 3 : 에셋 번들을 위한 에셋 준비


에셋 등록 전략

에셋 번들을 사용할때 원하는 번들에 에셋을 자유롭게 등록할 수 있다. 그러나 번들을 설정할 때 고려해야 할 전략이 있다. 이러한 그룹화 전략은 특정 프로젝트에 적합하다. 적합하다고 생각하는 대로 이 전략들을 자유롭게 혼합하고 매치하라.



Logical Entity Grouping

Logical entity grouping은 에셋을 프로젝트의 기능적 부분에 따라 에셋 번들에 등록하는 방법이다. 여기에는 사용자 인터페이스, 문자, 환경 및 응용 프로그램이 실행되는 동안에 자주 사용되는 섹션이 포함된다.


예시:

사용자 인터페이스 화면의 모든 텍스처 및 레이아웃 데이터 번들링

캐릭터/캐릭터 집합에 대한 모든 모델 및 애니메이션 번들링(Charater가 문자인지 캐릭터인지 불명확)

여러 레벨에 걸쳐서 공유되는 배경을 위한 텍스처와 모델 번들링


Logical entity grouping는 이러한 방식으로 모든것이 분리된 상태에서 단일 엔티티로 변경할 수 있으며 변경되지 않은 추가 에셋을 다운로드할 필요가 없기 때문에 다운로드 가능한 컨텐츠(DLC)에 이상적이다.


이 전략을 올바르게 구현하는데 있어서 가장 큰 트릭은 개발자가 각 번들에 에셋을 등록하는 경우 프로젝트에서 각 에셋을 언제 어디서 사용하는지를 잘 알고 있어야 한다는 것이다.



Type Grouping

이 전략에서는 오디오 트랙이나 언어 현지화 파일과 같은 유형의 에셋을 단일 에셋 번들에 등록한다.


Type Grouping은 여러 플랫폼에서 사용할 에셋 번들을 빌드하기 위한 더 나은 전략 중 하나이다. 예를 들어 오디오 압축 설정이 Windows와 Mac 플랫폼간에 동일하다면 모든 오디오 데이터를 자체적으로 에셋 번들로 압축하고 해당 번들을 재사용할 수 있다. 반면 셰이더는 더 많은 플랫폼 별 옵션으로 컴파일되는 경향이 있으므로 Mac 용으로 빌드한 셰이더 번들은 Windows에서 재사용할 수 없다. 또한 이 방법은 텍스처 압축 형식 및 설정이 코드 스크립트나 프리팹과 같은 것보다 자주 변경되지 않으므로 에셋 번들을 더 많은 플레이어 버전과 호환되도록 만드는데 유용하다.



Concurrent Content Grouping

Concurrent Content Grouping은 동시에 로드되고 사용되는 자산을 함께 번들링하는 방법이다. 이러한 유형의 번들은 각 레벨에 고유한 캐릭터, 텍스처, 음악 등이 포함된 레벨 기반 게임에 사용되는 것으로 생각할 수 있다. 이러한 에셋 번들 중 하나의 에셋을 사용할 때 나머지 에셋을 동시에 사용해야한다. Concurrent Content Grouping 번들 내의 단일 자산에 대한 종속성을 가지게 되면 로딩 시간이 현저하게 늘어나게 된다. 단일 에셋에 대한 전체 번들을 다운로드해야 한다.


Concurrent Content Grouping 번들의 가장 일반적인 유스 케이스는 Scene을 기반으로 하는 번들이다. 이 등록 전략에서 각 scene 번들은 대부분의 scene 또는 모든 scene에 대한 종속성을 포함해야 한다.


프로젝트는 필요에 따라 이러한 전략들을 혼합하여 사용할 수 있어야 한다. 특정 상황에 대해 최적의 에셋 등록 전략을 사용하면 모든 프로젝트의 효율성이 상승한다.


예를 들어 프로젝트에서 서로 다른 플랫폼의 UI 요소를 고유한 플랫폼-UI 특정 번들로 그룹화하지만 레벨/scene 별로 대화형 콘텐츠를 그룹화하기로 결정할 수 있다.


사용하고자 하는 전략에 관계 없이, 다음과 같은 추가적인 팁이 있다 :

- 자주 업데이트되는 개체와 거의 변경되지 않는 개체를 별도로 에셋 번들로 분할한다.

- 동시에 로드될 수 있는 그룹 객체

-- 모델, 텍스쳐, 애니메이션과 같은

- 여러 에셋 집합에서 여러 객체가 완전히 다른 에셋 집합의 단일 에셋에 종속되어 있는 것을 발견하면 종속 집합을 별도의 에셋 번들로 이동시켜야 한다. 여러 에셋 번들이 다른 에셋 번들의 동일한 에셋 그룹을 참조하는 경우, 중복을 줄이기 위해 이러한 종속성을 공유 에셋 번들로 가져올 가치가 있다.

- 표준 및 고화질 에셋과 같이 두 세트의 개체가 동시에 로드되지 않을 가능성이 있는 경우 해당 개체가 에셋의 에셋 번들에 포함되어 있는지 확인해야 한다.

- 해당 번들의 50% 미만이 동시에 로드되는 경우 에셋 번들을 분리하는 것을 고려해야 한다.