TIL - 6

Chu_uhC·2023년 9월 11일
1

TIL

목록 보기
6/16
post-thumbnail

📄 23.09.11 ✍ 기존 코딩 스타일 버리기

GameObject.Find와 GetComponent<> :
오늘 강의 중 충격적인 한마디 Find쓰는 포폴은 보지도 않는다.
그 이유는 오브젝트가 수백 개가 되면 Find로 성능을 유지할 수 있는가?
이름이 바뀌면 찾을 수 있겠는가?
그리고 GetComponent<>는 생각보다 무거운 작업이다!

그리하여 오늘부터 낡은 것을 버리고 새로운 것을 찾으러 떠날 것이다.

🔨 GameObject가 아닌 Script로 오브젝트 관리하기

public GameObject _ball1;
public BallScript _ball2;

이렇게 두 개의 자료형이 있다.
이제 각각 'Rigidbody'에 접근해보자

_ball1.GetComponent<Rigidbody>();
_ball2.rigid;

Script를 불러왔을 때 우리는 그 안에 있는 함수나 변수에 접근이 가능하다.

class BallScript
{
	public Rigidbody rigid;
}

이런 식으로 구성이 되어있기에 'GetComponent'를 하지 않아도 쉽게 접근이 가능해진다.
'gameObject'로의 접근 또한 당연하게도 가능하다.

📌 찾을 수 있게 된 계기는 강의 속에 있는 한 줄의 코드에 대한 의심이다.
     디자인과 마찬가지로 고수의 작품에는 하나하나 다 이유가 있는 것이었다.

📌 당연하지만 센스껏 Find, GetComponent는 써야 한다 😋

🎉 한 장 요약



📄 23.09.12 ✍ 오브젝트 풀링

오브젝트 풀링(Object Pooling) :
오브젝트를 파괴하고 생성을 반복할 때 메모리에는 찌꺼기가 남는다.
그렇기 때문에 오브젝트를 파괴하지않고 비활성 상태로 바꾸고 웅덩이pool에 보관한다.
그리고 그 오브젝트가 필요할 때 활성 상태로 바꾸어서 재활용하는 방식이다.

public List<BallScript> balls = new();
// 웅덩이 역할을 할 리스트이다.

public void MakeBall()
{
    for (int i = 0; i < balls.Count; i++)
    {
        if (balls[i].gameObject.activeSelf == false) // 
        {
            balls[i].gameObject.SetActive(true);
            lastMakeBall = balls[i];
            return;
            
            // ball 중에서 비활성 상태인 오브젝트를 찾아서 활성화시키고
            // 변수 lastMakeBall에 할당한다. 이후 세팅은 이거와는 별개의 문제
        }
    }

    lastMakeBall = Instantiate(ball).GetComponentInChildren<BallScript>();
    lastMakeBall.name = "Ball";
    balls.Add(lastMakeBall);
    // 없는 경우 새롭게 인스턴스를 만들어서 웅덩이에 넣는다.
}

📌 메모리뿐만 아니라 객체를 생성하고 파괴할 때 들어가는 자원들도 아낄 수 있다.

📌 재활용을 할 때는 반드시 초기 세팅으로 바꿔주어야 한다.
     하지 않는다면 온갖 버그들이 넘쳐날 것이다. 라이프 사이클로 관리하는 것도 하나의 방법

📌 전날 TIL 주제인 Script로 관리하며 사용한다면 많은 오브젝트를 비교적 가볍게 사용할 수 있다.

🎉 한 장 요약



📄 23.09.13 ✍ 코드 최적화해보기

🔨 1. 어제 만든 오브젝트 풀링 (빈 오브젝트를 찾는 알고리즘)

원안. 'for'문을 이용하여 탐색하여 찾는 방법
for (int i = 0; i < balls.Count; i++)
{
	if (balls[i].gameObject.activeSelf == false) 
	{
		balls[i].gameObject.SetActive(true);
		lastMakeBall = balls[i];
		return;
    }
}

수정안. 오브젝트가 비활성화될 때 'Queue'에 쌓고 꺼내는 방식
if (_disabledBalls.Count != 0)
{
    _lastMakeBall = _disabledBalls.Dequeue();
    _lastMakeBall.gameObject.SetActive(true);
    return;
}

📌 SetActiveDestroy처럼 다음 프레임에 동작하는 구조인 것 같다.
     Queue에 넣고 비활성화되기 전에 다시 사용되는 것이 걱정이긴 했지만
     어차피 꺼내고 활성화해준다 ㅋ..

📌 Queue를 쓸 수 있었던 이유는 모두 같은 프리팹들이기 때문이다.

✅ 리스트에 있던 참조형 타입을 다른 배열에 옮기면 메모리는 어떻게 되는가?
     내가 쓴 Queue에서 메모리는 추가로 할당되는가?
     내가 아는 지식에서는 추가는 없는 것이 맞는데 오피셜을 들어보고 싶다


🔨 2. -60° ~ 60° 에서 반복하기

원안. 각도를 넘어가면 bool값이 true & false로 바뀌면서 방향이 전환된다.
if (reverseRotation == false)
{
	tagetBall.transform.Rotate(rotateSpd * Time.deltaTime * Vector3.forward);
	if (tagetBall.transform.rotation.z >= 0.6)
		reverseRotation = true;
}
else if (_reverseRotation == true)
{
	tagetBall.transform.Rotate(rotateSpd * Time.deltaTime * Vector3.back);
	if (tagetBall.transform.rotation.z <= -0.6)
		reverseRotation = false;
}

수정안. 절대값과 -1을 이용한 스위치
if (Mathf.Abs(tagetBall.transform.rotation.z) > 0.6)
	rotateSpd *= -1;

tagetBall.transform.Rotate(_rotateSpd * Time.deltaTime * Vector3.forward);

📌 bool값을 이용하면 각각 +-180도 까지는 버그가 발생하여도 정상 작동이 된다.

📌 절대값을 이용하는 방식은 프레임에따라 불안정하게 작동될 수도 있다.

✅ 원안은 단순한 기능치고 복잡한 코드이고 수정안은 불안정하고 더 좋은 방법은 없을까?
     수정안의 if 절대값이 아닌 음수양수로 나누어 보자.

🎉 한 장 요약



📄 23.09.14 ✍ 직렬화

직렬화 (Serialization) :
우리의 프로그램은 시작하고 메모리를 할당받아 끝이 날 때 사라진다.
이런 휘발성 메모리를 직렬(스트림)로 잘 정리해서 데이터로 저장하는 것이 직렬화 그리고 그것을 다시 프로그램으로 읽는 것이 역 직렬화이다.

📌 알게 모르게 사용해왔던 직렬화 기능들, prefab으로 오브젝트 데이터를 파일로
     저장하였고 public, SerializeField로 인스펙터에서 수치를 조절하였다.

📌 프리팹의 컴퍼넌트 변수들을 직렬화하여 사용하면 프리팹 자체의 것들만 넣을 수있는데
     직렬화한 파일에는 자기의 컴퍼넌트의 fileID 외엔 주소가 존재하지 않기 때문이다.

📌 ※ 유니티에서는 스크립터블 오브젝트(Scriptable Object)이라는 직렬화를 지원한다.

📌 JSON, XML, Unity.META 등 다양한 직렬화 파일 형식들이 있다..
     META파일에는 GUID (전역 고유 식별자)값이 들어있고 종류에 따라 설정값도 있다.

📌 스트림이란, { "_name": "Chu-Nyan", "live": true, "House": none } 한 줄이다

🎉 역직렬화까지 D-540



📄 23.09.15 ✍ 스크립터블 오브젝트

스크립터블 오브젝트(Scriptable Object) :
유니티에서 지원하는 직렬화 기능 중 하나이며 대표적인 장점으로는 1. 값의 사본이 생기는 걸 방지하여 메모리 사용을 줄임과 씬이 넘어가도 2. 데이터는 디스크에 유지됨으로써 클린 슬레이트,임시 데이터 없이 새로운 씬을 사용 할 수 있다.

🔨 만들고 사용하는 방법

[CreateAssetMenu(menuName = "DB/Unit", fileName = "UnitData", order = 1)]
public class UnitData : ScriptableObject
{
    public UnitType _type;
    public string _name;
    public string _description;
    public int _maxHp;
    public int _hp;
	
    public void MonsterValue(Unit unit)
    {
        unit._description = _description;
    }
}
// menuName : 'Assets->Create'항목에 추가할 이름
// fileName : 추가할 시 기본 이름 // order : 메뉴 순서
// 'ScriptableObject'는 'MonoBehaviour'와 관련 없지만 'object'를 똑같이 상속

1. 에셋으로 편리하게 추가하기위해 메뉴에 추가해줘야 한다.
2. 'ScriptableObject'를 상속 받아야한다.
3. 스크립트를 이용하여 접근하기에 'public'을 사용 해줘야한다.(함수도 사용 가능)
4. 'Assets->Create'로 추가해주고 값을 수정해주면 끝!

📌 1. 값의 사본이 생기는 걸 방지하여 메모리 사용을 줄임

public class Unit : MonoBehaviour
{
    [SerializeField] UnitData data;
    // 불러올 'UnitData' Asset을 선택

    // 1. 메모리의 사본을 방지하고 메모리의 사용을 줄이는 방법
    public int MaxHp { get { return data._maxHp; } }
    public int _hp;
    void Start()
    {
        _hp = data._maxHp;
    }
}
- MaxHP의 경우 data의 값을 직접 불러오기에 추가적인 메모리 할당이 필요 없다.
- hp는 data._maxHp의 값을 할당하기에 복사되고 메모리에 할당된다.

📌 2. 데이터는 디스크에 유지됨

public string _name { get { return data._name; } set { data._name = value; } }
- 'data'를 직접 읽고 쓰기에 씬이 넘어가도 디스크의 값을 유지를 할 수 있다.

📌 3. enumint형이 될 수 있기에 사용할 수 있다.

⛔ 이것으로 이벤트를 관리하는 시스템도 있는 데 아직은 잘 모르겠다.

🎉 한 장 요약

📔 주간 결산

  1. 코딩 스타일의 변화

기존의 코딩 방식은 유니티의 구조를 배워나가기 위해 사용한 느낌이다.
덕분에 라이프 사이클같이 유니티만의 구조를 직관적으로 이해하기 쉬웠다.
하지만 이제 코드들도 점점 어려워지고 복잡해지고 TIL에 설명하기도 어려워지는 느낌이다.

  1. 디자인 패턴

이번 주차에서는 오브젝트 풀링을 사용해 보았다.
코딩을 잘 몰라도 패턴들의 사용 이유를 보면 고개가 저절로 끄덕여지게 된다.
빠르게 성장하는 지름길은 선배님들의 코드를 배우며 따라 가보는 게 아닐까?

📌 기억해야 할 이번 주 키워드

이미지의 크기는 2의 배수가 최적화에 좋다
레이어와 태그의 차이 : 태그 이름표, 레이어 물리와 시스템
Vector3.Distance(A,B) : 백터 A와 B의 거리
Mathf.FloorToInt : 버리면서 int형변
Mathf.CeilToInt : 올리면서 Int형번
Quaternion.FromToRotation(신의 방향, 볼 방향)
profile
ChuNyan

1개의 댓글

comment-user-thumbnail
2023년 9월 11일

잘 보고갑니다

답글 달기