티스토리 뷰
한 달 만에 C# 언어 기본을 마치고 드디어 유니티 수업이 시작되었다.
그리고 개강 두 달째, 개인 2D 프로젝트가 시작되었다.
다른 학원생들은 시중의 게임 리소스를 가져다 썼지만,
나는 몇 년 전에 한 번 만들어보려다 덮어둔 게임이 생각나서
직접 만들었던 리소스를 가져다 프로젝트를 진행했다.
아무래도 유명 게임들의 리소스에 비해 퀄리티가 처참하지만...
내가 직접 그린 그림에 본을 심고 애니메이션까지 만들어
움직이는 모습을 보니 큰 성취감을 느꼈다.
1. 기획부터?
게임 개발자라면 누구나 마음속에 '나의 게임'이란 걸 생각하고 있을 것이다.
취업을 위한 직업 훈련 학원에서조차 1인 개발을 하고 싶어 왔다는 사람들이 있을 정도로...;
이런 기회가 아니면 언제 내 게임을 만들어 보겠는가!
게임은 옛날에 재미있게 했던 컴투스 미니게임천국의 날려 날려라는 게임을 참고했다.
좌우로 움직이는 캐릭터를 조작해서 양쪽에서 몰려오는 적들을 물리치는 그 게임...
게임의 내용은 이러하다.
1. 당근 밭을 서리하러 온 적들을 쳐 죽이고 거름으로 만든다.
2. 서리범들의 시체로 비옥해진 땅에서 더 맛있는 당근이 자란다.
3. 더 맛있는 당근을 서리하러 더 많은 적들이 몰려온다!
허접하지만 이를 토대로 플로우 차트를 만들어 보았다.
그러면 만들어야 하는 건
당근과 여러 가지 동물 캐릭터, 그리고 그 적들을 쳐 죽일 다양한 무기!
밭을 망치는 동물로 먼저 떠올린 것은 두더지였다.
두더지를 잡는 건 역시 뿅망치 아닌가?
그래서 떠올린 게임의 제목.
뿅망치를 휘두르며 동물들을 무참히 죽이는 캐릭터도 생각해 보았다.
당근을 좋아한다면 토끼 캐릭터가 좋지 않을까?
토끼 캐릭터라면 바니걸도 나쁘지 않겠다 싶어 '라비 리비'라는 게임이 떠올랐다.
하지만 역시 너무 노골적인 거 같아 다른 컨셉을 고민해 보았다.
서리범들을 도축하느라 피범벅이 된 앞치마를 두른
정신 나간 사이코패스 토끼 소녀?!
그렇게 가벼운 생각으로 쉽게 쉽게 그려버린 도안.
여기서부터 작업이 시작되었다.
2. 리소스 작업 (유니티 2D 스프라이트 애니메이션)
유니티에서는 JPG나 PNG 같은 기본 이미지 확장자 외에도
레이어 정보를 유지한 스프라이트 이미지 파일도 사용할 수 있도록
'PSD Importer'라는 패키지를 제공한다.
이것으로 요새 흔히 볼 수 있는 라이브 2D처럼
부위마다 움직이는 그림을 만들 수 있다!
먼저 움직일 부위를 계산해 레이어를 나누어 따로따로 그림을 그려본다.
이것을 PSD Importer로 불러오면 위와 같이
레이어로 겹쳐진 이미지를 펼쳐 새로운 Atlas가 생성된 것을 확인할 수 있다.
여기서 문제는 이름이 PSD Importer라고 PSD 파일을 준비했지만
레이어가 나눠지지 않아 작업이 막혀버렸었는데...
PSB라고?????
그럴 거였으면 PSB Importer라고 이름 지었어야 하는 거 아니야??????
유니티야 나를 속인 거니?
스프라이트 애니메이션의 핵심은
이미지를 기반으로 3D 모델링처럼 Geometry를 만들고
버텍스와 엣지를 얻는다.
그렇게 뼈대를 만들고,
3D의 리깅에 해당되는 스키닝 작업을 거친다.
이러면 bone 을 조작하여 키 프레임 애니메이션을 만들 수 있게 된다.
스프라이트 애니메이션으로 요즈음 흔히 볼 수 있는 움직이는 타이틀 화면을 만들어 보았다.
3. 클래스에서 부터 설계하는 객체의 충돌 판정
적에게 데미지를 입히거나 공격을 받는 것은 OnTriggerEnter2D를 사용하여 처리하기로 하였다.
정리해보면 위와 같다.
1. 플레이어의 캐릭터는 적과 부딪히면 피해를 입는다.
2. 무기로 적을 공격할 수 있다.
3. 적들은 서로 부딪히지 않는다.
4. 당근(아이템)은 플레이어만 획득할 수 있다.
이러한 충돌 검사를 하기 위해선
OnTriggerEnter2D 함수 안에 If 문을 사용하여 예외처리를 할 수 있지만,
레이어 기반 충돌 검사 설정이란 것을 사용하면
객체의 레이어 설정을 통해 부딪힐 수 있는 객체와
부딪히지 않는 객체를 에디터 설정 만으로 정리할 수 있다.
그렇다면 객체에 추가할 컴포넌트 스크립트를 상속으로 한 번에 정리할 수 있다.
플레이어 캐릭터와 적 캐릭터는 전부 Actor를 상속 받는다.
Actor 에서 구현해야 할 기능은 체력과 이동기능이며
그 아래 Player 클래스에서 구현해야하는 것은 유저의 입력을 받아 이동하는 것이다.
Enemy 클래스에선 플레이어 캐릭터와 부딪혔을 때 데미지를 입히는 것을 구현해야한다.
일단, 적 캐릭터와 부딪힐 수 있는 객체는 플레이어 캐릭터와 무기 뿐.
public class Enemy : Actor
{
protected void OnCollisionEnter2D(Collision2D collision)
{
if (collision.transform.TryGetComponent<Actor>(out Actor _actor))
{
//적과 충돌할 수 있는 Actor 객체는 Player 뿐.
//검출된 오브젝트는 Player 이므로 데미지를 주는 함수를 불러와 처리한다.
}
}
}
적 캐릭터와 부딪힌 collision 객체에게 Actor 컴포넌트를 가져올 수 있다면
collision은 Player 캐릭터라는 의미가 된다.
이후 플레이어에게 피해를 주는 이벤트를 처리하면 될 것이다.
public class Weapon : MonoBehaviour
{
protected virtual void OnTriggerEnter2D(Collider2D collision)
{
if (collision.transform.TryGetComponent<Actor>(out Actor _actor))
{
//collision이 Player나 Enemy 든지 class Acort를 상속 받으므로
//향후 Enemy 객체가 무기를 들고 Player를 공격하는 것도 가능하다!
}
}
}
여기서 더 나아가,
Player 캐릭터가 휘두르는 Weapon의 경우 충돌한 오브젝트로부터 class Actor 를 받아온다.
만약 weapon의 레이어 설정을 적절히 적용한다면
향후 Enemy 객체가 무기를 들고 Player를 공격하는 것도 구현 가능하다!
플레이어가 들고 있는 Weapon의 레이어를 'Player Weapon' 으로 설정하고
적 캐릭터가 들고 있는 Weapon의 레이어를 'Enemy Weapon' 으로 설정하면
서로가 같은 무기를 들고도 서로에게 데미지를 주는 것이 가능할 것이다.
하지만...
작업을 마치고 나서 느낀 점은,
상속보타 컴포넌트 방식의 기능 구현이 유니티에 더 적합하다는 것이었다.
아무리 내가 상속 개념을 처음 배웠다지만,
Enemy를 상속받아 내부에 아무런 내용이 없는 Class Wolf 가 생겨난 것을 보니...
'무언가 잘못되었다' 고 생각하지 않을 수가 없었다...
더욱이, 나중에 다른 Actor 객체처럼 이동은 가능하지만 공격을 받지 않는 오브젝트가 추가된다면?
공격을 할 수 있지만 이동하지 않는 고정된 오브젝트라면...?!
...
무엇보다 게임에서 다른 동물들의 움직임 방식은 평범한 횡이동 뿐이지만,
이 토끼 캐릭터는 대신 점프를 하며 이동을 한다...
Coroutine Jumping;
IEnumerator Jumping_co()
{
WaitForSeconds _jumpTerm = new WaitForSeconds(jumpTerm);
while (true)
{
OutOfRangeCheck();
yield return _jumpTerm;
if (isViolent)
{
Transform target = GameManager.instance.Player.transform;
SetRotation(target);
}
if(IsGrounded())
anim.SetTrigger("Jump");
Jump();
}
}
이러면 Actor에서 이동을 구현한 의미가 퇴색되어 버린다...
아마 다음 프로젝트에선 부모 클래스에서 모든 기능을 구현하지 않고
필요한 기능을 컴포넌트 별로 나누어 구현한 후,
오브젝트마다 필요한 기능을 모듈처럼 추가할 것이다.
4. 처음써보는 Json...
이전 콘솔 게임 프로젝트에선 데이터 포맷으로 CSV 파일을 사용했었다.
그때에도 Json을 써야할 텐데... 라며 생각은 했지만,
그 유용성보다는 그저 남들이 사용하니까 나도 사용해야 한다는 생각 뿐이었다.
하지만 실제로 접한 Json은 전혀 어렵지 않았고
임의로 만든 class를 문서화 하여 저장한 후
쉽게 수정하고 간단하게 불러와 사용할 수 있어 매우 편리했다.
하지만
충격적이게도
이번 프로젝트에서 사용한 Json은 꼴랑!...
이것으로 나는 Json을 (한 번) '써본' 사람으로 발전한것이다...
이것은 플레이어가 장착한, 아니 마지막으로 장착했던 무기 리스트를 저장하는 것뿐...
이거면 Playerfrebs를 쓰면 되는거 아냐? 라고 생각했지만,
처음으로 굳이 Json을 써보려고 노력한 것이 이것이다...
게임을 시작하면 리스트를 저장하여 다음 게임 때 다시 불러올 것이다.
이것으로 게임을 종료한 후,
다시 게임을 실행했을 때 마지막으로 장착했던 무기를 그대로 이어서 사용할 수 있게 된다.
5. 무기 선택
이제서야 말하지만,
게임은 안드로이드 빌드 프로젝트로써
한 손으로 가볍게 즐기는 게임으로 기획되었다.
안드로이드 빌드를 위해 열심히 만든 빌드용 icon을 보라!
때문에 게임에는 두가지 기능이 필요했는데,
캐릭터의 방향을 바꾸는 것과
장착한 무기를 바꾸는 것이었다.
일전에 이를 위해 고민하며 작성한 글이 있다.
방향 전환의 경우 간단한 방법이므로
무기 교체에 대해 설명하자면,
마우스 드래그로 원형 UI 선택 (tistory.com)
이 라운드 형태의 UI 와 드래그앤 드랍 방식을 고수하여
아래와 같은 모습이 되었다.
이를 플로우 차트로 정리하자면,
터치를 시작한 시간에서 특정 시간이 지나면 UI를 띄우거나,
정해둔 시간보다 일찍 터치를 종료하면 방향을 바꾸는 그런 구조이다.
이후에는 터치 지점에서 UI를 검출하는 추가 작업이 필요했다.
이는 Raycast 나 OnPointerHandler를 사용할 수 있지만
UI가 팝업되면 시간을 느려지게 만든 부분에서 약간의 문제가 생겨,
GraphicRaycaster를 사용하여 해결하였다.
[SerializeField]
GameObject popUpUI;
[SerializeField]
GraphicRaycaster gr;
void TouchCheck()
{
PointerEventData ped = new PointerEventData(EventSystem.current)
{
#if UNITY_ANDROID
position = Input.touches[0].position
#elif UNITY_STANDALONE_WIN
position = Input.mousePosition
#endif
};
List<RaycastResult> results = new List<RaycastResult>();
gr.Raycast(ped, results);
if (results.Count > 0)
{
print(results[0].gameObject.name);
if(results[0].gameObject.TryGetComponent<ButtonActive>(out ButtonActive button))
{
SwitchingWaepon(button.MyID);
}
}
Time.timeScale = 1;
popUpUI.SetActive(false);
}
이 코드가 Cavas 오브젝트의 GraphicRaycaster 컴포넌트로부터
터치에 감지된 UI 오브젝트를 검출하는 부분이다.
이로써 검출된 UI의 (추가된 ButtonActive 컴포넌트에서 설정한) ID 번호에
해당하는 무기로 교체할 수 있게 된다.
워낙 간단한 게임이다보니 더 깊이 설명할 기술은 없지만
나름 신경쓴 것은 단발성 프로젝트임에도
향후 업데이트를 위해
게임을 어떻게 설계할 수 있을까?
하는 고민이었다.
아직 더 만들 수 있는 캐릭터와 무기들이 많이 있는데...
이대로 덮어두기엔 너무 아쉽다.
하지만 지금은 개인 프로젝트보다
취업을 위한 포트폴리오 작업이 절실하니
기회가 된다면 꼭 완성해보고 싶다.
(이 스프라이트도 괜찮은거 같았는데 말이지...)
'게임 개발 > 게임 개발 프로젝트' 카테고리의 다른 글
[팀 프로젝트] 'The Last Hunt' 개발 후기 - 02 (0) | 2024.08.20 |
---|---|
[팀 프로젝트] 'The Last Hunt' 개발 후기 - 01 (0) | 2024.08.20 |
학원 수업 ) 길 찾기 알고리즘 (1) | 2024.06.11 |
C# 콘솔로 포켓몬스터 게임 만들기 (완결) (0) | 2024.06.06 |
C# 콘솔로 7포커... (feat. 버블정렬, ) (0) | 2024.05.20 |