From da783d9f0dba03d728063fd132241f5c2e41a462 Mon Sep 17 00:00:00 2001 From: george Date: Wed, 17 Jun 2026 18:00:07 +0300 Subject: [PATCH] =?UTF-8?q?=D0=AD=D1=82=D0=B0=D0=BF=205:=20=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=BC=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BF=D0=BE=D0=B5=D0=B4?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=B5=D0=B4=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В момент съедания на месте еды расходится затухающее кольцо. - Animatable popProgress (0->1 за один укус), запуск из step() - отрисовка кольца в drawBoard() через drawCircle + Stroke, радиус растёт, прозрачность падает с прогрессом - цвет кольца — цвет еды Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 6 +++- .../main/java/com/example/snake/SnakeGame.kt | 30 +++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 20fdae6..ec19c40 100644 --- a/README.md +++ b/README.md @@ -84,4 +84,8 @@ app/src/main/ (низкая задержка). Смотри настройку `SoundPool` и `soundPool.play(...)` в `step()`; освобождение ресурса — в `DisposableEffect`. -Идеи для следующих этапов: анимации поедания, пауза, выбор размера поля. +- **`snake-stage-5`** — анимация поедания: в момент укуса на месте еды + расходится затухающее кольцо. Смотри `Animatable` (`popProgress` 0→1), + запуск анимации в `step()` и отрисовку кольца в `drawBoard()`. + +Идеи для следующих этапов: пауза, выбор размера поля, экран настроек. diff --git a/app/src/main/java/com/example/snake/SnakeGame.kt b/app/src/main/java/com/example/snake/SnakeGame.kt index 7c14093..c73a6ca 100644 --- a/app/src/main/java/com/example/snake/SnakeGame.kt +++ b/app/src/main/java/com/example/snake/SnakeGame.kt @@ -2,6 +2,8 @@ package com.example.snake import android.media.AudioAttributes 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.background 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.graphics.Color 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.text.font.FontWeight import androidx.compose.ui.unit.dp @@ -107,6 +110,11 @@ fun SnakeGame() { onDispose { soundPool.release() } } + // Этап 5: анимация поедания — расходящееся затухающее кольцо на месте еды. + // popProgress идёт 0→1 за один укус; 1f означает «анимация завершена». + val popProgress = remember { Animatable(1f) } + var popCell by remember { mutableStateOf(food) } + fun reset() { snake = startSnake dir = Cell(0, -1) @@ -135,6 +143,12 @@ fun SnakeGame() { score++ // звук поедания (left=1f, right=1f, prio=1, без зацикливания, обычная скорость) 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) { best = score // новый рекорд — сохраняем на диск (асинхронно, UI не блокируется) @@ -196,7 +210,7 @@ fun SnakeGame() { ) { // Игровое поле 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. */ -private fun DrawScope.drawBoard(snake: List, food: Cell) { +private fun DrawScope.drawBoard(snake: List, food: Cell, popCell: Cell, popProgress: Float) { val cell = minOf(size.width / COLS, size.height / ROWS) val offX = (size.width - cell * COLS) / 2f val offY = (size.height - cell * ROWS) / 2f @@ -256,6 +270,18 @@ private fun DrawScope.drawBoard(snake: List, food: Cell) { snake.forEachIndexed { idx, c -> 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) {