Этап 5: анимация поедания еды
В момент съедания на месте еды расходится затухающее кольцо. - Animatable popProgress (0->1 за один укус), запуск из step() - отрисовка кольца в drawBoard() через drawCircle + Stroke, радиус растёт, прозрачность падает с прогрессом - цвет кольца — цвет еды Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
05d65eab14
commit
da783d9f0d
@ -84,4 +84,8 @@ app/src/main/
|
|||||||
(низкая задержка). Смотри настройку `SoundPool` и `soundPool.play(...)`
|
(низкая задержка). Смотри настройку `SoundPool` и `soundPool.play(...)`
|
||||||
в `step()`; освобождение ресурса — в `DisposableEffect`.
|
в `step()`; освобождение ресурса — в `DisposableEffect`.
|
||||||
|
|
||||||
Идеи для следующих этапов: анимации поедания, пауза, выбор размера поля.
|
- **`snake-stage-5`** — анимация поедания: в момент укуса на месте еды
|
||||||
|
расходится затухающее кольцо. Смотри `Animatable` (`popProgress` 0→1),
|
||||||
|
запуск анимации в `step()` и отрисовку кольца в `drawBoard()`.
|
||||||
|
|
||||||
|
Идеи для следующих этапов: пауза, выбор размера поля, экран настроек.
|
||||||
|
|||||||
@ -2,6 +2,8 @@ package com.example.snake
|
|||||||
|
|
||||||
import android.media.AudioAttributes
|
import android.media.AudioAttributes
|
||||||
import android.media.SoundPool
|
import android.media.SoundPool
|
||||||
|
import androidx.compose.animation.core.Animatable
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.Canvas
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.gestures.detectDragGestures
|
import androidx.compose.foundation.gestures.detectDragGestures
|
||||||
@ -27,6 +29,7 @@ import androidx.compose.ui.geometry.Offset
|
|||||||
import androidx.compose.ui.geometry.Size
|
import androidx.compose.ui.geometry.Size
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||||
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@ -107,6 +110,11 @@ fun SnakeGame() {
|
|||||||
onDispose { soundPool.release() }
|
onDispose { soundPool.release() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Этап 5: анимация поедания — расходящееся затухающее кольцо на месте еды.
|
||||||
|
// popProgress идёт 0→1 за один укус; 1f означает «анимация завершена».
|
||||||
|
val popProgress = remember { Animatable(1f) }
|
||||||
|
var popCell by remember { mutableStateOf(food) }
|
||||||
|
|
||||||
fun reset() {
|
fun reset() {
|
||||||
snake = startSnake
|
snake = startSnake
|
||||||
dir = Cell(0, -1)
|
dir = Cell(0, -1)
|
||||||
@ -135,6 +143,12 @@ fun SnakeGame() {
|
|||||||
score++
|
score++
|
||||||
// звук поедания (left=1f, right=1f, prio=1, без зацикливания, обычная скорость)
|
// звук поедания (left=1f, right=1f, prio=1, без зацикливания, обычная скорость)
|
||||||
soundPool.play(eatSoundId, 1f, 1f, 1, 0, 1f)
|
soundPool.play(eatSoundId, 1f, 1f, 1, 0, 1f)
|
||||||
|
// запускаем анимацию вспышки на месте съеденной еды
|
||||||
|
popCell = nh
|
||||||
|
scope.launch {
|
||||||
|
popProgress.snapTo(0f)
|
||||||
|
popProgress.animateTo(1f, animationSpec = tween(durationMillis = 260))
|
||||||
|
}
|
||||||
if (score > best) {
|
if (score > best) {
|
||||||
best = score
|
best = score
|
||||||
// новый рекорд — сохраняем на диск (асинхронно, UI не блокируется)
|
// новый рекорд — сохраняем на диск (асинхронно, UI не блокируется)
|
||||||
@ -196,7 +210,7 @@ fun SnakeGame() {
|
|||||||
) {
|
) {
|
||||||
// Игровое поле
|
// Игровое поле
|
||||||
Canvas(modifier = Modifier.fillMaxSize()) {
|
Canvas(modifier = Modifier.fillMaxSize()) {
|
||||||
drawBoard(snake, food)
|
drawBoard(snake, food, popCell, popProgress.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Счёт сверху (+ индикатор скорости — видно ускорение)
|
// Счёт сверху (+ индикатор скорости — видно ускорение)
|
||||||
@ -231,7 +245,7 @@ private fun Overlay(title: String, subtitle: String, modifier: Modifier = Modifi
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Вся отрисовка поля внутри DrawScope. */
|
/** Вся отрисовка поля внутри DrawScope. */
|
||||||
private fun DrawScope.drawBoard(snake: List<Cell>, food: Cell) {
|
private fun DrawScope.drawBoard(snake: List<Cell>, food: Cell, popCell: Cell, popProgress: Float) {
|
||||||
val cell = minOf(size.width / COLS, size.height / ROWS)
|
val cell = minOf(size.width / COLS, size.height / ROWS)
|
||||||
val offX = (size.width - cell * COLS) / 2f
|
val offX = (size.width - cell * COLS) / 2f
|
||||||
val offY = (size.height - cell * ROWS) / 2f
|
val offY = (size.height - cell * ROWS) / 2f
|
||||||
@ -256,6 +270,18 @@ private fun DrawScope.drawBoard(snake: List<Cell>, food: Cell) {
|
|||||||
snake.forEachIndexed { idx, c ->
|
snake.forEachIndexed { idx, c ->
|
||||||
drawCell(c, if (idx == 0) HEAD else BODY, cell, offX, offY)
|
drawCell(c, if (idx == 0) HEAD else BODY, cell, offX, offY)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// анимация поедания: расходящееся затухающее кольцо на месте съеденной еды
|
||||||
|
if (popProgress < 1f) {
|
||||||
|
val center = Offset(offX + popCell.x * cell + cell / 2f, offY + popCell.y * cell + cell / 2f)
|
||||||
|
val radius = cell * (0.4f + popProgress * 1.4f)
|
||||||
|
drawCircle(
|
||||||
|
color = FOOD.copy(alpha = 1f - popProgress),
|
||||||
|
radius = radius,
|
||||||
|
center = center,
|
||||||
|
style = Stroke(width = cell * 0.16f)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun DrawScope.drawCell(c: Cell, color: Color, cell: Float, offX: Float, offY: Float) {
|
private fun DrawScope.drawCell(c: Cell, color: Color, cell: Float, offX: Float, offY: Float) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user