Jetpack Compose Project for Beginners
This is a small project for learning purpose for the beginners who are learning Android App Development using Jetpack Compose. This project is build using the Android Studio Ladybug without using any third party dependency.
Game for Beginners
This Game for Beginners is being build using Jetpack Compose if you are a beginners then you should try to build this game. Learning through project is the best way to understand the basic of development. So, Lets try to build the Game for Beginners.
What are we going to build?
We are going to build a game to learn the basics of Jetpack Compose. The game will feature a screen where balls begin to fall when the play button is pressed and pause when it is pressed again. At the bottom of the screen, we will have a button to play and pause the game and a basket to catch the balls. At the top of the screen, we will have a text display showing how many balls are caught in the basket. For each ball caught, the score will increase by 1.
Steps:
- Create a New Project
- Select the Empty Activity(Compose) from the template and click on Next.
- In the Name section write the name of project in my case it is My First App.
- Now create two files one for Game Screen and second for data and functions for this game
- Connect your real Android device and Run the Game.
MainActivity
Copy this code →
package com.example.myfirstapp
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.ui.Modifier
import com.example.myfirstapp.ui.theme.MyFirstAppTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
MyFirstAppTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
GameForBeginners(
modifier = Modifier.padding(innerPadding)
)
}
}
}
}
}
GameScreen
Copy this code →
package com.example.myfirstapp
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
fun GameForBeginners(modifier: Modifier) {
val screenWidthPx = with(LocalDensity.current) { 520.dp.toPx() }
val screenHeightPx = with(LocalDensity.current) { 800.dp.toPx() }
var isRunning by remember { mutableStateOf(false) }
var gameState by remember {
mutableStateOf(
GameState(
stars = emptyList(),
basket = Basket(
x = screenWidthPx / 2 - 10f,
y = screenHeightPx - 100f,
width = 200f,
height = 200f
)
)
)
}
var lastStarTime by remember { mutableLongStateOf(0L) }
val starInterval = 1_000_000_000L // 1 second in nanoseconds
var starId by remember { mutableIntStateOf(0) }
LaunchedEffect(isRunning) {
while (isRunning) {
withFrameNanos { frameTimeNanos ->
val elapsedTime = frameTimeNanos - lastStarTime
val deltaTime = 0.016f // Assuming 60 FPS
val newStars = moveStars(gameState.stars, deltaTime)
val (remainingStars, scoreIncrement) = checkCatches(newStars, gameState.basket)
if (elapsedTime >= starInterval) {
lastStarTime = frameTimeNanos
starId += 1
gameState = gameState.copy(
stars = addNewStar(remainingStars, screenWidthPx, starId),
score = gameState.score + scoreIncrement
)
} else {
gameState = gameState.copy(
stars = remainingStars,
score = gameState.score + scoreIncrement
)
}
}
}
}
GameScreen(
gameState = gameState,
onMoveBasket = { deltaX, deltaY ->
gameState = gameState.copy(
basket = moveBasket(
gameState.basket,
deltaX,
deltaY,
screenWidthPx,
screenHeightPx
)
)
},
onToggleGame = { isRunning = !isRunning },
isRunning = isRunning
)
}
@Composable
fun GameScreen(
gameState: GameState,
onMoveBasket: (Float, Float) -> Unit,
onToggleGame: () -> Unit,
isRunning: Boolean
) {
Box(modifier = Modifier.fillMaxSize().background(color = Color.Black)) {
// Render stars
gameState.stars.forEach { star ->
Canvas(
modifier = Modifier
.size(star.size.dp)
.offset(star.x.dp, star.y.dp)
) {
drawCircle(color = star.color, radius = star.size / 2)
}
}
// This is basket to catch the balls
Image(
painter = painterResource(id = R.drawable.basket),
contentDescription = "Basket",
modifier = Modifier
.size(gameState.basket.width.dp, gameState.basket.height.dp)
.offset(gameState.basket.x.dp, gameState.basket.y.dp)
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consume()
onMoveBasket(dragAmount.x, dragAmount.y)
}
}
)
Button(
onClick = { onToggleGame() },
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(bottom = 16.dp)
) {
Text(text = if (isRunning) "Pause" else "Play")
}
Text(text = "Coding Bihar", color = Color.Green,
fontWeight = FontWeight.Bold, fontSize = 37.sp,
modifier = Modifier.padding(start = 20.dp, top = 70.dp)
.align(Alignment.TopStart))
// Display score
Text(
text = "Score: ${gameState.score}",
fontSize = 45.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.align(Alignment.TopCenter)
.padding(16.dp)
.background(color = Color.White, shape = RoundedCornerShape(4.dp))
.padding(8.dp),
color = Color.Red
)
}
}
GameData
Copy this code →
package com.example.myfirstapp
import androidx.compose.ui.graphics.Color
import kotlin.random.Random
data class Basket(
val x: Float,
val y: Float,
val width: Float,
val height: Float
)
data class GameState(
val stars: List = emptyList(),
val basket: Basket,
val score: Int = 0,
val speedMultiplier: Float = 1f)
data class Star(
val id: Int,
val x: Float,
val y: Float,
val size: Float,
val speed: Float,
val color: Color
)
fun getRandomColor(): Color {
val red = Random.nextInt(256)
val green = Random.nextInt(256)
val yellow = Random.nextInt(356)
return Color(red, green, yellow)
}
fun checkCatches(stars: List, basket: Basket): Pair, Int> {
val caughtStars = stars.filter { star ->
star.y + star.size >= basket.y &&
star.y <= basket.y + basket.height &&
star.x + star.size >= basket.x &&
star.x <= basket.x + basket.width
}
val newStars = stars - caughtStars.toSet()
val scoreIncrement = caughtStars.size
return newStars to scoreIncrement
}
fun moveStars(stars: List, deltaTime: Float): List {
return stars.map { star ->
star.copy(y = star.y + star.speed * deltaTime)
}
}
fun addNewStar(stars: List, screenWidth: Float, starId: Int): List {
val newStar = Star(
id = starId,
x = Random.nextFloat() * screenWidth,
y = 0f,
size = 80f,
speed = 320f,
color = getRandomColor()
)
return stars + newStar
}
fun moveBasket(basket: Basket, deltaX: Float, deltaY: Float, screenWidth: Float, screenHeight: Float): Basket {
val newX = (basket.x + deltaX).coerceIn(0f, screenWidth - basket.width)
val newY = (basket.y + deltaY).coerceIn(0f, screenHeight - basket.height)
return basket.copy(x = newX, y = newY)
}
Also Read