유니티 일기
탑다운 2D RPG - 대화 애니메이션 느낌있게 만들기
mky
2025. 9. 6. 18:52
골드메탈님의 유튜브 강의를 보고 배운 것을 정리하였습니다.
1. 대화창 이펙트
- 애니메이터 하나 생성하여 대화 UI에 추가
- 대회 UI를 화면 아래로 내려서 안보이게 설정
- 변수 타입을 애니메이터로 교채했으므로 재할당 필
2. 초상화 이펙트
- 애니메이터, 애니메이션 1개씩 초상화 Image에 생성
- 과거 스프라이트를 저장해두어 비교 후, 애니메이션 실행
3. 타이핑 이펙트
- 타이핑이펙트 스크립트 생성
- 표시할 대화 문자열을 따로 변수로 저장
- 글자 재생 속도를 위한 변수 생성(CPS)
- 대화 문자열을 받는 함수 생성
- 애니메이션 재생을 위한 시작-재생-종료 3개 함수 생성
- TextMeshProUGUI 변수 생성, 초기화 후, 시작함수에서 공백 처리
- 시간차 반복 호출을 위한 Invoke 함수를 사용
- 1초 / CPS = 1글자가 나오는 딜레이
- 대화 문자열과 Text내용이 일치하면 종료
- 게임매니저에서 기존에 사용하던 Text 변수를 작성한 이펙트스크립트로 변경
- 확실한 소수값을 얻기 위해 분자 1.0f 작성
- AudioSource 변수를 생성, 초기화 후 재생 함수에서 Play()
- 공백과 마침표는 사운드 재생 제외
- 게임 실행시 사운드가 1회 재생되는 문제 -> 사운드소스의 Play On Awake 끄기
- 애니메이션 실행 판단을 위한 플래그 변수 생성 -> 플래그 변수를 이용하여 분기점 로직 작성
- 매니저에서도 플래그 변수를 이용하여 분기점 로직 작성
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class TypeEffect : MonoBehaviour
{
string targetMsg;
public float CPS; // 글자 재생 속도
public GameObject EndCursor;
TextMeshProUGUI msgText;
AudioSource audioSource;
int index;
float interval;
public bool isAnim; //애니메이션 실행 판단을 위한 플래그 변수 생성
private void Awake()
{
msgText = GetComponent<TextMeshProUGUI>();
audioSource = GetComponent<AudioSource>();
}
public void SetMsg(string msg)
{
if (isAnim) // Interupt
{
msgText.text = targetMsg; // 다 채우기
CancelInvoke(); // 인보크 함수 꺼짐
EffectEnd();
}
else
{
targetMsg = msg;
EffectStart();
}
}
void EffectStart()
{
msgText.text = "";
index = 0;
EndCursor.SetActive(false);
// Start Animation
interval = 1.0f/CPS;
Debug.Log(interval);
isAnim = true;
Invoke("Effecting", 1/CPS);
}
void Effecting() // 재귀
{
if (msgText.text == targetMsg)
{
EffectEnd();
return;
}
msgText.text += targetMsg[index];
//Sound
if (targetMsg[index] != ' ' || targetMsg[index] != '.')
{
audioSource.Play();
Debug.Log("사운드 재생");
}
Invoke("Effecting", 1 / CPS);
index++;
}
void EffectEnd()
{
isAnim = false;
EndCursor.SetActive(true);
}
}
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class GameManager : MonoBehaviour
{
//public GameObject talkPanel;
public Animator talkPanel;
public Image portraitImg;
public Animator portraitAnim;
public Sprite prevPortrait;
//public TextMeshProUGUI talkText;
public TypeEffect talk;
public GameObject scanObject;
public bool isAction; // 상호작용 중인지 아닌지 판단
public int talkIndex; // 대화 인덱스
public static GameManager instance = null;
void Awake()
{
if (instance == null)
{
instance = this;
}
}
void Start()
{
Debug.Log(QuestManager.instance.CheckQuest());
}
public void Action(GameObject scanObj)
{
isAction = true;
scanObject = scanObj;
ObjData objData = scanObject.GetComponent<ObjData>(); // 오브젝트의 데이터를 가져옴
Talk(objData.id, objData.isNpc); // 대화
//Debug.Log(objData.id + ", " + objData.isNpc);
// Visible Talk for Action
talkPanel.SetBool("isShow", isAction);
}
void Talk(int id, bool isNpc)
{
// Set Talk Data
int questTalkIndex = 0;
string talkData = "";
if (talk.isAnim)
{
talk.SetMsg("");
return;
}
//talk.SetMsg("");
else
{
questTalkIndex = QuestManager.instance.GetQuestTalkIndex(id); // 퀘스트번호를 가져옴
talkData = TalkManager.instance.GetTalk(id + questTalkIndex, talkIndex); // npc id + 퀘스트 번호 = 퀘스트 대화 데이터 ID
}
//int questTalkIndex = QuestManager.instance.GetQuestTalkIndex(id); // 퀘스트번호를 가져옴
//string talkData = TalkManager.instance.GetTalk(id + questTalkIndex, talkIndex); // npc id + 퀘스트 번호 = 퀘스트 대화 데이터 ID
//End Talk
if (talkData == null)
{
isAction = false;
talkIndex = 0;
Debug.Log(QuestManager.instance.CheckQuest(id)); // 퀘스트 진행 상황 체크
return; // 대화 끝
}
// Continue Talk
if (isNpc)
{
talk.SetMsg(talkData.Split(':')[0]); // 수정
portraitImg.sprite = TalkManager.instance.GetPortrait(id, int.Parse(talkData.Split(':')[1]));
portraitImg.color = new Color(1, 1, 1, 1); // 알파값 1로 변경
//Animation Portrait
if(prevPortrait != portraitImg.sprite)
{
portraitAnim.SetTrigger("doEffect");
prevPortrait = portraitImg.sprite; //갱신
}
}
else
{
//talkText.text = talkData;
talk.SetMsg(talkData); // 수정
//Hide Portrait
portraitImg.color = new Color(1, 1, 1, 0);
}
isAction = true;
talkIndex++;
}
}