최종프로젝트가 종료되고 포트폴리오 정리를 하고 있는 도중에 내가 작업한 부분의 트러블 슈팅이 TIL에 기록되있지 않아
지금이라도 기록을 하려고 한다.

구현을 목적으로 작업하는 도중 매니저 클래스들이 많아지며 점점 호출 시점이 꼬이기 시작
해당 문제를 해결하기 위해 GameSceneTrigger 라는 클래스를 만들어 매니저들의 호출시점을 정리해주었습니다.
최종프로젝트가 종료되고 포트폴리오 정리를 하고 있는 도중에 내가 작업한 부분의 트러블 슈팅이 TIL에 기록되있지 않아
지금이라도 기록을 하려고 한다.
구현을 목적으로 작업하는 도중 매니저 클래스들이 많아지며 점점 호출 시점이 꼬이기 시작
해당 문제를 해결하기 위해 GameSceneTrigger 라는 클래스를 만들어 매니저들의 호출시점을 정리해주었습니다.
프로젝트가 마무리 시점이 되어 , 최적화 작업에 들어갔다.
동적배칭 은 Mesh와 파티클 시스템과 같은 동적으로 생성된 지오메트리에 대해 작은 메쉬(vertex 900개 이하) 및 같은 Material를 사용하는 조건하에 하나의 드로우콜로 처리 하는 것입니다. 해당 프로젝트에서 해당 기술을 사용하여 Batch수를 약 40% 감소 시켰습니다.
정적배칭 은 같은 Material를 사용하는 움직이지 않은 오브젝트를 하나의 Mesh로 결합하여 최적화 하는 기법입니다. 메모리 사용량이 증가 할 수 있지만 , CPU에서 오브젝트를 배치한 후(하나의 Mesh로 결합) GPU에서 한번만 처리하기에 드로우 콜이 감소하여 최적화를 할 수 있습니다.
정적 배칭은 생각밖으로 최적화 효과를 못봤지만 , 동적 배칭에 대해서는 많은 최적화가 진행 되었다 .
using DG.Tweening
Sequence seq = DOTween.Sequence();
seq.SetUpdate(true);
seq.SetUpdate(false);
Sequence의 SetUpdate() 메서드를 사용하면 TimeScale 이 0이여도 DoTween 시퀀스에 저장된 애니메이션들은 동작하게 된다.
예제)
private void MakeSquence()
{
Time.timeScale = 0;
Sequence seq = DOTween.Sequence();
seq.SetUpdate(true);
seq.Append(gachaImage.transform.DOMoveY(500, 0));
seq.Append(gachaText.DOFade(0, 0));
seq.Append(gachaText.DOFade(1, 1));
seq.Append(gachaText.DOFade(0, 0.5f));
seq.Append(gachaImage.DOColor(Color.white, 1));
seq.Append(gachaImage.transform.DOMoveY(-500, 1));
seq.Append(gachaImage.DOColor(Color.black, 1));
seq.OnComplete(() =>
{
Time.timeScale = 1;
this.gameObject.SetActive(false);
});
}
모든 애니메이션이 동작한 뒤 Time.timeScale을 원복하는 메서드이다.
+p.s) DoTween 에셋 기능이 아닌 , Animator의 UpdateMode를 사용하는것도 방법이다.
animator.updateMode = AnimatorUpdateMode.UnscaledTime;
3가지를 선언하여 사용하면된다.
1. 도전과제 저장/불러오기 구현 : 도전과제를 클리어했거나 업적 진척도를 Json으로 역직렬화를 하여 저장과불러오기를 구현하였음
2.플레이어 사망시 PopUp UI 비활성화 : 플레이어 사망시 동그랗게 마스크처리가 되는데 , 해당 영역에 PopUP창이 계속 유지 되는 상태였음 -> UIManager에 플레이어가 Die시 모든 PopUP UI를 비활성화 처리하는 메서드를 만들어 적용 시킴
3.수동조작 상태일때 ,플레이어 사망시 슬라이딩 하는 버그 : 플레이어 사망시에 cuyInputMove라는 vector2 변수 데이터가 남아있어 플레이어 사망 -> 게임오버 로직 처리시에 해당 변수를 0,0으로 초기화 함
4.Enemy를 대상으로 플레이어가 와라가리하는 경우 Enemy의 공격 노쿨 버그 : 플레이어가 FSM 공격 상태일때 계속 쿨타임이 초기화 되는 버그가 발생하여 , FSM 생성자에서 한번만 바로 공격할수 있게 구성한 뒤 그 이후에는 해당 공격의 쿨타임을 저장하고 있어 공격상태일때만 공격쿨타임이 줄어들게 설계함, 이렇게되면 와리가리를 해도 Enemy는 벙찌는 현상을 줄일수 있음
5.아이템 획득시, 강화 스택량이 저장할때 불러오는 데이터가 다른 버그 : 아이템 획득시 스택이 1/1 인데 , 게임 종료후 불러오기를 하면 1/3 이 되어있음 , 불러오기시 해당 변수의 데이터를 매직넘버로 넣고있었기에 발생한 버그 , 변수화 하여 데이터를 넣게 리팩토링 함
2025.01.08 작업 내용
1.이름 입력시 받는 데이터 전달 작업
2.몬스터들이 이제 플레이어를 발견하면 바로 공격을 실행합니다. (밸런스 조정 -> 코드수정 보다는 공격 애니메이션의 event Trigger 시점을 변경함)
3.스테이지에 몬스터가 등장하지 않는 버그 수정 (몬스터 수 제한 조건을 걸어놓구 초기화가 되어있지 않음)
4. UI 위치 조정 및 골드, 다이아몬드 재화 자료형 long으로 변경 -> 골드와 다이아몬드의 재화량이 21억(int) 자료형을 넘어서는 경우가 있어 더욱 큰 long 자료형을 사용하였음
우리 게임은 방치형 게임이기에 BigInteger를 사용하여 큰 정수들을 관리하고 있는데 ,
int 자료형으로는 값 감당이 되지않아 ulong으로 변경하여 사용하였다 .
1:1 전투시에는 문제가 없지만
1 : 다 전투 + 다단히트 + 광역 기술을 사용할경우 콜라이더 충돌 처리 하여 데미지 계산을 함과 동시에
BigInteger 클래스 -> ulong 으로 변경할때 너무 많은 연산처리가 일어나 유니티 크래쉬가 일어난다.
public void TakeDamage(BigInteger damage, StatHandler statHandler)
{
var maxHelth = BigInteger.ToUInt64(statHandler.CurrentStat.maxHealth);
var curHelth = BigInteger.ToUInt64(statHandler.CurrentStat.health);
var result = BigInteger.ToUInt64(damage);
if(curHelth - result > result) //unsinged 자료형이기에
{
statHandler.CurrentStat.health = 0;
}
else
{
statHandler.CurrentStat.health = curHelth - result;
}
HpUpdate();
}
BigInteger.ToUInt64를 통해 변환하는과정을 겪는데, 해당 과정의 처리비용이 정말 높다.
하지만 Int 값으로 조정될떄는 처리비용이 낮다고는 할수없지만, ulong으로 변경되며 처리비용을 생각하지 못하고
해당 코드를 그대로 작성하여 사용한 결과
유니티 크래쉬가 발생하며 , 원인이 어디서 발생했는지 찾기가 어려웠다.
찾기위해서 유니티 크래쉬가 일어나는 시점을 찾기위해 런타임 + 디버그 모드를 통해 크래쉬 발생 지점을 유추하고
해당 코드를 보면서 대략적으로 몇번의 루프문이 도는지 , 오버플로우가 일어나는지 생각해보았다.
근데 이 코드에서는 변환과정이 필요하지 않은데 억지로 변환하여 사용하고 있고 또한 그 전 메서드에서 damage 값을 처리해서 오는데 그걸 또 비교문을 통해서 동작하고 있었던것이 원인이였다.
//플레이어와 Enemy가 공동으로 사용하는 TakeDamage 메서드
public void TakeDamage(BigInteger damage, StatHandler statHandler)
{
statHandler.CurrentStat.health = statHandler.CurrentStat.health - damage;
HpUpdate();
}
해당 코드로 변경하고 나서 많은 연산처리가 줄어 정상적으로 유니티크래쉬가 발생하지 않게 되었다.
유저테스트를 통해 유저분들게 피드백을 받았고 피드백으로 많이 받은 것은
1. 투사체 오브젝트 풀링 문제
2. 스탯 UI 적용 오류 문제
3. UI/UX가 불친절하다는 문제
4. 레벨디자인이 너무 쉽다.
여러 문제점이 있지만, 4가지 문제가 주를 이뤄었다.
최종 발표까지 남은 기간동안 해당 피드백들을 개선하며 버그를 고쳐 잡아야될것같다.
2. 스탯 UI 적용 오류 문제 - > 해당 문제는 플레이어의 레벨업 수치와 아이템 및 소울 패시브로 추가되는 스탯을 분리하지 않아 일어나는 버그였다 .
BaseStat => 플레이어의 레벨업만 적용되는 스텟
CurrentStat => Item,Soul 패시브 스탯 + BaseStat
이렇게 두가지의 Stat으로 나눠 리팩토링을 진행 하였고 .
플레이어 인포 UI도 총합 스탯과 기본 스텟 두가지로 나눠 가독성을 올려주었다.
4. 레벨디자인이 너무 쉽다. -> 레벨디자인이 너무 쉬운 문제는 Enemy들의 스텟을 Stage의 넘버링에 따라 배율을 다르게
설정해놨는데 해당 수치를 올렸고 , 플레이어의 성장곡선 및 업그레이드 비용이 지수함수였으나 로그 함수로 변경하여 점점 게임을 할수록 레벨 디자인을 어렵게 만들었다.
WebGL로 빌드해서 정상적으로 게임 플레이가 되는것은 확인은했지만
저장이 될떄가 있고 안될떄가 있다.
현재 프로젝트는 Application.persistentDataPath 메서드를 사용하여 Json으로 파일을 관리하고있다.
WebGL의 저장 작업이 안되는 이유
1. WebGL에서 IndexedDB는 비동기 방식으로 작동하므로, 파일 작업이 지연될 수 있다.
2. 브라우저 간 데이터 호환성이 없으므로, 동일한 브라우저에서만 데이터가 유효하다.
즉, WebGL에서 저장 데이터를 관리하기 위해서는 약간의 시간이 걸릴 수 있다.
해당 문제를 처리하기 위해서는 WebGL 동기화 에 대해 알아보고 리팩토링을 적용 시켜보아야할것 같다 .