Tutorial: Jogo de corrida mobile (android studio)
Hello World
Para começar é preciso um 'novo projeto' com uma activity em branco.
packageName square.motor
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@drawable/carro"
android:label="square motor">
<activity
android:name=".MainActivity"
android:exported="true"
android:screenOrientation="landscape"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
Pasta res
Exclua a pasta res e coloque esta pasta (descompactada) no mesmo lugar com o mesmo nome.
MainActivity
package square.motor
import android.app.Activity
import android.os.Bundle
class MainActivity : Activity() {
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(SquareMotor(this))
}
}
Base da classe principal
package square.motor
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Matrix
import android.graphics.Paint
import android.graphics.Typeface
import android.view.MotionEvent
import android.view.View
import java.util.Timer
import java.util.TimerTask
class SquareMotor(context: Context?) : View(context) {
}
Para exibir as imagens no tamanho correto
var btnPrevious: Bitmap? = null
var btnNext: Bitmap? = null
var logo: Bitmap? = null
var btncw: Bitmap? = null
var btnccw: Bitmap? = null
var star: Bitmap? = null
var opaqueStar: Bitmap? = null
var menu: Bitmap? = null
var car = arrayOfNulls<Bitmap>(36)
var tile = arrayOfNulls<Bitmap>(21)
var numbers = arrayOfNulls<Bitmap>(6)
var miniTiles = arrayOfNulls<Bitmap>(21)
Algumas imagens são rotacionadas
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
screenWidth = w.toFloat()
screenHeight = h.toFloat()
fractionScreenSize = screenHeight / 600
stage = 0
val typeface = Typeface.create(Typeface.SERIF, Typeface.BOLD)
paint.typeface = typeface
paint.textAlign = Paint.Align.CENTER
val r = resources
logo = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(r, R.drawable.logo), Math.ceil((645 * fractionScreenSize).toDouble()).toInt(), Math.ceil((450 * fractionScreenSize).toDouble()).toInt(), true)
btnccw = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(r, R.drawable.ccw), Math.ceil((120 * fractionScreenSize).toDouble()).toInt(), Math.ceil((120 * fractionScreenSize).toDouble()).toInt(), true)
btncw = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(r, R.drawable.cw), Math.ceil((120 * fractionScreenSize).toDouble()).toInt(), Math.ceil((120 * fractionScreenSize).toDouble()).toInt(), true)
btnPrevious = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(r, R.drawable.prev), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), true)
btnNext = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(r, R.drawable.prox), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), true)
car[0] = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(r, R.drawable.carro), Math.ceil((72 * fractionScreenSize).toDouble()).toInt(), Math.ceil((72 * fractionScreenSize).toDouble()).toInt(), true)
numbers[1] = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(r, R.drawable.n1), Math.ceil((240 * fractionScreenSize).toDouble()).toInt(), Math.ceil((240 * fractionScreenSize).toDouble()).toInt(), true)
numbers[2] = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(r, R.drawable.n2), Math.ceil((240 * fractionScreenSize).toDouble()).toInt(), Math.ceil((240 * fractionScreenSize).toDouble()).toInt(), true)
numbers[3] = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(r, R.drawable.n3), Math.ceil((240 * fractionScreenSize).toDouble()).toInt(), Math.ceil((240 * fractionScreenSize).toDouble()).toInt(), true)
numbers[4] = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(r, R.drawable.n4), Math.ceil((240 * fractionScreenSize).toDouble()).toInt(), Math.ceil((240 * fractionScreenSize).toDouble()).toInt(), true)
numbers[5] = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(r, R.drawable.n5), Math.ceil((240 * fractionScreenSize).toDouble()).toInt(), Math.ceil((240 * fractionScreenSize).toDouble()).toInt(), true)
var matrix: Matrix
for (x in 1..35) {
matrix = Matrix()
matrix.postRotate((x * 10).toFloat())
car[x] = Bitmap.createBitmap(car[0]!!, 0, 0, Math.ceil((72 * fractionScreenSize).toDouble()).toInt(), Math.ceil((72 * fractionScreenSize).toDouble()).toInt(), matrix, true)
}
tile[1] = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(r, R.drawable.linha), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), true)
matrix = Matrix()
matrix.postRotate(180f)
tile[2] = Bitmap.createBitmap(tile[1]!!, 0, 0, Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), matrix, true)
matrix = Matrix()
matrix.postRotate(90f)
tile[3] = Bitmap.createBitmap(tile[1]!!, 0, 0, Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), matrix, true)
matrix = Matrix()
matrix.postRotate(270f)
tile[4] = Bitmap.createBitmap(tile[1]!!, 0, 0, Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), matrix, true)
tile[5] = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(r, R.drawable.curvahorario), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), true)
tile[6] = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(r, R.drawable.curvaantihorario), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), true)
matrix = Matrix()
matrix.postRotate(90f)
tile[7] = Bitmap.createBitmap(tile[5]!!, 0, 0, Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), matrix, true)
matrix = Matrix()
matrix.postRotate(90f)
tile[8] = Bitmap.createBitmap(tile[6]!!, 0, 0, Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), matrix, true)
matrix = Matrix()
matrix.postRotate(180f)
tile[9] = Bitmap.createBitmap(tile[5]!!, 0, 0, Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), matrix, true)
matrix = Matrix()
matrix.postRotate(180f)
tile[10] = Bitmap.createBitmap(tile[6]!!, 0, 0, Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), matrix, true)
matrix = Matrix()
matrix.postRotate(270f)
tile[11] = Bitmap.createBitmap(tile[5]!!, 0, 0, Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), matrix, true)
matrix = Matrix()
matrix.postRotate(270f)
tile[12] = Bitmap.createBitmap(tile[6]!!, 0, 0, Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), matrix, true)
tile[13] = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(r, R.drawable.inicio), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), true)
tile[14] = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(r, R.drawable.chegada), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), true)
matrix = Matrix()
matrix.postRotate(270f)
tile[15] = Bitmap.createBitmap(tile[13]!!, 0, 0, Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), matrix, true)
matrix = Matrix()
matrix.postRotate(180f)
tile[16] = Bitmap.createBitmap(tile[13]!!, 0, 0, Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), matrix, true)
matrix = Matrix()
matrix.postRotate(90f)
tile[17] = Bitmap.createBitmap(tile[13]!!, 0, 0, Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), matrix, true)
matrix = Matrix()
matrix.postRotate(90f)
tile[18] = Bitmap.createBitmap(tile[14]!!, 0, 0, Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), matrix, true)
matrix = Matrix()
matrix.postRotate(180f)
tile[19] = Bitmap.createBitmap(tile[14]!!, 0, 0, Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), matrix, true)
matrix = Matrix()
matrix.postRotate(270f)
tile[20] = Bitmap.createBitmap(tile[14]!!, 0, 0, Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), Math.ceil((200 * fractionScreenSize).toDouble()).toInt(), matrix, true)
star = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(resources, R.drawable.star), (50 * fractionScreenSize).toInt(), (50 * fractionScreenSize).toInt(), true)
opaqueStar = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(resources, R.drawable.notstar), (50 * fractionScreenSize).toInt(), (50 * fractionScreenSize).toInt(), true)
menu = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(resources, R.drawable.menu), (64 * fractionScreenSize).toInt(), (64 * fractionScreenSize).toInt(), true)
miniTilesSize = screenHeight / 18
for (x in 1..20)
miniTiles[x] = Bitmap.createScaledBitmap(tile[x]!!, miniTilesSize.toInt(), miniTilesSize.toInt(), true)
// Timer().schedule(gameloop, 0, 10)
}
O método onDraw que exibe as imagens
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
paint.color = Color.rgb(255, 255, 255)
canvas.drawPaint(paint)
paint.color = Color.rgb(0, 0, 0)
if (time == -5) //splashscreen
//...
else if (time == -4) //tela 'passou de fase'
//...
else if (time == -3) //tela 'zerou'
//...
else if (time == -2) //tela 'não conseguiu'
//...
else if (time == -1) { //tela menu
//desenha minimapa
for (y in course[stage].indices)
for (x in course[stage][y].indices)
if (course[stage][y][x] != 0)
canvas.drawBitmap(miniTiles[course[stage][y][x]]!!, screenWidth / 2 - course[stage][y].size * miniTilesSize / 2 + x * miniTilesSize, screenHeight / 2 - course[stage].size * miniTilesSize / 2 + y * miniTilesSize, paint)
//desenha estrela
canvas.drawBitmap(star!!, screenWidth / 2 - 75 * fractionScreenSize, (screenHeight * 0.02).toFloat(), paint)
//desenha botao para mudar de fase
canvas.drawBitmap(btnPrevious!!, 40 * fractionScreenSize, (screenHeight - 300) / 2 * fractionScreenSize, paint)
}
else if (time > 0) { //tela gameplay
//desenha a pista
for (y in course[stage].indices)
for (x in course[stage][y].indices)
if (course[stage][y][x] != 0)
canvas.drawBitmap(tile[course[stage][y][x]]!!, carPositionX + x * 200 * fractionScreenSize + screenWidth / 2, carPositionY + y * 200 * fractionScreenSize + screenHeight / 2, paint)
//desenha o carro no angulo certo
canvas.drawBitmap(car[angle3 / 3]!!, (screenWidth - car[angle3 / 3]!!.width) / 2, (screenHeight - car[angle3 / 3]!!.height) / 2, paint)
//desenha o botão de virar
paint.color = Color.rgb(127, 127, 127)
if (ccwPressed) {
canvas.drawCircle(80 * fractionScreenSize, screenHeight - 80 * fractionScreenSize, 58 * fractionScreenSize, paint)
canvas.drawBitmap(btnccw!!, 20 * fractionScreenSize, screenHeight - 140 * fractionScreenSize, paint)
}
}
course[stage] é um array de int que representa o traçado da fase.
var course = arrayOf(
arrayOf(intArrayOf(12, 4, 6), intArrayOf(2, 0, 1), intArrayOf(2, 0, 1), intArrayOf(2, 0, 1), intArrayOf(2, 0, 1), intArrayOf(2, 0, 1), intArrayOf(19, 0, 16)),
\\ ...
\\ mais mapa das fases
\\ ...
\\ (acesse o tutorial no site original)
arrayOf(intArrayOf(12, 4, 6, 0, 12, 4, 4, 4, 6), intArrayOf(2, 0, 1, 0, 2, 0, 0, 0, 1), intArrayOf(2, 0, 9, 4, 7, 0, 11, 3, 8), intArrayOf(2, 0, 0, 0, 0, 0, 1, 0, 0), intArrayOf(10, 3, 5, 0, 14, 0, 9, 4, 6), intArrayOf(0, 0, 2, 0, 1, 0, 0, 0, 1), intArrayOf(12, 4, 7, 0, 1, 0, 11, 3, 8), intArrayOf(2, 0, 0, 0, 1, 0, 1, 0, 0), intArrayOf(10, 3, 3, 3, 8, 0, 9, 4, 6), intArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 1), intArrayOf(11, 3, 5, 0, 11, 3, 5, 0, 1), intArrayOf(1, 0, 2, 0, 1, 0, 2, 0, 1), intArrayOf(16, 0, 10, 3, 8, 0, 10, 3, 8))
)
0 representa grama.
1 representa uma seta para cima.
2 representa uma seta para baixo.
...
12 representa curva da esquerda para baixo.
onTouchEvent
O método que recebe e trata o toque do usuário também é 'seccionado/fatiado' pela variável time (tempo)
override fun onTouchEvent(me: MotionEvent): Boolean {
val x = me.x
val y = me.y
if (time == -1 && me.action == MotionEvent.ACTION_DOWN) {
if (x < 250 * fractionScreenSize) //qdo o user aperta btn muda de fase
stage = (stage + qttStages - 1) % qttStages
else if (x > screenWidth - 250 * fractionScreenSize)
stage = (stage + 1) % qttStages //qdo o user aperta btn proxima fase
else { //inicializa variáveis para inicio do gameplay
time = 0
angle3 = startAngle[stage]
carPositionX = (-stageStarts[stage][1] * 200 - 100) * fractionScreenSize
carPositionY = (-stageStarts[stage][0] * 200 - 100) * fractionScreenSize
cwPressed = false
ccwPressed = false
walkOut = false
}
}
if (time > 0) {
cwPressed = false
ccwPressed = false
if (x < 330 * fractionScreenSize && y > screenHeight - 380 * fractionScreenSize)
ccwPressed = true
if (x > screenWidth - 330 * fractionScreenSize && y > screenHeight - 380 * fractionScreenSize)
cwPressed = true
if (me.action == MotionEvent.ACTION_UP) {
cwPressed = false
ccwPressed = false
}
if (x > screenWidth - 130 * fractionScreenSize && y < 100 * fractionScreenSize)
time = -1
}
if (time < -1 && me.action == MotionEvent.ACTION_DOWN) time = -1
return true
}
TimerTask
O onSizeChanged invoca/chama/starta um timer
Timer().schedule(gameloop, 0, 10)
As variáveis deltaDistance determinam o delocamento do carrinho no eixo x e no eixo y
São 36 valores
Cada valor é referente a um ângulo de inclinação.
deltaDistanceY[3] = 6 //porque quando o carro está 30 graus ele desloca 6 pixel no eixo y
var gameloop: TimerTask = object : TimerTask() {
val deltaDistanceX = intArrayOf(0, -1, -2, -3, -4, -5, -6, -7, -8, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1)
val deltaDistanceY = intArrayOf(9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5, -6, -7, -8, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8)
override fun run() {
if (time >= 0) {
time++
if (ccwPressed) { //quando o botão de virar estiver pressinado
angle3--
if (angle3 == -3) angle3 = 105
}
if (cwPressed) {
angle3++
if (angle3 == 108) angle3 = 0
}
val upTile = intArrayOf(-1, -1) //variavel que armazena em qual pedaço da pista o carro está (x,y)
if (-carPositionY / 200 / fractionScreenSize > 0 && -carPositionX / 200 / fractionScreenSize > 0 && -carPositionY / 200 / fractionScreenSize < course[stage].size && -carPositionX / 200 / fractionScreenSize < course[stage][0].size && course[stage][(-carPositionY / 200 / fractionScreenSize).toInt()][(-carPositionX / 200 / fractionScreenSize).toInt()] != 0) {
upTile[0] = (-carPositionY / 200 / fractionScreenSize).toInt()
upTile[1] = (-carPositionX / 200 / fractionScreenSize).toInt()
}
if (upTile[0] == stageEnds[stage][0] && upTile[1] == stageEnds[stage][1]) { //quando o carro está no fim da pista
if (!walkOut && time < stageTimeLimit[stage]) {
time = -4
} else time = -2
} else {
// recalcula a posição do carro na pista
var deltaPositionX = deltaDistanceX[angle3 / 3].toFloat()
var deltaPositionY = deltaDistanceY[angle3 / 3].toFloat()
if (upTile[0] == -1) {
walkOut = true
deltaPositionX /= 3f
deltaPositionY /= 3f
}
if (time > 100) carPositionX += fractionScreenSize * deltaPositionX * maxSpeed[stage] * 0.0642f
if (time > 100) carPositionY += fractionScreenSize * deltaPositionY * maxSpeed[stage] * 0.0642f
}
}
invalidate()
}
}
invalidate é um método da classe view que invoca o onDraw
Código completo
Clique aqui e conheça o tutorial completo (gratuito open-source)
Arquivos do Projeto
Projeto Square Motor
Projeto Fluid Fuel