ハエ取りゲームを高速化。
入門Google Androidプログラミングのシューティングゲームのサンプルを動かしてみると、変なGCも発生せずに高速で動く。テクスチャの表示にjavax.microedition.khronos.opengles.GL10ではなく、javax.microedition.khronos.opengles.GL11を使っているせいらしい。
そこで、シューティングゲームのサンプルを改造してハエ取りゲームに。
主な修正箇所は、以下の通り。
Polygonインターフェースにテクスチャ描画用のメソッドを追加。
public interface Polygon { /** * 描画メソッド * @param gl */ void draw(GL10 gl); void drawTexture(GL10 gl, float x, float y, float angle, float scale_x, float scale_y); }
Polygonインターフェースを実装するFlyPolygonクラスで、drawTextureメソッドを実装。
glDrawTexfOESメソッドは高速化のため簡素化されているため回転などが出来ない制約有り。
あらかじめ回転角度ごとに回転したテクスチャを用意して、それを切り替えることで回転しているように見せればいいのだろうけど、それは省略。
public class FlyPolygon implements Polygon { private int textureId; //テクスチャのサイズ public static int TEXTURE_WIDTH = 512; public static int TEXTURE_HEIGHT = 512; //表示サイズ public static int SCALE_WIDTH = 50; public static int SCALE_HEIGHT = 50; /** * コンストラクタ */ public FlyPolygon(int textureId) { this.textureId = textureId; } /** * @Override 描画 */ public void draw(GL10 gl) { } @Override public void drawTexture(GL10 gl, float x, float y, float angle, float scaleX, float scaleY) { gl.glPushMatrix(); // 色をセット gl.glColor4x(0x10000, 0x10000, 0x10000, 0x10000); // テクスチャユニット0番をアクティブに gl.glActiveTexture(GL10.GL_TEXTURE0); // テクスチャIDに対応するテクスチャをバインド gl.glBindTexture(GL10.GL_TEXTURE_2D, textureId); // 2Dテクスチャを有効に gl.glEnable(GL10.GL_TEXTURE_2D); // 座標と、幅・高さを指定 int[] rect = {0, 512, TEXTURE_WIDTH, -TEXTURE_HEIGHT}; // バインドされているテクスチャのどの部分を使うかを指定 ((GL11)gl).glTexParameteriv(GL10.GL_TEXTURE_2D, GL11Ext.GL_TEXTURE_CROP_RECT_OES, rect, 0); // 2次元座標を指定して、テクスチャを描画 ((GL11Ext)gl).glDrawTexfOES(x, y, 0, SCALE_WIDTH, SCALE_HEIGHT ); gl.glPopMatrix(); } }
GameBaseを継承したShootingSampleクラスをEsGameクラスに改名して、タッチイベントの処理をハエとの当たり判定を実行するように修正。
/** * タッチされた瞬間に呼ばれるメソッド * * @param x * タッチイベントで渡されたX座標 * @param y * タッチイベントで渡されたY座標 */ private void touchDown(float x, float y) { if (gameState == GAMESTATE_PLAYING) { // スクリーン座標からOpenGLのワールド座標に変換する float tmp_x = this.surfaceWidth - x; float tmp_y = this.surfaceHeight - y; //ハエとの当たり判定 for (int i = 0; i < ENEMY_NUM; i++) { if (flys[i].isAlive ) { if( flys[i].isPointInside(tmp_x, tmp_y) ){ sePlayer.start(); Log.d("EsGAME", "fly:[" + flys[i].x + "," + flys[i].y +"] touch:[" + tmp_x + "," + tmp_y +"]" ); flys[i].isAlive = false; score = score + 10; } } } } else if (gameState == GAMESTATE_TITLE) { gameState = GAMESTATE_PLAYING; initGame(); } else if (gameState == GAMESTATE_GAMEOVER) { gameState = GAMESTATE_TITLE; } }
FlyPolygonクラスを持つFlyクラスはサンプルの敵クラスからプロパティを減らしただけ
public class Fly extends ActorBase { // Enemy用のポリゴンと、描画クラスへの参照 private Polygon2DRenderer polygon2DRenderer; private FlyPolygon enemyPolygon; // Enemyの大きさ public static final int ENEMY_SIZE = 5; public static final int MIN_SIZE = 5; // Enemyの速度 private static final float MIN_SPEED = 1.0f; public float speedX, speedY; // Enemyの回転量 private static final float TURN_DEGREE = 10.0f; private float angle; /** * コンストラクタ * * @param enemy * 敵のポリゴンへの参照 * @param renderer * 描画用クラスへの参照 */ public Fly(FlyPolygon enemy, Polygon2DRenderer renderer) { super(0.0f, 0.0f, 0.0f, 0.0f, false); enemyPolygon = enemy; polygon2DRenderer = renderer; speedX = 0.0f; speedY = 0.0f; angle = 0.0f; } /** * 指定座標から敵を生成 * * @param x * 敵が生成されるX座標 * @param y * 敵が生成されるY座標 */ public void execute(float x, float y) { this.x = x; this.y = y; w = MIN_SIZE * ENEMY_SIZE; h = w; // 大きいほど遅く speedX = (float) Math.random() + MIN_SPEED; speedY = (float) Math.random() + MIN_SPEED; isAlive = true; angle = 0; } public boolean isPointInside(float x, float y) { float dx = x - this.x; float dy = y - this.y; float distance = (float) Math.sqrt(dx * dx + dy * dy); if (distance <= FlyPolygon.SCALE_HEIGHT) { return true; } else { return false; } } /** * 毎フレームの更新処理 */ public void update() { x += speedX; y += speedY; angle += TURN_DEGREE; if (angle >= 360.0f) angle -= 360.0f; } /** * 毎フレームの描画処理 * * @param gl */ public void draw(GL10 gl) { polygon2DRenderer.draw(gl, enemyPolygon, x, y, angle, w, h); } }
BackGroundクラスは背景を描くだけ。
public class BackGround implements Polygon { //画面サイズを手抜きで指定 public static int WIDTH = 480; public static int HEIGHT = 640; private int textureId; public BackGround(int textureId){ this.textureId = textureId; } @Override public void draw(GL10 gl) { // 色をセット gl.glColor4x(0x10000, 0x10000, 0x10000, 0x10000); // モデルビュー行列を指定 gl.glMatrixMode(GL10.GL_MODELVIEW); // 現在選択されている行列(モデルビュー行列)に、単位行列をセット gl.glLoadIdentity(); // Magic offsets to promote consistent rasterization. gl.glTranslatef(0.0f, 0.0f, 0.0f); // テクスチャユニット0番をアクティブに gl.glActiveTexture(GL10.GL_TEXTURE0); // テクスチャIDに対応するテクスチャをバインド gl.glBindTexture(GL10.GL_TEXTURE_2D, textureId); // 2Dテクスチャを有効に gl.glEnable(GL10.GL_TEXTURE_2D); // 座標と、幅・高さを指定 int[] rect = {0, 512, WIDTH, -HEIGHT}; // バインドされているテクスチャのどの部分を使うかを指定 ((GL11)gl).glTexParameteriv(GL10.GL_TEXTURE_2D, GL11Ext.GL_TEXTURE_CROP_RECT_OES, rect, 0); // 2次元座標を指定して、テクスチャを描画 ((GL11Ext)gl).glDrawTexiOES(0, 0, 0, WIDTH, HEIGHT); } @Override public void drawTexture(GL10 gl, float x, float y, float angle, float scaleX, float scaleY) { } }
これで、以前は2秒に1回、40000Objectほどが処理されていたGCが数分に1回、15000ObjectがGC処理される程度にまで減りました。
比較動画。
GL10で21個のテクスチャを描画。2秒間に1回の割合でGCが実行され、画面全体が一瞬停止する。↓
GL11で21個のテクスチャを描画。GCよる停止は無い。↓
LogCatに出力されるGCのログを見ても、その差は明確。