어제는 새벽까지 지속되는 작업에 정신이 없어 자랑을 못했지만
무려 내 TIL이 캠프 5주차 우수 TIL로 선발 되었다.
굉장히 가벼운 분위기로 일기 쓰듯 단순 복기 해보는 시간으로 써온 TIL인데,
이것이 우수로 뽑힌다니 당황스러우면서도 처음 해보는 이런 생활 속에 못하고 있다는 불안함이 많았지만
생각보다는 내가 잘하고 있는 거라는 안심을 할 수 있었다. 이것이 가장 큰 보상이 아닐까.
TIL 선발 사례 내용 :
1. 오늘의 알고리즘 코드카타 - 문자열 다루기 기본
답안 :
//문자열이 숫자인지 확인하는 법 필요
//콘솔 과제때 잘썼던 int TryParse를 이용 가능하지 않을까
public class Solution {
public bool solution(string s) {
bool answer = false;
if(s.Length == 4 || s.Length == 6)
{
answer = int.TryParse(s, out int result);
}
return answer;
}
}
초반에는 for문으로 s의 길이를 체크하려 하였지만,
생각해보니 string은 Length를 붙여주면 알아서 길이를 체크할 수 있다는 점이 기억났다.
여기에 더해 C# 개인 과제 때 이용했던 int.TryParse를 이용해 해당 문자열이 숫자 값인지 체크하는 방법으로
처음 구상했던 코드 내용 보다 훨씬 내용이 짧아졌다.
그래서 그런지, 어제 오늘 둘 다 코드 점수가 평소 1점보다 한참 높은 11점이 채점 되었다.
정확한 기준은 모르지만 성장하는 느낌이 들어 뿌듯해진다.
2. 오늘의 작업 내용
오늘은 제출 시간 전까지 1분 1초 낭비할 시간 없이 제출하는 순간까지 작업, 제출 후에도 발표 전까지는 급하게 머지 하면서 일어난 문제들을 지속적인 버그 픽스를 해줘야 했다.
캐릭터 변경 시스템이 제출하는 그 순간 완료되었다 보니
이를 이용하려던 애니메이션에 파티클 추가는 제대로 완료되지 못했기에 빼버렸다.
어제 작업 내용이 많기 때문에 그 부분을 설명하면서 시작하겠다.
추가 기능 :
사운드 매니저 구현
-최대 사운드 소스 갯수를 오브젝트 풀로 저장
-BGM 적용
-보스 방 BGM 별개 적용
이 부분을 유니티 엔진에서 본 모습이다
사운드 매니저를 만들어 사운드를 전반적으로 조절할 수 있도록 하고
여기에 각종 BGM Clip을 등록해 두어서 해당하는 트리거가 발생하면 노래를 바꿔준다.
또한 기존에 탄환(Projectile)에 쓰였던 오브젝트 풀에 사운드 소스를 등록하였다.
각종 탄막이 날아다니며 피격음 + 발사음 + 배경음 + 사망음 + 몬스터의 소리 등등 아주 다양한 소리가 나야하는데
이런 것들을 미리 최대 갯수를 정해준다. 특히 한번에 수십발이 날아드는 발사음에 대한 처리가 이렇게 하게 되어 많이 가벼워 졌다.
사운드 매니저의 코드는 강의 교안에서 사용했던 것과 같은 방식이다.
추가 된 사운드들은 시연 영상으로 대체 하도록 하겠다.
코드 내용 :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class SoundManager : MonoBehaviour
{
public static SoundManager instance;
[SerializeField][Range(0f, 1f)] private float soundEffectVolume;
[SerializeField][Range(0f, 1f)] private float soundEffectPitchVariance;
[SerializeField][Range(0f, 1f)] private float musicVolume;
private ObjectPool objectPool;
private AudioSource musicAudioSource;
public AudioClip musicClip;
public AudioClip bossBGM;
private void Awake()
{
instance = this;
musicAudioSource = GetComponent<AudioSource>();
musicAudioSource.volume = musicVolume;
musicAudioSource.loop = true;
objectPool = GetComponent<ObjectPool>();
}
private void Start()
{
ChangeBackGroundMusic(musicClip);
}
public static void ChangeBackGroundMusic(AudioClip music)
{
instance.musicAudioSource.Stop();
instance.musicAudioSource.clip = music;
instance.musicAudioSource.Play();
}
public static void PlayClip(AudioClip clip)
{
GameObject obj = instance.objectPool.SpawnFromPool("SoundSource");
obj.SetActive(true);
SoundSource soundSource = obj.GetComponent<SoundSource>();
soundSource.Play(clip, instance.soundEffectVolume, instance.soundEffectPitchVariance);
}
void OnEnable()
{
SceneManager.sceneLoaded += OnSceneLoaded;
}
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if(scene.name == "BossLevel")
{
instance.musicAudioSource.Stop();
instance.musicAudioSource.clip = bossBGM;
instance.musicAudioSource.Play();
}
}
void OnDisable()
{
Debug.Log("OnDisable");
SceneManager.sceneLoaded -= OnSceneLoaded;
}
}
추가 적 기능
-적들마다 각자의 탄막을 가짐.
-적들 애니메이션 추가.
-적 탄막 애니메이션 추가
-적 피격 / 탄막 소리 추가
-적 배치 끝
-보스는 플레이어를 쫓는 기능
-보스 몬스터 울음소리 추가
-일부 스프라이트 변경
이 부분은 적 종류마다 하나씩 설명하려고 한다. 모두 목적이 있는 기획을 통해 준비 되었기 때문에 짧게 컨셉을 설명하면서 넘어 가겠다. 내가 기초 로직이나 코드 적인 부분으로 크게 건들인 것은 없기 때문에 코드 부분은 넘어가겠다. 각자 마찬가지로 소리는 시연 영상으로 대체한다.
GIF의 용량이 커서 사진으로 대체하고 후에 시연 영상에서 보여주도록 하겠다.
오브젝트 풀 코드 변경
-DontDestroyOnLoad의 문제점으로 인해 다음 씬에서 재생성 하는 방법으로 변경
이 부분은 두 가지 해결 방법이 팀에서 나왔었다.
나는 계속 씬 로드와 관련된 작업을 했다 보니까 당연스럽게
SceneManager.sceneLoaded
의 기능 중 OnSceneLoad를 이용하려고 했었다. 다음 씬이 불러올 때 오브젝트 풀도 같이 부르면 되겠지 라는 단순한 생각이었다.
다른 조원은 해당 기능 말고 튜터에게 도움을 받아 Linq를 이용하는 코드를 배워왔다.
------------생략---------------
InitPool(pool);
}
}
void InitPool(string tag)
{
var pool = pools.Find(p => p.tag == tag);
InitPool(pool);
}
void InitPool(Pool pool)
{
Queue<GameObject> objectPool = new Queue<GameObject>();
for (int i = 0; i < pool.size; i++)
{
GameObject obj = Instantiate(pool.prefab);
obj.SetActive(false);
objectPool.Enqueue(obj);
//DontDestroyOnLoad(obj);
}
if (poolDictionary.ContainsKey(pool.tag))
{
poolDictionary[pool.tag] = objectPool;
}
else
{
-------------생략---------------------
GameObject obj = poolDictionary[tag].Dequeue();
if (obj == null)
{
InitPool(tag);
obj = poolDictionary[tag].Dequeue();
}
poolDictionary[tag].Enqueue(obj);
Linq를 사용한 코드가 더 배울 것도 많고 좋은 형태라고 생각해 이 쪽을 쓰게 되었다.
파티클 이펙트 적용
-플레이어 이동 파티클 < - 캐릭터 구현이 늦음에 따라 파기됨
-탄막 피격 파티클
맵 구현
-보스 방 추가
-맵에 탄막 제거용 전용 콜라이더 타일맵 추가
-일부 prefab props에도 해당 효과 적용
맵마다 탄막에만 적용되는 콜라이더가 들어간 모습, 랜덤 맵에서 프리메이드 맵을 쓰게 되면서 좀 더 구체적인 콜라이더를 배치해 주기로 하였다. 이곳에서만 탄막을 체크하는 것은 별 거 아닌 그저 레이어를 추가해 해당 레이어에 탄이 부딛히면 삭제되는 것.
레이어로 작동하기에 적용이 간편해서 오브젝트들에도 일부 적용되어 있다.
그런 것들을 이용해서 이런 식으로 오브젝트를 엄폐물 삼아 전진하는 기믹을 구현해 보았다.
맵 클리어 시에 문 열고 닫히는 로직 구현
다른 팀원이 UI를 작업하며 맵의 클리어 횟수를 기록하는 코드를 만들어야 했고 나는 이를 이용해 클리어 판정이 났을 때 방문들의 닫힘 상태(닫힌 모습의 스프라이트와 콜라이더)를 SetActive(false)해주고 열린 상태를 SetActive(true)해주는 코드를 만들 생각을 하였다.
하지만 코드를 작성하다보니 초기에 잘못했던 구조 구상이 스노우볼이 되어서 돌아왔다.
맵들은 매우 비효율 적인 씬 분리가 되어있고, 그 중에서도 적에 배치가 스포너를 쓰려던 방식에서 단순히 맵에 미리 배치해둔 점 때문에 이들을 카운트 하는 방법도 조금 복잡해졌고
이런 것들을 이전에 만들어둔 DoorManager에서 값을 받아들이는데 직관적인 방법이 힘들고 오래 고민을 해야했다.
단순한 기능 구현을 하려던 것임에도 불구하고
이전 랜덤 생성되던 로직 구상에 맞추어 준비하던 DoorManager와 RoomControll등의 코드가 문제가 되어 힘들었던 것.
각 문들은 DoorType을 가지고 있고 이를 이용해 이 문이 챌린지 문인지 베이직 문인지 등을 구분하게 하는 방식이었다.
그런데 각 미리 배치된 개체들이 가지고 있는 값을 받는 코드를 쓰는게 힘들었다.
처음에는 방을 넘어갈 때 문들의 갯수를 리스트로 저장하는 방법을 생각하고 있었다.
하지만 nullrefernce가 계속 뜨고 있었고 (사실 내 코드 작동 순서가 잘못된 것이었다. Start가 아닌 Awake쪽에 넣어주니 같은 코드로 해결됬었다...) 이를 배열로 바꾸어 해결 시도를 하였다. 게임 매니저에서 3의 크기의 배열을 받는 방식으로 이를 위해 우선 DoorType에다가
public enum DoorType
{
challenge = 0, standard, basic
}
로 수정하여 각 타입이 숫자로 접근될 수 있도록 하였다. 그리고 이 숫자를 받아 Switch 문으로 값을 보내 작동하는 방식을 생각하다가 뒤늦게 문제를 찾게 되었다. 각 난이도의 최종 방은 보스룸으로 이어지는 하나의 방만 존재하고 보스룸에서도 엔딩룸까지 하나의 방만이 존재한다. 이때 3만큼 값을 받지 못하면 오류가 뜨던 것이었다.
그래서 다시 리스트로 방향을 전환했다. 도어 컨트롤은 각 맵마다 배치되어 있으므로 이를 추가할 때 편리하게
GameManager.instance.doorControll.Add(this);
를 이용해 이를 가진 개체들이 자연스럽게 리스트에 추가되도록 하였다.
public List<DoorControll> doorControll;
---------------생략-------
doorControll = new List<DoorControll>();
----------생략----------
if (enemiesCount <= 0)
{
for(int i = 0; i < doorControll.Count; i++)
{
doorControll[i].OnRoomClear();
}
doorControll.Clear();
그리고 게임 매니저에서는 다음과 같이 룸 클리어가 발생할 시에는 이 리스트를 비워주는 방식으로 하였다.
enemiesCount가 <= 0 인 이유는 시작 방이 0마리기 때문에 바로 클리어를 내버리면 안되기 때문...
또한 시작 방은 문이 닫혀있을 이유가 없기에 수동으로 열어놓으면 그만이었다.
그런데 문제로 죽어서 리트라이를 하게될 때 이 코드가 방을 깨서 클리어 하기 전에 다시 작동하기 때문에 추가할 방을 찾지 못해 오류가 뜨던 것
그래서 이는 코드 작동에는 문제가 없지만 작업하며 보기에 문제가 있기 때문에
try
{
ClosedDoor.SetActive(false);
OpenedDoor.SetActive(true);
}
catch(MissingReferenceException e)
{
return;
}
try catch문으로 해당 내용을 넣어 무시할 수 있도록 했다.
문이 열리고 닫히는 것이 구동하는 모습
버그 해결
-이미지 깨짐 문제 해결 <- 유니티 Compression 문제, Compression = none으로 변경
-프로젝타일 전용 레이어가 변경되던 오류 <- 계단과 문에 적용된 레이어 변경 코드가 탄막에 적용 되었다.
탄막의 레이어와 해당 변경 코드를 가진 레이어가 충돌되지 않도록 프로젝트 세팅에서 설정
머지 작업
-각종 컨플릭트 문제 해결
설명은... 아끼겠다.
3명이라는 적은 인원,
그 중에서도 코드를 주도할 만큼 코딩 경험이 풍부한 사람이 없는
누가 봐도 상대적으로 힘든 조 였기 때문에
결과물이 아쉬우면서도 뿌듯한 것 같다.
발표 피드백은 담당 튜터님이 열심히 도와준 덕분인지 다행히도 생각보다 좋게 받은 편이고
다른 조에서 지적된 문제점인 너무 많은 Update문에 GetComponent, 이걸 바로 수정하는 코드들 등등
이런 부분에서 조심하려고 한 노력이 효과가 없지는 않았다고 생각된다.
깃 리드미 작성 부분은 점차 시간적 여유를 내야할 부분이고
DontDestroy + find 쓰기 보다는
게임 자체에서 플레이어가 계속 인지되도록 하는게 더 좋다는 이야기는 공부할거리라고 생각된다.
피곤했던 주차 마무리는 시연 연상으로 하고자 한다.
'TIL' 카테고리의 다른 글
[TIL]2024-2-02 / 30일차 - 강의 영상 끝마치기 (0) | 2024.02.02 |
---|---|
[TIL]2024-2-01 / 29일차 - Unity 숙련 주차 시작 (0) | 2024.02.01 |
[TIL]2024-1-30 / 27일차 - Unity 팀 과제 마지막 마무리 단계 (1) | 2024.01.30 |
[TIL]2024-1-29 / 26일차 - Merge의 시간이다, 하루 종일 버그를 고쳐보자 (0) | 2024.01.29 |
[TIL]2024-1-26 / 25일차 - 팀 과제 맵 이동 구현 (2) | 2024.01.26 |