第三パートでは、空のactivityを作成しました。onCreateScene メソッド内で、scene として"null"を表示するよう engine に設定しました。 ネット上にあるサンプルを見ると、常にonCreateScene 内で実際のscene を作成しています。 ですが、今回は複数のSceneを使用したいので、Sceneを切り替える仕組みが必要です。
また、重要なオブジェクトを各Sceneに渡す仕組みも必要です。Sceneのコンストラクタを呼び出す際にオブジェクトの引渡しができます。 もしくは、ResourceManagerと呼ばれる別のクラスを使用することもできます。
このクラスは、TextureやSoundやMusicのようなリソースのために使用されます。 リソースは全てのSceneで広範囲に使用されるので、Resource managerには重要なオブジェクトもまたいくつか定義します。 Textureなどの読み込みについては次のパートで説明します。必要に応じて順次加筆していきます。 今のところは、以下のような本当にシンプルなリソースマネージャーを作成します。:
package is.kul.squongtutorial.resources;
import is.kul.squongtutorial.GameActivity;
import org.andengine.engine.Engine;
import org.andengine.engine.camera.Camera;
import org.andengine.opengl.vbo.VertexBufferObjectManager;
public class ResourceManager {
private static final ResourceManager INSTANCE = new ResourceManager();
//common objects
public GameActivity activity;
public Engine engine;
public Camera camera;
public VertexBufferObjectManager vbom;
private ResourceManager() {}
public static ResourceManager getInstance() {
return INSTANCE;
}
public void init(GameActivity activity) {
this.activity = activity;
this.engine = activity.getEngine();
this.camera = engine.getCamera();
this.vbom = engine.getVertexBufferObjectManager();
}
}
GameActivity を変更してResourceManagerを初期化します:
@Override
public void onCreateResources(
OnCreateResourcesCallback pOnCreateResourcesCallback)
throws IOException {
ResourceManager.getInstance().init(this);
pOnCreateResourcesCallback.onCreateResourcesFinished();
}
このクラスはシングルトンパターンと呼ばれるものに準拠しています。 これはプログラム全体でインスタンスは一つだけということを意味します。そのためコンストラクタは非公開となっており、誰もインスタンスを新規作成できません。 ハードウェアの制限のためにメモリ内で使われるアセットのインスタンスは一つだけにしたいでしょうから、リソースマネージャーを一つだけにするのは重要です。
今のところ、このリソースマネージャではactivity, engine, camera , vertex buffer object managerへの参照を持っているだけです。
Vertex Buffer Object は OpenGLの一部です。これにより頂点データの描写を行うことができます。 AndEngine はこの描写処理を見えないところであなたに代わって行います。少なくとも初めてのゲーム制作では、常に VBO Managerを使うだけでも問題ないでしょう。
Scene は現在画面に表示されている基本的なentityの集まりです。 通常はメニュー画面とゲーム画面ではまったく別のentityを使いたいでしょう。 そのためメニューの scene とゲームの sceneを作成します。
まず最初に抽象scene を作成し、次に上記各sceneのクラスを作成し、最後にそれらを切り替えるScene Managerを作成します。
Javaでは、抽象クラスとはインスタンス化することができないクラスのことです。 これはいくつかの機能を定義できる親クラスですが、"new"を使って呼び出すことができません。 抽象クラスを持つことができ、それを強制的に子クラスにも実装させます。
Entity とは仮想オブジェクトです。これは位置情報、親、子を持ちますが画面には描写されません。 これの拡張クラスにはShape (およびその子クラスRectangle, ...)やSprite (TiledSprite, ...)があり、それらは画面上に物理的に表示されます。
Scene とは画面上に表示されるEntity(AndEngine class Entity)の階層です。 任意のEntity を Sceneに貼り付けることができます。そして任意のEntityに別のEntityを貼り付けて親子関係を作ることができます。 実際のところ、Scene もまたEntityです。
AbstractScene では基本的なオブジェクトと重要なメソッドを定義します。またユーザーがバックキーを押した時の既定の振る舞いも定義します。
package is.kul.squongtutorial.scene;
import is.kul.squongtutorial.GameActivity;
import is.kul.squongtutorial.resources.ResourceManager;
import org.andengine.engine.Engine;
import org.andengine.engine.camera.Camera;
import org.andengine.entity.scene.Scene;
import org.andengine.opengl.vbo.VertexBufferObjectManager;
import org.andengine.util.debug.Debug;
public abstract class AbstractScene extends Scene {
protected ResourceManager res = ResourceManager.getInstance();
protected Engine engine;
protected GameActivity activity;
protected VertexBufferObjectManager vbom;
protected Camera camera;
public void initialize(GameActivity activity, ResourceManager res) {
this.res = res;
this.activity = activity;
this.vbom = activity.getVertexBufferObjectManager();
this.engine = activity.getEngine();
this.camera = engine.getCamera();
}
public abstract void loadResources();
public abstract void create();
public abstract void unloadResources();
public abstract void destroy();
public void onBackKeyPressed() {
Debug.d("Back key pressed");
}
public abstract void onPause();
public abstract void onResume();
}
クラス内で上書きや実装しなければならないメソッドは以下の通りです:
各scene ごとにJavaクラスを作成し、その全てでAbstractSceneを継承します。 そしてそれぞれの見た目を少し変えるために、各Sceneごとに色を変えて以下の一行を記述します:
@Override
public void create() {
getBackground().setColor(Color.BLUE);
}
最後に、scene の切り替えを制御するもう一つのシングルトンクラスが必要です。 これは少しトリッキーなもので、Androidのより高度な概念 - AsyncTasksを使用します。 詳細については別のチュートリアルで説明します。 ここでは最もシンプルな形式のAsyncTaskを使用しています。 Async タスクや Asynchronous タスクとは、別のスレッド上で動作するタスクのことです。 以下にクラスの完全な内容を記載しました。これはどんなゲームでも変わらないので、変更する必要がありません。 次のパートで説明する、共通リソースの読み込みを追加するだけです。
重要なメソッドは以下です:
package is.kul.squongtutorial;
import is.kul.squongtutorial.resources.ResourceManager;
import is.kul.squongtutorial.scene.AbstractScene;
import is.kul.squongtutorial.scene.LoadingScene;
import is.kul.squongtutorial.scene.MenuScene;
import is.kul.squongtutorial.scene.SplashScene;
import org.andengine.util.debug.Debug;
import android.os.AsyncTask;
public class SceneManager {
private static final SceneManager INSTANCE = new SceneManager();
private ResourceManager res = ResourceManager.getInstance();
private AbstractScene currentScene;
private LoadingScene loadingScene;
private SceneManager() {}
/**
* Shows splash screen and loads resources on background
*/
public void showSplash() {
Debug.i("Scene: Splash");
final SplashScene splash = new SplashScene();
setCurrentScene(splash);
splash.loadResources();
splash.create();
res.engine.setScene(splash);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
long timestamp = System.currentTimeMillis();
// TODO later load common resources here
MenuScene menu = new MenuScene();
menu.loadResources();
menu.create();
loadingScene = new LoadingScene();
loadingScene.loadResources();
loadingScene.create();
// we want to show the splash at least SPLASH_DURATION miliseconds
long elapsed = System.currentTimeMillis() - timestamp;
if (elapsed < GameActivity.SPLASH_DURATION) {
try {
Thread.sleep(GameActivity.SPLASH_DURATION - elapsed);
} catch (InterruptedException e) {
Debug.w("This should not happen");
}
}
setCurrentScene(menu);
res.engine.setScene(menu);
splash.destroy();
splash.unloadResources();
return null;
}
}.execute();
}
public void showScene(Class<? extends AbstractScene> sceneClazz) {
if (sceneClazz == LoadingScene.class) {
throw new IllegalArgumentException("You can't switch to Loading scene");
}
try {
final AbstractScene scene = sceneClazz.newInstance();
Debug.i("Showing scene " + scene.getClass().getName());
final AbstractScene oldScene = getCurrentScene();
setCurrentScene(loadingScene);
res.engine.setScene(loadingScene);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
if (oldScene != null) {
oldScene.destroy();
oldScene.unloadResources();
}
scene.loadResources();
scene.create();
setCurrentScene(scene);
res.engine.setScene(scene);
return null;
}
}.execute();
} catch (Exception e) {
String message = "Error while changing scene";
Debug.e(message, e);
throw new RuntimeException(message, e);
}
}
public static SceneManager getInstance() {
return INSTANCE;
}
public AbstractScene getCurrentScene() {
return currentScene;
}
private void setCurrentScene(AbstractScene currentScene) {
this.currentScene = currentScene;
}
}
このsplash は少なくともSPLASH_DURATION ミリ秒表示されるので注意してください。 基本的にはこの時間を設定してユーザーがあなたのバッジを(3-4秒)視認できるようにします。 読み込みに時間がかかる場合は、リソースが読み込まれてメニューScene の準備ができるまでこのSceneManager はSplash を表示し続けます。 GameActivityに以下を追加してください:
@Override
public void onPopulateScene(Scene pScene,
OnPopulateSceneCallback pOnPopulateSceneCallback)
throws IOException {
SceneManager.getInstance().showSplash();
pOnPopulateSceneCallback.onPopulateSceneFinished();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
try {
SceneManager.getInstance().getCurrentScene().onBackKeyPressed();
} catch (NullPointerException ne) {
// in highly unlikely situation when this there is no scene, do nothing
Debug.e("The current scene is null", ne);
}
}
return false;
}
これで準備ができました。このアプリを実行できます。 splash sceneがSPLASH_DURATION ミリ秒間表示された後、メニューsceneになります。 それぞれのsceneで背景色が違うことを確認してください! また、バックキーを押してLogCatを見てください。
メモ: The AsyncTask には多くの利点があります。例えば、タスクがバックグラウンドで動作している間に表示中のsceneでアニメーションを行うことができます。 タスクから進捗状況についての情報を送って表示することもできます。
あなたのプロジェクトは現時点でこのようになっているでしょう。どこか違っている場合はダウンロードをしてください。
次のチャプターでは、 Splash Sceneとリソースの読み込み・解放について説明します。