🤍ref.

  1. Unity Learn Tanks
  2. Unity Documentation

✨Unity info
본 튜토리얼 문서의 유니티 버전은 5.2 v이나, 2021.3.8f1 v으로 실습 후 게시글을 작성하였습니다.

잘못 이해한 부분이나 오역이 있다면 메일 부탁드립니다. 감사합니다.🙇‍♀️
✉️ link: yj.osoq@gmail.com



⚙️Scene Setup


1. Layout과 Scene 설정

image

각 view를 한눈에 볼 수 있도록 Layout을 2 by 3으로 지정한다. 덧붙여 Inspector view의 위치에 console view를 추가해 주었다. Hierarchy view에 Main Camera와 Directional Light가 기본 오브젝트로 구성되어 있다. 향후 추가할 Level 프리팹에 lighting setup이 되어 있으므로, directional light는 삭제해 준다.


image

게임 무대가 될 LevelArt 프리팹


✏️ 머티리얼 렌더

image

LevelArt 프리팹을 Hierarchy view로 임포트했을 때 프리팹의 머티리얼이 분홍색으로 변하는 문제가 생겼다.

프로젝트 생성 시 URP Core을 선택하거나, 렌더링 과정을 한 번 더 거치면 해결할 수 있는 문제라고 한다. 프로젝트를 디폴트 렌더로 생성했으므로, 템플릿을 URP로 변경하니 머티리얼이 정상적으로 렌더되었다.

그래도 문제가 해결되지 않는다면, [Window] -> [Rendering] -> [Render Pipeline Converter] -> [Built-in to URP]를 선택한 후 Material Upgrade를 체크해 애셋을 convert해보자.

Unity Doc 렌더 파이프라인

  • 렌더 파이프라인 정리



2. Lighting

[Window] -> [Rendering] -> [Lighting]을 통해 설정할 수 있다.

image

Light 선택 시 뜨는 패널

Auto Generate의 체크 표시를 해제해준다. (움직임마다 그림자 등을 다시 bake하는 setup을 미리 구현해놓았다고 한다.)

  • Auto Generate: Unity의 조명을 자동으로 미리 계산하고 씬 내의 오브젝트의 형태 혹은 위치가 달라질 때마다 업데이트


image

본 프로젝트에서는 Realtime GI만을 사용할 것이므로 Baked GI 옵션은 해제해둔다. realtime lighting을 사용하면 포탄을 발사할 때 Scene을 밝혀주는 효과를 줄 수 있다.

움직이는 오브젝트의 광원을 표현하기 위해 Realtime Lighting은 활성화했지만, 런타임시 프로그램의 계산량을 줄이기 위해 Baked는 비활성화해두자!

  • Realtime: 움직이는 객체에게 향하는 조명을 실시간으로 계산
  • Baked: 주로 풍경이나 정적 오브젝트와 같이 움직이지 않는 사물에 대한 조명을 미리 계산
  • Mixed: Realtime과 Baked를 결합
    • 베이크된 조명보다 많은 런타임 계산, 실시간 조명보다 많은 메모리 사용량 필요


image

Directional Light 오브젝트 외에도 조명을 얻을 수 있다 ! Environment Lighting프로퍼티의 Source를 조정해 주변광을 조정해보자. 주변광 조정을 마치면 Generate Lighting 버튼을 눌러 변경 사항을 적용해준다.

  • Source: 주변광. 씬 전체에 퍼져 있으나 특정 오브젝트가 광원지가 되지는 않음 (default: Skybox)
    • Color: 모든 주변광에 균일한 색상 적용
    • Gradient: 하늘, 지평선, 지면에 각각 다른 주변광을 설정한 후 블렌드
    • Skybox: 여러 각도에서 비추는 주변광, Gradient 보다 정확한 효과



3. Camera Setup

image

Main Camera 오브젝트의 기본 position값으로는 LevelArt의 건물 모델만 볼 수 있으므로, Position과 Rotation을 조정해 한 눈에 게임 맵을 확인할 수 있도록 한다. 또, 카메라의 Projection 프로퍼티를 Projection에서 Orthographic으로 변경한다. 변경 후 Scene view와 Game view에서 우그러진 형태의 평면 맵을 확인할 수 있다 !

왜 우그러진 시야를 사용하나 ?

zoom을 효과적으로 사용할 경우, 원근감에 구애받지 않는 정확한 좌표계 표현이 가능하다.


  • Projection: 카메라의 원근 시뮬레이션 성능

    • Perspective: 카메라의 원근감을 그대로 적용

    • Orthographic: 원근감을 고려하지 않고 오브젝트를 균일하게 렌더링

      (거리에 따른 scale의 변화가 없다. Camera 오브젝트의 zoom 프로퍼티를 조절해 확인할 수 있다.)


image

zoomout을 했을 경우, 여백 화면에 그라데이션이 보이는 Skybox 대신 지정한 색의 배경이 보이도록 camera 컴포넌트의 Background Type 프로퍼티를 Solid color( RGB: (80, 60, 50) )로 바꾸어준다.



⚙️Tank Creation & Control


1. Layers

image

Tank 오브젝트의 Layer를 Player로 설정한다. 두 플레이어가 서로 공격할 때, 건물이나 땅을 제외한 탱크만 폭파시킬 수 있도록 한다.

  • Layer: 카메라가 일부 씬만 렌더링하거나, 콜라이더를 선택적으로 무시하기 위해 사용

image

자식 오브젝트들의 레이어 변경 여부를 묻는 메시지 창이 나타난다. 튜토리얼에서는 탱크의 가장 부모 오브젝트인 부분만 충돌하면 되므로, No, this object only를 클릭해준다.



2. Audio

image

게임이 진행되는 동안, 탱크의 엔진은 항상 동작할 것이므로 EngineIdle 클립을 설정한 후, Loop 프로퍼티를 체크해준다. 이후 오디오 컴포넌트를 하나 더 생성한 후, 오디오 소스에 재생할 특정 오디오 클립을 알려주기 위해 재생 체크 박스를 모두 해제해준다.


이후 게임 매니저 오브젝트에 의해서 탱크가 생성되도록 프리팹화한다. (Scene에 단순 게임 오브젝트로 남아있지 않도록 한다.) 프리팹으로 여러 대의 탱크를 생성해 멀티 플레이가 가능하도록 할 수 있다.



3. 3D Mesh

ezgif com-gif-maker (8)

DustTrail 모델을 드래그했을 때의 애니메이션 효과

image

탱크가 움직일 때 탱크 오브젝트 주변에 먼지가 날리는 애니메이션이 발생하도록 DustTrail 모델을 자식 오브젝트로 드래그한다.

image

image

Simulation Space 프로퍼티를 World로 설정한 후, Emission을 Distance로 지정해 주어 이동거리마다 입자가 방출하는 효과를 낼 수 있다.

  • Rate over Distance: 한 번에 이동할 때마다 방출되는 입자의 수


image

입자들의 생성부터 소멸까지의 size와 Lifetime. Size over Lifetime 프로퍼티에서 확인할 수 있다.


탱크의 트랙(바퀴의 열)마다 먼지가 날리는 애니메이션이 발생해야 하므로, DustTrail을 복사해 LeftDustTrail과 RightDustTrail을 만들어준다. (^D)

  • LeftDustTrail의 Position: ( -0.5, 0, -0.75 )
  • RightDustTrail의 Position: ( 0.5, 0, -0.75 )



4. TankMovement.cs

오디오 설정, 전후 이동, turn 설정

  • m_: 멤버 변수.
    • 드래그앤드롭이나 에디터에서 게임을 디자인할 때 사용하는 것이 아닌 변수는 대부분 private으로 선언한다. (단순히 기능성을 위해 만들어짐)
    • public으로 선언되는 경우: 속도, 회전 속도, 게임 내 동작 조정


  • m_PlayerNumber: 플레이어 번호.
    • tank manager가 얼마나 많은 탱크가 있고, 어떤 조작이 어떤 탱크에 할당되는 것인지 관리하기 위해 사용
    • 만약 tank1이 이겼을 경우 UI에 플레이어 1이 이겼음을 알리거나, 1에 해당하는 입력 키들이 tank1을 조종
      • Input Manager에서도 움직임을 조작할 수 있으나, 플레이어 번호를 지정하면 작동 방식 간략화 가능
  • m_Speed: 탱크의 속도
  • m_TurnSpeed: 시간에 따른 회전 각도
  • m_MovementAudio: 엔진 동작 소리를 나타내는 오디오 소스
    • 상황마다 변수에 다른 오디오 소스를 할당하여 운전할 때와 정지할 때 다른 클립 재생 가능
  • m_PitchRange: 음 높이 범위. 이후 랜덤값을 넣어 탱크마다 엔진 소리를 다르게 함


image

  • Input Manager의 입력 키값을 문자열으로 받기 위해 m_MovementAxisNamem_TurnAxisName 모두 String으로 선언
    • m_MovementAxisName 수평 axis
    • m_TurnAxisName: 수직 axis
  • m_Rigidbody: 탱크의 차체를 움직이게 하기 위해 사용
  • m_MovementInputValue, m_TurnInputValue: 실제로 입력을 받았을 때의 값
    • 변수에 입력값을 저장하고 원하는 메서드에 활용 가능
  • m_OriginalPitch: 원본 소리의 높낮이를 기준으로 음높이 변경
    • 높낮이 기준이 현재 pitch가 되면 엔진 효과음이 계속 높아질 수 있음


4.1. Awake()

  • OnEnable(): 게임 시작 후 Awake()Update() 메서드 사이에 호출
    • .isKinematic = false: 탱크의 rigidbody가 Kinematic을 따르지 않도록 함
      • isKinematic: true시 강체가 운동학의 영향만을 받음. 물리학 계산 무시
      • 강체가 운동학을 따를 경우 이동하는데 문제 발생
  • OnDisable(): 게임을 종료할 때 호출
    • .isKinematic = true: 탱크가 폭발할 때는(게임 종료) 더이상 탱크에 힘이 가해지지 않도록 운동학 사용


4.2. Start(), Update()

  • m_MovementAxisName, m_TurnAxisName: 각각 수평, 수직 이동 축에 m_PlayerNumber 변수를 결합해 Input Manager가 어느 플레이어의 이동값인지 확인할 수 있도록 함
  • m_OriginalPitch: 이동 효과음의 원본 음역대 저장

image

Audio Source 컴포넌트의 프로퍼티 (Pitch)

오디오 클립이 스크립트에 할당되면 Pitch 값을 얻거나 조작할 수 있다!


  • Update(): 모든 프레임 실행
    • Input.GetAxis(): Input Manager에서 입력값 할당
      • 프레임 update마다 입력값이 계산되므로, Update()메서드에서 Input을 받는 것이 가장 바람직함
      • 인자는 일반적으로 String 변수
    • EngineAudio(); 모든 프레임마다 올바른 오디오를 재생하도록 업데이트에서 선언


4.3. EngineAudio()

  • 조건문 if()
    • 수평, 수직 이동값이 모두 $\vert 0.1 \vert$ 보다 작은 경우, 움직이지 않는 것으로 판단
    • MovementAudio.clipEngineDriving이라면 EngineIdling으로 변경
    • Audio pitch값을 OriginalPitch 에 기반한 PitchRange 내에서 Random으로 저장
  • else문에서는 Audio clip을 Idle에서 Driving으로 변경


4.4. FixedUpdate()

  • FixedUpdate(): 렌더링된 모든 프레임을 실행하지 않고, 실행 중인 모든 물리 step들을 실행
    • 일반적으로 물리 기능(이동, 회전 등)을 FixedUpdate 함수에 호출


Move()

  • 탱크가 움직일 Vector 좌표 계산 메서드
  • transform.forward * m_MovementInputValue * m_Speed * Time.deltaTime
    • 정방향 벡터와 입력값을 곱해 입력량에 따라 벡터의 크기 조정
      • 입력값이 $1$일 경우 앞으로 이동, $-1$일 경우 뒤로 이동
    • 프레임마다 이동할 속도와 1프레임 당 실행 시간을 곱해 프레임 처리 시간과 상관없이 이동 속도를 유지할 수 있도록 계산
  • MovePosition()
    • Rigidbody 강체에 움직임을 적용할 것이므로 현재 Rigidbody값과 이동값을 가산


Turn()

  • m_TurnInputValue * m_TurnSpeed * Time.deltaTime: 프레임 당 회전량 계산
    • 회전량을 알기 위해 vector 대신 float 사용
  • Quaternion.Euler: 유니티에서 회전을 다루는 각도계. float값을 변환시켜 주어야 함
    • y축에 회전량 지정
  • MoveRotation()
    • 강체의 rotation값에 turnRotation을 곱해주어 회전 정도를 적용

ezgif com-gif-maker (9)

y축 rotation을 변경할 경우, 탱크가 자연스러운 방향으로 회전한다.



5. 스크립트 적용

image

public 변수(프로퍼티)에 해당하는 값을 대입해 Tank가 스크립트를 반영할 수 있도록 완성한다.


ezgif com-gif-maker (10)

결과 화면

Leave a comment