Create a Snake Game with Jetpack Compose
The classisc game Snake is one of the legendary games that once graced the screens of Nokia phones in the 90s. Now, with the development of Android technology and declarative frameworks like Jetpack Compose, we can revive this game with a modern design and more efficient code.
In this article, I will guide you though the porcess of creatign a snake game form scratch using Jetpack Compose, including game logic, motion controls, and rendering with the Canvas API. Let’s get starterd! 🔥
Step 1: Setting up a Jetpack Compose Project
If you don’t already have a Compose project, create a new project in Android Studio with the Empty Copose Activity template. Make sure the dependencies in build.gradle support Jetpack Compose:
dependencies {
implementation "androidx.compose.ui:ui:1.5.0"
implementation "androidx.compose.ui:ui-tooling:1.5.0"
implementation "androidx.compose.material:material:1.5.0"
}
Step 2: Data Structure and Game Logic
To start, we need to define the basic mode of our game, namely the snake’s position, food, and direction of movement.
enum class Direction { UP, DOWN, LEFT, RIGHT }
data class SnakeGameState(
val snake: List<Pair<Int, Int>> = listOf(Pair(5, 5)),
val food: Pair<Int, Int> = Pair(10, 10),
val direction: Direction = Direction.RIGHT,
val isGameOver: Boolean = false
)
Next, we create the snake movement logic.
fun moveSnake(state: SnakeGameState): SnakeGameState {
val head = state.snake.first()
val newHead = when (state.direction) {
Direction.UP -> head.copy(second = head.second - 1)
Direction.DOWN -> head.copy(second = head.second + 1)
Direction.LEFT -> head.copy(first = head.first - 1)
Direction.RIGHT -> head.copy(first = head.first + 1)
}
val newSnake = listOf(newHead) + state.snake.dropLast(1)
val isGameOver = newHead in newSnake.drop(1) ||
newHead.first !in 0..19 ||
newHead.second !in 0..19
return state.copy(snake = newSnake, isGameOver = isGameOver)
}
The code above sets the snake to move in the chosen direction, and checks whether the game ends because the snake hits itself or goes out of the screen boundary.
Step 3: Draw the Game with Canvas
Now we need to display the game using Canvas.
@Composable
fun SnakeGameCanvas(state: SnakeGameState) {
Canvas(modifier = Modifier.fillMaxSize()) {
val cellSize = size.minDimension / 20
// Snake picture
state.snake.forEach { (x, y) ->
drawRect(
color = Color.Green,
topLeft = Offset(x * cellSize, y * cellSize),
size = Size(cellSize, cellSize)
)
}
// Picture of food
drawRect(
color = Color.Red,
topLeft = Offset(state.food.first * cellSize, state.food.second * cellSize),
size = Size(cellSize, cellSize)
)
}
}
Step 4: Adding Input Controls
For the player to control the movement of the snake, we need to detect screen movement with pointerInput.
@Composable
fun DirectionController(onDirectionChange: (Direction) -> Unit) {
Box(modifier = Modifier
.fillMaxSize()
.pointerInput(Unit) {
detectTapGestures { offset ->
val (x, y) = offset
when {
y < size.height / 2 -> onDirectionChange(Direction.UP)
y > size.height / 2 -> onDirectionChange(Direction.DOWN)
x < size.width / 2 -> onDirectionChange(Direction.LEFT)
else -> onDirectionChange(Direction.RIGHT)
}
}
}
)
}
Next, we combine SnakeGameCanvas and DirectionController in one main component:
@Composable
fun SnakeGameScreen() {
var gameState by remember { mutableStateOf(SnakeGameState()) }
var gameRunning by remember { mutableStateOf(true) }
LaunchedEffect(gameRunning) {
while (gameRunning && !gameState.isGameOver) {
delay(200L)
gameState = moveSnake(gameState)
}
}
Column(modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center) {
Box(modifier = Modifier.weight(1f)) {
SnakeGameCanvas(gameState)
}
DirectionController { newDirection ->
gameState = gameState.copy(direction = newDirection)
}
if (gameState.isGameOver) {
Button(
onClick = {
gameState = SnakeGameState()
gameRunning = true
},
modifier = Modifier.padding(16.dp)
) {
Text("Restart Game")
}
}
}
}
@Composable
fun SnakeGame() {
SnakeGameScreen()
}
Step 5: Run the Game in MainActivity.kt
Now, we set the MainActivity to run the game
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
SnakeGame()
}
}
}
🎉 Congratulations! You’ve just created your first snake game using Jetpack Compose!
Conclusion
With Jetpack Compose, we can build snake games more quickly, concisely, and modernly. The Canvas API allows us to easily draw game elements, while the state management in Compose makes game control simpler.