-
유니티에서 타자 효과 적용하기 (How to apply a typing effect in unity)유니티 개발/팁 2021. 9. 27. 14:57
*투고에 앞서 본 블로그의 게시물들은 필자가 개인적으로 학습한 내용을 정리하고 공유하기 위한 내용이므로 불충분하거나 비효율적인 부분이 있을 수 있음을 알립니다.
1. 개요
게임에서 NPC나 캐릭터 간에 대화는 필수적입니다. 모든 것을 음성만으로 완벽히 전달할 수 있다면 모르겠지만, 스토리의 전개를 위해서도 텍스트는 필연적으로 들어가게 됩니다. 이런 텍스트를 단순하게 화면에 띄우기만 한다면 플레이하는 사람의 흥미를 끌어내기 어려울 것입니다.
어릴 때 다들 해본 레트로 게임에서 타자를 치듯 텍스트를 보여주는 이펙트를 기억하시나요?
그림1. 포켓몬스터에서 보여주는 타자효과 이러한 효과는 단순하지만, 저처럼 비전공자나 처음 개발을 하는 사람에게는 적용이 어려울 수도 있습니다. 이번 게시물에서는 위의 예시처럼 타자를 치는듯한 효과를 만들어 보고자 합니다.
2. 시작하기
*TMP_pro를 적용한 기준이므로 적용하지 않으신 분은 코드를 참고하시기 전에 TMP_pro를 먼저 설치하는 것을 권합니다.
우선 게임 매니저들을 모아놓은 GameManagers 오브젝트에 자식 오브젝트로 TypingManager를 생성하고 Inspecter에서 AddComponent -> New Script로 새로운 C# 파일을 생성, 이름을 TypingManager로 작성합니다.
그림2. 타이핑 매니저 스크립트 생성된 모습 이후 비주얼 스튜디오로 Typing Manager를 엽니다.
3. 코드 작성하기
a) 네임스페이스
그림3. 네임스페이스와 클래스 변수 목록 TMP_pro와 UI를 사용하기 때문에 네임 스페이스에서 이를 표기해줍니다. 밑의 변수에 대해선 메서드와 함께 다시 설명하겠습니다.
b) 어웨이크 메서드
그림4. 어웨이크 메서드 instance를 검사해서 null이면 해당 클래스를 유일 인스턴스로 하는 싱글톤 패턴을 구현합니다.
timer는 타자 효과가 반복되는 시간을 맞추는 변수입니다.
characterTime은 실제로 적용되는 타자 시간입니다. 이를 별도 변수로 한 이유는 타자 효과가 입력을 받을 때 가변적으로 효과가 빨라지는 것을 구현하기 위해 별도로 선언했습니다.
c) 타이핑 메서드
그림5. 타이핑 메서드 타이핑 메서드는 다른 클래스가 문자열 배열과 이를 표기할 텍스트 메쉬 프로를 넘기면 받아와서 타자 효과와 넘김 효과를 제작해주는 메서드입니다.
isDialogEnd는 받아온 모든 문장이 넘김 처리까지 다 끝났을 때 True로 만들어주는 bool값입니다.
받은 dialog를 dialogs로 백업하고 tmpSave로 텍스트 메쉬 프로 또한 백업해둡니다.
dialogNumber는 대화의 페이지 넘버입니다. 한 페이지가 모두 재생되면 뒤에서 설명할 코루틴에서 이 넘버를 +1 해주면서 다음 대화로 진행하게 도와줍니다.
char[] chars = dialogs[dialogNumber].ToCharArray(); 를 이용해 받아온 다이얼 로그 대화를 글자 단위로 분해해서 코루틴으로 넘겨줍니다.
d) 타이퍼 코루틴
그림6. 타이퍼 코루틴 타이핑 메서드로부터 분해된 글자와 텍스트 메쉬를 받아오고 charLength보다 currentChar가 낮은 동안 (즉 타이핑이 완료가 안 된 동안) 타이머를 초기화시키며 타이핑을 합니다.
타이핑이 끝나면 isTypingEnd 를 true로 하여 타이핑이 끝났음을 알려주고, dialogNumber를 1 더 해주고 코루틴을 종료시킵니다.
이 과정까지만 끝내도 타이핑 효과는 적용될 것입니다. 하지만 입력을 받아도 다음 대화로 넘어가지도 않고, 대화 도중에 지겨워서 대화를 빠르게 넘기려고 해도 아무런 반응을 하지 않을 것입니다.
텍스트 스킵을 위해 인풋 다운과 인풋 업 메서드를 제작해 보겠습니다.
e) 인풋 다운 메서드
그림7. 인풋다운 메서드 인풋이 들어왔을 경우 타이핑이 끝난 경우엔 다음 대화로, 타이핑이 진행 중일 땐 대화를 빠르게 넘어가게끔 해야 합니다.
이를 위해 코 루틴에서 호출해주는 isTypingEnd를 검사해서 타이핑이 끝난 경우는 다음 대화로 넘어가게 해 주고, 아니면 속도를 변경시켜 줍니다.
f) 인풋 업 메서드
그림8. 인풋업 메서드 인풋이 끝났을 경우엔 타자 속도를 원래 속도로 변경시켜줘야 합니다. 위와 같이 시간을 초기화시켜 줍니다.
4. 인스펙터 설정
a) 타이핑 캔버스 및 텍스트 샌더 제작
그림9. 타이핑 매니저 인스펙터 다시 유니티로 넘어와서 인스펙터에서 타자 속도를 설정합니다. 평소엔 0.08, 빠른 속도는 0.03이 적절하다고 생각해 제가 해놓은 세팅이고 게임 스타일에 맞게 속도를 커스텀해주시면 됩니다.
그림10. 텍스트 샌더 그림11. 캔버스와 텍스트 메쉬 텍스트를 보내줄 텍스트 샌더 오브젝트를 만들고 그 텍스트를 올릴 캔버스와 텍스트를 만들었습니다. 텍스트는 기본으로 들어가 있는 NewText를 지웁니다.
b) Text 오브젝트에 Envent Trigger 추가
그림12. 이벤트 트리거 컴포넌트 추가 텍스트 박스를 클릭하면 GetInputDown 메서드를 호출하도록 이벤트 트리거를 설정합니다. 포인터가 Up 되었을 때는 GetInputUp을 호출하도록 설정해줍니다.
c) 텍스트 샌더 코드 작성 및 설정
그림13. 텍스트 샌더 클래스 텍스트와 텍스트 메쉬를 보내는 샌더 코드를 작성합니다.
그림14. 인스펙터 설정 인스펙터에서 대화문의 사이즈와 내용, TextObj를 설정해 줍니다.
5. 대화 출력하기
그림15. 완성 위의 과정을 거친 뒤 게임을 플레이하면 사진과 같이 텍스트 박스 클릭에 맞춰 텍스트가 재생이 됩니다.
그림 16. 커스터마이징 텍스트 메쉬의 폰트나 배경 설정 또는 마침표 추가 등으로 좀 더 게임에 어울리게 변경할 수 있습니다.
6. 소스코드
a) 타이핑 매니저 소스코드
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.UI;using TMPro;public class TypingManager : MonoBehaviour{public static TypingManager instance;[Header("Times for each character")]public float timeForCharacter; //0.08이 기본.[Header("Times for each character when speed up")]public float timeForCharacter_Fast; //0.03이 빠른 텍스트.float characterTime; // 실제 적용되는 문자열 속도.//임시 저장되는 대화 오브젝트와 대화내용.string[] dialogsSave;TextMeshProUGUI tmpSave;public static bool isDialogEnd;bool isTypingEnd = false; //타이핑이 끝났는가?int dialogNumber = 0; //대화 문단 숫자.float timer; //내부적으로 돌아가는 시간 타이머private void Awake(){if (instance == null){instance = this;}timer = timeForCharacter;characterTime = timeForCharacter;}public void Typing(string[] dialogs, TextMeshProUGUI textObj){isDialogEnd = false;dialogsSave = dialogs;tmpSave = textObj;if (dialogNumber < dialogs.Length){char[] chars = dialogs[dialogNumber].ToCharArray(); //받아온 다이얼 로그를 char로 변환.StartCoroutine(Typer(chars, textObj)); //레퍼런스로 넘겨보는거 테스트 해보자.}else{//문장이 끝났으므로 다른 문장을 받을 준비... 다이얼로그 초기화, 다이얼로그 세이브와 티엠피 세이브 초기화tmpSave.text = "";isDialogEnd = true; // 호출자는 다이알로그 엔드를 보고 다음 동작을 진행해주면 됨.dialogsSave = null;tmpSave = null;dialogNumber = 0;}}public void GetInputDown(){//인풋이 들어왔을때 -> 텍스트가 진행중이면 빠르게 진행되고 텍스트가 마감되어있으면 다음 텍스트로 넘어감.//그리고 인풋이 캔슬되면 다시 문자열 속도를 정상화 시켜야함.if (dialogsSave != null){if (isTypingEnd){tmpSave.text = ""; //비어있는 문장 넘겨서 초기화.Typing(dialogsSave, tmpSave);}else{characterTime = timeForCharacter_Fast; //빠른 문장 넘김.}}}public void GetInputUp(){//인풋이 끝났을때.if (dialogsSave != null){characterTime = timeForCharacter;}}IEnumerator Typer(char[] chars, TextMeshProUGUI textObj){int currentChar = 0;int charLength = chars.Length;isTypingEnd = false;while (currentChar < charLength){if (timer >= 0){yield return null;timer -= Time.deltaTime;}else{textObj.text += chars[currentChar].ToString();currentChar++;timer = characterTime; //타이머 초기화}}if (currentChar >= charLength){isTypingEnd = true;dialogNumber++;yield break;}}}cs b) 텍스트 샌더
123456789101112131415161718using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.UI;using TMPro;public class TextSender : MonoBehaviour{[Header("Dialogs")]public string[] dialogStrings;public TextMeshProUGUI textObj;private void Start(){TypingManager.instance.Typing(dialogStrings, textObj);}}cs