지난 몇 년간 모바일 장치용 첨단 게임이 그 어느 때보다도 많이 출시되었습니다. 이제 3D 그래픽은 최첨단 장치뿐만 아니라 여러 가지 미드레인지 모바일 전화에도 사용되고 있습니다.
자바 ME를 주로 사용하는 게임 개발자들이 3D 그래픽을 제작할 때 사용할 수 있는 API는 모바일 3D API(JSR-184)와 자바 ME용 OpenGL ES 바인딩(JSR-239)의 두 가지입니다. 모바일 3D API는 기본적으로 자바 SE에서 사용하던 자바 3D API에 해당하는 자바 ME이고, JSR-239는 Khronos Group이 관리하는 OpenGL ES 표준의 자바 구현입니다(표준 OpenGL API의 자바 SE 구현인 JOGL도 있음).
JSR-239는 JSR-184보다 널리 통용되는 표준이고 새로운 3D 그래픽 칩에 더 잘 맞기 때문에 장기적으로 보다 유리한 선택이라고 생각됩니다. Java3D와 JOGL을 비교할 때도 이와 동일한 현상이 있었습니다. 즉, 개발자들이 확장하거나 새로운 그래픽 기능을 사용하기가 너무 힘들다는 이유로 자바 3D를 포기한 것입니다.
3D 그래픽을 사용하면 대단히 멋진 게임을 만들 수 있습니다. 그러나 바이너리 방식의 버튼보다 더 민감한 컨트롤을 사용하여 게임을 조정할 수 없다면 게임의 즐거움은 제한될 것입니다. 터치스크린은 게임의 재미를 더하는 한 가지 방법입니다. 그러나 제 생각에는 그보다 더 성공적인 결과를 얻을 수 있는 솔루션이 하나 있습니다.
현재 출시되어 있는 모바일 장치 중 일부에는 닌텐도 위 컨트롤에 사용된 것과 같은 가속도계가 내장되어 있습니다. 이를 사용하여 장치의 현재 경사도와 움직임을 감지할 수 있습니다. 이 가속도계의 피드백은 거의 실시간으로 이루어지므로, 썬 장치와 상호 작용하는 새로운 방식이 될 수 있습니다.
가속도계를 각종 애플리케이션과 게임에 사용하기 시작한 지는 얼마 되지 않았으며, 새로운 모바일 게임들이 출시됨에 따라 모바일 전화에 내장된 가속도계를 사용하는 사례가 점점 늘어날 것으로 생각됩니다. 자바 ME의 경우, 이러한 가속도계를 읽기 위한 API인 모바일 센서 API(JSR-256)가 이미 마련되어 있습니다. 이 기사에서는 자바 ME용 OpenGL ES API와 모바일 센서 API를 모두 사용하는 간단한 소니 에릭슨 w910i용 게임을 소개하겠습니다.
이 게임은 앞으로 곧장 날아가면서 다가오는 소행성과의 충돌을 피해야 하는 단순한 스페이스 게임입니다. 사용자는 원하는 방향으로 전화기를 기울여 우주선을 조종합니다. 제가 3D 전문가도 아니고 심각한 색맹이기 때문에 그래픽이 매우 조잡합니다만, 이런 API를 함께 사용할 때의 가능성은 그런대로 알아보실 수 있을 겁니다. 모든 게임 코드는 https://mobilespaceracer.dev.java.net/에서 다운로드할 수 있습니다. 이 코드를 마음껏 시험하고 원하는 대로 사용하십시오.
목차
| - | OpenGL ES |
| - | 모바일 센서 API |
| - | 게임 루프 |
| - | 결론 및 미래를 위한 아이디어 |
| - | 자료 |
| - | 저자 정보 |
OpenGL ES는 Khronos Group에서 관리하는 표준으로, Khronos Group은 이 밖에 몇 가지 다른 그래픽 및 오디오 표준(OpenGL, OpenAL)도 관리합니다. OpenGL ES는 표준 OpenGL API를 기반으로 합니다. 따라서 OpenGL에 익숙한 사용자라면 아래의 코드는 대부분 새롭지 않을 것입니다. 자바 ME로 구현한 OpenGL ES는 표준 OpenGL 버전 1.5를 기반으로 한 버전 1.1을 지원합니다. OpenGL ES 버전 2.0도 있으나 이 버전의 경우 필요한 하드웨어가 훨씬 많으며 현재로서는 썬의 모바일 전화에 적합하지 않습니다.
3D 프로그래밍에 대해 여기서 자세히 설명하지는 않겠습니다. OpenGL과 3D 프로그래밍의 일반론을 소개하는 여러 가지 자료가 많이 있습니다. 여기서는 자바 ME에서 OpenGL ES를 초기화하는 방법과 사용자 장치에 맞게 적절히 구성하는 방법에 대해 알아보겠습니다.
JSR-239는 기본적으로 OpenGL ES API를 C 프로그래밍 언어용으로 직접 구현한 것입니다. 자바 SE NIO API의 부분적인 자바 ME 구현이 포함된 java.nio라는 패키지도 있습니다. 이 구현은 OpenGL ES가 꼭지점, 수직선, 텍스처 등에 사용하는 기본 버퍼를 만드는 데 사용됩니다.
JSR-239로 3D 드로잉을 하기 위해 GameCanvas를 사용하고 그 위에 3D 장면을 그리겠습니다. 우선 그래픽을 초기화하고 색상 심도 및 기타 OpenGL 관련 등록 정보를 구성해야 합니다. 다음은 빨간색 5비트, 초록색 6비트, 파란색 5비트(색상 심도 총 16비트)와 16비트의 심도 버퍼를 사용하여 그래픽을 초기화하는 코드입니다. 초록색이 빨간색 및 파란색보다 1비트 큰 이유는 인간의 눈으로 판별할 수 있는 초록색 색조의 수가 더 많기 때문입니다.
private boolean initGraphics() {
egl = (EGL11) EGLContext.getEGL();
if (egl == null) {
System.out.println("Error: could not initialize OpenGL ES");
return false;
}
eglDisplay = egl.eglGetDisplay(EGL11.EGL_DEFAULT_DISPLAY);
if (eglDisplay == null) {
System.out.println("Error: could not open connection to display");
return false;
}
int[] majorMinor = new int[2];
if (!egl.eglInitialize(eglDisplay, majorMinor)) {
System.out.println("Error: could not initialize OpenGL ES display");
return false;
}
System.out.println("EGL version: " + majorMinor[0] + "." + majorMinor[1]);
int[] numConfigs = new int[1];
egl.eglGetConfigs(eglDisplay, null, 0, numConfigs);
if (numConfigs[0] < 1) {
System.out.println("Error: no configurations found");
return false;
}
int configAttributes[] = {
EGL11.EGL_RED_SIZE, 5, EGL11.EGL_GREEN_SIZE, 6, EGL11.EGL_BLUE_SIZE, 5,
EGL11.EGL_ALPHA_SIZE, 0,
EGL11.EGL_DEPTH_SIZE, 16,
// EGL11.EGL_STENCIL_SIZE, EGL11.EGL_DONT_CARE, // don't care about stencils
EGL11.EGL_SURFACE_TYPE, EGL11.EGL_WINDOW_BIT,
EGL11.EGL_NONE
};
EGLConfig eglConfigs[] = new EGLConfig[numConfigs[0]];
if (!egl.eglChooseConfig(eglDisplay, configAttributes, eglConfigs,
eglConfigs.length, numConfigs)) {
System.out.println("Error: could not find a suitable configuration");
return false;
}
EGLConfig eglConfig = eglConfigs[0];
eglContext = egl.eglCreateContext(eglDisplay, eglConfig,
EGL11.EGL_NO_CONTEXT, null);
if (eglContext == null) {
System.out.println("Error: could not create a rendering state");
return false;
}
g2d = getGraphics();
gl = (GL11) eglContext.getGL();
if (gl == null) {
System.out.println("Error: could not create a 3D graphics context");
return false;
}
eglWinSurface = egl.eglCreateWindowSurface(eglDisplay, eglConfig, g2d, null);
if (eglWinSurface == null) {
System.out.println("Error: could not create a drawing surface window");
return false;
}
if (!egl.eglMakeCurrent(eglDisplay, eglWinSurface, eglWinSurface, eglContext)) {
System.out.println("Error: could not make the context current");
return false;
}
return true;
}
|
3D 장면을 그리거나 JSR-239를 사용해야 하는 텍스처 또는 기타 리소스를 초기화하려면 먼저 이 메소드를 호출해야 합니다. 3D 장면을 그리려면 이터레이션 사이에 지연이 전혀 없고 최대한 빨리 실행되는 루프로 새 스레드를 만들어야 합니다. 이렇게 해야 초당 프레임 수를 최대한으로 늘릴 수 있습니다. 이 게임 루프가 실제의 게임 로직을 관리합니다(아래 참조). 일반적으로, OpenGL을 사용하여 3D 장면을 디자인할 때는 모든 3D 개체를 장면 그래프로 구조화합니다. 이로써 몇 개의 작은 개체를 모은 복합 개체를 만들고 이것을 한꺼번에 이동할 수 있습니다. 아래 코드는 GameCanvas를 캡처하고 장면 그리기를 시작하도록 JSR-239에 지시하는 방법을 보여 줍니다. 이 예제에서는 자바를 사용하여 JSR-239를 렌더링하는 것과 관련이 있는 중요 부분만 남기고 코드의 일부분을 삭제했습니다.
private void renderScene() {
egl.eglWaitNative(EGL11.EGL_CORE_NATIVE_ENGINE, g2d);
// clear the scene, reset the transformations and set the lighting
...
// Start rendering the scene graph
root.renderNode(gl);
// Tell the device we have finished drawing 3D for this frame and release the device
gl.glFinish();
egl.eglWaitGL();
// Draw ordinary 2D graphics using the Graphics2D object for the GameCanvas
g2d.setColor(0x00aa00);
...
// Flush the graphics of the GameCanvas to display the new frame
flushGraphics();
}
|
장치 그래픽을 캡처하기 위해 각 렌더링을 전달하기 전에 eglWaitNative 메소드를 호출해야 합니다. 또한 이 과정을 마친 뒤 장치를 해제하고 마지막으로 flushGraphics를 호출하여 새 프레임을 표시해야 합니다. eglWaitNative 및 eglWaitGL을 사용하면 보통의 자바 ME 2D 그래픽과 JSR-239를 결합할 수 있습니다. JSR-239를 모바일 3D API(JSR-184) 또는 확장형 벡터 그래픽(JSR-226) 등 다른 그래픽 API와 결합하는 것도 가능합니다.
모바일 센서 API(JSR-256)는 자바 ME 장치에 통합되어 있는 여러 가지 센서와의 통신을 주로 다루는 일반 사양입니다. 온도계, 광 센서 또는 이 예제의 가속도계 등 여러 종류의 센서가 있을 수 있습니다. 가속도계는 가속도를 측정하며, 이 게임에서 중요한 가속도는 다른 무엇보다도 중력 가속도입니다. 중력은 일정하게 유지되기 때문에 전화기의 경사도를 판별하는 데 사용됩니다.
소니 에릭슨 w910i에는 3축 가속도계가 내장되어 있습니다. 즉, 세 차원 모두에서 가속도를 측정할 수 있습니다. 전화기를 평평하게 놓으면 가속도계는 Z축(상하) 방향의 가속도가 약 1000이라고 알려 줍니다(이 값은 1G에 해당). 전화기는 정지 상태이고 중력은 아래 방향으로만 작용하므로 X축 및 Y축 방향의 가속도(측면 가속도)는 0에 가까울 것입니다. 전화기를 뒤집어 화면이 아래쪽을 향하도록 하면 가속도계는 Z축 값 -1000을 가리킵니다. 옆으로 세워 놓을 경우, 세운 방향에 따라 X축 또는 Y축 값이 1000 또는 -1000이 됩니다.
전화기를 X축을 따라 45도 각도로 놓으면 Z축 값은 ñ707, Y축 값은 ±707이 됩니다. 두 축 모두에 최대의 중력이 발휘될 수는 없기 때문입니다(특정 각도에 대한 각 축의 값은 사인 및 코사인 함수를 사용하여 손쉽게 계산 가능). 언제든지 가속도계에서 판독한 X축 및 Y축 값으로 전화기의 자세를 판별하고, 이 값을 토대로 우주선을 움직여 다가오는 소행성을 피할 수 있습니다.
JSR-256용 API는 매우 간단합니다. 가속도계에 액세스하려면 일반 연결 프레임워크를 사용하여 주어진 장치 URL에 대한 연결을 개시합니다. 또한 SensorManager를 사용하여 연결을 위한 URL 등 해당 장치에서 사용 가능한 모든 센서에 관한 정보를 검색할 수도 있습니다. 아래 예제에서는 URL에 하드 코드 문자열을 사용했습니다. 그러나 보다 일반적인 방법으로 구현하고자 하는 경우 SensorManager는 사용 가능한 가속도계를 찾아 그것을 사용하거나, 사용자에게 경고를 표시합니다.
SensorConnection으로 구현된 센서를 참조해야 하는 경우, 장치 폴링을 통해 센서 데이터를 검색하거나 DataListener를 등록하여 자동으로 업데이트를 수신하는 두 가지 방법 중에서 선택할 수 있습니다. 제 생각에는 이벤트 위주로 접근할 때 보다 우수한 설계가 가능하며, 이 게임에서도 그 메소드를 사용했습니다. DataListener를 SensorConnection에 등록할 때는 버퍼 크기도 지정해야 합니다. 이 버퍼는 DataListener로 이벤트를 보내려면 얼마나 많은 데이터 값을 수집해야 하는지를 나타냅니다. 버퍼 크기를 적게 설정하면 업데이트 속도가 빨라집니다. 반면에 버퍼 크기가 크면 가속도계에서 평균을 계산하여 일탈 값을 보다 빨리 제거할 수 있습니다.
아래 코드는 게임용으로 알맞은 어댑터를 구현한 것입니다. 이 어댑터는 가속도계에 DataListener로 자동 등록되며 값을 세 가지 다른 정수로 저장합니다. 또한 URL도 지속적으로 센서에 저장합니다. 이 URL은 소니 에릭슨 전화기의 가속도계에만 해당되는 URL입니다. 보다 포괄적으로 만들려면 일단 모든 센서에 쿼리한 다음 가속도계 센서에 연결하도록 해야 합니다(또는 사용자에게 오류 메시지 표시). 수집된 값의 평균을 계산하여 일탈 값을 제거하려면 getAverage 메소드를 사용합니다.
public class SensorAdapter implements DataListener {
private int x, y, z;
private static final String sensorURL = "sensor:acceleration;contextType=user;model=ST_LIS302DL;location=inside";
public SensorAdapter() {
x = 0; y = 0; z = 0;
try {
SensorConnection sensor = (SensorConnection) Connector.open(sensorURL);
sensor.setDataListener(this, 10);
} catch (IOException e) {
e.printStackTrace();
}
}
public int[] getXYZ() {
synchronized(this) { return new int[] {x, y, z}; }
}
public void dataReceived(SensorConnection sensor, Data[] data, boolean isDataLost) {
synchronized(this) {
for(int i=0; i |
이 게임은 물론 다른 많은 게임에서도 핵심이 되는 것은 바로 3D 장면을 업데이트하고, 충돌을 검사하고, 게임 내 모든 개체의 상태를 추적하는 게임 루프입니다. 게임은 이 루프에 따라 진행됩니다. 여기서 사용하는 게임 루프는 사용자가 게임을 종료할 때까지 실행을 계속하는 스레드로 구성되어 있습니다. 이 게임은 매우 단순하며 기본적으로 다음과 같은 순서로 진행됩니다.
- 센서에서 데이터를 읽습니다.
- 우주선의 좌우 위치를 업데이트합니다.
- 우주선을 앞으로 이동합니다.
- 충돌 여부를 검사합니다.
- 점수를 업데이트합니다.
게임 루프가 위 단계를 모두 마친 뒤에는 Thread.sleep을 호출하여 다시 시작하기 전에 잠시 지연을 둡니다. 이 지연 시간에 따라 게임의 실행 속도가 달라집니다. 지연 값을 높게 설정하면 게임은 느리게 흘러가고, 값을 낮게 하면 플레이가 불가능할 만큼 빨라집니다.
우주선을 앞으로 움직일 때 실제로는, 즉 3D 용어로 표현하자면, 모든 소행성을 움직이는 것이고 우주선은 제자리에 머무릅니다. 이 3D 장면에서는 축마다 하나씩 세 개의 부동 값을 단순 환산하여 모든 개체의 위치를 결정하므로, 이 값이 너무 커지면 정밀도가 저하될 우려가 있음을 명심해야 합니다. 그러면 3D 개체는 왜곡되고 화면이 아주 이상하게 보입니다. 이를 방지하기 위해서는 "카메라"와 우주선이 원점 근처에 위치하도록 하고, 앞으로 움직일 때는 이 3D 장면의 다른 모든 개체에 음수 환산 값을 적용해야 합니다. 모바일 장치의 간단한 3D 게임이든 PC에서 실행하는 대규모 MMO 게임이든 간에 대부분의 3D 게임은 실제로 이런 방식으로 구현됩니다.
얼마나 우수하고 정밀한 감지가 요구되는가에 따라 충돌 감지는 매우 어려워질 수 있습니다. 이 게임에서는 가장 간단한 형태의 충돌 감지 기능 중 하나인 축 정렬 바운딩 상자를 사용했습니다. 즉, 각 개체를 둘러싸는 가상의 상자를 만들고 이 상자들이 교차하는지 확인하여 두 개체 간의 충돌 여부를 테스트하는 것입니다.
지금까지 내장된 가속도계로 컨트롤하는 간단한 자바 ME용 3D 게임을 만들어 보았습니다. 게임의 소스 코드를 다운로드하여 원하는 대로 마음껏 변경해 보십시오. 그리고 JSR-239 또는 JSR-256을 사용하여 게임이나 애플리케이션을 직접 제작하기 위한 아이디어를 얻으시기 바랍니다.
이 게임에서는 아주 기본적인 방식으로 가속도계를 사용했습니다. Z축 값에는 전혀 신경쓰지 않았습니다. 제가 아직 상상해 본 적이 없는 모바일 센서 API의 다른 용도가 분명히 더 있을 것이고, 그 방법은 이 기사에서 설명한 것보다 훨씬 흥미로울 것입니다. 최근 저는 블루투스를 통해 가속도계의 데이터를 컴퓨터로 보내면 전화기를 PC의 게임 컨트롤러로 사용할 수 있지 않을까 하는 생각을 하고 있습니다. 테니스 게임을 구현할 때 유용하겠지요. 이것은 또 제가 JOGL을 사용하여 보다 고급 3D 작업을 해봐야 하는 이유이기도 합니다. 이 모바일 센서 API에는 게임 외에도 엄청난 가능성이 더 숨겨져 있다고 생각합니다. 프레젠테이션용 무선 마우스를 만들면 어떨까 하는 생각이 갑자기 떠오르는군요.
여러분이 이 기사에서 멋진 아이디어를 얻으셨으면 좋겠습니다. 그리고 OpenGL ES나 모바일 센서 API를 사용하여 여러분만의 근사한 게임 또는 애플리케이션을 만드시길 바랍니다. 저에게 질문이 있거나 아이디어를 나누고 싶을 때는 언제든지 연락 주십시오.
- JSR-239에 대한 온라인 챕터(Dr. Andrew Davison)
- Khronos Group의 공식 OpenGL ES 사이트
- 소니 에릭슨 개발자 커뮤니티의 JSR-256에 관한 훌륭한 기사
- 게임 및 자바에 관한 훌륭한 커뮤니티 포럼인 JavaGaming.org
- 예제 게임의 프로젝트 웹 사이트
Erik Hellman은 10년이 조금 넘게 자바를 다루어 왔습니다. 에릭슨의 대규모 텔레콤 시스템에서 자바 EE로 작업한 뒤 자바 컨설턴트가 되었으며, 수많은 회의에서 광범위한 여러 주제에 관해 프레젠테이션을 한 바 있습니다. 현재 스웨덴 룬드에서 소니 에릭슨의 자바 SDK 개발 팀을 이끌고 있습니다.
이 글의 영문 원본은
New Gaming Experiences with OpenGL ES and the Mobile Sensor API
에서 보실 수 있습니다.
"Java ME" 카테고리의 다른 글
- 자바 ME 옵션 패키지 검색 (댓글 0개 / 트랙백 0개) 2008/03/10
- 모바일 및 임베디드 개발자 컨퍼런스 Recap (댓글 0개 / 트랙백 0개) 2008/02/26
- 서비스 지향 아키텍처 및 자바 ME (댓글 7개 / 트랙백 0개) 2007/04/10
- PIM API 시작하기 (댓글 2개 / 트랙백 0개) 2006/02/02
- Java ME에서 JSON(JavaScript Object Notation) 사용하여... (댓글 0개 / 트랙백 0개) 2008/10/13
- SOAP 없는 SOA: Java ME 전망 (댓글 14개 / 트랙백 0개) 2007/07/23
- 자바 ME를 사용한 초저 대역폭(Ultra-low Bandwidth) 통신 (댓글 0개 / 트랙백 0개) 2008/01/17
- 근접 통신(NFC) 및 비접촉식 통신 API 소개 (댓글 0개 / 트랙백 0개) 2008/07/01
- NetBeans를 사용해야 하는 12가지 이유 (댓글 4개 / 트랙백 0개) 2006/08/26
- 블루투스와 GPS 사용: 1부 - 무선 직렬 포트 데이터 읽기 (댓글 0개 / 트랙백 0개) 2008/08/11
댓글을 달아 주세요