시도한 것 2 : 리스트를 할당하여 이미 생성한 것이라면 리스트에서 제거하여 중복 검출을 한다.
장점 : 이미 생성한 것 이외의 조건도 추가가 가능하여 유연하다.
private void InstantiateRandomItems()
{
List<UpgradeItem> upgradeItems = new List<UpgradeItem>(WeaponManager.Instance.WeaponPrefabs);
int count = _itemCount;
while (count > 0)
{
int index = UnityEngine.Random.Range(0, upgradeItems.Count);
bool isMax = WeaponManager.Instance.IsLevelMax(upgradeItems[index]);
if (isMax)
{
upgradeItems.Remove(upgradeItems[index]);
continue;
}
Instantiate(upgradeItems[index], weaponParent);
upgradeItems.Remove(upgradeItems[index]);
count--;
}
}
오늘의 회고
오늘은 무기 데이터 구현과 UI 연결을 진행했다. 아직 실제 데이터가 적용(캐릭터의 투사체에 데이터 적용)은 되지 않았지만 내일 실제 데이터와 연결할 생각이다. 그리고 이후에는 각 무기 별로 발사되는 로직을 구현하도록 할 생각이다. 오늘 캐릭터와 연결하는 부분을 고민을 많이 해서 시간이 오래 걸렸지만 그래도 내일 어떻게 구현해야할지 답이 좀 나온 것 같아서 다행이다.
내일은 추석 연휴가 시작되기 전 날인데 열심히 해서 연휴 때 여유롭게 진행했으면 좋겠다. 내일도 파이팅!
나는 Canvas의 SortOrder도 고려하고 싶어서 Stack보다 삽입 삭제가 자유로운 LinkedList를 사용하였고
현재 팝업이 켜져있는지 검출하기 위해 name으로 검사해서 true라면 out으로 반환한 뒤 SortOrder를 적용하게 했다.
여기서 name으로 검출하기 전에 팝업을 생성할 시기에 Instantitate를 사용하면 이름 뒤에 (Clone)이 붙으므로 Substring으로 제거해주는 작업도 진행하였다.
public T ShowPopup<T>() where T : UIBase
{
return ShowPopup(typeof(T).Name) as T;
}
public UIBase ShowPopup(string popupName)
{
if (_currentPopups.Count > 0)
{
if (IsPopupExist(popupName, out UIBase popup))
{
SetPopupFront(popup);
return null;
}
}
if (!_popupDict.ContainsKey(popupName))
{
_popupDict.Add(popupName, ResourceManager.Instance.Load<UIBase>($"UI/{popupName}"));
}
UIBase uiPopup = UnityEngine.Object.Instantiate(_popupDict[popupName]);
uiPopup.name = GetNameSubStringClone(uiPopup.name);
SetPopupFront(uiPopup);
return uiPopup;
}
private bool IsPopupExist(string popupName, out UIBase uiPopup)
{
foreach (var popup in _currentPopups)
{
if (popupName.Equals(popup.name))
{
uiPopup = popup;
return true;
}
}
uiPopup = null;
return false;
}
업그레이드 틀 구현
무기 업그레이드 부분을 맡게 되어 UI도 구현하게 되었다.
오늘의 회고
오늘은 새 프로젝트 발제가 있어 팀 기획을 진행했다. 여러가지 종류 중에 뱀파이버 서바이벌 스타일의 게임을 제작하기로 하였고 나는 UI 팝업과 무기 업그레이드를 맡게 되었다. 원래 맡고 싶었던 부분이던 만큼 꽤 재미있게 진행했고 이후에 레벨업 시 다른 팀원으로부터 호출될 함수도 어떻게 구현할지 구상중이다.
내일은 무기 업그레이드 구체화와 레벨업 관련해서 팀원과 상의할 생각이다. 구현 후 시간이 남으면 다른 작업들을 더 맡아서 할 생각이다. 내일도 파이팅!
오늘은 3일간 진행했던 과제를 제출했다. 오전에 버그도 잡고 Status와 장비 장착을 연동하기도 했다. 항상 제출 기간이 가까워질수록 더 집중이 잘 되는 것 같다. 부족한 부분도 많았지만 팀 프로젝트 맡을 때는 그 부분들을 보완해서 적용하도록 역할을 나눌 때 적극적으로 어필해야겠다.
다음 주에는 팀 프로젝트 발제가 있다. 팀 프로젝트에서 이번 주에 아쉬웠던 점들을 모두 보완하도록 하자. 다음 주도 파이팅!
오늘은 개인 과제에서 인벤토리 관련 부분을 집중적으로 진행했다. 데이터는 어떻게 넘겨야 할지 구조에 어떻게 짜야될지에대해서도 고민을 많이한 날이었다. 튜터님의 선발대 강의도 있었는데 UI 관련해서 팝업 시스템을 알려주셨다. 팀 프로젝트 때는 팝업 시스템을 구현해봤으면 좋겠다는 생각이 들었다. 또한 다음 프로젝트 때는 무한 스크롤뷰라는 기능을 한 번 구현해봐야겠다는 생각도 들었다.
오늘부터 TextRPG UI에 대한 개인과제를 진행했다. MainUI와 StatusUI까지 구현을 끝내고 Inventory 구현을 하다가 말았는데 아마 오늘 작업보다 내일 작업이 더 많을 듯 싶다. 내 욕심으로썬 스크립터블 오브젝트까지 구현해서 데이터랑 연동하는 것까지 가능하다면 좋을 것 같은데 아마 과제 제출이 이미 끝난 상태에서 적용하지 않을까 싶다. 그래도 될 수 있는한 끝까지 열심히 해볼 생각이다.
오늘 할 게 많아서 무아지경 상태에서 한 것 같은데 내일도 이렇게만 집중해서 열심히 해보자. 파이팅!
NavMesh.SamplePosition(Vector3 sourcePosition, out NavMeshHit hit, float maxDistance, int areaMask)
첫 번째 인자는 기준 위치,
두 번째 인자는 결과 위치의 정보를 담는 NavMeshHit,
세 번째 인자는 검출할 최대 거리
네 번째 인자는 NavMesh 영역의 마스크이다.
이 메서드가 하는 일은 기준 위치에서 maxDistance만큼 areaMask만 검출해서 hit으로 정보를 받아오고 hit이 존재한다면 true를 반환한다.
아래의 코드에서 왜 30번이나 while문을 돌아야 하는지 이해가 되지 않았었다.
이후에 궁금해서 공식 문서를 찾아보았고 이유를 찾게 되었다.
충돌 영역을 체크하는 것은 보편적으로 비용이 많이 들기 때문에 작은 영역을 여러 번 체크하는 것이 좋다고 나와있었다.
하지만 30번을 왜 돌아야 하는지는 아직도 의문이 있었는데 이해가 되지 않아서 튜터님께 질문을 드렸다.
그 이유는 계속 찾지 못하는 경우라면 while문을 빠져 나오지 못해 무한루프 상태에 돌입돼서 임의의 값을 지정해 주는 것이라고 하였다.
int i = 0;
while (GetDestinationAngle(hit.position) > 90f || playerdistance < safeDistance)
{
NavMesh.SamplePosition(
transform.position + UnityEngine.Random.onUnitSphere * safeDistance,
out hit,
maxWanderDistance,
NavMesh.AllAreas);
i++;
if (i == 30)
break;
}
return hit.position;
오늘의 회고
오늘까지 개인 과제 수행 전에 공부를 마치고 내일부터 개인 과제를 진행할 생각이다. 내가 생각한 것보다 약간 늦어졌지만 더 시간을 보태서 개인 과제를 마무리 할 수 있도록 노력해야겠다. UI 쪽 집중 구현과 C#으로 개발하던 TextRPG를 유니티로 만들어 보는 과제인데 TextRPG가 더 재밌을 것 같아서 아마 그쪽으로 진행하지 않을까 싶다.
오늘은 코드카타를 진행하면서 한 문제에서 오래 걸려서 많이 풀지 못했는데 내일은 더 많이 풀어봐야겠다. 내일도 파이팅!
오늘은 낮과 밤 시스템과 1인칭 회전에 대해서 배웠다. 낮과 밤 시스템은 처음에 이해하기 어려웠으나 전자 보드에서 쓱쓱 그려보고 대충 개념을 잡은 뒤 나중에 정리하면서 피그마로 그려봤는데 이해가 된 것 같아 다행이다. 또한 1인칭 회전은 왜 저렇게 수행하는지 이유를 몰랐었는데 이번에 정확히 알게 되어서 기분이 좋다.
내일은 좀 더 공부를 진행한 뒤 개인 과제를 수행할 계획이다. 시간이 많지는 않아서 최대한 빨리 진행하려고 생각하고 있다. 내일도 열심히 해보자. 파이팅!
오늘은 유니티 컨퍼런스를 듣는 시간이 있었다. 내가 주목해서 봤던 것은 ProBuilder나 ChatGPT를 이용해 플랫포머 기획부터 개발까지 하는 것이었다. ProBuilder는 프로토타입을 만들기에 좋은 에셋인 것 같았고 ChatGPT는 클라이언트 입장에서 기획쪽에서 도움을 받아 개발에 집중할 수 있을 것 같았다. 나중에 프로젝트를 만든다면 ChatGPT의 도움을 받는 것도 괜찮다고 생각한다.
다음 주는 유니티 심화 개인 과제가 주어지는 주이다. 정말 배울 것이 많고 질문할 것도 많아서 엄청나게 성장할 수 있는 기회라고 생각한다. 다음 주 열심히 해보자. 파이팅!
Static Batching은 드로우 콜을 줄이기 위한 배칭의 한 기법으로 움직이지 않는 여러 오브젝트가 같은 Material을 공유할 때 메시를 통째로 메모리에 같이 올려 한 번에 그려주는 기능이다.
특징
- 정적인 오브젝트 대상 (인스펙터에서 static 체크)
- 여러 메시를 통째로 미리 메모리에 올리기 때문에 추가적인 메모리 필요
- 런타임 전에 상태 변경 명령을 수행
- 드로우콜 감소 -> CPU 병목 완화
*드로우 콜 : CPU가 GPU에게 상태 변경 명령부터 렌더링까지 명령하는 것
Dynamic Batching
Static Batching은 드로우 콜을 줄이기 위한 배칭의 한 기법으로 움직일 수 있는동일한 메쉬를 가진 여러 오브젝트가 같은 Material을 공유할 때 런타임 중에 버텍스를 모아 한 번에 그려주는 기능이다.
특징
- Skinned Mesh에는 적용 불가
- 버텍스 수가 일정 수치보다 높으면 적용 불가
- 런타임 중에 버텍스 정보를 읽어오므로 오버헤드 증가
- 드로우 콜 감소 -> CPU 병목 완화
GPU Instancing
GPU Instancing은 동일한 메시의 복사본들을 만들어 별도의 메시를 생성하지 않고GPU에서 원본 메시를 가져다가 여러 오브젝트를 한 번에 처리해서 렌더링한다.
특징
- GPU에서 인스턴싱 처리 -> 오버헤드나 메모리 이슈에서 자유로움 (버텍스 수로부터 자유로움)
- 동일한 모양의 오브젝트들이 많이 렌더링 되어야 할 때 유용한 기법
Mesh.CombineMeshes
Mesh.CombineMeshes는 동일한 Material을 공유하는 메시끼리 스크립트를 통해서 합쳐주는 메서드이다.
- 런타임 동안 파츠가 조합되어 오브젝트가 만들어져야 하는 경우라면 고려할만 하다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// 모델링 과정에서 하나의 오브젝트로 만들어져 생성하는 것이 좋겠지만
// 같은 Material을 공유하고 파츠별로 바꿔서 조립하며 버텍스 수가 많아 다이나믹 배칭이 어려운 상황에서는
// CombineMesh도 고려할만 하다.(배치와 SetPass 감소)
public class CombineMesh : MonoBehaviour
{
void Start()
{
CombineMeshs();
}
private void CombineMeshs()
{
// 자식들의 메쉬 필터와 렌더러를 가져옴
MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>();
MeshRenderer[] meshRenderers = GetComponentsInChildren<MeshRenderer>();
// 컴바인인스턴스 배열을 메쉬 필터의 길이만큼 생성
CombineInstance[] combineInstances = new CombineInstance[meshFilters.Length];
if (CanMeshCombine(meshRenderers))
{
// 메쉬 필터, 렌더러 정보를 컴바인인스턴스에 저장하고 게임 오브젝트 다 끄기(자식)
for (int i = 0; i < combineInstances.Length; i++)
{
combineInstances[i].mesh = meshFilters[i].sharedMesh;
combineInstances[i].transform = meshFilters[i].transform.localToWorldMatrix; // 트랜스폼 로컬 좌표를 월드 좌표로 변환
meshFilters[i].gameObject.SetActive(false);
}
// 새 메쉬 필터와 렌더러 컴포넌트 추가
MeshFilter meshFilter = gameObject.AddComponent<MeshFilter>();
MeshRenderer meshRenderer = gameObject.AddComponent<MeshRenderer>();
// 메쉬 렌더러 Material 초기화
meshRenderer.sharedMaterial = meshRenderers[0].sharedMaterial;
// 새 메쉬 생성 후 메쉬 필터에 넣어놓고 메쉬를 합침
Mesh newMesh = new Mesh();
meshFilter.mesh = newMesh;
//메쉬 합치기
newMesh.CombineMeshes(combineInstances);
// 이후 오브젝트 켜주기(부모)
transform.gameObject.SetActive(true);
}
}
// Material이 같다면
private bool CanMeshCombine(MeshRenderer[] renderers)
{
Material material = renderers[0].sharedMaterial;
for (int i = 1; i < renderers.Length; i++)
{
if (material != renderers[i].sharedMaterial)
{
return false;
}
}
return true;
}
}
오늘의 회고
오늘은 팀 프로젝트 제출과 발표가 있었다. 나는 오늘 오전에 버그를 잡고 시연 영상을 찍는 작업을 했는데 시연 영상을 찍을 때마다 새로운 버그가 나와서 바로바로 고쳐서 겨우 제출할 수 있었다. 오후에는 개인 공부 시간을 가졌는데 최적화 관련해서 배칭과 드로우콜 부분을 공부하게 되었다. CPU 병목일 때 드로우콜을 낮추기 위해 static batching, Dynamic Batching, GPU Instancing을 고려해봐야겠다는 생각이 들었다.
왜 저번처럼 CustomWindow를 사용하지 않고 CustomEditor를 사용했나면 GameManager의 기능은 모든 씬 전체에 영향을 주는 것이 아니고 존재하지도 않기 때문에 이 스크립트가 존재할 때만 적용할 생각이기 때문이다.
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(GameManager))]
public class GameHelperEditor : Editor
{
GameManager gm;
private void OnEnable()
{
gm = (GameManager)target;
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
EditorGUILayout.LabelField("Helper", new GUIStyle(EditorStyles.label)
{
alignment = TextAnchor.MiddleCenter,
});
if (GUILayout.Button("AddScore100"))
{
gm.AddScore(100);
}
if (GUILayout.Button("IncreaseLife"))
{
gm.IncreaseLife();
}
if (GUILayout.Button("DecreaseLife"))
{
gm.DecreaseLife();
}
if (GUILayout.Button("GameClear"))
{
gm.Clear();
}
if (GUILayout.Button("GameOver"))
{
gm.Over();
}
}
}
오늘의 회고
오늘은 다른 팀원의 작업을 도와주면서 버그를 잡는 작업을 많이 했던 것 같다. 내일 제출이기 때문에 우리가 목표로 한 작업들까지는 버그가 없이 잘 동작해야 한다고 생각을 해서 이 부분에 집중을 했다. 그리고 CustomEditor를 사용하면서 버그가 검출이 됐는데 아 이래서 처음부터 테스트용 목적으로 에디터를 만들어 놓는구나 하는 생각이 들었다.
내일은 이번에 진행한 프로젝트의 제출일이다. 내일은 readme 작성과 영상을 찍고 제출하는 것을 할 생각이다. 내일도 파이팅!