Important stuff
This commit is contained in:
parent
693607de7c
commit
b65222ddf9
1 changed files with 434 additions and 34 deletions
|
@ -52,16 +52,33 @@ class SecretView @JvmOverloads constructor(
|
|||
textAlign = Paint.Align.CENTER
|
||||
textSize = 48f
|
||||
}
|
||||
private var colorSecondary: Int = Color.GREEN
|
||||
|
||||
private var playerX = 0f
|
||||
private var viewWidth = 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 shieldRechargeTimer = 0L
|
||||
private var shieldFlashAlpha = 0f
|
||||
private var lastMissileSide = -1
|
||||
private var missileCooldown = 0L
|
||||
private var bulletCooldownMs = 0L
|
||||
private var gameOver = false
|
||||
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 enemyBullets = mutableListOf<Bullet>()
|
||||
private val enemies = mutableListOf<Enemy>()
|
||||
|
@ -69,11 +86,29 @@ class SecretView @JvmOverloads constructor(
|
|||
private val stars = mutableListOf<Star>()
|
||||
private val explosions = mutableListOf<Explosion>()
|
||||
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 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 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 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))
|
||||
}
|
||||
|
||||
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 {
|
||||
val primary = it.getColor(0, Color.CYAN)
|
||||
playerPaint.color = primary
|
||||
enemyPaint.color = primary
|
||||
playerPaint.color = it.getColor(0, Color.CYAN)
|
||||
enemyPaint.color = it.getColor(0, Color.CYAN)
|
||||
colorSecondary = it.getColor(1, Color.GREEN)
|
||||
}
|
||||
|
||||
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 ->
|
||||
rocket.trail.add(0, rocket.x to rocket.y)
|
||||
if (rocket.trail.size > 20) {
|
||||
|
@ -139,14 +191,27 @@ class SecretView @JvmOverloads constructor(
|
|||
explosions.forEach { it.timer-- }
|
||||
explosions.removeIf { it.timer <= 0 }
|
||||
|
||||
|
||||
bullets.forEach { it.y += it.dy * deltaMs / 16f }
|
||||
bullets.removeIf { it.y < 0 }
|
||||
bullets.removeIf { it.y < 0 || it.life <= 0 }
|
||||
|
||||
bulletCooldownMs -= deltaMs
|
||||
if (isTouching && bulletCooldownMs <= 0) {
|
||||
bullets.add(Bullet(playerX, viewHeight - 130f))
|
||||
bulletCooldownMs = 150L
|
||||
val baseCooldown = 450f
|
||||
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 {
|
||||
|
@ -155,6 +220,12 @@ class SecretView @JvmOverloads constructor(
|
|||
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 {
|
||||
val targetAngle = atan2(viewHeight - 100f - it.y, playerX - it.x)
|
||||
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.removeIf { it.y > viewHeight }
|
||||
|
||||
updatePlayerMissiles(deltaMs)
|
||||
|
||||
checkCollisions()
|
||||
spawnEnemies(deltaMs)
|
||||
}
|
||||
|
||||
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()
|
||||
while (enemyIter.hasNext()) {
|
||||
val enemy = enemyIter.next()
|
||||
if (hypot(playerX - enemy.x, viewHeight - 100f - enemy.y) < 40f) {
|
||||
if (shieldLevel > 0 && shieldRechargeTimer <= 0) {
|
||||
shieldRechargeTimer = calculateShieldRechargeTime()
|
||||
enemyIter.remove()
|
||||
explosions.add(Explosion(enemy.x, enemy.y))
|
||||
continue
|
||||
} else {
|
||||
explosions.add(Explosion(playerX, viewHeight - 100f))
|
||||
gameOver = true
|
||||
return
|
||||
}
|
||||
}
|
||||
for (b in bullets) {
|
||||
if (hypot(b.x - enemy.x, b.y - enemy.y) < 30f) {
|
||||
explosions.add(Explosion(enemy.x, enemy.y))
|
||||
enemyIter.remove()
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (b in enemyBullets) {
|
||||
if (hypot(b.x - playerX, b.y - (viewHeight - 100f)) < 20f) {
|
||||
val bulletIter = enemyBullets.iterator()
|
||||
while (bulletIter.hasNext()) {
|
||||
val b = bulletIter.next()
|
||||
val dy = b.y - (viewHeight - 100f)
|
||||
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()
|
||||
while (rocketIter.hasNext()) {
|
||||
val rocket = rocketIter.next()
|
||||
if (hypot(playerX - rocket.x, viewHeight - 100f - rocket.y) < 30f) {
|
||||
val dy = viewHeight - 100f - rocket.y
|
||||
val dx = playerX - rocket.x
|
||||
val dist = hypot(dx, dy)
|
||||
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) {
|
||||
if (hypot(b.x - rocket.x, b.y - rocket.y) < 20f) {
|
||||
explosions.add(Explosion(rocket.x, rocket.y))
|
||||
rocketIter.remove()
|
||||
score += 15
|
||||
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) {
|
||||
waveTimer -= deltaMs
|
||||
adaptiveSpawnTimer += deltaMs
|
||||
|
||||
// Passive asteroids
|
||||
if (Random.nextFloat() < 0.002f) {
|
||||
|
@ -233,40 +483,67 @@ class SecretView @JvmOverloads constructor(
|
|||
else -> "hard"
|
||||
}
|
||||
|
||||
enemiesLeftInWave = when (currentWaveType) {
|
||||
"easy" -> 3 + currentWave
|
||||
"medium" -> 2 + currentWave / 2
|
||||
"hard" -> 1 + currentWave / 4
|
||||
else -> 3
|
||||
val difficultyBoost = when (currentWaveType) {
|
||||
"easy" -> (currentWave * 1.2f).roundToInt()
|
||||
"medium" -> (currentWave * 1.5f).roundToInt()
|
||||
"hard" -> (currentWave * 1.8f).roundToInt()
|
||||
else -> currentWave
|
||||
}
|
||||
|
||||
enemiesLeftInWave = 3 + difficultyBoost
|
||||
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 0–1 range (assuming 0 to 6 cleared per second)
|
||||
enemyClearAggression = (avgEnemiesPerSecond / 6f).coerceIn(0f, 1f)
|
||||
}
|
||||
|
||||
// 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 baseY = -40f
|
||||
val spacing = 35f
|
||||
val sharedOffset = Random.nextFloat() * 1000f
|
||||
val sharedFireTime = Random.nextLong(2000L, 4000L)
|
||||
|
||||
val formationSize = when (currentWaveType) {
|
||||
// Dynamically adjust formation size
|
||||
val formationBase = when (currentWaveType) {
|
||||
"easy" -> 5
|
||||
"medium" -> 3
|
||||
"hard" -> 1
|
||||
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 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) {
|
||||
"easy" -> EnemyEasy(x, baseY)
|
||||
"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)
|
||||
}
|
||||
|
||||
|
@ -283,10 +560,31 @@ class SecretView @JvmOverloads constructor(
|
|||
rockets.clear()
|
||||
explosions.clear()
|
||||
rocketTrails.clear()
|
||||
playerMissiles.clear()
|
||||
pickups.clear()
|
||||
|
||||
// Reset upgrades
|
||||
multiFireLevel = 1
|
||||
piercingLevel = 1
|
||||
shieldLevel = 0
|
||||
missileLevel = 0
|
||||
rapidFireLevel = 1
|
||||
|
||||
// Reset gameplay state
|
||||
score = 0
|
||||
currentWave = 0
|
||||
enemiesLeftInWave = 0
|
||||
gameOver = false
|
||||
shieldRechargeTimer = 0L
|
||||
shieldFlashAlpha = 0f
|
||||
lastMissileSide = -1
|
||||
missileCooldown = 0L
|
||||
bulletCooldownMs = 0L
|
||||
enemyClearAggression = 0f
|
||||
adaptiveSpawnTimer = 0L
|
||||
lastEnemyCount = 0
|
||||
avgEnemiesPerSecond = 0f
|
||||
|
||||
playerX = viewWidth / 2f
|
||||
lastLogicTime = System.nanoTime()
|
||||
Choreographer.getInstance().postFrameCallback(this)
|
||||
|
@ -299,8 +597,13 @@ class SecretView @JvmOverloads constructor(
|
|||
canvas.drawColor(Color.parseColor("#121212"))
|
||||
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 ->
|
||||
rocket.trail.forEachIndexed { index, (x, y) ->
|
||||
val alpha = ((1f - index / 20f.toFloat()) * 255).toInt()
|
||||
|
@ -310,6 +613,26 @@ class SecretView @JvmOverloads constructor(
|
|||
}
|
||||
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 {
|
||||
val radius = 40f * (1f - it.timer / 12f.toFloat())
|
||||
val alpha = (255 * (it.timer / 12f.toFloat())).toInt()
|
||||
|
@ -326,7 +649,72 @@ class SecretView @JvmOverloads constructor(
|
|||
enemies.forEach { it.draw(canvas, enemyPaint) }
|
||||
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) {
|
||||
// Draw player
|
||||
val baseY = viewHeight - 100f
|
||||
val safeX = max(30f, min(playerX, viewWidth - 30f))
|
||||
playerX = safeX
|
||||
|
@ -337,6 +725,18 @@ class SecretView @JvmOverloads constructor(
|
|||
close()
|
||||
}
|
||||
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 {
|
||||
canvas.drawText("Game Over", viewWidth / 2f, viewHeight / 2f - 60f, textPaint)
|
||||
canvas.drawText("Score: $score", viewWidth / 2f, viewHeight / 2f + 10f, textPaint)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue