본문 바로가기

프로그래밍/libGDX 엔진

libGDX 튜토리얼(Tutorial) #2 - 게임 스크린(Games Screens)

이전 포스트에서 우리는 이클립스에 프로젝트 구조를 만들고, 게임에서 처리되는 이벤트와 데스크탑과 안드로이드의 실행기(Launcher)에 대해 이야기했다. 이번 포스팅에서는 게임 스크린에 대해 다룰 것이다.


■ 티리안(Tyrian)에 대해서

티리안은 1995년 개발되었습니다. 2004년 프리웨어가 되었고, 2007년 이후로 모든 그래픽 소스들이 오픈 라이센스로 변경되어 이용 가능합니다. 한 번 티리안을 플레이 해보시는건 어떠신가요? 아래 단계를 따라하세요:

  1. OpenTyrian 프로젝트로 이동
  2. 최신 Win32 OpenTyrian 릴리즈 빌드를 다운
  3. Tyrian 2.1 다운로드
  4. 압축파일들 압축 풀기
  5. opentyrian.exe 실행

멋진 게임 아닌가요? 이제 아래의 화면 흐름 계획을 보세요.

스크린 흐름에 대한 설명입니다.

  • 스플래시 화면이 처음 나타난 후, 메인 메뉴 화면으로 이동합니다.
  • 메뉴에서는 플레이어가 게임을 플레이하거나, 명예의 전당을 보거나, 몇 가지 옵션을 조정할 수 있습니다.
  • 게임시작 화면에서는 플레이어가 새로운 게임을 시작하거나 저장된 게임으로 플레이할 수 있습니다.
  • 프로필 화면에서는 플레이어가 성별 프로필을 볼 수 있으며, 비행기를 관리할 수 있습니다.
  • 레벨 화면에서는 레벨이 있으며, 플레이어가 플레이할 수 있습니다.


■ 화면 구현

티리안은 화면에 따라 코드를 분리하기에 좋습니다. 우리는 메인 클래스(티리안)를 ApplicationListener를 구현한 com.badlogic.gdx.Game 에 확장해야 합니다. 이벤트 핸들링 메소드가 super를 콜하도록 수정하는것도 잊으면 안됩니다. 이제 우리는 인터페이스를 구현하는것 대신에 클래스를 확장했습니다. 우리는 한 번에 구체적인 화면의 메소드를 다루는 이벤트를 위임하기 위해 인터페이스 com.badlogic.gdx.Screen 을 사용합니다. 결국 아래와 같은 그림의 구조가 나타납니다.

사실 티리안과 화면들이 이런 구조로 의존하고 있는게 자랑스럽지는 않습니다. 그러나 튜토리얼이기에 이러한 문제점은 무시하고 진행합니다. 티리안 클래스는 화면들이 생성하는것을 관리하며, 각각의 화면 인스턴스는 자기 화면을 렌더링 할 수 있습니다. 이러한 방식은 각각의 화면은 범위가 제한되어 코드 유지보수에 도움을 주는 좋은 접근입니다. 이 단계를 완료했으면, 우리는 게임이 스플래시 화면으로 보여지도록 초기화해야합니다. 아래는 코드의 일부분입니다.

package com.dpug.tyrian;

import com.badlogic.gdx.Game;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.FPSLogger;
import com.badlogic.gdx.graphics.GL10;
import com.dpug.tyrian.screens.SplashScreen;

/**
 * 게임 메인 클래스, 어플리케이션 이벤트가 실행될 때, 호출되는 곳
 */
public class Tyrian extends Game {
	// 로깅을 위한 유용한 상수 선언
	public static final String LOG = Tyrian.class.getSimpleName();

	// 현재 FPS의 기록을 도와주는 libgdx 클래스
	private FPSLogger fpsLogger;
	
	public SplashScreen getSplashScreen(){
		return new SplashScreen(this);
	}

	@Override
	public void create() {
		// Gdx는 싱글톤으로 구현되었으며, 그래픽, 오디오, 파일 등도 마찬가지임
		Gdx.app.log(Tyrian.LOG, "Creating Game");
		fpsLogger = new FPSLogger();
	}

	@Override
	public void dispose() {
		Gdx.app.log(Tyrian.LOG, "Disposing game");
	}

	@Override
	public void pause() {
		Gdx.app.log(Tyrian.LOG, "Pausing game");
	}

	@Override
	public void render() {
		// 아래의 코드는 화면을 클리어하고, RGB 컬러를 줌(green)
		Gdx.gl.glClearColor(0f, 1f, 0f, 1f);
		Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

		// 현재 FPS의 츨력, 게임 퍼포먼스를 쉽게 확인이 가능하다.
		fpsLogger.log();
	}

	@Override
	public void resize(int width, int height) {
		Gdx.app.log(Tyrian.LOG, "Resizing game to: " + width + " x " + height);
	}

	@Override
	public void resume() {
		Gdx.app.log(Tyrian.LOG, "Resuming game");
	}

}

우리는 베이스 화면(AbstractScreen) 클래스를 가지게 되었고, 이것을 리팩토링 할 것입니다. Tyrian 클래스의 render() 메소드가 super.render()를 호출하고, FPS를 출력하도록 합니다. AbstractScreen의 render() 메소드는 화면을 검은색으로 클리어합니다.
package com.dpug.tyrian.screens;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.dpug.tyrian.Tyrian;

/**
 * The base class for all game screens.
 */
public abstract class AbstractScreen implements Screen {
	protected final Tyrian game;
	protected final BitmapFont font;
	protected final SpriteBatch batch;

	public AbstractScreen(Tyrian game) {
		this.game = game;
		this.font = new BitmapFont();
		this.batch = new SpriteBatch();
	}

	@Override
	public void show() {
	}

	@Override
	public void resize(int width, int height) {
	}

	@Override
	public void render(float delta) {
		// 아래의 코드는 화면을 RGB(검은색)으로 초기화 합니다.
		Gdx.gl.glClearColor(0f, 0f, 0f, 1f);
		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
	}

	@Override
	public void hide() {
	}

	@Override
	public void pause() {
	}

	@Override
	public void resume() {
	}

	@Override
	public void dispose() {
		font.dispose();
		batch.dispose();
	}
}


■ Screen클래스 render 메소드의 delta 파라메터

delta 파라메터가 있던거 보셨나요? 이 경우에 이것이 뭘 의미하는지 궁금할꺼에요. 두 가지 시나리오를 상상해봐요. 첫 번째 시나리오는 플레이어가 FPS가 10프레임밖에 안나오는 구형 스마트폰을 가지고 있을때, 두 번째 시나리오는 플레이어가 100프레임이 나오는 쿼드코어 신형 스마트폰을 가지고 있을때이다.  두 디바이스에서 같은 시간 개념을 정의하고 싶다면, "delta" 파라메터를 사용해야 합니다. 이것은 최신형 디바이스가 구형 디바이스보다 10배 빠르게 게임이 보이지 않도록 해줍니다. 단지 많은 프레임으로 인해 게임이 더 부드럽게 출력될 뿐입니다. 지금 우리는 이것을 사용하지 않겠지만, 나중에 사용하게 될 수도 있습니다.


■ 스플래시 화면에 이미지 추가하기

만약, 두 번째 튜토리얼 글까지 진행했는데도 빈 화면만 뜬다면 정말 슬플껍니다. 웹을 뒤져서 우리 스플래시 화면에서 쓸 멋진 이미지를 찾았습니다. 확인해보세요:

안타깝게도 이 이미지만 리소스 폴더에 던지는 방식은 쓸 수 없습니다. Libgdx(다른 게임 프레임워크들도 마찬가지)는 2배수의 면적을 사용하는 이미지를 요구합니다. 그러나 여기에 한 가지 더 있습니다. 이 이미지는 하나 이상의 이미지를 포함할 수 있습니다.(image atlas 같은) 나중에 우리는 image atlas 그리는것에 대해 이야기할 것입니다.

우리는 모든 이용가능한 공간을 사용할 수 있도록 스플래시 이미지를 늘려야합니다. 먼저, 스플래시 이미지의 비율이 게임 윈도우의 비율과 같아지도록 1.6으로 정합니다(우리의 게임 윈도우 면적은 800x480임으로, 비율은 1.6666666666667 입니다). 포토샵을 사용하여 이미지 비율을 1.6으로 만들고, 아래처럼 512x512 image atlas 로 만들었습니다.

다음 단계는 이 atlas 이미지를 우리의 리소스 폴더, tyrian-android/assets 로 넣는것입니다. tyrian-game의 리소스 폴더는 이 폴더와 연결되어 있습니다. 이클립스에서는 자동으로 tyrian-game 프로젝트 트리 아래 있는 이미지를 보여줍니다. 마침내, 우리는 이 리소스를 사용하여 SplashScreen 클래스를 수정했습니다. 아래와 같이말이죠:

package com.dpug.tyrian.screens;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.dpug.tyrian.Tyrian;

public class SplashScreen extends AbstractScreen {

	private Texture splashTexture;
	private TextureRegion splashTextureRegion;

	public SplashScreen(Tyrian game) {
		super(game);
	}

	@Override
	public void show() {
		super.show();

		// 스플래시 이미지를 불러오고, 텍스쳐 지역을 생성합니다.
		splashTexture = new Texture("splash.png");

		// 이미지를 화면에 넓히기 위해 리니어(linear) 텍스쳐 필터를 셋팅합니다.
		splashTexture.setFilter(TextureFilter.Linear, TextureFilter.Linear);

		// image atlas 안에 우리의 스플래시 이미지가 
		//왼쪽 위 코너 (0,0)부터 시작하고, 512x301의 면적을 같습니다. 
		splashTextureRegion = new TextureRegion(splashTexture, 0, 0, 512, 301);
	}

	@Override
	public void render(float delta) {
		super.render(delta);

		// 우리는 2D 텍스쳐를 그리기 위해 Spritebatch를 사용합니다.
		// (이것은 base class인 AbstractScreen에 정의되어 있습니다)
		batch.begin();
		
		// 화면 사이즈만큼 영역을 그린다.
		batch.draw(splashTextureRegion, 0, 0, Gdx.graphics.getWidth(),
				Gdx.graphics.getHeight());

		// 그리는 메소드 종료
		batch.end();
	}

	@Override
	public void dispose() {
		super.dispose();
		splashTexture.dispose();
	}
}


이제 우리가 데스크탑 실행기를 실행시키면, 아래와 같은 윈도우 화면을 볼 수 있습니다.


■ 결론

우리는 메인 게임 프로젝트가 화면을 지원하도록 개선하였습니다. 각각의 화면들은 범위에 맞게 잘 정의되었습니다. 우리는 SplashScreen이 실제로 무언가 보이도록 약간의 코드를 리팩토링하고 변경했습니다. 그리고, 이미지를 다루는것이 쉽지 않다느걸 깨달았습니다. 그러나, 저를 믿으세요. 이미지를 다루는건 더 어려워지겠지만, Libgdx는 내부적으로 대부분의 작업을 하기 때문에, 우리는 화면 작업에만 직중할 수 있을껍니다.


소스코드는 Google Code web-site 에서 보기/다운이 가능합니다. 

궁금한점이 생기시면 댓글로 질문하세요 :)