Chapter 5

Project — Interactive Tap

In Chapter 3, we built a ball that bounced around the screen entirely on its own. It was fun to watch, but it wasn't really a game because you couldn't interact with it.

In this chapter, we are going to fix that. We are going to build a new project that listens for your finger tapping the screen. We will use the if and else statements we learned in Chapter 4 to figure out exactly where you tapped, and we will change the background color and the direction of our moving shape instantly.

By the end of this chapter, you will understand how to make an Android app react to user input.

In this chapter, we will:

Code for this chapter: the complete project lives in the Chapter 5 folder of the accompanying code at github.com/EliteIntegrity/Learning-Kotlin-by-Building-Android-Games.

Setting Up the Project

Let's get the boilerplate out of the way.

  1. Create a New Project in Android Studio.
  2. Pick Empty Views Activity.
  3. Name the application TapResponder.
  4. Make sure the Language is set to Kotlin.
  5. Click Finish.

When it loads, open MainActivity.kt and delete the auto-generated code, just like always.

The Code

Type or paste the following code into your empty MainActivity.kt file.

package com.example.tapresponder

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

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

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

    private val paint = Paint()

    // Our shape's variables
    var squareX = 400f
    var squareY = 500f
    var squareVelX = 0f  // Starts stationary horizontally
    var squareVelY = 10f // Moving downwards
    val squareSize = 100f

    var backgroundColor = Color.DKGRAY

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

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

        // Draw the background using our variable
        canvas.drawColor(backgroundColor)

        // Update position
        squareX += squareVelX
        squareY += squareVelY

        // Bounce off the top and bottom walls
        if (squareY < 0f) {
            squareY = 0f
            squareVelY = -squareVelY
        } else if (squareY + squareSize > height.toFloat()) {
            squareY = height.toFloat() - squareSize
            squareVelY = -squareVelY
        }

        // Wrap around the left and right walls to keep it interesting
        if (squareX > width.toFloat()) {
            squareX = -squareSize
        } else if (squareX + squareSize < 0f) {
            squareX = width.toFloat()
        }

        // Draw the square
        canvas.drawRect(squareX, squareY, squareX + squareSize, squareY + squareSize, paint)

        // The Game Loop
        invalidate()
    }

    // This function catches user taps!
    override fun onTouchEvent(event: MotionEvent): Boolean {

        // We only care about the moment the finger touches the screen (ACTION_DOWN)
        if (event.action == MotionEvent.ACTION_DOWN) {

            // Did they tap the left half of the screen?
            if (event.x < width / 2) {
                // Move left
                squareVelX = -15f
                backgroundColor = Color.parseColor("#440000") // Dark red background
            } else {
                // They tapped the right half! Move right.
                squareVelX = 15f
                backgroundColor = Color.parseColor("#000044") // Dark blue background
            }
        }

        // Return true to tell Android we successfully handled the touch event
        return true
    }
}

Understanding the Code

Let's break down the new concepts in this project.

The View and Variables

Our TapResponderView starts with some familiar variables. This time, we are drawing a square instead of a circle, so we use squareX, squareY, and squareSize.

We also added a new variable: var backgroundColor = Color.DKGRAY. In previous chapters, we hard-coded the background color directly in the drawColor function. By storing it in a variable, we can change it later while the game is running!

The Game Loop and Drawing

Inside onDraw, we do the standard game loop updates. Notice the code for the left and right walls:

        // Wrap around the left and right walls to keep it interesting
        if (squareX > width.toFloat()) {
            squareX = -squareSize
        } else if (squareX + squareSize < 0f) {
            squareX = width.toFloat()
        }

Instead of bouncing, we are wrapping the square. If the square goes completely off the right side of the screen (squareX > width), we instantly teleport it to just outside the left side of the screen (-squareSize). This makes it look like it seamlessly traveled through a portal from one side of the screen to the other!

Catching User Taps

Now for the magic. Below onDraw, we added a brand new function:

    override fun onTouchEvent(event: MotionEvent): Boolean {

Just like Android automatically calls onDraw when it needs to paint the screen, Android automatically calls onTouchEvent whenever the user touches the screen. It passes us an event object that contains all the information about that touch (where it happened, whether they are swiping, etc.).

        if (event.action == MotionEvent.ACTION_DOWN) {

A single tap actually generates multiple events: putting your finger down, moving it slightly, and lifting it up. We only want to trigger our logic the exact moment the finger hits the glass, which Android calls ACTION_DOWN.

Now we use our if and else statements to make a decision:

            if (event.x < width / 2) {
                // Move left
                squareVelX = -15f
                backgroundColor = Color.parseColor("#440000") // Dark red
            } else {
                // Move right
                squareVelX = 15f
                backgroundColor = Color.parseColor("#000044") // Dark blue
            }

We ask the event object exactly where the finger touched horizontally (event.x). We divide our screen's width by 2 to find the middle. (Remember from Chapter 2 that width is an Int, so width / 2 uses integer division and rounds down — but we only need the rough middle of the screen here, so a fraction of a pixel either way makes no difference.)

If the tap's X coordinate is less than the middle, they tapped the left side of the screen! We immediately set the square's velocity to -15f (moving left) and change the background color variable to dark red.

If the tap was not on the left side, the else block runs. It must have been the right side! We set the velocity to 15f (moving right) and change the background to dark blue.

Finally, we return true to tell Android, "Thanks, we handled this tap, you don't need to pass it to anyone else."

Running the Game

Hit the green Play button.

When the app launches, the background will be dark gray, and a cyan square will be moving straight down (because squareVelX starts at 0).

Now, tap the left side of the screen! The background instantly snaps to dark red, and the square starts moving diagonally left. Tap the right side! The background snaps to dark blue, and the square darts to the right.

The TapResponder app running, showing a cyan square moving right against a dark blue background after a tap on the right side.
Tapping the right half of the screen sets the background to dark blue and sends the square dashing to the right — the game reacts instantly to your input.

You have built a fully interactive application.

Experimenting

As with the bouncing ball, the best way to make this code your own is to change it and see what happens. Each of these is a small tweak.

Notice again that none of these change the structure of the program. You are turning the dials, not rebuilding the machine.

Summary

You just combined variables, the game loop, and control flow into a working prototype.

By overriding onTouchEvent, you gained the ability to listen to the player. By using an if/else statement, you taught the game to analyze that input and make a decision. And by modifying variables (squareVelX and backgroundColor), you instantly changed the behavior and look of the game while it was running.

This input-update-draw cycle is the beating heart of game programming.

In the next chapter, we will learn about Loops (for and while). If if statements let us make decisions, loops let us do things over and over again without writing identical lines of code. It is how we handle 100 enemies on screen without writing 100 sets of variables!

AI Exercise (Optional)

Let's challenge the AI to help us expand our touch logic.

Open your AI chatbot and paste this prompt:

"I have a Kotlin Canvas app where I override onTouchEvent. Currently, I use an if/else statement to check if the user tapped the left half or right half of the screen using event.x < width / 2. I want to change it so the screen is split into thirds (left, middle, right). I want the ball to move left if they tap the left third, stop moving horizontally if they tap the middle third, and move right if they tap the right third. Can you show me the if / else if / else code block for this logic? Assume event.x and width are available. Keep it simple and explain the math."

Review the AI's math carefully. Splitting things into thirds requires dividing the width by 3, but the logic for the middle boundary can be tricky to conceptualize at first. If it doesn't make sense, ask the AI to explain it again with an example where the screen is 900 pixels wide!