この本では214ページでRendererは本来グラフィックの描画が役割なので各種演算はゲームスレッド(マルチスレッド)を導入して分けると言うことが書かれていました。
実際のソースコードもスレッド側では計算しかしていません。(プログラム自体は起動時に直接ゲーム画面になる為、シーンという概念が無い) ですが今回自分が作りたいプログラムは下記の構成になっているのでテキストどおりには作れません。
・ゲーム全体の構成(シーン。これらは全て一つのアクティビティに収める)
|
|----TITLE
|----MENU
|----GAME_MAIN
|----RESULT
|----END
//シーンは目的に応じて追加できるようにする(GAME_MAINをSTAGE0,STAGE1と言う風に変更したり追加したり)
・シーン一つの構成
|
|----INIT (オブジェクト等のリソースの読み込み、座標などの初期化)
|----UPDATE(ゲームループ処理)
|----END(シーン中に使用したリソースの破棄等)
public class Global {
//二つの定数を組み合わせてゲームの基本ループは処理される(例:TITLEシーンのINIT等)
//シーン(タイトル画面か?それともゲーム画面か等)
public enum Scene{
TITLE,
GAME_MODE1,
GAME_MODE2,
RESULT,
ENDING
};
//シーンステータス(初期化か、更新か、破棄か)
public enum Status{
INIT,
UPDATE,
END
}
//現在のシーン
public static Scene scene;
//シーンの状態
public static Status status;
//アクティビティのコンテキストを保持する変数
public static Context context;
//GLコンテキストを保持する変数
public static GL10 gl;
//MainActivity(GameActivity)を保持させる(Activity内のメソッドを呼び出すため)
public static MainActivity mainActivity;
//乱数を生成する
public static Random rand = new Random(System.currentTimeMillis());
}
Task.java
public interface Task {
//GLコンテキストが関わってくるリソースなどの読み込み(RendererのSurfaceChanged等で使用)
public boolean load();
//初期化時の処理(数値の初期化のみ)
public boolean init();
//更新時の処理
public boolean update();
//描画時の処理
public void draw(GL10 gl);
//終了時の処理(オブジェクトやリソースの解放など)
public boolean end();
}
TitleSceneClass
MenuSceneClass
GameSceneClass
etc...
これらを使って組んでいきたいのですが、
参考書に記述されているプログラムではGLコンテキストを取得できるタイミングがRendererクラスのonSurfaceChangedな為に、別クラスのスレッドでSceneを利用した場合、loadメソッドが使用出来ませんでした。
(参考書では独立したGameThreadクラスを作りGLコンテキスト等に一切関与していない。またスレッドはMainActivityのonCreateで直接newしてstartしている)
参考書のプログラムを見せた方が伝えやすいのですが著作権とかでややこしいのであげられません・・・(ただ配布はされているので本のタイトルで少し調べれば出てくると思いますが)
シーン毎にリソースの読み込みなどを行いたいのでAndroidの館の
http://dixq.net/Android/s02_01.html
ページを参考にRendererにRunnnableをimplementsし、Renderer内に直接スレッドのrunメソッドを作ると言うことをしました。
package jp.denpa.glgametest;
//以下importに注意(似たものがあるため)
import jp.denpa.data.Global;
import jp.denpa.data.GraphicUtil;
import jp.denpa.data.Color;
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.egl.EGLConfig;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.Log;
public class MyRenderer implements GLSurfaceView.Renderer, Runnable{
//コンテキスト
private Context mContext;
//ゲームスレッド(マルチスレッド)
private Thread mThread;
//ゲームマネージャー
private GameMgr mGameMgr;
//画面サイズの保持
private int mScreen_Width;
private int mScreen_Height;
//タッチ時の座標をGL座標系で保持
private float mGL_Touch_x;
private float mGL_Touch_y;
//コンストラクタ
public MyRenderer(Context context){
this.mContext = context;
// this.mGameThread = gameThread;
mThread = new Thread(this);
mGameMgr = new GameMgr();
}
//描画を行う処理
public void renderMain(GL10 gl){
//描画処理
mGameMgr.draw(gl);
}
//描画処理の記述
@Override
public void onDrawFrame(GL10 gl){
//描画の初期化----------------------------------------------------------
//透過処理、加算合成の設定(ポリゴン毎に設定するのが好ましいか?)
//gl.glEnable(GL10.GL_BLEND);
//gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
//OpenGLの座標系などの設定
//"描画可能"領域の指定
gl.glViewport(0, 0, mScreen_Width, mScreen_Height); //glViewport(オフセットX, オフセットY, 幅, 高さ);
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
//OpenGLの座標系を指定している(この場合は左端[-1.0]右端[1.0]下端[-1.5]上端[1.5]手前(Z)[0.5f]奥(Z)[-0.5])
gl.glOrthof(-1.0f, 1.0f, -1.5f, 1.5f, 0.5f, -0.5f); //このメソッドはgmMatrixMode、glLoadIdentityメソッドの二つが呼ばれた直後に呼び出すこと
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
//画面のクリア(背景の設定)
gl.glClearColor(0.5f, 0.5f, 0.5f, 1.0f); //背景色の設定
gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
//ここまで--------------------------------------------------------------
//メイン描画部分
renderMain(gl);
}
//画面生成時、画面向き変更時に呼び出される。初期化処理などを行う
@Override
public void onSurfaceChanged(GL10 gl, int width, int height){
this.mScreen_Width = width;
this.mScreen_Height = height;
//GLコンテキスト取得(グローバル化)
Global.gl = gl;
//リソースの読み込み
mGameMgr.load();
//情報の初期化
mGameMgr.init();
//シーンステータスを更新にする
Global.status = Global.Status.UPDATE;
//スレッドのスタート
mThread.start();
}
//画面生成時、画面向き変更時に呼び出される。初期化処理などを行う
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
}
//画面がタッチされたときに呼ばれるメソッド
public void touched(float x, float y){
Log.i(getClass().toString(), String.format("touched! x= %f y= %f", x, y));
mGL_Touch_x = x;
mGL_Touch_y = y;
}
//マルチスレッドでゲームループを作る(リソースの読み込み、初期化、更新のみで描画はRendererのonDrawFrameに任せる)
@Override
public void run() {
while(mThread != null){
//シーンステータス毎の処理
switch(Global.status){
case INIT:
//リソースの読み込み
mGameMgr.load();
mGameMgr.init();
//シーンステータスを更新にする
Global.status = Global.Status.UPDATE;
break;
case UPDATE:
//座標などの数値情報の更新
mGameMgr.update();
break;
case END:
//リソースの破棄処理
mGameMgr.destroy();
//シーンステータスを初期化にする(次のシーンへ移行)
Global.status = Global.Status.INIT;
break;
default:
break;
}
}
}
}
長々と書きましたがようやく本題です。
・RendererクラスにRunnableし、中にrunメソッドを書くという行為は問題ないか?
・現在RendererにGameMgrを持たせその中でSceneごとの処理をし、さらにそのSceneの中で敵やプレイヤーなどのオブジェクトを持たせる(敵は最終的にコンテナで管理)しようとしているが、この様な深い階層の中で、さらに階層毎にコンテナを持たせる行為(GameMgrの中でScene等のTaskを管理するコンテナ、さらにSceneの中で敵を管理するコンテナ)を想定しているがこの様な根深く、コンテナなどを多用するような構造は深刻な問題が無いだろうか?
・上記以外でも現在の構築は何かしら危険を孕んでいないだろうか?
をお伺いしたいと思います。
非常に下手な説明、伝わりにくい内容で申し訳ございませんがよろしくお願いします。
(GlobalでGLコンテキストを取得しているのに一部のメソッドの引数[Task等]でGLコンテキストを取得しているのはまだ実験段階だからです。これらは修正予定に入っています)