본문 바로가기

유니티 개발 정보/개발 팁

Unity3D 게임 프로그래밍에서 당신이 반드시 알아야 하는 10가지

원문은 http://abitgames.com/2011/blog/10-things-to-know-programming-unity3d-games/에서 보실수 있습니다.
(* 2번 항목은 제 개인적은 판단으로는 현재 아무문제가 없는 것으로 판단되어 일부로 번역을 하지 않았습니다만, 혹시 현재까지도 2번의 항목이 유효하다면 댓글로 남겨주시면 감사하겠습니다.)



Unity3D 게임 프로그래밍에서 당신이 반드시 알아야 하는 10가지


Written on March 28, 2011 by  in Blog.
유니티3D는 게임 개발과 프로토 타입을 만드는데 굉장히 좋은 툴이다. 우리는 몇 달동안 유니티를 사용하였다. 그것은 잘 문서화 되어 있고 초보자들에게 충분한 커뮤니티가 있다.
또한 그것은 효율적인 도움을 받을 수 있는 Q&A 웹사이트를 제공하고 있다.

유니티 커뮤니티는 초보자들을 돕기 위해서 많은 것들을 하고 있지만, 아직까지 새로운 유저들에게 혼란을 줄 수 있는 자세히 문서화 되지 않은 부분들이 있다.
여기는 당신이 반드시 알고 있어야 하는 몇가지 것들이 있다:

1: my_foo_bar 대신에 myFooBar로 변수 이름 짓기


유니티는 인스펙터창(그리고 애니메이션 편집창)에 자동으로 public 변수를 노출시켜 준다. 그리고 또한 System.NonSerialized 속성을 추가하지 않으면 public 변수는 serialize된다.
당신이 인스펙터에서 게임오브젝트의 속성을 살펴볼 때, 만약 속성의 이름 가운데에 대문자를 사용할 경우, 속성의 이름이 여러 개의 단어로 나뉘는 것을 확인할 수 있다.

결과적으로, 만약 myFooBar변수를 정의한다면, 인스펙터창에 밑에 그림처럼 "My Foo Bar"라고 보여질 것이다.


나는 유니티를 사용하기전에, 나는 단어 사이에 "_"가 포함된 복잡한 변수 이름을 사용하곤 했다. 그래서 처음에는 모든 변수가 my_foo_bar처럼 보였다. 
안타깝게 Unity3D는 "_"를 인식하지 못한다. 그리고 결과적으로 "My_foo_bar"라는 이름을 보여준다.


Unity3D에서의 더 나은 경험을 위해서, 변수명을 소문자로 시작하고 그 뒤에 단어를 대문자로 짓는 것을 추천한다.

2: Be careful at operating transform.localPosition directly.


Moving a GameObject is very common in game development. In Unity3D, it seems easy as long as you add the distance to the transform.localPosition. The code below shows how to moving an object along the x-axis 10.0 unit per seconds:

transform.localPosition += new Vector3( 10.0f * Time.deltaTime, 0.0f, 0.0f );

Be careful in using this! Assume the GameObject above have a parent, and the parent’s localScale is at (2.0, 2.0, 2.0). Your GameObject will move in 20.0 unit per second. Because your GameObject’s world position equals to:


Vector3 offset = new Vector3( my.localPosition.x * parent.lossyScale.x,
                              my.localPosition.y * parent.lossyScale.y,
                              my.localPosition.z * parent.lossyScale.z );
Vector3 worldPosition = parent.position + parent.rotation * offset;

To solve the problem, Unity3D provide the Transform.Translate(x,y,z), which moves GameObject by distance without scale in different space. So the more reliable script looks like this:

transform.Translate( 10.0f * Time,deltaTime, 0.0f, 0.0f );


3: public 변수는 애니메이션 뷰에서 이용가능하다.


유니티3D는 인스펙터창에 속성을 노출시키는 좋은 메카니즘을 제공한다. 디자이너는 이 노출된 속성들을 좋아 할 것이다. 왜냐하면 그들은 어떤 코드도 읽을 필요 없이 그들을 수정할 수 있기 때문이다. 이들의 인스펙터 속성은 또한 애니메이션 뷰에서도 가능하다.

약간의 개발자들은 아마 애니메이션 뷰는 Maya나 블렌드3d같은 서드파티 애니메이션 패키지의 대체물로 생각할 수도 있을 것이다. 사실 당신은 애니메이션 뷰에서 스켈레톤을 다룰 뿐만 아니라 게임오브젝트의 public 변수또한 사용이 가능하다. 예를 들어, 시간이 지남에 따라 스프라이트의 알파값을 변경시키기 위해서 애니메이션 뷰를 사용한다면, 디자이너는 코드작성없이 페이드 인/아웃 효과를 만들 수 있을 것이다.

4: 상속은 훌륭하다, 그리고 GetComponent<base_class>()는 잘 작동한다.


유니티3D는 컴포넌트 상속을 허용한다. 이것은 MonoBehavior로 부터 확장한 foo 컴포넌트를 가질 수 있다는 것을 의미한다. 

public class foo : MonoBehaviour {
    ...}
 
public class bar : foo {
    ...}


만약 게임 오브젝트 A 그리고 B를 가지고 있으면 - A는 foo를 포함한다, B는 bar를 포함한다.
다음과 같이 작성했을 때:

foo comp1 = A.GetComponent();
bar comp2 = B.GetComponent();

우리가 예상하듯, comp1은  A에서 foo를 얻을 것이고 comp2는 B에서 bar를 얻을 것이다. bar는 foo의 서브 클래스이기 때문에,  몇몇 사람들은 기본 클래스 타입에 전달함으로써 B에서 bar를 얻는 것이 가능한지 물어 볼 수도 있다. 대답은 "예"이다. 우리는 B의 GetComponent를 다음과 같이 작성할 수 있다.

foo comp2 = B.GetComponent();

이 같은 방법으로, 당신은 comp2에 대해서 B에서 bar 컴포넌트를 얻을 수 있다. 제너릭 인터페이스를 가지는 복잡한 게임을 디자인할 때 이것을 아는 것은 좋다. 그리고 모든 것에 대해서 기본클래스를 얻을 수 있을 것이다.

5: Invoke, yield는 timeScale영향을 받는다.


게임을 일시 정지와 재개하는 것은 유니티3D에서 꽤 쉬운 방법이 아니다. 우리는 이것에 대한 완전한 해법을 찾고 있다. 보통은 게임을 멈추기 위해서 Time.timeScale을 변경하는 것이 일반적인 방법이다. 이것은 문제를 발생시킬 수 도 있는데, 왜냐하면 유니티3D에서의 많은 함수와 컴포넌트는 timeScale의 영향을 받기 때문이다. 여기 나의 현재 경계 목록이 있다:
  • MonoBehaviour.Invoke(…)
  • MonoBehaviour.InvokeRepeating(…)
  • yield WaitForSeconds(…)
  • GameObject.Destroy(…)
  • Animation Component
  • Time.time, Time.deltaTime

나는 커뮤니티에서 시간을 일시 정지할 때, 유저가 시간 기반 함수를 처리하는데 도움이 되기 위해서 Time.realtimeSinceStartup를 사용할 수 있다는 글을 보았다 . 나는 timeScale이 game의 일시정지와 재개를 위해서 디자인되었다고 생각하지 않는다. 그것은 우리가 슬로우 모션 효과를 적용하기위해서 사용할 때 더 효과적이다. 간단히 말해서 만약 게임을 일시정지하기 위해서 timeScale을 0.0으로 설정한 후, 일시 정지 메뉴를 실행시킨다면, 위에 함수들을 직접적으로 사용하지마라,  일시정지 메뉴는 절대 나타나지 않을 것이다. 왜냐하면 그것역시 일시정지 되기 때문이다.

나의 해법은 일시 정지 장면을 위해서, 실시간 yield 함수와 실시간 애니메이션 컴포넌트를 사용하는 것이다. 그것은 다음 포스트에서 보여줄 것이다.

나는 유니티3D가 컴포넌트에 affectByTimeScale 불리안 맴버 변수를 그리고 yield WaitForSecondsRealtime() 함수를 추가하기를 희망한다. 그것은 우리의 시간을 아껴주고,  더 그럴싸한 방법이다.

6: Instantiate()는 많은 양을 생성하는데 있어서 성능 문제를 발생시킬 수 있다.

유니티 유저들은 메뉴얼에서 Instantiate함수는 프리팹 또는 클론 객체로 부터 게임객체를 생성하는데 극도로 강력하다는 것을 배웠을 것이다. 그러나 메뉴얼에서 쓰여진 것이 아니다.
많은 객체를 생성이 필요한 게임 플레이를 위해서, 한번에 상당히 많은 인스턴스를 생성할 경우, 특히 iOS 플랫폼에서 Instantiate()는 성능 문제를 발생시킬 수 있다.

나는 총알을 생성하는 게임에서 Instantiate를 사용하는 것을 배웠다. 그러나 프레임 레이트는 불안정했고, 때때로 극도로 떨어졌다. 나는 이 문제를 발생시키는 것이 무엇인지 확실히 알지 못했다.(이 경우 가바지 컬렉션이 수반될 수 있다.) 그러나 나는 오프젝트 풀링을 사용함으로 써 많은 Instantiate() 호출을 피했다. 이것은 우리의 게임에 많은 성능 개선을 가져다 주었다.


7: Awake() 와 Start()의 차이


나는 처음에 Unity3D를 사용할 때 2개의 함수에 대해서 혼란스러웠다. 나는 초기화 코드를 Awake()함수 또는 Start()함수에 넣어야 하는지 잘 이해하지 못했다. 
이것을 이해하기 위해서 나는 밑에 예제를 작성했다.

player.cs:

private Transform handAnchor = null;
private bool readyToGo = false;

void Awake () 
{ 

handAnchor = transform.Find("hand_anchor");

}


void GetWeapon ( GameObject go ) 
{ 

go.transform.parent = handAnchor; 

}

void Start () 
{

readyToGo = true; 

}

other.cs:

...GameObject go = new GameObject("player"); go.AddComponent<player>(); // Awake invoke right after this! go.SendMessage( "GetWeapon", weapon_base.spawn("sword") );...


우리가 볼 수 있드시, other.cs에서 우리는 "player" 게임 오브젝트를 생성했고, player Component를 추가했다. player 컴포넌트의 Awake는  SendMessage가 실행되기 전에 실행된다.
GetWeapon가 호출되기전에  handAnchor가 할당되는 것을 보장하기 위해서이다. other.cs에서 스크립트 다음에 다음 프레임에서 Start()가 호출될 것이다.

내 생각에 객체 참조는 반드시 Awake()에 작성되어야 하고, Start()는 AI 캐릭터가 Default 상태로 들어가는 것을 작성해야 한다.

8: List<T>는 인스펙트창에 노출되지만 Dictionary<K, V>는 노출되지 않는다.


유니티3D는 인스펙터창에 float[], GameObject[]같이 내장된 배열을 노출하는 것을 가능하게 한다.  또한 List<T>같은 제네릭 클래스를 지원한다. 당신이 아마 Dictionary<K, V>또한 인스펙터창에 노출될 것이라고 생각할 지도 모른다. 불행하게도, 유니티3D에서는 문서에서 그렇지 않다고 나와있다. 명심해 두어야 한다.

당신이 디자이너가 맵같은 구조체를 편집하게끔 하고 싶다면, 나는 키-값 쌍을 저장하기 위해서 내장된 배열을 사용하길 제안한다. 이것은 사용자가 인스펙터창에 키-값 쌍을 추가/제거하게 끔 한다.그리고 배열을 불러와 Awake()에서 당신의 Dictionary에 추가한다.

9: GameObject 대신에 GameObject안에 있는 컴포넌트를 참조할 수 있다.


스크립트에서 다른 게임오브젝트를 참조할지 모른다. 우선 당신은 다음과 같이 작성한다:

GameObject otherGo = null;

그리고 인스펙터창에서 GameObject를 Other Go 속성에 드래그 할 것이다.

만약 우리가 단지 이 객체에서 foobar 컴포넌트를 얻기를 원한다면 어떨까? 물론 당신은 스크립트에서 다음과 같이 작성할 수 있을 것이다.

otherGo.GetComponent<foobar>();


이는 비효율적이다. GetComponent<T>()함수는 GameObject안에 있는 컴포넌트 리스트를 이동할 것이다. 그리고 T타입이거나 그것의 기본클래스인지를 비교한다.

성능을 개선시키기 위해서 Awake()에서 foobar를 얻을 것이다. 그러나 GameObject대신에 왜 foobar타입으로 정적변수를 선언하지 않는 것인가?

foobar otherFoobar = null;

위에 코드를 사용함으로써, 단지 우리는 인스펙터창에 같은 게임오브젝트를 드래그하기만 하면, 유니티3D는 foobar타입의 컴포넌트를 찾는 것을 도와주고 그것을 otherFoobar에 대입한다.

10: #pragma strict


이것은 오직 자바스크립트의 특징이다. 우리는 스크립트 언어로 유니티3D가 C#, 자바스크립트 그리고 Boo를 지원한다는 것을 알 것이다. C#은 엄격한 타입 언어이다. 반면에 자바스크립트는 변수를 선언하는데 있어서 덜 엄격하다.

유니티3D는 iOS 플랫폼 개발에서 엄격한 타입 선언을 사용하는 것을 요구한다. 이것은 당신이 자바스크립트를 사용할 때 유니티에서 제공하는 추가적인 문법을 사용해야한다는 것을 의미한다.
이렇게 작성해야 한다.

var foobar:String = "Hello";

밑에 코드를 사용하는 대신에

var foobar = "Hello";

그러나 iOS 플랫폼에 당신의 프로젝트를 빌드하기 전까지 당신의 문법 에러를 알아차리기란 쉽지가 않다. 다행히도 유니티3D는 컴파일러가 엄격한 타입의 자바스크립트를 사용할 수 있도록 #pragma strict 매크로를 제공한다. 단지 당신의 스크립트 맨위에 이 매크로를 입력하기만 하면, 당신의 편집기 콘솔에서 즉시 에러 메시지를 받을 수 있을 것이다.