화면 크기의 문제
모바일에서 유니티를 사용하는 경우 실제 디바이스의 크기는 제각각입니다. 예를 들어 어떤 큐브를 정확히 화면의 좌측 경계에 딱 맞게 두고 싶다면 어떻게 해야할까요?
- 화면의 2차원상의 픽셀사이즈를 얻는다.
- 0,0과 pixelWidth, pixelHeight가 3D상 어떤 좌표에 해당되는지를 찾는다.
- 큐브를 해당 위치에 정확히 위치시킨다.
개념적으로 보면 간단히 이런 3단계면 됩니다. 하지만 여기엔 복잡한 문제가 도사리고 있습니다. 그것을 하나씩 풀어가보겠습니다.
화면은 카메라가 그린 것!
결국 스크린에 보이는 모든 것은 카메라가 보고 있는 것입니다. 2D좌표가 되었든 3D좌표가 되었든 모든 열쇠는 카메라가 쥐고 있습니다. 유니티는 현재의 카메라를 참조하는 static속성을 특별한 정의 없이 사용할 수 있습니다.
private Camera _camera = Camera.main;
이 카메라는 특별한 설정이 없어도 이미 2D상의 화면 사이즈를 알고 있습니다. 다음과 같이 얻을 수 있습니다.
float screenWidth = _camera.pixelWidth; float screenHeight = _camera.pixelHeight;
2D상의 크기는 기본적으로 주어지기 때문에 처음 할 일은 3D상의 x,y좌표를 통한 3D경계상자를 설정하는 일입니다.
특정 z축의 경계상자
2D상의 화면 크기는 (0, 0)~(screenWidth, screenHeight) 까지입니다. 3D상의 경계상자는 대체 뭐라고 해야할까요?
3D상에서는 z축의 거리가 멀다면 x, y축으로 더 많이 포함될 수 있습니다. 즉 가까이에 있는 x = 100의 물체는 안보이지만 카메라로부터 멀리 떨어져있는 경우는 x = 300의 물체도 화면에 들어온다는 거죠. 따라서 카메라로 부터 z축 상 얼마나 떨어진 평면을 잘라서 x, y 축 기준의 절단면을 만든 뒤 그 절단면의 경계상자를 얻어야겠죠. 아래 그림은 이를 간단히 보여줍니다.
가장 왼쪽의 그림이 스마트폰에서 보이는화면입니다. x좌표가 300인 B만 보이고 A는 보이지 않는데 이는 카메라의 시야각 때문입니다. 이를 표현한 그림이 가운데입니다. 가장 오른쪽에는 카메라로부터 z축으로 멀리 떨어질 수록 x축 상의 더 먼 거리를 포함할 수 있다는 것을 보여줍니다. 이상에서 요점은 z축을 기준으로 경계상자를 얻어야한다는 점입니다.
2D상의 좌표를 3D상의 좌표로 변환하는 것은 카메라의 메서드인 ScreenToWorldPoint를 통해 가능합니다. 예를 들어 2D상의 좌표 x2, y2가 있고 이를 특정 z축 기반으로 변환시킨 3D상의 좌표값을 얻고 싶다면 Vector3( x2, y2, z )를 인자로 넘겨 얻을 수 있습니다. 이때 주의할 점은 z값이 절대적인 z축상의 값이 아니라 카메라와의 상대적인 거리를 의미한다는 점입니다. 이거 꼭 주의해야하는데 호스트코드에서 다시 한 번 다루겠습니다.
public Vector3 screenTo3D( float x2, float y2, float z ){ return _camera.ScreenToWorldPoint( new Vector( x2, y2, z ) ); }
이제 특정 z축을 기반으로 하는 3D상의 x,y 범위를 갖는 경계상자를 만들 수 있습니다.
public Rect bound3D( float z ){ Vector3 leftBottom = screenTo3D( 0, 0, z ); Vector3 rigthTop = screenTo3D(_camera.pixelWidth,_camera.pixelHeight,z); return new Rect( //좌상단 leftBottom.x, rigthTop.y, //가로길이, 세로길이 rigthTop.x - leftBottom.x, rigthTop.y - leftBottom.y ); }
뭐 껌이죠. 설명하게 없습니다. 이제 이를 바탕으로 cube하나를 정확히 화면에 배치해 봅시다.
//큐브 private GameObject cube; void Start(){ //카메라 z=-5 인 경우, 역전하면 5, 카메라로부터 5만큼 떨어진 곳은 z = 0인거죠! Rect bound = bound3D( -_camera.transform.position.z ); cube = GameObject.CreatePrimitive( PrimitiveType.Cube ); //좌상단으로 배치, 크기를 화면 꽉차게 cube.transform.TransformPoint( new Vector3( bound.x, bound.y, 0 ) ); cube.transform.localScale = new Vector3( bound.width, bound.height, 1 ); } void Update(){ //심심하니 돌려보자. cube.transform.Rotate( Time.deltaTime * angle ); }
대략 아래와 같이 되었나요.
결론
이 방법은 3D로 2D를 구현하는 기초적인 시스템입니다. GUI시스템이 아니라 3D객체라도 화면 상의 정확한 위치와 크기에 가변적으로 대응하게 해주는 것이죠.
전체 소스코드는 아래에서 내려받으세요.
http://blog.bsidesoft.com/unity/bside000.txt
P.S 웹서버 마인타입 추가하기 귀찮아서..(죄송..=.=;;)
하늘아이 정만군이 내 수다 상대를 해준게 큰 도움이 되었다는..