Android & Kotlinの環境でマインスイーパーを開発する方法を説明します。
今回は、地雷を開いた時にバイブレータを作動させてゲームオーバーを分かりやすく知らせる工夫をします。
ソースコード
開発環境は次の通りです。
PC | MacBook Pro(2016年モデル) |
IDE | Android Studio 4.0.1 |
Android SDK | minSdkVersion 21 targetSdkVersion 30 |
言語 | Kotlin 1.3.72 |
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sosotata.minesweeper">
<uses-permission android:name="android.permission.VIBRATE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity"
android:configChanges="orientation|screenSize"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
package com.sosotata.minesweeper.ui.main
import android.content.Context
import android.graphics.BitmapFactory
import android.os.Build
import android.os.Bundle
import android.os.VibrationEffect
import android.os.Vibrator
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()
animateButton {
resetButton.setImageBitmap(BitmapFactory.decodeResource(context?.resources, R.drawable.reset_button_gameover))
}
vibrate()
}
viewModel.tile.clearedGame = {
playTimeCounter?.stop()
animateButton {
resetButton.setImageBitmap(BitmapFactory.decodeResource(context?.resources, R.drawable.reset_button_victory))
}
}
viewModel.tile.updateBombCount = {count ->
bombCountText.text = "%03d".format(count)
}
viewModel.tile.initialize(requireContext())
playTimeCounter = PlayTimeCounter(playTimer)
squareView.initialize(viewModel.tile)
resetButton.setOnClickListener {
animateButton {
viewModel.tile.initialize(requireContext())
squareView.initialize(viewModel.tile)
resetButton.setImageBitmap(BitmapFactory.decodeResource(context?.resources, R.drawable.reset_button_normal))
playTimeCounter?.stop()
playTimeCounter?.reset()
}
}
}
override fun onResume() {
playTimeCounter?.resume()
super.onResume()
}
override fun onPause() {
playTimeCounter?.pause()
super.onPause()
}
private fun animateButton(Animated: () -> Unit)
{
val btnEffect = ScaleAnimation(
1.0f, 1.3f, 1.0f, 1.3f,
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) {
Animated()
}
})
resetButton.startAnimation(btnEffect)
}
private fun vibrate() {
val vibrator = context?.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
vibrator.let {
if (it.hasVibrator()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
it.vibrate(VibrationEffect.createOneShot(150, VibrationEffect.DEFAULT_AMPLITUDE))
} else {
it.vibrate(150)
}
}
}
}
}
解説
バイブレーターを使用するには、マニフェストに次のパーミッションを宣言する必要があります。
<uses-permission android:name="android.permission.VIBRATE" />
Android SDKのVibratorクラスのvibrateメソッドは、API Level 26でメソッドが新たに追加されて、古いものは全て非推奨(Deprecated)になっています。
そのため、動作環境がAPI Level 26以上かをチェックして使用するメソッドを呼び分けます。
private fun vibrate() {
val vibrator = context?.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
vibrator.let {
if (it.hasVibrator()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
it.vibrate(VibrationEffect.createOneShot(150, VibrationEffect.DEFAULT_AMPLITUDE))
} else {
it.vibrate(150)
}
}
}
}
そそたた
開発中のマインスイーパー は、minSdkVersionが21のためこのように呼び分けていますが、minSdkVersionがそもそも26以上の場合は不要です。
最後にゲームオーバーの通知を受けた時に自作したvibrateメソッドを実行すれば完成です。
viewModel.tile.gameOver = {
playTimeCounter?.stop()
animateButton {
resetButton.setImageBitmap(BitmapFactory.decodeResource(context?.resources, R.drawable.reset_button_gameover))
}
vibrate()
}
コメント