Этап 10: джойстик-управление пальцем
Змейка непрерывно поворачивает в сторону смещения пальца от точки касания, пока ведёшь по экрану (а не один поворот на отпускание свайпа). - обработка в onDrag (а не onDragEnd): turn() вызывается на каждое движение - направление по доминирующей оси смещения от origin; мёртвая зона 28dp - общий turn() с запретом разворота на 180°; быстрый флик тоже работает - подсказка на старте обновлена Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
64b47a5536
commit
f81a1a88a2
@ -7,7 +7,7 @@ Android-приложение на современном стеке (Kotlin + Je
|
|||||||
|
|
||||||
## Что внутри (итоговая версия на `main`)
|
## Что внутри (итоговая версия на `main`)
|
||||||
|
|
||||||
- Игровое поле-сетка 20×30, управление **свайпами**
|
- Игровое поле-сетка 20×30, управление пальцем как **джойстиком** (ведёшь — змейка поворачивает)
|
||||||
- Змейка растёт и **ускоряется** с ростом счёта; ведётся счёт, **уровень** и **рекорд** (сохраняется между запусками)
|
- Змейка растёт и **ускоряется** с ростом счёта; ведётся счёт, **уровень** и **рекорд** (сохраняется между запусками)
|
||||||
- **Бонус-еда** с таймером даёт больше очков; **препятствия** добавляются с каждым уровнем
|
- **Бонус-еда** с таймером даёт больше очков; **препятствия** добавляются с каждым уровнем
|
||||||
- Границы поля **телепортируют** на противоположную сторону (тор); смерть — от самой себя и от препятствий
|
- Границы поля **телепортируют** на противоположную сторону (тор); смерть — от самой себя и от препятствий
|
||||||
@ -65,8 +65,8 @@ app/src/main/
|
|||||||
|
|
||||||
| Действие | Жест |
|
| Действие | Жест |
|
||||||
|----------|------|
|
|----------|------|
|
||||||
| Начать игру | свайп или тап |
|
| Начать игру | касание/движение или тап |
|
||||||
| Повернуть | свайп ↑ ↓ ← → |
|
| Повернуть | **веди пальцем** как джойстиком (быстрый флик тоже работает) |
|
||||||
| Пауза / продолжить | тап по полю во время игры |
|
| Пауза / продолжить | тап по полю во время игры |
|
||||||
| Рестарт после проигрыша | тап по экрану |
|
| Рестарт после проигрыша | тап по экрану |
|
||||||
|
|
||||||
@ -88,6 +88,7 @@ app/src/main/
|
|||||||
| **`snake-stage-7`** | **Бонус-еда** с таймером (1 из 4), +3 очка, золотая, исчезает по времени | таймер в «шагах» (учитывает паузу) + `drawArc` | `bonusFood`/`bonusStepsLeft`, `step()`/`drawBoard()` |
|
| **`snake-stage-7`** | **Бонус-еда** с таймером (1 из 4), +3 очка, золотая, исчезает по времени | таймер в «шагах» (учитывает паузу) + `drawArc` | `bonusFood`/`bonusStepsLeft`, `step()`/`drawBoard()` |
|
||||||
| **`snake-stage-8`** | **Препятствия и уровни**: каждые 5 очков +порция стен; врезался — Game Over | генерация на `Set<Cell>`, коллизии, прогрессия | `obstacles`/`level`, `addObstacles()` |
|
| **`snake-stage-8`** | **Препятствия и уровни**: каждые 5 очков +порция стен; врезался — Game Over | генерация на `Set<Cell>`, коллизии, прогрессия | `obstacles`/`level`, `addObstacles()` |
|
||||||
| **`snake-stage-9`** | **Сквозь границы (тор)**: уходишь за край — появляешься с другой стороны | модульная арифметика координат `% COLS/ROWS` | `step()` |
|
| **`snake-stage-9`** | **Сквозь границы (тор)**: уходишь за край — появляешься с другой стороны | модульная арифметика координат `% COLS/ROWS` | `step()` |
|
||||||
|
| **`snake-stage-10`** | **Джойстик**: ведёшь пальцем — змейка непрерывно поворачивает в сторону смещения от точки касания | `detectDragGestures` с обработкой в `onDrag` (а не на отпускании) + мёртвая зона | `turn()`, блок `pointerInput` |
|
||||||
|
|
||||||
Посмотреть, что именно поменялось на этапе N, удобно diff'ом между ветками,
|
Посмотреть, что именно поменялось на этапе N, удобно diff'ом между ветками,
|
||||||
например для этапа 7:
|
например для этапа 7:
|
||||||
|
|||||||
@ -146,6 +146,15 @@ fun SnakeGame() {
|
|||||||
obstacles = emptySet()
|
obstacles = emptySet()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Сменить направление (для джойстика). Запрет разворота на 180°; первый ввод запускает игру.
|
||||||
|
fun turn(dx: Int, dy: Int) {
|
||||||
|
if (gameOver) return
|
||||||
|
if (!started) started = true
|
||||||
|
if (dx != 0 && dir.x == -dx) return
|
||||||
|
if (dy != 0 && dir.y == -dy) return
|
||||||
|
nextDir = Cell(dx, dy)
|
||||||
|
}
|
||||||
|
|
||||||
// Добавить порцию препятствий в свободные клетки (не у головы змейки).
|
// Добавить порцию препятствий в свободные клетки (не у головы змейки).
|
||||||
fun addObstacles(count: Int) {
|
fun addObstacles(count: Int) {
|
||||||
val head = snake.first()
|
val head = snake.first()
|
||||||
@ -255,28 +264,24 @@ fun SnakeGame() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Свайпы: смена направления (и старт первым свайпом)
|
// Джойстик: ведёшь пальцем — змейка непрерывно поворачивает в сторону
|
||||||
|
// смещения пальца от точки касания (доминирующая ось). Мёртвая зона
|
||||||
|
// у центра гасит дрожание.
|
||||||
.pointerInput(Unit) {
|
.pointerInput(Unit) {
|
||||||
var total = Offset.Zero
|
val deadZone = 28.dp.toPx()
|
||||||
|
var origin = Offset.Zero
|
||||||
detectDragGestures(
|
detectDragGestures(
|
||||||
onDragStart = {
|
onDragStart = { pos ->
|
||||||
total = Offset.Zero
|
origin = pos
|
||||||
if (!started && !gameOver) started = true
|
if (!started && !gameOver) started = true
|
||||||
},
|
|
||||||
onDragEnd = {
|
|
||||||
if (started && !gameOver) {
|
|
||||||
if (abs(total.x) > abs(total.y)) {
|
|
||||||
if (total.x > 0 && dir.x != -1) nextDir = Cell(1, 0)
|
|
||||||
else if (total.x < 0 && dir.x != 1) nextDir = Cell(-1, 0)
|
|
||||||
} else {
|
|
||||||
if (total.y > 0 && dir.y != -1) nextDir = Cell(0, 1)
|
|
||||||
else if (total.y < 0 && dir.y != 1) nextDir = Cell(0, -1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
) { change, drag ->
|
) { change, _ ->
|
||||||
total += drag
|
|
||||||
change.consume()
|
change.consume()
|
||||||
|
val off = change.position - origin
|
||||||
|
if (off.getDistance() >= deadZone) {
|
||||||
|
if (abs(off.x) > abs(off.y)) turn(if (off.x > 0) 1 else -1, 0)
|
||||||
|
else turn(0, if (off.y > 0) 1 else -1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
@ -299,7 +304,7 @@ fun SnakeGame() {
|
|||||||
|
|
||||||
// Оверлеи: старт, пауза и проигрыш
|
// Оверлеи: старт, пауза и проигрыш
|
||||||
if (!started) {
|
if (!started) {
|
||||||
Overlay(title = "ЗМЕЙКА", subtitle = "Свайпни, чтобы начать · тап — пауза", modifier = Modifier.align(Alignment.Center))
|
Overlay(title = "ЗМЕЙКА", subtitle = "Веди пальцем — поворот · тап — пауза", modifier = Modifier.align(Alignment.Center))
|
||||||
} else if (gameOver) {
|
} else if (gameOver) {
|
||||||
Overlay(title = "GAME OVER", subtitle = "Счёт: $score · тапни для рестарта", modifier = Modifier.align(Alignment.Center))
|
Overlay(title = "GAME OVER", subtitle = "Счёт: $score · тапни для рестарта", modifier = Modifier.align(Alignment.Center))
|
||||||
} else if (paused) {
|
} else if (paused) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user