코드 전체 구조
Manager Class
UIManager와 DataManager는 인스펙터에서 데이터를 가지고와야 하기 때문에 Mono를 상속시켜 GameManager를 통해 접근할 수 있게 했다.
굳이 Mono를 상속시킬 필요 없고 Instance가 필요 없는 EventManager와 PoolManager는 정적 클래스로 빼주었다. 이 클래스들은 정적 멤버 함수를 통해 접근시켰다.
SoundManager는 GameManager를 통해 접근 시키지 않았다. 난 사실 6개월 전의 나를 잘 모르겠어서 왜 GameManager를 통해 접근시키지 않았는지는 모르겠지만... 아마 모든 스크립트에서 쓸 일이 많아서 싱글톤으로 만든 것 같다.
Character Behavior Class
비주얼 스튜디오에 클래스 다이어그램을 자동으로 만들어주는 좋은 기능이 있어서 한 번 써봤다.
CharacterBehaviour
입력된 특성에 따라 CharacterBehavior를 상속받는 하위 클래스를 Player GameObject에 컴포넌트로 넣어주는 구조이다. 캐릭터의 움직임을 담당하는 클래스에서 이 하위 클래스들을 리스트로 관리하며 캐릭터의 방향과 움직임을 설정해준다.
상속
플레이할 때 업캐스팅을 이용해서 하위 클래스들을 한 리스트에서 관리해야했기 때문에 상속을 이용했다. 또, 상속을 이용해 부모의 함수를 재정의해 자식의 능력을 구현할 수 있다.
특성에 따른 캐릭터 이동
하위 클래스를 행동 타입에 맞게 컴포넌트로 추가하는 함수
/// <summary>
/// 행동 타입에 맞는 컴포넌트를 넣어주는 함수
/// </summary>
/// <param name="type">행동 타입</param>
/// <param name="item">대상 (아이템)</param>
private void AddSettingDirection(VerbType type, ItemObject item)
{
Type scriptType = Type.GetType(type.ToString());
CharacterBehavior behavior = GetComponent(scriptType) as CharacterBehavior;
// 해당 행동이 없거나 있지만 대상이 다를 때는 새로 넣어준다
if (behavior == null || behavior.IsActive)
{
behavior = gameObject.AddComponent(scriptType) as CharacterBehavior;
behaviors.Add(behavior);
}
behavior.Init(item);
}
게임을 실행하면 활성화된 행동만 방향을 설정하기
// CharacterMovement.Update...
if (character.IsInactive) return;
List<CharacterBehavior> directions = behaviors.FindAll(x => x.IsActive);
directions.ForEach(x => x.SetDirection());
Character Class
Character과 관련된 클래스들은 해당 오브젝트에 컴포넌트로 붙일 것이기 때문에 모두 Mono를 상속 받았다.
Character~ 클래스
모든 움직이는 오브젝트 (Player, Ghost, etc)는 해당 스크립트를 가지고 있으며 Character~ 클래스들은 모두 Character 클래스를 필요로 한다. (Require Component)
Character~ 클래스들이 Character에 접근하는 것은 아니지만, 스테이지에 있는 캐릭터라면 모두 가지고있어야하는 스크립트이기 때문에 필수로 해주었다.
그럼에도 Charater~ 클래스들이 Character 상속을 받지 않은 이유는 두 가지가 있다. 첫째, 맵에서 Character class를 사용해 캐릭터를 식별하기 때문이다. 그렇기 때문에 한 오브젝트당 하나의 Character class만 있어야 한다. 둘째, Charater 클래스가 코드 재사용에 도움이 되지 않았고, 마지막으로 Character로 업캐스팅할 이유가 아직은 보이지 않았따.
개선점
Character 클래스에는 플레이어인지 아닌지를 판단하는 bool 변수인 isPlayer가 있지만, 지금 와서 생각해보면 플레이어는 Character를 상속받는 별도의 Player 클래스를 새로 만들어주는 게 효율적인 것 같다.
스테이지 관리
모든 스테이지는 Prefab으로 만들어져있다.
Prefab으로 스테이지를 제작한 이유는 눈에 가장 잘 보이는 형태로 스테이지를 기획하고 제작할 수 있기 때문이다. 또, 변수 없이 게임에 들어가는 스테이지의 형태 그대로를 볼 수 있는 장점이 있다.
Prefab 개선점
나중에 유저가 맵을 직접 만들고 저장하는 형식으로는 부적합한 것 같다. 후에 JSON 데이터 형식으로 저장해 로드하고, 맵을 생성하는 것을 고려해봐야 한다.
이렇게 제작한 스테이지들은 Scriptable Object로 챕터마다 관리된다.
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(menuName = "SO/StagesSO")]
public class StagesSO : ScriptableObject
{
public int chapter;
public List<GameObject> stages;
}
스테이지 관리 SO 코드
Scriptable Object로 스테이지들을 관리한 이유는 스테이지의 삽입, 삭제, 순서 변경이 매우 편리하기 때문이다. 또, SO를 많이 만들어내서 챕터를 여러 개 늘리는 것또한 간단하게 할 수 있어 SO를 선택하였다.
한 스테이지가 시작될 때 해당 스테이지의 index 프리팹을 불러 생성 (Instantiate) 해주고 전에 있던 스테이지를 소멸(Destroy)한다.
맵 생성 개선점
확실히 이 부분은 스테이지에 있는 모든 오브젝트를 생성하고 삭제해야 하기 때문에 비효율적인 코드이다. 생각한 해결책은 전에 있던 같은 오브젝트들을 풀링하는 것이다.
현재는 별만 풀링이 되어있지만, 나무, 유령, 플랫폼과 같은 모든 오브젝트들을 풀링한다면 더 효율적인 코드가 될 것이라고 생각한다. 다만, 풀 구현 시 오브젝트를 잘 리셋하고 연동되어있는 이벤트 매니저가 꼬이지 않게 하는 게 중요할 것 같다.
프로젝트 깃허브 저장소 링크
'개발한 게임들 > 지지는 아무 생각이 없다' 카테고리의 다른 글
[프로젝트 후기] 지지는 아무 생각이 없다 (2) | 2023.01.20 |
---|---|
[Project] 🤖 지지는 아무 생각이 없다 (0) | 2022.11.09 |