본문 바로가기
게임 개발/유니티 게임 개발 일지

#9 조이스틱과 HUD 구현하기

by FlowTree 2020. 2. 19.
반응형

조이스틱과 HUD 구현 목적

  • 조이스틱: 모바일 환경에 맞는 조작 기능을 제공하기
  • HUD: 게임 플레이에 필요한 기본UI 구현하기(캐릭터 HP/MPbar, 공격과 스킬버튼, 스코어)

 

구현된 영상

이번 글 이후로 구현된 영상을 먼저 보여줘야겠다. 영상으로 먼저 확인하면 머릿속에 그려져서 어떤 기능들이 있고 어떻게 구현할지 본문을 쉽게 이해할 수 있을 것 같다.

 

https://youtu.be/YdYcJVVYWi8

 

조이스틱 구현하기

조이스틱 구현 참고

유니티UI-API설명(RectTransform, 앵커 등)
https://docs.unity3d.com/kr/530/Manual/UIBasicLayout.html

 

유튜브에 조이스틱을 구현하는 여러 동영상이 있었지만 스크립트도 깔끔하고, 이해하기도 좋아서 아래 링크를 참고했다.

https://truecode.tistory.com/32 

 

위의 블로그에서는 Event Trigger를 이용해서 조이스틱을 드래그할 때(터치했을 때), 조이스틱을 놓았을 때를 추가해서 사용했지만, 다른 구현 영상에서는 인터페이스를 이용해서 스크립트를 작성할 때 조건들을 간편하게 구현했다. 그래서 Event Trigger를 추가하지 않고 아래의 영상을 참고하여 구현했다.

https://www.youtube.com/watch?v=2pf1FE-Xcc8

조이스틱 리소스

Kenney님의 무료 UI 에셋을 이용했다. UI이외에 다른 리소스도 많이 있으니 살펴보면 좋을 것 같다.

https://www.kenney.nl/assets?q=ui

 

1.HUD Canvas 생성

화면에 UI를 보여주는 Canvas 오브젝트를 생성하고 이름을 HUD Canvas로 변경했다.

하이어라키 - 오른쪽 클릭 - UI - Canvas를 선택하여 Canvas를 생성할 수 있다.

 

Canvas를 생성하고 인스펙터창 - Canvas Scaler 컴포넌트 - UI Scale Mode에서 Scale With Screen Size를 선택한다. Reference Resolution의 값을 16:9 해상도 X 1280, Y 720으로 설정했다. 이 게임은 모바일 환경에서 세로모드로 실행할 것이기 때문에 16:9 해상도로 설정했다.

 

 

2.조이스틱 패널 생성

하이어라키 - UI - Panel로 조이스틱UI를 담을 패널을 생성한다. 패널 이름을 JoystickUI로 변경한다.

JoystickUI - 인스펙터 - RectTransform에서 앵커 프리셋을 좌하단 기준으로 변경한다. Alt를 누른 상태에서 RectTransform의 정렬 그림을 클릭하고 앵커 프리셋에서 좌하단 기준을 선택한다.

 

 

Pos X, Y를 변경하면 앵커를 기준으로 JoystickUI의 위치를 변경할 수 있다.

Width는 패널의 너비(폭), Height는 패널의 높이를 변경할 수 있다.

 

 

 

Pos X 160, Pos Y 160, Width 250, Height는 250으로 변경했다.

 

 

3.JoystickUI(패널)에서 Raw Image 생성하여 조이스틱 리소스 넣기

JoystickUI에서 오른쪽 클릭 UI - Raw Image를 추가한다. 이름을 JoystickBackground로 변경한다. 이곳에는 조이스틱의 배경을 넣어준다. Raw Image에서 Texture를 선택하여 조이스틱 배경 리소스를 넣는다. 배경 크기는 패널 크기와 같게 변경한다.

 

 

JoystickBackground에서 RawImage를 생성한다. 이름은 Joystick으로 변경하고 조이스틱의 스틱 리소스를 넣어준다. 크기는 JoystickBackground보다 작게 100*100 크기로 변경했다. 위와 동일한 방법으로 스틱 리소스를 넣는다. 그러면 조이스틱 리소스 준비는 끝이다.

 

 

만약 조이스틱의 스틱이 정중앙이 아니라면 앵커 프리셋을 middle, center로 선택하자.

 

 

4.조이스틱 스크립트 작성하기

Joystick 스크립트를 구현하다 보니 기존의 PlayerInput과 PlayerMovement 스크립트의 기능을 합치게 됐다.

Joystick의 기능은 스크립트의 주석을 참고해주세요.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems; //EventSystems 기능을 이용함 IDragHandler, IEndDragHandler
 
 
//PlayerInput + Movement 기능 => Joystick 스크립트 
 
 
public class Joystick : MonoBehaviour, IDragHandler, IEndDragHandler
{
    //IPointerDownHandler, IPointerUpHandler, IDragHandler 모두 인터페이스 ctrl+. 누르거나 오른쪽 클릭 빠른작업+리팩토링하면 아래의 함수 생김
 
    public Transform player; //플레이어
    public Transform stick; //조이스틱의 스틱
       
    [SerializeField] //SerializField는 private로 선언된 변수를 인스팩터창에서 볼 수 있게 해줌
    private Vector3 firstPos; //조이스틱의 처음 위치
    [SerializeField]
    private Vector3 dirJoystick; //조이스틱의 방향벡터
    [SerializeField]
    private float radius; //조이스틱 배경의 반지름
    private bool bCanMove; //플레이어의 움직임 스위치
 
    private Animator playerAnimator;
    private Rigidbody playerRigidbody;
 
    // Start is called before the first frame update
    void Start()
    {
        playerAnimator = GameObject.Find("Player").GetComponent<Animator>();
        playerRigidbody = GameObject.Find("Player").GetComponent<Rigidbody>();
 
        radius = GetComponent<RectTransform>().sizeDelta.y * 0.5f; //해당 오브젝트의 RectTransform의 sizeDelta.y값(해당 오브젝트의 y값)의 반 = 반지름
        firstPos = stick.transform.position; //Start()메서드가 실행되면 firstPos에 해당 오브젝트의 처음 위치를 저장한다.
 
        //캔버스 크기에 대한 반지름 조절
        float canvas = transform.parent.GetComponent<RectTransform>().localScale.x;
        radius *= canvas;
        bCanMove = false;
    }
 
    void FixedUpdate()
    {
        if(bCanMove)
        {
            player.transform.Translate(Vector3.forward * Time.deltaTime * 4f); //플레이어 캐릭터 앞으로 이동
        }
    }
 
 
    //조이스틱을 드래그했을 때
    public void OnDrag(PointerEventData eventData)
    {
        bCanMove = true;
        Vector3 dataPos = eventData.position;
 
        //조이스틱을 이동시킬 방향벡터 계산(오른쪽, 왼쪽, 위, 아래)
        dirJoystick = (dataPos - firstPos).normalized;
 
        //조이스틱의 처음 위치와 현재 드래그 중인 위치와의 거리를 계산
        float dist = Vector3.Distance(dataPos, firstPos);
 
        //거리가 반지름보다 작으면 조이스틱을 현재 드래그 중인 곳으로 이동
        if(dist < radius)
        {
            stick.position = firstPos + dirJoystick * dist;
        }
        
        //거리가 반지름보다 크면 조이스틱을 반지름의 크기만큼만 이동
        else
        {
            stick.position = firstPos + dirJoystick * radius;
        }
 
        player.eulerAngles = new Vector3(0, Mathf.Atan2(dirJoystick.x, dirJoystick.y) * Mathf.Rad2Deg, 0); //플레이어 캐릭터의 방향 변경
 
        playerAnimator.SetBool("isMove"true); //달리기 애니메이터의 파라미터인 bool을 true로 변경하여 달리기 애니메이션 실행
 
    }
 
    //조이스틱을 놓았을 때
    public void OnEndDrag(PointerEventData eventData)
    {
        stick.position = firstPos; //스틱을 원래의 위치로
        dirJoystick = Vector3.zero; //방향을 초기화
        bCanMove = false;
        playerAnimator.SetBool("isMove"false); //달리기 애니메이터의 파라미터인 bool을 false로 변경하여 달리기 애니메이션 중지, Idle로 변경
    }
}
 

 

 

 

 

알게된 것: 인터페이스(유니티 책에서 배웠는데 까먹음 복습하기),  어트리뷰트[SerializeField]

 

5.스크립트 추가 및 컴포넌트 필드 설정하기

완성된 Joystick 스크립트는 JoystickBackground 오브젝트에 추가한다. Joystick의 빈 컴포넌트 필드에 맞게 Player오브젝트, Joystick 오브젝트를 넣어준다.

 

 

6.애니메이터의 상태 변경

Movement 상태에서 float 타입의 Move 변수로 Input값에 따라서 Idle, Run 애니메이션을 실행하던 것에서 Bool로 Idle과 Run 상태 변경을 구현하기 위해서 Movement 블렌드 트리에서 Idle, Run 상태를 분리했다.

 

#참고링크
http://blog.naver.com/PostView.nhn?blogId=gold_metal&logNo=220491375543&parentCategoryNo=&categoryNo=40&viewDate=&isShowPopularPosts=true&from=search

 

기존 상태

 

Movement의 블렌드 트리

 

변경된 상태 - Idle과 Run 상태를 분리했다. BasicAttack은 스킬 구현할 때 적용할 예정

 

Idle > Run 트랜지션과 조건(isMove)

 

Run > Idle 트랜지션과 조건(isMove)

 

 

 

HUD와 버튼 구현하기

구현할 HUD

  • HpBarUI
  • MpBarUI
  • ButtonsUI(4개)
  • ScoreUI(Text)

 

HpBar, MpBar 구현하기

구현할 기능

  • 플레이어 캐릭터의 현재HP(MP)와 최대HP(MP)를 실시간으로 표시하기
  • 게임 처음 시작 시 초기값 HP, MP를 적용하기

 

1.HUD Canvas에 Slider 추가하고 배치하기

HUD Canvas를 선택하고 오른쪽 클릭 - UI - Panel을 클릭하고 이름을 HpBarUI로 변경한다. 패널은 UI를 구분하기 위해서 생성했다. HpBarUI를 선택하고 오른쪽 클릭 - UI - Slider를 클릭하여 Slider를 추가한다. Handle Slide Area는 필요가 없어서 삭제한다.

 

 

Slider를 선택하고 인스펙터 창의 앵커 프리셋을 이용하여 Slider를 원하는 곳에 배치한다. 나는 게임을 플레이 시 HPBar와 MPBar가 잘보이고 플레이 시야를 방해하지 않을 공간이 캐릭터 바로 밑이라고 생각하여 화면 중앙 하단에 배치했다.

 

 

기본 UI 리소스를 이용하면 filled로 채울 때 부자연스러운 문제가 있다. 그래서 HpBar 리소스(이미지)를 새로 만들어서 Slider의 Background와 Fill area의 Fill 오브젝트의 리소스를 내가 만든 HpBar 리소스로 변경했다.

 

 

가끔씩 사용할 이미지가 안 보이는 경우가 있는데 이때는 Project에서 해당 이미지 리소스를 선택하고 인스펙터창에서 Texture Type을 Sprite (2D and UI)를 선택하고 Apply를 하면된다.

 

 

HpBarUI 패널의 크기를 적절하게 변경하고(width 300, height 33으로 설정했음) 자식 오브젝트들(Slider와 하위 오브젝트들)을 모두 선택 후 앵커 프리셋으로 Stretch를 선택하여 자식 오브젝트들이 HpBarUI 패널 크기에 맞도록 리소스의 크기를 늘려 주었다.

 

HP라는 것을 쉽게 인지할 수 있도록 HpBar의 색을 빨간색으로 정했다. Background의 색은 HP가 비어있음을 보여주기 위해서 HP의 빨간색보다 연한색으로 선택을 했고 Fill의 색은 빨간색으로 선택했다. Fill에서는 색이 채워지는 방법을 정할 수 있어서, Image Type을 Filled 채우기로 변경 > Fill Method를 Horizontal 수평으로 변경하여 좌우로 색이 채워지도록 했다. 채워지는 시점은 Fill Origin에서 정할 수 있다. 왼쪽에서 오른쪽으로 색을 채울려고 했기 때문에 기본값인 left로 두었다.

 

HpBarUI의 Slider의 인스펙터창 > Slider 컴포넌트에서 Interactable를 체크해제, Transition을 None하여 터치 상호작용을 꺼준다. 그리고 Max value를 100으로 변경하여 Slider의 최대값을 설정 해준다. Value 값을 움직이면 Slider 채움 상황을 확인할 수 있다.

 

 

 

2.Text (CurHP, MaxHp)추가하기

현재HP와 최대HP값을 보여줄 Text를 추가한다. 

 

Text 오브젝트가 안보일 때가 있는데 Slider 오브젝트보다 아래에 있으면 된다.(Slider 오브젝트의 자식오브젝트가 되는 것이 아님, 같은 등급?에서 맨 아래).

 

Text의 인스펙터창에서 Text의 Width, Height, 내용, 폰트와 폰트 사이즈, 정렬 방식을 설정할 수 있다.

폰트 사이즈보다 Width와 Height 크기가 적절하지 않으면(보통 작을 때), Text가 내용이 안보인다.

Text가 안보일 때는 폰트 사이즈를 줄이거나, Width와 Height 크기를 키우면 된다. 

 

 

PlayerStats에서 HpBar기능과 연동하여 구현해야 하는데 현재는 구현X, 적과 적 HpBarUI를 구현하면서 같이 구현할 예정이다.

 

#MpBarUI는 HpBarUI처럼 구현하면된다. 가장 쉬운 방법은 HpBarUI를 복제해서 이름과 채움 색을 바꾸면 된다.

 

3.스크립트 추가 및 빈 컴포넌트 필드 추가하기

유니티 책을 참고하여 LivinEntity 클래스를 상속받는 PlayerStats 스크립트를 작성했다. 캐릭터의 Stats과 캐릭터와 관련 있는 상태를 구현할 스크립트다. 이곳에서 HpBar, MpBarUI를 제어한다. 현재는 HpBar, MpBar 기능만 구현했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; //Hp,MpBarUI에 접근하여 Value값을 조절하기 위해서
 
public class PlayerStats : LivingEntity
{
    public Slider playerHpBar;
    public Slider playerMpBar;
 
    /*
    public AudioClip deathClip; //사망소리
    public AudioClip hitClip; //피격 소리
    public AudioClip manaGetClip; //마나 습득 소리
 
    private AudioSource playerAudioPlayer; //플레이어 소리 재생기
    */
 
    protected override void OnEnable()
    {
 
        //LivingEntity의 OnEnable() 실행(상태초기화)
        base.OnEnable();
      
        //체력 슬라이더 활성화
        playerHpBar.gameObject.SetActive(true);
        //체력 슬라이더의 최댓값을 기본 체력값으로 변경
        playerHpBar.maxValue = startingHealth;
        //체력 슬라이더의 값을 현재 체력값으로 변경
        playerHpBar.value = health;
 
        //마나 슬라이더 활성화
        playerMpBar.gameObject.SetActive(true);
        //마나 슬라이더의 최댓값을 기본 체력값으로 변경
        playerMpBar.maxValue = startingMana;
        //체력 슬라이더의 값을 현재 체력값으로 변경
        playerMpBar.value = mana;
 
    }
 
    public void RestoreHealth()
    {
        
    }
 
    // Update is called once per frame
    void Update()
    {
        
    }
}
 
cs

 

Player 오브젝트에 PlayerStats 스크립트를 추가하고, 빈 컴포넌트 필드에 각각 HpBarUI의 Slider, MpBarUI의 Slider 오브젝트를 연결하였다. 

 

 

 

Buttons 배치하기

구현할 버튼

  • 일반공격 버튼
  • 텔레포트 버튼
  • 스킬1 버튼
  • 스킬2 버튼

 

#현재 버튼은 배치만 했다. 스킬 구현할 때 스크립트 작성 및 구현할 예정이다.

 

HUD Canvas의 자식 오브젝트로 ButtonsUI 패널을 생성하고 Raw Image 오브젝트를 4개 추가했다. 각각 이름을 AttackBtn, TeleportationBtn, Skill1Btn, Skill2Btn으로 이름을 변경했다. 위의 HpBarUI를 만드는 것처럼 원하는 리소스 이미지를 각각 변경했다. 이번에는 버튼 기능이 있어야 해서 Button 컴포넌트를 추가해줬다.

 

 

버튼의 위치는 자주 사용할 일반 공격 버튼은 오른쪽 맨하단에 배치했고, 나머지 버튼은 일반 공격 버튼을 기준으로 원형으로 배치하여 오른손 엄지 손가락의 움직임을 고려했다.

 

 

텔레포트 버튼의 경우 횟수 제한을 줄 예정이라서, 텔레포트 사용 여부를 직관적으로 알 수 있도록 점 2개를 추가해줬다. 아직 기능은 없지만 텔레포트는 최대 2회 사용가능하며, 1회 사용했을 경우 위의 작은 버튼은 검은색으로 변경, 사용가능할 경우 버튼을 흰색으로 변경할 것이다.

 

 

 

ScoreUI 배치하기

ScoreUI는 게임 플레이 시 현재 Score를 보여줄 UI이다. 위의 HpBarUI를 구현할 때 Text를 추가하는 것처럼 HUD Canvas에 UI - Text 오브젝트를 추가하면된다. Score는 게임 규칙과 관련이  있어서 현재는 배치만하고 게임 매니저를 구현할 때 스크립트를 작성할 것 이다. 

 

다음 구현 사항

필요한 HUD를 모두 배치했으니 적과 적AI, UI를 구현하여 전투 테스트를 준비하자.

 

  • 적 2~3개 타입 리소스와 애니메이션 구현
  • 적 AI 구현
  • 적 HpBarUI 구현

 

반응형

댓글