GUITexture 는 정말 애증의 컴포넌트죠. NGUI 를 쓰던가(유료라는 함정이!), plane 에 커스텀 UV 를 입히던가(카메라가 반드시 직교투영해야 한다는 함정이!) 하면 문제는 해결될 것 같지만 돈도 없고 3D 요소도 같이 공존해야 하면 별 수 없습니다.
그냥 어떻게든 GUITexture 로 승부를 봐야하는 거죠.
하지만 너무나도 기본적인 기능만 제공하는 GUITexture 로 뭔가 할라하면 스트레스가 이만 저만이 아닙니다. GUITexture 를 완전히 개조할 수는 없는 노릇이지만 이번 포스팅에서는 간단히 기능을 추가해서 살짝 개선해보겠습니다.
OnGUI 메서드
GUITexture 컴포넌트는 OnGUI 메서드를 구상하면 완전히 작동을 통제할 수 있습니다. 반대로 OnGUI 메서드를 구상한 이상 렌더링 그 자체를 전부 책임져야 합니다.
핵심적으로 보면 결국 그림을 그리는게 목적이므로 Texture 객체가 필요합니다. Texture 객체가 없다면 그릴 필요도 없겠죠.
관련 속성을 하나 잡아주고 Texture 가 없으면 그만두게 합시다.
public class GUITextureHelper : MonoBehaviour { public Texture _texture; void OnGUI(){ if( _texture == null ) return; }
Texture 를 지정할 수 있는 메서드도 구상해 둡니다(Get, Set 은 특정한 경우에 SendMessage 에서 문제를 일으키는 경우가 발생하더군요. 정확히 경우를 한정짓지는 못했지만 그 이후 방어적으로 메서드화 시키고 있습니다)
public void texture( Texture t ){ _texture = t; }
문제는 이 Texture 를 어떻게 그릴 것인가인데 이를 위해 동원될 API 는 Graphics.DrawTexture 메서드 입니다. 이 메서드 다양한 시그니처를 갖고 있지만 그 중, 위치와 크기, 텍스쳐, UV 를 받는 인터페이스를 사용하도록 하겠습니다.
static void DrawTexture( Rect screenRect, Texture texture, Rect sourceRect, int leftBorder, int rightBorder, int topBorder, int bottomBorder );
인자를 하나하나 설명해야 겠습니다(이게 복잡하고 그지 같습니다!)
- screenRect
실제 화면에 그려질 영역을 설정합니다. 픽셀기반이고 0, 0 이 좌상단을 가리킵니다. 즉 Rect( 0, 0, 100, 200 ) 을 넘겨주면 좌상단에서부터 가로 100, 세로200 짜리 사각형영역에 그림을 그립니다. 단지 실제 모바일 환경에서 이렇게 픽셀로 직접 크기를 지정하기는 어렵습니다. 화면 사이즈가 각각이기 때문이죠. 이를 위해 추상좌표계를 변환해서 써야합니다만, 그건 또 나중에 다른 포스팅에서…^^;;
- texture
텍스쳐 객체를 넣어주면 됩니다. 이게 가장 이해하기 쉬운 인자입니다 ^^;
- sourceRect
이 인자는 엄청 중요한데 2번의 텍스쳐에서 어떤 부분을 사용할 건지 UV 좌표를 지정하는 기능입니다. 상당히 지랄 같은데 정확한 지정법을 설명하겠습니다.- 512 x 512 짜리 비트맵을 텍스쳐로 만들었다고 하죠.
- 그 중에 사용하고 싶은 부분은 ( 256, 0 ) 위치를 기준으로 ( 150, 50 ) 짜리 직사각형이라고 하면,
- Rect( 256, 512 – 50, 150, 50 ) 입니다.
- Rect의 x, y 는 0, 0 일 때 좌 하단입니다. ( 0, 512 ) 이 좌상단이죠. 세로가 아래에서 위로 올라갑니다(여기서 엄청 해맸습니다 =.=)
- 따라서 세로 기준으로 0 에서 50 만큼의 사이즈라고 표현하고 싶지만, 높이을 기준으로 뺀 값을 y 에 지정하고 높이를 넣어야하는 거죠.
- 높이도 아래서 위로 계산됩니다. 즉 512 – 50 한 지점에서 위로 50 만큼을 인식합니다.
- 마지막으로 UV좌표계는 0 ~ 1 사이의 비율로 나타내기 때문에 전부 원래 텍스쳐 크기로 나눠줘야 합니다.
- Rect( 256/_texture.width, (512-50)/_texture.height, 150/_texture.width, 50/_texture.height ) 이게 최종이죠!
- 512 x 512 짜리 비트맵을 텍스쳐로 만들었다고 하죠.
- leftBorder ~ bottomBorder
혹시 scale 9 grid 라고 들어보신적 있나요. 비트맵은 확대되면 점점 열화되는 현상을 보입니다. 픽셀정보는 한정되어있는데 알고리즘을 통해 확대하기 때문에 약간 부드러워질 뿐이지 이미지는 점점 뭉게집니다. 이 현상은 특히 외곽선 부분에서 두드러지는데 둥근 모서리로 처리된 비트맵은 확대되면 엉망으로 뭉게집니다. 이런 현상을 방지하고자 비트맵을 확대할때 각 외곽선부분의 일정 영역은 확대하지 않고 내부만 확대한뒤 원본의 외곽부분 비트맵을 그대로 붙이는 방법을 사용하는데 이를 나인그리드라고 합니다(가운데를 포함하여 9조각으로 쪼개게 되기 때문입니다.) 이 메서드에서도 이를 지원하는데 상하좌우로 일정 픽셀을 지정하면 그 픽셀은 보호받게 되는 식입니다.
이상의 인자구조를 보면 내부에 rect 두개를 갖고 있는게 유리합니다. 그 외에 각 속성에 대응하는 메서드도 같이 만들겠습니다.
public Rect _screen = new Rect(); public Rect _uv = new Rect(); public void x( float v ){ _screen.x = v; } public void y( float v ){ _screen.y = v; } public void width( float v ){ _screen.width = v; } public void height( float v ){ _screen.height = v; } public void uv( Rect v ){ v.x /= _texture.width; v.y /= _texture.height; v.width /= _texture.width; v.height /= _texture.height; }
이제 OnGUI에서 그림을 그릴 수 있습니다.
void OnGUI(){ if( _texture == null ) return; Graphics.DrawTexture( _screen, _texture, _uv, 0,0,0,0 ); }
실제로 화면에 하나 그려봅시다.
//스테이지에 빈게임오브젝트를 하나 준비해둔다. GameObject go = GameObject.Find( "EmptyObject" ); //1. GUITexture를 붙이고 go.AddComponent( "GUITexture" ); //2. 방금 만든 클래스도 붙이자. go.AddComponent( "GUITextureHelper" ); //3. texture셋팅 go.SendMessage( "texture", Resources.Load( "texture1" ) ); //4. 좌표와 크기 go.SendMessage( "x", 0 ); go.SendMessage( "y", 0 ); go.SendMessage( "width", screen.width ); go.SendMessage( "height", screen.height ); //5. uv go.SendMessage( "uv", new Rect( 256, 512 - 50, 150, 50) );
그럼 아래와 같은 결과가 됩니다.
왼쪽은 원본 jpg 이미지입니다. 512 x 512 크기로 그라디언트 부분은 바로 저 부분에 있습니다. 오른쪽은 유니티로 컴파일된 결과입니다. 제가 width 와 height 를 스크린 사이즈로 줬으니까 저 부분만 잘라서 화면 가득히 채우게 되었습니다.
iTween 과의 연동
항상 추상 클래스를 만들면 표준 클래스 기반의 라이브러리들과 연동할 수 없다는 문제가 생깁니다. 애니메이션을 걸기 위해 자신만의 애니메이션 시스템을 전부 만들게 아니라면 기존 라이브러리들이 사용하는 표준 클래스 기반으로 재작성하지 않으면 안됩니다.
iTween 의 moveTo 의 경우 gameObject.transform.position 과 연동됩니다.
따라서 도우미 클래스도 전용 x, y 를 포기하고 게임오브젝트의 x, y 를 사용하도록 개조해보죠.
/*파기한다! public void x( float v ){ _screen.x = v; } public void y( float v ){ _screen.y = v; } */ void OnGUI(){ if( _texture == null ) return; //gameObject의 위치를 이용하자! _screen.x = transform.position.x; _screen.y = transform.position.y; Graphics.DrawTexture( _screen, _texture, _uv, 0,0,0,0 ); }
이제 iTween 을 통해 x, y 를 변경하면 애니메이션이 걸립니다. 위에서 사용했던 호스트 코드에서 x, y 가 메세지 전송에서 직접 지정으로 바뀌고 iTween.moveTo 가 추가해 봅시다.
GameObject go = GameObject.Find( "EmptyObject" ); go.AddComponent( "GUITexture" ); go.AddComponent( "GUITextureHelper" ); go.SendMessage( "texture", Resources.Load( "texture1" ) ); go.SendMessage( "width", screen.width ); go.SendMessage( "height", screen.height ); go.SendMessage( "uv", new Rect( 256, 512 - 50, 150, 50) ); //좌표지정 대체 go.transform.position = new Vector3( 0, 0, 0 ); //iTween.moveTo 100,100으로 이동! iTween.moveTo( go, new Vector3( 100, 100, 0 ), 1f );
마찬가지로 scaleTo 도 이용할 수 있습니다. 원본사이즈를 _screen.width, height 로 인식해두고 localScale을 반영하면 되므로 단순히 OnGUI에 몇 줄만 추가하면 됩니다.
void OnGUI(){ if( _texture == null ) return; _screen.x = transform.position.x; _screen.y = transform.position.y; //미리 원본사이즈를 기억해둠 float w = _screen.width; float h = _screen.height; //스케일을 반영한다. _screen.width *= transform.localScale.x; _screen.height *= transform.localScale.y; Graphics.DrawTexture( _screen, _texture, _uv, 0,0,0,0 ); //원래대로 되돌림 _screen.width = w; _screen.height = h; }
이제 iTween.scaleTo 도 가능해졌습니다!
//2배로 확대 iTween.scaleTo( go, new Vector3( 2, 2, 0 ), 1f );
결론
GUITexture 는 짜증나지만 적당히 개조해서 쓰다보면 그럭저럭 쓸만합니다.
특히 OnGUI 를 커스터마이즈하고 gameObject 의 속성을 대거 연동함으로서 iTween 과 같은 녀석들도 쓸 수 있게 되었습니다.
IDE 상 에서도 대 부분의 필드를 public 으로 열어 뒀으므로 간단히 헬퍼클래스를 붙이고 패널에서 편집해 쓸 수 있게 됩니다.
소스는 아래 있습니다.
http://blog.bsidesoft.com/unity/bside003.cs
추가된 사항
인간적으로 저 UV시스템을 쓰는건 너무 지옥이었습니다. 정상적으로 0, 0 이 좌상단을 가리키게 하고 가로, 세로를 지정하게 하는 방식으로 uv메서드를 수정했습니다.
public void uv( Rect r ){ _uv.x = r.x / _texture.width; _uv.y = ( _texture.height - r.height - r.y ) / _texture.height; _uv.width = r.width / _texture.width; _uv.height = r.height / _texture.height; }
위의 y변환을 이용하면 본문에서 다룬 샘플의 경우 x = 256, y = 0 에서부터 150, 50 만큼 이란 의미로 지정할 수 있게 됩니다.
go.SendMessage( "uv", new Rect( 256, 0, 150, 50) );
Its such as you read my mind! You seem to know a lot about
this, like you wrote the e book in it or something. I think that you can
do with a few percent to drive the message home a little bit, however instead of that, this is
excellent blog. A fantastic read. I’ll definitely be back.
??? is some type of spam ??