diff --git a/build.gradle.kts b/build.gradle.kts index c67be35..6511ced 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,4 +18,7 @@ apply() korge { id = "com.redhue.korgeboot" + + targetJs() + targetJvm() } diff --git a/src/commonMain/kotlin/components/attack/ShootsBulletsAtPlayer.kt b/src/commonMain/kotlin/components/attack/ShootsBulletsAtPlayer.kt index 0c07732..197e48c 100644 --- a/src/commonMain/kotlin/components/attack/ShootsBulletsAtPlayer.kt +++ b/src/commonMain/kotlin/components/attack/ShootsBulletsAtPlayer.kt @@ -4,11 +4,11 @@ import com.soywiz.klock.TimeSpan import com.soywiz.klock.seconds import com.soywiz.korge.component.FixedUpdateComponent import containers.GameEntity -import containers.enemy.Enemy +import containers.enemy.SpriteEnemy import containers.player.PLAYER_NAME class ShootsBulletsAtPlayer( - override val view: Enemy, + override val view: SpriteEnemy, private val bullet: GameEntity, step: TimeSpan = 2.seconds, maxAccumulated: Int = 10 diff --git a/src/commonMain/kotlin/components/collision/MovesWithTilemapCollision.kt b/src/commonMain/kotlin/components/collision/MovesWithTilemapCollision.kt index 8b03cb7..e793c02 100644 --- a/src/commonMain/kotlin/components/collision/MovesWithTilemapCollision.kt +++ b/src/commonMain/kotlin/components/collision/MovesWithTilemapCollision.kt @@ -1,13 +1,13 @@ package components.collision -import com.soywiz.klock.DateTime import com.soywiz.klock.TimeSpan import com.soywiz.korge.component.UpdateComponent import com.soywiz.korge.view.HitTestDirection import containers.GameEntity import program.LevelManager import program.Log -import utility.* +import utility.getDeltaScale +import utility.viewHitTest import kotlin.math.round class MovesWithTilemapCollision( @@ -16,24 +16,24 @@ class MovesWithTilemapCollision( ) : UpdateComponent { override fun update(dt: TimeSpan) { val delta = getDeltaScale(dt) - val mapView = levelManager.getCurrentMapView() + val mapView = levelManager.getMapView() - if (view.move.isMovingLeft() || view.move.isMovingRight()) { + if (view.isMovingLeft() || view.isMovingRight()) { val oldX = view.x - view.x += round(view.move.x * delta) + view.x += round(view.move.x * delta * view.speedModifier) if (mapView.viewHitTest(view, HitTestDirection.LEFT) !== null || mapView.viewHitTest(view, HitTestDirection.RIGHT) !== null ) { Log().debug { "map hit X " + view.pos } - if (view.move.isMovingLeft()) { + if (view.isMovingLeft()) { while (mapView.viewHitTest(view, HitTestDirection.LEFT) !== null && view.x < oldX ) { view.x += 1.0 } - } else if (view.move.isMovingRight()) { + } else if (view.isMovingRight()) { while (mapView.viewHitTest(view, HitTestDirection.RIGHT) !== null && view.x > oldX ) { @@ -44,22 +44,22 @@ class MovesWithTilemapCollision( } } - if (view.move.isMovingUp() || view.move.isMovingDown()) { + if (view.isMovingUp() || view.isMovingDown()) { val oldY = view.y - view.y += round(view.move.y * delta) + view.y += round(view.move.y * delta * view.speedModifier) if (mapView.viewHitTest(view, HitTestDirection.UP) !== null || mapView.viewHitTest(view, HitTestDirection.DOWN) !== null ) { Log().debug { "map hit Y " + view.pos } - if (view.move.isMovingUp()) { + if (view.isMovingUp()) { while (mapView.viewHitTest(view, HitTestDirection.UP) !== null && view.y < oldY ) { view.y += 1.0 } - } else if (view.move.isMovingDown()) { + } else if (view.isMovingDown()) { while (mapView.viewHitTest(view, HitTestDirection.DOWN) !== null && view.y > oldY ) { diff --git a/src/commonMain/kotlin/components/collision/MovesWithoutTilemapCollision.kt b/src/commonMain/kotlin/components/collision/MovesWithoutTilemapCollision.kt index 2d2969d..05c44b6 100644 --- a/src/commonMain/kotlin/components/collision/MovesWithoutTilemapCollision.kt +++ b/src/commonMain/kotlin/components/collision/MovesWithoutTilemapCollision.kt @@ -1,13 +1,9 @@ package components.collision -import com.soywiz.klock.DateTime import com.soywiz.klock.TimeSpan import com.soywiz.korge.component.UpdateComponent -import com.soywiz.korge.view.HitTestDirection import containers.GameEntity -import program.LevelManager -import program.Log -import utility.* +import utility.getDeltaScale class MovesWithoutTilemapCollision( override val view: GameEntity @@ -15,11 +11,11 @@ class MovesWithoutTilemapCollision( override fun update(dt: TimeSpan) { val delta = getDeltaScale(dt) - if (view.move.isMovingLeft() || view.move.isMovingRight()) { - view.x += view.move.x * delta + if (view.isMovingLeft() || view.isMovingRight()) { + view.x += view.move.x * delta * view.speedModifier } - if (view.move.isMovingUp() || view.move.isMovingDown()) { - view.y += view.move.y * delta + if (view.isMovingUp() || view.isMovingDown()) { + view.y += view.move.y * delta * view.speedModifier } } } diff --git a/src/commonMain/kotlin/components/input/HorizontalMoveInput.kt b/src/commonMain/kotlin/components/input/HorizontalMoveInput.kt index faff529..2b60ae5 100644 --- a/src/commonMain/kotlin/components/input/HorizontalMoveInput.kt +++ b/src/commonMain/kotlin/components/input/HorizontalMoveInput.kt @@ -1,13 +1,11 @@ package components.input import com.soywiz.klock.TimeSpan +import com.soywiz.klock.milliseconds import com.soywiz.korev.Key -import com.soywiz.korge.component.UpdateComponent import com.soywiz.korge.component.UpdateComponentWithViews -import com.soywiz.korge.input.Input import com.soywiz.korge.view.SpriteAnimation import com.soywiz.korge.view.Views -import com.soywiz.korma.math.clamp import containers.SpriteEntity class HorizontalMoveInput( @@ -18,10 +16,10 @@ class HorizontalMoveInput( override fun update(views: Views, dt: TimeSpan) { if (views.input.keys[Key.LEFT]) { view.move.x -= 1.0 - if (leftAnimation !== null) view.getSprite().playAnimationLooped(leftAnimation) + if (leftAnimation !== null) view.getSprite().playAnimationLooped(leftAnimation, spriteDisplayTime = 130.milliseconds) } else if (views.input.keys[Key.RIGHT]) { view.move.x += 1.0 - if (rightAnimation !== null) view.getSprite().playAnimationLooped(rightAnimation) + if (rightAnimation !== null) view.getSprite().playAnimationLooped(rightAnimation, spriteDisplayTime = 130.milliseconds) } else { view.move.x = 0.0 } diff --git a/src/commonMain/kotlin/components/input/VerticalMoveInput.kt b/src/commonMain/kotlin/components/input/VerticalMoveInput.kt index 727c9c8..ba57de4 100644 --- a/src/commonMain/kotlin/components/input/VerticalMoveInput.kt +++ b/src/commonMain/kotlin/components/input/VerticalMoveInput.kt @@ -1,6 +1,7 @@ package components.input import com.soywiz.klock.TimeSpan +import com.soywiz.klock.milliseconds import com.soywiz.korev.Key import com.soywiz.korge.component.UpdateComponentWithViews import com.soywiz.korge.view.SpriteAnimation @@ -15,10 +16,10 @@ class VerticalMoveInput( override fun update(views: Views, dt: TimeSpan) { if (views.input.keys[Key.UP]) { view.move.y -= 1.0 - if (upAnimation !== null) view.getSprite().playAnimationLooped(upAnimation) + if (upAnimation !== null) view.getSprite().playAnimationLooped(upAnimation, spriteDisplayTime = 130.milliseconds) } else if (views.input.keys[Key.DOWN]) { view.move.y += 1.0 - if (downAnimation !== null) view.getSprite().playAnimationLooped(downAnimation) + if (downAnimation !== null) view.getSprite().playAnimationLooped(downAnimation, spriteDisplayTime = 130.milliseconds) } else { view.move.y = 0.0 } diff --git a/src/commonMain/kotlin/components/movement/ClampMovement.kt b/src/commonMain/kotlin/components/movement/ClampMovement.kt index 9b71b0e..b27cd3e 100644 --- a/src/commonMain/kotlin/components/movement/ClampMovement.kt +++ b/src/commonMain/kotlin/components/movement/ClampMovement.kt @@ -6,12 +6,18 @@ import com.soywiz.korma.geom.XY import com.soywiz.korma.math.clamp import containers.GameEntity +@Suppress("MemberVisibilityCanBePrivate") class ClampMovement( override val view: GameEntity, val maxSpeedXY: XY ) : UpdateComponent { override fun update(dt: TimeSpan) { - view.move.x = view.move.x.clamp(-maxSpeedXY.x, maxSpeedXY.x) - view.move.y = view.move.y.clamp(-maxSpeedXY.y, maxSpeedXY.y) + if (view.canMove) { + view.move.x = view.move.x.clamp(-maxSpeedXY.x, maxSpeedXY.x) + view.move.y = view.move.y.clamp(-maxSpeedXY.y, maxSpeedXY.y) + } else { + //Log().warn { "move input disabled" } + view.move.setTo(0, 0) + } } } diff --git a/src/commonMain/kotlin/components/movement/HasFacing.kt b/src/commonMain/kotlin/components/movement/HasFacing.kt new file mode 100644 index 0000000..8311846 --- /dev/null +++ b/src/commonMain/kotlin/components/movement/HasFacing.kt @@ -0,0 +1,32 @@ +package components.movement + +import com.soywiz.klock.TimeSpan +import com.soywiz.korge.component.UpdateComponent +import containers.GameEntity + +@Suppress("MemberVisibilityCanBePrivate") +class HasFacing( + override val view: GameEntity, + initialDirection: MoveDirection = MoveDirection.DOWN +) : UpdateComponent { + private var facing: MoveDirection = initialDirection + + override fun update(dt: TimeSpan) { + val directions = view.getMoveDirections() + + // Priority order: l+r > u+d + if (directions.contains(MoveDirection.RIGHT)) { + facing = MoveDirection.RIGHT + } else if (directions.contains(MoveDirection.LEFT)) { + facing = MoveDirection.LEFT + } else if (directions.contains(MoveDirection.UP)) { + facing = MoveDirection.UP + } else if (directions.contains(MoveDirection.DOWN)) { + facing = MoveDirection.DOWN + } + } + + fun getFacing(): MoveDirection { + return facing + } +} diff --git a/src/commonMain/kotlin/components/movement/MoveDirection.kt b/src/commonMain/kotlin/components/movement/MoveDirection.kt index 0b3792b..aed861e 100644 --- a/src/commonMain/kotlin/components/movement/MoveDirection.kt +++ b/src/commonMain/kotlin/components/movement/MoveDirection.kt @@ -1,5 +1,5 @@ package components.movement enum class MoveDirection { - UP, DOWN, LEFT, RIGHT + UP, DOWN, LEFT, RIGHT, NONE } diff --git a/src/commonMain/kotlin/components/movement/StraightMovement.kt b/src/commonMain/kotlin/components/movement/StraightMovement.kt index aa84878..bd7260c 100644 --- a/src/commonMain/kotlin/components/movement/StraightMovement.kt +++ b/src/commonMain/kotlin/components/movement/StraightMovement.kt @@ -3,7 +3,9 @@ package components.movement import com.soywiz.klock.TimeSpan import com.soywiz.korge.component.UpdateComponent import com.soywiz.korge.view.View -import com.soywiz.korma.geom.* +import com.soywiz.korma.geom.XY +import com.soywiz.korma.geom.add +import com.soywiz.korma.geom.times import utility.getDeltaScale class StraightMovement( diff --git a/src/commonMain/kotlin/components/movement/VerticalSineMovement.kt b/src/commonMain/kotlin/components/movement/VerticalSineMovement.kt index 31f3215..a358ab2 100644 --- a/src/commonMain/kotlin/components/movement/VerticalSineMovement.kt +++ b/src/commonMain/kotlin/components/movement/VerticalSineMovement.kt @@ -2,9 +2,7 @@ package components.movement import com.soywiz.klock.TimeSpan import com.soywiz.korge.component.UpdateComponent -import com.soywiz.korge.view.View import containers.GameEntity -import utility.getDeltaScale import kotlin.math.sin class VerticalSineMovement( diff --git a/src/commonMain/kotlin/containers/GameEntity.kt b/src/commonMain/kotlin/containers/GameEntity.kt index 29fe2d4..a8d0006 100644 --- a/src/commonMain/kotlin/containers/GameEntity.kt +++ b/src/commonMain/kotlin/containers/GameEntity.kt @@ -1,23 +1,40 @@ package containers -import com.soywiz.korge.view.BaseImage import com.soywiz.korge.view.Container import com.soywiz.korge.view.HitTestDirection +import com.soywiz.korge.view.RectBase +import com.soywiz.korge.view.addTo import com.soywiz.korma.geom.Point import com.soywiz.korma.geom.Rectangle -import program.IAssetManager +import components.movement.MoveDirection import program.LevelManager import program.SoundManager import utility.rectHitTest open class GameEntity( - protected val assets: IAssetManager, + protected var image: RectBase, protected val soundManager: SoundManager, protected val levelManager: LevelManager, protected var hp: UInt = 1u ) : Container() { val move = Point(0, 0) - lateinit var image: BaseImage + var canMove = true + var speedModifier: Double = 1.0 + + init { + image.addTo(this) + } + + fun getHP(): UInt { + return hp + } + + open fun damage(amount: UInt = 1u) { + if (amount > hp) hp = 0u else hp -= amount + if (hp == 0u) kill() + } + + open fun kill() {} fun getCurrentBounds(): Rectangle { return getGlobalBounds() @@ -26,6 +43,47 @@ open class GameEntity( fun isTouchingGround(): Boolean { val clone = getCurrentBounds().clone() clone.y++ - return levelManager.getCurrentMapView().rectHitTest(clone, HitTestDirection.DOWN) !== null + return levelManager.getMapView().rectHitTest(clone, HitTestDirection.DOWN) !== null + } + + fun isMovingLeft(): Boolean { + return move.x < 0.0 + } + + fun isMovingRight(): Boolean { + return move.x > 0.0 + } + + fun isMovingUp(): Boolean { + return move.y < 0.0 + } + + fun isMovingDown(): Boolean { + return move.y > 0.0 + } + + fun isMoving(): Boolean { + return !getMoveDirections().contains(MoveDirection.NONE) + } + + fun getMoveDirections(): Set { + val set = mutableSetOf() + + if (!isMovingLeft() && !isMovingRight() && !isMovingUp() && !isMovingDown()) { + set.add(MoveDirection.NONE) + return set + } + + if (isMovingLeft()) { + set.add(MoveDirection.LEFT) + } else if (isMovingRight()) { + set.add(MoveDirection.RIGHT) + } + if (isMovingDown()) { + set.add(MoveDirection.DOWN) + } else if (isMovingUp()) { + set.add(MoveDirection.UP) + } + return set } } diff --git a/src/commonMain/kotlin/containers/SpriteEntity.kt b/src/commonMain/kotlin/containers/SpriteEntity.kt index 85790b5..7979286 100644 --- a/src/commonMain/kotlin/containers/SpriteEntity.kt +++ b/src/commonMain/kotlin/containers/SpriteEntity.kt @@ -1,20 +1,17 @@ package containers import com.soywiz.korge.view.Sprite -import com.soywiz.korge.view.addTo import com.soywiz.korge.view.anchor -import program.IAssetManager import program.LevelManager +import program.Settings import program.SoundManager -open class SpriteEntity(sprite: Sprite, assets: IAssetManager, soundManager: SoundManager, levelManager: LevelManager) - : GameEntity(assets, soundManager, levelManager) { +open class SpriteEntity(sprite: Sprite, soundManager: SoundManager, levelManager: LevelManager) + : GameEntity(sprite, soundManager, levelManager) { init { - image = sprite - .anchor(0,0) - .addTo(this) - image.smoothing = false + image.anchor(0,0) + image.smoothing = Settings.spriteSmoothing } fun getSprite(): Sprite { diff --git a/src/commonMain/kotlin/containers/bullet/DotBullet.kt b/src/commonMain/kotlin/containers/bullet/DotBullet.kt index 610d996..8fd9d5b 100644 --- a/src/commonMain/kotlin/containers/bullet/DotBullet.kt +++ b/src/commonMain/kotlin/containers/bullet/DotBullet.kt @@ -8,24 +8,21 @@ import com.soywiz.korma.geom.XY import com.soywiz.korma.geom.cos import com.soywiz.korma.geom.sin import containers.GameEntity -import program.IAssetManager import program.LevelManager import program.SoundManager import utility.getDeltaScale open class DotBullet( - assets: IAssetManager, - soundManager: SoundManager, - levelManager: LevelManager, bulletRect: RectBase, spawn: XY, target: XY, + soundManager: SoundManager, + levelManager: LevelManager, private val shotSpeed: Double = 200.0 -) : GameEntity(assets, soundManager, levelManager), Bullet { +) : GameEntity(bulletRect, soundManager, levelManager), Bullet { init { val angle = Angle.between(spawn, target) position(spawn) - addChild(bulletRect.clone()) addUpdater { val delta = getDeltaScale(it) x += cos(angle) * it.seconds * shotSpeed diff --git a/src/commonMain/kotlin/containers/bullet/DotEnemyBullet.kt b/src/commonMain/kotlin/containers/bullet/DotEnemyBullet.kt index e3186ad..0b3c931 100644 --- a/src/commonMain/kotlin/containers/bullet/DotEnemyBullet.kt +++ b/src/commonMain/kotlin/containers/bullet/DotEnemyBullet.kt @@ -2,20 +2,14 @@ package containers.bullet import com.soywiz.korge.view.RectBase import com.soywiz.korma.geom.XY -import program.IAssetManager import program.LevelManager import program.SoundManager class DotEnemyBullet( - assets: IAssetManager, - soundManager: SoundManager, - levelManager: LevelManager, bulletRect: RectBase, spawn: XY, target: XY, + soundManager: SoundManager, + levelManager: LevelManager, shotSpeed: Double = 100.0 -) : DotBullet(assets, soundManager, levelManager, bulletRect, spawn, target, shotSpeed), EnemyBullet { - init { - addChild(assets.enemyBulletRect) - } -} +) : DotBullet(bulletRect, spawn, target, soundManager, levelManager, shotSpeed), EnemyBullet diff --git a/src/commonMain/kotlin/containers/bullet/DotPlayerBullet.kt b/src/commonMain/kotlin/containers/bullet/DotPlayerBullet.kt index 022d2e0..35ba8e4 100644 --- a/src/commonMain/kotlin/containers/bullet/DotPlayerBullet.kt +++ b/src/commonMain/kotlin/containers/bullet/DotPlayerBullet.kt @@ -2,16 +2,14 @@ package containers.bullet import com.soywiz.korge.view.RectBase import com.soywiz.korma.geom.XY -import program.IAssetManager import program.LevelManager import program.SoundManager class DotPlayerBullet( - assets: IAssetManager, - soundManager: SoundManager, - levelManager: LevelManager, bulletRect: RectBase, spawn: XY, target: XY, + soundManager: SoundManager, + levelManager: LevelManager, shotSpeed: Double = 200.0, -) : DotBullet(assets, soundManager, levelManager, bulletRect, spawn, target, shotSpeed), PlayerBullet +) : DotBullet(bulletRect, spawn, target, soundManager, levelManager, shotSpeed), PlayerBullet diff --git a/src/commonMain/kotlin/containers/enemy/Enemy.kt b/src/commonMain/kotlin/containers/enemy/Enemy.kt index 85b5697..951938f 100644 --- a/src/commonMain/kotlin/containers/enemy/Enemy.kt +++ b/src/commonMain/kotlin/containers/enemy/Enemy.kt @@ -1,40 +1,5 @@ package containers.enemy -import com.soywiz.korge.view.BaseImage -import com.soywiz.korge.view.RectBase -import com.soywiz.korge.view.onCollision -import com.soywiz.korge.view.position -import com.soywiz.korma.geom.XY -import containers.GameEntity -import containers.bullet.PlayerBullet -import program.* - -@Suppress("MemberVisibilityCanBePrivate") -abstract class Enemy( - assets: IAssetManager, - soundManager: SoundManager, - levelManager: LevelManager, - position: XY -) : GameEntity(assets, soundManager, levelManager) { - protected open val value = 100 - - open fun kill() { - this.removeFromParent() - GameState.score += value - } - - init { - position(position) - onCollision { - if (it is PlayerBullet) { - it.removeFromParent() - hp-- - if (hp == 0u) { - this@Enemy.kill() - } - } - } - - Log().debug { "Enemy created @ $position" } - } +interface Enemy { + fun kill() } diff --git a/src/commonMain/kotlin/containers/enemy/SpriteEnemy.kt b/src/commonMain/kotlin/containers/enemy/SpriteEnemy.kt new file mode 100644 index 0000000..c0770a1 --- /dev/null +++ b/src/commonMain/kotlin/containers/enemy/SpriteEnemy.kt @@ -0,0 +1,24 @@ +package containers.enemy + +import com.soywiz.korge.view.Sprite +import com.soywiz.korge.view.position +import com.soywiz.korma.geom.IPoint +import containers.SpriteEntity +import program.GameState +import program.LevelManager +import program.Log +import program.SoundManager + +@Suppress("MemberVisibilityCanBePrivate") +abstract class SpriteEnemy( + sprite: Sprite, + soundManager: SoundManager, + levelManager: LevelManager +) : SpriteEntity(sprite, soundManager, levelManager), Enemy { + protected open val value = 100u + + override fun kill() { + this.removeFromParent() + GameState.score += value + } +} diff --git a/src/commonMain/kotlin/containers/enemy/TestEnemy.kt b/src/commonMain/kotlin/containers/enemy/TestEnemy.kt new file mode 100644 index 0000000..313a93c --- /dev/null +++ b/src/commonMain/kotlin/containers/enemy/TestEnemy.kt @@ -0,0 +1,11 @@ +package containers.enemy + +import com.soywiz.korge.view.Sprite +import program.AssetManager +import program.LevelManager +import program.SoundManager + +class TestEnemy(assets: AssetManager, soundManager: SoundManager, levelManager: LevelManager) + : SpriteEnemy(Sprite(assets.testEnemyBitmap), soundManager, levelManager) { + + } diff --git a/src/commonMain/kotlin/containers/item/GamePickup.kt b/src/commonMain/kotlin/containers/item/GamePickup.kt index e6e3a2d..ad750fc 100644 --- a/src/commonMain/kotlin/containers/item/GamePickup.kt +++ b/src/commonMain/kotlin/containers/item/GamePickup.kt @@ -1,5 +1,3 @@ package containers.item -interface GamePickup { - var value: Int -} +interface GamePickup diff --git a/src/commonMain/kotlin/containers/player/Player.kt b/src/commonMain/kotlin/containers/player/Player.kt index def7de0..28779bd 100644 --- a/src/commonMain/kotlin/containers/player/Player.kt +++ b/src/commonMain/kotlin/containers/player/Player.kt @@ -1,56 +1,85 @@ package containers.player +import com.soywiz.klock.milliseconds import com.soywiz.korge.view.Sprite -import com.soywiz.korge.view.addUpdater +import com.soywiz.korge.view.addTo import com.soywiz.korge.view.onCollision -import com.soywiz.korio.dynamic.dyn +import com.soywiz.korma.geom.Point +import components.collision.MovesWithTilemapCollision +import components.input.HorizontalMoveInput +import components.input.VerticalMoveInput +import components.movement.ClampMovement +import components.movement.HasFacing +import components.movement.MoveDirection import containers.SpriteEntity -import containers.bullet.EnemyBullet import containers.enemy.Enemy -import program.GameState -import program.IAssetManager +import containers.item.GamePickup +import program.AssetManager import program.LevelManager +import program.Log import program.SoundManager const val PLAYER_NAME = "PLAYER" -abstract class Player( - sprite: Sprite, - assets: IAssetManager, +open class Player( + private val assets: AssetManager, soundManager: SoundManager, - levelManager: LevelManager -) : SpriteEntity(sprite, assets, soundManager, levelManager) { + levelManager: LevelManager, +) : SpriteEntity(Sprite(assets.playerBitmap), soundManager, levelManager) { + private var facingComponent: HasFacing = HasFacing(this) private val initialHp = hp + var isDead = false init { name = PLAYER_NAME + addComponents() onCollision { - if (it is Enemy || it is EnemyBullet) { - damage() + when (it) { + is Enemy -> damage() + is GamePickup -> it.removeFromParent() } } - addUpdater { - if (!isDead) GameState.timeAlive += it.dyn.toDouble() else return@addUpdater - } } - open fun damage(value: UInt = 1u) { - hp -= value - if (hp == 0u) kill() + private fun addComponents() { + addComponent(HorizontalMoveInput(this, assets.playerWalkLeftAnimation, assets.playerWalkRightAnimation)) + addComponent(VerticalMoveInput(this, assets.playerWalkUpAnimation, assets.playerWalkDownAnimation)) + addComponent(ClampMovement(this, Point(2.0, 2.0))) + addComponent(facingComponent) + addComponent(MovesWithTilemapCollision(this, levelManager)) } - open fun kill() { + override fun kill() { + if (isDead) return isDead = true - GameState.timeAlive = 0.0 - removeFromParent() + + getSprite().playAnimation(assets.playerDeathAnimation, 40.milliseconds) + getSprite().onAnimationCompleted { + Log().info { "player removed after death" } + image.visible = false + } + soundManager.asyncPlaySound(assets.playerDieSfx) } override fun reset() { super.reset() hp = initialHp isDead = false + image.visible = true + Log().info { "player reset" } + } + + fun getFacing(): MoveDirection { + return facingComponent.getFacing() + } + + fun resetSpriteImage() { + getSprite().playAnimation(assets.playerIdleAnimation) } - abstract fun fire() + fun fire() { + TODO("Not yet implemented") + } } + diff --git a/src/commonMain/kotlin/containers/spawn/GameEntitySpawner.kt b/src/commonMain/kotlin/containers/spawn/GameEntitySpawner.kt new file mode 100644 index 0000000..9521987 --- /dev/null +++ b/src/commonMain/kotlin/containers/spawn/GameEntitySpawner.kt @@ -0,0 +1,24 @@ +package containers.spawn + +import com.soywiz.korge.view.RectBase +import com.soywiz.korge.view.position +import com.soywiz.korma.geom.XY +import containers.GameEntity +import factories.GameEntityFactory +import program.LevelManager +import program.SoundManager + +open class GameEntitySpawner( + soundManager: SoundManager, + levelManager: LevelManager, + factory: GameEntityFactory, + position: XY +) : GameEntity(RectBase(), soundManager, levelManager) { + + init { + position(position) + + val entity = factory.create(pos.copy()) + levelManager.getMapView().addChild(entity) + } +} diff --git a/src/commonMain/kotlin/factories/GameEntityFactory.kt b/src/commonMain/kotlin/factories/GameEntityFactory.kt new file mode 100644 index 0000000..87f39c4 --- /dev/null +++ b/src/commonMain/kotlin/factories/GameEntityFactory.kt @@ -0,0 +1,9 @@ +package factories + +import com.soywiz.korma.geom.IPoint +import com.soywiz.korma.geom.Point +import containers.GameEntity + +interface GameEntityFactory { + fun create(spawnPosition: IPoint = Point.Zero): GameEntity +} diff --git a/src/commonMain/kotlin/main.kt b/src/commonMain/kotlin/main.kt new file mode 100644 index 0000000..02c6903 --- /dev/null +++ b/src/commonMain/kotlin/main.kt @@ -0,0 +1,4 @@ +import com.soywiz.korge.Korge +import program.AppModule + +suspend fun main() = Korge(Korge.Config(module = AppModule)) diff --git a/src/commonMain/kotlin/program/AppModule.kt b/src/commonMain/kotlin/program/AppModule.kt new file mode 100644 index 0000000..b9330ba --- /dev/null +++ b/src/commonMain/kotlin/program/AppModule.kt @@ -0,0 +1,22 @@ +package program + +import com.soywiz.korinject.AsyncInjector +import containers.enemy.TestEnemy +import containers.player.Player +import scenes.GameScene +import scenes.MenuScene + +object AppModule : DefaultAppModule() { + override suspend fun AsyncInjector.configure() { + mapSingleton { SoundManager() } + mapSingleton { Config() } + mapSingleton { LevelManager(get()) } + mapSingleton { AssetManager() } + + mapPrototype { GameScene() } + mapPrototype { MenuScene(title) } + mapPrototype { Player(get(), get(), get()) } + mapPrototype { TestEnemy(get(), get(), get()) } + } +} + diff --git a/src/commonMain/kotlin/program/AssetManager.kt b/src/commonMain/kotlin/program/AssetManager.kt new file mode 100644 index 0000000..dd82590 --- /dev/null +++ b/src/commonMain/kotlin/program/AssetManager.kt @@ -0,0 +1,92 @@ +package program + +import com.soywiz.korau.sound.Sound +import com.soywiz.korau.sound.readSound +import com.soywiz.korge.tiled.TiledMap +import com.soywiz.korge.tiled.TiledMapData +import com.soywiz.korge.tiled.readTiledMapData +import com.soywiz.korge.tiled.readTiledSet +import com.soywiz.korge.view.RectBase +import com.soywiz.korge.view.SpriteAnimation +import com.soywiz.korim.bitmap.Bitmap +import com.soywiz.korim.font.Font +import com.soywiz.korim.font.readFont +import com.soywiz.korim.format.readBitmap +import com.soywiz.korinject.AsyncInjector +import com.soywiz.korinject.InjectorAsyncDependency +import com.soywiz.korio.file.std.resourcesVfs +import com.soywiz.korma.geom.Point +import com.soywiz.korma.geom.PointInt + +class AssetManager : InjectorAsyncDependency, IAssetManager { + override lateinit var tileSets: MutableList + override lateinit var levels: MutableMap + override lateinit var music: MutableMap + + override lateinit var mainFont: Font + + lateinit var playerDieSfx: Sound + + lateinit var playerBitmap: Bitmap + lateinit var playerDeathAnimBitmap: Bitmap + lateinit var playerWalkRightBitmap: Bitmap + lateinit var playerWalkDownBitmap: Bitmap + lateinit var playerWalkUpBitmap: Bitmap + lateinit var testEnemyBitmap: Bitmap + + lateinit var playerIdleAnimation: SpriteAnimation + lateinit var playerDeathAnimation: SpriteAnimation + lateinit var playerWalkRightAnimation: SpriteAnimation + lateinit var playerWalkLeftAnimation: SpriteAnimation + lateinit var playerWalkUpAnimation: SpriteAnimation + lateinit var playerWalkDownAnimation: SpriteAnimation + + override suspend fun init(injector: AsyncInjector) { + val config = injector.get() + val dirs = getResourceSubdirs(config) + + mainFont = resourcesVfs["${dirs["fonts"]}/go_mono.ttf"].readFont() + + playerBitmap = resourcesVfs["${dirs["graphics"]}/player_test.png"].readBitmap() + playerDeathAnimBitmap = playerBitmap + playerWalkRightBitmap = playerBitmap + playerWalkDownBitmap = playerBitmap + playerWalkUpBitmap = playerBitmap + + testEnemyBitmap = resourcesVfs["${dirs["graphics"]}/enemy_test.png"].readBitmap() + + playerDieSfx = resourcesVfs["${dirs["audio"]}/player_die.wav"].readSound() + + buildTiledMapData(dirs) + buildSpriteAnimations() + } + + private suspend fun buildTiledMapData(dirs: Map) { + tileSets = mutableListOf( + resourcesVfs["${dirs["maps"]}/map_tiles.tsx"].readTiledSet() + ) + + levels = mutableMapOf() + levels[1u] = resourcesVfs["${dirs["maps"]}/map001.tmx"].readTiledMapData() + + music = mutableMapOf() + } + + private fun buildSpriteAnimations() { + val playerImageSize = PointInt(playerBitmap.size.width.toInt(), playerBitmap.size.height.toInt()) + playerIdleAnimation = SpriteAnimation( + spriteMap = playerBitmap, + spriteWidth = playerImageSize.x, + spriteHeight = playerImageSize.y, + marginTop = 0, + marginLeft = 0, + columns = 1, + rows = 1 + ) + playerWalkRightAnimation = playerIdleAnimation + playerWalkLeftAnimation = playerIdleAnimation + playerWalkUpAnimation = playerIdleAnimation + playerWalkDownAnimation = playerIdleAnimation + playerDeathAnimation = playerIdleAnimation + } +} diff --git a/src/commonMain/kotlin/program/DefaultAppModule.kt b/src/commonMain/kotlin/program/DefaultAppModule.kt index f813cc2..6524a25 100644 --- a/src/commonMain/kotlin/program/DefaultAppModule.kt +++ b/src/commonMain/kotlin/program/DefaultAppModule.kt @@ -6,7 +6,7 @@ import com.soywiz.korma.geom.* import scenes.MenuScene open class DefaultAppModule(override val title: String = "KorGE Boot Game", windowScale: Double = 2.0) : Module() { - private val virtualScreenSize = SizeInt(Size(Point(320, 360))) + protected open val virtualScreenSize = SizeInt(Size(Point(320, 360))) override val mainScene = MenuScene::class override val windowSize = virtualScreenSize * windowScale @@ -19,5 +19,6 @@ open class DefaultAppModule(override val title: String = "KorGE Boot Game", wind mapSingleton { SoundManager() } mapSingleton { Config() } mapSingleton { LevelManager(get()) } + mapSingleton { AssetManager() } } } diff --git a/src/commonMain/kotlin/program/GameState.kt b/src/commonMain/kotlin/program/GameState.kt index f0b256f..7e618b9 100644 --- a/src/commonMain/kotlin/program/GameState.kt +++ b/src/commonMain/kotlin/program/GameState.kt @@ -1,7 +1,6 @@ package program object GameState { - var score: Int = 0 - var hiScore: Int = 0 - var timeAlive: Double = 0.0 + var score: ULong = 0u + var hiScore: ULong = 0u } diff --git a/src/commonMain/kotlin/program/IAssetManager.kt b/src/commonMain/kotlin/program/IAssetManager.kt index 5455474..e4528f4 100644 --- a/src/commonMain/kotlin/program/IAssetManager.kt +++ b/src/commonMain/kotlin/program/IAssetManager.kt @@ -1,14 +1,16 @@ package program +import com.soywiz.korau.sound.Sound import com.soywiz.korge.tiled.TiledMap -import com.soywiz.korge.view.RectBase +import com.soywiz.korge.tiled.TiledMapData import com.soywiz.korim.font.Font interface IAssetManager { - var defaultFont: Font - var levels: MutableMap - var bulletRect: RectBase - var enemyBulletRect: RectBase + var tileSets: MutableList + var levels: MutableMap + var music: MutableMap + + var mainFont: Font fun getResourceSubdirs(config: Config): Map { return mapOf( diff --git a/src/commonMain/kotlin/program/LevelManager.kt b/src/commonMain/kotlin/program/LevelManager.kt index cee95f2..a051dba 100644 --- a/src/commonMain/kotlin/program/LevelManager.kt +++ b/src/commonMain/kotlin/program/LevelManager.kt @@ -1,43 +1,136 @@ package program +import com.soywiz.kmem.toIntFloor import com.soywiz.korge.tiled.TiledMap import com.soywiz.korge.tiled.TiledMapView import com.soywiz.korge.view.Container import com.soywiz.korge.view.addTo +import com.soywiz.korma.geom.IPoint +import com.soywiz.korma.geom.Point +import utility.recreateTileLayers +import kotlin.random.Random -class LevelManager(private val assets: IAssetManager) { - var mapView: TiledMapView? = null - var smoothing = false +@Suppress("MemberVisibilityCanBePrivate") +class LevelManager(private val assets: AssetManager) { + private var mapView: TiledMapView? = null private var currentLevel: UShort = 0u private var currentMap: TiledMap? = null + private val emptyTileId = 0 + + var smoothing = Settings.tilemapSmoothing fun setNewMap(level: UShort, container: Container, callback: TiledMapView.() -> Unit = {}): TiledMapView { currentLevel = level - currentMap = assets.levels[level] - mapView?.removeFromParent() - return createMapView(container, callback) + if (assets.levels[level] === null) throw RuntimeException("Selected level not found in assets") + currentMap = TiledMap(assets.levels[level]!!.clone(), assets.tileSets) + return createMapView(currentMap!!, container, callback) } fun getLevel(): UShort { return currentLevel } - fun getCurrentMap(): TiledMap { - if (currentMap === null) throw RuntimeException("Trying to get current level map when there isn't one.") + fun setLevel(level: UShort) { + currentLevel = level + } + + fun getLevelName(level: UShort = currentLevel): String { + return when (level) { + 1.toUShort() -> "KorGE Boot Test" + else -> { "LEVEL ??" } + } + } + + fun getMap(): TiledMap { + if (currentMap === null) throw RuntimeException("Trying to get current level map when there isn't one") return currentMap!! } - fun getCurrentMapView(): TiledMapView { - if (currentMap === null) throw RuntimeException("Trying to get current map view when there isn't one.") + fun getMapView(): TiledMapView { + if (mapView === null) throw RuntimeException("Trying to get current map view when there isn't one") return mapView!! } + fun getMapObjects(): TiledMap.Layer.Objects { + if (currentMap === null) throw RuntimeException("Trying to get current level map when there isn't one") + return currentMap!!.objectLayers.first() + } + private fun createMapView( + map: TiledMap, container: Container, callback: TiledMapView.() -> Unit = {} ): TiledMapView { - mapView = TiledMapView(getCurrentMap(), false, smoothing) + mapView?.removeFromParent() + mapView = TiledMapView(map, false, smoothing) .addTo(container, callback) return mapView!! } + + fun globalXYToTileXY(x: Double, y: Double): IPoint { + return Point( + (x / currentMap!!.tilewidth).toIntFloor(), + (y / currentMap!!.tileheight).toIntFloor() + ) + } + + fun globalXYToTileXY(xy: IPoint): IPoint { + return globalXYToTileXY(xy.x, xy.y) + } + + fun tileXYToGlobalXY(x: Int, y: Int): IPoint { + return Point( + (x * currentMap!!.tilewidth), + (y * currentMap!!.tileheight) + ) + } + + fun tileXYToGlobalXY(xy: IPoint): IPoint { + return tileXYToGlobalXY(xy.x.toInt(), xy.y.toInt()) + } + + fun getTileIdAt(x: Int, y: Int): Int? { + if (x < 0 || y < 0 || x >= currentMap?.tileLayers?.get(0)?.width!! || y >= currentMap?.tileLayers?.get(0)?.height!!) { + return null + } + return currentMap?.tileLayers?.get(0)?.get(x, y) + } + + fun setTileIdAt(x: Int, y: Int, tileId: Int) { + if (currentMap?.tileLayers?.get(0)?.get(x, y) == tileId) return + if (x < 0 || y < 0 || x >= currentMap?.tileLayers?.get(0)?.width!! || y >= currentMap?.tileLayers?.get(0)?.height!!) { + throw RuntimeException("Trying to set map tile $x,$y is out of bounds") + } + + currentMap?.tileLayers?.get(0)?.set(x, y, tileId) + mapView?.recreateTileLayers(false) + } + + fun isTileEmpty(x: Int, y: Int): Boolean { + if (currentMap === null) return true + + val tile = getTileIdAt(x, y) + Log().debug { "tile find ID:$tile" } + if (tile === null) return false + return (tile == emptyTileId) + } + + fun isTileEmpty(xy: IPoint): Boolean { + return isTileEmpty(xy.x.toInt(), xy.y.toInt()) + } + + fun getRandomTile(): IPoint { + return Point(Random.nextInt(getMap().width), Random.nextInt(getMap().height)) + } + + fun getRandomEmptyTile(attemptLimit: Int = 50): IPoint? { + if (attemptLimit < 1) return null + var attempts = 1 + while (attempts <= attemptLimit) { + val tileXY = getRandomTile() + if (isTileEmpty(tileXY)) return tileXY + attempts++ + } + return null + } } diff --git a/src/commonMain/kotlin/program/Settings.kt b/src/commonMain/kotlin/program/Settings.kt index a02559e..b674ae1 100644 --- a/src/commonMain/kotlin/program/Settings.kt +++ b/src/commonMain/kotlin/program/Settings.kt @@ -5,4 +5,6 @@ object Settings { var musicEnabled: Boolean = true var sfxVolume: Double = 0.4 var musicVolume: Double = 0.5 + var spriteSmoothing: Boolean = false + var tilemapSmoothing: Boolean = false } diff --git a/src/commonMain/kotlin/scenes/GameScene.kt b/src/commonMain/kotlin/scenes/GameScene.kt index 44a507b..a68a645 100644 --- a/src/commonMain/kotlin/scenes/GameScene.kt +++ b/src/commonMain/kotlin/scenes/GameScene.kt @@ -1,22 +1,23 @@ package scenes +import com.soywiz.klock.seconds import com.soywiz.korev.Key import com.soywiz.korge.input.keys import com.soywiz.korge.scene.Scene -import com.soywiz.korge.tiled.TiledMapView -import com.soywiz.korge.view.Container -import com.soywiz.korge.view.addUpdater -import com.soywiz.korge.view.position +import com.soywiz.korge.tiled.TiledMap +import com.soywiz.korge.view.* import com.soywiz.korio.async.launchImmediately +import com.soywiz.korma.geom.Point import containers.GameEntity +import containers.enemy.TestEnemy import containers.player.Player import program.* +@Suppress("MemberVisibilityCanBePrivate") open class GameScene : Scene() { - protected lateinit var assets: IAssetManager + protected lateinit var assets: AssetManager protected lateinit var soundManager: SoundManager protected lateinit var config: Config - protected lateinit var mapView: TiledMapView protected lateinit var player: Player protected lateinit var levelManager: LevelManager @@ -25,23 +26,51 @@ open class GameScene : Scene() { assets = injector.get() soundManager = injector.get() levelManager = injector.get() + player = injector.get() Log.setLevel(config.getLogLevel()) views.gameWindow.fullscreen = config.getFullscreenOnStart() levelManager.setNewMap(1u, this) - mapView = levelManager.getCurrentMapView() - - player = injector.get() - resetGame() - mapView.addChild(player) + resetMapState() + levelManager.getMapView().addChild(player) keys.down { when (it.key) { Key.ESCAPE -> exitToMenu() + Key.R -> resetMapState() else -> {} } } + + setupUI() + } + + private fun Container.setupUI() { + text("LEVEL") { + textSize = 18.0 + font = assets.mainFont + position(10, views.virtualHeight - 40) + addUpdater { + text = levelManager.getLevelName() + } + } + text("SCORE 0") { + textSize = 18.0 + font = assets.mainFont + position(10, views.virtualHeight - 20) + addUpdater { + text = "SCORE ${GameState.score}" + } + } + text("HI 0") { + textSize = 18.0 + font = assets.mainFont + position(views.virtualWidth - 90, views.virtualHeight - 20) + addUpdater { + text = "HI ${GameState.hiScore}" + } + } } protected fun exitToMenu() { @@ -50,31 +79,52 @@ open class GameScene : Scene() { } } - protected fun resetGame() { - mapView.x = 0.0 - mapView.y = 0.0 - GameState.score = 0 + protected suspend fun resetMapState() { + levelManager.getMapView().x = 0.0 + levelManager.getMapView().y = 0.0 - mapView.forEachChild { + levelManager.getMapView().fastForEachChild { if (it is GameEntity && it !is Player) { it.removeFromParent() } } - player.reset() - val objectLayer = levelManager.getCurrentMap().objectLayers[0] - objectLayer.opacity = 0.0 - objectLayer.objectsByType["player"]?.forEach { + player.reset() + getTiledMapObjects("player")?.forEach { player.position(it.x, it.y) } - Log().debug { "Player spawn @ ${player.pos}" } + + getTiledMapObjects("test_enemy")?.forEach { + val enemy = injector.get() + enemy.position(it.x, it.y) + levelManager.getMapView().addChild(enemy) + } + } + + protected fun getTiledMapObjects(type: String): List? { + if (levelManager.getMap().objectLayers.isEmpty()) return null + + val objectLayer = levelManager.getMapObjects() + return objectLayer.objectsByType[type] } override suspend fun Container.sceneMain() { addUpdater { - Log().info { player.move } GameState.hiScore = if (GameState.score > GameState.hiScore) GameState.score else GameState.hiScore } + addFixedUpdater(1.0.seconds) { + levelManager.getMapView().sortChildrenBy( + Comparator { a, b -> + if (a is Player) { + if (b !is Player) return@Comparator 1 + return@Comparator 0 + } else if (b is Player) { + return@Comparator -1 + } + return@Comparator 0 + } + ) + } } } diff --git a/src/commonMain/kotlin/scenes/MenuScene.kt b/src/commonMain/kotlin/scenes/MenuScene.kt index 5771369..a1b5015 100644 --- a/src/commonMain/kotlin/scenes/MenuScene.kt +++ b/src/commonMain/kotlin/scenes/MenuScene.kt @@ -6,8 +6,8 @@ import com.soywiz.korge.scene.Scene import com.soywiz.korge.view.* import com.soywiz.korim.color.Colors import com.soywiz.korio.async.launchImmediately -import program.IAssetManager -import program.LevelManager +import com.soywiz.korio.util.OS +import program.AssetManager enum class MainMenuOptions { START_GAME, @@ -15,8 +15,7 @@ enum class MainMenuOptions { } open class MenuScene(private val title: String, private val fontsize: Double = 24.0) : Scene() { - private lateinit var assets: IAssetManager - private lateinit var levelManager: LevelManager + private lateinit var assets: AssetManager private var selection: MainMenuOptions = MainMenuOptions.START_GAME private val unselectedColor = Colors.DARKGRAY @@ -24,12 +23,11 @@ open class MenuScene(private val title: String, private val fontsize: Double = 2 override suspend fun Container.sceneInit() { assets = injector.get() - levelManager = injector.get() - - val uiFont = assets.defaultFont + val uiFont = assets.mainFont text(title) { font = uiFont + color = Colors.BLACK y = 24.0 centerXOnStage() } @@ -45,15 +43,17 @@ open class MenuScene(private val title: String, private val fontsize: Double = 2 } } } - text("QUIT") { - textSize = fontsize - font = uiFont - centerOnStage() - y += 50 - addUpdater { - color = when (selection) { - MainMenuOptions.QUIT -> selectedColor - else -> unselectedColor + if (!hidesExitOption()) { + text("QUIT") { + textSize = fontsize + font = uiFont + centerOnStage() + y += 50 + addUpdater { + color = when (selection) { + MainMenuOptions.QUIT -> selectedColor + else -> unselectedColor + } } } } @@ -71,7 +71,7 @@ open class MenuScene(private val title: String, private val fontsize: Double = 2 private fun chooseOption() { when (selection) { MainMenuOptions.START_GAME -> launchImmediately { - sceneContainer.changeTo() + sceneContainer.changeTo() } MainMenuOptions.QUIT -> exitGame() } @@ -80,7 +80,7 @@ open class MenuScene(private val title: String, private val fontsize: Double = 2 private fun cursorDown() { selection = when (selection) { MainMenuOptions.QUIT -> MainMenuOptions.START_GAME - MainMenuOptions.START_GAME -> MainMenuOptions.QUIT + MainMenuOptions.START_GAME -> if (hidesExitOption()) MainMenuOptions.START_GAME else MainMenuOptions.QUIT } } @@ -91,4 +91,8 @@ open class MenuScene(private val title: String, private val fontsize: Double = 2 private fun exitGame() { views.gameWindow.close() } + + private fun hidesExitOption(): Boolean { + return OS.isIos || OS.isAndroid || OS.isJs + } } diff --git a/src/commonMain/kotlin/utility/UtilityExt.kt b/src/commonMain/kotlin/utility/UtilityExt.kt index 2e5367b..417fd8b 100644 --- a/src/commonMain/kotlin/utility/UtilityExt.kt +++ b/src/commonMain/kotlin/utility/UtilityExt.kt @@ -1,14 +1,17 @@ package utility -import components.movement.MoveDirection +import com.soywiz.kds.iterators.fastForEachWithIndex import com.soywiz.klock.TimeSpan +import com.soywiz.korge.tiled.TiledMap import com.soywiz.korge.tiled.TiledMapView -import com.soywiz.korge.view.HitTestDirection -import com.soywiz.korge.view.View -import com.soywiz.korma.geom.Point +import com.soywiz.korge.view.* +import com.soywiz.korge.view.tiles.TileMap +import com.soywiz.korge.view.tiles.tileMap import com.soywiz.korma.geom.Rectangle +import com.soywiz.korma.geom.Size import com.soywiz.korma.geom.SizeInt import com.soywiz.korma.geom.shape.Shape2d +import com.soywiz.korma.math.roundDecimalPlaces import program.Log operator fun Shape2d.Rectangle.times(scale: Double): Shape2d.Rectangle { @@ -31,38 +34,6 @@ fun ByteArray.toUInt(): UInt { return result } -fun Point.isMovingLeft(): Boolean { - return x < 0.0 -} - -fun Point.isMovingRight(): Boolean { - return x > 0.0 -} - -fun Point.isMovingUp(): Boolean { - return y < 0.0 -} - -fun Point.isMovingDown(): Boolean { - return y > 0.0 -} - -fun Point.getDirections(): Set { - val set = mutableSetOf() - - if (isMovingLeft()) { - set.add(MoveDirection.LEFT) - } else if (isMovingRight()) { - set.add(MoveDirection.RIGHT) - } - if (isMovingDown()) { - set.add(MoveDirection.DOWN) - } else if (isMovingUp()) { - set.add(MoveDirection.UP) - } - return set -} - fun TiledMapView.viewHitTest(view: View, direction: HitTestDirection = HitTestDirection.ANY): View? { //return this.hitTestView(view, direction) return rectHitTest(view.getGlobalBounds(), direction) @@ -111,3 +82,34 @@ fun TiledMapView.rectHitTest(rect: Rectangle, direction: HitTestDirection = HitT } return null } + +fun TiledMapView.recreateTileLayers(smoothing: Boolean) { + fastForEachChild { + if (it is TileMap) { + it.removeFromParent() + } + } + tiledMap.allLayers.fastForEachWithIndex { _, layer -> + if (layer is TiledMap.Layer.Tiles) { + val view: View = tileMap( + map = layer.map, + tileset = tileset, + smoothing = smoothing, + orientation = tiledMap.data.orientation, + staggerAxis = tiledMap.data.staggerAxis, + staggerIndex = tiledMap.data.staggerIndex, + tileSize = Size(tiledMap.tilewidth.toDouble(), tiledMap.tileheight.toDouble()), + ) + view.visible(layer.visible) + .name(layer.name.takeIf { it.isNotEmpty() }) + .xy(layer.offsetx, layer.offsety) + .alpha(layer.opacity) + .also { it.addProps(layer.properties) } + } + } +} + +fun Double.getSecondsDisplay(): String { + return roundDecimalPlaces(1).toString().padStart(6, '0').padEnd(6, '0') +} + diff --git a/src/commonMain/kotlin/utility/timer/EventTimer.kt b/src/commonMain/kotlin/utility/timer/EventTimer.kt index 91443c9..dc5822b 100644 --- a/src/commonMain/kotlin/utility/timer/EventTimer.kt +++ b/src/commonMain/kotlin/utility/timer/EventTimer.kt @@ -3,9 +3,13 @@ package utility.timer import com.soywiz.korge.baseview.BaseView import kotlin.time.Duration -class EventTimer(view: BaseView, length: Duration, val callback: (Timer) -> Unit) : SimpleTimer(view, length) { +class EventTimer(view: BaseView, length: Duration, var callback: (Timer) -> Unit) : SimpleTimer(view, length) { override fun finish() { super.finish() callback(this) } + + fun newCallback(callback: (Timer) -> Unit) { + this.callback = callback + } } diff --git a/src/commonMain/kotlin/utility/timer/SimpleTimer.kt b/src/commonMain/kotlin/utility/timer/SimpleTimer.kt index a2cb967..1205acc 100644 --- a/src/commonMain/kotlin/utility/timer/SimpleTimer.kt +++ b/src/commonMain/kotlin/utility/timer/SimpleTimer.kt @@ -3,10 +3,12 @@ package utility.timer import com.soywiz.klock.TimeSpan import com.soywiz.korge.baseview.BaseView import com.soywiz.korge.component.UpdateComponent +import com.soywiz.korge.component.removeFromView +import program.Log import kotlin.time.Duration import kotlin.time.DurationUnit -open class SimpleTimer(override val view: BaseView, val length: Duration) : Timer, UpdateComponent { +open class SimpleTimer(override val view: BaseView, private var length: Duration) : Timer, UpdateComponent { private var secondsLeft: Double = 0.0 private var isRunning: Boolean = false @@ -41,6 +43,10 @@ open class SimpleTimer(override val view: BaseView, val length: Duration) : Time return this } + override fun destroy() { + removeFromView() + } + protected open fun finish() { secondsLeft = 0.0 stop() @@ -48,9 +54,14 @@ open class SimpleTimer(override val view: BaseView, val length: Duration) : Time override fun update(dt: TimeSpan) { if (isRunning && !isFinished()) { + Log().debug { "timer running $secondsLeft"} secondsLeft -= dt.seconds if (isFinished()) finish() } } + + open fun setLength(length: Duration) { + this.length = length + } } diff --git a/src/commonMain/kotlin/utility/timer/Timer.kt b/src/commonMain/kotlin/utility/timer/Timer.kt index d8ac500..5bec641 100644 --- a/src/commonMain/kotlin/utility/timer/Timer.kt +++ b/src/commonMain/kotlin/utility/timer/Timer.kt @@ -1,7 +1,5 @@ package utility.timer -import com.soywiz.klock.TimeSpan - interface Timer { fun restart(): Timer fun start(): Timer @@ -9,4 +7,5 @@ interface Timer { fun isFinished(): Boolean fun isRunning(): Boolean fun reset(): Timer + fun destroy() } diff --git a/src/commonMain/resources/app.properties b/src/commonMain/resources/app.properties new file mode 100644 index 0000000..8c35eff --- /dev/null +++ b/src/commonMain/resources/app.properties @@ -0,0 +1,11 @@ +fullscreenOnStart=false +logLevel=INFO + +virtualScreenWidth=640 +virtualScreenHeight=480 + +resourceDirMaps=maps +resourceDirAudio=audio +resourceDirGraphics=gfx +resourceDirParticles=particles +resourceDirFonts=fonts diff --git a/src/commonMain/resources/audio/player_die.wav b/src/commonMain/resources/audio/player_die.wav new file mode 100644 index 0000000..f02746b Binary files /dev/null and b/src/commonMain/resources/audio/player_die.wav differ diff --git a/src/commonMain/resources/fonts/go_mono.ttf b/src/commonMain/resources/fonts/go_mono.ttf new file mode 100644 index 0000000..e64e22f Binary files /dev/null and b/src/commonMain/resources/fonts/go_mono.ttf differ diff --git a/src/commonMain/resources/gfx/enemy_test.png b/src/commonMain/resources/gfx/enemy_test.png new file mode 100644 index 0000000..ff37005 Binary files /dev/null and b/src/commonMain/resources/gfx/enemy_test.png differ diff --git a/src/commonMain/resources/gfx/map_tiles.png b/src/commonMain/resources/gfx/map_tiles.png new file mode 100644 index 0000000..24a2727 Binary files /dev/null and b/src/commonMain/resources/gfx/map_tiles.png differ diff --git a/src/commonMain/resources/gfx/player_test.png b/src/commonMain/resources/gfx/player_test.png new file mode 100644 index 0000000..6c8ce40 Binary files /dev/null and b/src/commonMain/resources/gfx/player_test.png differ diff --git a/src/commonMain/resources/maps/map001.tmx b/src/commonMain/resources/maps/map001.tmx new file mode 100644 index 0000000..aac68f2 --- /dev/null +++ b/src/commonMain/resources/maps/map001.tmx @@ -0,0 +1,43 @@ + + + + + +2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +2,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,2,0,2, +2,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +2,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,2, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,2, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +2,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +2,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +2,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,2, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,2, +2,0,2,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,2, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 + + + + + + + + diff --git a/src/commonMain/resources/maps/map_tiles.tsx b/src/commonMain/resources/maps/map_tiles.tsx new file mode 100644 index 0000000..2d3769d --- /dev/null +++ b/src/commonMain/resources/maps/map_tiles.tsx @@ -0,0 +1,5 @@ + + + + +