Building the Flappy Musk.eteer Arcade Game with Jetpack Compose | by Nirbhay Pherwani | Oct, 2023
The GameScreen
composable is the central component responsible for managing the game play logic in the Flappy Musketeer game. This code file defines the behavior of the game during game play, including handling user input, updating the game state, and rendering game elements.
Let’s break down the GameScreen
composable step by step and explain each part of the code with relevant code snippets —
1. State Initialization
// Game state and scores initialization
var gameState by remember { mutableStateOf(GameState.NOT_STARTED) }
var score by remember { mutableLongStateOf(0L) }
var lastScore by remember { mutableLongStateOf(preferencesManager.getData("last_score", 0L)) }
var bestScore by remember { mutableLongStateOf(preferencesManager.getData("best_score", 0L)) }
var birdOffset by remember { mutableStateOf(0.dp) }
var birdRect by remember { mutableStateOf(Rect(0f, 0f, 64.dp.value, 64.dp.value)) }
In this section, we initialize various game state variables such as gameState
, score
, lastScore
, bestScore
, birdOffset
, and birdRect
. These variables are used to track the game’s progress and the position of the bird.
2. Pipe (Obstacles) Dimensions Initialization
var pipeDimensions by remember {
mutableStateOf(Triple(0.1f, 0.4f, 0.5f))
}
Here, we initialize pipeDimensions
as a Triple
to store the weights of the top, gap, and bottom pipes. These weights determine the relative sizes of the pipes.
3. Update Score Callback
// Callback function to update the score
val updateScoreCallback: (Long) -> Unit = {
score += it
}
updateScoreCallback
is a callback function used to update the game’s score whenever necessary.
4. Bird Falling Animation
LaunchedEffect(key1 = birdOffset, gameState) {
while (gameState == GameState.PLAYING) {
delay(16)
birdOffset += 4.dp
}
}
In this section, we use a LaunchedEffect
to continuously update the birdOffset
and simulate the bird falling when the game is in the PLAYING
state.
5. Update Bird and Pipe Rectangles
// Callback function to update the bird's rectangle
val updateBirdRect: (birdRect: Rect) -> Unit = {
birdRect = it
pipeDimensions = getPipeDimensions(it, screenHeight)
}
These callback functions are responsible for updating the bird’s and pipes’ rectangles. These rectangles are crucial for collision detection between the bird and pipes.
6. Collision Detection
// Callback function to update the pipe's rectangle
val updatePipeRect: (pipeRect: Rect) -> Unit = {
if (!it.intersect(birdRect).isEmpty) {
// Handle collision with pipes
// ...
}
}
This callback function handles collision detection between the bird and pipes. When a collision is detected, the game state transitions to COMPLETED
and we updated the best score and last scores.
In addition to this we also navigate to the game over screen (see navigation section to learn more)
7. Tap Gesture Handling
// Tap Gesture Handling
Box(
modifier = Modifier
.fillMaxSize()
.pointerInput(Unit) {
detectTapGestures(
onTap = {
if (gameState == GameState.PLAYING) {
// Handle bird jump
coroutineScope.launch {
var offsetChange = 80.dp
while (offsetChange > 0.dp) {
birdOffset -= 2.dp
delay(2L)
offsetChange -= 2.dp
}
}
}
}
)
}
)
Here, we set up tap gesture handling. When the player taps the screen during game play, the bird’s position is updated to simulate a jump.
8. Game Layout
Box
Composable —
Box(
modifier = Modifier.fillMaxSize()
) {
// ...
}
- The game layout is encapsulated within a
Box
composable, allowing the placement of multiple components on top of each other.
2. Background —
Background()
- The
Background
composable renders the game’s background, setting the appropriate background image based on the selected theme.
3. Pipes —
Pipes(
updatePipeRect = updatePipeRect,
updateScoreCallback = updateScoreCallback,
gameState = gameState,
pipeDimensions = pipeDimensions.copy()
)
- The
Pipes
composable manages the generation and movement of pipes in the game. It handles collision detection with the bird and updates the score.
4. GameState Handling —
when (gameState) {
// ...
}
This section uses a when
expression to handle different game states —
- GameState.PLAYING — Displays the bird, score, and pause button during game play. Tapping the pause button triggers the pause callback.
- GameState.NOT_STARTED, GameState.COMPLETED — Shows the “Play” button to start or restart the game. Displays the last score and best score if available.
- GameState.PAUSE — Displays the “Play” button to resume the game.
5. Bird —
Bird(birdOffset, updateBirdRect)
- The
Bird
composable renders the bird character on the screen. ThebirdOffset
determines the bird’s vertical position, simulating its movement.
6. Play Button —
Play(onPlayCallback)
- The
Play
composable displays the “Play” button, allowing the player to start or resume the game when tapped. It triggers theonPlayCallback
when pressed.
7. Ground —
Ground("Flappy Score", score, enablePause = true, onPauseCallback)
- The
Ground
composable displays the game’s score and includes an optional pause button whenenablePause
is set totrue
. TheonPauseCallback
is triggered when the pause button is tapped.