[Unity]미니 프로젝트 - 카드 뒤집기 게임 만들기
유니티 캠프 1주차 과제로 카드 뒤집기 게임을 만들게 되었다.
게임 자체는 간단한 캐주얼 게임이지만, 아무래도 게임 개발을 팀 단위로 진행하는건 처음이다 보니, 우여곡절을 많이 겪었던 것 같다. 개발 과정에서 겪은 문제들과 그 해결법을 중심으로 글을 작성해보려 한다.
🎯게임 소개 및 기능
- 장르 : 캐주얼 카드 매칭 퍼즐
- 게임 목표 : 제한 시간 내 짝이 맞는 카드를 모두 찾아 제거하는 게임
⚙️주요 기능 구현 & 문제 해결
1. 카드 셔플 방식 문제
- 문제 : 처음에는 다음과 같은 방식으로 카드를 섞었다.
arr = arr.OrderBy(x => Random.Range(0f, 5f)).ToArray();
하지만 카드가 제대로 섞이지 않는 느낌이 들었고, 실제로 이 방식은 무작위성이 보장되지 않는다는 결과가 나왔다.
-
원인 : 일단 원래 사용한 방식은 배열의 각 요소에
Random.Range(0f,5f)라는 랜덤한 키 값을 부여한 다음, 그 키를 기준으로 정렬하는 방식이다. 이는 sort 방식과 유사하기 때문에 난수 분포의 영향으로 일부 순열이 더 자주 나타나고, 일부 순열은 아예 나타나지 않을 수도 있다. 모든 순열이 동일한 확률로 나오지 않는다는 소리다. -
해결 : Fisher-Yates 알고리즘 사용
for (int i = array.Length - 1; i > 0; i--)
{
int rand = Random.Range(0, i + 1);
(array[i], array[rand]) = (array[rand], array[i]);
}
2. 카드 뒤집기 애니메이션 & 회전 초기화 문제
-
문제 : 카드를 애니메이션으로 Y축 기준 180도 회전시키는 방식으로 카드 뒤집기 연출을 구현하였다. 문제는 재활용을 위해 카드를 다시 활성화해도 회전된 상태가 계속 유지되었다는 점이다.
OnEnable()함수에서 회전값을 0으로 초기화시켜줘도 해결되지 않았다. -
원인 : 애니메이터가 문제였다. 애니메이터에서 조정한 로컬 회전 값이
transform과 충돌하여 예상대로 동작하지 않았던 것이다. -
해결 : 애니메이션이 끝나고, 카드가 비활성화 되었을 때(애니메이터도 꺼져 있을 때)
rotation값을 수동으로 초기화
//card.cs
public void DisableCardInvoke()
{
gameObject.SetActive(false);
transform.rotation = Quaternion.Euler(0f, 0f, 0f); /카드 회전 초기화
front.SetActive(false);
back.SetActive(true);
}
3. 카드 빠르게 연타 시 발생하는 문제
-
문제 : 같은 카드를 빠르게 연타하거나, 두 번째 카드를 뒤집은 후 다른 카드를 연속으로 뒤집을 시 게임이 비정상적으로 동작하는 문제가 생겼다.
-
원인 : 근본적으로는 애니메이션 때문에 발생하는 문제였다. 정확히는 카드가 절반정도 뒤집어지는 시점에 카드의 앞,뒤 이미지를 스위칭하기 위해
코루틴을 사용한게 문제.코루틴으로 딜레이를 준 사이에 다른 카드를 클릭하면 아직 카드 매칭 메서드가 제대로 실행되기 전에 다른 카드가 뒤집어지면서 문제가 생긴 것이다. -
해결 : 카드 클릭 이벤트 발생 시, 먼저 게임매니저를 통해 카드를 뒤집을 수 있는 상태인지 확인한다.
//CardFlip.cs
public void CardClick()
{
if (!GameManager.Instance.CanSelectCard())
{
return;
}
StartCoroutine(FlipCard());
}
//GameManager.cs
public bool CanSelectCard() //카드를 뒤집을 수 있는상태인지 알려줌
{
return !cardOpening && secondCard == null;
}
그리고 애니메이션을 위해 딜레이를 주는 라인은 적절한 위치에 두는 것이 중요하다.
//cardFlip.cs
IEnumerator FlipCard()
{
if (GameManager.Instance.firstCard == card) //같은 카드 두번 클릭 방지
{
yield break;
}
if (GameManager.Instance.firstCard == null) //첫번째 카드 할당
{
GameManager.Instance.firstCard = card;
}
else
{
GameManager.Instance.secondCard = card; //두번째 카드 할당
}
audioSource.PlayOneShot(clip);
anim.SetBool("isOpen", true);
//카드가 반쯤 뒤집힐 때까지 대기
yield return new WaitForSeconds(0.5f);
front.SetActive(true);
back.SetActive(false);
if (GameManager.Instance.secondCard == card) //두번째 카드인 경우만 매칭 시도
{
yield return new WaitForSeconds(0.5f);
GameManager.Instance.Matched();
}
}
첫 번째 if문을 통해 같은 카드를 연속으로 연타할 때 발생하는 문제 또한 해결할 수 있었다.
4. GitHub 협업 문제
-
문제 : 사실 가장 어려움을 겪었던 부분으로, 팀원 5명이 전부 깃허브를 통해 협업 프로젝트를 관리해본 경험이 없어서 적응하는 데에 고생을 많이 했다. 특히 브랜치를 병합하는 과정에서, 유니티 파일 관련
conflict가 많이 발생하고 이를 해결할 방법을 찾기 너무 힘들었다. -
원인 : 처음에는 잘 모르다보니 그냥 각자의 브랜치에서 작업하던 것을
main브랜치로 막 병합하는 식으로 진행했다. 당연히 충돌이 많이 발생했고, 코드 충돌은 직접 보고 선택하여merge할 수 있었지만 유니티 파일 관련 충돌은 봐도 뭔지 몰라서 한쪽을 아예 포기하는 방식으로 병합할 수 밖에 없었다. -
해결 : 일단
Develop브랜치를 새로 생성해여 중간 작업물을 관리하였다. 그리고 유니티 관련 충돌을 최대한 피하기 위해 메인 씬에서의 작업은 한명이 몰아서 하고, 나머지는 코드나 프리팹 위주로 작업하기 시작했다. 개발자가 왜 소통을 많이 해야 하는지 조금은 알 수 있었던 경험이었다.
✅결과 및 마무리
🎮실행 결과
🏁마무리하며
처음 경험해보는 팀 게임 프로젝트이다 보니 유니티나 C#에 관련된 지식보다는 GitHub 를 활용한 협업, 프로젝트 관리와 관련된 요소를 직접 경험하며 성장할 수 있었다.
그리고 운 좋게도 열정적이고 재미있는 팀원들을 만나서 재미있게 프로젝트를 진행할 수 있었다. 이제 유니티 캠프 1주일차를 보냈는데, 새로운 사람들을 만나고(물론 온라인이지만) 같이 얘기하거나 관심사를 나누고, 공부를 하면서 굉장히 즐거운 시간을 보내고 있다. 앞으로도 열심히 하여 남은 4개월을 소중하고 값진 경험으로 가득 채우고 싶다.
댓글남기기