diff --git a/README.md b/README.md index dfbe84a..20fdae6 100644 --- a/README.md +++ b/README.md @@ -79,4 +79,9 @@ app/src/main/ диск не блокирует UI. Смотри `LaunchedEffect` для загрузки рекорда и `scope.launch { ScoreStore.saveBest(...) }` при новом рекорде. -Идеи для следующих этапов: экранные кнопки-стрелки, анимации, звук, паузу. +- **`snake-stage-4`** — звук при поедании еды через **SoundPool**: короткий + WAV в `res/raw/eat.wav` грузится один раз, играется при каждом съедании + (низкая задержка). Смотри настройку `SoundPool` и `soundPool.play(...)` + в `step()`; освобождение ресурса — в `DisposableEffect`. + +Идеи для следующих этапов: анимации поедания, пауза, выбор размера поля. diff --git a/app/src/main/java/com/example/snake/SnakeGame.kt b/app/src/main/java/com/example/snake/SnakeGame.kt index 26e6c6e..7c14093 100644 --- a/app/src/main/java/com/example/snake/SnakeGame.kt +++ b/app/src/main/java/com/example/snake/SnakeGame.kt @@ -1,5 +1,7 @@ package com.example.snake +import android.media.AudioAttributes +import android.media.SoundPool import androidx.compose.foundation.Canvas import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectDragGestures @@ -9,6 +11,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf @@ -86,6 +89,24 @@ fun SnakeGame() { best = ScoreStore.bestFlow(context).first() } + // Этап 4: звук поедания еды через SoundPool (короткий эффект, низкая задержка). + // Пул и загруженный сэмпл создаём один раз и освобождаем при уходе с экрана. + val soundPool = remember { + SoundPool.Builder() + .setMaxStreams(4) + .setAudioAttributes( + AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_GAME) + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .build() + ) + .build() + } + val eatSoundId = remember { soundPool.load(context, R.raw.eat, 1) } + DisposableEffect(Unit) { + onDispose { soundPool.release() } + } + fun reset() { snake = startSnake dir = Cell(0, -1) @@ -112,6 +133,8 @@ fun SnakeGame() { snake = if (grew) listOf(nh) + snake else listOf(nh) + snake.dropLast(1) if (grew) { score++ + // звук поедания (left=1f, right=1f, prio=1, без зацикливания, обычная скорость) + soundPool.play(eatSoundId, 1f, 1f, 1, 0, 1f) if (score > best) { best = score // новый рекорд — сохраняем на диск (асинхронно, UI не блокируется) diff --git a/app/src/main/res/raw/eat.wav b/app/src/main/res/raw/eat.wav new file mode 100644 index 0000000..a17034d Binary files /dev/null and b/app/src/main/res/raw/eat.wav differ