일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 노하우
- 픽셀아트
- 스마일게이트
- 드로잉
- 도트공부
- 드로잉 연습
- 연습
- 애니메이션
- TOOL
- Pixelart
- 모작
- 개발
- 반환원정대
- 에이세프라이트
- 멋쟁이사자처럼
- 포토샵
- COSMO
- menu
- Aseprite
- 도트
- 자원순환보증금관리센터
- pixel art
- 픽셀 아트
- 장학팀
- 인디게임 개발
- photoshop
- layer
- 채색
- 서포터즈
- 기초
- Today
- Total
소소한 나의 하루들
2d 종스크롤 슈팅(5) - UI 간단하게 완성하기 본문
출처: https://youtube.com/playlist?list=PLO-mt5Iu5TeYI4dbYwWP8JqZMC9iuUIW2&feature=shared
이번에는 UI에 대해서 만들어보도록 한다.
#1. 목숨과 점수
슈팅게임에서 필요한 UI는 무엇일까? 점수(Score)다. 그래서 Score도 만들어볼 것이고 목숨(Life)도 좀 더 구체화할 수 있게 해본다.
플레이어 로직에 목숨과 점수 변수를 추가한다. 그리고 점수는 Text UI, 목숨은 Image UI로 표현한다. Anchored 설정으로 위치도 정렬시킨다. 목숨(Life)는 Image UI에 주어진 sprite를 적용시킨다. (설정은 픽셀아트 그래픽 설정) 이미지 크기도 주어진 화면에 맞게 설정한다. 그리고 목숨 개수대로 Image UI를 추가한다.
그리고 UI를 약간의 여백을 줘서 정렬해준다. Canvas UI의 Canvas Scaler는 UI Scale Mode를 Scale With Screen Size(기준 해상도의 UI 크기 유지)로 설정한다. 기준 해상도 크기를 설정해주고, 다시 UI 크기를 적절하게 재설정해준다.
추가로 게임 오버도 Text UI로, 재시작은 Button UI로 표현해준다. 버튼 sprite의 Pixels per Unit은 32로 설정한다.
*Button UI는 sprite Editor에서 Border 설정을 해줘야한다.
그리고 게임오버 문구와 재시작 버튼은 묶어서 Canvas 안의 빈 오브젝트 그룹으로 묶어준다. (Over Set)
이렇게 UI 세팅은 끝낸다.
#2. UI 로직
이제 적 비행기를 잡았을 때 점수를 얻는다. 점수는 Enemy에서 확인해야한다. Destroy()가 되면, 점수를 Player에게 넘겨줘야한다. 그리고 전체적인 UI 관리는 GameManager에서 한다.
//GameManager
public TextMeshProUGUI scoreText;
public Image[] lifeImage;
public GameObject gameOver;
//Enemy
public int enemyScore;
private void OnHit(int damage)
{
health -= damage;
spriteRenderer.sprite = sprites[1];
Invoke("ReturnSprite", 0.1f);
if (health <= 0)
{
Player playerLogic = player.GetComponent<Player>();
playerLogic.Score += enemyScore;
Destroy(gameObject);
}
}
UI는 먼저 using UnityEngine.UI를 선언해야하고, TextMeshPro UI는 using TMPro를 선언해야한다.
점수를 텍스트 UI에 입력해야한다. 참고
Player playerLogic = player.GetComponent<Player>();
scoreText.text = string.Format("{0:n0}", playerLogic.Score);
그런데 999, 999, 999 처럼 끊어주고 싶다. 그렇게 하기 위해서는 string.format()으로 지정된 양식으로 나타낼 수 있다.
string.format() : 지정된 양식으로 문자열을 반환해주는 함수
*매개변수는 2개인데, 첫번째 매개변수는 어떠한 format을 할 것인지 지정하는 것이다.
{0:n0} : 세자리마다 쉼표로 나눠주는 숫자 양식 (↓아래 참고)
c# 형식 지정자
형식 지정자 | 설명 | 예제 | 출력 |
C 또는 c | 통화 | Console.Write("{0:C}", 2.5); Console.Write("{0:C}", -2.5); |
$2.50 ($2.50) |
D 또는 d | Decimal | Console.Write("{0:D5}", 25); | 00025 |
E 또는 e | 지수 | Console.Write("{0:E}", 250000); | 2.500000E+005 |
F 또는 f | 고정 소수점 | Console.Write("{0:F2}", 25); Console.Write("{0:F0}", 25); |
25.00 25 |
G 또는 g | 일반 | Console.Write("{0:G}", 2.5); | 2.5 |
N 또는 n | 수(천 단위 표시) | Console.Write("{0:N}", 2500000); | 2,500,000.00 |
X 또는 x | 16진수 | Console.Write("{0:X}", 250); Console.Write("{0:X}", 0xffff); |
FA FFFF |
대문자는 기본적으로 소수점 이하 자리수를 포함하고, 소문자는 기본적으로 소수점 이하 자리수를 포함하지 않지만, {0:n2}처럼 하면 이 다음 0번째 인수(숫자형)을 소수점 2번째 자리까지 표시해서 수로 나타내라는 의미가 된다.
콜론 :은 형식 지정자의 '시작'을 의미한다. '이 다음부터 작성된 형식대로 표시해주세요'
string.Format("{0:n2}", 1234);
string.Format("{1:C}", 5674);
또는
string.Format("{0:n3}, {1:C}, {2:D5}", 1234, 5678, 1257);
맨 앞의 수 0은 0부터 시작해서 n번째까지 String.Format()에 들어간 인수(수)의 인덱스 순서를 말하는 것이다. 형식 지정자 타입과 관계없이 이미 전에 한번 Format()에 인수가 들어갔다면, 이번에 들어갈 인수(수)의 인덱스는 1이 되는 것이다.
Format() 함수는 string을 반환한다.
//GameManager
public void UpdateLifeUI()
{
//Life UI Init Disable
for (int index = 0; index < 3; index++)
{
lifeImage[index].color = new Color(1, 1, 1, 0);
}
//Life UI Active
for (int index = 0; index < life; index++ )
{
lifeImage[index].color = new Color(1, 1, 1, 1);
}
}
public void GameOver()
{
gameOver.SetActive(true);
}
//Player
public void OnTriggerEnter2D(Collider2D collision)
{
...
else if(collision.gameObject.tag == "Enemy" || collision.gameObject.tag == "EnemyBullet")
{
life--;
gameManager.UpdateLifeUI();
if((life == 0)) {
gameManager.UpdateLifeUI();
gameManager.GameOver();
}
else
{
gameManager.RespawnPlayer();
}
gameObject.SetActive(false);
Destroy(collision.gameObject);
}
}
처음에 Inager를 일단 투명 상태로 만들고, 게임을 시작하고 적에게 피격당하면 그때 목숨대로 반투명 상태로 설정한다.
만약 life가 0라면 게임을 끝내야한다. life가 0일 때 RespawnPlayer()를 호출하지 않기 때문에 다시 플레이어는 살아나지 않는다.
실행하기 전에 GameManager에 UI 변수를 잊지않고 초기화해주고, 적 비행기 프리펩의 점수들도 입력해준다. Player 오브젝트의 생명(Life)도 3으로 잊지말고 입력해야한다.
이제 Retry 버튼만 구현하면 된다.
//GameManager
using UnityEngint.SceneManagement;
public void GameRetry()
{
SceneManager.LoadScene(0);
}
게임을 다시 재시작해야하기 때문에 Scene을 불러와야한다. 참고
맨 위에 using UnityEngint.SceneManagement;를 선언한다. 그리고 재시작을 위해 현재 Scene을 새로 불러온다. LoadScene()에 0을 입력해도 되고, Scene의 이름을 적어도 된다.
*LoadScene()을 사용하기 위해 File 메뉴>Build Setting을 확인한다. 꼭 이동하려는 Scene을 Build해주어야한다.
LoadScene()에 적어준 숫자가 Build Settings의 Scenes In Build에 나와있는 숫자이다.
이렇게 코드도 작성하고, Build도 해주었다면, Retry 버튼에 OnClick() 이벤트 함수로 연결한다.
*버튼에 연결시킬 함수는 반드시 public으로 선언해야 버튼에서 찾을 수 있다.
#3. 예외 처리
그런데 실행 중 추가적인 문제가 발생한다. 가장 큰 적 비행기가 총알을 2대 발사하여, 플레이어가 한번에 총알을 2대 맞으면 체력이 2가 한번에 깎여버린다.
혹은 적 비행기와 총알을 동시에 맞아서 체력이 2가 한번에 깎이는 경우도 생길 것이다.
이렇게 예상하지 못한 버그는 항상 발생할 수 있다.
//Player
else if(collision.gameObject.tag == "Enemy" || collision.gameObject.tag == "EnemyBullet")
{
if (isHit)
return;
isHit = true;
life--;
gameManager.UpdateLifeUI(life);
if(life == 0) {
gameManager.GameOver();
}
else
{
gameManager.RespawnPlayer();
}
gameObject.SetActive(false);
Destroy(collision.gameObject);
isHit = true;
}
//GameManager
public void RespawnPlayer()
{
Invoke("RespawnPlayerExe", 2.0f);
}
void RespawnPlayerExe()
{
player.transform.position = Vector3.down * 3.5f;
player.SetActive(true);
playerLogic.isHit = false;
}
따라서 처음 1발 맞을 때 같은 기체에서 발사된 총알도 맞지 않도록 로직을 닫아준다. 플래그 변수를 활용한다.
: 피격 중복을 방지하기 위한 bool형 변수 추가
bool 변수를 다시 초기화하는 구간도 꼭 구현한다. 그래야 한꺼번에 총알을 2발 동시에 맞는 일이 없을 것이다.
'개발 > 유니티' 카테고리의 다른 글
2d 종스크롤 슈팅(7) - 원근감있는 무한 배경 만들기 (0) | 2024.02.21 |
---|---|
2d 종스크롤 슈팅(6) - 아이템과 필살기 구현하기 (0) | 2024.02.20 |
2d 종스크롤 슈팅(4) - 적 전투와 피격 이벤트 만들기 (0) | 2024.02.17 |
2d 종스크롤 슈팅(3) - 적 비행기 만들기 (0) | 2024.02.16 |
2d 종스크롤 슈팅(2) - 총알발사 구현하기 (0) | 2024.02.13 |