Android & Kotlinの環境でマインスイーパーを開発する方法を説明します。
今回は、Chronometerを使ってゲームのプレイ時間(経過時間)を表示します。
そそたた
Chronometerは、経過時間を表示する時に使用するAndroid SDKのGUIコンポーネントです。
動作イメージ
タイルを開いたらプレイ時間をカウントアップしていき、爆弾を開いたらカウントアップを停止します。
ソースコード
開発環境は次の通りです。
PC | MacBook Pro(2016年モデル) |
IDE | Android Studio 4.0.1 |
Android SDK | minSdkVersion 16 targetSdkVersion 30 |
言語 | Kotlin 1.3.72 |
package com.sosotata.minesweeper.ui.widgets
import android.os.SystemClock
import android.widget.Chronometer
/**
* プレイ時間カウンター
*/
class PlayTimeCounter (chronometer: Chronometer) {
private val timer = chronometer
private var offsetPlayTimer = 0L
private var isStartPlayTimer = false
private var stopTime = 0L
fun start() {
if (!isStartPlayTimer) {
isStartPlayTimer = true
offsetPlayTimer = 0
stopTime = 0
timer.base = SystemClock.elapsedRealtime()
timer.start()
}
}
fun stop() {
if (isStartPlayTimer) {
timer.stop()
isStartPlayTimer = false
stopTime = SystemClock.elapsedRealtime() - timer.base
}
}
fun pause() {
if (isStartPlayTimer && offsetPlayTimer == 0L) {
timer.stop()
offsetPlayTimer = SystemClock.elapsedRealtime() - timer.base
}
}
fun resume() {
if (isStartPlayTimer) {
timer.base = SystemClock.elapsedRealtime() - offsetPlayTimer
offsetPlayTimer = 0
timer.start()
}
}
fun reset() {
timer.base = SystemClock.elapsedRealtime()
offsetPlayTimer = 0
}
fun currentTime(): Long {
return if (isStartPlayTimer) {
if (offsetPlayTimer > 0) {
offsetPlayTimer
} else {
SystemClock.elapsedRealtime() - timer.base
}
} else {
stopTime
}
}
}
package com.sosotata.minesweeper.ui.main
import android.graphics.BitmapFactory
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.Animation
import android.view.animation.ScaleAnimation
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProviders
import com.sosotata.minesweeper.R
import com.sosotata.minesweeper.model.TileController
import com.sosotata.minesweeper.model.TileType
import com.sosotata.minesweeper.ui.widgets.PlayTimeCounter
import kotlinx.android.synthetic.main.game_fragment.*
class GameFragment : Fragment() {
companion object {
fun newInstance() = GameFragment()
}
private lateinit var viewModel: GameViewModel
private var playTimeCounter: PlayTimeCounter? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.game_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)
viewModel.tile = TileController.create(TileType.Square)
viewModel.tile.startedGame = {
playTimeCounter?.start()
}
viewModel.tile.gameOver = {
playTimeCounter?.stop()
resetButton.setImageBitmap(BitmapFactory.decodeResource(context?.resources, R.drawable.reset_button_gameover))
}
viewModel.tile.initialize(requireContext())
playTimeCounter = PlayTimeCounter(playTimer)
squareView.initialize(viewModel.tile)
resetButton.setOnClickListener {
val btnEffect = ScaleAnimation(
1.0f, 0.9f, 1.0f, 0.9f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f)
btnEffect.duration = 100
btnEffect.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation) {}
override fun onAnimationRepeat(animation: Animation) {}
override fun onAnimationEnd(animation: Animation) {
// アニメーション完了後にやらせる処理を書く
}
})
resetButton.startAnimation(btnEffect)
}
}
override fun onResume() {
playTimeCounter?.resume()
super.onResume()
}
override fun onPause() {
playTimeCounter?.pause()
super.onPause()
}
}
解説
スマホアプリは、電話がかかってきてバックグラウンドに遷移したり、画面ロックすることを想定すると、プレイ時間を一時停止→再開する機能が必要になりますがChronometerにそのような機能がありません。
そのため、Chronometerを使って一時停止や再開ができるプレイ時間カウンターとしてPlayTimeCounterクラスを自作します。
PlayTimeCounterクラスのインターフェースは次の通りです。
start() | カウントを開始する |
stop() | カウントを停止する |
pause | カウントを一時停止する |
resume | カウントを再開する |
reset | カウントをリセットする |
currentTime | 現在のプレイ時間を取得する |
Chronometerの使い方
Chronometerのインターフェースは単純でやることは次の4つのみです。
- ベース時間としてSystemClock.elapsedRealtime()をbaseに設定
- start()で開始
- stop()で停止
- プレイ時間をSystemClock.elapsedRealtime() – timer.baseで取得
一時停止は、次のようにChronometerをいったん停止してプレイ時間を保存しておきます。
fun pause() {
if (isStartPlayTimer && offsetPlayTimer == 0L) {
timer.stop()
offsetPlayTimer = SystemClock.elapsedRealtime() - timer.base
}
}
再開時に、SystemClock.elapsedRealtime()から一時停止した時間を引くことでプレイ時間を再開できます。
fun resume() {
if (isStartPlayTimer) {
timer.base = SystemClock.elapsedRealtime() - offsetPlayTimer
offsetPlayTimer = 0
timer.start()
}
}
そそたた
ターゲットのAPIレベルによって使えるメソッドが変わってきますのでChronometerのドキュメントを確認してください。
PlayTimeCounterの使い方
ゲーム開始時にカウントを開始。
viewModel.tile.startedGame = {
playTimeCounter?.start()
}
バックグラウンド遷移時にカウントを一時停止。
override fun onPause() {
playTimeCounter?.pause()
super.onPause()
}
バックグラウンドから復帰時にカウントを再開。
override fun onResume() {
playTimeCounter?.resume()
super.onResume()
}
ゲームオーバー時にカウントを停止。
viewModel.tile.gameOver = {
playTimeCounter?.stop()
resetButton.setImageBitmap(BitmapFactory.decodeResource(context?.resources, R.drawable.reset_button_gameover))
}
コメント