170905 싱글턴과 정적 멤버, 게임오브젝트와 월드
게임오브젝트
기본적인 단위나 씬 안의 개체 등 모든 '것'에 대응하는 개념
게이머에게 보이게 할 때도 있고 보이지 않게 할 때도 있다.
씬 안 컴포넌트들의 집합으로 인스턴스화됨.
컴포넌트는 MonoBehaviour에서 파생한 클래스. 게임오브젝트에 붙어 동작을 변화시킴.
모든 게임오브젝트는 공통적으로 최소 1개 이상의 컴포넌트를 가지는데, transform 컴포넌트임(제거 불가)
--
컴포넌트 상호작용
다른 컴포넌트의 함수를 호출하는 방법
1. SendMessage, BroadcastMassage
장점은 컴포넌트 형식에 대해 전혀 신경쓰지 않게 함.
문제점은 모든 컴포넌트의 함수를 이름으로 부르거나, 어떤 함수도 부르지 못할 가능성이 있다.
그리고 update 등 이벤트 안에서 자주 사용시 성능 문제를 야기할 수 있다.
2. GetComponent
게임오브젝트에 붙어있는 컴포넌트 중 일치하는 첫 번째 컴포넌트에 접근.
이를 참조해 public 변수를 읽고 쓰는 것이 가능하고 public 메소드를 부를 수 있음.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | private Transform ThisTF = null; void Start() { ThisTF = GetComponent<Transform>(); // 게임오브젝트의 트랜스폼 컴포넌트에 대한 참조 값 할당 //이거 대신 그냥 ThisTF = transform; 처럼 사용할 수도 있다고 한다. } void Update() { if(ThisTF != null) { ThisTF.localPosition += Time.deltaTime * 10.0f * ThisTF.forward; //ThisTF변수를 이용해 게임오브젝트의 localposition을 직접 설정함. } } | cs |
3. GetComponents
복수의 컴포넌트를 담은 리스트가 필요할 때는 GetComponents 함수를 사용(s가 붙음에 유의)
Getcomponent는 Update 등 빈번히 불리는 이벤트에 쓰지만, Getcomponents는 Start나 Awake등 일회성 이벤트에 쓴다.
1 2 3 4 5 6 7 8 9 | //모든 컴포넌트에 대한 참조를 담는 배열 private Component[] AllComponents = null; void Start() { //현재 오브젝트의 모든 컴포넌트 리스트를 얻는다. AllComponents = GetComponents<Component>(); foreach(Component C in AllComponents) Debug.Log(C.ToString()); } | cs |
4. Invoke
일치하는 함수를 부르기 위해 존재한다.
1 2 3 4 5 6 7 8 | //함수를 호출할 오브젝트에 대한 참조 //MonoBehaviour나 이 클래스에서 파생된 모든 클래스를 지정 . public MonoBehaviour Handler = null; Void Start() { // OnSave와 일치하는 이름의 함수를 0.0f초 뒤에 호출 Handler.Invoke("OnSave", 0.0f); } | cs |
--
localPosition과 position의 차이
부모가 없는 경우엔 localPosition과 position 멤버의 값이 동일하다. 이 때의 기준은 월드의 원점이다.
부모가 있는 경우에 localPosition은 기준이 부모의 위치로 바뀐다는 차이가 보인다. position은 똑같이 월드의 원점을 기준으로 둔다.
--
게임오브젝트와 월드
스크립트에서 씬 안의 오브젝트를 검색하는 기능이 있다.
유용하지만 호출 비용이 비싸므로 Start나 Awake같은 일회성 이벤트에만 사용할 것.
게임오브젝트 찾기
GameObject.Find와 GameObject.FindObjectWithTag 함수를 이용하여 씬의 오브젝트를 찾을 수 있다.
성능상의 이유로 후자를 많이 쓴다.
1. GameObject.Find
씬 안의 일치하는 이름 중 처음으로 발견되는 것을 찾아 반환.
문자열 비교를 이용하기에 느리고, 각 오브젝트가 서로 다른 고유한 이름을 가지고 있을 때만 유효.
이름만 적절하게 자주 부르는게 아니라면 유용하게 쓸 수 있는 함수.
1 | ObjP = GameObject.Find("Player"); |
2. GameObject.FindObjectWithTag
우선 GameObject.FindGameObjectWithTag는 씬에서 일치하는 태그를 가진 오브젝트를 검색해 처음 발견되는 오브젝트를 반환한다.
그와 달리 GameObject.FindObjectWithTag는 발견되는 모든 오브젝트를 배열로 반환한다.
함수에 문자열 파라미터를 넣으나, 내부적으로 문자열을 숫자 형태로 변환해 비교 속도가 빠르다.
1 2 3 4 5 6 7 | public string tName = "Man"; public GameObject[] FoundObjs; void Start() { //일치하는 태그를 가지는 오브젝트 검색 FoundObjs = GameObject.FindGameObjectsWithTag(tName); } | cs |
//이건 FindObjectesWithTag여야하는게 아닌가 의문. 답변을 기다리자.
---
오브젝트 비교
CompareTag함수는 태그를 비교하는 함수이다.
1 2 | //현재 오브젝트와 obj 오브젝트의 bool a = gameObject.CompareTag(Obj.tag); | cs |
그리고 GetInstanceID를 이용해 두 오브젝트가 동일한 오브젝트인지 확인한다.
1 2 3 4 5 6 7 8 9 | FoundObj = GameObject.FindGameObjectsWithTag(TagName); foreach(GameObject O in FoundObj) { if(O.GetInstanceID() == gameObj.GetInstanceID()) continue; //동일한 오브젝트면 아래 코드 생략 //필요한 짓을 여기서 한다 } | cs |
---
가장 가까운 오브젝트 찾기
GameObject 형식의 주어진 배열에서 직선 거리상 가장 가까운 오브젝트를 찾는 방법.
Vector3.Distance를 이용해 씬 안의 두 점 간 최단 거리를 구해 가장 가까운 오브젝트를 찾는다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | GameObject GetNearestGameObject(GameObject Source, GameObject[] DestObjects) { //첫 오브젝트 할당 GameObject Nearest = DestObjects[0]; //최단거리 float ShortestDistance = Vector3.Distance(Source.transform.position, DestObjects[0].transform.position); foreach(GameObjecty obj in DestObjects) { //거리 계산 float Distance = Vector3.Distance(source.transform.position, Obj.transform.position); //새 거리가 최단거리면 값 교체 if(Distance<ShortestDistance) { Nearest = Obj; ShortestDistance = Distance; } } return Nearest; } | cs |
---
지정한 형식의 오브젝트 모두 찾기
컴포넌트가 어떤 오브젝트와 붙어있는지에 관계없이 씬 안에 지정된 형식의 컴포넌트 전체의 리스트를 얻길 원할 때.
Object.FindObjectsOfType 함수를 사용.
비활성화된 오브젝트를 제외하고 모든 인스턴스 리스트를 얻을 수 있음.
단, 호출 비용이 비싸므로 Start나 Awake 등에 사용
1 2 3 4 | Void Start() { Collider[] Cols = Object.FindObjectsOfType<Collider>(); } | cs |
---
게임오브젝트 간 경로 만들기
두 게임오브젝트 간에 가상의 선을 그려, 여기에 교차하는 충돌체가 있는지 검사하는 것이 가장 흔한 방법
여러 방법이 있으나 보일 방법은 Physics.LineCast 함수를 이용하는 것.,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public GameObject Man = null; //선 검출을 위한 레이어마스크 Public LayerMask LM; void Update() { //오브젝트 사이 경로가 이동이 가능한지 조사 //씬에서 어떤 레이어를 충돌 검사할지 가리키는 비트마스크 지정. if(!Physics.Linecast(transform.position, Man.transform.position, LM)) { //경로 이동 가능 Debug.Log("path clear"); } } void OnDrawGizmos() { //뷰에 디버그용 선 표시 Gizmos.DrawLine(transform.position, Man.transform.position); //두 오브젝트 자체에 충돌체가 있으면 검사에 오브젝트들도 포함된다. //그러므로 LayerMask를 이용해 특정 레이어를 포함, 제외한다. } | cs |
---
오브젝트 계층에 접근
코드 안에서 오브젝트를 처리할 일이 있을 수 있다.
오브젝트의 부모를 지정하는 방법
1 2 3 4 5 6 7 8 9 | private GameObject Child; private GameObject Parent; void Start() { Child = GameObject.Find("Child"); Parent = GameObject.Find("Parent"); Child.transform.parent = Parent.transform; //부모 지정 } | cs |
부모에 붙어있는 모든 자식을 순회하는 방법
1 2 3 4 5 6 | void Start() { //오브젝트의 자식 순회 for(int i = 0; i < transform.childCount; ++i) Debug.Log(transform.GetChild(i).name); } | cs |
---
월드/시간과 업데이트
Time클래스를 이용해 시간을 읽고 흐름을 알아낼 수 있다.
MonoBehaviour 클래스가 제공하는 이벤트. 시간에 따라 지속적으로 업데이트 된다.
1. Update
프레임 당 1번씩 Update 이벤트가 불린다. 단, 비활성화되면 활성화되기 전까지 불리지 않는다.
단, 모든 컴포넌트에 매 프레임이 불려지는지는 보장할 수 없음.
2. FixedUpdate
Update와 같이 프레임마다 여러 번 불림.
각각의 호출은 고정된 시간 간격을 기반으로 규칙적으로 표준화됨.
시간에 따라 rigidbody의 속도, 속성을 업데이트하는데 적합.
3. LateUpdate
매 프레임 호출되나, Update와 FixedUpdate가 호출된 이후에만 호출됨.
대표적으로 1인칭 카메라에서 오브젝트의 마지막 위치만 따라가는 경우 유용하게 쓰임.
참조할 것.
1. 프레임은 소중한 것이다.
Update 함수 안의 부하를 줄이지 않으면 성능이 많이 저하된다.
2. 움직임은 시간과 비례해야 한다.
플레이어들의 컴퓨터가 다 다르므로 프레임의 주기를 일정하게 보장할 수는 없다.
즉 움직임은 시간의 흐름에 따라 변경되어야 한다.
1 2 3 4 5 | public float speed = 1.0f; void Update() { transform.localPosition += trandform.forward * speed * Time.deltaTime; } | cs |
deltaTime 변수는 이전 Update 함수가 호출된 후 몇 초나 지났는지 부동소수점 단위로 표시해준다.
즉 매 프레임마다 속력에 deltaTime을 곱하여 얼마나 오브젝트를 움직여야 하는지 보여주는 셈이다.
---
소멸되지 않는 오브젝트
DontDestroyOnLoad 함수를 이용하면 파괴되지 않고 계속 유지되는 오브젝트를 쉽게 만들 수 있다.
1 2 3 4 | void Start() { DontDestroyOnLoad(gameObject); } | cs |
이후 씬 전환 시점에 파괴되는 것을 방지한다.
구체적으로 말하자면 씬을 다시 로드하거나 다시 이 씬으로 되돌아왔을 때 오브젝트가 중복해 만들어져 계속 유지된다.
허나 이렇게 하면 씬에 진입할 때마다 계속 새로운 오브젝트의 복제된 인스턴스가 생겨서 문제가 점점 커지게 된다.
그러므로 싱글턴 오브젝트를 사용한다.
---
싱글턴 오브젝트와 정적 멤버
오디오 매니저, 게임 매니저, 저장 매니저 등 유일한 독립체로 존재하는 클래스 인스턴스들을 싱글턴 오브젝트라고 부른다.
거의 대부분의 게임에는 GameManager와 GameController 클래스가 있고 거의 대부분의 경우 싱글턴 오브젝트이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | using ... //생략 public class GameManager : MonoBehaviour { public static GameManager Instance { // private 변수 instance의 참조를 반환 get { return instance; } } //static으로 함으로써 모든 인스턴스에 걸쳐 공유되는 변수가 됨 private static GameManager instance = null; public int HighScore = 0; public bool IsPaused = false; public bool InputAllowed = true; void Awake() //오브젝트 생성 시 호출 { //씬에 이미 인스턴스가 존재하는지 검사 if(instance) { //인스턴스가 존재하는 경우 이 인스턴스는 소멸 DestroyImmediate(gameObject); return; // 이렇게 여러 씬에 걸쳐 유일한 원본 인스턴스가 됨 } instance = this; // 이 인스턴스를 유일 오브젝트로 만듦 DontDestroyOnLoad(gameObject); //게임 매니저 지속화 } } | cs |
참고로 다른 클래스에서 이 GameManager에 접근하려면 이렇게 하면 된다.
1 2 3 4 | void Start() { GameManager.Instance.HighScore = 100; } | cs |
---
Awake와 Start
Awake는 Start에 앞서 호출된다
Awake는 오브젝트 생성 시에, Start는 게임오브젝트가 활성화되는 첫 프레임에 호출된다.
클래스의 지역변수에 컴포넌트 참조를 담으려면 Start보단 Awake가 좋다.
일반적으로 Start 이벤트가 일어날 땐 오브젝트에 대한 모든 지역 참조들이 이미 할당되어 유효하다고 가정된다.