이번주는 고멤무스메 플레이 영상을 보고 그와 비슷한 느낌의 러너 게임을 제작해보았다. 고멤무스메 그냥 보기에는 간단한 러너 게임 같지만 자세하게 들여다보면 꽤 신경써서 만들어야 할 부분이 많아 보인다. 특히 카메라를 활용한 연출이 그렇다.
우왁굳의 시청자 참여를 선발하기 위한 게임이라고 한다. 우왁굳 방송을 안 봐서 몰랐는데 팬이 만들었다고? 심지어 트위치 실시간 댓글과 아래 관객 대사랑 연동 됨....ㅋㅋㅋㅋㅋㅋㅋㅋㅋ
어쨌든 카메라 연출을 제외하고 위의 이미지를 클릭해 달리기에 참여할 참가자를 정하고, 각 달리기 참가자의 캐릭터를 월드에 배치해서 달리게 하는 부분을 만들어봤다.
Runner
Runner Data 생성
- 달리기 캐릭터의 능력치 정보는 Scriptable Object로 관리할 수 있다.
- Scriptable Object를 상속하는 StatusData.cs 만들고 CreateAssetMenu를 통해 Asset 창에서 Scriptable Object 생성할 수 있도록 한다.
- Assets - Create - Scriptable Object 생성 후 각 오브젝트마다 Runner의 스탯 (시작 속도, 최고 속도, 가속도, 얼굴 이미지) 등을 설정해준다.
using UnityEngine;
[CreateAssetMenu(fileName = "StatusData", menuName = "ScriptableObj", order = int.MaxValue)] // 메뉴 가장 아래쪽에 생성
public class StatusData : ScriptableObject
{
public string runnerName;
[Header("Images")]
public Sprite runnerFaceImage;
public Sprite runnerTopImage;
[Header("Stats")]
public float startSpd;
public float maxSpd;
public float acceleration;
}
Runner Object 생성
준비
- 생성할 캐릭터 오브젝트 prefab을 담은 Runner Prefab List
- Spawn Position (빈 오브젝트 만들어 캐릭터 생성하고 싶은 위치에 갖다놓기)
- 각 Runner의 스탯 (Scriptable Object statustData) 을 담아놓기 위한 StatusDatas List
- 생성된 Runner를 담은 runnerObject List
- 생성된 Runner의 Portrait 이미지를 담은 runnerPortraits List
- RunnerSpawner에 필요한 object를 넣어준다.
방법
- Enum에 Runner Status Data 개수만큼 만들어놓는다.
- Status Data 개수만큼 for 문 돌면서 runner를 미리 만들어놓는다.
- 특정 Runner가 선택되었을 때 화면에 Runner의 이미지를 띄우고 Runner 오브젝트를 SetActive(true);
SpawnRunner(RunnerType type)
Runner의 Status Data를 미리 담아놓고 Runner를 생성하기 위한 Script
- Runner prefab과 Spawn Position을 이용해 새로운 Runner를 생성한다.
- 새로운 Runner의 MovementControl component의 StatusData를 넣고 싶은 StatusData로 설정한다.
- runnerObject List에 새로 생성한 오브젝트를 추가한다.
ToggleSpawnIndexArray(int index)
Runner 선택했을 때 일어나야 할 일을 담은 script
- 선택한 runner object가 Hierarchy 상에서 꺼져 있으면 켠다. / 켜져 있으면 끈다.
- runnerIndexArray 배열에서 선택/해제한 runner object의 자리를 찾아 bool 값을 true/false로 설정한다.
- runnerPortraits 배열에서 선택/해제한 runner 이미지를 찾아 켜거나/끈다.
using System.Collections.Generic;
using UnityEngine;
public enum RunnerType
{
boy1,
boy2,
boy3,
}
public class RunnerSpawner : MonoBehaviour
{
[SerializeField]
private List<StatusData> statusDatas;
public List<StatusData> StatusDatas { get { return statusDatas; } }
[Header("Runner")]
[SerializeField]
private GameObject runnerPrefab; // character
[SerializeField]
private Transform spawnPos; // start position
[SerializeField]
private bool[] runnerIndexArray;
public List<GameObject> runnerPortraits; // character img
public List<GameObject> runnerObject = new List<GameObject> ();
private void Awake()
{
for(int i = 0; i< statusDatas.Count; i++)
{
var runner = SpawnRunner((RunnerType)i); // int -> enum casting
runner.SetActive(false);
}
runnerIndexArray = new bool[statusDatas.Count];
}
public GameObject SpawnRunner(RunnerType type)
{
// Instantiate(Object, Vector3 Position, Quaternion Rotation, Parent)
var newRunner = Instantiate(runnerPrefab, spawnPos.position + new Vector3(2 * runnerObject.Count, 0, 0), Quaternion.identity, transform);
newRunner.GetComponent<MovementControl>().StatusData = statusDatas[(int)type];
runnerObject.Add(newRunner);
return newRunner;
}
public void ToggleSpawnIndexArray(int index)
{
// When runner object is off, turn it on. When it is on, turn it off.
runnerObject[index].SetActive(!runnerObject[index].activeInHierarchy);
runnerIndexArray[index] = !runnerIndexArray[index]; // bool array
runnerPortraits[index].SetActive(!runnerPortraits[index].activeInHierarchy);
}
}
UI
캐릭터 선택창
- SpawnUI: Canvas 생성 - Canvas Scalar - Scale with Screen Size로 캔버스 크기 설정 (1920 x 1080)
- RunnerSelect: 빈 GameObject 생성 - Pivot 기준 Top, 좌우 Stretch로 설정
- RunnerSelect: Grid Layout Group 추가
- 자식이 될 각 캐릭터 이미지의 Cell Size (150 x 150) 설정
- 자식 간의 간격 설정
- Start: 오른쪽에서부터 추가되도록 Corner Upper Right로 설정
- Child Alignment : 캐릭터 추가될 때마다 가운데에 올 수 있도록 Middle Center로 설정
- RunnerToggle: Image 생성 - Toggle component 추가
- Runner Toggle을 Prefab으로 만듦
결과
Runner Spawner의 리스트에 넣어둔 Scriptable Object에서 캐릭터 사진을 가져와 보여주게 된다.
Event
달리는 중간에 플레이어의 달리기 스탯이 바뀌는 이벤트
RaceEvent 생성
- public enum으로 이벤트 종류 생성
- System.Enum.GetValues(typeof(enum이름)).Length로 enum 개수 받아오기
- Random.Range(0, Enum개수)로 숫자 하나 뽑고 생성한 이벤트 enum의 데이터 타입으로 casting
- enum 변수를 string으로 변경한 뒤 Invoke로 실행
using UnityEngine;
public enum EventEnum
{
AllSlow, AllFast, HalfSlow, HalfFast
}
public class RaceEvents : MonoBehaviour
{
[ContextMenu("RandomEvent")]
public void CallRandomEvent()
{
EventEnum RandomEvent = (EventEnum)Random.Range( 0, System.Enum.GetValues(typeof(EventEnum)).Length );
Invoke(RandomEvent.ToString(), 0f); // execute
}
}
번외) 문제해결
이번 프로젝트는 3D URP를 써보겠다는 생각으로 만들었기 때문에 Asset을 내려받아 사용할 때 Material이 깨지는 문제가 있었다. Render pipeline이 달라서 생기는 문제였고, 간단히 해결할 수 있었다.
[Unity 공부] - [Unity] 3D URP Asset Material 깨지는 문제 (Built-in → 3D URP 변환)
[Unity] 3D URP Asset Material 깨지는 문제 (Built-in → 3D URP 변환)
일반적으로 Asset store의 자료들은 이전 Built-in pipeline 기준으로 만들어졌기 때문에 3D URP 프로젝트에서 불러오면 asset이 전부 분홍색으로 뜨면서 material이 하나도 적용되어 있지 않은 모습을 확인
psych-dobby.tistory.com
후기
확실히 그냥 기능을 배우는 것도 중요하지만, 이렇게 이미 존재하는 게임의 기능을 파악하고, 그에 맞추어 어떻게 구현해야 할지 방법을 생각해서 직접 만들어보는 게 훨씬 재미있긴 하다. 특히 이번 게임은 지난주에 배운 시네머신을 활용해 여러 가상 카메라를 옮겨다니면서 더 박진감 있는 달리기 게임 연출을 할 수 있을 것 같아서 기대가 된다.
지금은 게임이 끝났을 때 승자와 순위를 알려줄 UI를 만들고 있고, 캐릭터를 중앙에서부터 생성할 수 있도록 script를 수정했는데 됐다가 다시 안 되고 있어서 손볼 예정이다.
unity 만드는 것도 좋지만 정리를 하면서 진행하는 게 나중에 다시 예전 프로젝트를 들여다보았을 때 이해가 빠를 것 같아서 정리를 하는 것에도 시간을 들이고 있다. Notion에 적는 게 편해서 Notion에 기록하다보니 여기는 조금 방치되고 있지만 어딘가에라도 정리해두는 게 훌륭하지!
이번주에는 유데미 측과 간담회가 있었다. 다들 최종 평가가 어떻게 이루어질지에 대한 걱정이 많았던 것 같은데, 앞으로 진행될 최종 평가와 수료 기준에 대해 명확히 짚어주어서 좋았다. 열심히 해서 최종 프로젝트, 인턴까지 모두와 함께하고 싶다. 알고리즘 스터디도 열심히 하고 있으니, 뭐든 하나씩 해치우다 보면 나아져있으리라 믿는다.
유데미코리아 바로가기 : https://bit.ly/3b8JGeD
본 포스팅은 유데미-웅진씽크빅 취업 부트캠프 유니티 1기 과정 후기로 작성되었습니다.
새로운 가능성의 시작, 유데미 x 웅진씽크빅
글로벌 최신 IT 기술과 실무 교육을 입문부터 심화까지! 프로그래밍, 인공지능, 데이터, 마케팅, 디자인 등 세계 최고의 강의를 경험하세요.
www.udemykorea.com
댓글