This document written: 2014-05-31 .. 2022-07-09
本家(BAG)の実装は AndroidFastRenderView となっている。この部分については、プラットフォーム毎に大きく異なるものなので、インターフェースは用意されておらず、Game クラスの補助クラスとしての位置付けであり、ゲーム開発者は意識する必要のない部分となる。
よって、僕の側で何ら独自に工夫する余地は基本的にはない(コードの掲載は割愛するので BAG を参照のこと)。プラットフォーム毎に実装を分ける必要性のなさから、名前だけ、FastRenderView に昇格させた形となる。
技術的には Android API の SurfaceView を使ったものであり、Java 標準 API の Thread によって生成した別スレッドで無限ループを回す形となっている。解説は 実装に使う Android API 群(第 4 章)中編で紹介した通りであり、そこのサンプルプログラムの内部クラスとして定義した FastRenderView ともほとんど同じである。
BAG はインターフェースが Game、実装が AndroidGameとなっている。
この実装は、ここまでで部品として実装した各種ドライバー的オブジェクトを集成する存在である。またそれだけでなく、Android アプリのメインプログラムである Activity として実装するので、onCreate / onResume / onPause という Activity のライフサイクルの中に、プログラムを組み込むことにもなる。例えば、スクリーンのフルスクリーン化などといった作業はこのクラスの onCreate の中で行うことになる。
ここでも、インターフェースを省いて直接実装してしまう作業が基本になるので、implements Game
を除去して AndroidGames を Game クラスそのものとして扱う形になる。しかし、フルスクリーン化処理、画面ロック、ディスプレイのサイズの算出方法などは、Android API の変遷のため、BAG とはかなり異なっている。
また、メインループが update()
と present()
に分けてあったのを libGDX 同様、render()
メソッド一つにしたため、テキストのフレームワークとは相違している。
abstract class Game : Activity() {
// default 16:9
protected var horizontalSize = 640
protected var verticalSize = 360
protected lateinit var fastRenderView: FastRenderView
lateinit var touchHandler: TouchHandler
protected set
lateinit var graphics: Graphics
private set
lateinit var fileIO: FileIO
private set
lateinit var audio: Audio
private set
lateinit var currentScreen: Screen
private set
// フレームワークの利用者(プログラマー)が設定するアプリの初期スクリーンオブジェクト
abstract val startScreen: Screen
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// WakeLock に対する代替手法
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
// グラフィックス関係のセットアップ
val isLandscape =
(resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE)
val framebufferWidth = if (isLandscape) horizontalSize else verticalSize
val framebufferHeight = if (isLandscape) verticalSize else horizontalSize
val framebuffer = Bitmap.createBitmap(
framebufferWidth, framebufferHeight, Bitmap.Config.RGB_565
)
val displayWidth = AppWindowUtil.getScreenWidth(this, true)
val displayHeight = AppWindowUtil.getScreenHeight(this, true)
// タッチ座標をゲームの論理座標に変換するための縮尺
val scaleX = framebufferWidth.toFloat() / displayWidth
val scaleY = framebufferHeight.toFloat() / displayHeight
fastRenderView = FastRenderView(this)
setContentView(fastRenderView)
fastRenderView.setup(this, framebuffer)
graphics = Graphics(assets, framebuffer)
// マルチタッチ関係のセットアップ
touchHandler = TouchHandler(fastRenderView, scaleX, scaleY)
// ファイル入出力ドライバーの初期化
fileIO = FileIO(this)
// オーディオドライバーの初期化
audio = Audio(this)
currentScreen = startScreen
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus) {
val decorView = window.decorView
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val windowInsetsController = decorView.windowInsetsController
windowInsetsController!!.systemBarsBehavior =
WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
windowInsetsController.hide(
WindowInsets.Type.statusBars() // status bar の on/off
or WindowInsets.Type.navigationBars() // navigation bar の on/off
)
// status/navigation bar の on/off に依らないレイアウティング
window.setDecorFitsSystemWindows(false)
} else {
decorView.systemUiVisibility = (
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
or View.SYSTEM_UI_FLAG_FULLSCREEN // status bar の on/off
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
)
}
}
}
override fun onResume() {
super.onResume()
// onResume では Screen が先、RenderView が後
currentScreen.resume()
fastRenderView.resume()
}
override fun onPause() {
// onPause では RenderView が先、Screen が後
fastRenderView.pause()
if (isFinishing) {
currentScreen.dispose()
} else {
currentScreen.pause()
}
super.onPause()
}
fun setScreen(screen: Screen) {
currentScreen.pause()
currentScreen.dispose()
screen.resume()
screen.render(0f)
currentScreen = screen
}
}
この Game クラスはあくまでも抽象(abstract)クラスである。このフレームワークを利用して個々のゲームを開発する時に、この Game クラスを継承した GameActivity クラスを実装して、StartScreen となる Screen オブジェクト名を指定する作業だけは必要である。
また、ADT (Eclipse) ではいくつかの警告が表示されるかもしれない。複数バージョンの API 用に if ブロックで対応してあるのが原因である。気になるのであれば、API バージョンに関する警告を無視するようにアノテーションを付せばいい。
BAG のインターフェースは Screen となっている。
このインターフェースは、フレームワークを利用するゲーム開発者が、ゲームのプログラムを記述するための雛形であり、プラットフォームに依存した実装とは無縁なものなので、フレームワーク側としてはインターフェースのみとなる。
ほとんど変更する余地はないが、update()
と present()
に分けてあったのを render()
に統一したため、前 2 者の定義を削除して、代わりに次の render の定義を追加する作業が必要となる:
abstract class Screen(protected val game: Game) {
abstract fun dispose()
abstract fun resume()
abstract fun pause()
abstract fun render(deltaTime: Float)
}
以上で、自家版の Android 専用ゲーム開発フレームワークは完成した。
フレームワークの利用にあたっては、Game クラスを継承した GameActivity を作成し、その中で開始画面となる Screen オブジェクトのクラス名を指定する。具体的には次のような感じである。開始画面のクラス名が StartScreen.kt ならば:
class GameActivity : Game() {
override val startScreen: Screen
get() = StartScreen(this)
}
あとは色々な Screen オブジェクトを用意して、それらの Screen を入れ替えたりしながら、ゲームプログラムが成立することになる。Screen オブジェクトの実装内容は、ゲーム開発者次第である。