Runner game created in Kotlin and for Android Mobile systems.
Project Goal & Deliverable
The ideal product for this project is a Runner Game created from the Kotlin programming language. At this point, it is expected that you are well versed to how to use Kotlin and how to use it for Android Programming. This post is more about extra knowledge, namely, SQLite, Threads, and Glide.
Jump to: Code and Logic | DBHelper | Main | Scoreboard | Game | GameLogic
Game Mechanics
Jump: The player will be able to jump through obstacles as a form of player control.
Obstacle: Obstacles on the road will randomly spawn blocking the player. If the player touches an obstacle the game ends.
Money & Point System: Money on the road will randomly spawn for the player to pick up. If the player touches a money 1 point will be added to the player’s score.
Scoreboard: After the game ends, the player will be able to record their score with their name.
Technologies Used
Kotlin Programming Language is designed to be a lightweight version of the Java Programming language specifically designed for mobile android app development. While being based on Java it also utilizes JVM and Java Class Library. The recommended IDE to use for this language is IntelliJ IDEA. This being a programming language based on Java we will also compare differences on how syntax on these 2 differ
Android Studio is the official development environment for Mobile Android App Development. This IDE is officially supported by Google as well as based on the IntelliJ IDEA so importing assets and source files is a breeze.
SQLite is a lightweight SQL solution for quick database storage of information. It is a library that utilizes C to function as an embedded database. Generally follows PostgreSQL syntax but does not enforce type checking.
Program: Code and Logic
Unlike the first blog, we will go straight to android programming in this stage utilizing the same kind of techniques and knowledge as before. Android Studio will be used in this part.
Jump to: Resource setup | Image resources | Android Manifest
Resources setup:
build.gradle
Under Gradle Scripts in build.gradle add in the dependencies, build and sync:
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")
implementation ("com.github.bumptech.glide:glide:4.11.0") {
exclude group: "com.android.support"
}
themes.xml
Under app/res/values/themes open both themes files, add these tags:
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:windowFullscreen">true</item>
Image resources




Download the images above and go to app/res/drawable and drag and drop all files in the zip file.
bg_scroll.xml
Animation for the background scrolling. Go to app/res/anim/ and create bg_scroll.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromYDelta="0.0"
android:fromXDelta="0.0"
android:toYDelta="0.0"
android:toXDelta="-5000"
android:duration="10000"
android:repeatCount="infinite"
/>
</set>
AndroidManifest.xml
Under app/manifests open the manifest file add these tags in every activity tag that gets created:
android:screenOrientation="landscape"
This is how your AndroidManifest activities tags should look later after later activities are created:
<activity
android:name=".Game"
android:exported="false"
android:screenOrientation="landscape" />
<activity
android:name=".Scoreboard"
android:exported="false"
android:screenOrientation="landscape" />
<activity
android:name=".MainActivity"
android:exported="true"
android:screenOrientation="landscape">
DBHelper.kt | Class
This class will be focused on utilizing SQLite to create table, add row, and get data.
Jump to: Documentation | Dissection
package neilgilbert.gallardo.burglarrunner
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
class DBHelper(context: Context, factory: SQLiteDatabase.CursorFactory?) :
SQLiteOpenHelper(context, DATABASE_NAME, factory, DATABASE_VERSION) {
override fun onCreate(db: SQLiteDatabase) {
val query = ("CREATE TABLE " + TABLE_NAME + " ("
+ ID_COL + " INTEGER PRIMARY KEY, " +
NAME_COl + " TEXT," +
SCORE_COL + " INTEGER" + ")")
db.execSQL(query)
}
override fun onUpgrade(db: SQLiteDatabase, p1: Int, p2: Int) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME)
onCreate(db)
}
fun addScore(name : String, score : Int ){
val values = ContentValues()
values.put(NAME_COl, name)
values.put(SCORE_COL, score)
val db = this.writableDatabase
db.insert(TABLE_NAME, null, values)
db.close()
}
fun getScores(): Cursor? {
val db = this.readableDatabase
return db.rawQuery("SELECT * FROM " + TABLE_NAME + " ORDER BY " + SCORE_COL + " DESC", null)
}
companion object{
private val DATABASE_NAME = "BurglarRunner"
private val DATABASE_VERSION = 1
val TABLE_NAME = "scoreboard"
val ID_COL = "id"
val NAME_COl = "name"
val SCORE_COL = "score"
}
}
Documentation
- onCreate()
- An overridden method from SQLiteOpenHelper. Will create the necessary table if not already created.
- onUpgrade()
- An overridden method from SQLiteOpenHelper. Will drop related the related tables and call on create
- addScore()
- Requires 2 parameters, one String for a name and an Integer for a score. Will add a new row in the scoreboard table based on the passed parameters.
- getScores()
- Will retrieve all rows from the scoreboard table.
Code dissected:
Executing a script
In the code db.execSQL or db.rawQuery() executes a script. This method from the SQLite library, execute the passed SQL script passed as parameter.

Inserting a row into the database
In the code the combination of the use of ContentValues and db.insert() inserts a row into the database. First initialize the ContentValues() variable use .put(COL_NAME, value) to place values in the initialized variable.

Finally, get the .writableDatabase and use .insert(TABLE_NAME, null, values) and pass the ContentValues variable to initiate the insert row in the database.

Main | Activity
This activity will mostly function as UI. There will be 2 buttons that will lead to starting the game or go to the scoreboard
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Burglar Runner"
app:layout_constraintBottom_toBottomOf="@id/toScoreboard"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.44" />
<Button
android:id="@+id/toStartGame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="37dp"
android:text="Start Game"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2" />
<Button
android:id="@+id/toScoreboard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="To Scoreboard"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.499"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toStartGame" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt
package neilgilbert.gallardo.burglarrunner
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val toScoreboard = findViewById<Button>(R.id.toScoreboard);
toScoreboard.setOnClickListener {
val intent = Intent(this, Scoreboard::class.java);
startActivity(intent);
}
val toStartGame = findViewById<Button>(R.id.toStartGame);
toStartGame.setOnClickListener {
val intent = Intent(this, Game::class.java);
startActivity(intent);
}
}
}
Scoreboard | Activity
This activity’s function will be mostly about displaying the scoreboard data. In this page there will also be button to start a new game again or go back to the main menu.
activity_scoreboard.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:context=".Scoreboard">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TableRow
android:layout_width="match_parent"
android:layout_height="38dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/btnBack"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Back To Main Menu"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
<Button
android:id="@+id/btnRetr"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Play Again"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.021"
app:layout_constraintStart_toEndOf="@+id/btnBack"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TableLayout
android:id="@+id/scoreBoard"
android:layout_width="729dp"
android:layout_height="361dp">
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/nameHead"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Name" />
<TextView
android:id="@+id/scoreHead"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Score" />
</LinearLayout>
</TableRow>
</TableLayout>
</TableRow>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Scoreboard.kt
Jump to: Documentation | Dissection
package neilgilbert.gallardo.burglarrunner
import android.annotation.SuppressLint
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.*
class Scoreboard : AppCompatActivity() {
val db = DBHelper(this, null)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_scoreboard)
val scoreBoard = findViewById<TableLayout>(R.id.scoreBoard);
refreshScoreboard(scoreBoard)
findViewById<Button>(R.id.btnRetr).setOnClickListener {
val intent = Intent(this, Game::class.java);
startActivity(intent);
finish()
}
findViewById<Button>(R.id.btnBack).setOnClickListener {
finish()
}
}
var scoreRows = arrayListOf<TableRow>();
@SuppressLint("Range")
fun refreshScoreboard(scoreboard : TableLayout){
for(r in scoreRows)
{
scoreboard.removeView(r)
}
val cursor = db.getScores()
cursor!!.moveToFirst()
var tableRow = TableRow(this)
var rowCols = LinearLayout(this)
var nameCol = TextView(this)
var scoreCol = TextView(this)
rowCols.orientation = LinearLayout.HORIZONTAL;
if(cursor.count>0){
nameCol.text = cursor.getString(cursor.getColumnIndex(DBHelper.NAME_COl))
scoreCol.text = cursor.getString(cursor.getColumnIndex(DBHelper.SCORE_COL))
}
rowCols.addView(nameCol)
rowCols.addView(scoreCol)
tableRow.addView(rowCols)
scoreRows.add(tableRow)
scoreboard.addView(tableRow)
while(cursor.moveToNext()){
tableRow = TableRow(this)
rowCols = LinearLayout(this)
nameCol = TextView(this)
scoreCol = TextView(this)
rowCols.orientation = LinearLayout.HORIZONTAL;
nameCol.text = cursor.getString(cursor.getColumnIndex(DBHelper.NAME_COl))
scoreCol.text = cursor.getString(cursor.getColumnIndex(DBHelper.SCORE_COL))
rowCols.addView(nameCol)
rowCols.addView(scoreCol)
tableRow.addView(rowCols)
scoreRows.add(tableRow)
scoreboard.addView(tableRow)
}
cursor.close()
}
}
Documentation
- refreshScoreboard()
- Takes a parameter of a table layout, initializes a db instance and retrieves all rows. After that it will use the cursor made from the getScores()
Code dissected:
Utilizing cursors from a DBHelper
First, initialize a variable that will utilize the return value from the DBHelper.

Next, make sure the cursor is at the first row with .moveToFirst().

Next, to get data from the cursor use .get<datatype>(), for this case .getString() will be enough for our purposes. To make sure the correct column is retrieved use .getColumnIndex() with the column name as parameter.

To iterate to the rest of the rows use .moveToNext() and do the same operations as stated before.

Each time a row is retrieved, initialize views as follows:

Game | Activity
This activity is where the game itself happens. Several things are happening here. There will be a helper class named GameLogic along the the backend Game. Several things are happening between these 2 classes, they will be responsible in displaying and updating the positions of the player, obstacle, and money. Also responsible in displaying the score. Responsible in handling conditions such as adding to the score if the player touches a money and ending the game if player touches an obstacle. Finally, responsible in prompting the user for a name and recording the score with the entered name.
activity_game.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/gameDisplay"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".Game">
<ImageView
android:id="@+id/bg"
android:layout_width="2676dp"
android:layout_height="700dp"
android:layout_marginStart="100dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.062"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@drawable/bgv2" />
<LinearLayout
android:layout_width="57dp"
android:layout_height="26dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:orientation="horizontal"
android:background="#E6919191"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/scrLbl"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Score:" />
<TextView
android:id="@+id/scr"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0" />
</LinearLayout>
<Button
android:id="@+id/gamePrompt"
android:layout_width="0dp"
android:layout_height="0dp"
android:backgroundTint="#00000000"
android:text=" "
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Game.kt
Jump to: Documentation | Dissection
package neilgilbert.gallardo.burglarrunner
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.DisplayMetrics
import android.view.MotionEvent
import android.view.animation.AnimationUtils
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
class Game : AppCompatActivity() {
var display : ConstraintLayout? = null;
var scoreDisplay : TextView? = null;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val game = GameLogic(this);
setContentView(R.layout.activity_game)
display = findViewById(R.id.gameDisplay)
scoreDisplay = findViewById(R.id.scr)
val displayMetrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(displayMetrics)
game.setScreenDimensions(displayMetrics.widthPixels, displayMetrics.heightPixels)
game.start()
val gamePrompt = findViewById<Button>(R.id.gamePrompt)
gamePrompt.setOnTouchListener { view, motionEvent ->
// Task here
when (motionEvent.action){
MotionEvent.ACTION_DOWN -> {
println("input ON")
game.inputDown()
}
MotionEvent.ACTION_UP -> {
println("input OFF")
game.inputUp()
}
}
true
}
val bg_scroll = AnimationUtils.loadAnimation(this, R.anim.bg_scroll)
findViewById<ImageView>(R.id.bg).startAnimation(bg_scroll)
}
fun recordScore(pName : String, score : Int){
DBHelper(this, null).addScore(pName, score)
val intent = Intent(this, Scoreboard::class.java);
startActivity(intent);
}
fun endSession(){
finish()
}
}
Documentation
- recordScore()
- Has 2 parameters for the name and score. Quickly initializes a DBHelper and evokes .addScore() utilizing the parameters. Finally, initializes a Scoreboard class and sends the user there to show the new record.
- endSession()
- Ends the current Game session.
GameLogic.kt
Jump to: Documentation | Dissection
package neilgilbert.gallardo.burglarrunner
import android.app.AlertDialog
import android.content.Context
import android.content.DialogInterface
import android.os.Looper
import android.text.InputType
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import com.bumptech.glide.Glide
import kotlinx.coroutines.delay
import kotlin.random.Random
class GameLogic(g : Game): Thread(){
private var gameLoop = true;
private val g = g;
private var screenWidth = 0;
private var screenHeight = 0;
private val GAME_SPEED = 50;
private var gameCounter = 0;
private val SPAWN_SPEED = 25;
private var spawnCounter = 0;
private var player = ImageView(g)
private var PLAYER_JUMPHEIGHT = 200;
private var playerScore = 0;
private var playerloc_x = 250;
private var playerloc_y = 500;
private val PLAYER_SIZES = 400;
private var playerJump = false;
private var obstacle = arrayListOf<ImageView>();
private var obstacleXLocs = arrayListOf<Int>();
private var obstacleYLocs = arrayListOf<Int>();
private val OBS_FAIL_RAD = 10;
private val OBS_LIMIT = 1;
private val OBS_SIZES = 250;
private var money = arrayListOf<ImageView>();
private var moneyXLocs = arrayListOf<Int>();
private var moneyYLocs = arrayListOf<Int>();
private val MON_COLLECT_RAD = 10;
private val MON_LIMIT = 1;
private val MON_SIZES = 250;
override fun run() {
addPlayer()
while (gameLoop)
{
gameCounter++;
println("Game Loop RUNNING")
// Check if update speed is valid
if(gameCounter>0 && gameCounter%GAME_SPEED == 0){
gameCounter = 0;
spawnCounter++;
// Spawn check
if(spawnCounter>0 && spawnCounter%SPAWN_SPEED == 0){
spawnCounter = 0;
if(obstacle.size > 0){
if(MON_LIMIT > money.size){
if(Random.nextInt(0, 100) > 90){
addMoney()
}
}
}
if(OBS_LIMIT > obstacle.size){
if(Random.nextInt(0, 100) > 90){
addObstacle()
}
}
}
// OBSTACLE Method Calls
updateObstaclePosition();
if(checkIfObsCollided()){
removeObstacle(getCollidedObstacle())
endGame()
}
// MONEY Method Calls
updateMoneyPosition();
if(checkIfMonCollided()){
removeMoney(getCollidedMoney())
playerScore++;
g.runOnUiThread {
g.scoreDisplay!!.text = playerScore.toString();
}
}
for(mon in money)
{
val monIndex = money.indexOf(mon)
if(moneyXLocs.get(monIndex) <= 0){
removeMoney(mon)
}
}
// PLAYER Method Calls
updatePlayerPosition();
}
}
recordScore()
}
/////// OBSTACLE Functions
private fun addObstacle(){
var obs = ImageView(g)
obs.setImageResource(R.drawable.obstacle)
obstacle.add(obs)
obstacleXLocs.add(screenWidth)
obstacleYLocs.add(playerloc_y)
g.runOnUiThread {
g.display!!.addView(obs)
obs.layoutParams.width = OBS_SIZES;
obs.layoutParams.height = OBS_SIZES;
}
}
private fun removeObstacle(obs : View?){
var obsIndex = obstacle.indexOf(obs)
obstacle.removeAt(obsIndex)
obstacleXLocs.removeAt(obsIndex)
obstacleYLocs.removeAt(obsIndex)
g.runOnUiThread {
g.display!!.removeView(obs)
}
}
private fun updateObstaclePosition(){
for(obs in obstacle)
{
val obsIndex = obstacle.indexOf(obs)
if(obstacleXLocs.get(obsIndex) > 0) {
obstacleXLocs.set(obsIndex, obstacleXLocs.get(obsIndex)-1)
} else {
removeObstacle(obs);
break;
}
obs.x = obstacleXLocs.get(obsIndex).toFloat();
obs.y = obstacleYLocs.get(obsIndex).toFloat();
}
}
private fun checkIfObsCollided() : Boolean{
for(obs in obstacle)
{
if( (Math.abs(player.x-obs.x) <= OBS_FAIL_RAD) &&
(Math.abs(player.y-obs.y) <= OBS_FAIL_RAD) ){
return true;
}
}
return false;
}
private fun getCollidedObstacle() : View?{
for(obs in obstacle)
{
if( (Math.abs(player.x-obs.x) <= OBS_FAIL_RAD) &&
(Math.abs(player.y-obs.y) <= OBS_FAIL_RAD) ){
return obs;
}
}
return null;
}
/////// MONEY Functions
private fun addMoney(){
var mon = ImageView(g)
g.runOnUiThread {
Glide.with(g).load(R.drawable.money).into(mon);
}
money.add(mon)
moneyXLocs.add(screenWidth)
moneyYLocs.add(playerloc_y)
g.runOnUiThread {
g.display!!.addView(mon)
mon.layoutParams.width = MON_SIZES;
mon.layoutParams.height = MON_SIZES;
}
}
private fun removeMoney(mon : View?){
var monIndex = money.indexOf(mon)
money.removeAt(monIndex)
moneyXLocs.removeAt(monIndex)
moneyYLocs.removeAt(monIndex)
g.runOnUiThread {
g.display!!.removeView(mon)
}
}
private fun updateMoneyPosition(){
for(mon in money)
{
val monIndex = money.indexOf(mon)
if(moneyXLocs.get(monIndex) > 0) {
moneyXLocs.set(monIndex, moneyXLocs.get(monIndex)-1)
} else {
removeMoney(mon);
break;
}
mon.x = moneyXLocs.get(monIndex).toFloat();
mon.y = moneyYLocs.get(monIndex).toFloat();
}
}
private fun checkIfMonCollided() : Boolean{
for(mon in money)
{
if( (Math.abs(player.x-mon.x) <= MON_COLLECT_RAD) &&
(Math.abs(player.y-mon.y) <= MON_COLLECT_RAD) ){
return true;
}
}
return false;
}
private fun getCollidedMoney() : View?{
for(mon in money)
{
if( (Math.abs(player.x-mon.x) <= MON_COLLECT_RAD) &&
(Math.abs(player.y-mon.y) <= MON_COLLECT_RAD) ){
return mon;
}
}
return null;
}
/////// PLAYER Functions
private fun addPlayer(){
g.runOnUiThread {
Glide.with(g).load(R.drawable.burglar).into(player);
}
g.display!!.addView(player)
player.layoutParams.width = PLAYER_SIZES;
player.layoutParams.height = PLAYER_SIZES;
player.x = playerloc_x.toFloat();
player.y = playerloc_y.toFloat();
}
private fun updatePlayerPosition(){
if(playerJump){
if (Math.abs(player.y-playerloc_y.toFloat()) >= PLAYER_JUMPHEIGHT){
playerJump = false;
} else {
player.y--;
}
} else {
if (player.y >= playerloc_y.toFloat()){
player.y = playerloc_y.toFloat()
} else {
player.y++;
}
}
}
/////// INPUT Functions
fun inputDown() {
playerJump = true;
}
fun inputUp() {
playerJump = false;
}
/////// GAME Functions
fun endGame(){
gameLoop = false;
println("Game Loop ENDED.")
}
fun recordScore(){
if(playerScore > 0){
g.runOnUiThread {
val builder: AlertDialog.Builder = android.app.AlertDialog.Builder(g)
builder.setTitle("Record Score?")
val inPName = EditText(g)
inPName.setHint("Enter Name")
inPName.inputType = InputType.TYPE_CLASS_TEXT
builder.setView(inPName)
builder.setPositiveButton("OK", DialogInterface.OnClickListener { dialog, which ->
// Here you get get input text from the Edittext
g.recordScore(inPName.text.toString(), playerScore)
g.endSession()
})
builder.setNegativeButton("Cancel", DialogInterface.OnClickListener { dialog, which ->
dialog.cancel()
g.endSession()
})
builder.show()
}
} else {
g.endSession()
}
}
fun setScreenDimensions(width : Int, height : Int){
screenWidth = width;
screenHeight = height;
}
}
Documentation
- run()
- Inherited from the Thread library. Starts the game and initiates the game loop calls the necessary methods for each loop.
- addObstacle()
- Adds necessary entries to the arrays obstacle, obstacleXLocs and obstacleYLocs. Finally adds the obstacle to the display in the app screen.
- removeObstacle()
- Removes necessary entries from the arrays obstacle, obstacleXLocs and obstacleYLocs. And removes obstacle from the display in the app screen.
- updateObstaclePosition()
- Updates the obstacle location on screen as well as updates the location in the entries in obstacleXLocs and obstacleYLocs.
- checkIfObsCollided()
- Checks if any obstacle has collided with the player
- getCollidedObstacle()
- Gets an obstacle that has collided with the player
- addMoney()
- Adds necessary entries to the arrays money, moneyXLocs and moneyYLocs. Finally adds the money to the display in the app screen.
- removeMoney()
- Removes necessary entries from the arrays money, moneyXLocs and moneyYLocs. And removes money from the display in the app screen.
- updateMoneyPosition()
- Updates the money location on screen as well as updates the location in the entries in moneyXLocs and moneyYLocs.
- checkIfMonCollided()
- Checks if any money has collided with the player
- getCollidedMoney()
- Gets a money that has collided with the player
- addPlayer()
- Adds a player to the display in the app screen and initializes the necessary
- updatePlayerPosition()
- Updates the player position on screen with respect to if the player input. If player input is true will simulate player jumping, if not will simulate falling or running on ground.
- inputDown()
- Primary player input to tick if player is jumping. Use this to initiate a jump.
- inputUp()
- Primary player input to tick if player is jumping. Use this to if player has ended the jump.
- endGame()
- Ends game and calls record score to record the playerScore.
- recordScore()
- Builds a custom prompt with a TextField and records the name with the score into the database if OK is selected.
- setScreenDimensions()
- Sets the screen dimensions of the playarea.
Code dissected:
Utilizing Kotlin Threads
First, make the class inherit the Thread class. Next, override the run() class.


Finally in the class you want the thread to run call the class with the .start() method

Avoiding IllegalArgumentException
Normally if you attempt to manipulate the display int GameLogic Thread we created you would get the following exception:

In our special case we need the thread to manipulate elements in the game display so we need to utilize .runOnUiThread() like so:

Building a Custom Popup Dialog Box
To build a custom popup dialog: First, initialize the AlertDialog.Builder().

To set the prompt title use .setTitle().

To set the hint use .setHint().

To have a custom field initialize an EditText and pass it to the .setView().


To setup the OK and Cancel buttons, use .setPositiveButton() and .setNegativeButton().

Finally to show the built prompt use .show()

Generating a Random number
To generate a random number, use Random.nextInt(). First parameter is the first possible value and the second parameter is the last possible value.

Utilizing the Glide library for .gif
To even use .gif in android apps you need to import the Glide Library. Add these lines in the build.gradle app level:

After building the gradle and synching the project, to add the .gif file to an ImageView, use Glide.with(this).load(Res_ID).into(imgView) like:

GitHub Repository: https://github.com/neilgilbertg/burglarrunner-kotlin

