Давайте напишем простую мини игру "Лопни шарик" для Android на Kotlin. Мы создадим приложение, где шарики появляются на экране, двигаются вверх, и их можно лопать касанием.
1. Создание проекта в Android Studio.
- Запусти Android Studio.
- Выбери New Project → Empty Activity.
- Укажи:
- Name: Balloonpop
- Package name: com.example.balloonpop
- Language: Kotlin
- Minimum SDK: API 21 (Android 5.0)
2. Файл манифеста (AndroidManifest.xml):
//----------------------------------------------
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.balloonpop">
<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/Theme.Balloonpop">
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:exported="true"
android:screenOrientation="fullSensor"
tools:ignore="DiscouragedApi">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
//----------------------------------------------
3. activity_main.xml:
//----------------------------------------------
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.balloonpop.BalloonGame
android:id="@+id/gameView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
//----------------------------------------------
4. Balloon.kt (Класс шарика).
//----------------------------------------------
package com.example.balloonpop
class Balloon(
var x: Float,
var y: Float,
var radius: Float,
val speed: Float,
val color: Int,
var isPopped: Boolean = false
)
//----------------------------------------------
5. BalloonGame.kt (Основная логика игры).
//----------------------------------------------
package com.example.balloonpop
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.SurfaceView
import kotlin.math.pow
import kotlin.math.sqrt
import kotlin.random.Random
class BalloonGame(context: Context, attrs: AttributeSet?) : SurfaceView(context, attrs), Runnable {
private var gameThread: Thread? = null
private var isPlaying = false
private val balloons = mutableListOf<Balloon>()
private val paint = Paint()
private var score = 0
// Настройки игры
private val maxBalloons = 10
private val spawnRate = 2000L // 2 секунды
private var lastSpawnTime = 0L
override fun run() {
while (isPlaying) {
update()
draw()
controlFPS()
}
}
private fun update() {
// Обновление позиций шариков
balloons.forEach { balloon ->
if (!balloon.isPopped) {
balloon.y -= balloon.speed
}
}
// Удаление вышедших за пределы экрана
balloons.removeAll { it.y + it.radius < 0 || it.isPopped }
// Создание новых шариков
if (System.currentTimeMillis() - lastSpawnTime > spawnRate && balloons.size < maxBalloons) {
spawnBalloon()
lastSpawnTime = System.currentTimeMillis()
}
}
private fun draw() {
if (holder.surface.isValid) {
val canvas: Canvas = holder.lockCanvas()
canvas.drawColor(Color.WHITE)
balloons.forEach { balloon ->
if (!balloon.isPopped) {
paint.color = balloon.color
canvas.drawCircle(balloon.x, balloon.y, balloon.radius, paint)
}
}
// Отрисовка счета
paint.color = Color.BLACK
paint.textSize = 50f
canvas.drawText("Score: $score", 50f, 50f, paint)
holder.unlockCanvasAndPost(canvas)
}
}
private fun controlFPS() {
try {
Thread.sleep(16) // ~60 FPS
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
private fun spawnBalloon() {
val radius = Random.nextFloat() * 50 + 50
val x = Random.nextFloat() * (width - radius * 2) + radius
val speed = Random.nextFloat() * 5 + 2
val color = Color.argb(
255,
Random.nextInt(256),
Random.nextInt(256),
Random.nextInt(256)
)
balloons.add(Balloon(x, height.toFloat() + radius, radius, speed, color))
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_DOWN) {
val x = event.x
val y = event.y
balloons.forEach { balloon ->
val dx = x - balloon.x
val dy = y - balloon.y
val distance = sqrt(dx.pow(2) + dy.pow(2))
if (!balloon.isPopped && distance < balloon.radius) {
balloon.isPopped = true
score += 10
}
}
}
return true
}
fun resume() {
isPlaying = true
gameThread = Thread(this)
gameThread?.start()
}
fun pause() {
isPlaying = false
gameThread?.join()
}
}
//----------------------------------------------
6. MainActivity.kt
//----------------------------------------------
package com.example.balloonpop
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
private lateinit var gameView: BalloonGame
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
gameView = BalloonGame(this, null)
setContentView(gameView)
}
override fun onResume() {
super.onResume()
gameView.resume()
}
override fun onPause() {
super.onPause()
gameView.pause()
}
}
//----------------------------------------------
Пояснение кода:
- SurfaceView используется для рисования в отдельном потоке, что важно для плавной анимации.
- Игровой цикл реализован в методе run():
- update() - обновление позиций шариков.
- draw() - отрисовка всех объектов.
- controlFPS() - контроль частоты кадров. - Обработка касаний:
- При касании проверяем попадание в шарик с помощью формулы расстояния между точками.
- Лопнувшие шарики помечаются флагом isPopped. - Генерация шариков:
- Случайный размер, цвет и скорость.
- Появление снизу экрана. - Управление жизненным циклом:
- Запуск и остановка игрового потока при паузе/возобновлении активности.
Советы для новичков:
- SurfaceView требует управления потоком вручную.
- Все операции с холстом должны быть внутри lockCanvas() и unlockCanvasAndPost().
- Для сложных игр рассмотрите использование игровых движков (LibGDX, Unity).
- Можно добавить:
- Звуки при лопании.
- Анимацию взрыва.
- Уровни сложности.
- Сохранение рекордов.
Этот код дает базовую реализацию игры. Вы можете модифицировать параметры шариков, добавить новые функции и улучшить графику по своему усмотрению.