오늘의 학습 키워드

UI 중복 없이 랜덤 뽑기

 

공부한 내용

UI 중복 없이 랜덤 뽑기

구현해야할 것 : UI 안에 아이템을 등록해야 하는데 중복 없이 뽑으려고 한다.

시도한 것 1 : 배열을 검사하여 이미 생성했다면 continue로 다시 돌아가 반복하기

문제점 : 이미 검사한 것을 반복적으로 검사하게 되어 시도 횟수가 많아질 수 밖에 없다.

시도한 것 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 연결을 진행했다. 아직 실제 데이터가 적용(캐릭터의 투사체에 데이터 적용)은 되지 않았지만 내일 실제 데이터와 연결할 생각이다. 그리고 이후에는 각 무기 별로 발사되는 로직을 구현하도록 할 생각이다. 오늘 캐릭터와 연결하는 부분을 고민을 많이 해서 시간이 오래 걸렸지만 그래도 내일 어떻게 구현해야할지 답이 좀 나온 것 같아서 다행이다.

 내일은 추석 연휴가 시작되기 전 날인데 열심히 해서 연휴 때 여유롭게 진행했으면 좋겠다. 내일도 파이팅!

오늘의 학습 키워드

UI 팝업 관리

 

공부한 내용

UI 팝업 관리

지난 주에 계획했던 대로 UI 팝업 관리를 구현하게 되었다.

ShowPopup에서 현재 켜져있는 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 팝업과 무기 업그레이드를 맡게 되었다. 원래 맡고 싶었던 부분이던 만큼 꽤 재미있게 진행했고 이후에 레벨업 시 다른 팀원으로부터 호출될 함수도 어떻게 구현할지 구상중이다.

 내일은 무기 업그레이드 구체화와 레벨업 관련해서 팀원과 상의할 생각이다. 구현 후 시간이 남으면 다른 작업들을 더 맡아서 할 생각이다. 내일도 파이팅!

공부한 내용

Status와 장착 UI를 연동

아이템을 장착할 때 Status에 적용이 되도록 구현하였습니다.

 

테스트 영상

3일간 진행했던 기능 테스트입니다.

 

오늘의 회고

  오늘은 3일간 진행했던 과제를 제출했다. 오전에 버그도 잡고 Status와 장비 장착을 연동하기도 했다. 항상 제출 기간이 가까워질수록 더 집중이 잘 되는 것 같다. 부족한 부분도 많았지만 팀 프로젝트 맡을 때는 그 부분들을 보완해서 적용하도록 역할을 나눌 때 적극적으로 어필해야겠다.

 다음 주에는 팀 프로젝트 발제가 있다. 팀 프로젝트에서 이번 주에 아쉬웠던 점들을 모두 보완하도록 하자. 다음 주도 파이팅!

오늘의 학습 키워드

ScriptableObject

 

공부한 내용

스크립터블 오브젝트로 아이템 만들고 관리하기

아이템의 데이터를 인벤토리 UI에 넘겨 받고 인벤토리 UI에서 다른 UI로 아이템 데이터를 넘겨주기 위해 스크립터블 오브젝트를 사용하게 되었다.

인벤토리의 아이템 UI를 클릭하면 그 해당 아이템의 정보가 팝업에 넘겨지게 되며 UI가 갱신된다.

장착하면 아이템 UI에 E라는 표시가 뜨게 되고 장착을 해제하면 E가 사라진다.

 

아이템 데이터 전달에 대한 고민

고민 : 인벤토리 아이템 데이터를 팝업UI에게 넘겨주는 과정에서 아이템의 정보를 어떻게 넘겨야 할지 생각해보았다.

시도한 것 : 팝업 UI에서 현재 아이템UI를 가지고 있는 방법이다.

문제점 : 팝업 UI가 현재 아이템 정보를 가지게 되면 인벤토리와의 중복 데이터가 생기게 될 수도 있고 구조상 크게 관련 없는 객체가 다른 객체를 참조하게 된다.(굳이? 느낌)

public class UsePanel : MonoBehaviour
{
    private ItemUI _currentItemUI;
}

시도한 것 2 : 인벤토리에서 콜백으로 아이템UI에게 데이터만 넘기고 명령은 인벤토리에서 이루어지는 방법 - 구조상으로도 관리자가 시키는 방식

private void InitItems()
{
    foreach (var item in items)
    {
        ItemUI itemUI = Instantiate(itemUIPrefab, itemFrameArr[_currentIdx].transform).GetComponent<ItemUI>();
        itemUI.SetData(item.itemData);
        itemUI.OnItemClicked += ActiveUsePopup;
        _currentIdx++;
    }

    usePanel.OnUsed += OnUsedItem;
}

private void ActiveUsePopup(bool isActive, ItemUI itemUI)
{
    ItemData tempData = itemUI.Data;
    ItemData newData = InitNewData(tempData);

    itemUI.SetData(newData);
    _currentItemUI = itemUI;
    usePanel.SetCurrentItemData(itemUI.Data);
    usePanel.gameObject.SetActive(isActive);
}

 

 

오늘의 회고

  오늘은 개인 과제에서 인벤토리 관련 부분을 집중적으로 진행했다. 데이터는 어떻게 넘겨야 할지 구조에 어떻게 짜야될지에 대해서도 고민을 많이한 날이었다. 튜터님의 선발대 강의도 있었는데 UI 관련해서 팝업 시스템을 알려주셨다. 팀 프로젝트 때는 팝업 시스템을 구현해봤으면 좋겠다는 생각이 들었다. 또한 다음 프로젝트 때는 무한 스크롤뷰라는 기능을 한 번 구현해봐야겠다는 생각도 들었다.

 내일은 과제 제출날이다. 열심히 한 만큼 잘 정리해서 제출하도록 하자. 내일도 파이팅!

오늘의 학습 키워드

Raw Image, ScrollView

 

공부한 내용

캐릭터 UI에 띄우기

캐릭터를 UI에 띄우려고 하는데 저번에 업 스케일링해서 했던 것처럼 Canvas에 RawImage를 적용하는 방식으로 시도해봤다.

문제점 : UI 카메라에서 적용했던 것처럼 Flags를 Clear Depth로 하면 잔상이 남는다는 것이었다.

해결 : Raw Image를 넣는 카메라의 Flags를 Solid Color로 바꾸고 나니 UI 상에서 잔상이 사라졌다.

캐릭터 카메라를 띄우는 소스코드는 다음과 같다.

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class CharacterCamera : MonoBehaviour
{
    private Camera _characterCamera;
    [SerializeField] private Canvas characterCanvas;

    private void Awake()
    {
        _characterCamera = GetComponent<Camera>();    
    }

    private void Start()
    {
        CreateRT();
    }

    private void CreateRT()
    {
        RenderTexture rt = new RenderTexture(Screen.width, Screen.height, 24, RenderTextureFormat.Default);

        rt.Create();
        _characterCamera.targetTexture = rt;

        RawImage raw = Instantiate(characterCanvas.gameObject).GetComponentInChildren<RawImage>();
        raw.texture = _characterCamera.targetTexture;
    }
}

관련 일지 :

https://jcdevelop98.tistory.com/331

 

내일배움캠프 23일차 TIL - UpScale Sampling

오늘의 학습 키워드 UpScale Sampling 공부한 내용 UpScale Sampling 업 스케일링 샘플링 : UI는 원래 해상도로 하고 3D 씬만 낮은 해상도로 렌더링 하는 방법 - 해상도를 줄이면 픽셀이 도드라져 게임 유저

jcdevelop98.tistory.com

 

 

스크롤 뷰 사이즈 자동화

문제점 : 스크롤 뷰에서 콘텐츠 사이즈를 수동으로 정해줄 때 개수가 늘어나거나 줄어든다면 사이즈도 다시 수동으로 바꿔줘야 한다는 것이 불편했다.

내가 구현한 방법 : 먼저 배열에 자식들을 담고 아이템들을 정렬하는 그룹인 Grid Layout Group의 요소들(간격)을 가져와 아이템 사이즈와 더했다. 그 이후 아이템의 열 개수 만큼(세로 스크롤) 사이즈를 늘려주도록 했다.

이후에 게임을 시작하면 사이즈에 맞게 Height가 조절된 것을 볼 수 있다.

private void InitItemArr()
{
    int i = 0;

    foreach (RectTransform trans in _inventoryRectTramsform)
    {
        itemArr[i] = trans.gameObject;
        i++;
    }
}

private void InitContentSize()
{
    _totalItemSize = itemSpace + itemSize;
    float initYSize = (itemArr.Length / initRowSize - initColumnSize) * _totalItemSize;
    Vector2 sizeDelta = _inventoryRectTramsform.sizeDelta;
    sizeDelta.y = initYSize;
    _inventoryRectTramsform.sizeDelta = sizeDelta;
}

 

 

오늘의 회고

 오늘부터 TextRPG UI에 대한 개인과제를 진행했다. MainUI와 StatusUI까지 구현을 끝내고 Inventory 구현을 하다가 말았는데 아마 오늘 작업보다 내일 작업이 더 많을 듯 싶다. 내 욕심으로썬 스크립터블 오브젝트까지 구현해서 데이터랑 연동하는 것까지 가능하다면 좋을 것 같은데 아마 과제 제출이 이미 끝난 상태에서 적용하지 않을까 싶다. 그래도 될 수 있는한 끝까지 열심히 해볼 생각이다.

 오늘 할 게 많아서 무아지경 상태에서 한 것 같은데 내일도 이렇게만 집중해서 열심히 해보자. 파이팅!

오늘의 학습 키워드

NavMesh.SamplePosition

 

공부한 내용

NavMesh.SamplePosition

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가 더 재밌을 것 같아서 아마 그쪽으로 진행하지 않을까 싶다.

 오늘은 코드카타를 진행하면서 한 문제에서 오래 걸려서 많이 풀지 못했는데 내일은 더 많이 풀어봐야겠다. 내일도 파이팅!

 

 

참고 : 

https://docs.unity3d.com/kr/530/ScriptReference/NavMesh.SamplePosition.html

 

NavMesh-SamplePosition - Unity 스크립팅 API

Finds the closest point on NavMesh within specified range.

docs.unity3d.com

 

오늘의 학습 키워드

낮과 밤 구현 해석, 1인칭 회전

 

공부한 내용

낮과 밤

다음과 같은 코드를 해석하려고 그림을 그려가면서 그리고 유니티에서 어떻게 동작하는가 보면서 이해해보았다.

Update에서 불리는 time이 더해지면서 1로 나누는 로직에 따라 라이팅의 회전이 변하는 코드이다.

time = (time + timeRate * Time.deltaTime) % 1.0f;
lightSource.transform.eulerAngles = (time - (lightSource == sun ? 0.25f : 0.75f)) * noon * 4.0f;

가장 중요한 부분해가 뜰 때를 이해하는 것이었다.

해가 뜰 때 기준으로 6시니 time은 0.25겠고 lightSource가 sun이니 0.25f - 0.25f == 0이니까 각도가 0으로 나오게 된다.

이것만 이해해도 다른 것들이 이해가 될 것이다.

여기서 이해가 가지 않는다면 낮 12시에 해의 각도가 90도(위에서 아래로 내리쬐는 각도)라는 것을 기억하면서 그림을 봐보자.

 

 

1인칭 카메라 회전

1인칭 카메라 회전을 구현할 때 x축과 y축의 순서 또는 짐벌락 현상 때문에 고생을 한 적이 있을 것이다.

여기 예제에서는 y축 회전을 transform.eulerAngles로 부모에서 처리하고 x축 회전을 localEulerAngles로 자식에서 처리하는 것으로 각 회전이 독립적으로 수행될 수 있도록 하였다.

void CameraLook()
{
    camCurXRot += mouseDelta.y * lookSensitivity;
    camCurXRot = Mathf.Clamp(camCurXRot, minXLook, maxXLook);
    cameraContainer.localEulerAngles = new Vector3(-camCurXRot, 0, 0);

    transform.eulerAngles += new Vector3(0, mouseDelta.x * lookSensitivity, 0);
}

부모와 자식의 x,y 회전 값을 반대로 수행한다면 안 되는 것을 볼 수 있었다.

찾아보니 이것은 유니티의 회전 순서 때문인 것으로 파악이 된다.

z -> x -> y 순으로 진행해야 제대로 회전이 될 수 있다.

그래서 여기서 알 수 있는 사실은 부모 자식간의 회전은 자식이 먼저 실행 된 뒤 부모가 실행된다.

void CameraLook()
{
    camCurXRot += mouseDelta.y * lookSensitivity;
    camCurXRot = Mathf.Clamp(camCurXRot, minXLook, maxXLook);
    cameraContainer.localEulerAngles = new Vector3(0, mouseDelta.x * lookSensitivity, 0);
    
    transform.eulerAngles += new Vector3(-camCurXRot, 0, 0);
}

 

 

오늘의 회고

 오늘은 낮과 밤 시스템과 1인칭 회전에 대해서 배웠다. 낮과 밤 시스템은 처음에 이해하기 어려웠으나 전자 보드에서 쓱쓱 그려보고 대충 개념을 잡은 뒤 나중에 정리하면서 피그마로 그려봤는데 이해가 된 것 같아 다행이다. 또한 1인칭 회전은 왜 저렇게 수행하는지 이유를 몰랐었는데 이번에 정확히 알게 되어서 기분이 좋다.

 내일은 좀 더 공부를 진행한 뒤 개인 과제를 수행할 계획이다. 시간이 많지는 않아서 최대한 빨리 진행하려고 생각하고 있다. 내일도 열심히 해보자. 파이팅!

 

 

참고 : 

https://docs.unity3d.com/kr/2021.3/Manual/QuaternionAndEulerRotationsInUnity.html#:~:text=Unity%EB%8A%94%20Z%EC%B6%95%2C%20X,%EC%A2%8C%ED%91%9C%EA%B3%84%EA%B0%80%20%EB%B3%80%EA%B2%BD%EB%90%98%EC%A7%80%20%EC%95%8A%EC%8A%B5%EB%8B%88%EB%8B%A4.

 

Unity의 회전 및 방향 - Unity 매뉴얼

Unity에서는 오일러 각과 쿼터니언을 모두 사용하여 회전과 방향을 나타낼 수 있습니다. 표현은 둘 다 동일하지만 용도와 제한 사항은 서로 다릅니다.

docs.unity3d.com

 

오늘의 학습 키워드

Quaternion, LayerMask, 연산자 우선순위

 

공부한 내용

Quaternion과 벡터의 곱셈

쿼터니언과 벡터를 곱하면 다음과 같은 작업이 이루어진다.

1. 벡터를 쿼터니언으로 변환 : v = (x,y,z)인 벡터는 (0,x,y,z)의 쿼터니언으로 변환됨

2. 이렇게 변환된 쿼터니언을 원래의 쿼터니언 q와 그 켤레 쿼터니언 q'의 사이에 두고 곱셈을 수행(qvq)한다. (순서 중요 ! => 순서에 따라서 값이 달라짐)

3. 결과 쿼터니언의 (x,y,z)요소는 원래 벡터 v가 쿼터니언 q에 의해 회전한 후의 좌표를 나타냄

아래 예시는 벡터 v를 축으로 degree만큼 회전한 벡터 값이 나온다.

private static Vector2 RotateVector2(Vector2 v, float degree)
{
    // 벡터와 쿼터니언을 곱하면 벡터에서 쿼터니언으로 회전한 벡터 값이 나온다.
    return Quaternion.Euler(0, 0, degree) * v;
}

 

레이어마스크와 비트 연산과 쉬프트 연산

게임 오브젝트에는 레이어가 있는데 레이어 마스크레이어를 비트 마스크로 포맷한 정수이다.

예시로 레이어가 6이라면 레이어 마스크는 비트 연산을 통해 64가 된다.

레이어

근데 아래의 상황에서 한 가지 의문점이 생겼다. 

| 비트 연산이 << 쉬프트 연산보다 먼저 일어나면 문제가 생기는 것이 아닐까? 라는 의문이었다.

괄호를 넣더라도 안 넣더라도 결과가 같기 때문에 무슨 이유가 있을 것이라고 생각했다.

그래서 어떤 게 더 빠른지 찾아보게 되었다.

답은 연산자 우선순위라는 것이 있었고 어렴풋이 예전에 공부했던 기억이 났다.

쉬프트 연산자가  [|, &, ^] 비트 연산자보다는 빠르고 [~] 비트 연산자보다는 느리다고 한다.

levelCollisionLayer.value == (levelCollisionLayer | 1 << collision.gameObject.layer)

 

 

오늘의 회고

 오늘은 유니티 컨퍼런스를 듣는 시간이 있었다. 내가 주목해서 봤던 것은 ProBuilder나 ChatGPT를 이용해 플랫포머 기획부터 개발까지 하는 것이었다. ProBuilder는 프로토타입을 만들기에 좋은 에셋인 것 같았고 ChatGPT는 클라이언트 입장에서 기획쪽에서 도움을 받아 개발에 집중할 수 있을 것 같았다. 나중에 프로젝트를 만든다면 ChatGPT의 도움을 받는 것도 괜찮다고 생각한다.

 다음 주는 유니티 심화 개인 과제가 주어지는 주이다. 정말 배울 것이 많고 질문할 것도 많아서 엄청나게 성장할 수 있는 기회라고 생각한다. 다음 주 열심히 해보자. 파이팅!

 

 

참고 : 

http://www.tcpschool.com/codingmath/priority

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com

https://docs.unity3d.com/kr/2022.2/Manual/layers-and-layermasks.html

 

레이어와 레이어마스크 - Unity 매뉴얼

모든 게임 오브젝트는 단일 레이어에 존재하지만 API가 영향을 미치는 레이어를 설정할 수 있는 Unity API는 레이어를 직접 사용하지 않습니다. 대신 레이어마스크를 사용합니다.

docs.unity3d.com

 

오늘의 학습 키워드

Static Batching, Dynamic Batching, Gpu Instancing

 

공부한 내용

Static Batching

Static Batching은 드로우 콜을 줄이기 위한 배칭의 한 기법으로 움직이지 않는 여러 오브젝트가 같은 Material을 공유할 때 메시를 통째로 메모리에 같이 올려 한 번에 그려주는 기능이다.

특징

- 정적인 오브젝트 대상 (인스펙터에서 static 체크)

- 여러 메시를 통째로 미리 메모리에 올리기 때문에 추가적인 메모리 필요

- 런타임 전에 상태 변경 명령을 수행

- 드로우콜 감소 -> CPU 병목 완화

*드로우 콜 : CPU가 GPU에게 상태 변경 명령부터 렌더링까지 명령하는 것

Static Batching 전
Static Batching 후

 

Dynamic Batching

Static Batching은 드로우 콜을 줄이기 위한 배칭의 한 기법으로 움직일 수 있는 동일한 메쉬를 가진 여러 오브젝트가 같은 Material을 공유할 때 런타임 중에 버텍스를 모아 한 번에 그려주는 기능이다.

특징

- Skinned Mesh에는 적용 불가

- 버텍스 수가 일정 수치보다 높으면 적용 불가

- 런타임 중에 버텍스 정보를 읽어오므로 오버헤드 증가

- 드로우 콜 감소 -> CPU 병목 완화

Dynamic Batching 전
Dynamic Batching 설정
Dynamic Batching 후

 

GPU Instancing

GPU Instancing은 동일한 메시의 복사본들을 만들어 별도의 메시를 생성하지 않고 GPU에서 원본 메시를 가져다가 여러 오브젝트를 한 번에 처리해서 렌더링한다.

특징

- GPU에서 인스턴싱 처리 -> 오버헤드나 메모리 이슈에서 자유로움 (버텍스 수로부터 자유로움)

- 동일한 모양의 오브젝트들이 많이 렌더링 되어야 할 때 유용한 기법

GPU Instancing 전
Inspector 설정
GPU Intancing 후

 

Mesh.CombineMeshes

Mesh.CombineMeshes는 동일한  Material을 공유하는 메시끼리 스크립트를 통해서 합쳐주는 메서드이다.

- 런타임 동안 파츠가 조합되어 오브젝트가 만들어져야 하는 경우라면 고려할만 하다.

Mesh Combine 전
Mesh Combine 후

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을 고려해봐야겠다는 생각이 들었다.

 내일도 최적화 부분을 좀 더 공부하지 않을까 싶다. 내일도 파이팅!

오늘의 학습 키워드

CustomEditor

 

공부한 내용

CustomEditor

게임 플레이 중에 바로 변화를 보기 위해 CustomEditor를 이용하게 되었다.

왜 저번처럼 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 작성과 영상을 찍고 제출하는 것을 할 생각이다. 내일도 파이팅!

+ Recent posts