Important stuff

This commit is contained in:
partisan 2025-05-25 21:59:07 +02:00
parent 693607de7c
commit b65222ddf9

View file

@ -52,16 +52,33 @@ class SecretView @JvmOverloads constructor(
textAlign = Paint.Align.CENTER textAlign = Paint.Align.CENTER
textSize = 48f textSize = 48f
} }
private var colorSecondary: Int = Color.GREEN
private var playerX = 0f private var playerX = 0f
private var viewWidth = 0f private var viewWidth = 0f
private var viewHeight = 0f private var viewHeight = 0f
private var enemyClearAggression = 0f
private var adaptiveSpawnTimer = 0L
private var lastEnemyCount = 0
private var avgEnemiesPerSecond = 0f
private var isTouching = false private var isTouching = false
private var shieldRechargeTimer = 0L
private var shieldFlashAlpha = 0f
private var lastMissileSide = -1
private var missileCooldown = 0L
private var bulletCooldownMs = 0L private var bulletCooldownMs = 0L
private var gameOver = false private var gameOver = false
private var score = 0 private var score = 0
// Player level
private var multiFireLevel = 1
private var piercingLevel = 1
private var shieldLevel = 0
private var missileLevel = 0
private var rapidFireLevel = 1
private val bullets = mutableListOf<Bullet>() private val bullets = mutableListOf<Bullet>()
private val enemyBullets = mutableListOf<Bullet>() private val enemyBullets = mutableListOf<Bullet>()
private val enemies = mutableListOf<Enemy>() private val enemies = mutableListOf<Enemy>()
@ -69,11 +86,29 @@ class SecretView @JvmOverloads constructor(
private val stars = mutableListOf<Star>() private val stars = mutableListOf<Star>()
private val explosions = mutableListOf<Explosion>() private val explosions = mutableListOf<Explosion>()
private val rocketTrails = mutableListOf<Pair<Float, Float>>() private val rocketTrails = mutableListOf<Pair<Float, Float>>()
private val pickups = mutableListOf<Pickup>()
private val playerMissiles = mutableListOf<PlayerMissile>()
private data class Bullet(var x: Float, var y: Float, val dy: Float = -15f) private data class Bullet(var x: Float, var y: Float, val dy: Float = -15f, var life: Int = 1)
private data class Star(var x: Float, var y: Float, val radius: Float, val speed: Float) private data class Star(var x: Float, var y: Float, val radius: Float, val speed: Float)
private data class Rocket(var x: Float, var y: Float, var angle: Float, val trail: MutableList<Pair<Float, Float>> = mutableListOf()) private data class Rocket(var x: Float, var y: Float, var angle: Float, val trail: MutableList<Pair<Float, Float>> = mutableListOf())
private data class Explosion(var x: Float, var y: Float, var timer: Int = 12) private data class Explosion(var x: Float, var y: Float, var timer: Int = 12)
private data class Pickup(
val x: Float,
var y: Float,
val type: Int,
var hue: Float = Random.nextFloat() * 360f,
)
private data class PlayerMissile(
var x: Float,
var y: Float,
var angle: Float,
var ttl: Long = 90000L,
var target: Enemy? = null,
var recheckCooldown: Long = 0L,
var side: Int, // -1 = left, 1 = right
val trail: MutableList<Pair<Float, Float>> = mutableListOf()
)
private var lastLogicTime = 0L private var lastLogicTime = 0L
private val logicStepMs = 16L private val logicStepMs = 16L
@ -88,11 +123,14 @@ class SecretView @JvmOverloads constructor(
stars.add(Star(Random.nextFloat() * 1080f, Random.nextFloat() * 1920f, Random.nextFloat() * 2f + 1f, Random.nextFloat() * 2f + 0.5f)) stars.add(Star(Random.nextFloat() * 1080f, Random.nextFloat() * 1920f, Random.nextFloat() * 2f + 1f, Random.nextFloat() * 2f + 0.5f))
} }
val colorAttrs = intArrayOf(com.google.android.material.R.attr.colorPrimaryVariant) val colorAttrs = intArrayOf(
com.google.android.material.R.attr.colorPrimaryVariant,
com.google.android.material.R.attr.colorSecondary
)
context.obtainStyledAttributes(colorAttrs).use { context.obtainStyledAttributes(colorAttrs).use {
val primary = it.getColor(0, Color.CYAN) playerPaint.color = it.getColor(0, Color.CYAN)
playerPaint.color = primary enemyPaint.color = it.getColor(0, Color.CYAN)
enemyPaint.color = primary colorSecondary = it.getColor(1, Color.GREEN)
} }
Choreographer.getInstance().postFrameCallback(this) Choreographer.getInstance().postFrameCallback(this)
@ -129,6 +167,20 @@ class SecretView @JvmOverloads constructor(
} }
} }
// Update shield recharge timer and flash animation
if (shieldRechargeTimer > 0) {
shieldRechargeTimer -= deltaMs
if (shieldRechargeTimer <= 0) {
shieldRechargeTimer = 0
shieldFlashAlpha = 1f // trigger visual flash on recharge
}
}
if (shieldFlashAlpha > 0f) {
shieldFlashAlpha -= deltaMs / 300f
if (shieldFlashAlpha < 0f) shieldFlashAlpha = 0f
}
rockets.forEach { rocket -> rockets.forEach { rocket ->
rocket.trail.add(0, rocket.x to rocket.y) rocket.trail.add(0, rocket.x to rocket.y)
if (rocket.trail.size > 20) { if (rocket.trail.size > 20) {
@ -139,14 +191,27 @@ class SecretView @JvmOverloads constructor(
explosions.forEach { it.timer-- } explosions.forEach { it.timer-- }
explosions.removeIf { it.timer <= 0 } explosions.removeIf { it.timer <= 0 }
bullets.forEach { it.y += it.dy * deltaMs / 16f } bullets.forEach { it.y += it.dy * deltaMs / 16f }
bullets.removeIf { it.y < 0 } bullets.removeIf { it.y < 0 || it.life <= 0 }
bulletCooldownMs -= deltaMs bulletCooldownMs -= deltaMs
if (isTouching && bulletCooldownMs <= 0) { if (isTouching && bulletCooldownMs <= 0) {
bullets.add(Bullet(playerX, viewHeight - 130f)) val baseCooldown = 450f
bulletCooldownMs = 150L var multiplier = 1f
for (level in 2..rapidFireLevel) {
val reduction = ((36 - level * 1.2f).coerceAtLeast(4f)) / 100f
multiplier *= (1f - reduction)
}
bulletCooldownMs = max(50L, (baseCooldown * multiplier).toLong())
val baseY = viewHeight - 100f
val spacing = 10f
val count = multiFireLevel
for (i in 0 until count) {
val offset = (i - (count - 1) / 2f) * spacing
bullets.add(Bullet(playerX + offset, baseY))
}
} }
enemies.forEach { enemies.forEach {
@ -155,6 +220,12 @@ class SecretView @JvmOverloads constructor(
if (it.y > viewHeight + 100f) it.y = -40f // respawn at top if (it.y > viewHeight + 100f) it.y = -40f // respawn at top
} }
pickups.forEach {
it.y += 2.5f * deltaMs / 16f
it.hue = (it.hue + deltaMs * 0.01f) % 360f
}
pickups.removeIf { it.y > viewHeight - 40f }
rockets.forEach { rockets.forEach {
val targetAngle = atan2(viewHeight - 100f - it.y, playerX - it.x) val targetAngle = atan2(viewHeight - 100f - it.y, playerX - it.x)
it.angle += ((targetAngle - it.angle + PI).mod(2 * PI) - PI).toFloat() * 0.1f it.angle += ((targetAngle - it.angle + PI).mod(2 * PI) - PI).toFloat() * 0.1f
@ -166,58 +237,237 @@ class SecretView @JvmOverloads constructor(
enemyBullets.forEach { it.y += it.dy * deltaMs / 16f } enemyBullets.forEach { it.y += it.dy * deltaMs / 16f }
enemyBullets.removeIf { it.y > viewHeight } enemyBullets.removeIf { it.y > viewHeight }
updatePlayerMissiles(deltaMs)
checkCollisions() checkCollisions()
spawnEnemies(deltaMs) spawnEnemies(deltaMs)
} }
private fun checkCollisions() { private fun checkCollisions() {
val pickupIter = pickups.iterator()
while (pickupIter.hasNext()) {
val p = pickupIter.next()
if (hypot(playerX - p.x, viewHeight - 100f - p.y) < 40f) {
when (p.type) {
0 -> { // Multi-fire
if (multiFireLevel < 4) {
multiFireLevel++
} else {
applyRandomPowerupExcept(0)
}
}
1 -> shieldLevel++
2 -> if (missileLevel < 8) missileLevel++
3 -> if (rapidFireLevel < 30) rapidFireLevel++
4 -> if (piercingLevel < 15) piercingLevel++
}
pickupIter.remove()
}
}
val enemyIter = enemies.iterator() val enemyIter = enemies.iterator()
while (enemyIter.hasNext()) { while (enemyIter.hasNext()) {
val enemy = enemyIter.next() val enemy = enemyIter.next()
if (hypot(playerX - enemy.x, viewHeight - 100f - enemy.y) < 40f) { if (hypot(playerX - enemy.x, viewHeight - 100f - enemy.y) < 40f) {
explosions.add(Explosion(playerX, viewHeight - 100f)) if (shieldLevel > 0 && shieldRechargeTimer <= 0) {
gameOver = true shieldRechargeTimer = calculateShieldRechargeTime()
return enemyIter.remove()
explosions.add(Explosion(enemy.x, enemy.y))
continue
} else {
explosions.add(Explosion(playerX, viewHeight - 100f))
gameOver = true
return
}
} }
for (b in bullets) { for (b in bullets) {
if (hypot(b.x - enemy.x, b.y - enemy.y) < 30f) { if (hypot(b.x - enemy.x, b.y - enemy.y) < 30f) {
explosions.add(Explosion(enemy.x, enemy.y)) explosions.add(Explosion(enemy.x, enemy.y))
enemyIter.remove() enemyIter.remove()
score += 10 score += 10
b.life--
if (Random.nextFloat() < 0.03f) {
val type = Random.nextInt(5) // 5 pickup types
val hue = Random.nextFloat() * 360f
pickups.add(Pickup(enemy.x, enemy.y, type, hue))
}
break break
} }
} }
} }
for (b in enemyBullets) { val bulletIter = enemyBullets.iterator()
if (hypot(b.x - playerX, b.y - (viewHeight - 100f)) < 20f) { while (bulletIter.hasNext()) {
explosions.add(Explosion(playerX, viewHeight - 100f)) val b = bulletIter.next()
gameOver = true val dy = b.y - (viewHeight - 100f)
return val dx = b.x - playerX
val dist = hypot(dx, dy)
val hitRadius = if (shieldLevel > 0 && shieldRechargeTimer <= 0) 60f else 20f
if (dist < hitRadius) {
if (shieldLevel > 0 && shieldRechargeTimer <= 0) {
shieldRechargeTimer = calculateShieldRechargeTime()
bulletIter.remove()
explosions.add(Explosion(b.x, b.y))
} else {
explosions.add(Explosion(playerX, viewHeight - 100f))
gameOver = true
return
}
} }
} }
val rocketIter = rockets.iterator() val rocketIter = rockets.iterator()
while (rocketIter.hasNext()) { while (rocketIter.hasNext()) {
val rocket = rocketIter.next() val rocket = rocketIter.next()
if (hypot(playerX - rocket.x, viewHeight - 100f - rocket.y) < 30f) { val dy = viewHeight - 100f - rocket.y
explosions.add(Explosion(playerX, viewHeight - 100f)) val dx = playerX - rocket.x
gameOver = true val dist = hypot(dx, dy)
return val hitRadius = if (shieldLevel > 0 && shieldRechargeTimer <= 0) 60f else 30f
if (dist < hitRadius) {
if (shieldLevel > 0 && shieldRechargeTimer <= 0) {
shieldRechargeTimer = calculateShieldRechargeTime()
rocketIter.remove()
explosions.add(Explosion(rocket.x, rocket.y))
continue
} else {
explosions.add(Explosion(playerX, viewHeight - 100f))
gameOver = true
return
}
} }
for (b in bullets) { for (b in bullets) {
if (hypot(b.x - rocket.x, b.y - rocket.y) < 20f) { if (hypot(b.x - rocket.x, b.y - rocket.y) < 20f) {
explosions.add(Explosion(rocket.x, rocket.y)) explosions.add(Explosion(rocket.x, rocket.y))
rocketIter.remove() rocketIter.remove()
score += 15
break break
} }
} }
} }
val missileIter = playerMissiles.iterator()
while (missileIter.hasNext()) {
val missile = missileIter.next()
val enemyHit = enemies.firstOrNull { enemy ->
hypot(missile.x - enemy.x, missile.y - enemy.y) < 30f
}
if (enemyHit != null) {
explosions.add(Explosion(enemyHit.x, enemyHit.y))
enemies.remove(enemyHit)
missileIter.remove()
score += 10
if (Random.nextFloat() < 0.5f) {
val type = Random.nextInt(5)
val hue = Random.nextFloat() * 360f
pickups.add(Pickup(enemyHit.x, enemyHit.y, type, hue))
}
}
}
}
private fun updatePlayerMissiles(deltaMs: Long) {
// Missile cooldown and launch
missileCooldown -= deltaMs
if (missileLevel > 0 && missileCooldown <= 0) {
val cooldown = when (missileLevel) {
1 -> 20000L
2 -> 15000L
3 -> 12000L
4 -> 20000L
5 -> 18000L
6 -> 17000L
7 -> 16000L
else -> 15000L
}
val baseY = viewHeight - 100f
if (missileLevel >= 4) {
// fire both sides
playerMissiles.add(PlayerMissile(playerX - 20f, baseY, -PI.toFloat() / 2f, side = -1))
playerMissiles.add(PlayerMissile(playerX + 20f, baseY, -PI.toFloat() / 2f, side = 1))
} else {
lastMissileSide *= -1
val offsetX = 20f * lastMissileSide
playerMissiles.add(PlayerMissile(playerX + offsetX, baseY, -PI.toFloat() / 2f, side = lastMissileSide))
}
missileCooldown = cooldown
}
// Update missiles
val lockedEnemies = playerMissiles.mapNotNull { it.target }.toSet()
val availableTargets = enemies.filter { it !in lockedEnemies }
playerMissiles.forEach { missile ->
missile.ttl -= deltaMs
if (missile.ttl <= 0) return@forEach
// Add current position to trail
missile.trail.add(0, missile.x to missile.y)
if (missile.trail.size > 20) {
missile.trail.removeLast()
}
if (missile.target == null || !enemies.contains(missile.target)) {
missile.recheckCooldown -= deltaMs
if (missile.recheckCooldown <= 0) {
val newTarget = availableTargets.minByOrNull {
hypot((it.x - missile.x).toDouble(), (it.y - missile.y).toDouble())
}
if (newTarget != null) {
missile.target = newTarget
} else {
missile.recheckCooldown = 1000L
}
}
}
val target = missile.target
val angleTo = if (target != null) {
atan2(target.y - missile.y, target.x - missile.x)
} else missile.angle
// steer towards target slowly
missile.angle += ((angleTo - missile.angle + PI).mod(2 * PI) - PI).toFloat() * 0.1f
missile.x += cos(missile.angle) * 6f
missile.y += sin(missile.angle) * 6f
}
playerMissiles.removeIf { it.ttl <= 0 || it.x < 0 || it.x > viewWidth || it.y < 0 || it.y > viewHeight }
}
private fun applyRandomPowerupExcept(excludedType: Int) {
val types = (0..4).filter { it != excludedType }
val type = types.random()
when (type) {
1 -> shieldLevel++
2 -> if (missileLevel < 8) missileLevel++
3 -> if (rapidFireLevel < 30) rapidFireLevel++
4 -> if (piercingLevel < 15) piercingLevel++
}
}
private fun createCubeIconPath(): Path {
return Path().apply {
addRect(-10f, -10f, 10f, 10f, Path.Direction.CW)
}
}
private fun calculateShieldRechargeTime(): Long {
val base = 60000L // 60 seconds
val min = 10000L // 10 seconds
val reduction = (1.0 - exp(-shieldLevel / 40.0)).toFloat()
return (base - (base - min) * reduction).toLong()
} }
private fun spawnEnemies(deltaMs: Long) { private fun spawnEnemies(deltaMs: Long) {
waveTimer -= deltaMs waveTimer -= deltaMs
adaptiveSpawnTimer += deltaMs
// Passive asteroids // Passive asteroids
if (Random.nextFloat() < 0.002f) { if (Random.nextFloat() < 0.002f) {
@ -233,40 +483,67 @@ class SecretView @JvmOverloads constructor(
else -> "hard" else -> "hard"
} }
enemiesLeftInWave = when (currentWaveType) { val difficultyBoost = when (currentWaveType) {
"easy" -> 3 + currentWave "easy" -> (currentWave * 1.2f).roundToInt()
"medium" -> 2 + currentWave / 2 "medium" -> (currentWave * 1.5f).roundToInt()
"hard" -> 1 + currentWave / 4 "hard" -> (currentWave * 1.8f).roundToInt()
else -> 3 else -> currentWave
} }
enemiesLeftInWave = 3 + difficultyBoost
waveTimer = 3000L waveTimer = 3000L
adaptiveSpawnTimer = 0L
lastEnemyCount = enemies.size
avgEnemiesPerSecond = 0f
enemyClearAggression = 0f
}
// Adjust aggression every 1s
if (adaptiveSpawnTimer >= 1000L) {
val cleared = (lastEnemyCount - enemies.size).coerceAtLeast(0)
avgEnemiesPerSecond = avgEnemiesPerSecond * 0.7f + cleared * 0.3f
lastEnemyCount = enemies.size
adaptiveSpawnTimer = 0L
// Normalize to 01 range (assuming 0 to 6 cleared per second)
enemyClearAggression = (avgEnemiesPerSecond / 6f).coerceIn(0f, 1f)
} }
// Spawn enemies in group // Spawn enemies in group
if (enemiesLeftInWave > 0 && enemies.count { it !is EnemyAsteroid } < 3) { if (enemiesLeftInWave > 0 && enemies.count { it !is EnemyAsteroid } < 10) {
val baseX = Random.nextFloat() * (viewWidth - 100f) + 50f val baseX = Random.nextFloat() * (viewWidth - 100f) + 50f
val baseY = -40f val baseY = -40f
val spacing = 35f val spacing = 35f
val sharedOffset = Random.nextFloat() * 1000f val sharedOffset = Random.nextFloat() * 1000f
val sharedFireTime = Random.nextLong(2000L, 4000L) val sharedFireTime = Random.nextLong(2000L, 4000L)
val formationSize = when (currentWaveType) { // Dynamically adjust formation size
val formationBase = when (currentWaveType) {
"easy" -> 5 "easy" -> 5
"medium" -> 3 "medium" -> 3
"hard" -> 1 "hard" -> 1
else -> 3 else -> 3
} }
for (i in 0 until min(formationSize, enemiesLeftInWave)) { // Scale formation by aggression (max +2)
val dynamicBonus = (enemyClearAggression * 2).roundToInt()
val formationSize = (formationBase + dynamicBonus).coerceAtMost(enemiesLeftInWave)
for (i in 0 until formationSize) {
val offsetX = (i - (formationSize - 1) / 2f) * spacing val offsetX = (i - (formationSize - 1) / 2f) * spacing
val x = baseX + offsetX val x = baseX + offsetX
// The group sync isn't working as enemies gradually get out of sync, but whatever, it's fine.
val enemy = when (currentWaveType) { val enemy = when (currentWaveType) {
"easy" -> EnemyEasy(x, baseY) "easy" -> EnemyEasy(x, baseY)
"medium" -> EnemyMedium(x, baseY, sharedOffset, sharedFireTime) { enemyBullets.add(it) } "medium" -> EnemyMedium(x, baseY, sharedOffset, sharedFireTime) { enemyBullets.add(it) }
"hard" -> EnemyHard(x, baseY, { rockets.add(it) }, sharedOffset, sharedFireTime) "hard" -> {
val hardEnemiesSoFar = enemies.count { it is EnemyHard }
if (hardEnemiesSoFar >= 2) {
EnemyMedium(x, baseY, sharedOffset, sharedFireTime) { enemyBullets.add(it) }
} else {
EnemyHard(x, baseY, { rockets.add(it) }, sharedOffset, sharedFireTime)
}
}
else -> EnemyEasy(x, baseY) else -> EnemyEasy(x, baseY)
} }
@ -283,10 +560,31 @@ class SecretView @JvmOverloads constructor(
rockets.clear() rockets.clear()
explosions.clear() explosions.clear()
rocketTrails.clear() rocketTrails.clear()
playerMissiles.clear()
pickups.clear()
// Reset upgrades
multiFireLevel = 1
piercingLevel = 1
shieldLevel = 0
missileLevel = 0
rapidFireLevel = 1
// Reset gameplay state
score = 0 score = 0
currentWave = 0 currentWave = 0
enemiesLeftInWave = 0 enemiesLeftInWave = 0
gameOver = false gameOver = false
shieldRechargeTimer = 0L
shieldFlashAlpha = 0f
lastMissileSide = -1
missileCooldown = 0L
bulletCooldownMs = 0L
enemyClearAggression = 0f
adaptiveSpawnTimer = 0L
lastEnemyCount = 0
avgEnemiesPerSecond = 0f
playerX = viewWidth / 2f playerX = viewWidth / 2f
lastLogicTime = System.nanoTime() lastLogicTime = System.nanoTime()
Choreographer.getInstance().postFrameCallback(this) Choreographer.getInstance().postFrameCallback(this)
@ -299,8 +597,13 @@ class SecretView @JvmOverloads constructor(
canvas.drawColor(Color.parseColor("#121212")) canvas.drawColor(Color.parseColor("#121212"))
stars.forEach { canvas.drawCircle(it.x, it.y, it.radius, starPaint) } stars.forEach { canvas.drawCircle(it.x, it.y, it.radius, starPaint) }
enemyBullets.forEach { canvas.drawLine(it.x, it.y, it.x, it.y + 20f, rocketPaint) } val enemyBulletPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.RED
strokeWidth = 6f // same as player bullets
}
enemyBullets.forEach { canvas.drawLine(it.x, it.y, it.x, it.y + 20f, enemyBulletPaint) }
// Enemy rockets
rockets.forEach { rocket -> rockets.forEach { rocket ->
rocket.trail.forEachIndexed { index, (x, y) -> rocket.trail.forEachIndexed { index, (x, y) ->
val alpha = ((1f - index / 20f.toFloat()) * 255).toInt() val alpha = ((1f - index / 20f.toFloat()) * 255).toInt()
@ -310,6 +613,26 @@ class SecretView @JvmOverloads constructor(
} }
rocketPaint.alpha = 255 rocketPaint.alpha = 255
// Player rockets
val missilePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = colorSecondary
style = Paint.Style.STROKE
strokeWidth = 3f
}
playerMissiles.forEach { missile ->
missile.trail.forEachIndexed { index, (x, y) ->
val alpha = ((1f - index / 20f.toFloat()) * 255).toInt()
missilePaint.alpha = alpha
canvas.drawCircle(x, y, 2f, missilePaint)
}
}
missilePaint.alpha = 255
playerMissiles.forEach { missile ->
canvas.drawCircle(missile.x, missile.y, 10f, missilePaint)
}
explosions.forEach { explosions.forEach {
val radius = 40f * (1f - it.timer / 12f.toFloat()) val radius = 40f * (1f - it.timer / 12f.toFloat())
val alpha = (255 * (it.timer / 12f.toFloat())).toInt() val alpha = (255 * (it.timer / 12f.toFloat())).toInt()
@ -326,7 +649,72 @@ class SecretView @JvmOverloads constructor(
enemies.forEach { it.draw(canvas, enemyPaint) } enemies.forEach { it.draw(canvas, enemyPaint) }
rockets.forEach { canvas.drawCircle(it.x, it.y, 10f, rocketPaint) } rockets.forEach { canvas.drawCircle(it.x, it.y, 10f, rocketPaint) }
// Draw pickups
pickups.forEach { pickup ->
// Draw cube (filled rect with outline)
val size = 30f
val left = pickup.x - size / 2f
val top = pickup.y - size / 2f
val right = pickup.x + size / 2f
val bottom = pickup.y + size / 2f
val hsv = floatArrayOf(pickup.hue, 1f, 1f)
val fillPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.HSVToColor(hsv)
style = Paint.Style.FILL
}
val outlinePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.WHITE
style = Paint.Style.STROKE
strokeWidth = 2f
}
canvas.drawRect(left, top, right, bottom, fillPaint)
canvas.drawRect(left, top, right, bottom, outlinePaint)
// Draw icon
val iconPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.WHITE
strokeWidth = 3f
style = Paint.Style.STROKE
}
canvas.save()
canvas.translate(pickup.x, pickup.y)
canvas.rotate(-45f)
when (pickup.type) {
0 -> { // Multi-fire: two parallel lines
canvas.drawLine(-8f, -10f, -8f, 10f, iconPaint)
canvas.drawLine(8f, -10f, 8f, 10f, iconPaint)
}
1 -> { // Shield: quarter circle
val path = Path()
path.addArc(RectF(-10f, -10f, 10f, 10f), -90f, 90f)
canvas.drawPath(path, iconPaint)
}
2 -> { // Missiles: circle + trail
canvas.drawCircle(0f, 0f, 4f, iconPaint)
canvas.drawLine(-6f, 6f, -2f, 2f, iconPaint)
}
3 -> { // Rapid fire: two lines in succession
canvas.drawLine(-5f, -10f, -5f, 10f, iconPaint)
canvas.drawLine(5f, -10f, 5f, 10f, iconPaint)
}
4 -> { // Piercing bullets: arrow head
val path = Path()
path.moveTo(-8f, 10f)
path.lineTo(0f, -10f)
path.lineTo(8f, 10f)
canvas.drawPath(path, iconPaint)
}
}
canvas.restore()
}
if (!gameOver) { if (!gameOver) {
// Draw player
val baseY = viewHeight - 100f val baseY = viewHeight - 100f
val safeX = max(30f, min(playerX, viewWidth - 30f)) val safeX = max(30f, min(playerX, viewWidth - 30f))
playerX = safeX playerX = safeX
@ -337,6 +725,18 @@ class SecretView @JvmOverloads constructor(
close() close()
} }
canvas.drawPath(path, playerPaint) canvas.drawPath(path, playerPaint)
// Draw shield
if (shieldLevel > 0 && shieldRechargeTimer <= 0) {
val baseColor = colorSecondary
val shieldPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = baseColor
style = Paint.Style.STROKE
strokeWidth = 6f
alpha = (180 + shieldFlashAlpha * 75f).toInt().coerceAtMost(255)
}
canvas.drawCircle(playerX, viewHeight - 100f, 60f, shieldPaint)
}
} else { } else {
canvas.drawText("Game Over", viewWidth / 2f, viewHeight / 2f - 60f, textPaint) canvas.drawText("Game Over", viewWidth / 2f, viewHeight / 2f - 60f, textPaint)
canvas.drawText("Score: $score", viewWidth / 2f, viewHeight / 2f + 10f, textPaint) canvas.drawText("Score: $score", viewWidth / 2f, viewHeight / 2f + 10f, textPaint)