정체불명의 모모

[유니티C# 스크립팅 마스터하기] - 3챕터. 싱글턴과 정적멤버, 게임 오브젝트와 월드 본문

프로그래밍(C#) /유니티공부

[유니티C# 스크립팅 마스터하기] - 3챕터. 싱글턴과 정적멤버, 게임 오브젝트와 월드

정체불명의 모모 2022. 5. 10. 16:58

이번장에는 작업을 하는데 알아야 하는 함수들이 많이 나온다.

유니티 스크립트를 능수능란하게 다루려면

씬과 오브젝트, 오브젝트간의 통신이 어떻게 일어나는지에 대한 구조를 이해하는것이 중요 하다!

라는게 이번장 Point ! 

그럼 어떻게 오브젝트 간에 통신이 이뤄지는지 알아 봅시다!

그리고 오브젝트를 만들고 이것이 옮겨질 때에도 데이터를

유지하도록 만들어주는 정적 멤버와 싱글턴등의 개념에 대해서도 봅시다.

(이미.. 대부분들 아실거라고 생각 합니다.)

그럼... gogo!


게임 오브젝트 

: 게임 오브젝트는 기본적인 단위나 씬 안의 개체 등 여러 가지 의미를 가진다.

  게임오브젝트는 일상에서의 모든 '것'에 대응하는 개념이다.

  컴포넌트는 기본적으로 MonoBehaviour에서 파생한 클래스로서 씬 안의 게임오브젝트에 붙어 동작을 변화시킬 수 있다.

  모든 게임오브젝트는 공통적으로 적어도 하나의 컴포넌트를 가지고 있는데, 바로 트랜스폼 컴포넌트이며 이 컴포넌트를 

  제거할 수 있다. (트랜스폼 컴포넌트는 오브젝트의 위치, 회전, 스케일 값을 가지고 있다.)

  

  오브젝트들은 씬 계층에 정의된 대로 서로 다른 오브젝트와 중요한 관계를 맺고 있다.

  오브젝트는 다른 오브젝트와 서로 부모-자식 관계가 될 수 있다.

  자식인 게임오브젝트는 항상 부모에 상대적인 위치와 변환을 가지게 된다.


컴포넌트 상호작업

◻︎ GetComponent

: 데이터 형식을 알고 있는 특정한 컴포넌트 하나에 직접 접근할 때 사용한다.

  이 함수는 게임오브젝트에 붙여진 컴포넌트 중 일치하는 첫 번째 컴포넌트에 접근하도록 해준다.

 

◻︎ GetComponents(여러 개의 컴포넌트에 접근)

: 때로는 전체 컴포넌트의 리스트나 특정 형식의 컴포넌트 리스트처럼 복수의 컴포넌트를 담은 리스트가 필요할 수 있다.

  (비용 많이 들어 Start나 Awake 와 같이 일회성 이벤트에서 호출한다.)

 

◻︎ 그 외의 컴포넌트 함수들

: GetComponmentsInChildren 과 오브젝트의 부모에 존재하는 모든 컴포넌트를 가져오는

  GetComponentInParent가 포함한다.


컴포넌트와 메세지

: GetComponent류의 함수들은 휼륭하게 동작하며 컴포넌트 간 통신에 필요한 거의 모든 기능을 충족시킨다.

  이 함수들을 적합하게 사용하면 SendMessage 나 BroadcastMessage 함수에 비해 분명히 더 나은 동작을 보여준다.

  하지만 오브젝트들에게 통적으로 불러와야 하는 함수가 있는 경우에 SendMessage, BroadcastMessage 를 이용하여 

  함수를 불르도록 처리 하는것이 좋습니다.

 

- invoke 메소드는 일치하는 이름을 가진 함수를 실행하기 위해 호출 합니다.

  다만, 두번째 파라미터는 몇 초 후에 함수가 불릴지 시간을 초로 지정하는 것입니다.(시간을 0으로 하면 즉시 불리게 된다.)


게임오브젝트와 월드

: 유니티의 또 다른 중추적인 기능으로 스크립트에서 씬 안의 오브젝트를 검색하는 기능이 있습니다.

  이 함수들은 유용하지만 호출 비용이 비싸므로 Start나 Awake 와 같은 일회성 이벤트에서 부르도록 해야 한다.

 

◻︎ 게임 오브젝트 찾기 ( GameObject.Find / GameObject.FindObjectWithTag )

: 위 함수를 이용해서 씬의 오브젝트를 찾을 수 있습니다.

  대부분 후자의 함수가 성능상의 이유로 거의 선호되는 편 입니다.

 

1. 오브젝트 찾는 속도

 

• GameObject.Find -> 문자열 비교를 통해 오브젝트를 찾음

• GameObject.FindObjectWithTage -> 문자열을 숫자 형태로 변환해 태그 비교 속도 향상

 

그렇기 때문에 GameObject.FindObjectWithTag를 선호(속도가 빠름)

 

2. 오브젝트 찾는 방법

 

• GameObject.Find -> 씬 안에 있는 모든 오브젝트를 비교하며, 그 중 처음으로 일치하는 이름을 가진 오브젝트를 반환

  - 단점 : 각 오브젝트가 고유한 이름을 가지고 있을때만 유효하다. 만약 같은 이름의 오브젝트가 있다면,

    Hierarchy상 첫번째 오브젝트가 반환된다.(무조건 첫번째 오브젝트)

 

• GameObject.FindObjectWithTage -> 씬 안에서 일치하는 태그를 가진 오브젝트를 검색해 처음 발견되는 오브젝트 반환

   위 함수는 발견되는 모든 오브젝트를 배열로 반환한다.

◻︎ 오브젝트 비교 

• 두 오브젝트의 태그를 비교 하고 싶을 경우 -> CompareTag

bool bMatch = gameObject.CompareTag(Obj_Y.tag);

• 두 오브젝트가 동일한 오브젝트인지 비교 하고 싶은 경우 -> GetInstanceID

// 일치하는 태그를 가진 오브젝트를 찾는다
FoundObjects = GameObject.FindGameObjectsWithTag(TagName);

// 모든 오브젝트를 검색하고 자신은 제외한다
foreach(GameObject O in FoundObjects)
{
    // 두  오브젝트가 동일한 오브젝트인 경우
    if(O.GetInstanceID() == gameObject.GetInstranceID())
       continue;	// 반복문의 순회를 생략 한다.
 }

◻︎ 가장 가까운 오브젝트 찾기 (Vector3.Distance)

: 오브젝트들 간에 직선거리상 가장 가까운 오브젝트를 찾아 내는 방법은 Vector3.Distance 함수를 이용해 씬 안의 

  임의의 두 점 간의 최단 거리를 구해 가장 가까운 오브젝트를 찾는 방법 입니다.

// 가장 가까운 게임오브젝트를 반환한다.
GameObject GetNearestGameObject(GaemObject Source , GameObjectp[ DesObjects)
{
	// 첫 번째 오브젝트를 할당한다.
    GameObject Nearest = DestObjects[0];
    
    //최단 거리
    float ShortestDistance = Vector3.Distance(Source.transform.position,
    						DestObjects[0].transform.position);
                            
   // 모든 오브젝트를 순회한다.
   foreach(GameObject Obj in DestObjects)
   {
   		// 거리를 계산한다.
        float Distance = 
          Vector3.Distance(Source.transform.position,
              Obj.transform.position);
              
        // 계산한 거리가 가장 짧다면 업데이트한다.
        if(Distance < ShortestDistance)
        {
        	Nearest = Obj;
            ShortestDistance = Distance;
        }
   }
   // 가장 가까운 오브젝트의 참조를 반환한다.
   return Nearest;
  }

◻︎ 지정한 형식의 오브젝트 모두 찾기 ( Object.FindObjectsOfType )

: Object.FindObjectsOfType 함수를 호출 하면 오브젝트가 비활성화 되지 않은 이상, 씬 안에서 지정한

  오브젝트의 모든 인스턴스 리스트를 얻을 수 있습니다.

  이 함수는 호출 비용이 비싼 관계로, Start나 Awake 와 같이 드물게 일어나는 이벤트를 이용하는것이 좋습니다.

void Start()
{
    // 씬 안의 모든 충돌체의 리스트를 얻을 수 있다.
    Collider[] Cols = Objects.FindObjectsOfType<Collider>();
}

◻︎ 게임오브젝트 간 경로 만들기

: Physics.LineCast 함수를 이용하여, 플레이어와 적 캐릭터 처럼 두 게임 오브젝트 간에 그리는 가상의 선을 통해

  교차하는 충돌체가 있는지 검사하는 경로를 만드는 흔한 방법 입니다.

 


◻︎ 오브젝트 계층에 접근

: 트랜스폼 컴포넌트를 통해 오브젝트를 다른 오브젝트의 자식으로 붙일 수 있는지를 보여준다.

부모에 붙어 있는 모든 자식을 순회하는 방법을 알아 봅시다.(transform.GetChild)


월드 / 시간과 업데이트 

: 유니티의 씬은 같은 3D 공간에 존재 하면서 같은 시간을 공유하는 유한한 수의 게임오브젝트의 집합을 표시하는 개념입니다.

  애니메이션을 동기화 및 변경하기 위해 모든 게임엔 통일된 시간 개념을 정할 필요가 있습니다.

  그러기 위해 유니티에서는 Time 클래스를 이용해 시간을 읽고 시간의 흐름을 알아 낼 수 있습니다.

  이 클래스를 이용하는 것은 예측 가능하고 일관성 있는 움직임을 게임에 구현하기 위한 중요한 기술 입니다.

 

  유니티의 모든 MonoBehaviour 클래스가 제공하는 세 가지 클래스 이벤트를 통해 프레임의 개념과 비슷한 방식으로 시간에

  따라 지속적으로 업데이트 되도록 구현 할 수 있습니다. 

 

➣ Update : 이벤트는 씬안의 모든 활성화된 게임  오브젝트의 모든 컴포넌트에 대해 프레임당 한 번씩 Update 이벤트가

     불리게 됩니다. 오브젝트가 MonoBehaviour.SetActive 메소드에 의해 비활성화 되면 활성화 되기 전까지는 

     Update 이벤트가 호출되지 않습니다.

     반복적인 동작이나 업데이트, 키보드 눌림이나 마우스 클릭을 확인하는 등의 모니터링 기능이 필요할 때 유용 합니다.

      다만, Update 이벤트가 모든 컴포넌트에 매 프레임 마다 불려지는 것이 보장되지 않습니다.

 

➣ FixedUpdate : Update 와 같이 이 이벤트는 프레임 마다 보통 여러 번 불리게 된다. 하지만 각각의 호출은 고정된

     시간 간격을 기반으로 규칙적으로 표준화 되어 일어납니다. 

     FixedUpdate를 가장 널리 사용하는 경우는 유니티의 물리 기능을 사용 할 때 입니다.

     시간에 따라 Rigidbody 컴포넌트의 속도나 속성을 업데이트 하는 경우 Update 보다 FixedUpdate가 적합 합니다.

 

➣ LateUpdate : Update 와 마찬가지로 매 프레임 호출 됩니다. 하지만 LateUpdate는 언제나 Update와 FixedUpdated가

     호출된 이후에 호출됩니다. 1인칭 카메라를 구현하는 경우, 현재 프레임에서 항상 오브젝트의 마지막 위치를 따라가도록 움직임을 

     업데이트할 때 LateUpdate의 이런 속성이 유용하게 이용됩니다.

 

 

☑︎ 규칙 1 : 프레임은 소중한 것이다.

: 매 프레임마다 씬 안의 모든 활성화된 MonoBehaviour 컴포넌트에 Update 이벤트가 호출됩니다.

  Update 이벤트 안에서 하는 일들이 매 프레임 해당 씬을 처리하는 계산 복잡도에 큰 영향을 미친다는 말 입니다.

  기능이 많을 수록 CPU 와 GPU 모두에  더 많은 처리 시간과 더 큰 부하를 일으킵니다. 

  신중한 계획을 통해 Update 함수 안의 부하를 줄이지 않으면 많은 오브젝트와 컴포넌트를 가지는 큰 씬을 감당하기 어려워 집니다.

  Update나 프레임 기반으로 규칙적으로 호출되는 이벤트를 아껴쓰는것이 중요합니다.

 

방법 : 이벤트 주도적 프로그래밍을 고려하는 것이 Update 함수 안에 들어가는 부하를 크게 줄이는데 도움이 될 것 입니다.

 

☑︎ 규칙 2 : 움직임은 시간과 비례해야 한다.

: 프레임 레이트에 의존적이면 컴퓨터 사양에 따라 보이는것이 달라 보입니다.

  그렇기때문에 움직임을 프레임이 아닌 시간에 비례하도록 해야 합니다.

  프레임은 가변적이지만 시간은 일정하기 때문입니다. 

  Time클래스의 멤버인 deltaTime 변수를 이용하면 일정한 움직임 구현이 가능해 집니다.

public class Mover : MonoBehaviour
{
	// 육면체의 속력
    public float Speed = 1.0f;
    
    // Update는 매프레임 마다 한번씩
    void Update()
    {
    	// 육면체를 앞쪽 방향으로 속력에 따라 움직인다.
        transform.localPosition += transform.forward * Speed * Time.deltaTime;
    }
 }
  deltaTime 변수는 부동소수점 값으로서 항상 이전 Update 함수가 호출된 이후로 시간이 얼마나 지났는지를 초 단위로 표시합니다.
  이런 특성으로 인해 deltalTime을 배율로 해서 곱할 수 있으므로 유용하게 이용할 수 있습니다.
   '거리 = 속력 x 시간' 이므로 매 프레임마다 속력에 deltaTime을 곱함으로써 얼마나 멀리 오브젝트를 움직여야 하는지 알 수 있습니다.

소멸되지 않는 오브젝트( DontDestroyOnLoad )

: 씬 마다 파괴되지 않길 원하는 오브젝트가 있을 수 있습니다. 

  플레이어 캐릭터나 최고 점수를 표시하는 시스템, 게임 매니저 클래스와 같이 , 바뀐 다른 씬에까지 계속 이어지는 오브젝트가 

  필요 할 수 있습니다.

  이럴때 여러 씬을 넘나 들을 수 있게 DontDestroyOnLoad 함수를 이용해서 계속 유지되는 오브젝트를 쉽게 만들 수 있습니다.

ㅁ 씬 전환하기
: 유니티에서 활성화된 씬을 전환하려면 Application.LoadLevel 함수를 사용한다.
  LoadLevelAsync , LoadLevelAdditive , LoadLevelAdditiveAsync와 같은 다른 버전의 함수들도 있습니다.

싱글턴 오브젝트와 정적 멤버

: 하나의 클래스 인스턴스만 유일하게 존재하게 되며 절대 그보다 많은 인스턴스를 만들진 않습니다.

  하나 보다 많은 인스턴스를 가지는 것은 모순적인 부분이 발생하거나 오브젝트의 역할을 망가뜨리고 어떤 면에서는 쓸모없어질 수도 

  있습니다.

  이런 종류의 오브젝트를 싱글턴(sigleton)이라고 합니다. 

  싱글턴은 대개 여러 씬에 걸쳐 지속적으로 유지되는 오브젝트 입니다.

  싱글턴으로 만드는 기보적인 요소는 항상 메모리에 클래스의 인스턴스가 하나만 존재한다는 것 뿐 입니다. 


Comments