I could use help how to solve my issue of ball not fully bouncing of walls of my maze. The ball will go inside the walls, but won’t be able to pass through them completely, bouncing from inside. Below I provide code of my app:
MainActivity.kt
`package com.example.mazeball
import android.app.Activity
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.os.Bundle
import android.util.DisplayMetrics
import android.view.View
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import kotlin.math.max
import kotlin.math.min
class MainActivity : Activity(), SensorEventListener {
private lateinit var sensorManager: SensorManager
private var accelerometer: Sensor? = null
private lateinit var btnRestart: Button
private lateinit var mDrawable: ImageView
private lateinit var mazeView: MazeView
private lateinit var darkOverlay: View
private lateinit var youWonText: TextView
private lateinit var btnRestartGame: Button
private lateinit var finishLine: ImageView
companion object {
var x = 0
var y = 0
}
private var velocityX = 0f
private var velocityY = 0f
private val DAMPING_FACTOR = 0.65f
private val BOUNCE_REDUCTION_FACTOR = 0.5f
private val SPEED_REDUCTION_FACTOR = 0.01f
private var isPaused = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mazeView = findViewById(R.id.mazeView)
mDrawable = findViewById(R.id.ball)
btnRestart = findViewById(R.id.btnRestart)
darkOverlay = findViewById(R.id.darkOverlay)
youWonText = findViewById(R.id.youWonText)
btnRestartGame = findViewById(R.id.btnRestartGame)
finishLine = findViewById(R.id.finish_line)
btnRestart.setOnClickListener {
resetBallPosition()
}
btnRestartGame.setOnClickListener {
resumeGame()
}
sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
initializeMaze()
}
private fun resetBallPosition() {
x = 0
y = 0
velocityX = 0f
velocityY = 0f
updateImageViewPosition()
if (isPaused) {
resumeGame()
}
}
private fun initializeMaze() {
val maze = arrayOf(
intArrayOf(0, 0, 0, 0, 0, 1, 0, 0, 0, 1),
intArrayOf(1, 0, 1, 1, 0, 1, 0, 1, 0, 1),
intArrayOf(1, 0, 1, 1, 0, 0, 0, 1, 0, 1),
intArrayOf(0, 0, 1, 1, 0, 1, 1, 1, 0, 1),
intArrayOf(0, 0, 0, 1, 1, 1, 0, 0, 0, 0),
intArrayOf(1, 1, 0, 1, 0, 0, 0, 1, 1, 0),
intArrayOf(0, 0, 0, 1, 1, 1, 1, 1, 0, 0),
intArrayOf(0, 1, 0, 0, 0, 1, 0, 0, 0, 1),
intArrayOf(0, 1, 1, 1, 0, 1, 0, 1, 1, 1),
intArrayOf(0, 0, 1, 0, 0, 1, 0, 0, 0, 0),
intArrayOf(1, 1, 1, 0, 1, 1, 1, 1, 1, 0),
intArrayOf(0, 0, 0, 0, 0, 0, 1, 0, 0, 0),
intArrayOf(0, 1, 1, 0, 1, 0, 1, 0, 1, 1),
intArrayOf(1, 1, 0, 0, 1, 0, 0, 0, 0, 0),
intArrayOf(0, 0, 0, 1, 1, 0, 1, 1, 0, 1),
intArrayOf(0, 1, 1, 1, 0, 0, 1, 1, 0, 1)
)
mazeView.maze = maze
}
private fun checkMazeCollision() {
val cellSize = if (mazeView.width > 0 && mazeView.maze!!.isNotEmpty()) mazeView.width / mazeView.maze!![0].size else return
val futureX = (x + velocityX).toInt()
val futureY = (y + velocityY).toInt()
if (isCollision(futureX, y)) {
velocityX *= -BOUNCE_REDUCTION_FACTOR * DAMPING_FACTOR
} else {
x = futureX
}
if (isCollision(x, futureY)) {
velocityY *= -BOUNCE_REDUCTION_FACTOR * DAMPING_FACTOR
} else {
y = futureY
}
checkFinishCollision()
}
private fun checkFinishCollision() {
val ballRadius = mDrawable.width / 2
val finishLineX = finishLine.x.toInt()
val finishLineY = finishLine.y.toInt()
val finishLineWidth = finishLine.width
val finishLineHeight = finishLine.height
if (x + ballRadius >= finishLineX && x - ballRadius <= finishLineX + finishLineWidth &&
y + ballRadius >= finishLineY && y - ballRadius <= finishLineY + finishLineHeight) {
pauseGame()
}
}
private fun isCollision(x: Int, y: Int): Boolean {
val cellSize = mazeView.width / mazeView.maze!![0].size
val mazeX = x / cellSize
val mazeY = y / cellSize
return mazeY in mazeView.maze!!.indices && mazeX in mazeView.maze!![mazeY].indices && mazeView.maze!![mazeY][mazeX] == 1
}
private fun updateImageViewPosition() {
mDrawable.y = y.toFloat()
mDrawable.x = x.toFloat()
}
private fun pauseGame() {
isPaused = true
sensorManager.unregisterListener(this)
darkOverlay.visibility = View.VISIBLE
youWonText.visibility = View.VISIBLE
btnRestartGame.visibility = View.VISIBLE
}
private fun resumeGame() {
isPaused = false
sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_GAME)
darkOverlay.visibility = View.GONE
youWonText.visibility = View.GONE
btnRestartGame.visibility = View.GONE
resetBallPosition()
}
override fun onResume() {
super.onResume()
if (!isPaused) {
sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_GAME)
}
}
override fun onPause() {
super.onPause()
sensorManager.unregisterListener(this)
}
override fun onSensorChanged(event: SensorEvent?) {
if (!isPaused) {
event?.let {
if (it.sensor.type == Sensor.TYPE_ACCELEROMETER) {
velocityX -= it.values[0] * SPEED_REDUCTION_FACTOR
velocityY += it.values[1] * SPEED_REDUCTION_FACTOR
checkMazeCollision()
handleScreenCollisions()
updateImageViewPosition()
}
}
}
}
private fun handleScreenCollisions() {
val displayMetrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(displayMetrics)
val screenWidth = displayMetrics.widthPixels
val screenHeight = displayMetrics.heightPixels
val maxX = screenWidth - mDrawable.width
val maxY = screenHeight - mDrawable.height
if (x < 0 || x > maxX) {
x = max(0, min(x, maxX))
velocityX *= -1 * DAMPING_FACTOR
}
if (y < 0 || y > maxY) {
y = max(0, min(y, maxY))
velocityY *= -1 * DAMPING_FACTOR
}
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
// Not needed for this example
}
}
`
MazeView.kt
package com.example.mazeball
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
class MazeView: View {
var maze: Array<IntArray>? = null
set(value) {
field = value
invalidate()
}
private val wallPaint = Paint().apply { color = 0xFF000000.toInt() }
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
initializeMaze()
}
private fun initializeMaze() {
if (maze == null) {
maze = arrayOf(
intArrayOf(1, 0),
intArrayOf(0, 1)
)
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
maze?.let {
val cellSize = width / it[0].size
for (i in it.indices) {
for (j in it[i].indices) {
if (it[i][j] == 1) {
canvas.drawRect(
(j * cellSize).toFloat(),
(i * cellSize).toFloat(),
((j + 1) * cellSize).toFloat(),
((i + 1) * cellSize).toFloat(),
wallPaint
)
}
}
}
}
}
}
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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.mazeball.MazeView
android:id="@+id/mazeView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ImageView
android:id="@+id/finish_line"
android:layout_width="39dp"
android:layout_height="30dp"
android:layout_gravity="bottom|left"
android:layout_marginEnd="36dp"
android:baselineAligned="false"
android:src="@drawable/black_checkerboard"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ImageView
android:id="@+id/ball"
android:layout_width="42dp"
android:layout_height="42dp"
android:layout_marginStart="5dp"
android:layout_marginTop="5dp"
android:baselineAligned="false"
android:src="@drawable/redball"
android:visibility="visible"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btnRestart"
android:layout_width="98dp"
android:layout_height="47dp"
android:layout_marginTop="17dp"
android:layout_marginEnd="16dp"
android:text="@string/restart"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- Dodane elementy -->
<View
android:id="@+id/darkOverlay"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#80000000"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/youWonText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/you_won"
android:textSize="24sp"
android:textColor="#FFFFFF"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Button
android:id="@+id/btnRestartGame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/restart"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/youWonText"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
I hope someone has idea how can I improve the code to have properly bouncing of walls ball
New contributor
Vergil Sparda is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.