단기 시일에 간단한 게임을 부트캠프에서 구현해 보았다
| 게임 컨셉 : 하늘에서 떨어지는 독촉과 할일(과제,TIL,코드카타)을 처리하며 살아남아 초보 개발자에서 시니어 개발자로 성장하기.
| 참여 인원 : 5명
| 제작시간 : 1주일
깃 사용 과정
깃의 충돌과 방지를 위하 각자의 브랜치, 각자의 Scene에서 작업 후 test에 merge 최종 완성본에서 main에 merge하는 방식으로 협업을 하였다.
각자의 Scene을 만든 이유는 Scene을 변경할 때마다 충돌이 일어나는 것을 미리 방지하기 위해 각자의 Scene에서 업무 작업후에 서로 회의를 통해 메인Scene에 작업물을 합치는 작업을 가지니 미리 충돌을 회피할 수 있다는 장점을 느낄수 있었다.
구현 과정
구현의 경우 위의 브랜치처럼 1 : 1: 1 : 2로 각각 크게 플레이어, 몬스터, UI, 시스템을 담당하게되었다.
플레이어
| 이동
플레이어 이동, 점프, 공격의 경우 새로운 InputSystem의 경우 기존 Input System과 비교하여 Custom을 통하여 나만의 InputSystem을 제작할 수 있고 플랫폼 호환성, 개선된 UI라는 장점이 있는 것을 알게되어 사용하게 되었다.
단 하나 아쉬웠던 점은 InputSystem을 SendMessage 방식을 사용하여 구현하였는데 이는 C#의 리플렉션 기능에 의존하여 성능이 좋지 않다고 하여 Invoke 방식을 사용하면 좋았겠다는 점과 Custom c# script를 사용하지 않아 어쩔수 없이 Update문에서 마우스의 움직임을 감지한 부분이 있는데 이부분에 더 공부해야겠다고 생각하였다
| 스텟
플레이어가 여러명이 아니며 직업이 여러개가 아니여서 ScriptableObject를 통해 관리하지 않아도 된다고 판단하여 기본 class와 스텟을 제어 할 수 있는 스크립트를 통해 스텟을 제어 할 수 있게 하였다.
몬스터
| 스텟
3가지의 종류의 몬스터로 구현 되어 있어 Scriptable Obejct를 사용하여 구현이 되어있으며 이 Obejct를 통해 여러 가지 능력을 지닌 몬스터가 제작될 수 있도록 만들어 졌다.
using UnityEngine;
[CreateAssetMenu(menuName = "CreatureStat", fileName ="Creature_")]
public class CreatureStatSO : ScriptableObject
{
public Sprite sprite;
public float hp;
public float speed;
}
| 이동
이동은 기본적으로 rigidBody를 이용하여 아래로 내려가며 추가적으로 각 몬스터의 특정에 변화가 주어져 있다.
총 2가지의 이동 방식이 존재한다.
- 기본 rigidBody.velovity
- 기본 + coroutine를 이용해 일정거리 이동후 순간 가속 | 가속의 경우 Animation에 Event로 함수를 제공하여 실행된다.
private IEnumerator RushStandby()
{
float current = 0;
float percent = 0;
float time_temp = 2f;
Vector3 startVelocity = _rigid.velocity;
while (percent < 1)
{
current += Time.deltaTime;
percent = current / time_temp;
_rigid.velocity = Vector3.Lerp(startVelocity, Vector3.zero, percent);
yield return null;
}
_rigid.velocity = Vector3.zero;
_animator.SetTrigger("RushStandby");
}
public void OnRush()
{
_rigid.velocity = Vector2.down * _moveSpeed * 5;
}
| 레이저
구현된 것을 보았을때 가장 궁금했던 코드 부분이 레이저 부분이였다.
레이저는 주로 코루틴이 많이 사용되었는데 생성은 간단하게 코루틴을 일정 주기동안 생성이되며 몬스터의 감속 로직과 비슷하게 작성되어 있는데
아래의 로직을 통하여 크기가 감소 되었다 증가하는 부분을 표현할 수 있다는 것을 배울 수 있게 되었다.
private IEnumerator Warning(float start, float end)
{
_warning.gameObject.SetActive(true);
SpriteRenderer warningRenderer = _warning.GetComponent<SpriteRenderer>();
float currntTime = 0;
float percent = 0;
while (percent < 1)
{
currntTime += Time.deltaTime;
percent = currntTime / warningTime;
Color color = warningRenderer.color;
color.a = Mathf.Lerp(start, end, percent);
warningRenderer.color = color;
yield return null;
}
if (warningCount == WARNING)
{
warningCount = 0;
_warning.gameObject.SetActive(false);
yield return new WaitForSeconds(RAY_DEALY);
StartCoroutine(LazerAppear());
}
else
{
warningCount++;
StartCoroutine(Warning(end, start));
}
}
Sound
| 사운드
현재 AduioManager에서 사운드를 관리하고 있으며 각각의 사운드 종류를 string으로 구분하고 있는데 좀더 사운드가 많아지고 규모가 커진다면 Enum으로 관리하는 것이 좀더 효율적일거라는 생각이 들었다.
Data
| Json
처음에는 PlayerPrefs로 데이터를 저장하는 방식을 사용하였지만 이를 사용하면 내 PC에만 데이터가 저장되는 단점이 있어 Git으로 협업한 사람들의 데이터도 자동으로 저장 될 수 있도록 Json을 사용하였다.
private void SaveRankings()
{
string jsonData = JsonUtility.ToJson(new Serialization<PlayerScoreData>(_playerRankings));
System.IO.File.WriteAllText(_filePath, jsonData);
}
[System.Serializable]
public class PlayerScoreData
{
public string playerName;
public int score;
public PlayerScoreData(string name, int score)
{
this.playerName = name;
this.score = score;
}
}
[System.Serializable]
public class Serialization<T>
{
[SerializeField]
private List<T> _data;
public List<T> ToList() { return _data; }
public Serialization(List<T> data) { this._data = data; }
}
여기서 데이터를 저장 할때 PlayerScoreData라는 class가 저장되어 있는 playerRankings라는 리스트를 Json화 하는데 JsonUtility.ToJson의 경우 자동으로 직렬화를 즉 파싱이 가능한 데이터 형태로 저장해 주지 못하므로 직점 Serialize를 설정해 주어야 한다.
한가지 아쉬웠던 점은 단기 프로젝트여서 firebase같은 서버를 활용하여 저장하는 것이 아닌 파일이나 로컬 상에서 저장하는 방식을 사용하여 다음 프로젝트는 서버를 이용하면 어떨까 라는 생각이 들었다.
UI
| DOTween
각종 UI에는 다양한 기능들이 들어가 있는데 그중 이번 프로젝트로 새롭게 알게된 기능은 DOTween이라는 기능이다.
DOTween은 애니메이션이나 긴 코드로 작성해야되는 각종 이벤트나 동작들을 간단한 코드로 작설할수있게 도와주는 애니메이션 엔진이다.
아래의 예시 코드는 글자가 time / 2시간 뒤에 점점 흐려지는 코드이며 DOTween을 사용하지 않을경우 Animation을 이용해 효과를 구현해야한다.
countTimeText.DOFade(1, time / 2).OnComplete(() =>
{
countTimeText.DOFade(0, time / 2);
}
Controller
전반적인 값에 대한 관리를 Controller에서 관리를 하니 어떤 동작이나 값을 test 및 제어 하는것이 간편하여 Controller가 몇가지 구현되어 있다.
| Stage : 각종 스폰 시간, 스폰 종류제어
| Monster : 몬스터 스텟제어
| Gauge : 게임 시간 제어
| PlayerStat : 플레이어 스텟제어
| ObjectPool : 오브젝트 무한 생성 방지 및 게임 최적화
| Aduio : 효과금, 배경음등 각종 사운드 제어
| GameManager : 싱글톤 화로 각종 컨트롤러 제어
Design
이번 게임에서 제작된 모든 스프라이트는 모두 국지윤님의 작품으로 정말 감사함을 표한다.
최종회고
빠른 시간안에 게임을 제작해 보는것은 처음이여서 마지막 회고에서 몇가지 아쉬운 점을 보면
- GameManager하나만을 싱글톤화하여 몇가지 따로 싱글톤화 된 것들을 함게 싱글톤 화 하는것이 좋았을 것이다.
- 처음부터 하이어라이키 창에 많은 Object들이 생성되어 있는데 이는 규모가 점점 커지면 부하가 커지므로 필요한 경우 생성, 불필요항 오브젝트는 파괴 하는 형식의 메모리 관리가 필요하다.
두가지 정도의 큰 아쉬움이 들었으며
이번 개발을 토대로 다음 개발에는 new Input System의 Custom화, 최적화를 추가하며 좀더 체계적인 게임을 개발할 수 있겠다는 생각이 들었다.
'unity > 기술개발일지' 카테고리의 다른 글
2) TMPro 글자 모자이크처리 매우 간단한 방법 - 2D 게임 (0) | 2025.01.27 |
---|---|
2D 스토리게임 개발 1) PPU, TMPro (0) | 2025.01.27 |
Unity - 절차적 동굴 생성 (셀룰러 오토마타) (0) | 2024.10.26 |
2인용 간단한 pingpong 만들기 - Photon, firebase (0) | 2024.10.19 |
sprite sorting 설정 - 2D horror 게임 (0) | 2024.08.03 |