이전에 레벨 씬과 스테이지1 씬을 만들어서 테스트 플레이를 통해 레벨 씬에 해당하는 버튼을 클릭하면 해당 스테이지로 씬 전환이 이루어지고 해당 스테이지에서 미션 성공 후 Next 버튼을 누르면 currentLevel + 1을 통해서 다음 스테이지로 이동하는 것을 했습니다. 그런데 씬 전환이 뭔가 어색하다는 것을 알 수 있습니다. 다른 게임을 플레이하고 나서 현재까지 개발된 상태의 예시 게임의 씬 전환을 보면 즉각적으로 씬 전환이 되는 것을 체감할 수 있습니다. 딱딱한 씬 전환을 부드럽게 만드는 방법은 여러가지가 있지만 가장 많이 사용하는 것이 Fade In & Out 입니다. Fade In & Out은 화면을 천천히 들어나게 하고 반대로 천천히 가리는 기능입니다. 예를 들어 연극이나 뮤지컬에서도 다른 장면 즉 씬 전환이 필요한데 세트를 관객이 훤히 보는 앞에서 바꾸는 것은 매끄럽지도 않고 프로답지 않은 처사입니다. 따라서 이 때 커튼을 사용하거나 주변을 어둡게 만들고 세트를 변경하는데 게임에서의 씬 전환에서도 부드러운 전환에 필요한 역할을 하는 것입니다. Fade In & Out 방식 말고도 게임의 스타일에 따라서 마치 연극의 커튼이 내려오거나 또는 양쪽에서 닫히고 열리는 방식을 취하기도 하고 모자이크나 모니터 노이즈를 통해서 전환하는 등 다양하게 씬 전환을 구현하고 있습니다. 예시 게임에서는 가장 많이 그리고 쉽고 간단한 Fade In & Out으로 해보겠습니다.
1. 부드러운 Scene 전환
Fade In & Out은 UI창에서 커튼 역할을 하는 한 개의 이미지를 alpha 값을 통해서 나타나게 만들었다가 사라지게 만드는 방법입니다. 이를 이용하기 위해서는 Canvas Group이라는 컴포넌트를 추가해 주어야 합니다. 이 Canvas Group은 UI를 그룹 전체로 한 번에 제어할 수 있도록 하는 것입니다.
Alpha: 그룹 UI에 투명 또는 불투명 값을 줍니다. 0이면 완전한 투명이며 1이면 완전 불투명입니다.
Interactable: 그룹 UI에 플레이어의 상호작용(클릭)을 받는지에 대한 것입니다. Fade처리 이미지는 체크하지 않습니다.
Block Raycasts: 그룹 UI에 Raycast 차단 여부입니다. Raycast는 추후에 다루겠습니다. 역시 체크해제입니다.
Ignore Parent Groups: 부모 Canvas Group 여부 설정입니다. Fade에서는 해크해제 입니다.
Fade In & Out에서 제일 중요한 것은 알파값입니다. 해당 이미지의 투명도를 변경해서 이미지로 씬을 가리거나 보여주는 역할을 하게 됩니다. Hierarchy뷰에 있는 Canvas를 클릭한 상태에서 마우스 우클릭을 해서 UI - image 클릭을 통해 이미지를 한 개 만들고 이름을 Fade로 변경해 줍니다. 이 때 중요한 것은 Canvas 내에서 Fade 오브젝트의 위치입니다. Fade 오브젝트는 UI에 모든 부분을 가려주거나 들어나게 해야 하므로 Canvas 가장 하단에 위치해야만 합니다. 그래야 게임 화면에서 모든 UI보다 가장 상단에 노출되어서 모든 UI를 가리고 드러내는 역할을 할 수 있습니다. Canvas 순위 위치 조정이 확실하다면 이제 Fade 오브젝트를 클릭해서 Canvas Group을 추가해 줍니다.
이 순서가 잘 못되면 에러가 발생하거나 크래쉬가 나는 문제는 없지만 플레이어가 보기에는 분명 문제가 있는 현상이 발생합니다. Canvas를 클릭하고 UI 오브젝트를 생성하면 유니티는 자동적으로 Canvas의 자식 UI 오브젝트를 최하단에 배치시킵니다. Fade를 먼저 만들고 게임을 만들다가 추가적인 UI를 생성하고 위치 조정을 안 해 주고 플레이하면 UI가 Fade를 뚫고 나와 있는 모습을 볼 수 있습니다.
위 그림은 Fade Out 즉 Canvas Group의 알파값이 1에서 0이 되어 가는 과정일 때 처음 1인 불투명 상태에서 순서 문제로 인해 일부 UI 오브젝트의 Fade가 안 된 상태를 보여주고 있습니다. 이 문제는 간단하게 Fade를 가장 Canvas의 가장 하단으로 내려주면 해결됩니다. Fade in & Out을 처리하기 위해서 스크립트를 한 개 생성하고 이름을 FadeHandler라고 해주고, 해당 스크립트를 Fade오브젝트에 추가해 줍니다.
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class FadeHandler : MonoBehaviour { private CanvasGroup canvasgroup; public float fadeSpeed = 1.5f; public float DelayFade = 0.75f; public bool fadeOut; public bool fadeIn; // Start is called before the first frame update void Start() { canvasgroup = GetComponent<CanvasGroup>(); if(fadeOut) { canvasgroup.alpha = 1f; StartCoroutine(FadeOut()); } if (fadeIn) { canvasgroup.alpha = 1f; StartCoroutine(FadeIn()); } } IEnumerator FadeIn() { yield return new WaitForSeconds(DelayFade); while(canvasgroup.alpha < 1) { canvasgroup.alpha += Time.deltaTime * fadeSpeed; yield return null; } } IEnumerator FadeOut() { yield return new WaitForSeconds(DelayFade); while (canvasgroup.alpha > 0) { canvasgroup.alpha -= Time.deltaTime * fadeSpeed; yield return null; } } } |
Fade가 즉각적으로 시작하기 보다는 약간의 딜레이를 주어서 좀 더 부드럽게 만들 수 있습니다. 이를 위해서 코루틴을 사용함과 동시에 조건에 따른 반복 실행을 위해서 while 을 사용합니다. 먼저 CanvasGroup 컴포넌트를 사용하기 위한 변수인 canvasgroup를 만듭니다. CanvasGroup 컴포넌트는 다른 오브젝트에 있지 않고 해당 스크립트(FadeHandler)가 있는 같은 오브젝트인 Fade 오브젝트에 있기 때문에 GameObject.Find를 사용할 필요가 없습니다. GetComponent();를 통해서 컴포넌트의 사용이 가능해집니다. 딜레이를 주기 위한 변수와 전환 속도는 소수값을 갖기 때문에 float 타입 변수를 만들어 주고 이를 상황에 따라 실행해 주어야 하기 때문에 Fade In 인지 Fade Out인지구분을 위한 bool 불리언 변수를 Fade In & Out 각각 만들어 줍니다. 코루틴 내에서 딜레이가 끝나면 바로 while을 통해서 캔버스그룹의 알파값이 조건에 따라서 알파값을 0에서 1로 또는 1에서 0으로 변경됩니다. canvasgroup의 알파값만 바꾸기 때문에 return 반환되는 것은 없으므로 null이 됩니다. 스크립트를 저장하고 Fade를 클릭한 상태에서 Inspector뷰를 보면 FadeIn과 FadeOut 체크박스(boolean)가 있습니다. 게임 시작이 되는 화면이 필요하기 때문에 FadeOut이 필요하니 FadeOut은 체크(bool=true)하고, FadeIn은 체크해제(bool=false)해서 FadeOut의 코루틴만 사용되도록 합니다. 유니티에서 플레이 버튼을 누르면 페이드 아웃 코루틴이 실행되면서 해당 알파값을 계속 깍아 내려서 0인 투명상태로 만듭니다. 반대로 스테이지 버튼을 누르면 Fade In을 해서 레벨 씬 UI들을 부드럽게 가려주고 Stage 씬으로 넘어가도록 합니다.
스테이지 버튼을 눌러서 스테이지 씬으로 이동하기 위해서는 IEnumerator FadeIn()를 호출해야 하는데 코루틴은 바로 호출할 수 없기 때문에 별도의 함수를 만들어 줍니다.
public void StartFadeIn () { StartCoroutine(FadeIn()); } |
위 명령줄을 FadeHandler에 추가한 다음 레벨 씬 각 스테이지 버튼의 on Click에 Fade 오브젝트를 드래그 드롭한 후 StartFadeIn ()를 호출하도록 설정합니다. Fade In & Out을 사용할 때 한 가지 유의할 점이 있습니다. 유니티에서 씬을 전환할 때는 SceneManager를 사용하는데 씬의 전환과 Fade In의 시간이 같도록 맞추어 주어야 한다는 것입니다. 예를 들어서 SceneManager.LoadScene(2)를 통해서 스테이지 2로 이동한다고 가정하면 버튼을 누르면 Fade In이 실행되기도 전에 바로 씬이 로드가 되어 버립니다. 왜냐하면 SceneManager.LoadScene(2)은 딜레이 없이 스크립트에 쓰여져 있기 때문입니다. 이를 해결하기 위해서는 SceneManager.LoadScene(2)가 바로 실행되지 않도록 코루틴 사용을 통해서 딜레이를 주어야 합니다.
IEnumerator LoadingScene () { yield return new WaitForSeconds (SceneLoadingTime); SceneManager.LoadScene(2); } |
여기서 SceneManager를 통한 씬의 전환이 Fade In 되기 전에 너무 빨라도 안 되고, 너무 느려도 안 됩니다. 너무 빠르면 좀 전에 설명한 것처럼 Fade In 실행전에 화면이 넘어가고, 너무 느리면 Fade In이 끝났는데도 지루하게 Fade된 화면만 보다가 스테이지로 넘어가는 문제가 생깁니다. 가장 적절한 것은 SceneLoadingTime이 FadeHandler에 있는 DelayFade보다 값이 좀 큰 것이 좋습니다. 한 마디로 약간 여유를 두고 씬로드를 하는 것이 좋다는 뜻입니다. 이 부분은 FadeSpeed와도 관련이 있기 때문에 수치를 바꿔가면서 가장 적절한 씬 전환 값을 찾는 반복적 테스트가 필요합니다.
2. UI Tween
스테이지로 부드럽게 넘어오면서 게임을 플레이하지만 스테이지에서 발생하는 미션 성공이나 실패의 경우 발생하는 UI에서 어색함을 볼 수 있습니다. 성공이나 실패를 하면 해당 UI가 바로 발생하는 부분인데 이 부분을 간단한 애니메이션으로 처리해서 UI 발생을 재미있게 표현할 수 있습니다.
유니티에서 tweening은 여러가지 에셋을 통해서 구현할 수 있습니다. 에셋 스토어에 가서 tween으로 검색하면 좋은 UI tween 에셋들을 많이 볼 수 있습니다. 무료 에셋도 있으나 당연히 유료가 다양한 방법으로 구성되어 있기 때문에 잘 살펴 보고 나의 게임에 잘 사용할 수 있는 것으로 선택하면 됩니다. 그러나 대부분의 UI tween은 크게 다르지는 않기 때문에 적절한 가격의 에셋을 활용하지만 유니티 또는 게임 개발 첫 입문자라면 무료를 통해서 사용법을 익히는 것도 좋습니다. UI Tween의 구현은 비슷하더라도 사용법은 조금씩 다릅니다. Tween은 UI 오브젝트의 애니메이션을 만들어서 UI 발생이나 종료 시 다양한 애니메이션 효과를 줍니다. 애니메이션의 구현을 위해서 2D 만화 같은 것이 아닌 단순한 이동, 회전, 크기 변경, 색상 변경(투명도 포함) 요소를 활용합니다.
Rect Tranform Component
- PosX, PosY: 오브젝트 이동
- Rotation: 오브젝트 회전
- Scale: 오브젝트 크기
Material Component
- Color: 색상 변경
- alpha: 투명도 변경
이 예제 게임에서는 LeanTween을 이용해서 UI창의 크기를 조정해서 Tween을 구현하겠습니다. LeanTween을 유니티에서 사용하기 위해서는 에셋 스토어에서 해당 에셋을 import해야 합니다. 검색창에 LeanTween으로 검색해서 가져올 수 있으며 이 에셋은 무료인데 간단한 UI Tween 구현에 괜찮습니다. 해당 에셋을 유니티로 가져오면 이제 LeanTween을 사용할 수 있게 되는데 LeanTween은 스크립트에서 간단한 한 줄의 명령을 통해서 바로 실행됩니다.
UI Tween은 당연히 게임의 UI 발생을 관장하는 GameManager에서 사용합니다. 이전에 만든 GameController(게임매니저) 스크립트를 열어서 LeanTween 명령어를 추가해 줍니다.
using UnityEngine.UI; using UnityEngine.SceneManagement; public class GameController : MonoBehaviour { void winBoard() { StartCoroutine(DelayWinBoard()); gameScorePopupText.text = gameScore.ToString() } IEnumerator DelayWinBoard () { yield return new WaitForSeconds(1.7f); LeanTween.scale(SuccessBoard, new Vector3(1, 1, 1), 0.3f).setEase(LeanTweenType.easeOutQuart); SuccessBoard.SetActive(true); } public void loseBoard() { StartCoroutine(DelayFailBoard()); } IEnumerator DelayFailBoard () { yield return new WaitForSeconds(1.7f); LeanTween.scale(FailBoard, new Vector3(1, 1, 1), 0.3f).setEase(LeanTweenType.easeOutQuart); FailBoard.SetActive(true); } } |
LeanTween에서 scale로 접근하면 GameObject 타입, Vector3(x, y, z), float 값을 필요로 합니다. 오브젝트에는 각각의 성공 또는 실패 오브젝트 변수를 지정해주고, Vector3는 새로 선언 크기를 설정하기 때문에 new Vector3(1, 1, 1)로 지정합니다. 유니티 Canvas에서 생성된 모든 UI 오브젝트는 scale이 1입니다. 1보다 커지면 UI가 커지고 0이면 아예 없게 됩니다. 따라서 해당 값을 원래의 크기인 1로 만들겠다는 뜻이 new Vector3(1, 1, 1)이며 이를 통해서 안 보였던 UI 창이 나타나게 됩니다. 나타나는 시간은 0.3f float 타입으로 설정할 수 있습니다. 실행 전에 성공 또는 실패 UI창이 나타날 때 scale이 이미 Vector3(1, 1, 1)이라면 Tween.scale이 구현이 안 됩니다. 팝업창이 없다가 나타나야 하기 때문에 scale의 크기를 0에서 1로 만들어야 하는데 초기값 0을 스크립트에 넣어주면 됩니다. 스크립트로 처리하지 않을 경우 Canvas에서 SuccessBoard나 FailBoard 오브젝트를 클릭하고 Inspector뷰에서 scale값을 모두 0으로 변경합니다. 하지만 이럴 경우 UI 작업을 할 때마다 scale값을 변경해줘야 하는 번거로움 때문에 스크립트로 처리합니다.
using UnityEngine.UI; using UnityEngine.SceneManagement; public class GameController : MonoBehaviour { void winBoard() { SuccessBoard.transform.localScale = new Vector3(0, 0, 0); StartCoroutine(DelayWinBoard()); gameScorePopupText.text = gameScore.ToString() } IEnumerator DelayWinBoard () { yield return new WaitForSeconds(1.7f); LeanTween.scale(SuccessBoard, new Vector3(1, 1, 1), 0.3f).setEase(LeanTweenType.easeOutQuart); SuccessBoard.SetActive(true); } public void loseBoard() { FailBoard.transform.localScale = new Vector3(0, 0, 0); StartCoroutine(DelayFailBoard()); } IEnumerator DelayFailBoard () { yield return new WaitForSeconds(1.7f); LeanTween.scale(FailBoard, new Vector3(1, 1, 1), 0.3f).setEase(LeanTweenType.easeOutQuart); FailBoard.SetActive(true); } } |
코루틴이 시작되기 전에 scale값을 0으로 만들어 해당 UI 오브젝트를 보이지 않게 합니다. 사실 이 명령줄은 코루틴 내에서 처리해도 됩니다. 중요한 것은 스크립트 실행의 순서이기 때문에 LeanTween 전에 먼저 실행되도록 하기만 하면 됩니다.
추가로 LeanTween에서는 몇가지 옵션이 있는데 옵션의 실행은 setEase, setEaseInBack, SetEaseInBounce 등 매우 많습니다. LeatnTweenType도 다양하기 때문에 원하는 옵션을 선택하고 플레이 해서 마음에 드는 것으로 사용하면 됩니다.
LeanTween은 옵션에서 구현되는 Type도 다양하기 때문에 원하는 부분은 플레이 테스트를 통해서 정하면 됩니다. 만약 UI 메뉴창 발생을 위에서 아래로 내릴 때 통통 튀기는 것을 하고 싶다면 LeanTween.MoveY 를 사용해서 위치변경을 해주고, 옵션에서 Bounce 관련 부분을 지정해 주면 됩니다. 이런 UI Tween은 원하고자 하는 결과를 얻기 위해 반복적으로 수치와 옵션을 변경해야 합니다. 예시에서는 scale을 통해 구현했습니다. 이제 게임을 실행해서 테스트를 해보면 게임의 성공이나 실패 시 발생하는 UI가 이전보다 부드럽게 간단한 scale animation으로 보여지는 것을 확인할 수 있습니다. 다음에는 게임 내에서 이벤트 처리와 관련해서 RigidBody2D와 Collider2D에 대해서 포스팅하겠습니다.