전체 페이지뷰

2017년 8월 9일 수요일

Unity Tutorial: Tanks, part 1



Scene Setup


이번에 따라 만들어볼 게임은 2인 탱크 대전 게임입니다. 온라인 접속 게임은 아니며 하나의 키보드로 두 명이 조작하는 게임입니다.

먼저 Tanks라는 이름으로 새 프로젝트를 생성합니다.

먼저 여기로 가서 전과 마찬가지로 에셋을 추가하고 Open In Unity한 후 다운받아 임포트하여 개발환경을 갖춥니다.

그리고 프로젝트창에서 Create>Folder로 새 폴더를 만들고 _Scenes라는 이름을 지어준 후 현재의 씬을 Main이라는 이름으로 저장합니다.

Hierarchy 창에 현재 Main Camera, Directional Light의 두가지 오브젝트가 있는데 그 중 Directional Light를 삭제합니다.

다음으로 Prefabs폴더에 있는 LevelArt를 Hierarchy창으로 드래그해 넣습니다. 이것은 게임의 배경 지형을 미리 프리팹화 해서 넣어둔 것입니다.


씬에 이 배경 프리팹을 인스턴스화해서 넣게되면 위 그림과 같은 상태가 되고 우하단에 파란색 바가 생기면서 조명을 baking하게 됩니다.

조명 설정을 좀 변경해야할 필요가 있으므로, Window>Lighting>Settings 를 선택하여 창을 띄워서 인스펙터 창 옆에 도킹시킵니다(동영상은 2015년 영상인데 그 이후로 유니티에 또 많은 변화가 있었습니다. 현재 버전에 맞도록 최대한 연구해서 하겠지만 부족한 점이 있을 수 있으므로 혹 보시는 분이 있다면 참고 바랍니다).



처음으로 할 일은 Lighting 창 최하단의 Auto Generate를 체크 해제 하는 것입니다. 이것을 체크 해제 하면 필요한 때에 조명을 bake할 수 있게 됩니다. 여러가지 변경을 거치고 필요한 때에 전체 lightmap을 bake하기 위해서 체크 해제합니다.

다음으로 Mixed LightingBaked Global Illumination을 체크해제합니다. 포탄이 발사될 때 포탄 주변으로 조명이 들어가고 그것이 씬을 밝혀주는 real time lighting을 사용할 것입니다. (Global Illumination이란 것은 간단히 말해 간접광을 말하는 것입니다. 광원에서 나온 빛이 직접 조명을 한다면 그것이 물체에 반사되어 여러 군데에서 간접 조명을 만들게 됩니다. 따라서 좀 더 현실에 가까운 장면을 만들기 위해서는 간접광까지 처리되어야 하는 것입니다.)

Environment Ligthing에서 Source를 Skybox가 아닌 Color로 변경하고 Ambient Color 창을 활성화 하여 HEX Color를 483E71로 합니다.


여기까지 했으면 이제 Auto Generate 옆의 Generate Lighting을 눌러 현재의 세팅에서 조명을 bake합니다.



다음으로 카메라를 설정하겠습니다.

메인 카메라의 디폴트 위치는 (0, 1, -10)입니다. 이 위치를 (-43, 42, -25)로 옮기고, 로테이션을 (40, 60, 0)으로 합니다. 그리고 Projection을 Orthographic으로 바꿔줍니다. Clear Flags는 Skybox에서 Solid Color로 변경하고 Color RGB를 (80, 60, 50)으로 합니다.




Tank Creation & Control


이제 탱크를 씬에 더하고 움직이게 만들어볼 차례입니다.

Assets>Models 폴더의 Tank 모델을 드래그하여 Hierarchy 창에 넣고 F키를 눌러 확대해 봅니다. 이 배경들에 들어가는 탱크들을 다른 배경과 분리하기 위해서 Layer를 Players로 바꿉니다.


그러면 모든 자식 오브젝트들의 레이어도 옮길 것인가 물어 보는데, "No, this object only"를 고릅니다. 우리는 콜라이더가 있는 부분만 Players레이어로 옮겨야 하고 그 컴포넌트는 parent에 속하기 때문입니다.

다음으로 Add Component>Physics>Rigidbody를 적용하고 탱크가 잘못된 움직임을 보이지 않도록 Constraints를 조절합니다.



다음은 콜라이더를 적용할 차례입니다. 탱크 자체에 복잡한 콜라이더는 필요없으므로 Add Component>Physics>Box Collider를 선택하고 크기를 그림과 같이 조절해줍니다.


이제 탱크 엔진 소리를 더해주려고 합니다. 추후 스크립트를 통해 조금씩 다른 높이의 소리를 내게할 예정입니다.  Add Component>Audio>Audio Source를 선택합니다.
EngineIdle이라는 음원을 연결해 주고, 엔진 소리는 계속 나야하므로 Loop에 체크합니다.


다음으로는 포탄 발사 소리를 더해줘야 합니다. 여기에는 두가지 소리가 필요한데 하나는 장전하는 소리고 다른 하나는 발사되는 소리입니다. 이 게임에서는 발사 버튼을 계속 누르고 있으면 발사거리가 길어지고, 놓으면 발사됩니다. 따라서 두 가지 소리가 필요합니다.

역시 Add Component>Audio>Audio Source를 선택하고 Play On Awake는 체크 해제합니다. 일단 이 시점에서 어떤 오디오 클립도 연결하지 않은 채로 Tank 오브젝트를 하단 Project창의 Assets>Prefabs폴더로 드래그하여 프리팹화 하고 씬을 저장합니다.



이 탱크는 사막을 돌아다니므로 움직일 때 먼지가 피어날 것입니다. 그 효과가 이미 프리팹 폴더에 DustTrail이라는 이름으로 들어가 있습니다. 이 DustTrail은 유니티에서 먼지나 스파크와 같은 시각효과를 만들 수 있는 flat sprite 오브젝트들로 구성되어 있으며 3D 메쉬를 가지지는 않습니다. 이 DustTrail을 드래그하여 Hierarchy의 Tank로 끌어놓아 자식으로 만듭니다.


자식으로 들어간 DustTrail에 우클릭> Dupilicate 하여 두개로 만들고, 하나는 LeftDustTrail, 나머지는 RightDustTrail로 이름을 바꿉니다.


그리고, LeftDustTrail의 Position을 (-0.5, 0, -0.75), RightDustTrail의 포지션은 (0.5, 0, -0.75)로 해서 무한궤도 바퀴 아래로 오게 합니다.

이제 이 탱크의 움직임을 제어하기 위해 Assets>Scripts>Tank 폴더에 미리 준비된 TankMovement 스크립트를 Tank 오브젝트에 드래그하여 연결하고 에디터로 엽니다.

이 스크립트에서 작성할 내용은
1. 입력 체크
2. 오디오 설정
3. 전/후진 움직임 구현
4. 회전 움직임 구현
입니다.

코드의 많은 부분이 주석 처리되어 있는 것이 보입니다. /* */ 주석을 지우고 나니 7개의 public 변수와 6개의 private 변수, 그리고 메소드들이 보입니다.

public int m_PlayerNumber = 1;         
public float m_Speed = 12f;            
public float m_TurnSpeed = 180f;       
public AudioSource m_MovementAudio;    
public AudioClip m_EngineIdling;       
public AudioClip m_EngineDriving;      
public float m_PitchRange = 0.2f;
 
private string m_MovementAxisName;     
private string m_TurnAxisName;         
private Rigidbody m_Rigidbody;         
private float m_MovementInputValue;    
private float m_TurnInputValue;        
private float m_OriginalPitch;   
cs

레퍼런스와 기본 수치들로 이루어진 변수들입니다.

Awake()함수에서 Rigidbody에 대한 레퍼런스를 얻어 오고,
private void Awake()
{
    m_Rigidbody = GetComponent<Rigidbody>();
}
cs

OnEnable() 메소드는 스크립트가 작동된 후, 그러니까 Awake() 다음, Update() 전에 호출되는 메소드인데 여기서 탱크가 움직일수 있도록 합니다.
private void OnEnable ()
{
    m_Rigidbody.isKinematic = false;
    m_MovementInputValue = 0f;
    m_TurnInputValue = 0f;
}
cs
kinematic이라 하면 외력이 작용하지 못하게 하는 것을 말합니다. 그래서 isKinematicfalse이면 외력이 작용할 수 있도록 하는 것이고, 게임이 끝났을 때 true로 적용되어 더 이상 움직이지 못하도록 해 줄 것입니다. 그리고 움직임에 대한 입력값들을 0으로 초괴화 해둡니다.

Start()에서는 플레이어이름을 기반으로 축이름을 정해주고 오리지널 오디오 소스의 음높이를 지정합니다.
private void Start()
{
    // 플레이어 이름에 따른 축이름 저장
    m_MovementAxisName = "Vertical" + m_PlayerNumber;
    m_TurnAxisName = "Horizontal" + m_PlayerNumber;
 
    // 오디오소스의 원래 피치를 저장
    m_OriginalPitch = m_MovementAudio.pitch;
}
cs


30fps 혹은 60fps로 호출되는 Update()에서는 사용자의 입력값을 받아오고 엔진음이 실행되도록 합니다.
private void Update()
{
    // Store the player's input and make sure the audio for the engine is playing.
    m_MovementInputValue = Input.GetAxis(m_MovementAxisName);
    m_TurnInputValue = Input.GetAxis(m_TurnAxisName);
 
    EngineAudio();
}
cs

EngineAudio()에서는 움직임을 체크하여 회전이나 전후진 입력값이 거의 없을 경우 EngineIdling으로, 입력값이 들어올 경우엔 EngineDriving으로 바꾸어 플레이합니다. 그 와중에 소리가 다르게 들릴 수 있도록 피치를 랜덤으로 조절합니다.

private void EngineAudio()
{
    // Play the correct audio clip based on whether or not the tank is moving 
    // and what audio is currently playing.
    if (Mathf.Abs(m_MovementInputValue) < 0.1f && Mathf.Abs(m_TurnInputValue) < 0.1f)
    {
        if (m_MovementAudio.clip == m_EngineDriving)
        {
            m_MovementAudio.clip = m_EngineIdling;
            m_MovementAudio.pitch = Random.Range(m_OriginalPitch - m_PitchRange,
                m_OriginalPitch + m_PitchRange);
            m_MovementAudio.Play();
        }
    }
    else
    {
        if (m_MovementAudio.clip == m_EngineIdling)
        {
            m_MovementAudio.clip = m_EngineDriving;
            m_MovementAudio.pitch = Random.Range(m_OriginalPitch - m_PitchRange,
                m_OriginalPitch + m_PitchRange);
            m_MovementAudio.Play();
        }
    }
}
cs

FixedUpdate()는 물리적 변화가 있을 때 호출됩니다. 이 안에서 탱크가 움직이도록 하는 코드를 넣습니다.
private void FixedUpdate()
{
    // Move and turn the tank.
    Move();
    Turn();
}
cs

다음으로 Move()Turn()을 작성합니다.
private void Move()
{
    // 입력된 값에 맞는 전후방 벡터를 형성
    Vector3 movement = transform.forward * m_MovementInputValue * m_Speed * Time.deltaTime;
 
    // 벡터를 rigidbody에 적용
    m_Rigidbody.MovePosition(m_Rigidbody.position + movement);
}
 
 
private void Turn()
{
    // 입력된 값으로 몇도 회전하는지를 계산
    float turn = m_TurnInputValue * m_TurnSpeed * Time.deltaTime;
 
    // 유니티의 회전 단위인 Quaternian으로 변환
    Quaternion turnRotation = Quaternion.Euler(0f, turn, 0f);
 
    // 회전을 rigidbody에 적용
    m_Rigidbody.MoveRotation(m_Rigidbody.rotation * turnRotation);
}
cs

세이브 후 유니티로 돌아갑니다.

스크립트에 몇가지 프로퍼티를 연결합니다.

실행해 보면 탱크가 요란한 소리와 함께 움직입니다. 이제 변화된 내용을 Apply를 눌러 프리팹에도 적용시키고

씬을 저장합니다.

댓글 없음:

댓글 쓰기