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
는 써야 한다 😋
🎉 한 장 요약
오브젝트 풀링(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
로 관리하며 사용한다면 많은 오브젝트를 비교적 가볍게 사용할 수 있다.
🎉 한 장 요약
🔨 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;
}
📌 SetActive
는 Destroy
처럼 다음 프레임에 동작하는 구조인 것 같다.
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
절대값이 아닌 음수양수로 나누어 보자.
🎉 한 장 요약
직렬화 (Serialization) :
우리의 프로그램은 시작하고 메모리를 할당받아 끝이 날 때 사라진다.
이런 휘발성 메모리를 직렬(스트림)로 잘 정리해서 데이터로 저장하는 것이직렬화
그리고 그것을 다시 프로그램으로 읽는 것이역 직렬화
이다.
📌 알게 모르게 사용해왔던 직렬화 기능들, prefab
으로 오브젝트 데이터를 파일로
저장하였고 public, SerializeField
로 인스펙터에서 수치를 조절하였다.
📌 프리팹의 컴퍼넌트 변수들을 직렬화하여 사용하면 프리팹 자체의 것들만 넣을 수있는데
직렬화한 파일에는 자기의 컴퍼넌트의 fileID 외엔 주소가 존재하지 않기 때문이다.
📌 ※ 유니티에서는 스크립터블 오브젝트(Scriptable Object)
이라는 직렬화를 지원한다.
📌 JSON
, XML
, Unity.META
등 다양한 직렬화 파일 형식들이 있다..
META
파일에는 GUID (전역 고유 식별자)
값이 들어있고 종류에 따라 설정값도 있다.
📌 스트림이란, { "_name": "Chu-Nyan", "live": true, "House": none }
한 줄이다
🎉 역직렬화까지 D-540
스크립터블 오브젝트(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. enum
은 int
형이 될 수 있기에 사용할 수 있다.
⛔ 이것으로 이벤트를 관리하는 시스템도 있는 데 아직은 잘 모르겠다.
🎉 한 장 요약
- 코딩 스타일의 변화
기존의 코딩 방식은 유니티의 구조를 배워나가기 위해 사용한 느낌이다.
덕분에 라이프 사이클같이 유니티만의 구조를 직관적으로 이해하기 쉬웠다.
하지만 이제 코드들도 점점 어려워지고 복잡해지고 TIL에 설명하기도 어려워지는 느낌이다.
- 디자인 패턴
이번 주차에서는 오브젝트 풀링
을 사용해 보았다.
코딩을 잘 몰라도 패턴들의 사용 이유를 보면 고개가 저절로 끄덕여지게 된다.
빠르게 성장하는 지름길은 선배님들의 코드를 배우며 따라 가보는 게 아닐까?
📌 기억해야 할 이번 주 키워드
이미지의 크기는 2의 배수가 최적화에 좋다
레이어와 태그의 차이 : 태그 이름표, 레이어 물리와 시스템
Vector3.Distance(A,B) : 백터 A와 B의 거리
Mathf.FloorToInt : 버리면서 int형변
Mathf.CeilToInt : 올리면서 Int형번
Quaternion.FromToRotation(신의 방향, 볼 방향)
잘 보고갑니다