전체 페이지뷰

2017년 7월 28일 금요일

Unity Tutorial: Survival Shooter, part 3

Health HUD



이제 새로운 UI 시스템을 접목시켜보려 합니다. 그를 위해 먼저 할 일은 2D 모드 버튼을 누르는 것입니다. 유니티 4.3 버전에서 도입된 이 모드는 GUI를 사용하는데에 도움을 줍니다.

씬뷰 탭에 2D라는 작은 버튼이 있습니다. 이 버튼을 누르면 씬뷰가 평평한 화면으로 전환되고 기즈모가 사라집니다.



다음으로 GameObject>UI>Canvas를 선택하여 화면에 캔버스를 만들고 이름을 HUDCanvas라고 해줍니다.

이 Canvas라는 것은 모든 UI 요소의 부모이며, 기본적으로 rect Transform을 기반으로 이루어집니다. HUDCanvas를 선택하고 Frame selected하면 이 캔버스의 아웃라인을 볼수가 있는데 게임 필드에 비해 굉장히 크게 보여집니다. 그것은 캔버스 크기를 1 world 유닛당 1 픽셀로 매핑시키기 때문입니다. 우리 게임이 만약 512*512픽셀 화면에서 돌아간다면 이 캔버스는 512*512 유닛 크기로 그려집니다. 우리 게임에서 캡슐 콜라이더를 고작 0.6 유닛으로 설정했던 것을 생각해보면 이 512*512 유닛이 얼마나 큰 것인지 상상이 될 것입니다.

캔버스가 화면에 렌더링 되는 방법에는 몇가지 종류가 있는데 현재는 Screen Space - Overlay로 설정되어 있습니다. 이것은 화면 전체를 채우는데 쓰이는 옵션이고, Screen Space - Camera는 입체적인 UI를 만들수 있으며, World Space는 말풍선처럼 3D 내에 들어가는 것입니다. 우리는 Screen Space -Overlay를 그대로 둡니다.

거기에 Add Component>Layout>Canvas Group을 추가합니다. canvas group은 childrend의 Alpha, Raycast등을 제어하는데에 쓰입니다. 이 캔버스는 디스플레이용이므로 사용자 조작에 반응해서도 안 되고, 빛을 가려서도 안되므로 Interactable, Block Raycast를 모두 체크 해제합니다.


다음으로 이 HUDCanvas에 우클릭하여 Create Empty하여 빈 오브젝트를 자식으로 만들어주고 이름을 HealthUI라고 합니다.


다른 곳에 빈 오브젝트를 만들어 옮긴 것이 아니고 직접 우클릭하여 자식을 만들었기 때문에 HealthUI에는 자동으로 Rect Transform이 포함되어 있습니다.

씬뷰 위의 툴 중 Rect tool을 선택하면 새로 생성된 HealthUI의 사각 모서리마다 파란 동그라미가 생겨나고, 그것을 드래그하면 크기와 모양을 변형 시킬 수 있습니다. 가운데에는 프로펠러 모양이 있는데 그것은 앵커 포인트입니다.



우리는 이 HealthUI를 화면 좌하단에 고정시키려 합니다. 인스펙터 창의 Rect transform에 있는 사각 표시를 눌러 프리셋창을 띄우고 Alt, Shift 키를 누른 상태에서 좌하단을 클릭해 피벗, 포지션을 동시에 왼쪽 아래로 옮깁니다. 그리고 Width는 75, Height는 60으로 합니다.


이 HealthUI에 하트 모양 아이콘을 추가하기 위해 우클릭 후 UI>Image를 선택하여 또 하나의 자식을 만들고 이름을 Heart로 정합니다.


Width, Height를 모두 30으로 합니다. 이제 좌하단의 HealthUI 안에 Heart 아이콘을 위한 하얀 공간이 만들어졌습니다.


인스펙터 창에서 Source Image 옆 동그라미를 클릭하면 선택할 수 있는 이미지 목록이 나타나고, 그 중 Heart를 골라 연결해줍니다.


이제 하트 게이지를 위한 슬라이더를 추가해야 합니다. 다시 HelthUI를 우클릭하고, UI>Slider를 선택하고 이름은 HealthSlider로 하겠습니다.


이 슬라이더에는 핸들이 달려있는데 지금은 단지 게이지일 뿐 사용자가 조작할 필요가 없으므로 Hierarchy에서 Handle Slide Area를 지웁니다.

그리고 슬라이더의 Pos X는 95로 해서 하트아이콘 옆으로 위치하게 합니다. 또 영상과는 달리 슬라이더의 Width Default 값이 160으로 바뀌었습니다. 영상처럼 130으로 바꾸겠습니다.

영상과 다른 것이 또 있습니다. 이제는 HealthSlider에 Backgorund라는 child 오브젝트가 있습니다. 그것을 선택하고 Color를 눌러 Alpha를 120으로 바꿉니다.



또 HealthSlider의 자식인 Fill Area를 선택하고 Right를 15에서 0으로 변경합니다.



이제 슬라이더의 인스펙터 창으로 가서 Slider Component를 들여다 봅시다.
Interactable에 체크가 되어 있지만 부모인 캔버스 자체가 Interactable하지 않으므로 굳이 체크 해제할 필요가 없습니다. Transition은 None으로 바꾸고, Max Value는 100으로 하겠습니다. 그리고 Value 역시 100으로 옮겨 놓습니다.


다음으로 할 일은 우리가 상처를 입을 때마다 시각적 효과를 주는 것입니다. 화면과 슬라이더가 작기 때문에 상처를 입었음을 잘 알려주기 위해서 필요합니다. 이번엔 HudCanvasUI>Image를 선택하여 이미지를 추가하고 이름은 DamageImage라고 하겠습니다.


그리고 Rect transform의 프리셋에서 가장 우하단을 Alt+클릭하여 화면 전체를 커버하게 만듭니다. 전체 화면이 하얗게 되었는데 Color를 누르고 Alpha를 0으로 만들어주면 셋업이 끝납니다.





Player Health



플레이어 HP를 나타내는 슬라이더를 만들었으니 이제 구체적으로 그 내용을 구현하고 UI와 상호작용하게 만들어야 합니다. 그러자면, 적 역시 일정한 HP를 가지고 플레이어를 공격해야 하고, 플레이어 역시 적을 공격하여 그 HP를 깎을 수 있어야 하겠습니다.

사실, 플레이어의 HP용 스크립트가 이미 Assets>Scripts>Player 폴더에 PlayerHealth라는 이름으로 작성되어 준비되어 있습니다. 드래그하여 Hierarchy의 Player에 연결합니다. 화면이 아직 2D 모드로 되어 있다면 해제하고 Player에 Frame Selected하거나 더블클릭, 또는 F키를 누르던 하여 Player를 비춰줍니다.

잠시 PlayerHealth 스크립트를 살펴봅시다.

using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using UnityEngine.SceneManagement;
public class PlayerHealth : MonoBehaviour
{
    public int startingHealth = 100;
    public int currentHealth;
    public Slider healthSlider;
    public Image damageImage;
    public AudioClip deathClip;
    public float flashSpeed = 5f;
    public Color flashColour = new Color(1f, 0f, 0f, 0.1f);
    Animator anim;
    AudioSource playerAudio;
    PlayerMovement playerMovement;
    //PlayerShooting playerShooting;
    bool isDead;
    bool damaged;
    void Awake ()
    {
        anim = GetComponent <Animator> ();
        playerAudio = GetComponent <AudioSource> ();
        playerMovement = GetComponent <PlayerMovement> ();
        //playerShooting = GetComponentInChildren <PlayerShooting> ();
        currentHealth = startingHealth;
    }
    void Update ()
    {
        if(damaged)
        {
            damageImage.color = flashColour;
        }
        else
        {
            damageImage.color = Color.Lerp (damageImage.color, Color.clear, flashSpeed * Time.deltaTime);
        }
        damaged = false;
    }
    public void TakeDamage (int amount)
    {
        damaged = true;
        currentHealth -= amount;
        healthSlider.value = currentHealth;
        playerAudio.Play ();
        if(currentHealth <= 0 && !isDead)
        {
            Death ();
        }
    }
    void Death ()
    {
        isDead = true;
        //playerShooting.DisableEffects ();
        anim.SetTrigger ("Die");
        playerAudio.clip = deathClip;
        playerAudio.Play ();
        playerMovement.enabled = false;
        //playerShooting.enabled = false;
    }
    public void RestartLevel ()
    {
        SceneManager.LoadScene (0);
    }
}
cs


이런 형태로 스크립트가 작성되어 있습니다.
using UnityEngine.UI문을 사용하였다는 것을 주의 바랍니다. UI 요소들을 사용하기 위한 선언입니다. 초기 Hp, 현재 Hp, 각종 레퍼런스들을 설정하고, Awake()내에서 초기화합니다. 플레이어가 총을 쏘는 것이 아직 구현되어 있지 않아 주석 처리되어 있습니다.

Update()에서는 데미지를 입었을 때의 시각 효과를 설정합니다.
TakeDamage()메소드는 퍼블릭으로 선언되어 있어서 이 스크립트가 아닌 다른 곳에서 사용할 수 있습니다. 적의 스크립트에서 충돌 판정 후 사용하게 될 것입니다.

다시 유니티로 돌아갑니다.

Player를 선택해보면 인스펙터창에 여러가지 프로퍼티들이 생성되어 있습니다. Health Slider, Damage Image, Death Clip을 각각 그림처럼 연결합니다.



다음으로 적이 플레이어를 공격하도록 해야 합니다. Assets>Scripts>Enemy 폴더에 EnemyAttack이라는 스크립트가 있습니다. 이 스크립트를 Hierarchy의 Zombunny에 연결하고 에디터로 열어 봅시다.

using UnityEngine;
using System.Collections;
public class EnemyAttack : MonoBehaviour
{
    public float timeBetweenAttacks = 0.5f;
    public int attackDamage = 10;
    Animator anim;
    GameObject player;
    PlayerHealth playerHealth;
    //EnemyHealth enemyHealth;
    bool playerInRange;
    float timer;
    void Awake ()
    {
        player = GameObject.FindGameObjectWithTag ("Player");
        playerHealth = player.GetComponent <PlayerHealth> ();
        //enemyHealth = GetComponent<EnemyHealth>();
        anim = GetComponent <Animator> ();
    }
    void OnTriggerEnter (Collider other)
    {
        if(other.gameObject == player)
        {
            playerInRange = true;
        }
    }
    void OnTriggerExit (Collider other)
    {
        if(other.gameObject == player)
        {
            playerInRange = false;
        }
    }
    void Update ()
    {
        timer += Time.deltaTime;
        if(timer >= timeBetweenAttacks && playerInRange/* && enemyHealth.currentHealth > 0*/)
        {
            Attack ();
        }
        if(playerHealth.currentHealth <= 0)
        {
            anim.SetTrigger ("PlayerDead");
        }
    }
    void Attack ()
    {
        timer = 0f;
        if(playerHealth.currentHealth > 0)
        {
            playerHealth.TakeDamage (attackDamage);
        }
    }
}
cs

역시 몇 개의 변수 설정으로 시작합니다. 공격 사이의 시간을 정의하는 timeBetweenAttacks와 한 번의 공격에 주어지는 데미지를 뜻하는 attackDamage를 퍼블릭으로 선언하고, 레퍼런스들 몇 개를 선언한 후 Awake()에서 초기화합니다.

OnTriggerEnter()OnTriggerExit()는 각각 트리거 내에 다른 콜라이더가 들어오고 나갈때 호출됩니다. 그 안에서 들어온 콜라이더가 player인지 확인하고 공격범위 내에 들어왔는지를 판정합니다.

Update()내에서 실제 공격을 계산합니다. 다음 공격 시간이 되었고, 공격범위 내에 있으면 공격한 후 플레이어의 HP가 0 이하가 되면 죽었다고 판정하는 코드입니다.

enemyHealth와 관련된 코드는 아직 작성되지 않었으므로 모두 주석처리 되어 있습니다.

저장 후 유니티로 돌아가 테스트 해 봅시다. Zombunny가 따라와 사정없이 플레이어의 HP를 감소시키는 것을 볼 수 있습니다. 그런데 공격 받았음을 알리기 위해 사용한 화면 번쩍임이 좀 흐린 듯 합니다.

플레이어 오브젝트를 선택하고 PlayerHealth 스크립트의 Flash Colour 프로퍼티를 클릭하여 26으로 되어 있는 알파를 110 정도로 조절합니다.


효과거 훨씬 선명해졌습니다.

댓글 없음:

댓글 쓰기