Chapter 3

Project — Bouncing Ball

In the last chapter, we worked through Int, Float, and Boolean in the abstract. Boxes with labels on them. That is a fine way to learn what variables are, but it doesn't quite show you what they do.

The point of this chapter is to take those building blocks and make them produce something visible. By the end, you will have a bright red ball bouncing around the screen, and every single thing about that ball — its position, its size, its speed — will be controlled by a variable you can change.

This is the first project where you will feel that satisfying click of theory turning into pixels. It is also the first thing you have built that genuinely behaves like a game: it moves, it reacts to its surroundings, and it keeps going until you stop it.

In this chapter, we will:

Let's build it.

Code for this chapter: the complete project lives in the Chapter 3 folder of the accompanying code at github.com/EliteIntegrity/Learning-Kotlin-by-Building-Android-Games. If anything goes wrong as you type, you can check your version against that one.

Setting Up the Project

Open Android Studio and follow the exact same steps you used in Chapter 1.

  1. New Project, pick Empty Views Activity.
  2. Name the application BouncingBall.
  3. Make sure the Language is set to Kotlin.
  4. Click Finish.

Once the project syncs, expand the app > java > com.example.bouncingball folder, open MainActivity.kt, and delete all the code inside it, just like we did last time.

Coding the Game

We are going to build the program piece by piece.

The Setup and Variables

Start by typing or pasting this at the very top of your empty MainActivity.kt file:

package com.example.bouncingball

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(BouncingBallView(this))
    }
}

class BouncingBallView(context: Context) : View(context) {

    private val paint = Paint()

    // Ball variables
    var ballX = 200f
    var ballY = 200f
    var ballVelX = 15f
    var ballVelY = 12f
    val ballRadius = 50f

    init {
        paint.color = Color.RED
        paint.style = Paint.Style.FILL
    }

The code will have errors because there is a missing ending curly brace }. We will add it in the final block of code — or feel free to add it now. This is often the case throughout the book as we add code in multiple chunks. Feel free to add the code in one go by copying it from GitHub and then follow along with the chunks in the book just to learn about it.

This should look very familiar from Chapter 1. We create our MainActivity and tell it to display our custom BouncingBallView.

Inside BouncingBallView, we define the variables for our ball. ballX and ballY are the ball's position on the screen. We start the ball at coordinates (200, 200).

ballVelX and ballVelY are the ball's velocity — how many pixels it should move each time the screen redraws. A positive ballVelX means the ball is moving to the right. A positive ballVelY means it is moving down. If we made them negative, the ball would move left and up. The behavior of the ball is wired directly to the values in these boxes.

We also use a val for ballRadius. The radius won't change while the game is running, so making it a val is perfect. Notice the f on the end of every number. We are using Float because Android's drawing system requires precise decimal numbers.

Finally, we have an init block. This block of code runs exactly once when the BouncingBallView is first created. It is the perfect place to set our paintbrush color to red so we don't have to do it over and over again.

The Game Loop and Movement

Now, let's add the drawing and movement logic. Type this right below the init block, but make sure it is still inside the BouncingBallView class (before the final closing } brace).

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        // Clear the screen
        canvas.drawColor(Color.DKGRAY)

        // Update the ball's position
        ballX += ballVelX
        ballY += ballVelY

Here, we clear the screen to dark gray. Then, we use the compound assignment operator += that we met in Chapter 2.

ballX += ballVelX means "take the current horizontal position, add the velocity to it, and store the new position." If ballX is 200 and ballVelX is 15, the ball moves to 215. The next time this runs, it moves to 230. Two lines of math, and our ball is smoothly moving diagonally across the screen.

Bouncing Off the Walls

If we stopped there, the ball would slide off the bottom-right of the screen and never come back. We need to check if it has hit a wall and reverse its direction. Continue adding this code:

        // Bounce off the left wall
        if (ballX - ballRadius < 0f) {
            ballX = ballRadius
            ballVelX = -ballVelX
        }

        // Bounce off the right wall
        if (ballX + ballRadius > width.toFloat()) {
            ballX = width.toFloat() - ballRadius
            ballVelX = -ballVelX
        }

        // Bounce off the top wall
        if (ballY - ballRadius < 0f) {
            ballY = ballRadius
            ballVelY = -ballVelY
        }

        // Bounce off the bottom wall
        if (ballY + ballRadius > height.toFloat()) {
            ballY = height.toFloat() - ballRadius
            ballVelY = -ballVelY
        }

We will cover the if statement properly in the next chapter, but the logic is intuitive: "If this condition is true, run the code inside the braces."

The condition ballX - ballRadius < 0f is asking, "Has the left edge of the ball gone past the left edge of the screen?" If yes, we park the ball flush against the wall and flip the horizontal velocity with -ballVelX.

That little minus sign does the heavy lifting. If ballVelX was 15f, it is now -15f. The ball is now moving left!

Notice we use width.toFloat() and height.toFloat(). Android views automatically know their own width and height, but they store them as whole numbers (Int). We use .toFloat() to convert them so they play nicely with our Float math.

Drawing and Looping

Finally, let's draw the ball and keep the engine running. Add this to finish the onDraw function:

        // Draw the ball
        canvas.drawCircle(ballX, ballY, ballRadius, paint)

        // Tell Android to redraw this view immediately
        invalidate()
    }
}

We draw the circle at our new coordinates. Then we hit the magic function: invalidate().

Normally, Android only calls onDraw when it thinks the screen needs updating. By calling invalidate() at the end of onDraw, we are telling Android, "Hey, this screen is already invalid (out of date). Draw it again immediately."

Android will finish drawing the frame, realize it is invalid, and instantly call onDraw again. The position updates, the ball moves, it draws, and it invalidates again. We have just created a game loop.

It is worth being honest about one thing here. Our ball moves a fixed number of pixels per frame, and invalidate() draws frames as fast as the device can manage. That means the exact same code will run the ball a little faster on a powerful phone than on an older one. For a bouncing ball nobody will notice or care. But when we start building real games in Act 3, we will replace this simple invalidate() approach with a proper game loop running on its own thread, one that measures how much time has passed between frames and moves things accordingly. That is what keeps a game running at the same speed everywhere. For now, invalidate() is the perfect tool to get pixels moving with code we already understand.

Playing the Game

Hit the green Play button at the top of Android Studio.

An emulator window will open. You will see a dark gray screen, and a red ball will drift away from the center, hit the edge, and bounce. It will keep bouncing forever until you close the app.

The Bouncing Ball app running in the emulator, with a red ball mid-bounce near the edge of the screen.
The Bouncing Ball program running — the red ball bounces endlessly, its every movement driven by the variables you just wrote.

Sit and watch it for a minute. It is strangely satisfying.

Experimenting

Now that you have it running, the best thing you can do is mess with it. Each of these is a one-line change. Stop the app, change the code, and run it again to see what happens.

None of this changes the structure of the program. But the result on screen is dramatically different. That is what variables give you. The structure stays put; the behavior flexes.

Summary

You have taken the variables we covered in the last chapter and turned them into a bouncing ball. The position is a variable. The velocity is a variable. The size is a constant (val). Tweaking any of them changes the game's behavior immediately.

That tight loop between "change a value" and "see something different on screen" is the most important thing a beginner programmer can experience. It is the moment code stops feeling like a foreign language and starts feeling like a set of dials you are learning to turn.

In the next chapter, we will teach our code to make decisions: if, else, and logical operators. That is the missing piece that turns "things on screen" into "things on screen that react." Then in Chapter 5, we will use those decisions to build something a lot more interactive than a ball minding its own business.

AI Exercise (Optional)

If you would like to take this project a little further with AI help, try this low-stakes challenge.

Open your AI chatbot and try a prompt like this:

"I have a small Kotlin Android program that uses a custom Canvas View to bounce a single red ball around the screen. I have variables for ballX, ballY, ballVelX, ballVelY, and ballRadius. I'm a beginner who has only just learned about variables and basic math operators — no loops, no complex functions, no arrays yet. Show me how to add a second ball of a different color, using only more variables of the same kinds I already have. Don't introduce any new Kotlin features or complex game loops. Paste your full View code so I can compare it to mine."

Notice the constraints! We explicitly told the AI not to use advanced features like arrays or objects. Without that constraint, the AI will happily give you a complex List<Ball> architecture that you don't know how to read yet.

When the AI comes back, read every line. Can you spot the new variables? Can you follow the bouncing logic for the second ball? Make the AI work to your level.