Cicero
06/05/2023, 5:46 PMCicero
06/05/2023, 5:46 PMimport androidx.compose.foundation.Canvas
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color.Companion.Green
import androidx.compose.ui.graphics.Color.Companion.Red
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.platform.LocalFontFamilyResolver
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import kotlinx.coroutines.delay
import kotlin.math.max
/**
* Constants for time calculations.
*/
private const val oneSecondInNano = 1_000_000_000.0
private const val fps = 60.0
private const val desiredMillisPerFrame = oneSecondInNano / fps
/**
* Runs the provided block of code at a regular interval determined by the desired FPS.
*/
@Composable
fun Run(deltaBlock: (Double, Int) -> Unit) {
LaunchedEffect(Unit) {
var lastLoopTime = time.now()
while (true) {
withFrameNanos { it } // This waits for the next frame.
val now = time.now()
val updateLength = now - lastLoopTime
if (updateLength >= desiredMillisPerFrame) {
lastLoopTime = now
val delta = updateLength / desiredMillisPerFrame
val fps = if (delta > 0) {
100.0/delta
}else{
0
}
deltaBlock(delta, fps.toInt())
val waitTime = (lastLoopTime - time.now() + desiredMillisPerFrame) / oneSecondInNano
delay(max(waitTime.toUInt(), 0u).toLong())
}
}
}
}
/**
* Renders the provided content on a Canvas, updating it at a regular interval.
*/
@Composable
fun RenderCompose(modifier: Modifier = Modifier, content: DrawScope.(Double) -> Unit) {
var state by remember { mutableStateOf(0.0) }
var currentFPS = 0
val fontFamily = LocalFontFamilyResolver.current
Run { delta, fps ->
state = delta
currentFPS = fps
}
Canvas(modifier) {
if (showFPS) {
drawFPS(fontFamily, currentFPS)
}
content(state)
}
}
/**
* Runs the provided simulation at a regular interval.
*/
@Composable
fun SimulateCompose(simulation: (Double) -> Unit) {
Run { delta, _ ->
simulation(delta)
}
}
@OptIn(ExperimentalTextApi::class)
private fun DrawScope.drawFPS(fontFamily: FontFamily.Resolver, currentFPS: Int) {
this.drawRect(color = Red, size = Size(height = 35f, width = 240f))
this.drawText(
style = TextStyle.Default.copy(color = Green),
textMeasurer = TextMeasurer(
fallbackFontFamilyResolver = fontFamily,
fallbackDensity = Density(1f),
fallbackLayoutDirection = LayoutDirection.Ltr,
),
size = Size(height = 35f, width = 240f),
text = AnnotatedString("Current FPS: $currentFPS"),
)
}
Cicero
06/05/2023, 5:47 PM@Composable
fun MovingBallSample() {
val bounds = Bounds(maxX = 100.dp.value.toDouble(), maxY = 100.dp.value.toDouble())
val movingObject = ObjectWithAcceleration(bounds = bounds)
Column {
RenderCompose(
Modifier
.size(bounds.maxX.dp, bounds.maxY.dp)
.background(Color.White)
) {
movingObject.render(this)
}
SimulateCompose {
movingObject.simulation(it)
}
// Controller(moveUp = { movingObject.move(y = -100.0) },
// moveDown = { movingObject.move(y = 1.0) },
// moveLeft = { movingObject.move(x = -1.0) },
// moveRight = { movingObject.move(x = 1.0) },
// quitGame = { movingObject.stop() }
// )
}
}
Cicero
06/05/2023, 5:47 PMdata class Position(var x: Double, var y: Double, val z: Int = 0)
data class Bounds(
val x: Double = 0.0,
val maxX: Double = 0.0,
val y: Double = 0.0,
val maxY: Double = 0.0
)
class ObjectWithAcceleration(
private val bounds: Bounds,
private val radius: Float = 10F
) {
var position = Position(10.0 + radius, 10.0 + radius)
private var vx = 0.1
private var vy = 0.1
fun simulation(delta: Double) {
//bounce on collision
detectCollision(delta)
// Update position based on velocity
simulateMovement(
position = position,
vx = vx,
vy = vy,
delta = delta,
bounds = bounds,
radius = radius
)
}
fun move(x: Double = 0.0, y: Double = 0.0) {
vx += x
vy += y
}
fun stop() {
vx = 0.0
vy = 0.0
}
private fun detectCollision(delta: Double) {
val speed = sqrt(vx * vx + vy * vy)
val maxSpeed = radius / delta // deltaTime is the time elapsed since the last update
// If the speed is above the maximum, scale the velocity vector down to the maximum speed
if (speed > maxSpeed) {
val scale = maxSpeed / speed
vx *= scale
vy *= scale
}
// Your original collision detection code
when {
position.x + radius > bounds.maxX -> {
vx *= -1.0
position.x = bounds.maxX - radius
}
position.x - radius < bounds.x -> {
vx *= -1.0
position.x = bounds.x + radius
}
position.y + radius > bounds.maxY -> {
vy *= -1.0
position.y = bounds.maxY - radius
}
position.y - radius < bounds.y -> {
vy *= -1.0
position.y = bounds.y + radius
}
}
}
fun render(content: DrawScope) {
content.drawCircle(
Color.Black,
radius = radius,
center = Offset(position.x.toFloat(), position.y.toFloat()),
style = Stroke(width = 10F)
)
}
}