From 2d650485fe927ef457bc5e9f072ffa96fa8de64d Mon Sep 17 00:00:00 2001 From: Pierre Blaarkies Roux Date: Sun, 29 Mar 2020 16:23:37 +0200 Subject: [PATCH 01/11] Add player aiming and firing --- README.md | 2 +- src/main/kotlin/Main.kt | 2 +- src/main/kotlin/display/draw/Drawer.kt | 29 ++- .../kotlin/display/graphic/BasicShapes.kt | 3 +- src/main/kotlin/display/gui/GuiController.kt | 26 ++- src/main/kotlin/engine/GameState.kt | 42 +++- src/main/kotlin/engine/freeBody/Vehicle.kt | 7 +- src/main/kotlin/engine/freeBody/Warhead.kt | 73 +++++++ src/main/kotlin/game/GamePhaseHandler.kt | 185 +++++++++++++----- src/main/kotlin/game/GamePhases.kt | 6 +- src/main/kotlin/game/GamePlayer.kt | 10 +- src/main/kotlin/game/MapGenerator.kt | 4 +- src/main/kotlin/game/PlayerAim.kt | 10 + src/main/kotlin/input/CameraView.kt | 5 +- src/main/kotlin/utility/Common.kt | 2 +- 15 files changed, 336 insertions(+), 70 deletions(-) create mode 100644 src/main/kotlin/engine/freeBody/Warhead.kt create mode 100644 src/main/kotlin/game/PlayerAim.kt diff --git a/README.md b/README.md index e13b7fe..f665540 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ #

Volynov

-

A space artillery game designed for players that want to destroy each other and the planets around them. Fire missiles, bomb, high tech payloads, and sabotage equipment at the enemy and odge their attempts to do the same. All spaceships, planets and warheads are influenced by realistic physics to produce n-body orbits and collisions.

+

A space artillery game designed for players that want to destroy each other and the planets around them. Fire missiles, bombs, high tech payloads, and sabotage equipment at the enemy and dodge their attempts to do the same. All spaceships, planets and warheads are influenced by realistic physics to produce n-body orbits and collisions.


2020 03 01, First contact, again diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index d9a7f1b..8ee5797 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -6,7 +6,7 @@ import kotlin.system.exitProcess fun main() = runBlocking { try { val gameLogic: IGameLogic = AppLogic() - val gameEngine = AppRunner("Volynov", 700, 700, true, gameLogic) + val gameEngine = AppRunner("Volynov", 1000, 1000, true, gameLogic) gameEngine.run() } catch (exception: Exception) { exception.printStackTrace() diff --git a/src/main/kotlin/display/draw/Drawer.kt b/src/main/kotlin/display/draw/Drawer.kt index 1fa13d4..5646975 100644 --- a/src/main/kotlin/display/draw/Drawer.kt +++ b/src/main/kotlin/display/draw/Drawer.kt @@ -7,8 +7,11 @@ import display.graphic.Texture import engine.freeBody.FreeBody import engine.physics.CellLocation import engine.physics.GravityCell +import game.GamePlayer +import org.jbox2d.common.MathUtils.sin import org.jbox2d.common.Vec2 import java.util.* +import kotlin.math.cos import kotlin.math.sqrt class Drawer(val renderer: Renderer, val textures: TextureHolder) { @@ -26,7 +29,7 @@ class Drawer(val renderer: Renderer, val textures: TextureHolder) { x + accelerationX * multiplier, y + accelerationY * multiplier ) - val triangleStripPoints = BasicShapes.getLineTriangleStrip(linePoints, 2f) + val triangleStripPoints = BasicShapes.getLineTriangleStrip(linePoints, .2f) val arrowHeadPoints = BasicShapes.getArrowHeadPoints(linePoints) val data = getColoredData( triangleStripPoints + arrowHeadPoints, @@ -99,6 +102,27 @@ class Drawer(val renderer: Renderer, val textures: TextureHolder) { renderer.drawShape(data, scale = Vec2(1f, 1f).mul(45f)) } + fun drawPlayerAimingPointer(player: GamePlayer) { + val playerLocation = player.vehicle!!.worldBody.position + val angle = player.playerAim.angle + val aimLocation = Vec2(cos(angle), sin(angle)).mul(player.playerAim.power / 10f) + + val linePoints = listOf( + playerLocation.x, + playerLocation.y, + playerLocation.x + aimLocation.x, + playerLocation.y + aimLocation.y + ) + val triangleStripPoints = BasicShapes.getLineTriangleStrip(linePoints, .2f) + val arrowHeadPoints = BasicShapes.getArrowHeadPoints(linePoints, .5f) + val data = getColoredData( + triangleStripPoints + arrowHeadPoints, Color.RED.setAlpha(.5f), Color.RED.setAlpha(.1f) + ).toFloatArray() + + textures.white_pixel.bind() + renderer.drawStrip(data) + } + companion object { fun getColoredData( @@ -118,7 +142,8 @@ class Drawer(val renderer: Renderer, val textures: TextureHolder) { chunk[0], chunk[1], 0f, /* pos*/ color.red, color.green, color.blue, color.alpha, /* color*/ 0f, 0f /* texture*/ - ) } + ) + } } fun getLine( diff --git a/src/main/kotlin/display/graphic/BasicShapes.kt b/src/main/kotlin/display/graphic/BasicShapes.kt index 3655aa1..aafcbae 100644 --- a/src/main/kotlin/display/graphic/BasicShapes.kt +++ b/src/main/kotlin/display/graphic/BasicShapes.kt @@ -28,14 +28,13 @@ object BasicShapes { listOf(cos(t).toFloat(), sin(t).toFloat()) } - fun getArrowHeadPoints(linePoints: List): List { + fun getArrowHeadPoints(linePoints: List, headSize: Float = 1f): List { val (ax, ay, bx, by) = linePoints val normalY = bx - ax val normalX = -by + ay val magnitude = Director.getDistance(normalX, normalY) - val headSize = 5f val x = headSize * normalX / magnitude val y = headSize * normalY / magnitude return listOf( diff --git a/src/main/kotlin/display/gui/GuiController.kt b/src/main/kotlin/display/gui/GuiController.kt index eee14a1..8c5cc08 100644 --- a/src/main/kotlin/display/gui/GuiController.kt +++ b/src/main/kotlin/display/gui/GuiController.kt @@ -22,6 +22,7 @@ class GuiController(private val drawer: Drawer) { onClickSettings: () -> Unit, onClickQuit: () -> Unit ) { + clear() val buttonScale = Vec2(200f, 44f) val menuButtons = listOf( GuiButton(drawer, scale = buttonScale, title = "New Game", textSize = .27f, onClick = onClickNewGame), @@ -44,6 +45,7 @@ class GuiController(private val drawer: Drawer) { onRemovePlayer: () -> Unit, playerList: MutableList ) { + clear() elements.add(GuiLabel(drawer, Vec2(-10f, 250f), "Select Players", .2f)) updateMainMenuSelectPlayers(playerList, onAddPlayer, onRemovePlayer) @@ -100,6 +102,7 @@ class GuiController(private val drawer: Drawer) { } fun createPlayersPickShields(player: GamePlayer, onClickShield: (player: GamePlayer) -> Unit) { + clear() val shieldPickerWindow = GuiWindow( drawer, Vec2(200f, -200f), Vec2(150f, 150f), title = "Player ${player.name} to pick a shield", @@ -111,14 +114,23 @@ class GuiController(private val drawer: Drawer) { elements.add(shieldPickerWindow) } - fun createPlayerCommandPanel(player: GamePlayer, onClickFire: (player: GamePlayer) -> Unit) { - val commandPanelWindow = - GuiWindow( - drawer, Vec2(200f, -200f), Vec2(150f, 150f), title = "Player ${player.name}", - draggable = true + fun createPlayerCommandPanel( + player: GamePlayer, + onClickAim: (player: GamePlayer) -> Unit, + onClickPower: (player: GamePlayer) -> Unit, + onClickFire: (player: GamePlayer) -> Unit + ) { + clear() + val commandPanelWindow = GuiWindow( + drawer, Vec2(350f, -350f), Vec2(150f, 150f), + title = "Player ${player.name}", draggable = true + ) + commandPanelWindow.addChildren( + listOf( + GuiButton(drawer, Vec2(-100f, 0f), Vec2(50f, 25f), title = "Aim", onClick = { onClickAim(player) }), + GuiButton(drawer, Vec2(-100f, -50f), Vec2(50f, 25f), title = "Power", onClick = { onClickPower(player) }), + GuiButton(drawer, Vec2(-100f, -100f), Vec2(50f, 25f), title = "Fire", onClick = { onClickFire(player) }) ) - commandPanelWindow.addChild( - GuiButton(drawer, scale = Vec2(100f, 25f), title = "Fire all guns!", onClick = { onClickFire(player) }) ) elements.add(commandPanelWindow) } diff --git a/src/main/kotlin/engine/GameState.kt b/src/main/kotlin/engine/GameState.kt index 5dcc430..ba7282e 100644 --- a/src/main/kotlin/engine/GameState.kt +++ b/src/main/kotlin/engine/GameState.kt @@ -2,14 +2,20 @@ package engine import input.CameraView import display.Window +import display.draw.TextureConfig +import display.draw.TextureHolder +import display.graphic.BasicShapes import engine.freeBody.Planet import engine.freeBody.Vehicle +import engine.freeBody.Warhead import engine.motion.Motion import engine.physics.CellLocation import engine.physics.Gravity import engine.physics.GravityCell import game.GamePlayer import game.GamePlayerTypes +import org.jbox2d.common.MathUtils.cos +import org.jbox2d.common.MathUtils.sin import org.jbox2d.common.Vec2 import org.jbox2d.dynamics.World @@ -23,14 +29,14 @@ class GameState { var world = World(Vec2()) var vehicles = mutableListOf() var planets = mutableListOf() + var warheads = mutableListOf() // private var worlds = mutableListOf() // private var asteroids = mutableListOf() // private var stars = mutableListOf() -// private var warheads = mutableListOf() val tickables - get() = vehicles + planets + get() = vehicles + planets + warheads var gravityMap = HashMap() var resolution = 0f @@ -66,4 +72,36 @@ class GameState { planets.clear() } + fun fireWarhead( + textures: TextureHolder, + player: GamePlayer, + warheadType: String = "will make this some class later" + ): Warhead { + checkNotNull(player.vehicle) { "Player does not have a vehicle." } + val vehicle = player.vehicle!! + val angle = player.playerAim.angle + val power = player.playerAim.power * .15f + val origin = vehicle.worldBody.position + val originVelocity = vehicle.worldBody.linearVelocity + + val warheadRadius = .2f + val minimumSafeDistance = 3f * vehicle.radius + val warheadLocation = Vec2( + origin.x + cos(angle) * minimumSafeDistance, + origin.y + sin(angle) * minimumSafeDistance + ) + val warheadVelocity = Vec2(originVelocity.x + cos(angle) * power, originVelocity.y + sin(angle) * power) + + return Warhead.create( + world, player, warheadLocation.x, warheadLocation.y, angle, + warheadVelocity.x, warheadVelocity.y, 0f, + .1f, warheadRadius, textureConfig = TextureConfig(textures.metal) + ) + .let { + warheads.add(it) + it + } + + } + } diff --git a/src/main/kotlin/engine/freeBody/Vehicle.kt b/src/main/kotlin/engine/freeBody/Vehicle.kt index 7ad5168..713aabe 100644 --- a/src/main/kotlin/engine/freeBody/Vehicle.kt +++ b/src/main/kotlin/engine/freeBody/Vehicle.kt @@ -20,6 +20,7 @@ class Vehicle( ) : FreeBody(id, motion, shapeBox, worldBody, radius, textureConfig) { var shield: VehicleShield? = null + var hitPoints: Float = 100f companion object { @@ -39,13 +40,15 @@ class Vehicle( textureConfig: TextureConfig ): Vehicle { val shapeBox = PolygonShape() - val vertices = BasicShapes.polygon4.chunked(2).map { Vec2(it[0] * radius, it[1] * radius) }.toTypedArray() + val vertices = BasicShapes.polygon4.chunked(2) + .map { Vec2(it[0] * radius, it[1] * radius) } + .toTypedArray() shapeBox.set(vertices, vertices.size) val bodyDef = createBodyDef(BodyType.DYNAMIC, x, y, h, dx, dy, dh) val worldBody = createWorldBody(shapeBox, mass, radius, friction, restitution, world, bodyDef) textureConfig.chunkedVertices = - shapeBox.vertices.flatMap { listOf(it.x / radius, it.y / radius) }.chunked(2) + shapeBox.vertices.map { listOf(it.x / radius, it.y / radius) } return Vehicle(player.name, Motion(), shapeBox, worldBody, radius, textureConfig) .let { diff --git a/src/main/kotlin/engine/freeBody/Warhead.kt b/src/main/kotlin/engine/freeBody/Warhead.kt new file mode 100644 index 0000000..f214ab5 --- /dev/null +++ b/src/main/kotlin/engine/freeBody/Warhead.kt @@ -0,0 +1,73 @@ +package engine.freeBody + +import display.draw.TextureConfig +import display.graphic.BasicShapes +import engine.motion.Motion +import game.GamePlayer +import org.jbox2d.collision.shapes.PolygonShape +import org.jbox2d.collision.shapes.Shape +import org.jbox2d.common.Vec2 +import org.jbox2d.dynamics.Body +import org.jbox2d.dynamics.BodyType +import org.jbox2d.dynamics.World + +class Warhead( + id: String, + firedBy: GamePlayer, + motion: Motion, + shapeBox: Shape, + worldBody: Body, + radius: Float, + textureConfig: TextureConfig +) : FreeBody(id, motion, shapeBox, worldBody, radius, textureConfig) { + + + companion object { + + fun create( + world: World, + firedBy: GamePlayer, + x: Float, + y: Float, + h: Float, + dx: Float, + dy: Float, + dh: Float, + mass: Float, + radius: Float = .7F, + restitution: Float = .3f, + friction: Float = .6f, + textureConfig: TextureConfig + ): Warhead { + val shapeBox = PolygonShape() + val vertices = BasicShapes.polygon4.chunked(2) + .map { Vec2(it[0] * radius * 2f, it[1] * radius) } + .toTypedArray() + shapeBox.set(vertices, vertices.size) + + val bodyDef = createBodyDef(BodyType.DYNAMIC, x, y, h, dx, dy, dh) + val worldBody = createWorldBody(shapeBox, mass, radius, friction, restitution, world, bodyDef) + textureConfig.chunkedVertices = + shapeBox.vertices.map { listOf(it.x / radius, it.y / radius) } + + return Warhead("1", firedBy, Motion(), shapeBox, worldBody, radius, textureConfig) + .let { +// it.textureConfig.updateGpuBufferData() + it.textureConfig.gpuBufferData = it.textureConfig.chunkedVertices.flatMap { + val (x, y) = it + listOf( + x, y, 0f, + 1f, .3f, .3f, 1f, + (x * .5f - 0.5f) * .100f, + (y * .5f - 0.5f) * .100f + ) + }.toFloatArray() + + firedBy.warheads.add(it) + it + } + } + + } + +} diff --git a/src/main/kotlin/game/GamePhaseHandler.kt b/src/main/kotlin/game/GamePhaseHandler.kt index caa5f1d..94a67e2 100644 --- a/src/main/kotlin/game/GamePhaseHandler.kt +++ b/src/main/kotlin/game/GamePhaseHandler.kt @@ -8,6 +8,8 @@ import display.events.MouseButtonEvent import display.graphic.Color import display.gui.GuiController import engine.GameState +import engine.freeBody.Warhead +import engine.motion.Director import engine.shields.VehicleShield import org.jbox2d.common.Vec2 import utility.Common.getTimingFunctionEaseOut @@ -41,10 +43,16 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val fun init(window: Window) { exitCall = { window.exit() } - setupMainMenu() +// setupMainMenu() -// gameState.gamePlayers.add(GamePlayer("Bob")) -// setupStartGame() + currentPhase = GamePhases.PLAYERS_PICK_SHIELDS + isTransitioning = false + gameState.gamePlayers.addAll((1..3).map { GamePlayer(it.toString()) }) + MapGenerator.populateNewGameMap(gameState, textures) + gameState.gamePlayers.forEach { it.vehicle?.shield = VehicleShield() } + gameState.playerOnTurn = gameState.gamePlayers[0] + + setupNextPlayersTurn() } fun dragMouseRightClick(movement: Vec2) { @@ -73,30 +81,46 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val val cp = currentPhase when { - cp == GamePhases.PAUSE && isTransitioning -> tickGamePausing(pauseDownDuration, GamePhases.PAUSE) + cp == GamePhases.PAUSE && isTransitioning -> tickGamePausing() cp == GamePhases.PAUSE -> return - cp == GamePhases.PLAY && isTransitioning -> tickGameUnpausing(pauseDownDuration, GamePhases.PLAY) + cp == GamePhases.PLAY && isTransitioning -> tickGameUnpausing() cp == GamePhases.MAIN_MENU -> return cp == GamePhases.MAIN_MENU_SELECT_PLAYERS -> return - cp == GamePhases.NEW_GAME_INTRO && isTransitioning -> tickGameUnpausing( - pauseDownDuration, GamePhases.NEW_GAME_INTRO - ) + cp == GamePhases.NEW_GAME_INTRO && isTransitioning -> tickGameUnpausing() cp == GamePhases.NEW_GAME_INTRO -> handleIntro() cp == GamePhases.PLAYERS_PICK_SHIELDS && isTransitioning -> { - if (elapsedTime < pauseDownDuration) isTransitioning = false + if (elapsedTime < pauseTime) isTransitioning = false } cp == GamePhases.PLAYERS_PICK_SHIELDS -> return cp == GamePhases.PLAYERS_TURN -> return + cp == GamePhases.PLAYERS_TURN_FIRED && isTransitioning -> tickGameUnpausing(quickStartTime) + cp == GamePhases.PLAYERS_TURN_FIRED -> handlePlayerShot() + cp == GamePhases.PLAYERS_TURN_AIMING -> return + cp == GamePhases.PLAYERS_TURN_POWERING -> return + cp == GamePhases.END_ROUND && isTransitioning -> tickGamePausing(endSpeed = .3f) + cp == GamePhases.END_ROUND -> gameState.tickClock(timeStep * .3f, velocityIterations, positionIterations) + + else -> gameState.tickClock(timeStep, velocityIterations, positionIterations) + } + } + private fun handlePlayerShot() { + when { + // if (no active warheads && all players in contact with a moon or bigger) setupNextPlayersTurn() + elapsedTime > maxTurnDuration -> setupNextPlayersTurn() + elapsedTime > (maxTurnDuration - pauseTime) -> tickGamePausing( + pauseTime, calculatedElapsedTime = (elapsedTime - maxTurnDuration + pauseTime) + ) else -> gameState.tickClock(timeStep, velocityIterations, positionIterations) } + } private fun handleIntro() { when { elapsedTime > introDuration -> playerSelectsShield() elapsedTime > (introDuration - introStartSlowdown) -> tickGamePausing( - introStartSlowdown, GamePhases.PLAYERS_PICK_SHIELDS, (elapsedTime - introDuration + introStartSlowdown) + introStartSlowdown, calculatedElapsedTime = (elapsedTime - introDuration + introStartSlowdown) ) else -> gameState.tickClock(timeStep, velocityIterations, positionIterations) } @@ -106,19 +130,18 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val player?.vehicle?.shield = VehicleShield() if (gameState.gamePlayers.all { it.vehicle?.shield != null }) { - setupPlayersTurn() + setupNextPlayersTurn() return } - guiController.clear() + setNextPlayerOnTurn() setupPlayersPickShields() currentPhase = GamePhases.PLAYERS_PICK_SHIELDS - startTransition() +// startTransition() } - private fun setupPlayersTurn() { - guiController.clear() + private fun setupNextPlayersTurn() { setNextPlayerOnTurn() setupPlayerCommandPanel() @@ -127,19 +150,49 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val } private fun setupPlayerCommandPanel() { - guiController.clear() guiController.createPlayerCommandPanel( player = gameState.playerOnTurn!!, + onClickAim = { player -> playerSetsAim(player) }, + onClickPower = { player -> playerSetsPower(player) }, onClickFire = { player -> playerFires(player) } ) } + private fun playerSetsPower(player: GamePlayer) { + currentPhase = GamePhases.PLAYERS_TURN_POWERING + startTransition() + } + + private fun playerSetsAim(player: GamePlayer) { + currentPhase = GamePhases.PLAYERS_TURN_AIMING + startTransition() + } + private fun playerFires(player: GamePlayer) { println("Player ${player.name} fired a gun!") + gameState.gamePlayers.filter { it != gameState.playerOnTurn }.forEach { it.vehicle!!.hitPoints -= 10f } + gameState.gamePlayers.forEach { println("Player ${it.name} has <${it.vehicle?.hitPoints}> hitPoints left") } + + val vehiclesDestroyed = + gameState.gamePlayers.count { it.vehicle!!.hitPoints <= 0f } >= gameState.gamePlayers.size - 1 + if (vehiclesDestroyed) { + currentPhase = GamePhases.END_ROUND + startTransition() + return + } + /////////// + + // check() {} player has enough funds && in stable position to fire large warheads + + val firedWarhead = gameState.fireWarhead(textures, player, "boom small") + camera.trackFreeBody(firedWarhead, 200f) + + currentPhase = GamePhases.PLAYERS_TURN_FIRED + startTransition() } private fun setNextPlayerOnTurn() { - check(gameState.playerOnTurn != null) { "Cannot play a game with no players." } + checkNotNull(gameState.playerOnTurn) { "No player is on turn." } val playerOnTurn = gameState.playerOnTurn!! val players = gameState.gamePlayers gameState.playerOnTurn = players[(players.indexOf(playerOnTurn) + 1).rem(players.size)] @@ -148,7 +201,6 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val } private fun setupPlayersPickShields() { - guiController.clear() guiController.createPlayersPickShields( onClickShield = { player -> playerSelectsShield(player) }, player = gameState.playerOnTurn!! @@ -159,14 +211,17 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val when (currentPhase) { GamePhases.MAIN_MENU -> guiController.render() GamePhases.MAIN_MENU_SELECT_PLAYERS -> guiController.render() - GamePhases.PLAYERS_PICK_SHIELDS -> { - drawPlayPhase() - guiController.render() + GamePhases.PLAYERS_PICK_SHIELDS -> drawWorldAndGui() + GamePhases.PLAYERS_TURN -> drawWorldAndGui() + GamePhases.PLAYERS_TURN_AIMING -> { + drawWorldAndGui() + drawer.drawPlayerAimingPointer(gameState.playerOnTurn!!) } - GamePhases.PLAYERS_TURN -> { - drawPlayPhase() - guiController.render() + GamePhases.PLAYERS_TURN_POWERING -> { + drawWorldAndGui() + drawer.drawPlayerAimingPointer(gameState.playerOnTurn!!) } + GamePhases.END_ROUND -> drawWorldAndGui() else -> drawPlayPhase() } @@ -183,6 +238,11 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val ) } + private fun drawWorldAndGui() { + drawPlayPhase() + guiController.render() + } + private fun drawPlayPhase() { drawer.drawPicture(textures.stars_2k) @@ -193,10 +253,14 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val // drawer.drawGravityCells(gameState.gravityMap, gameState.resolution) } - private fun tickGameUnpausing(duration: Float = pauseDownDuration, endPhase: GamePhases) { - val interpolateStep = elapsedTime / duration + private fun tickGameUnpausing( + duration: Float = pauseTime, + endPhase: GamePhases? = null, + calculatedElapsedTime: Float? = null + ) { + val interpolateStep = (calculatedElapsedTime ?: elapsedTime.toFloat()) / duration if (interpolateStep >= 1f) { - currentPhase = endPhase + currentPhase = endPhase ?: currentPhase isTransitioning = false } else { val timeFunctionStep = getTimingFunctionEaseOut(interpolateStep) @@ -205,17 +269,23 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val } private fun tickGamePausing( - duration: Float = pauseDownDuration, - endPhase: GamePhases, - calculatedElapsedTime: Float? = null + duration: Float = pauseTime, + endPhase: GamePhases? = null, + calculatedElapsedTime: Float? = null, + endSpeed: Float = 0f ) { val interpolateStep = (calculatedElapsedTime ?: elapsedTime.toFloat()) / duration if (interpolateStep >= 1f) { - currentPhase = endPhase + currentPhase = endPhase ?: currentPhase isTransitioning = false } else { - val timeFunctionStep = getTimingFunctionSineEaseIn(1f - interpolateStep) + val timeFunctionStep = getTimingFunctionSineEaseIn((1f - endSpeed) - interpolateStep) gameState.tickClock(timeStep * timeFunctionStep, velocityIterations, positionIterations) + +// println( +// "${interpolateStep.toString().padEnd(4, '0').substring(1, 4)} <> ${timeFunctionStep.toString() +// .padEnd(4, '0').substring(1, 4)}" +// ) } } @@ -241,14 +311,39 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val } fun moveMouse(location: Vec2) { - if (mouseElementPhases.any { currentPhase == it }) { - guiController.checkHover(getScreenLocation(location)) + when { + mouseElementPhases.any { currentPhase == it } -> guiController.checkHover(getScreenLocation(location)) + currentPhase == GamePhases.PLAYERS_TURN_AIMING -> { + checkNotNull(gameState.playerOnTurn) { "No player is on turn." } + val playerOnTurn = gameState.playerOnTurn!! + + val transformedLocation = getScreenLocation(location).mul(camera.z).add(camera.location) + val playerLocation = playerOnTurn.vehicle!!.worldBody.position + val aimDirection = Director.getDirection( + transformedLocation.x, transformedLocation.y, playerLocation.x, playerLocation.y + ) + playerOnTurn.playerAim.angle = aimDirection + } + currentPhase == GamePhases.PLAYERS_TURN_POWERING -> { + checkNotNull(gameState.playerOnTurn) { "No player is on turn." } + val playerOnTurn = gameState.playerOnTurn!! + + val transformedLocation = getScreenLocation(location).mul(camera.z).add(camera.location) + val playerLocation = playerOnTurn.vehicle!!.worldBody.position + val distance = Director.getDistance( + transformedLocation.x, transformedLocation.y, playerLocation.x, playerLocation.y + ) + playerOnTurn.playerAim.power = distance * 10f + } } + } fun leftClickMouse(event: MouseButtonEvent) { - if (mouseElementPhases.any { currentPhase == it }) { - guiController.checkLeftClick(getScreenLocation(event.location)) + when { + mouseElementPhases.any { currentPhase == it } -> guiController.checkLeftClick(getScreenLocation(event.location)) + currentPhase == GamePhases.PLAYERS_TURN_AIMING -> currentPhase = GamePhases.PLAYERS_TURN + currentPhase == GamePhases.PLAYERS_TURN_POWERING -> currentPhase = GamePhases.PLAYERS_TURN } } @@ -268,7 +363,7 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val private fun setupMainMenu() { currentPhase = GamePhases.MAIN_MENU - guiController.clear() + gameState.reset() guiController.createMainMenu( onClickNewGame = { setupMainMenuSelectPlayers() }, onClickSettings = {}, @@ -279,7 +374,6 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val private fun setupMainMenuSelectPlayers() { currentPhase = GamePhases.MAIN_MENU_SELECT_PLAYERS gameState.reset() - guiController.clear() guiController.createMainMenuSelectPlayers( onClickStart = { setupStartGame() }, onClickCancel = { setupMainMenu() }, @@ -302,23 +396,24 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val } private fun setupStartGame() { + guiController.clear() currentPhase = GamePhases.NEW_GAME_INTRO startTransition() MapGenerator.populateNewGameMap(gameState, textures) - if (gameState.gamePlayers.size > 0) { - val startingPlayer = gameState.gamePlayers.random() - gameState.playerOnTurn = startingPlayer - } + check(gameState.gamePlayers.size > 0) { "Cannot play a game with no players." } + gameState.playerOnTurn = gameState.gamePlayers.random() } companion object { - private const val pauseDownDuration = 1000f - private const val introDuration = 5000f + private const val pauseTime = 1000f + private const val introDuration = 3500f private const val introStartSlowdown = 2000f - private const val maxPlayDuration = 30000f + private const val maxTurnDuration = 5000f + private const val quickStartTime = 300f + private val mouseElementPhases = listOf( GamePhases.MAIN_MENU, GamePhases.MAIN_MENU_SELECT_PLAYERS, diff --git a/src/main/kotlin/game/GamePhases.kt b/src/main/kotlin/game/GamePhases.kt index e5a7360..73c67c2 100644 --- a/src/main/kotlin/game/GamePhases.kt +++ b/src/main/kotlin/game/GamePhases.kt @@ -8,6 +8,10 @@ enum class GamePhases { NEW_GAME_INTRO, PLAYERS_PICK_SHIELDS, NONE, - PLAYERS_TURN + PLAYERS_TURN, + PLAYERS_TURN_FIRED, + PLAYERS_TURN_AIMING, + PLAYERS_TURN_POWERING, + END_ROUND } diff --git a/src/main/kotlin/game/GamePlayer.kt b/src/main/kotlin/game/GamePlayer.kt index 384a2c4..15c4e08 100644 --- a/src/main/kotlin/game/GamePlayer.kt +++ b/src/main/kotlin/game/GamePlayer.kt @@ -1,9 +1,15 @@ package game import engine.freeBody.Vehicle +import engine.freeBody.Warhead class GamePlayer( val name: String, val type: GamePlayerTypes = GamePlayerTypes.HUMAN, - var vehicle: Vehicle? = null -) + var vehicle: Vehicle? = null, + val playerAim: PlayerAim = PlayerAim() +) { + + val warheads = mutableListOf() + +} diff --git a/src/main/kotlin/game/MapGenerator.kt b/src/main/kotlin/game/MapGenerator.kt index 06d2565..274316a 100644 --- a/src/main/kotlin/game/MapGenerator.kt +++ b/src/main/kotlin/game/MapGenerator.kt @@ -80,7 +80,7 @@ object MapGenerator { .withIndex() .map { (i, _) -> val ratio = (2 * PI * 0.07 * i).toFloat() - val radius = 10f + val radius = 15f floatArrayOf( (i * .04f + radius) * cos(ratio), (i * .04f + radius) * sin(ratio), @@ -89,7 +89,7 @@ object MapGenerator { } .map { val direction = Director.getDirection(-it[0], -it[1]) + PI * .5f - val speed = 8f + val speed = 7f Planet.create( world, "${it[2].toInt()}", it[0], it[1], 0f, cos(direction).toFloat() * speed, diff --git a/src/main/kotlin/game/PlayerAim.kt b/src/main/kotlin/game/PlayerAim.kt new file mode 100644 index 0000000..ad0388b --- /dev/null +++ b/src/main/kotlin/game/PlayerAim.kt @@ -0,0 +1,10 @@ +package game + +class PlayerAim(var angle: Float = 0f, power: Float = 100f) { + + var power = power + set(value) { + field = value.coerceIn(0f, 100f) + } + +} diff --git a/src/main/kotlin/input/CameraView.kt b/src/main/kotlin/input/CameraView.kt index 2e3a1c9..15f5cc6 100644 --- a/src/main/kotlin/input/CameraView.kt +++ b/src/main/kotlin/input/CameraView.kt @@ -19,7 +19,7 @@ class CameraView(private val window: Window) { var currentPhase = CameraPhases.STATIC var lastStaticLocation = location private var lastPhaseTimestamp = System.currentTimeMillis() - private val transitionDuration = 1000f + private var transitionDuration = 1000f private lateinit var trackFreeBody: FreeBody @@ -47,12 +47,13 @@ class CameraView(private val window: Window) { location = position } - fun trackFreeBody(newFreeBody: FreeBody) { + fun trackFreeBody(newFreeBody: FreeBody, transitionTime: Float = 1000f) { currentPhase = CameraPhases.TRANSITION_TO_TARGET lastPhaseTimestamp = System.currentTimeMillis() trackFreeBody = newFreeBody lastStaticLocation = location + transitionDuration = transitionTime } fun moveLocation(movement: Vec2) { diff --git a/src/main/kotlin/utility/Common.kt b/src/main/kotlin/utility/Common.kt index 82be51f..6eb7c79 100644 --- a/src/main/kotlin/utility/Common.kt +++ b/src/main/kotlin/utility/Common.kt @@ -49,7 +49,7 @@ object Common { fun getTimingFunctionFullSine(interpolateStep: Float) = (sin(interpolateStep * PI - PI * .5) * .5 + .5).toFloat() fun getTimingFunctionSigmoid(interpolateStep: Float, centerGradient: Float = 1f) = - (1f / (1f + exp((-(interpolateStep - .5f) * 10f)) * centerGradient)) + (1f / (1f + exp((-(interpolateStep - .5f) * 10f)) * centerGradient)) * 1.023f - 0.0022f } From dab9693ea9abd585f6ef4df84fa6f8d61d4abe0f Mon Sep 17 00:00:00 2001 From: Pierre Blaarkies Roux Date: Tue, 31 Mar 2020 20:14:57 +0200 Subject: [PATCH 02/11] Add update method to print real-time data in labels --- src/main/kotlin/display/gui/GuiButton.kt | 7 ++-- src/main/kotlin/display/gui/GuiController.kt | 24 +++++++++-- src/main/kotlin/display/gui/GuiElement.kt | 42 ++++++++----------- .../kotlin/display/gui/GuiElementInterface.kt | 3 ++ src/main/kotlin/display/gui/GuiLabel.kt | 5 ++- src/main/kotlin/display/gui/GuiWindow.kt | 25 +++++------ src/main/kotlin/game/GamePhaseHandler.kt | 3 +- src/main/kotlin/game/PlayerAim.kt | 6 +++ src/main/kotlin/utility/Common.kt | 4 +- 9 files changed, 68 insertions(+), 51 deletions(-) diff --git a/src/main/kotlin/display/gui/GuiButton.kt b/src/main/kotlin/display/gui/GuiButton.kt index 7a1c241..7d0ae20 100644 --- a/src/main/kotlin/display/gui/GuiButton.kt +++ b/src/main/kotlin/display/gui/GuiButton.kt @@ -12,8 +12,9 @@ class GuiButton( title: String, textSize: Float = .2f, color: Color = Color.WHITE.setAlpha(.7f), - private val onClick: () -> Unit = {} -) : GuiElement(drawer, offset, scale, title, textSize, color) { + private val onClick: () -> Unit = {}, + updateCallback: (GuiElement) -> Unit = {} +) : GuiElement(drawer, offset, scale, title, textSize, color, updateCallback) { private var buttonOutline: FloatArray private var buttonBackground: FloatArray @@ -59,6 +60,4 @@ class GuiButton( } } - - } diff --git a/src/main/kotlin/display/gui/GuiController.kt b/src/main/kotlin/display/gui/GuiController.kt index 8c5cc08..d7ef695 100644 --- a/src/main/kotlin/display/gui/GuiController.kt +++ b/src/main/kotlin/display/gui/GuiController.kt @@ -2,8 +2,11 @@ package display.gui import display.draw.Drawer import display.graphic.Color +import engine.GameState import game.GamePlayer import org.jbox2d.common.Vec2 +import utility.Common +import utility.Common.roundFloat class GuiController(private val drawer: Drawer) { @@ -11,6 +14,8 @@ class GuiController(private val drawer: Drawer) { fun render() = elements.forEach { it.render() } + fun update() = elements.forEach { it.update() } + fun clear() = elements.clear() fun checkHover(location: Vec2) = elements.forEach { it.handleHover(location) } @@ -127,14 +132,26 @@ class GuiController(private val drawer: Drawer) { ) commandPanelWindow.addChildren( listOf( - GuiButton(drawer, Vec2(-100f, 0f), Vec2(50f, 25f), title = "Aim", onClick = { onClickAim(player) }), - GuiButton(drawer, Vec2(-100f, -50f), Vec2(50f, 25f), title = "Power", onClick = { onClickPower(player) }), - GuiButton(drawer, Vec2(-100f, -100f), Vec2(50f, 25f), title = "Fire", onClick = { onClickFire(player) }) + GuiButton(drawer, Vec2(-100f, 0f), Vec2(50f, 25f), title = "Aim", + onClick = { onClickAim(player) }), + GuiButton(drawer, Vec2(-100f, -50f), Vec2(50f, 25f), title = "Power", + onClick = { onClickPower(player) }), + GuiButton(drawer, Vec2(-100f, -100f), Vec2(50f, 25f), title = "Fire", + onClick = { onClickFire(player) }), + + GuiLabel(drawer, + Vec2(0f, 90f), + player.playerAim.getDegreesAngle().let { displayNumber(it, 2) }, .15f, updateCallback = + { it.title = player.playerAim.getDegreesAngle().let { displayNumber(it, 2) } }), + GuiLabel(drawer, Vec2(0f, 70f), player.playerAim.power.let { displayNumber(it, 2) }, .15f, + updateCallback = { it.title = player.playerAim.power.let { displayNumber(it, 2) } }) ) ) elements.add(commandPanelWindow) } + private fun displayNumber(value: Float, decimals: Int): String = roundFloat(value, decimals).toString() + private fun setElementsInColumns(elements: List, gap: Float = 0f, centered: Boolean = true) { val totalWidth = elements.map { it.scale.x * 2f }.sum() + gap * (elements.size - 1) val columnSize = totalWidth / elements.size @@ -163,5 +180,4 @@ class GuiController(private val drawer: Drawer) { } } - } diff --git a/src/main/kotlin/display/gui/GuiElement.kt b/src/main/kotlin/display/gui/GuiElement.kt index 5e6e38b..1604312 100644 --- a/src/main/kotlin/display/gui/GuiElement.kt +++ b/src/main/kotlin/display/gui/GuiElement.kt @@ -9,9 +9,10 @@ open class GuiElement( protected val drawer: Drawer, override var offset: Vec2, override val scale: Vec2 = Vec2(1f, 1f), - override val title: String, + override var title: String, override val textSize: Float, override val color: Color, + override val updateCallback: (GuiElement) -> Unit, override var id: GuiElementIdentifierType = GuiElementIdentifierType.DEFAULT ) : GuiElementInterface { @@ -20,33 +21,26 @@ open class GuiElement( protected var topRight: Vec2 = Vec2() protected var bottomLeft: Vec2 = Vec2() - override fun render() { - } + override fun render() = Unit - override fun addOffset(newOffset: Vec2) { - GuiElement.addOffset(this, newOffset) - } + override fun update() = updateCallback(this) - override fun updateOffset(newOffset: Vec2) { - GuiElement.updateOffset(this, newOffset) - } + override fun addOffset(newOffset: Vec2) = GuiElement.addOffset(this, newOffset) - override fun handleHover(location: Vec2) { - when { - isHover(location) -> currentPhase = GuiElementPhases.HOVERED - else -> currentPhase = GuiElementPhases.IDLE - } - } + override fun updateOffset(newOffset: Vec2) = GuiElement.updateOffset(this, newOffset) - override fun handleClick(location: Vec2) { + override fun handleHover(location: Vec2) = when { + isHover(location) -> currentPhase = GuiElementPhases.HOVERED + else -> currentPhase = GuiElementPhases.IDLE } - protected fun isHover(location: Vec2): Boolean { - return location.x > bottomLeft.x - && location.x < topRight.x - && location.y > bottomLeft.y - && location.y < topRight.y - } + override fun handleClick(location: Vec2) = Unit + + protected fun isHover(location: Vec2): Boolean = + location.x > bottomLeft.x + && location.x < topRight.x + && location.y > bottomLeft.y + && location.y < topRight.y companion object { @@ -63,9 +57,7 @@ open class GuiElement( element.topRight = element.offset.add(element.scale) } - fun addOffset(element: GuiElement, newOffset: Vec2) { - updateOffset(element, element.offset.add(newOffset)) - } + fun addOffset(element: GuiElement, newOffset: Vec2) = updateOffset(element, element.offset.add(newOffset)) fun updateOffset(element: GuiElement, newOffset: Vec2) { element.offset = newOffset diff --git a/src/main/kotlin/display/gui/GuiElementInterface.kt b/src/main/kotlin/display/gui/GuiElementInterface.kt index 8c4f7a5..6bf686e 100644 --- a/src/main/kotlin/display/gui/GuiElementInterface.kt +++ b/src/main/kotlin/display/gui/GuiElementInterface.kt @@ -10,11 +10,14 @@ interface GuiElementInterface { val title: String val textSize: Float val color: Color + val updateCallback: (GuiElement) -> Unit var id: GuiElementIdentifierType fun render() + fun update() fun addOffset(newOffset: Vec2) fun updateOffset(newOffset: Vec2) fun handleHover(location: Vec2) fun handleClick(location: Vec2) + } diff --git a/src/main/kotlin/display/gui/GuiLabel.kt b/src/main/kotlin/display/gui/GuiLabel.kt index caf9728..ed80115 100644 --- a/src/main/kotlin/display/gui/GuiLabel.kt +++ b/src/main/kotlin/display/gui/GuiLabel.kt @@ -9,8 +9,9 @@ class GuiLabel( offset: Vec2 = Vec2(), title: String, textSize: Float = 0f, - color: Color = Color.WHITE.setAlpha(.7f) -) : GuiElement(drawer, offset, title = title, textSize = textSize, color = color) { + color: Color = Color.WHITE.setAlpha(.7f), + updateCallback: (GuiElement) -> Unit = {} +) : GuiElement(drawer, offset, title = title, textSize = textSize, color = color, updateCallback = updateCallback) { override fun render() { super.render() diff --git a/src/main/kotlin/display/gui/GuiWindow.kt b/src/main/kotlin/display/gui/GuiWindow.kt index 62805c7..d5b56bf 100644 --- a/src/main/kotlin/display/gui/GuiWindow.kt +++ b/src/main/kotlin/display/gui/GuiWindow.kt @@ -14,8 +14,9 @@ class GuiWindow( textSize: Float = .3f, color: Color = Color.BLACK.setAlpha(.5f), private val childElements: MutableList = mutableListOf(), - private val draggable: Boolean = true -) : GuiElement(drawer, offset, scale, title, textSize, color) { + private val draggable: Boolean = true, + updateCallback: (GuiElement) -> Unit = {} +) : GuiElement(drawer, offset, scale, title, textSize, color, updateCallback) { private val childElementOffsets = HashMap() @@ -44,33 +45,29 @@ class GuiWindow( super.render() } + override fun update() = childElements.forEach { it.update() } + override fun addOffset(newOffset: Vec2) { GuiElement.addOffset(this, newOffset) - update() + calculateNewOffsets() } - override fun handleHover(location: Vec2) { - childElements.forEach { it.handleHover(location) } - } + override fun handleHover(location: Vec2) = childElements.forEach { it.handleHover(location) } - override fun handleClick(location: Vec2) { - childElements.forEach { it.handleClick(location) } - } + override fun handleClick(location: Vec2) = childElements.forEach { it.handleClick(location) } - fun update() { - childElements.forEach { it.updateOffset(childElementOffsets[it]!!.add(offset)) } - } + fun calculateNewOffsets() = childElements.forEach { it.updateOffset(childElementOffsets[it]!!.add(offset)) } fun addChildren(elements: List) { childElements.addAll(elements) childElementOffsets.putAll(elements.map { Pair(it, it.offset.clone()) }) - update() + calculateNewOffsets() } fun addChild(element: GuiElement) { childElements.add(element) childElementOffsets[element] = element.offset.clone() - update() + calculateNewOffsets() } } diff --git a/src/main/kotlin/game/GamePhaseHandler.kt b/src/main/kotlin/game/GamePhaseHandler.kt index 94a67e2..9dd47b4 100644 --- a/src/main/kotlin/game/GamePhaseHandler.kt +++ b/src/main/kotlin/game/GamePhaseHandler.kt @@ -8,7 +8,6 @@ import display.events.MouseButtonEvent import display.graphic.Color import display.gui.GuiController import engine.GameState -import engine.freeBody.Warhead import engine.motion.Director import engine.shields.VehicleShield import org.jbox2d.common.Vec2 @@ -323,6 +322,7 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val transformedLocation.x, transformedLocation.y, playerLocation.x, playerLocation.y ) playerOnTurn.playerAim.angle = aimDirection + guiController.update() } currentPhase == GamePhases.PLAYERS_TURN_POWERING -> { checkNotNull(gameState.playerOnTurn) { "No player is on turn." } @@ -334,6 +334,7 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val transformedLocation.x, transformedLocation.y, playerLocation.x, playerLocation.y ) playerOnTurn.playerAim.power = distance * 10f + guiController.update() } } diff --git a/src/main/kotlin/game/PlayerAim.kt b/src/main/kotlin/game/PlayerAim.kt index ad0388b..d133228 100644 --- a/src/main/kotlin/game/PlayerAim.kt +++ b/src/main/kotlin/game/PlayerAim.kt @@ -1,5 +1,9 @@ package game +import utility.Common +import utility.Common.Pi2 +import utility.Common.radianToDegree + class PlayerAim(var angle: Float = 0f, power: Float = 100f) { var power = power @@ -7,4 +11,6 @@ class PlayerAim(var angle: Float = 0f, power: Float = 100f) { field = value.coerceIn(0f, 100f) } + fun getDegreesAngle() = (angle + Pi2) % Pi2 * radianToDegree + } diff --git a/src/main/kotlin/utility/Common.kt b/src/main/kotlin/utility/Common.kt index 6eb7c79..99df37b 100644 --- a/src/main/kotlin/utility/Common.kt +++ b/src/main/kotlin/utility/Common.kt @@ -35,9 +35,11 @@ object Common { fun roundFloat(value: Float, decimals: Int = 2): Float { val multiplier = 10f.pow(decimals) - return (value * multiplier).roundToInt() / multiplier + return value.times(multiplier).roundToInt().div(multiplier) } + const val Pi2 = 2f * PI.toFloat() + val vectorUnit = Vec2(1f, 1f) val radianToDegree = Math.toDegrees(1.0).toFloat() From 6a0763f95d4b5344c9e32ad4239785c3df36dab5 Mon Sep 17 00:00:00 2001 From: Pierre Blaarkies Roux Date: Wed, 1 Apr 2020 21:20:50 +0200 Subject: [PATCH 03/11] Add warheads --- src/main/kotlin/display/draw/Drawer.kt | 6 +- src/main/kotlin/display/draw/TextureConfig.kt | 2 +- src/main/kotlin/engine/GameState.kt | 47 ++++++-- src/main/kotlin/engine/freeBody/FreeBody.kt | 45 ++++---- src/main/kotlin/engine/freeBody/Particle.kt | 21 ++++ src/main/kotlin/engine/freeBody/Warhead.kt | 22 ++++ src/main/kotlin/game/GamePhaseHandler.kt | 103 +++++++++++------- src/main/kotlin/game/MapGenerator.kt | 4 +- src/main/kotlin/utility/Common.kt | 2 +- src/test/kotlin/utility/CommonTest.kt | 45 ++++++++ 10 files changed, 220 insertions(+), 77 deletions(-) create mode 100644 src/main/kotlin/engine/freeBody/Particle.kt create mode 100644 src/test/kotlin/utility/CommonTest.kt diff --git a/src/main/kotlin/display/draw/Drawer.kt b/src/main/kotlin/display/draw/Drawer.kt index 5646975..f57da85 100644 --- a/src/main/kotlin/display/draw/Drawer.kt +++ b/src/main/kotlin/display/draw/Drawer.kt @@ -10,6 +10,8 @@ import engine.physics.GravityCell import game.GamePlayer import org.jbox2d.common.MathUtils.sin import org.jbox2d.common.Vec2 +import utility.Common +import utility.Common.vectorUnit import java.util.* import kotlin.math.cos import kotlin.math.sqrt @@ -55,12 +57,12 @@ class Drawer(val renderer: Renderer, val textures: TextureHolder) { } fun drawFreeBody(freeBody: FreeBody) { - freeBody.textureConfig.texture.bind() + freeBody.textureConfig.texture?.bind() renderer.drawShape( freeBody.textureConfig.gpuBufferData, freeBody.worldBody.position, freeBody.worldBody.angle, - Vec2(freeBody.radius, freeBody.radius) + vectorUnit.mul(freeBody.radius) ) } diff --git a/src/main/kotlin/display/draw/TextureConfig.kt b/src/main/kotlin/display/draw/TextureConfig.kt index d8d3418..0c4c872 100644 --- a/src/main/kotlin/display/draw/TextureConfig.kt +++ b/src/main/kotlin/display/draw/TextureConfig.kt @@ -4,7 +4,7 @@ import Vector2f import display.graphic.Texture class TextureConfig( - val texture: Texture, val scale: Vector2f = Vector2f(1f, 1f), val offset: Vector2f = Vector2f(), + var texture: Texture? = null, val scale: Vector2f = Vector2f(1f, 1f), val offset: Vector2f = Vector2f(), var chunkedVertices: List> = listOf(), var gpuBufferData: FloatArray = floatArrayOf() ) { diff --git a/src/main/kotlin/engine/GameState.kt b/src/main/kotlin/engine/GameState.kt index ba7282e..524b930 100644 --- a/src/main/kotlin/engine/GameState.kt +++ b/src/main/kotlin/engine/GameState.kt @@ -4,16 +4,16 @@ import input.CameraView import display.Window import display.draw.TextureConfig import display.draw.TextureHolder -import display.graphic.BasicShapes +import engine.freeBody.Particle import engine.freeBody.Planet import engine.freeBody.Vehicle import engine.freeBody.Warhead +import engine.motion.Director import engine.motion.Motion import engine.physics.CellLocation import engine.physics.Gravity import engine.physics.GravityCell import game.GamePlayer -import game.GamePlayerTypes import org.jbox2d.common.MathUtils.cos import org.jbox2d.common.MathUtils.sin import org.jbox2d.common.Vec2 @@ -27,17 +27,20 @@ class GameState { lateinit var camera: CameraView var world = World(Vec2()) - var vehicles = mutableListOf() - var planets = mutableListOf() - var warheads = mutableListOf() + val vehicles = mutableListOf() + val planets = mutableListOf() + val warheads = mutableListOf() + val particles = mutableListOf() -// private var worlds = mutableListOf() // private var asteroids = mutableListOf() // private var stars = mutableListOf() - val tickables + val gravityBodies get() = vehicles + planets + warheads + val trailerBodies + get() = vehicles + warheads + var gravityMap = HashMap() var resolution = 0f @@ -46,7 +49,7 @@ class GameState { } private fun tickGravityChanges() { - Gravity.addGravityForces(tickables) + Gravity.addGravityForces(gravityBodies) .let { (gravityMap, resolution) -> this.gravityMap = gravityMap this.resolution = resolution @@ -61,7 +64,31 @@ class GameState { world.step(timeStep, velocityIterations, positionIterations) tickGravityChanges() - Motion.addNewTrailers(tickables.filter { it.radius > .5f }) + Motion.addNewTrailers(trailerBodies) + + particles.toList().forEach { + it.worldBody.position.addLocal(it.worldBody.linearVelocity.mul(timeStep)) + if (it.ageTime > 1000f) { + particles.remove(it) + } + } + + warheads.toList().forEach { warhead -> + val contactList = warhead.worldBody.contactList + if (contactList != null && contactList.contact.isTouching) { + val particle = warhead.createParticles(particles, world, contactList.other) + + world.destroyBody(warhead.worldBody) + warheads.remove(warhead) + + vehicles.map { Pair(it, Director.getDistance(it.worldBody, particle.worldBody)) } + .filter { (_, distance) -> distance < particle.radius } + .forEach { (vehicle, distance) -> + val damage = (100f / particle.radius) * (particle.radius - distance / particle.radius) + vehicle.hitPoints -= damage + } + } + } } fun reset() { @@ -85,7 +112,7 @@ class GameState { val originVelocity = vehicle.worldBody.linearVelocity val warheadRadius = .2f - val minimumSafeDistance = 3f * vehicle.radius + val minimumSafeDistance = 2f * vehicle.radius val warheadLocation = Vec2( origin.x + cos(angle) * minimumSafeDistance, origin.y + sin(angle) * minimumSafeDistance diff --git a/src/main/kotlin/engine/freeBody/FreeBody.kt b/src/main/kotlin/engine/freeBody/FreeBody.kt index bf98569..5bedaee 100644 --- a/src/main/kotlin/engine/freeBody/FreeBody.kt +++ b/src/main/kotlin/engine/freeBody/FreeBody.kt @@ -28,28 +28,33 @@ open class FreeBody( world: World, bodyDef: BodyDef ): Body { - val fixtureDef = FixtureDef() - fixtureDef.shape = shapeBox - fixtureDef.density = mass / (PI.toFloat() * radius.pow(2f)) - fixtureDef.friction = friction - fixtureDef.restitution = restitution - - val worldBody = world.createBody(bodyDef) - worldBody.createFixture(fixtureDef) - - return worldBody + val fixtureDef = FixtureDef().let { + it.shape = shapeBox + it.density = mass / (PI.toFloat() * radius.pow(2f)) + it.friction = friction + it.restitution = restitution + it + } + + return world.createBody(bodyDef).let { + it.createFixture(fixtureDef) + it + } } - fun createBodyDef(bodyType: BodyType, - x: Float, y: Float, h: Float, - dx: Float, dy: Float, dh: Float): BodyDef { - val bodyDef = BodyDef() - bodyDef.type = bodyType - bodyDef.position.set(x, y) - bodyDef.angle = h - bodyDef.linearVelocity = Vec2(dx, dy) - bodyDef.angularVelocity = dh - return bodyDef + fun createBodyDef( + bodyType: BodyType, + x: Float, y: Float, h: Float, + dx: Float, dy: Float, dh: Float + ): BodyDef { + return BodyDef().let { + it.type = bodyType + it.position.set(x, y) + it.angle = h + it.linearVelocity = Vec2(dx, dy) + it.angularVelocity = dh + it + } } } diff --git a/src/main/kotlin/engine/freeBody/Particle.kt b/src/main/kotlin/engine/freeBody/Particle.kt new file mode 100644 index 0000000..4ae0b7d --- /dev/null +++ b/src/main/kotlin/engine/freeBody/Particle.kt @@ -0,0 +1,21 @@ +package engine.freeBody + +import display.draw.TextureConfig +import org.jbox2d.dynamics.Body + +class Particle( + val id: String, + val worldBody: Body, + val radius: Float, + val textureConfig: TextureConfig +) { + + private val currentTime + get() = System.currentTimeMillis() + + val ageTime + get() = (currentTime - createdAt) + + private val createdAt = currentTime + +} diff --git a/src/main/kotlin/engine/freeBody/Warhead.kt b/src/main/kotlin/engine/freeBody/Warhead.kt index f214ab5..71da908 100644 --- a/src/main/kotlin/engine/freeBody/Warhead.kt +++ b/src/main/kotlin/engine/freeBody/Warhead.kt @@ -4,6 +4,7 @@ import display.draw.TextureConfig import display.graphic.BasicShapes import engine.motion.Motion import game.GamePlayer +import org.jbox2d.collision.shapes.CircleShape import org.jbox2d.collision.shapes.PolygonShape import org.jbox2d.collision.shapes.Shape import org.jbox2d.common.Vec2 @@ -21,6 +22,27 @@ class Warhead( textureConfig: TextureConfig ) : FreeBody(id, motion, shapeBox, worldBody, radius, textureConfig) { + fun createParticles( + particles: MutableList, + world: World, + other: Body + ): Particle { + val shapeBox = CircleShape() + shapeBox.radius = 2f + + val location = worldBody.position + val velocity = other.linearVelocity + val bodyDef = createBodyDef(BodyType.STATIC, location.x, location.y, 0f, velocity.x, velocity.y, 0f) + val worldBody = world.createBody(bodyDef) +// createWorldBody(shapeBox, 0f, radius, 0f, 0f, world, bodyDef) + + val textureConfig = TextureConfig(chunkedVertices = BasicShapes.polygon30.chunked(2)) + return Particle(id, worldBody, shapeBox.radius, textureConfig).let { + particles.add(it) + it + } + + } companion object { diff --git a/src/main/kotlin/game/GamePhaseHandler.kt b/src/main/kotlin/game/GamePhaseHandler.kt index 9dd47b4..fa4eb63 100644 --- a/src/main/kotlin/game/GamePhaseHandler.kt +++ b/src/main/kotlin/game/GamePhaseHandler.kt @@ -1,18 +1,19 @@ package game -import display.draw.Drawer -import display.draw.TextureHolder import display.KeyboardEvent import display.Window +import display.draw.Drawer +import display.draw.TextureHolder import display.events.MouseButtonEvent +import display.graphic.BasicShapes import display.graphic.Color import display.gui.GuiController import engine.GameState import engine.motion.Director import engine.shields.VehicleShield import org.jbox2d.common.Vec2 +import utility.Common.getTimingFunctionEaseIn import utility.Common.getTimingFunctionEaseOut -import utility.Common.getTimingFunctionSineEaseIn import utility.Common.vectorUnit import kotlin.math.roundToInt @@ -63,9 +64,10 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val } fun pauseGame(event: KeyboardEvent) { - when (currentPhase) { - GamePhases.PAUSE -> currentPhase = GamePhases.PLAY - GamePhases.PLAY -> currentPhase = GamePhases.PAUSE + currentPhase = when (currentPhase) { + GamePhases.PAUSE -> GamePhases.PLAY + GamePhases.PLAY -> GamePhases.PAUSE + else -> GamePhases.PAUSE } startTransition() } @@ -78,6 +80,15 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val fun update() { camera.update() + gameState.particles.filter { it.textureConfig.texture == null } + .forEach { it.textureConfig.texture = textures.white_pixel } + gameState.particles.forEach { particle -> + val scale = getTimingFunctionEaseOut(particle.ageTime / 1000f) + particle.textureConfig.chunkedVertices = BasicShapes.polygon15.chunked(2) + .map { listOf(it[0] * scale, it[1] * scale) } + particle.textureConfig.updateGpuBufferData() + } + val cp = currentPhase when { cp == GamePhases.PAUSE && isTransitioning -> tickGamePausing() @@ -96,8 +107,8 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val cp == GamePhases.PLAYERS_TURN_FIRED -> handlePlayerShot() cp == GamePhases.PLAYERS_TURN_AIMING -> return cp == GamePhases.PLAYERS_TURN_POWERING -> return - cp == GamePhases.END_ROUND && isTransitioning -> tickGamePausing(endSpeed = .3f) - cp == GamePhases.END_ROUND -> gameState.tickClock(timeStep * .3f, velocityIterations, positionIterations) + cp == GamePhases.END_ROUND && isTransitioning -> tickGamePausing(endSpeed = .1f) + cp == GamePhases.END_ROUND -> gameState.tickClock(timeStep * .1f, velocityIterations, positionIterations) else -> gameState.tickClock(timeStep, velocityIterations, positionIterations) } @@ -105,6 +116,10 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val private fun handlePlayerShot() { when { + gameState.warheads.none() + && gameState.particles.none() + && gameState.vehicles + .all { it.worldBody.contactList != null && it.worldBody.contactList.other.mass > 5f } -> setupNextPlayersTurn() // if (no active warheads && all players in contact with a moon or bigger) setupNextPlayersTurn() elapsedTime > maxTurnDuration -> setupNextPlayersTurn() elapsedTime > (maxTurnDuration - pauseTime) -> tickGamePausing( @@ -141,6 +156,17 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val } private fun setupNextPlayersTurn() { + val vehiclesDestroyed = gameState.gamePlayers + .count { it.vehicle!!.hitPoints <= 0f } >= gameState.gamePlayers.size - 1 + if (vehiclesDestroyed) { + currentPhase = GamePhases.END_ROUND + startTransition() + return + } + + gameState.gamePlayers.map { "Player ${it.name} HP:${it.vehicle!!.hitPoints.toInt()}" } + .joinToString().let { println(it) } + setNextPlayerOnTurn() setupPlayerCommandPanel() @@ -168,19 +194,6 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val } private fun playerFires(player: GamePlayer) { - println("Player ${player.name} fired a gun!") - gameState.gamePlayers.filter { it != gameState.playerOnTurn }.forEach { it.vehicle!!.hitPoints -= 10f } - gameState.gamePlayers.forEach { println("Player ${it.name} has <${it.vehicle?.hitPoints}> hitPoints left") } - - val vehiclesDestroyed = - gameState.gamePlayers.count { it.vehicle!!.hitPoints <= 0f } >= gameState.gamePlayers.size - 1 - if (vehiclesDestroyed) { - currentPhase = GamePhases.END_ROUND - startTransition() - return - } - /////////// - // check() {} player has enough funds && in stable position to fire large warheads val firedWarhead = gameState.fireWarhead(textures, player, "boom small") @@ -245,9 +258,21 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val private fun drawPlayPhase() { drawer.drawPicture(textures.stars_2k) - val allFreeBodies = gameState.tickables + val allFreeBodies = gameState.gravityBodies allFreeBodies.forEach { drawer.drawTrail(it) } + + gameState.particles.forEach { + it.textureConfig.texture?.bind() + drawer.renderer.drawShape( + it.textureConfig.gpuBufferData, + it.worldBody.position, + it.worldBody.angle, + vectorUnit.mul(it.radius) + ) + } + allFreeBodies.forEach { drawer.drawFreeBody(it) } + // allFreeBodies.forEach { drawDebugForces(it) } // drawer.drawGravityCells(gameState.gravityMap, gameState.resolution) } @@ -278,20 +303,16 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val currentPhase = endPhase ?: currentPhase isTransitioning = false } else { - val timeFunctionStep = getTimingFunctionSineEaseIn((1f - endSpeed) - interpolateStep) + val timeFunctionStep = getTimingFunctionEaseIn(1f - interpolateStep) * (1f - endSpeed) + endSpeed gameState.tickClock(timeStep * timeFunctionStep, velocityIterations, positionIterations) - -// println( -// "${interpolateStep.toString().padEnd(4, '0').substring(1, 4)} <> ${timeFunctionStep.toString() -// .padEnd(4, '0').substring(1, 4)}" -// ) +// println("${roundFloat(interpolateStep, 2).toString().padEnd(4, '0')} <> " + roundFloat(timeFunctionStep, 2).toString().padEnd(4, '0')) } } fun doubleLeftClick(location: Vec2, click: MouseButtonEvent) { val transformedLocation = getScreenLocation(location).mul(camera.z).add(camera.location) - val clickedBody = gameState.tickables.find { + val clickedBody = gameState.gravityBodies.find { it.worldBody.position .add(transformedLocation.mul(-1f)) .length() <= it.radius @@ -313,11 +334,7 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val when { mouseElementPhases.any { currentPhase == it } -> guiController.checkHover(getScreenLocation(location)) currentPhase == GamePhases.PLAYERS_TURN_AIMING -> { - checkNotNull(gameState.playerOnTurn) { "No player is on turn." } - val playerOnTurn = gameState.playerOnTurn!! - - val transformedLocation = getScreenLocation(location).mul(camera.z).add(camera.location) - val playerLocation = playerOnTurn.vehicle!!.worldBody.position + val (playerOnTurn, transformedLocation, playerLocation) = getPlayerAndMouseLocations(location) val aimDirection = Director.getDirection( transformedLocation.x, transformedLocation.y, playerLocation.x, playerLocation.y ) @@ -325,21 +342,25 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val guiController.update() } currentPhase == GamePhases.PLAYERS_TURN_POWERING -> { - checkNotNull(gameState.playerOnTurn) { "No player is on turn." } - val playerOnTurn = gameState.playerOnTurn!! - - val transformedLocation = getScreenLocation(location).mul(camera.z).add(camera.location) - val playerLocation = playerOnTurn.vehicle!!.worldBody.position + val (playerOnTurn, transformedLocation, playerLocation) = getPlayerAndMouseLocations(location) val distance = Director.getDistance( transformedLocation.x, transformedLocation.y, playerLocation.x, playerLocation.y ) - playerOnTurn.playerAim.power = distance * 10f + playerOnTurn.playerAim.power = (distance - 1f) * 10f guiController.update() } } } + private fun getPlayerAndMouseLocations(location: Vec2): Triple { + checkNotNull(gameState.playerOnTurn) { "No player is on turn." } + val playerOnTurn = gameState.playerOnTurn!! + val transformedLocation = getScreenLocation(location).mul(camera.z).add(camera.location) + val playerLocation = playerOnTurn.vehicle!!.worldBody.position + return Triple(playerOnTurn, transformedLocation, playerLocation) + } + fun leftClickMouse(event: MouseButtonEvent) { when { mouseElementPhases.any { currentPhase == it } -> guiController.checkLeftClick(getScreenLocation(event.location)) @@ -412,7 +433,7 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val private const val pauseTime = 1000f private const val introDuration = 3500f private const val introStartSlowdown = 2000f - private const val maxTurnDuration = 5000f + private const val maxTurnDuration = 30000f private const val quickStartTime = 300f private val mouseElementPhases = listOf( diff --git a/src/main/kotlin/game/MapGenerator.kt b/src/main/kotlin/game/MapGenerator.kt index 274316a..5e93eb0 100644 --- a/src/main/kotlin/game/MapGenerator.kt +++ b/src/main/kotlin/game/MapGenerator.kt @@ -37,7 +37,7 @@ object MapGenerator { gameState.planets.addAll(listOf(terra, luna)) gameState.planets.addAll(createPlanets(gameState.world, 5, textures)) - gameState.tickables.forEach { it.textureConfig.updateGpuBufferData() } + gameState.gravityBodies.forEach { it.textureConfig.updateGpuBufferData() } } fun populateTestMap(gameState: GameState, textures: TextureHolder) { @@ -72,7 +72,7 @@ object MapGenerator { gameState.planets.addAll(listOf(terra, luna)) gameState.planets.addAll(createPlanets(gameState.world, 20, textures)) - gameState.tickables.forEach { it.textureConfig.updateGpuBufferData() } + gameState.gravityBodies.forEach { it.textureConfig.updateGpuBufferData() } } private fun createPlanets(world: World, count: Int, textures: TextureHolder): List { diff --git a/src/main/kotlin/utility/Common.kt b/src/main/kotlin/utility/Common.kt index 99df37b..18b385b 100644 --- a/src/main/kotlin/utility/Common.kt +++ b/src/main/kotlin/utility/Common.kt @@ -46,7 +46,7 @@ object Common { fun getTimingFunctionEaseOut(interpolateStep: Float) = getTimingFunctionFullSine(sqrt(interpolateStep)) - fun getTimingFunctionSineEaseIn(interpolateStep: Float) = 1f - getTimingFunctionEaseOut(1f - interpolateStep) + fun getTimingFunctionEaseIn(interpolateStep: Float) = 1f - getTimingFunctionEaseOut(1f - interpolateStep) fun getTimingFunctionFullSine(interpolateStep: Float) = (sin(interpolateStep * PI - PI * .5) * .5 + .5).toFloat() diff --git a/src/test/kotlin/utility/CommonTest.kt b/src/test/kotlin/utility/CommonTest.kt new file mode 100644 index 0000000..a19db5a --- /dev/null +++ b/src/test/kotlin/utility/CommonTest.kt @@ -0,0 +1,45 @@ +package utility + +import org.jbox2d.common.Vec2 +import org.junit.jupiter.api.Test + +import utility.Common.getTimingFunctionEaseIn +import utility.Common.getTimingFunctionEaseOut +import utility.Common.getTimingFunctionFullSine +import utility.Common.getTimingFunctionSigmoid +import utility.Common.joinLists +import utility.Common.roundFloat + +internal class CommonTest { + + fun testest() { + + val data = getChartedData { + getTimingFunctionEaseIn(1f - it) + } + println(data) + } + + private fun getChartedData(timingFunction: (Float) -> Float): String { + val resolution = 50 + val values = (0 until resolution).map { it / resolution.minus(1).toFloat() } + .map { timingFunction(it) } + .map { roundFloat(it, 2) } + + val visualGraph = HashMap, Float>() + values.withIndex().forEach { (index, y) -> + visualGraph[Pair(index, (y * (resolution)).toInt())] = y + } + + val intRange = (0 until resolution).toList() + val result = joinLists(intRange, intRange) + .sortedBy { (x, y) -> -y } + .sortedBy { (x, y) -> x } + .map { (x, y) -> visualGraph[Pair(y, x)] } + .map { if (it != null) "x" else "." } + .chunked(resolution) + .map { it.joinToString("") } + .joinToString("\n") + return result + } +} From 69c8452b208b0a7b56d838396e40e80252835eab Mon Sep 17 00:00:00 2001 From: Pierre Blaarkies Roux Date: Sat, 4 Apr 2020 20:32:45 +0200 Subject: [PATCH 04/11] Add texture enums --- src/main/kotlin/app/AppLogic.kt | 7 +- src/main/kotlin/display/draw/Drawer.kt | 35 ++- src/main/kotlin/display/draw/TextureConfig.kt | 6 +- src/main/kotlin/display/draw/TextureEnum.kt | 10 + src/main/kotlin/display/draw/TextureHolder.kt | 25 +-- .../kotlin/display/graphic/BasicShapes.kt | 15 +- src/main/kotlin/display/gui/GuiButton.kt | 3 +- src/main/kotlin/display/gui/GuiController.kt | 42 +++- src/main/kotlin/display/gui/GuiLabel.kt | 10 +- src/main/kotlin/display/gui/GuiWindow.kt | 3 +- src/main/kotlin/engine/GameState.kt | 58 +++-- src/main/kotlin/engine/freeBody/Particle.kt | 4 +- src/main/kotlin/engine/freeBody/Vehicle.kt | 38 +++- src/main/kotlin/engine/freeBody/Warhead.kt | 23 +- src/main/kotlin/game/GamePhaseHandler.kt | 204 +++++++++--------- src/main/kotlin/game/GamePhases.kt | 1 + src/main/kotlin/game/MapGenerator.kt | 31 +-- 17 files changed, 333 insertions(+), 182 deletions(-) create mode 100644 src/main/kotlin/display/draw/TextureEnum.kt diff --git a/src/main/kotlin/app/AppLogic.kt b/src/main/kotlin/app/AppLogic.kt index 04a66e8..9a7db9c 100644 --- a/src/main/kotlin/app/AppLogic.kt +++ b/src/main/kotlin/app/AppLogic.kt @@ -13,16 +13,15 @@ class AppLogic : IGameLogic { private val renderer = Renderer() private val gameState = GameState() - private val textures = TextureHolder() - private val drawer = Drawer(renderer, textures) - private val gamePhaseHandler = GamePhaseHandler(gameState, drawer, textures) + private val drawer = Drawer(renderer) + private val gamePhaseHandler = GamePhaseHandler(gameState, drawer) private val inputHandler = InputHandler(gamePhaseHandler) @Throws(Exception::class) override fun init(window: Window) { gameState.init(window) renderer.init(gameState.camera) - textures.init() + drawer.init() gamePhaseHandler.init(window) inputHandler.init(window) } diff --git a/src/main/kotlin/display/draw/Drawer.kt b/src/main/kotlin/display/draw/Drawer.kt index f57da85..3dc7091 100644 --- a/src/main/kotlin/display/draw/Drawer.kt +++ b/src/main/kotlin/display/draw/Drawer.kt @@ -3,20 +3,25 @@ package display.draw import display.graphic.BasicShapes import display.graphic.Color import display.graphic.Renderer -import display.graphic.Texture import engine.freeBody.FreeBody +import engine.freeBody.Particle import engine.physics.CellLocation import engine.physics.GravityCell import game.GamePlayer import org.jbox2d.common.MathUtils.sin import org.jbox2d.common.Vec2 -import utility.Common import utility.Common.vectorUnit import java.util.* import kotlin.math.cos import kotlin.math.sqrt -class Drawer(val renderer: Renderer, val textures: TextureHolder) { +class Drawer(val renderer: Renderer) { + + val textures = TextureHolder() + + fun init() { + textures.init() + } fun drawDebugForces(freeBody: FreeBody) { val x = freeBody.worldBody.position.x @@ -38,7 +43,7 @@ class Drawer(val renderer: Renderer, val textures: TextureHolder) { Color(0f, 1f, 1f, 1f), Color(0f, 1f, 1f, 0.0f) ).toFloatArray() - textures.white_pixel.bind() + textures.getTexture(TextureEnum.white_pixel).bind() // renderer.drawStrip(data) renderer.drawText(freeBody.id, freeBody.worldBody.position, Vec2(1f, 1f), Color.WHITE) @@ -52,12 +57,12 @@ class Drawer(val renderer: Renderer, val textures: TextureHolder) { } val data = getLine(linePoints, Color(0.4f, 0.7f, 1f, 0.5f), Color.TRANSPARENT, .1f, 0f) - textures.white_pixel.bind() + textures.getTexture(TextureEnum.white_pixel).bind() renderer.drawStrip(data) } fun drawFreeBody(freeBody: FreeBody) { - freeBody.textureConfig.texture?.bind() + textures.getTexture(freeBody.textureConfig.texture).bind() renderer.drawShape( freeBody.textureConfig.gpuBufferData, freeBody.worldBody.position, @@ -67,7 +72,7 @@ class Drawer(val renderer: Renderer, val textures: TextureHolder) { } fun drawGravityCells(gravityMap: HashMap, resolution: Float) { - textures.white_pixel.bind() + textures.getTexture(TextureEnum.white_pixel).bind() val maxMass = gravityMap.maxBy { (_, cell) -> cell.totalMass }!!.value.totalMass val scale = 0.707106781f * resolution gravityMap.forEach { (key, cell) -> @@ -83,8 +88,8 @@ class Drawer(val renderer: Renderer, val textures: TextureHolder) { } } - fun drawPicture(texture: Texture, scale: Vec2 = Vec2(1f, 1f), offset: Vec2 = Vec2()) { - texture.bind() + fun drawPicture(textureEnum: TextureEnum, scale: Vec2 = Vec2(1f, 1f), offset: Vec2 = Vec2()) { + val texture = textures.getTexture(textureEnum).bind() val left = -texture.width / 2f val right = texture.width / 2f @@ -121,10 +126,20 @@ class Drawer(val renderer: Renderer, val textures: TextureHolder) { triangleStripPoints + arrowHeadPoints, Color.RED.setAlpha(.5f), Color.RED.setAlpha(.1f) ).toFloatArray() - textures.white_pixel.bind() + textures.getTexture(TextureEnum.white_pixel).bind() renderer.drawStrip(data) } + fun drawParticle(particle: Particle) { + textures.getTexture(particle.textureConfig.texture).bind() + renderer.drawShape( + particle.textureConfig.gpuBufferData, + particle.worldBody.position, + particle.worldBody.angle, + vectorUnit.mul(particle.radius) + ) + } + companion object { fun getColoredData( diff --git a/src/main/kotlin/display/draw/TextureConfig.kt b/src/main/kotlin/display/draw/TextureConfig.kt index 0c4c872..f1f5d4f 100644 --- a/src/main/kotlin/display/draw/TextureConfig.kt +++ b/src/main/kotlin/display/draw/TextureConfig.kt @@ -1,14 +1,13 @@ package display.draw import Vector2f -import display.graphic.Texture class TextureConfig( - var texture: Texture? = null, val scale: Vector2f = Vector2f(1f, 1f), val offset: Vector2f = Vector2f(), + var texture: TextureEnum, val scale: Vector2f = Vector2f(1f, 1f), val offset: Vector2f = Vector2f(), var chunkedVertices: List> = listOf(), var gpuBufferData: FloatArray = floatArrayOf() ) { - fun updateGpuBufferData() { + fun updateGpuBufferData(): TextureConfig { gpuBufferData = chunkedVertices.flatMap { val (x, y) = it listOf( @@ -18,6 +17,7 @@ class TextureConfig( (y * .5f - 0.5f) * scale.y + offset.y ) }.toFloatArray() + return this } } diff --git a/src/main/kotlin/display/draw/TextureEnum.kt b/src/main/kotlin/display/draw/TextureEnum.kt new file mode 100644 index 0000000..ea7166f --- /dev/null +++ b/src/main/kotlin/display/draw/TextureEnum.kt @@ -0,0 +1,10 @@ +package display.draw + +enum class TextureEnum { + marble_earth, + full_moon, + metal, + pavement, + white_pixel, + stars_2k +} diff --git a/src/main/kotlin/display/draw/TextureHolder.kt b/src/main/kotlin/display/draw/TextureHolder.kt index 4b98944..1f9ef4f 100644 --- a/src/main/kotlin/display/draw/TextureHolder.kt +++ b/src/main/kotlin/display/draw/TextureHolder.kt @@ -4,20 +4,21 @@ import display.graphic.Texture class TextureHolder { - lateinit var marble_earth: Texture - lateinit var full_moon: Texture - lateinit var metal: Texture - lateinit var pavement: Texture - lateinit var white_pixel: Texture - lateinit var stars_2k: Texture + private val textureHashMap = HashMap() fun init() { - marble_earth = Texture.loadTexture("src\\main\\resources\\textures\\marble_earth.png") - full_moon = Texture.loadTexture("src\\main\\resources\\textures\\full_moon.png") - metal = Texture.loadTexture("src\\main\\resources\\textures\\metal.png") - pavement = Texture.loadTexture("src\\main\\resources\\textures\\pavement.png") - white_pixel = Texture.loadTexture("src\\main\\resources\\textures\\white_pixel.png") - stars_2k = Texture.loadTexture("src\\main\\resources\\textures\\stars_2k.png") + val hMap = textureHashMap + hMap[TextureEnum.marble_earth] = Texture.loadTexture("src\\main\\resources\\textures\\marble_earth.png") + hMap[TextureEnum.full_moon] = Texture.loadTexture("src\\main\\resources\\textures\\full_moon.png") + hMap[TextureEnum.metal] = Texture.loadTexture("src\\main\\resources\\textures\\metal.png") + hMap[TextureEnum.pavement] = Texture.loadTexture("src\\main\\resources\\textures\\pavement.png") + hMap[TextureEnum.white_pixel] = Texture.loadTexture("src\\main\\resources\\textures\\white_pixel.png") + hMap[TextureEnum.stars_2k] = Texture.loadTexture("src\\main\\resources\\textures\\stars_2k.png") + } + + fun getTexture(texture: TextureEnum): Texture = textureHashMap[texture].let { + checkNotNull(it) { "Cannot find texture $texture" } + it } } diff --git a/src/main/kotlin/display/graphic/BasicShapes.kt b/src/main/kotlin/display/graphic/BasicShapes.kt index aafcbae..14a375f 100644 --- a/src/main/kotlin/display/graphic/BasicShapes.kt +++ b/src/main/kotlin/display/graphic/BasicShapes.kt @@ -23,8 +23,19 @@ object BasicShapes { val square = polygon4.map { it * sqrt(2f) } - private fun getPolygonVertices(corners: Int): List = (0 until corners).flatMap { - val t = 2 * PI * (it / corners.toFloat()) + PI * .25 + val squareStar = makeSquareStar() + + private fun makeSquareStar(): List { + return polygon4.chunked(2) + .zip(getPolygonVertices(4, .5).chunked(2) + .map { listOf(it[0] * .6f, it[1] * .6f) } + ) + .flatMap { listOf(it.first, it.second.toList()) } + .flatten() + } + + private fun getPolygonVertices(corners: Int, rotate: Double = .25): List = (0 until corners).flatMap { + val t = 2 * PI * (it / corners.toFloat()) + PI * rotate listOf(cos(t).toFloat(), sin(t).toFloat()) } diff --git a/src/main/kotlin/display/gui/GuiButton.kt b/src/main/kotlin/display/gui/GuiButton.kt index 7d0ae20..98a2579 100644 --- a/src/main/kotlin/display/gui/GuiButton.kt +++ b/src/main/kotlin/display/gui/GuiButton.kt @@ -1,6 +1,7 @@ package display.gui import display.draw.Drawer +import display.draw.TextureEnum import display.graphic.BasicShapes import display.graphic.Color import org.jbox2d.common.Vec2 @@ -31,7 +32,7 @@ class GuiButton( } override fun render() { - drawer.textures.white_pixel.bind() + drawer.textures.getTexture(TextureEnum.white_pixel).bind() when (currentPhase) { GuiElementPhases.HOVERED -> drawer.renderer.drawShape(buttonBackground, offset, useCamera = false) diff --git a/src/main/kotlin/display/gui/GuiController.kt b/src/main/kotlin/display/gui/GuiController.kt index d7ef695..b669d0b 100644 --- a/src/main/kotlin/display/gui/GuiController.kt +++ b/src/main/kotlin/display/gui/GuiController.kt @@ -2,10 +2,8 @@ package display.gui import display.draw.Drawer import display.graphic.Color -import engine.GameState import game.GamePlayer import org.jbox2d.common.Vec2 -import utility.Common import utility.Common.roundFloat class GuiController(private val drawer: Drawer) { @@ -139,17 +137,44 @@ class GuiController(private val drawer: Drawer) { GuiButton(drawer, Vec2(-100f, -100f), Vec2(50f, 25f), title = "Fire", onClick = { onClickFire(player) }), - GuiLabel(drawer, - Vec2(0f, 90f), - player.playerAim.getDegreesAngle().let { displayNumber(it, 2) }, .15f, updateCallback = - { it.title = player.playerAim.getDegreesAngle().let { displayNumber(it, 2) } }), - GuiLabel(drawer, Vec2(0f, 70f), player.playerAim.power.let { displayNumber(it, 2) }, .15f, - updateCallback = { it.title = player.playerAim.power.let { displayNumber(it, 2) } }) + GuiLabel(drawer, Vec2(0f, 90f), getPlayerAimAngleDisplay(player), .15f, + updateCallback = { it.title = getPlayerAimAngleDisplay(player) }), + GuiLabel(drawer, Vec2(0f, 70f), getPlayerAimPowerDisplay(player), .15f, + updateCallback = { it.title = getPlayerAimPowerDisplay(player) }) ) ) elements.add(commandPanelWindow) } + private fun getPlayerAimPowerDisplay(player: GamePlayer): String = + player.playerAim.power.let { displayNumber(it, 2) + "%" } + + private fun getPlayerAimAngleDisplay(player: GamePlayer): String = + player.playerAim.getDegreesAngle().let { displayNumber(it, 2) + "º" } + + + fun createRoundLeaderboard(players: MutableList, onClickNextRound: () -> Unit) { + clear() + val leaderBoardWindow = GuiWindow(drawer, Vec2(), Vec2(200f, 300f), "Leaderboard", draggable = true) + val playerLines = players.map { + GuiLabel( + drawer, + title = "${it.name} ${Math.random().times(100f).toInt()}".padStart(15, ' '), + textSize = .2f + ) + } + setElementsInRows(playerLines, 10f) + + leaderBoardWindow.addChildren(playerLines) + leaderBoardWindow.addChild( + GuiButton( + drawer, Vec2(0f, -280f), Vec2(100f, 25f), "Next Round", + onClick = onClickNextRound + ) + ) + elements.add(leaderBoardWindow) + } + private fun displayNumber(value: Float, decimals: Int): String = roundFloat(value, decimals).toString() private fun setElementsInColumns(elements: List, gap: Float = 0f, centered: Boolean = true) { @@ -180,4 +205,5 @@ class GuiController(private val drawer: Drawer) { } } + } diff --git a/src/main/kotlin/display/gui/GuiLabel.kt b/src/main/kotlin/display/gui/GuiLabel.kt index ed80115..0c33450 100644 --- a/src/main/kotlin/display/gui/GuiLabel.kt +++ b/src/main/kotlin/display/gui/GuiLabel.kt @@ -11,7 +11,15 @@ class GuiLabel( textSize: Float = 0f, color: Color = Color.WHITE.setAlpha(.7f), updateCallback: (GuiElement) -> Unit = {} -) : GuiElement(drawer, offset, title = title, textSize = textSize, color = color, updateCallback = updateCallback) { +) : GuiElement( + drawer, + offset, + Vec2(title.length * 16f, textSize * 100f), + title = title, + textSize = textSize, + color = color, + updateCallback = updateCallback +) { override fun render() { super.render() diff --git a/src/main/kotlin/display/gui/GuiWindow.kt b/src/main/kotlin/display/gui/GuiWindow.kt index d5b56bf..9dfbc9a 100644 --- a/src/main/kotlin/display/gui/GuiWindow.kt +++ b/src/main/kotlin/display/gui/GuiWindow.kt @@ -1,6 +1,7 @@ package display.gui import display.draw.Drawer +import display.draw.TextureEnum import display.graphic.BasicShapes import display.graphic.Color import org.jbox2d.common.Vec2 @@ -25,7 +26,7 @@ class GuiWindow( } override fun render() { - drawer.textures.white_pixel.bind() + drawer.textures.getTexture(TextureEnum.white_pixel).bind() drawer.renderer.drawShape(BasicShapes.square .let { Drawer.getColoredData(it, color) } .toFloatArray(), diff --git a/src/main/kotlin/engine/GameState.kt b/src/main/kotlin/engine/GameState.kt index 524b930..fdfb274 100644 --- a/src/main/kotlin/engine/GameState.kt +++ b/src/main/kotlin/engine/GameState.kt @@ -3,7 +3,7 @@ package engine import input.CameraView import display.Window import display.draw.TextureConfig -import display.draw.TextureHolder +import display.draw.TextureEnum import engine.freeBody.Particle import engine.freeBody.Planet import engine.freeBody.Vehicle @@ -18,6 +18,7 @@ import org.jbox2d.common.MathUtils.cos import org.jbox2d.common.MathUtils.sin import org.jbox2d.common.Vec2 import org.jbox2d.dynamics.World +import utility.Common class GameState { @@ -32,8 +33,8 @@ class GameState { val warheads = mutableListOf() val particles = mutableListOf() -// private var asteroids = mutableListOf() -// private var stars = mutableListOf() + // private var asteroids = mutableListOf() + // private var stars = mutableListOf() val gravityBodies get() = vehicles + planets + warheads @@ -66,27 +67,52 @@ class GameState { tickGravityChanges() Motion.addNewTrailers(trailerBodies) + tickWarheads() + tickParticles(timeStep) + } + + private fun tickParticles(timeStep: Float) { particles.toList().forEach { it.worldBody.position.addLocal(it.worldBody.linearVelocity.mul(timeStep)) if (it.ageTime > 1000f) { particles.remove(it) + return@forEach } + + val scale = Common.getTimingFunctionEaseOut(it.ageTime / 1000f) + it.radius = it.fullRadius * scale } + } + private fun tickWarheads() { warheads.toList().forEach { warhead -> val contactList = warhead.worldBody.contactList - if (contactList != null && contactList.contact.isTouching) { - val particle = warhead.createParticles(particles, world, contactList.other) - - world.destroyBody(warhead.worldBody) - warheads.remove(warhead) - - vehicles.map { Pair(it, Director.getDistance(it.worldBody, particle.worldBody)) } + val madeContact = contactList?.contact != null + && contactList.other != null + && contactList.contact.isTouching + if (madeContact || (warhead.ageTime > warhead.selfDestructTime) + ) { + val particle = warhead.createParticles(particles, world, contactList?.other ?: warhead.worldBody) + + vehicles.toList() + .map { + Pair(it, (Director.getDistance(it.worldBody, particle.worldBody) + - it.radius - warhead.radius).coerceAtLeast(0f)) + } .filter { (_, distance) -> distance < particle.radius } .forEach { (vehicle, distance) -> - val damage = (100f / particle.radius) * (particle.radius - distance / particle.radius) - vehicle.hitPoints -= damage + val damageUnit = (1f - distance / particle.radius).coerceAtLeast(0f) + .let { Common.getTimingFunctionEaseIn(it) } + vehicle.hitPoints -= damageUnit * warhead.damage + + if (vehicle.hitPoints <= 0) { + world.destroyBody(vehicle.worldBody) + vehicles.remove(vehicle) + } } + + world.destroyBody(warhead.worldBody) + warheads.remove(warhead) } } } @@ -99,11 +125,7 @@ class GameState { planets.clear() } - fun fireWarhead( - textures: TextureHolder, - player: GamePlayer, - warheadType: String = "will make this some class later" - ): Warhead { + fun fireWarhead(player: GamePlayer, warheadType: String = "will make this some class later"): Warhead { checkNotNull(player.vehicle) { "Player does not have a vehicle." } val vehicle = player.vehicle!! val angle = player.playerAim.angle @@ -122,7 +144,7 @@ class GameState { return Warhead.create( world, player, warheadLocation.x, warheadLocation.y, angle, warheadVelocity.x, warheadVelocity.y, 0f, - .1f, warheadRadius, textureConfig = TextureConfig(textures.metal) + .1f, warheadRadius, textureConfig = TextureConfig(TextureEnum.metal) ) .let { warheads.add(it) diff --git a/src/main/kotlin/engine/freeBody/Particle.kt b/src/main/kotlin/engine/freeBody/Particle.kt index 4ae0b7d..6986af9 100644 --- a/src/main/kotlin/engine/freeBody/Particle.kt +++ b/src/main/kotlin/engine/freeBody/Particle.kt @@ -6,10 +6,12 @@ import org.jbox2d.dynamics.Body class Particle( val id: String, val worldBody: Body, - val radius: Float, + var radius: Float, val textureConfig: TextureConfig ) { + var fullRadius: Float = radius + private val currentTime get() = System.currentTimeMillis() diff --git a/src/main/kotlin/engine/freeBody/Vehicle.kt b/src/main/kotlin/engine/freeBody/Vehicle.kt index 713aabe..bd4d61e 100644 --- a/src/main/kotlin/engine/freeBody/Vehicle.kt +++ b/src/main/kotlin/engine/freeBody/Vehicle.kt @@ -9,6 +9,8 @@ import org.jbox2d.collision.shapes.PolygonShape import org.jbox2d.collision.shapes.Shape import org.jbox2d.common.Vec2 import org.jbox2d.dynamics.* +import kotlin.math.PI +import kotlin.math.pow class Vehicle( id: String, @@ -40,13 +42,47 @@ class Vehicle( textureConfig: TextureConfig ): Vehicle { val shapeBox = PolygonShape() - val vertices = BasicShapes.polygon4.chunked(2) + val vertices = BasicShapes.squareStar.chunked(2) .map { Vec2(it[0] * radius, it[1] * radius) } .toTypedArray() shapeBox.set(vertices, vertices.size) val bodyDef = createBodyDef(BodyType.DYNAMIC, x, y, h, dx, dy, dh) val worldBody = createWorldBody(shapeBox, mass, radius, friction, restitution, world, bodyDef) + + + + val shapeBox2 = PolygonShape() + val vertices2 = listOf( + Vec2(0f, 1f), + Vec2(-.5f, 0f), + Vec2(0f, -1f), + Vec2(.5f, 0f) + ) + .toTypedArray() + shapeBox2.set(vertices2, vertices2.size) + val fixtureDef = FixtureDef().let { + it.shape = shapeBox2 + it + } + worldBody.createFixture(fixtureDef) + + val shapeBox3 = PolygonShape() + val vertices3 = listOf( + Vec2(1f, 0f), + Vec2(0f, -.5f), + Vec2(-1f, 0f), + Vec2(0f, .5f) + ) + .toTypedArray() + shapeBox3.set(vertices3, vertices3.size) + val fixtureDef2 = FixtureDef().let { + it.shape = shapeBox3 + it + } + worldBody.createFixture(fixtureDef2) + + textureConfig.chunkedVertices = shapeBox.vertices.map { listOf(it.x / radius, it.y / radius) } diff --git a/src/main/kotlin/engine/freeBody/Warhead.kt b/src/main/kotlin/engine/freeBody/Warhead.kt index 71da908..bac7c2f 100644 --- a/src/main/kotlin/engine/freeBody/Warhead.kt +++ b/src/main/kotlin/engine/freeBody/Warhead.kt @@ -1,6 +1,7 @@ package engine.freeBody import display.draw.TextureConfig +import display.draw.TextureEnum import display.graphic.BasicShapes import engine.motion.Motion import game.GamePlayer @@ -22,21 +23,33 @@ class Warhead( textureConfig: TextureConfig ) : FreeBody(id, motion, shapeBox, worldBody, radius, textureConfig) { + private val currentTime + get() = System.currentTimeMillis() + + val ageTime + get() = (currentTime - createdAt) + + private val createdAt = currentTime + val selfDestructTime = 45000f + + val damage = 100f + fun createParticles( particles: MutableList, world: World, - other: Body + impacted: Body ): Particle { val shapeBox = CircleShape() shapeBox.radius = 2f val location = worldBody.position - val velocity = other.linearVelocity + val velocity = impacted.linearVelocity val bodyDef = createBodyDef(BodyType.STATIC, location.x, location.y, 0f, velocity.x, velocity.y, 0f) val worldBody = world.createBody(bodyDef) // createWorldBody(shapeBox, 0f, radius, 0f, 0f, world, bodyDef) - val textureConfig = TextureConfig(chunkedVertices = BasicShapes.polygon30.chunked(2)) + val textureConfig = TextureConfig(TextureEnum.white_pixel, chunkedVertices = BasicShapes.polygon30.chunked(2)) + .updateGpuBufferData() return Particle(id, worldBody, shapeBox.radius, textureConfig).let { particles.add(it) it @@ -80,8 +93,8 @@ class Warhead( listOf( x, y, 0f, 1f, .3f, .3f, 1f, - (x * .5f - 0.5f) * .100f, - (y * .5f - 0.5f) * .100f + (x * .5f - 0.5f) * .1f, + (y * .5f - 0.5f) * .1f ) }.toFloatArray() diff --git a/src/main/kotlin/game/GamePhaseHandler.kt b/src/main/kotlin/game/GamePhaseHandler.kt index fa4eb63..d41a45f 100644 --- a/src/main/kotlin/game/GamePhaseHandler.kt +++ b/src/main/kotlin/game/GamePhaseHandler.kt @@ -3,21 +3,23 @@ package game import display.KeyboardEvent import display.Window import display.draw.Drawer -import display.draw.TextureHolder +import display.draw.TextureEnum import display.events.MouseButtonEvent -import display.graphic.BasicShapes import display.graphic.Color import display.gui.GuiController import engine.GameState import engine.motion.Director import engine.shields.VehicleShield +import kotlinx.coroutines.yield import org.jbox2d.common.Vec2 +import org.jbox2d.dynamics.Body +import org.jbox2d.dynamics.contacts.ContactEdge import utility.Common.getTimingFunctionEaseIn import utility.Common.getTimingFunctionEaseOut import utility.Common.vectorUnit import kotlin.math.roundToInt -class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val textures: TextureHolder) { +class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer) { private val timeStep = 1f / 60f private val velocityIterations = 8 @@ -45,14 +47,14 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val // setupMainMenu() - currentPhase = GamePhases.PLAYERS_PICK_SHIELDS - isTransitioning = false - gameState.gamePlayers.addAll((1..3).map { GamePlayer(it.toString()) }) - MapGenerator.populateNewGameMap(gameState, textures) - gameState.gamePlayers.forEach { it.vehicle?.shield = VehicleShield() } - gameState.playerOnTurn = gameState.gamePlayers[0] + currentPhase = GamePhases.PLAYERS_PICK_SHIELDS + isTransitioning = false + gameState.gamePlayers.addAll((1..3).map { GamePlayer(it.toString()) }) + MapGenerator.populateNewGameMap(gameState) + gameState.gamePlayers.forEach { it.vehicle?.shield = VehicleShield() } + gameState.playerOnTurn = gameState.gamePlayers[0] - setupNextPlayersTurn() + setupNextPlayersTurn() } fun dragMouseRightClick(movement: Vec2) { @@ -77,18 +79,14 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val isTransitioning = true } + private fun startNewPhase(newPhase: GamePhases) { + currentPhase = newPhase + startTransition() + } + fun update() { camera.update() - gameState.particles.filter { it.textureConfig.texture == null } - .forEach { it.textureConfig.texture = textures.white_pixel } - gameState.particles.forEach { particle -> - val scale = getTimingFunctionEaseOut(particle.ageTime / 1000f) - particle.textureConfig.chunkedVertices = BasicShapes.polygon15.chunked(2) - .map { listOf(it[0] * scale, it[1] * scale) } - particle.textureConfig.updateGpuBufferData() - } - val cp = currentPhase when { cp == GamePhases.PAUSE && isTransitioning -> tickGamePausing() @@ -99,28 +97,70 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val cp == GamePhases.NEW_GAME_INTRO && isTransitioning -> tickGameUnpausing() cp == GamePhases.NEW_GAME_INTRO -> handleIntro() cp == GamePhases.PLAYERS_PICK_SHIELDS && isTransitioning -> { - if (elapsedTime < pauseTime) isTransitioning = false + if (elapsedTime > pauseTime) isTransitioning = false } cp == GamePhases.PLAYERS_PICK_SHIELDS -> return cp == GamePhases.PLAYERS_TURN -> return cp == GamePhases.PLAYERS_TURN_FIRED && isTransitioning -> tickGameUnpausing(quickStartTime) cp == GamePhases.PLAYERS_TURN_FIRED -> handlePlayerShot() + cp == GamePhases.PLAYERS_TURN_FIRED_ENDS_EARLY -> handlePlayerShotEndsEarly() cp == GamePhases.PLAYERS_TURN_AIMING -> return cp == GamePhases.PLAYERS_TURN_POWERING -> return - cp == GamePhases.END_ROUND && isTransitioning -> tickGamePausing(endSpeed = .1f) + cp == GamePhases.END_ROUND && isTransitioning -> tickGamePausing(outroDuration, endSpeed = .1f) cp == GamePhases.END_ROUND -> gameState.tickClock(timeStep * .1f, velocityIterations, positionIterations) else -> gameState.tickClock(timeStep, velocityIterations, positionIterations) } } + fun render() { + when (currentPhase) { + GamePhases.MAIN_MENU -> guiController.render() + GamePhases.MAIN_MENU_SELECT_PLAYERS -> guiController.render() + GamePhases.PLAYERS_PICK_SHIELDS -> drawWorldAndGui() + GamePhases.PLAYERS_TURN -> drawWorldAndGui() + GamePhases.PLAYERS_TURN_AIMING -> { + drawWorldAndGui() + drawer.drawPlayerAimingPointer(gameState.playerOnTurn!!) + } + GamePhases.PLAYERS_TURN_POWERING -> { + drawWorldAndGui() + drawer.drawPlayerAimingPointer(gameState.playerOnTurn!!) + } + GamePhases.END_ROUND -> drawWorldAndGui() + else -> drawPlayPhase() + } + + drawer.renderer.drawText( + "Animating: ${isTransitioning.toString().padEnd(5, ' ')} ${currentPhase.name}", + Vec2(120f - camera.windowWidth * .5f, -10f + camera.windowHeight * .5f), + vectorUnit.mul(0.1f), Color.GREEN, false + ) + + drawer.renderer.drawText( + "${elapsedTime.div(100f).roundToInt().div(10f)} seconds", + Vec2(40f - camera.windowWidth * .5f, -30f + camera.windowHeight * .5f), + vectorUnit.mul(0.1f), Color.GREEN, false + ) + } + + private fun handlePlayerShotEndsEarly() { + when { + isTransitioning -> tickGamePausing() + else -> setupNextPlayersTurn() + } + } + private fun handlePlayerShot() { + val roundEndsEarly = (gameState.warheads.none() + && gameState.particles.none() + && gameState.vehicles + .all { + it.worldBody.contactList != null + && getContactBodies(it.worldBody.contactList).any { other -> other.mass > 50f } + }) when { - gameState.warheads.none() - && gameState.particles.none() - && gameState.vehicles - .all { it.worldBody.contactList != null && it.worldBody.contactList.other.mass > 5f } -> setupNextPlayersTurn() - // if (no active warheads && all players in contact with a moon or bigger) setupNextPlayersTurn() + roundEndsEarly -> if (!checkStateEndOfRound()) startNewPhase(GamePhases.PLAYERS_TURN_FIRED_ENDS_EARLY) elapsedTime > maxTurnDuration -> setupNextPlayersTurn() elapsedTime > (maxTurnDuration - pauseTime) -> tickGamePausing( pauseTime, calculatedElapsedTime = (elapsedTime - maxTurnDuration + pauseTime) @@ -130,6 +170,16 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val } + private fun getContactBodies(contactEdge: ContactEdge): Sequence = sequence { + var currentContact = contactEdge + yield(currentContact.other) + + while (currentContact.next != null) { + yield(currentContact.other) + currentContact = currentContact.next + } + } + private fun handleIntro() { when { elapsedTime > introDuration -> playerSelectsShield() @@ -152,15 +202,11 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val setupPlayersPickShields() currentPhase = GamePhases.PLAYERS_PICK_SHIELDS -// startTransition() + // startTransition() } private fun setupNextPlayersTurn() { - val vehiclesDestroyed = gameState.gamePlayers - .count { it.vehicle!!.hitPoints <= 0f } >= gameState.gamePlayers.size - 1 - if (vehiclesDestroyed) { - currentPhase = GamePhases.END_ROUND - startTransition() + if (checkStateEndOfRound()) { return } @@ -170,43 +216,41 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val setNextPlayerOnTurn() setupPlayerCommandPanel() - currentPhase = GamePhases.PLAYERS_TURN - startTransition() + startNewPhase(GamePhases.PLAYERS_TURN) + } + + private fun checkStateEndOfRound(): Boolean { + val vehiclesDestroyed = gameState.gamePlayers.count { it.vehicle!!.hitPoints > 0 } < 2 + if (vehiclesDestroyed) { + startNewPhase(GamePhases.END_ROUND) + guiController.createRoundLeaderboard(gameState.gamePlayers, onClickNextRound = {}) + return true + } + return false } private fun setupPlayerCommandPanel() { guiController.createPlayerCommandPanel( player = gameState.playerOnTurn!!, - onClickAim = { player -> playerSetsAim(player) }, - onClickPower = { player -> playerSetsPower(player) }, + onClickAim = { startNewPhase(GamePhases.PLAYERS_TURN_AIMING) }, + onClickPower = { startNewPhase(GamePhases.PLAYERS_TURN_POWERING) }, onClickFire = { player -> playerFires(player) } ) } - private fun playerSetsPower(player: GamePlayer) { - currentPhase = GamePhases.PLAYERS_TURN_POWERING - startTransition() - } - - private fun playerSetsAim(player: GamePlayer) { - currentPhase = GamePhases.PLAYERS_TURN_AIMING - startTransition() - } - private fun playerFires(player: GamePlayer) { // check() {} player has enough funds && in stable position to fire large warheads - val firedWarhead = gameState.fireWarhead(textures, player, "boom small") + val firedWarhead = gameState.fireWarhead(player, "boom small") camera.trackFreeBody(firedWarhead, 200f) - currentPhase = GamePhases.PLAYERS_TURN_FIRED - startTransition() + startNewPhase(GamePhases.PLAYERS_TURN_FIRED) } private fun setNextPlayerOnTurn() { checkNotNull(gameState.playerOnTurn) { "No player is on turn." } val playerOnTurn = gameState.playerOnTurn!! - val players = gameState.gamePlayers + val players = gameState.gamePlayers.filter { it.vehicle!!.hitPoints > 0 } gameState.playerOnTurn = players[(players.indexOf(playerOnTurn) + 1).rem(players.size)] camera.trackFreeBody(gameState.playerOnTurn!!.vehicle!!) @@ -219,60 +263,18 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val ) } - fun render() { - when (currentPhase) { - GamePhases.MAIN_MENU -> guiController.render() - GamePhases.MAIN_MENU_SELECT_PLAYERS -> guiController.render() - GamePhases.PLAYERS_PICK_SHIELDS -> drawWorldAndGui() - GamePhases.PLAYERS_TURN -> drawWorldAndGui() - GamePhases.PLAYERS_TURN_AIMING -> { - drawWorldAndGui() - drawer.drawPlayerAimingPointer(gameState.playerOnTurn!!) - } - GamePhases.PLAYERS_TURN_POWERING -> { - drawWorldAndGui() - drawer.drawPlayerAimingPointer(gameState.playerOnTurn!!) - } - GamePhases.END_ROUND -> drawWorldAndGui() - else -> drawPlayPhase() - } - - drawer.renderer.drawText( - "Animating: ${isTransitioning.toString().padEnd(5, ' ')} ${currentPhase.name}", - Vec2(120f - camera.windowWidth * .5f, -10f + camera.windowHeight * .5f), - vectorUnit.mul(0.1f), Color.GREEN, false - ) - - drawer.renderer.drawText( - "${elapsedTime.div(100f).roundToInt().div(10f)} seconds", - Vec2(40f - camera.windowWidth * .5f, -30f + camera.windowHeight * .5f), - vectorUnit.mul(0.1f), Color.GREEN, false - ) - } - private fun drawWorldAndGui() { drawPlayPhase() guiController.render() } private fun drawPlayPhase() { - drawer.drawPicture(textures.stars_2k) + drawer.drawPicture(TextureEnum.stars_2k) val allFreeBodies = gameState.gravityBodies allFreeBodies.forEach { drawer.drawTrail(it) } - - gameState.particles.forEach { - it.textureConfig.texture?.bind() - drawer.renderer.drawShape( - it.textureConfig.gpuBufferData, - it.worldBody.position, - it.worldBody.angle, - vectorUnit.mul(it.radius) - ) - } - + gameState.particles.forEach { drawer.drawParticle(it) } allFreeBodies.forEach { drawer.drawFreeBody(it) } - // allFreeBodies.forEach { drawDebugForces(it) } // drawer.drawGravityCells(gameState.gravityMap, gameState.resolution) } @@ -305,7 +307,7 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val } else { val timeFunctionStep = getTimingFunctionEaseIn(1f - interpolateStep) * (1f - endSpeed) + endSpeed gameState.tickClock(timeStep * timeFunctionStep, velocityIterations, positionIterations) -// println("${roundFloat(interpolateStep, 2).toString().padEnd(4, '0')} <> " + roundFloat(timeFunctionStep, 2).toString().padEnd(4, '0')) + // println("${roundFloat(interpolateStep, 2).toString().padEnd(4, '0')} <> " + roundFloat(timeFunctionStep, 2).toString().padEnd(4, '0')) } } @@ -363,7 +365,8 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val fun leftClickMouse(event: MouseButtonEvent) { when { - mouseElementPhases.any { currentPhase == it } -> guiController.checkLeftClick(getScreenLocation(event.location)) + mouseElementPhases.any { currentPhase == it } -> guiController.checkLeftClick( + getScreenLocation(event.location)) currentPhase == GamePhases.PLAYERS_TURN_AIMING -> currentPhase = GamePhases.PLAYERS_TURN currentPhase == GamePhases.PLAYERS_TURN_POWERING -> currentPhase = GamePhases.PLAYERS_TURN } @@ -419,10 +422,9 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val private fun setupStartGame() { guiController.clear() - currentPhase = GamePhases.NEW_GAME_INTRO - startTransition() + startNewPhase(GamePhases.NEW_GAME_INTRO) - MapGenerator.populateNewGameMap(gameState, textures) + MapGenerator.populateNewGameMap(gameState) check(gameState.gamePlayers.size > 0) { "Cannot play a game with no players." } gameState.playerOnTurn = gameState.gamePlayers.random() @@ -435,12 +437,14 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer, val private const val introStartSlowdown = 2000f private const val maxTurnDuration = 30000f private const val quickStartTime = 300f + private const val outroDuration = 5000f private val mouseElementPhases = listOf( GamePhases.MAIN_MENU, GamePhases.MAIN_MENU_SELECT_PLAYERS, GamePhases.PLAYERS_PICK_SHIELDS, - GamePhases.PLAYERS_TURN + GamePhases.PLAYERS_TURN, + GamePhases.END_ROUND ) } diff --git a/src/main/kotlin/game/GamePhases.kt b/src/main/kotlin/game/GamePhases.kt index 73c67c2..7024f65 100644 --- a/src/main/kotlin/game/GamePhases.kt +++ b/src/main/kotlin/game/GamePhases.kt @@ -10,6 +10,7 @@ enum class GamePhases { NONE, PLAYERS_TURN, PLAYERS_TURN_FIRED, + PLAYERS_TURN_FIRED_ENDS_EARLY, PLAYERS_TURN_AIMING, PLAYERS_TURN_POWERING, END_ROUND diff --git a/src/main/kotlin/game/MapGenerator.kt b/src/main/kotlin/game/MapGenerator.kt index 5e93eb0..599ab34 100644 --- a/src/main/kotlin/game/MapGenerator.kt +++ b/src/main/kotlin/game/MapGenerator.kt @@ -2,6 +2,7 @@ package game import Vector2f import display.draw.TextureConfig +import display.draw.TextureEnum import display.draw.TextureHolder import display.graphic.BasicShapes import engine.GameState @@ -15,36 +16,36 @@ import kotlin.math.sin object MapGenerator { - fun populateNewGameMap(gameState: GameState, textures: TextureHolder) { + fun populateNewGameMap(gameState: GameState) { val terra = Planet.create( gameState.world, "terra", 0f, 0f, 0f, 0f, 0f, .1f, 1800f, 4.5f, .3f, - textureConfig = TextureConfig(textures.marble_earth, chunkedVertices = BasicShapes.polygon30.chunked(2)) + textureConfig = TextureConfig(TextureEnum.marble_earth, chunkedVertices = BasicShapes.polygon30.chunked(2)) ) val luna = Planet.create( - gameState.world, "luna", -20f, 0f, 0f, 0f, 4.4f, -.4f, 100f, 1.25f, .5f, - textureConfig = TextureConfig(textures.full_moon, chunkedVertices = BasicShapes.polygon30.chunked(2)) + gameState.world, "luna", -20f, 0f, 0f, 0f, 4.8f, -.4f, 100f, 1.25f, .5f, + textureConfig = TextureConfig(TextureEnum.full_moon, chunkedVertices = BasicShapes.polygon30.chunked(2)) ) val vehicles = gameState.gamePlayers.withIndex().map { (index, player) -> Vehicle.create( - gameState.world, player, -30f * index + 15f * gameState.gamePlayers.size, + gameState.world, player, -10f * index + .5f * gameState.gamePlayers.size, 10f, index * 1f, 0f, 0f, 1f, 3f, radius = .75f, - textureConfig = TextureConfig(textures.metal, Vector2f(.7f, .7f), Vector2f(0f, 0f)) + textureConfig = TextureConfig(TextureEnum.metal, Vector2f(.7f, .7f), Vector2f(0f, 0f)) ) } gameState.vehicles.addAll(vehicles) gameState.planets.addAll(listOf(terra, luna)) - gameState.planets.addAll(createPlanets(gameState.world, 5, textures)) + gameState.planets.addAll(createPlanets(gameState.world, 5)) gameState.gravityBodies.forEach { it.textureConfig.updateGpuBufferData() } } - fun populateTestMap(gameState: GameState, textures: TextureHolder) { + fun populateTestMap(gameState: GameState) { val terra = Planet.create( gameState.world, "terra", 0f, 0f, 0f, 0f, 0f, .1f, 1800f, 4.5f, .3f, textureConfig = TextureConfig( - textures.marble_earth, + TextureEnum.marble_earth, Vector2f(1f, 1f), Vector2f(0f, 0f), BasicShapes.polygon30.chunked(2) @@ -53,7 +54,7 @@ object MapGenerator { val luna = Planet.create( gameState.world, "luna", -20f, 0f, 0f, 0f, 4.4f, -.4f, 100f, 1.25f, .5f, textureConfig = TextureConfig( - textures.full_moon, + TextureEnum.full_moon, Vector2f(1f, 1f), Vector2f(0f, 0f), BasicShapes.polygon30.chunked(2) @@ -61,21 +62,21 @@ object MapGenerator { ) val alice = Vehicle.create( gameState.world, GamePlayer("alice"), -30f, 5f, 0f, -2f, 2.7f, 1f, 3f, radius = .75f, - textureConfig = TextureConfig(textures.metal, Vector2f(.7f, .7f), Vector2f(0f, 0f), listOf()) + textureConfig = TextureConfig(TextureEnum.metal, Vector2f(.7f, .7f), Vector2f(0f, 0f), listOf()) ) val bob = Vehicle.create( gameState.world, GamePlayer("bob"), 25f, 0f, 0f, 2f, -3f, 0f, 3f, radius = .75f, - textureConfig = TextureConfig(textures.metal, Vector2f(.7f, .7f), Vector2f(0f, 0f), listOf()) + textureConfig = TextureConfig(TextureEnum.metal, Vector2f(.7f, .7f), Vector2f(0f, 0f), listOf()) ) gameState.vehicles.addAll(listOf(alice, bob)) gameState.planets.addAll(listOf(terra, luna)) - gameState.planets.addAll(createPlanets(gameState.world, 20, textures)) + gameState.planets.addAll(createPlanets(gameState.world, 20)) gameState.gravityBodies.forEach { it.textureConfig.updateGpuBufferData() } } - private fun createPlanets(world: World, count: Int, textures: TextureHolder): List { + private fun createPlanets(world: World, count: Int): List { return (1..count) .withIndex() .map { (i, _) -> @@ -97,7 +98,7 @@ object MapGenerator { .5f, 0.3f * it[2].rem(6f), .2f + it[2].rem(6f) * .05f, friction = .6f, textureConfig = TextureConfig( - textures.pavement, + TextureEnum.pavement, Vector2f(.3f, .3f), Vector2f(it[0].rem(20f) / 10 - 1, it[1].rem(20f) / 10 - 1), BasicShapes.polygon9.chunked(2) From 93b913e59f7a96cb4d1b5fb24fa657df7ea68b8c Mon Sep 17 00:00:00 2001 From: Pierre Blaarkies Roux Date: Thu, 9 Apr 2020 09:28:43 +0200 Subject: [PATCH 05/11] Add build task to package an uberJar --- build.gradle.kts | 146 ++++++++++++------ src/main/kotlin/display/draw/TextureHolder.kt | 12 +- src/main/kotlin/display/graphic/Renderer.kt | 8 +- src/main/kotlin/display/graphic/Texture.kt | 11 +- src/main/kotlin/game/GamePhaseHandler.kt | 20 +-- src/main/kotlin/utility/Common.kt | 10 +- 6 files changed, 135 insertions(+), 72 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 62ea56a..586100f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,14 +4,8 @@ plugins { kotlin("jvm") version "1.3.41" } -kotlin { - experimental { - coroutines = org.jetbrains.kotlin.gradle.dsl.Coroutines.ENABLE - } -} - group = "blaarkies" -version = "0.0-SNAPSHOT" +version = "0.0.0" repositories { mavenCentral() @@ -21,13 +15,6 @@ val kotlinVersion = "1.3.10" val lwjglVersion = "3.2.3" val lwjglNatives = "natives-windows" -tasks.test { - useJUnitPlatform() - testLogging { - events("PASSED", "SKIPPED", "FAILED") - } -} - dependencies { implementation(kotlin("stdlib-jdk8")) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3") @@ -39,49 +26,110 @@ dependencies { implementation(platform("org.lwjgl:lwjgl-bom:$lwjglVersion")) val lwjglList = listOf( arrayOf("lwjgl", true), -// arrayOf("lwjgl-assimp", true), -// arrayOf("lwjgl-bgfx", true), -// arrayOf("lwjgl-cuda", false), -// arrayOf("lwjgl-egl", false), + // arrayOf("lwjgl-assimp", true), + // arrayOf("lwjgl-bgfx", true), + // arrayOf("lwjgl-cuda", false), + // arrayOf("lwjgl-egl", false), arrayOf("lwjgl-glfw", true), arrayOf("lwjgl-jawt", false), -// arrayOf("lwjgl-jemalloc", true), -// arrayOf("lwjgl-libdivide", true), -// arrayOf("lwjgl-llvm", true), -// arrayOf("lwjgl-lmdb", true), -// arrayOf("lwjgl-lz4", true), -// arrayOf("lwjgl-meow", true), -// arrayOf("lwjgl-nanovg", true), -// arrayOf("lwjgl-nfd", true), -// arrayOf("lwjgl-nuklear", true), -// arrayOf("lwjgl-odbc", false), -// arrayOf("lwjgl-openal", true), -// arrayOf("lwjgl-opencl", false), + // arrayOf("lwjgl-jemalloc", true), + // arrayOf("lwjgl-libdivide", true), + // arrayOf("lwjgl-llvm", true), + // arrayOf("lwjgl-lmdb", true), + // arrayOf("lwjgl-lz4", true), + // arrayOf("lwjgl-meow", true), + // arrayOf("lwjgl-nanovg", true), + // arrayOf("lwjgl-nfd", true), + // arrayOf("lwjgl-nuklear", true), + // arrayOf("lwjgl-odbc", false), + // arrayOf("lwjgl-openal", true), + // arrayOf("lwjgl-opencl", false), arrayOf("lwjgl-opengl", true), -// arrayOf("lwjgl-opengles", true), -// arrayOf("lwjgl-openvr", true), -// arrayOf("lwjgl-opus", true), -// arrayOf("lwjgl-ovr", true), -// arrayOf("lwjgl-par", true), -// arrayOf("lwjgl-remotery", true), -// arrayOf("lwjgl-rpmalloc", true), -// arrayOf("lwjgl-shaderc", true), -// arrayOf("lwjgl-sse", true), + // arrayOf("lwjgl-opengles", true), + // arrayOf("lwjgl-openvr", true), + // arrayOf("lwjgl-opus", true), + // arrayOf("lwjgl-ovr", true), + // arrayOf("lwjgl-par", true), + // arrayOf("lwjgl-remotery", true), + // arrayOf("lwjgl-rpmalloc", true), + // arrayOf("lwjgl-shaderc", true), + // arrayOf("lwjgl-sse", true), arrayOf("lwjgl-stb", true) -// arrayOf("lwjgl-tinyexr", true), -// arrayOf("lwjgl-tinyfd", true), -// arrayOf("lwjgl-tootle", true), -// arrayOf("lwjgl-vma", true), -// arrayOf("lwjgl-vulkan", false), -// arrayOf("lwjgl-xxhash", true), -// arrayOf("lwjgl-yoga", true), -// arrayOf("lwjgl-zstd", true) + // arrayOf("lwjgl-tinyexr", true), + // arrayOf("lwjgl-tinyfd", true), + // arrayOf("lwjgl-tootle", true), + // arrayOf("lwjgl-vma", true), + // arrayOf("lwjgl-vulkan", false), + // arrayOf("lwjgl-xxhash", true), + // arrayOf("lwjgl-yoga", true), + // arrayOf("lwjgl-zstd", true) ) lwjglList.forEach { implementation("org.lwjgl", it[0].toString()) } lwjglList.filter { it[1] == true } .forEach { runtimeOnly("org.lwjgl", it[0].toString(), classifier = lwjglNatives) } } -tasks.withType { - kotlinOptions.jvmTarget = "11" +sourceSets { + main { + resources { + exclude("textures", "fonts") + } + } +} + +tasks { + + withType { + kotlinOptions.jvmTarget = "11" + } + + task("renameFolder") { + mustRunAfter("createBat") + doLast { + file("$buildDir/libs").renameTo(file("$buildDir/Volynov-$version")) + } + } + + task("createBat") { + mustRunAfter("uberJar") + doLast { + File("$buildDir/libs/run.bat") + .writeText("""|chcp 65001 + |java -Dfile.encoding=UTF-8 -jar ./volynov-$version-uber.jar + |pause + """.trimMargin()) + } + } + + register("uberJar") { + dependsOn(configurations.runtimeClasspath) + + group = "build" + archiveClassifier.set("uber") + manifest { attributes["Main-Class"] = "MainKt" } + + from(sourceSets.main.get().output) + from({ + configurations.runtimeClasspath.get() + .filter { it.name.endsWith("jar") } + .map { zipTree(it) } + }) + } + + task("package") { + group = "build" + dependsOn("clean", "uberJar", "copyTextures", "createBat", "renameFolder") + } +} + +tasks.test { + group = "build" + useJUnitPlatform() + testLogging { events("PASSED", "SKIPPED", "FAILED") } +} + +tasks.register("copyTextures") { + from("$projectDir/src/main/resources") + exclude("shaders") + into("$buildDir/libs") } diff --git a/src/main/kotlin/display/draw/TextureHolder.kt b/src/main/kotlin/display/draw/TextureHolder.kt index 1f9ef4f..f359115 100644 --- a/src/main/kotlin/display/draw/TextureHolder.kt +++ b/src/main/kotlin/display/draw/TextureHolder.kt @@ -8,12 +8,12 @@ class TextureHolder { fun init() { val hMap = textureHashMap - hMap[TextureEnum.marble_earth] = Texture.loadTexture("src\\main\\resources\\textures\\marble_earth.png") - hMap[TextureEnum.full_moon] = Texture.loadTexture("src\\main\\resources\\textures\\full_moon.png") - hMap[TextureEnum.metal] = Texture.loadTexture("src\\main\\resources\\textures\\metal.png") - hMap[TextureEnum.pavement] = Texture.loadTexture("src\\main\\resources\\textures\\pavement.png") - hMap[TextureEnum.white_pixel] = Texture.loadTexture("src\\main\\resources\\textures\\white_pixel.png") - hMap[TextureEnum.stars_2k] = Texture.loadTexture("src\\main\\resources\\textures\\stars_2k.png") + hMap[TextureEnum.marble_earth] = Texture.loadTexture("./textures/marble_earth.png") + hMap[TextureEnum.full_moon] = Texture.loadTexture("./textures/full_moon.png") + hMap[TextureEnum.metal] = Texture.loadTexture("./textures/metal.png") + hMap[TextureEnum.pavement] = Texture.loadTexture("./textures/pavement.png") + hMap[TextureEnum.white_pixel] = Texture.loadTexture("./textures/white_pixel.png") + hMap[TextureEnum.stars_2k] = Texture.loadTexture("./textures/stars_2k.png") } fun getTexture(texture: TextureEnum): Texture = textureHashMap[texture].let { diff --git a/src/main/kotlin/display/graphic/Renderer.kt b/src/main/kotlin/display/graphic/Renderer.kt index 9acbecc..3ea6916 100644 --- a/src/main/kotlin/display/graphic/Renderer.kt +++ b/src/main/kotlin/display/graphic/Renderer.kt @@ -11,6 +11,7 @@ import org.lwjgl.opengl.GL20.GL_FRAGMENT_SHADER import org.lwjgl.opengl.GL20.GL_VERTEX_SHADER import org.lwjgl.system.MemoryUtil import utility.Common +import utility.Common.getSafePath import java.awt.FontFormatException import java.io.FileInputStream import java.io.IOException @@ -40,7 +41,8 @@ class Renderer { glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) font = try { - Font(FileInputStream("src\\main\\resources\\fonts\\ALBMT___.TTF"), 80) + val fontPath = getSafePath("./fonts/ALBMT___.TTF") + Font(FileInputStream(fontPath), 80) } catch (ex: FontFormatException) { Logger.getLogger(Renderer::class.java.name).log(Level.CONFIG, null, ex) Font() @@ -158,8 +160,8 @@ class Renderer { scale: Vec2 = Vec2(1f, 1f), useCamera: Boolean ) { -// val uniTex = program!!.getUniformLocation("texImage") -// program!!.setUniform(uniTex, 0) + // val uniTex = program!!.getUniformLocation("texImage") + // program!!.setUniform(uniTex, 0) val model = Matrix4f.translate(offset.x, offset.y, z) .multiply(Matrix4f.rotate(h * Common.radianToDegree, 0f, 0f, 1f)) diff --git a/src/main/kotlin/display/graphic/Texture.kt b/src/main/kotlin/display/graphic/Texture.kt index b09660c..db28d7f 100644 --- a/src/main/kotlin/display/graphic/Texture.kt +++ b/src/main/kotlin/display/graphic/Texture.kt @@ -4,6 +4,8 @@ import org.lwjgl.BufferUtils import org.lwjgl.opengl.GL11.* import org.lwjgl.stb.STBImage.* import org.lwjgl.system.MemoryStack +import utility.Common.getSafePath +import java.io.File import java.nio.ByteBuffer class Texture { @@ -62,7 +64,9 @@ class Texture { return texture } - fun loadTexture(path: String): Texture { + fun loadTexture(resourcePath: String): Texture { + val safePath = getSafePath(resourcePath) + var image = BufferUtils.createByteBuffer(0) var width = 0 var height = 0 @@ -72,14 +76,15 @@ class Texture { val comp = stack.mallocInt(1) stbi_set_flip_vertically_on_load(true) - image = stbi_load(path, w, h, comp, 0) - ?: throw RuntimeException("Failed to load a texture file!\n${stbi_failure_reason()}") + image = stbi_load(safePath, w, h, comp, 0) + ?: throw RuntimeException("Cannot load texture file at $safePath \n${stbi_failure_reason()}") width = w.get() height = h.get() } return createTexture(width, height, image) } + } } diff --git a/src/main/kotlin/game/GamePhaseHandler.kt b/src/main/kotlin/game/GamePhaseHandler.kt index d41a45f..96a4f2f 100644 --- a/src/main/kotlin/game/GamePhaseHandler.kt +++ b/src/main/kotlin/game/GamePhaseHandler.kt @@ -45,16 +45,16 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer) { fun init(window: Window) { exitCall = { window.exit() } -// setupMainMenu() - - currentPhase = GamePhases.PLAYERS_PICK_SHIELDS - isTransitioning = false - gameState.gamePlayers.addAll((1..3).map { GamePlayer(it.toString()) }) - MapGenerator.populateNewGameMap(gameState) - gameState.gamePlayers.forEach { it.vehicle?.shield = VehicleShield() } - gameState.playerOnTurn = gameState.gamePlayers[0] - - setupNextPlayersTurn() + setupMainMenu() + +// currentPhase = GamePhases.PLAYERS_PICK_SHIELDS +// isTransitioning = false +// gameState.gamePlayers.addAll((1..3).map { GamePlayer(it.toString()) }) +// MapGenerator.populateNewGameMap(gameState) +// gameState.gamePlayers.forEach { it.vehicle?.shield = VehicleShield() } +// gameState.playerOnTurn = gameState.gamePlayers[0] +// +// setupNextPlayersTurn() } fun dragMouseRightClick(movement: Vec2) { diff --git a/src/main/kotlin/utility/Common.kt b/src/main/kotlin/utility/Common.kt index 18b385b..7190d78 100644 --- a/src/main/kotlin/utility/Common.kt +++ b/src/main/kotlin/utility/Common.kt @@ -1,6 +1,7 @@ package utility import org.jbox2d.common.Vec2 +import java.io.File import java.util.* import kotlin.math.* @@ -17,6 +18,14 @@ object Common { return result } + fun getSafePath(resourcePath: String): String { + val alternativePath = "src/main/resources/${resourcePath.substring(1)}" + return when { + File(alternativePath).exists() -> alternativePath + else -> resourcePath + } + } + fun joinLists(aList: List, bList: List): Sequence> = sequence { aList.forEach { aItem -> bList.forEach { bItem -> @@ -53,5 +62,4 @@ object Common { fun getTimingFunctionSigmoid(interpolateStep: Float, centerGradient: Float = 1f) = (1f / (1f + exp((-(interpolateStep - .5f) * 10f)) * centerGradient)) * 1.023f - 0.0022f - } From 9c8ff4c9e18c731f5dccd7acd7413be001f4eb77 Mon Sep 17 00:00:00 2001 From: Pierre Blaarkies Roux Date: Thu, 9 Apr 2020 20:21:16 +0200 Subject: [PATCH 06/11] Add concave shaped vehicles Improve collision detection of warheads --- src/main/kotlin/display/Window.kt | 12 +- src/main/kotlin/display/draw/Drawer.kt | 14 +- .../kotlin/display/graphic/BasicShapes.kt | 20 +-- src/main/kotlin/display/graphic/Renderer.kt | 9 +- src/main/kotlin/display/gui/GuiController.kt | 8 +- src/main/kotlin/display/gui/GuiElement.kt | 3 +- src/main/kotlin/display/text/Font.kt | 11 +- src/main/kotlin/engine/FreeBodyCallback.kt | 6 + src/main/kotlin/engine/GameState.kt | 151 +++++++++++++----- src/main/kotlin/engine/freeBody/FreeBody.kt | 1 - src/main/kotlin/engine/freeBody/Planet.kt | 6 +- src/main/kotlin/engine/freeBody/Vehicle.kt | 73 ++++----- src/main/kotlin/engine/freeBody/Warhead.kt | 16 +- src/main/kotlin/game/GamePhaseHandler.kt | 17 +- src/main/kotlin/game/GamePlayer.kt | 27 +++- src/main/kotlin/game/MapGenerator.kt | 3 +- src/main/kotlin/utility/Common.kt | 12 ++ src/test/kotlin/engine/motion/DirectorTest.kt | 23 +-- src/test/kotlin/engine/physics/GravityTest.kt | 11 +- 19 files changed, 257 insertions(+), 166 deletions(-) create mode 100644 src/main/kotlin/engine/FreeBodyCallback.kt diff --git a/src/main/kotlin/display/Window.kt b/src/main/kotlin/display/Window.kt index f9eb144..78ad21e 100644 --- a/src/main/kotlin/display/Window.kt +++ b/src/main/kotlin/display/Window.kt @@ -11,6 +11,8 @@ import org.lwjgl.opengl.GL11 import org.lwjgl.opengl.GL11.* import org.lwjgl.system.Callback import org.lwjgl.system.MemoryUtil +import utility.Common.makeVec2 +import java.nio.DoubleBuffer class Window(private val title: String, var width: Int, var height: Int, private var vSync: Boolean) { @@ -87,15 +89,15 @@ class Window(private val title: String, var width: Int, var height: Int, private }?.let { callbacks.add(it) } GLFW.glfwSetCursorPosCallback(windowHandle) { window, xPos, yPos -> - cursorPositionEvent.onNext(Vec2(xPos.toFloat(), yPos.toFloat())) + cursorPositionEvent.onNext(makeVec2(xPos, yPos)) }?.let { callbacks.add(it) } GLFW.glfwSetScrollCallback(windowHandle) { window, xOffset, yOffset -> - mouseScrollEvent.onNext(Vec2(xOffset.toFloat(), yOffset.toFloat())) + mouseScrollEvent.onNext(makeVec2(xOffset, yOffset)) }?.let { callbacks.add(it) } -// glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); -// glfwSetCharCallback(window, character_callback); + // glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + // glfwSetCharCallback(window, character_callback); } fun setClearColor(r: Float, g: Float, b: Float, alpha: Float) { @@ -120,7 +122,7 @@ class Window(private val title: String, var width: Int, var height: Int, private val y = BufferUtils.createDoubleBuffer(1) GLFW.glfwGetCursorPos(windowHandle, x, y) - return Vec2(x.get().toFloat(), y.get().toFloat()) + return makeVec2(x, y) } fun exit() { diff --git a/src/main/kotlin/display/draw/Drawer.kt b/src/main/kotlin/display/draw/Drawer.kt index 3dc7091..3c601da 100644 --- a/src/main/kotlin/display/draw/Drawer.kt +++ b/src/main/kotlin/display/draw/Drawer.kt @@ -10,6 +10,8 @@ import engine.physics.GravityCell import game.GamePlayer import org.jbox2d.common.MathUtils.sin import org.jbox2d.common.Vec2 +import utility.Common.makeVec2 +import utility.Common.makeVec2Circle import utility.Common.vectorUnit import java.util.* import kotlin.math.cos @@ -44,9 +46,9 @@ class Drawer(val renderer: Renderer) { ).toFloatArray() textures.getTexture(TextureEnum.white_pixel).bind() -// renderer.drawStrip(data) + // renderer.drawStrip(data) - renderer.drawText(freeBody.id, freeBody.worldBody.position, Vec2(1f, 1f), Color.WHITE) + renderer.drawText(freeBody.id, freeBody.worldBody.position, vectorUnit, Color.WHITE) } fun drawTrail(freeBody: FreeBody) { @@ -84,11 +86,11 @@ class Drawer(val renderer: Renderer) { (it[0] / 2 - 0.5f), (it[1] / 2 - 0.5f) ) }.toFloatArray() - renderer.drawShape(data, Vec2(key.x * resolution, key.y * resolution), 0f, Vec2(scale, scale)) + renderer.drawShape(data, makeVec2(key.x, key.y).mul(resolution), 0f, makeVec2(scale)) } } - fun drawPicture(textureEnum: TextureEnum, scale: Vec2 = Vec2(1f, 1f), offset: Vec2 = Vec2()) { + fun drawPicture(textureEnum: TextureEnum, scale: Vec2 = vectorUnit, offset: Vec2 = Vec2()) { val texture = textures.getTexture(textureEnum).bind() val left = -texture.width / 2f @@ -106,13 +108,13 @@ class Drawer(val renderer: Renderer) { ) }.toFloatArray() - renderer.drawShape(data, scale = Vec2(1f, 1f).mul(45f)) + renderer.drawShape(data, scale = vectorUnit.mul(45f)) } fun drawPlayerAimingPointer(player: GamePlayer) { val playerLocation = player.vehicle!!.worldBody.position val angle = player.playerAim.angle - val aimLocation = Vec2(cos(angle), sin(angle)).mul(player.playerAim.power / 10f) + val aimLocation = makeVec2Circle(angle).mul(player.playerAim.power / 10f) val linePoints = listOf( playerLocation.x, diff --git a/src/main/kotlin/display/graphic/BasicShapes.kt b/src/main/kotlin/display/graphic/BasicShapes.kt index 14a375f..572b2f2 100644 --- a/src/main/kotlin/display/graphic/BasicShapes.kt +++ b/src/main/kotlin/display/graphic/BasicShapes.kt @@ -23,22 +23,22 @@ object BasicShapes { val square = polygon4.map { it * sqrt(2f) } - val squareStar = makeSquareStar() - - private fun makeSquareStar(): List { - return polygon4.chunked(2) - .zip(getPolygonVertices(4, .5).chunked(2) - .map { listOf(it[0] * .6f, it[1] * .6f) } - ) - .flatMap { listOf(it.first, it.second.toList()) } - .flatten() - } + val polygon4Spiked = getSpikedPolygon(8) private fun getPolygonVertices(corners: Int, rotate: Double = .25): List = (0 until corners).flatMap { val t = 2 * PI * (it / corners.toFloat()) + PI * rotate listOf(cos(t).toFloat(), sin(t).toFloat()) } + private fun getSpikedPolygon(corners: Int, smoothness: Float = .6f): List { + return getPolygonVertices(corners).chunked(2) + .withIndex() + .flatMap { (index, vertex) -> + val scale = (if (index.rem(2) == 0) 1f else smoothness) + listOf(vertex[0] * scale, vertex[1] * scale) + } + } + fun getArrowHeadPoints(linePoints: List, headSize: Float = 1f): List { val (ax, ay, bx, by) = linePoints diff --git a/src/main/kotlin/display/graphic/Renderer.kt b/src/main/kotlin/display/graphic/Renderer.kt index 3ea6916..31257e2 100644 --- a/src/main/kotlin/display/graphic/Renderer.kt +++ b/src/main/kotlin/display/graphic/Renderer.kt @@ -12,6 +12,7 @@ import org.lwjgl.opengl.GL20.GL_VERTEX_SHADER import org.lwjgl.system.MemoryUtil import utility.Common import utility.Common.getSafePath +import utility.Common.vectorUnit import java.awt.FontFormatException import java.io.FileInputStream import java.io.IOException @@ -21,7 +22,7 @@ import java.util.logging.Logger class Renderer { - var debugOffset: Vec2 = Vec2(1f, 1f) + var debugOffset: Vec2 = vectorUnit private lateinit var vao: VertexArrayObject private lateinit var vbo: VertexBufferObject @@ -89,7 +90,7 @@ class Renderer { data: FloatArray, offset: Vec2 = Vec2(), h: Float = 0f, - scale: Vec2 = Vec2(1f, 1f), + scale: Vec2 = vectorUnit, useCamera: Boolean = true ) = drawEntity(data, offset, h, scale, GL_TRIANGLE_FAN, useCamera) @@ -97,7 +98,7 @@ class Renderer { data: FloatArray, offset: Vec2 = Vec2(), h: Float = 0f, - scale: Vec2 = Vec2(1f, 1f), + scale: Vec2 = vectorUnit, useCamera: Boolean = true ) = drawEntity(data, offset, h, scale, GL_TRIANGLE_STRIP, useCamera) @@ -157,7 +158,7 @@ class Renderer { offset: Vec2 = Vec2(), z: Float = 0f, h: Float = 0f, - scale: Vec2 = Vec2(1f, 1f), + scale: Vec2 = vectorUnit, useCamera: Boolean ) { // val uniTex = program!!.getUniformLocation("texImage") diff --git a/src/main/kotlin/display/gui/GuiController.kt b/src/main/kotlin/display/gui/GuiController.kt index b669d0b..610b52b 100644 --- a/src/main/kotlin/display/gui/GuiController.kt +++ b/src/main/kotlin/display/gui/GuiController.kt @@ -5,6 +5,7 @@ import display.graphic.Color import game.GamePlayer import org.jbox2d.common.Vec2 import utility.Common.roundFloat +import kotlin.math.roundToInt class GuiController(private val drawer: Drawer) { @@ -40,7 +41,6 @@ class GuiController(private val drawer: Drawer) { elements.addAll(menuButtons) } - fun createMainMenuSelectPlayers( onClickStart: () -> Unit, onClickCancel: () -> Unit, @@ -152,14 +152,13 @@ class GuiController(private val drawer: Drawer) { private fun getPlayerAimAngleDisplay(player: GamePlayer): String = player.playerAim.getDegreesAngle().let { displayNumber(it, 2) + "º" } - fun createRoundLeaderboard(players: MutableList, onClickNextRound: () -> Unit) { clear() val leaderBoardWindow = GuiWindow(drawer, Vec2(), Vec2(200f, 300f), "Leaderboard", draggable = true) - val playerLines = players.map { + val playerLines = players.sortedByDescending { it.score }.map { GuiLabel( drawer, - title = "${it.name} ${Math.random().times(100f).toInt()}".padStart(15, ' '), + title = "${it.name.padEnd(10, ' ')}${it.score.roundToInt()}".padStart(10, ' '), textSize = .2f ) } @@ -205,5 +204,4 @@ class GuiController(private val drawer: Drawer) { } } - } diff --git a/src/main/kotlin/display/gui/GuiElement.kt b/src/main/kotlin/display/gui/GuiElement.kt index 1604312..c778f3b 100644 --- a/src/main/kotlin/display/gui/GuiElement.kt +++ b/src/main/kotlin/display/gui/GuiElement.kt @@ -4,11 +4,12 @@ import display.draw.Drawer import display.graphic.Color import org.jbox2d.common.Vec2 import utility.Common +import utility.Common.vectorUnit open class GuiElement( protected val drawer: Drawer, override var offset: Vec2, - override val scale: Vec2 = Vec2(1f, 1f), + override val scale: Vec2 = vectorUnit, override var title: String, override val textSize: Float, override val color: Color, diff --git a/src/main/kotlin/display/text/Font.kt b/src/main/kotlin/display/text/Font.kt index 46203bb..d04a297 100644 --- a/src/main/kotlin/display/text/Font.kt +++ b/src/main/kotlin/display/text/Font.kt @@ -16,7 +16,6 @@ import java.io.InputStream import kotlin.math.hypot import java.awt.Color as AwtColor - class Font constructor(font: Font = Font(MONOSPACED, BOLD, 32), antiAlias: Boolean = true) { private val glyphs: MutableMap @@ -225,8 +224,10 @@ class Font constructor(font: Font = Font(MONOSPACED, BOLD, 32), antiAlias: Boole color: Color, useCamera: Boolean ) { - val glyphScale = Vec2(glyph.width / texture.width.toFloat(), glyph.height / texture.height.toFloat()) - val glyphOffset = Vec2((glyph.x + glyph.width) / texture.width.toFloat(), glyph.y / texture.height.toFloat()) + val textureWidth = texture.width.toFloat() + val textureHeight = texture.height.toFloat() + val glyphScale = Vec2(glyph.width / textureWidth, glyph.height / textureHeight) + val glyphOffset = Vec2((glyph.x + glyph.width) / textureWidth, glyph.y / textureHeight) val debug = renderer.debugOffset val data = BasicShapes.square @@ -244,8 +245,8 @@ class Font constructor(font: Font = Font(MONOSPACED, BOLD, 32), antiAlias: Boole texture.bind() renderer.drawShape(data, offset, 0f, scale, useCamera) -// textures.white_pixel.bind() -// renderer.drawShape(data, offset, 0f, scale, useCamera) + // textures.white_pixel.bind() + // renderer.drawShape(data, offset, 0f, scale, useCamera) } fun dispose() { diff --git a/src/main/kotlin/engine/FreeBodyCallback.kt b/src/main/kotlin/engine/FreeBodyCallback.kt new file mode 100644 index 0000000..5d6239b --- /dev/null +++ b/src/main/kotlin/engine/FreeBodyCallback.kt @@ -0,0 +1,6 @@ +package engine + +import engine.freeBody.FreeBody +import org.jbox2d.dynamics.Body + +class FreeBodyCallback(val freeBody: FreeBody, val callback: (FreeBody, Body) -> Unit) diff --git a/src/main/kotlin/engine/GameState.kt b/src/main/kotlin/engine/GameState.kt index fdfb274..828163c 100644 --- a/src/main/kotlin/engine/GameState.kt +++ b/src/main/kotlin/engine/GameState.kt @@ -4,21 +4,23 @@ import input.CameraView import display.Window import display.draw.TextureConfig import display.draw.TextureEnum -import engine.freeBody.Particle -import engine.freeBody.Planet -import engine.freeBody.Vehicle -import engine.freeBody.Warhead +import engine.freeBody.* import engine.motion.Director import engine.motion.Motion import engine.physics.CellLocation import engine.physics.Gravity import engine.physics.GravityCell import game.GamePlayer -import org.jbox2d.common.MathUtils.cos -import org.jbox2d.common.MathUtils.sin +import org.jbox2d.callbacks.ContactImpulse +import org.jbox2d.callbacks.ContactListener +import org.jbox2d.collision.Manifold import org.jbox2d.common.Vec2 +import org.jbox2d.dynamics.Body import org.jbox2d.dynamics.World +import org.jbox2d.dynamics.contacts.Contact +import org.jbox2d.dynamics.contacts.ContactEdge import utility.Common +import utility.Common.makeVec2Circle class GameState { @@ -44,6 +46,7 @@ class GameState { var gravityMap = HashMap() var resolution = 0f + val activeCallbacks = mutableListOf<() -> Unit>() fun init(window: Window) { camera = CameraView(window) @@ -64,6 +67,9 @@ class GameState { ) { world.step(timeStep, velocityIterations, positionIterations) + activeCallbacks.forEach { it() } + activeCallbacks.clear() + tickGravityChanges() Motion.addNewTrailers(trailerBodies) @@ -85,36 +91,40 @@ class GameState { } private fun tickWarheads() { - warheads.toList().forEach { warhead -> - val contactList = warhead.worldBody.contactList - val madeContact = contactList?.contact != null - && contactList.other != null - && contactList.contact.isTouching - if (madeContact || (warhead.ageTime > warhead.selfDestructTime) - ) { - val particle = warhead.createParticles(particles, world, contactList?.other ?: warhead.worldBody) - - vehicles.toList() - .map { - Pair(it, (Director.getDistance(it.worldBody, particle.worldBody) - - it.radius - warhead.radius).coerceAtLeast(0f)) - } - .filter { (_, distance) -> distance < particle.radius } - .forEach { (vehicle, distance) -> - val damageUnit = (1f - distance / particle.radius).coerceAtLeast(0f) - .let { Common.getTimingFunctionEaseIn(it) } - vehicle.hitPoints -= damageUnit * warhead.damage - - if (vehicle.hitPoints <= 0) { - world.destroyBody(vehicle.worldBody) - vehicles.remove(vehicle) - } - } - - world.destroyBody(warhead.worldBody) - warheads.remove(warhead) - } + warheads.toList() + .filter { it.ageTime > it.selfDestructTime } + .forEach { detonateWarhead(it) } + } + + private fun detonateWarhead(warhead: Warhead, body: Body? = null) { + val particle = warhead.createParticles(particles, world, body ?: warhead.worldBody) + + checkToDamageVehicles(particle, warhead) + + world.destroyBody(warhead.worldBody) + warheads.remove(warhead) + } + + private fun checkToDamageVehicles(particle: Particle, warhead: Warhead) { + vehicles.toList().map { + Pair(it, (Director.getDistance(it.worldBody, particle.worldBody) + - it.radius - warhead.radius).coerceAtLeast(0f)) } + .filter { (_, distance) -> distance < particle.radius } + .forEach { (vehicle, distance) -> + val damageUnit = (1f - distance / particle.radius).coerceAtLeast(0f) + .let { Common.getTimingFunctionEaseIn(it) } + val totalDamage = damageUnit * warhead.damage + vehicle.hitPoints -= totalDamage + warhead.firedBy.scoreDamage(warhead, totalDamage, vehicle) + + if (vehicle.hitPoints <= 0) { + warhead.firedBy.scoreKill(vehicle) + + world.destroyBody(vehicle.worldBody) + vehicles.remove(vehicle) + } + } } fun reset() { @@ -123,6 +133,8 @@ class GameState { world = World(Vec2()) vehicles.clear() planets.clear() + + world.setContactListener(MyContactListener(this)) } fun fireWarhead(player: GamePlayer, warheadType: String = "will make this some class later"): Warhead { @@ -130,21 +142,26 @@ class GameState { val vehicle = player.vehicle!! val angle = player.playerAim.angle val power = player.playerAim.power * .15f - val origin = vehicle.worldBody.position + val originLocation = vehicle.worldBody.position val originVelocity = vehicle.worldBody.linearVelocity val warheadRadius = .2f - val minimumSafeDistance = 2f * vehicle.radius - val warheadLocation = Vec2( - origin.x + cos(angle) * minimumSafeDistance, - origin.y + sin(angle) * minimumSafeDistance - ) - val warheadVelocity = Vec2(originVelocity.x + cos(angle) * power, originVelocity.y + sin(angle) * power) + val minimumSafeDistance = 1.5f * vehicle.radius + val angleVector = makeVec2Circle(angle) + + val warheadLocation = angleVector.mul(minimumSafeDistance).add(originLocation) + val warheadVelocity = angleVector.mul(power).add(originVelocity) + + val warheadMass = .1f + // val body = player.vehicle!!.worldBody + // body.applyLinearImpulse(angleVector.mul(power).negate(), body.localCenter) + // TODO: recoil causes excessive spin return Warhead.create( world, player, warheadLocation.x, warheadLocation.y, angle, warheadVelocity.x, warheadVelocity.y, 0f, - .1f, warheadRadius, textureConfig = TextureConfig(TextureEnum.metal) + warheadMass, warheadRadius, textureConfig = TextureConfig(TextureEnum.metal), + onWarheadCollision = { self, body -> detonateWarhead(self as Warhead, body) } ) .let { warheads.add(it) @@ -153,4 +170,52 @@ class GameState { } + companion object { + + fun getContactBodies(contactEdge: ContactEdge): Sequence = sequence { + var currentContact = contactEdge + yield(currentContact.other) + + while (currentContact.next != null) { + yield(currentContact.other) + currentContact = currentContact.next + } + } + + fun getContactEdges(contactEdge: ContactEdge): Sequence = sequence { + var currentContact = contactEdge + yield(currentContact.contact) + + while (currentContact.next != null) { + yield(currentContact.contact) + currentContact = currentContact.next + } + } + + } +} + +class MyContactListener(val gameState: GameState) : ContactListener { + + override fun beginContact(contact: Contact) { + val bodies = listOf(contact.fixtureA, contact.fixtureB).map { it.body } + bodies.mapNotNull { it.userData } + .map { it as FreeBodyCallback } + .filter { it.freeBody is Warhead } + .forEach { warhead -> + val otherBody = bodies.find { body -> body != warhead.freeBody.worldBody }!! + gameState.activeCallbacks.add { warhead.callback(warhead.freeBody, otherBody) } + } + + } + + override fun endContact(contact: Contact) { + } + + override fun preSolve(contact: Contact, oldManifold: Manifold) { + } + + override fun postSolve(contact: Contact, impulse: ContactImpulse) { + } + } diff --git a/src/main/kotlin/engine/freeBody/FreeBody.kt b/src/main/kotlin/engine/freeBody/FreeBody.kt index 5bedaee..997e3b0 100644 --- a/src/main/kotlin/engine/freeBody/FreeBody.kt +++ b/src/main/kotlin/engine/freeBody/FreeBody.kt @@ -11,7 +11,6 @@ import kotlin.math.pow open class FreeBody( val id: String, var motion: Motion, - var shapeBox: Shape, var worldBody: Body, var radius: Float, val textureConfig: TextureConfig diff --git a/src/main/kotlin/engine/freeBody/Planet.kt b/src/main/kotlin/engine/freeBody/Planet.kt index e6ae3b4..c0adaf7 100644 --- a/src/main/kotlin/engine/freeBody/Planet.kt +++ b/src/main/kotlin/engine/freeBody/Planet.kt @@ -3,17 +3,15 @@ package engine.freeBody import display.draw.TextureConfig import engine.motion.Motion import org.jbox2d.collision.shapes.CircleShape -import org.jbox2d.collision.shapes.Shape import org.jbox2d.dynamics.* class Planet( id: String, motion: Motion, - shapeBox: Shape, worldBody: Body, radius: Float, textureConfig: TextureConfig -) : FreeBody(id, motion, shapeBox, worldBody, radius, textureConfig) { +) : FreeBody(id, motion, worldBody, radius, textureConfig) { companion object { @@ -38,7 +36,7 @@ class Planet( val bodyDef = createBodyDef(BodyType.DYNAMIC, x, y, h, dx, dy, dh) val worldBody = createWorldBody(shapeBox, mass, radius, friction, restitution, world, bodyDef) - return Planet(id, Motion(), shapeBox, worldBody, radius, textureConfig) + return Planet(id, Motion(), worldBody, radius, textureConfig) } } diff --git a/src/main/kotlin/engine/freeBody/Vehicle.kt b/src/main/kotlin/engine/freeBody/Vehicle.kt index bd4d61e..227175a 100644 --- a/src/main/kotlin/engine/freeBody/Vehicle.kt +++ b/src/main/kotlin/engine/freeBody/Vehicle.kt @@ -6,20 +6,19 @@ import engine.motion.Motion import engine.shields.VehicleShield import game.GamePlayer import org.jbox2d.collision.shapes.PolygonShape -import org.jbox2d.collision.shapes.Shape import org.jbox2d.common.Vec2 import org.jbox2d.dynamics.* +import utility.Common.makeVec2 import kotlin.math.PI import kotlin.math.pow class Vehicle( id: String, motion: Motion, - shapeBox: Shape, worldBody: Body, radius: Float, textureConfig: TextureConfig -) : FreeBody(id, motion, shapeBox, worldBody, radius, textureConfig) { +) : FreeBody(id, motion, worldBody, radius, textureConfig) { var shield: VehicleShield? = null var hitPoints: Float = 100f @@ -41,52 +40,36 @@ class Vehicle( friction: Float = .6f, textureConfig: TextureConfig ): Vehicle { - val shapeBox = PolygonShape() - val vertices = BasicShapes.squareStar.chunked(2) - .map { Vec2(it[0] * radius, it[1] * radius) } - .toTypedArray() - shapeBox.set(vertices, vertices.size) - + val fullShape = BasicShapes.polygon4Spiked.chunked(2) val bodyDef = createBodyDef(BodyType.DYNAMIC, x, y, h, dx, dy, dh) - val worldBody = createWorldBody(shapeBox, mass, radius, friction, restitution, world, bodyDef) - - - - val shapeBox2 = PolygonShape() - val vertices2 = listOf( - Vec2(0f, 1f), - Vec2(-.5f, 0f), - Vec2(0f, -1f), - Vec2(.5f, 0f) - ) - .toTypedArray() - shapeBox2.set(vertices2, vertices2.size) - val fixtureDef = FixtureDef().let { - it.shape = shapeBox2 - it - } - worldBody.createFixture(fixtureDef) - - val shapeBox3 = PolygonShape() - val vertices3 = listOf( - Vec2(1f, 0f), - Vec2(0f, -.5f), - Vec2(-1f, 0f), - Vec2(0f, .5f) - ) - .toTypedArray() - shapeBox3.set(vertices3, vertices3.size) - val fixtureDef2 = FixtureDef().let { - it.shape = shapeBox3 - it - } - worldBody.createFixture(fixtureDef2) + val worldBody = world.createBody(bodyDef) + (listOf(fullShape.last()) + fullShape + listOf(fullShape.first())) + .map { listOf(it[0] * radius, it[1] * radius) } + .windowed(3, 2) + .map { (a, b, c) -> + val shapeBox = PolygonShape() + val vertices = listOf( + makeVec2(a), + makeVec2(b), + makeVec2(c), + Vec2() + ) + .toTypedArray() + shapeBox.set(vertices, vertices.size) + FixtureDef().let { + it.shape = shapeBox + it.density = mass / (PI.toFloat() * radius.pow(2f) * (fullShape.size * .5f)) + it.friction = friction + it.restitution = restitution + it + } + } + .forEach { worldBody.createFixture(it) } - textureConfig.chunkedVertices = - shapeBox.vertices.map { listOf(it.x / radius, it.y / radius) } + textureConfig.chunkedVertices = listOf(listOf(0f, 0f)) + fullShape + listOf(fullShape.first()) - return Vehicle(player.name, Motion(), shapeBox, worldBody, radius, textureConfig) + return Vehicle(player.name, Motion(), worldBody, radius, textureConfig) .let { player.vehicle = it it diff --git a/src/main/kotlin/engine/freeBody/Warhead.kt b/src/main/kotlin/engine/freeBody/Warhead.kt index bac7c2f..f5a325a 100644 --- a/src/main/kotlin/engine/freeBody/Warhead.kt +++ b/src/main/kotlin/engine/freeBody/Warhead.kt @@ -3,11 +3,11 @@ package engine.freeBody import display.draw.TextureConfig import display.draw.TextureEnum import display.graphic.BasicShapes +import engine.FreeBodyCallback import engine.motion.Motion import game.GamePlayer import org.jbox2d.collision.shapes.CircleShape import org.jbox2d.collision.shapes.PolygonShape -import org.jbox2d.collision.shapes.Shape import org.jbox2d.common.Vec2 import org.jbox2d.dynamics.Body import org.jbox2d.dynamics.BodyType @@ -15,13 +15,12 @@ import org.jbox2d.dynamics.World class Warhead( id: String, - firedBy: GamePlayer, + val firedBy: GamePlayer, motion: Motion, - shapeBox: Shape, worldBody: Body, radius: Float, textureConfig: TextureConfig -) : FreeBody(id, motion, shapeBox, worldBody, radius, textureConfig) { +) : FreeBody(id, motion, worldBody, radius, textureConfig) { private val currentTime get() = System.currentTimeMillis() @@ -31,6 +30,7 @@ class Warhead( private val createdAt = currentTime val selfDestructTime = 45000f + // TODO: player in current aiming phase could just wait out this time if they wanted to val damage = 100f @@ -72,7 +72,8 @@ class Warhead( radius: Float = .7F, restitution: Float = .3f, friction: Float = .6f, - textureConfig: TextureConfig + textureConfig: TextureConfig, + onWarheadCollision: (FreeBody, Body) -> Unit ): Warhead { val shapeBox = PolygonShape() val vertices = BasicShapes.polygon4.chunked(2) @@ -82,10 +83,12 @@ class Warhead( val bodyDef = createBodyDef(BodyType.DYNAMIC, x, y, h, dx, dy, dh) val worldBody = createWorldBody(shapeBox, mass, radius, friction, restitution, world, bodyDef) + worldBody.isBullet = true + textureConfig.chunkedVertices = shapeBox.vertices.map { listOf(it.x / radius, it.y / radius) } - return Warhead("1", firedBy, Motion(), shapeBox, worldBody, radius, textureConfig) + return Warhead("1", firedBy, Motion(), worldBody, radius, textureConfig) .let { // it.textureConfig.updateGpuBufferData() it.textureConfig.gpuBufferData = it.textureConfig.chunkedVertices.flatMap { @@ -99,6 +102,7 @@ class Warhead( }.toFloatArray() firedBy.warheads.add(it) + worldBody.userData = FreeBodyCallback(it, onWarheadCollision) it } } diff --git a/src/main/kotlin/game/GamePhaseHandler.kt b/src/main/kotlin/game/GamePhaseHandler.kt index 96a4f2f..c2c7975 100644 --- a/src/main/kotlin/game/GamePhaseHandler.kt +++ b/src/main/kotlin/game/GamePhaseHandler.kt @@ -8,9 +8,9 @@ import display.events.MouseButtonEvent import display.graphic.Color import display.gui.GuiController import engine.GameState +import engine.GameState.Companion.getContactBodies import engine.motion.Director import engine.shields.VehicleShield -import kotlinx.coroutines.yield import org.jbox2d.common.Vec2 import org.jbox2d.dynamics.Body import org.jbox2d.dynamics.contacts.ContactEdge @@ -167,17 +167,6 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer) { ) else -> gameState.tickClock(timeStep, velocityIterations, positionIterations) } - - } - - private fun getContactBodies(contactEdge: ContactEdge): Sequence = sequence { - var currentContact = contactEdge - yield(currentContact.other) - - while (currentContact.next != null) { - yield(currentContact.other) - currentContact = currentContact.next - } } private fun handleIntro() { @@ -373,7 +362,7 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer) { } private fun getScreenLocation(location: Vec2): Vec2 = - location.add(Vec2(-camera.windowWidth * .5f, -camera.windowHeight * .5f)) + location.add(Vec2(-camera.windowWidth, -camera.windowHeight).mul(.5f)) .let { it.y *= -1f it @@ -435,7 +424,7 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer) { private const val pauseTime = 1000f private const val introDuration = 3500f private const val introStartSlowdown = 2000f - private const val maxTurnDuration = 30000f + private const val maxTurnDuration = 20000f private const val quickStartTime = 300f private const val outroDuration = 5000f diff --git a/src/main/kotlin/game/GamePlayer.kt b/src/main/kotlin/game/GamePlayer.kt index 15c4e08..f346bef 100644 --- a/src/main/kotlin/game/GamePlayer.kt +++ b/src/main/kotlin/game/GamePlayer.kt @@ -2,14 +2,39 @@ package game import engine.freeBody.Vehicle import engine.freeBody.Warhead +import utility.Common.getTimingFunctionEaseIn class GamePlayer( val name: String, val type: GamePlayerTypes = GamePlayerTypes.HUMAN, var vehicle: Vehicle? = null, - val playerAim: PlayerAim = PlayerAim() + val playerAim: PlayerAim = PlayerAim(), + var score: Float = 0f ) { + fun scoreDamage(warhead: Warhead, totalDamage: Float, vehicle: Vehicle) { + val selfHarm = when (vehicle) { + this.vehicle -> -.5f + else -> 1f + } + val noAgeBonusTime = 2000f + val age = warhead.ageTime + .minus(noAgeBonusTime) + .coerceAtLeast(0f) + .div(warhead.selfDestructTime - noAgeBonusTime) + .let { getTimingFunctionEaseIn(it) * 5 + 1f } + + score += selfHarm * totalDamage * age + } + + fun scoreKill(vehicle: Vehicle) { + val selfHarm = when (vehicle) { + this.vehicle -> -.5f + else -> 1f + } + score += selfHarm * 2f + } + val warheads = mutableListOf() } diff --git a/src/main/kotlin/game/MapGenerator.kt b/src/main/kotlin/game/MapGenerator.kt index 599ab34..5607c8f 100644 --- a/src/main/kotlin/game/MapGenerator.kt +++ b/src/main/kotlin/game/MapGenerator.kt @@ -3,7 +3,6 @@ package game import Vector2f import display.draw.TextureConfig import display.draw.TextureEnum -import display.draw.TextureHolder import display.graphic.BasicShapes import engine.GameState import engine.freeBody.Planet @@ -28,7 +27,7 @@ object MapGenerator { val vehicles = gameState.gamePlayers.withIndex().map { (index, player) -> Vehicle.create( - gameState.world, player, -10f * index + .5f * gameState.gamePlayers.size, + gameState.world, player,-10f * index + .5f * gameState.gamePlayers.size, 10f, index * 1f, 0f, 0f, 1f, 3f, radius = .75f, textureConfig = TextureConfig(TextureEnum.metal, Vector2f(.7f, .7f), Vector2f(0f, 0f)) ) diff --git a/src/main/kotlin/utility/Common.kt b/src/main/kotlin/utility/Common.kt index 7190d78..f47b13d 100644 --- a/src/main/kotlin/utility/Common.kt +++ b/src/main/kotlin/utility/Common.kt @@ -1,7 +1,9 @@ package utility +import org.jbox2d.common.MathUtils import org.jbox2d.common.Vec2 import java.io.File +import java.nio.DoubleBuffer import java.util.* import kotlin.math.* @@ -51,6 +53,16 @@ object Common { val vectorUnit = Vec2(1f, 1f) + fun makeVec2(list: List): Vec2 = Vec2(list[0], list[1]) + + fun makeVec2(x: Number, y: Number): Vec2 = Vec2(x.toFloat(), y.toFloat()) + + fun makeVec2(x: DoubleBuffer, y: DoubleBuffer): Vec2 = makeVec2(x.get(), y.get()) + + fun makeVec2(duplicate: Number): Vec2 = makeVec2(duplicate, duplicate) + + fun makeVec2Circle(angle: Float): Vec2 = Vec2(cos(angle), sin(angle)) + val radianToDegree = Math.toDegrees(1.0).toFloat() fun getTimingFunctionEaseOut(interpolateStep: Float) = getTimingFunctionFullSine(sqrt(interpolateStep)) diff --git a/src/test/kotlin/engine/motion/DirectorTest.kt b/src/test/kotlin/engine/motion/DirectorTest.kt index be0c1ed..252bdf4 100644 --- a/src/test/kotlin/engine/motion/DirectorTest.kt +++ b/src/test/kotlin/engine/motion/DirectorTest.kt @@ -3,6 +3,7 @@ package engine.motion import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import utility.Common +import utility.Common.roundFloat import kotlin.math.PI internal class DirectorTest { @@ -20,31 +21,35 @@ internal class DirectorTest { fun direction() { // Test direction from client, to server val pointed0 = Director.getDirection(0f, 0f, -1f, 0f) - assertEquals(0f, pointed0, "0") + assertRoundEquals(0, pointed0, "0") val pointed45 = Director.getDirection(0f, 0f, -1f, -1f) - assertEquals(PI / 4, pointed45, "45") + assertRoundEquals(PI / 4, pointed45, "45") val pointed90 = Director.getDirection(0f, 0f, 0f, -1f) - assertEquals(PI / 2, pointed90, "90") + assertRoundEquals(PI / 2, pointed90, "90") val pointed135 = Director.getDirection(0f, 0f, 1f, -1f) - assertEquals(PI * 3 / 4, pointed135, "135") + assertRoundEquals(PI * 3 / 4, pointed135, "135") val pointed180 = Director.getDirection(0f, 0f, 1f, 0f) - assertEquals(PI, pointed180, "180") + assertRoundEquals(PI, pointed180, "180") val pointed225 = Director.getDirection(0f, 0f, 1f, 1f) - assertEquals(-PI * 3 / 4, pointed225, "225") + assertRoundEquals(-PI * 3 / 4, pointed225, "225") val pointed270 = Director.getDirection(0f, 0f, 0f, 1f) - assertEquals(-PI / 2, pointed270, "270") + assertRoundEquals(-PI / 2, pointed270, "270") val pointed315 = Director.getDirection(0f, 0f, -1f, 1f) - assertEquals(-PI / 4, pointed315, "315") + assertRoundEquals(-PI / 4, pointed315, "315") val vector = Director.getDirection(1f, 0f) - assertEquals(0f, vector, "vector") + assertRoundEquals(0, vector, "vector") + } + + private fun assertRoundEquals(expected: Number, actual: Float, message: String) { + assertEquals(roundFloat(expected.toFloat(), 2), roundFloat(actual, 2), message) } } diff --git a/src/test/kotlin/engine/physics/GravityTest.kt b/src/test/kotlin/engine/physics/GravityTest.kt index b51d40a..7f1a128 100644 --- a/src/test/kotlin/engine/physics/GravityTest.kt +++ b/src/test/kotlin/engine/physics/GravityTest.kt @@ -1,6 +1,7 @@ package engine.physics import display.draw.TextureConfig +import display.draw.TextureEnum import display.graphic.Texture import engine.freeBody.Planet import org.jbox2d.common.Vec2 @@ -29,17 +30,17 @@ internal class GravityTest { assertTrue(forceUpLeft.y > 0) } - @Test - fun in_binary_system_the_massive_body_moves_less() { - } +// @Test +// fun in_binary_system_the_massive_body_moves_less() { +// } private fun getGravityForceBetweenPlanetSatellite(sx: Float = 0f, sy: Float = 0f): Vec2 { val world = World(Vec2(0f, 0f)) val terra = Planet.create(world, "terra", sx, sy, 0f, 0f, 0f, 0f, 100f, 10f, - textureConfig = TextureConfig(Texture())) + textureConfig = TextureConfig(TextureEnum.white_pixel)) val luna = Planet.create(world, "luna", 0f, 0f, 0f, 0f, 0f, 0f, 100f, 10f, - textureConfig = TextureConfig(Texture())) + textureConfig = TextureConfig(TextureEnum.white_pixel)) return Gravity.gravitationalForce(luna, terra) } From 0b8040fa1d087c70283aea13f9bdef29b4d7b2a4 Mon Sep 17 00:00:00 2001 From: Pierre Blaarkies Roux Date: Sat, 11 Apr 2020 19:13:08 +0200 Subject: [PATCH 07/11] Add GuiInput element --- src/main/kotlin/display/Window.kt | 15 ++- src/main/kotlin/display/draw/Drawer.kt | 41 +++--- .../kotlin/display/graphic/BasicShapes.kt | 2 + src/main/kotlin/display/graphic/Renderer.kt | 10 +- src/main/kotlin/display/gui/GuiButton.kt | 2 +- src/main/kotlin/display/gui/GuiController.kt | 21 +++- src/main/kotlin/display/gui/GuiElement.kt | 6 +- .../kotlin/display/gui/GuiElementPhases.kt | 3 +- src/main/kotlin/display/gui/GuiInput.kt | 118 ++++++++++++++++++ src/main/kotlin/display/gui/GuiWindow.kt | 2 + src/main/kotlin/display/text/Font.kt | 25 +++- src/main/kotlin/display/text/TextJustify.kt | 7 ++ src/main/kotlin/engine/GameState.kt | 5 +- src/main/kotlin/engine/freeBody/FreeBody.kt | 12 +- src/main/kotlin/engine/freeBody/Vehicle.kt | 8 +- src/main/kotlin/engine/freeBody/Warhead.kt | 9 +- src/main/kotlin/game/GamePhaseHandler.kt | 36 ++++-- src/main/kotlin/game/GamePlayer.kt | 2 +- src/main/kotlin/input/InputHandler.kt | 10 +- src/main/kotlin/utility/Common.kt | 2 + src/test/kotlin/engine/motion/DirectorTest.kt | 4 +- src/test/kotlin/engine/physics/GravityTest.kt | 2 +- 22 files changed, 259 insertions(+), 83 deletions(-) create mode 100644 src/main/kotlin/display/gui/GuiInput.kt create mode 100644 src/main/kotlin/display/text/TextJustify.kt diff --git a/src/main/kotlin/display/Window.kt b/src/main/kotlin/display/Window.kt index 78ad21e..393c9d1 100644 --- a/src/main/kotlin/display/Window.kt +++ b/src/main/kotlin/display/Window.kt @@ -5,6 +5,8 @@ import io.reactivex.subjects.PublishSubject import org.jbox2d.common.Vec2 import org.lwjgl.BufferUtils import org.lwjgl.glfw.GLFW +import org.lwjgl.glfw.GLFW.glfwGetKey +import org.lwjgl.glfw.GLFW.glfwGetKeyName import org.lwjgl.glfw.GLFWErrorCallback import org.lwjgl.opengl.GL import org.lwjgl.opengl.GL11 @@ -24,6 +26,7 @@ class Window(private val title: String, var width: Int, var height: Int, private val mouseButtonEvent = PublishSubject.create() val cursorPositionEvent = PublishSubject.create() val mouseScrollEvent = PublishSubject.create() + val textInputEvent = PublishSubject.create() fun init() { // Setup an error callback. The default implementation @@ -69,7 +72,7 @@ class Window(private val title: String, var width: Int, var height: Int, private } private fun setupInputCallbacks() { - GLFW.glfwSetKeyCallback(windowHandle) { window, key, scancode, action, mods -> + GLFW.glfwSetKeyCallback(windowHandle) { _, key, scancode, action, mods -> if (action == GLFW.GLFW_PRESS) { when { key == GLFW.GLFW_KEY_F12 -> { @@ -84,20 +87,22 @@ class Window(private val title: String, var width: Int, var height: Int, private } }?.let { callbacks.add(it) } - GLFW.glfwSetMouseButtonCallback(windowHandle) { window, button, action, mods -> + GLFW.glfwSetMouseButtonCallback(windowHandle) { _, button, action, mods -> mouseButtonEvent.onNext(MouseButtonEvent(button, action, mods, getCursorPosition())) }?.let { callbacks.add(it) } - GLFW.glfwSetCursorPosCallback(windowHandle) { window, xPos, yPos -> + GLFW.glfwSetCursorPosCallback(windowHandle) { _, xPos, yPos -> cursorPositionEvent.onNext(makeVec2(xPos, yPos)) }?.let { callbacks.add(it) } - GLFW.glfwSetScrollCallback(windowHandle) { window, xOffset, yOffset -> + GLFW.glfwSetScrollCallback(windowHandle) { _, xOffset, yOffset -> mouseScrollEvent.onNext(makeVec2(xOffset, yOffset)) }?.let { callbacks.add(it) } + GLFW.glfwSetCharCallback(windowHandle) { _, codepoint -> + textInputEvent.onNext(Character.toChars(codepoint)[0].toString()) + }?.let { callbacks.add(it) } // glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); - // glfwSetCharCallback(window, character_callback); } fun setClearColor(r: Float, g: Float, b: Float, alpha: Float) { diff --git a/src/main/kotlin/display/draw/Drawer.kt b/src/main/kotlin/display/draw/Drawer.kt index 3c601da..d4661b5 100644 --- a/src/main/kotlin/display/draw/Drawer.kt +++ b/src/main/kotlin/display/draw/Drawer.kt @@ -3,18 +3,17 @@ package display.draw import display.graphic.BasicShapes import display.graphic.Color import display.graphic.Renderer +import display.text.TextJustify import engine.freeBody.FreeBody import engine.freeBody.Particle import engine.physics.CellLocation import engine.physics.GravityCell import game.GamePlayer -import org.jbox2d.common.MathUtils.sin import org.jbox2d.common.Vec2 import utility.Common.makeVec2 import utility.Common.makeVec2Circle import utility.Common.vectorUnit import java.util.* -import kotlin.math.cos import kotlin.math.sqrt class Drawer(val renderer: Renderer) { @@ -26,29 +25,29 @@ class Drawer(val renderer: Renderer) { } fun drawDebugForces(freeBody: FreeBody) { - val x = freeBody.worldBody.position.x - val y = freeBody.worldBody.position.y - val accelerationX = freeBody.worldBody.m_force.x - val accelerationY = freeBody.worldBody.m_force.y - - val multiplier = 2000f - val linePoints = listOf( - x, - y, - x + accelerationX * multiplier, - y + accelerationY * multiplier - ) - val triangleStripPoints = BasicShapes.getLineTriangleStrip(linePoints, .2f) - val arrowHeadPoints = BasicShapes.getArrowHeadPoints(linePoints) - val data = getColoredData( - triangleStripPoints + arrowHeadPoints, - Color(0f, 1f, 1f, 1f), Color(0f, 1f, 1f, 0.0f) - ).toFloatArray() +// val x = freeBody.worldBody.position.x +// val y = freeBody.worldBody.position.y +// val accelerationX = freeBody.worldBody.m_force.x +// val accelerationY = freeBody.worldBody.m_force.y +// +// val multiplier = 2000f +// val linePoints = listOf( +// x, +// y, +// x + accelerationX * multiplier, +// y + accelerationY * multiplier +// ) +// val triangleStripPoints = BasicShapes.getLineTriangleStrip(linePoints, .2f) +// val arrowHeadPoints = BasicShapes.getArrowHeadPoints(linePoints) +// val data = getColoredData( +// triangleStripPoints + arrowHeadPoints, +// Color(0f, 1f, 1f, 1f), Color(0f, 1f, 1f, 0.0f) +// ).toFloatArray() textures.getTexture(TextureEnum.white_pixel).bind() // renderer.drawStrip(data) - renderer.drawText(freeBody.id, freeBody.worldBody.position, vectorUnit, Color.WHITE) + renderer.drawText(freeBody.id, freeBody.worldBody.position, vectorUnit, Color.WHITE, TextJustify.LEFT) } fun drawTrail(freeBody: FreeBody) { diff --git a/src/main/kotlin/display/graphic/BasicShapes.kt b/src/main/kotlin/display/graphic/BasicShapes.kt index 572b2f2..1f18aaa 100644 --- a/src/main/kotlin/display/graphic/BasicShapes.kt +++ b/src/main/kotlin/display/graphic/BasicShapes.kt @@ -25,6 +25,8 @@ object BasicShapes { val polygon4Spiked = getSpikedPolygon(8) + val verticalLine = listOf(0f, 1f, 0f, -1f) + private fun getPolygonVertices(corners: Int, rotate: Double = .25): List = (0 until corners).flatMap { val t = 2 * PI * (it / corners.toFloat()) + PI * rotate listOf(cos(t).toFloat(), sin(t).toFloat()) diff --git a/src/main/kotlin/display/graphic/Renderer.kt b/src/main/kotlin/display/graphic/Renderer.kt index 31257e2..2bae3f8 100644 --- a/src/main/kotlin/display/graphic/Renderer.kt +++ b/src/main/kotlin/display/graphic/Renderer.kt @@ -3,6 +3,7 @@ package display.graphic import Matrix4f import input.CameraView import display.text.Font +import display.text.TextJustify import org.jbox2d.common.Vec2 import org.lwjgl.opengl.GL11.* import org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER @@ -83,8 +84,13 @@ class Renderer { numVertices = 0 } - fun drawText(text: CharSequence, offset: Vec2, scale: Vec2, color: Color, useCamera: Boolean = true) = - font.drawText(this, text, offset, scale, color, useCamera) + fun drawText(text: CharSequence, + offset: Vec2, + scale: Vec2, + color: Color, + justify: TextJustify = TextJustify.LEFT, + useCamera: Boolean = true + ) = font.drawText(this, text, offset, scale, color, justify, useCamera) fun drawShape( data: FloatArray, diff --git a/src/main/kotlin/display/gui/GuiButton.kt b/src/main/kotlin/display/gui/GuiButton.kt index 98a2579..d0c2c67 100644 --- a/src/main/kotlin/display/gui/GuiButton.kt +++ b/src/main/kotlin/display/gui/GuiButton.kt @@ -47,7 +47,7 @@ class GuiButton( else -> drawer.renderer.drawStrip(buttonOutline, offset, useCamera = false) } - GuiElement.drawLabel(drawer, this) + drawLabel(drawer, this) super.render() } diff --git a/src/main/kotlin/display/gui/GuiController.kt b/src/main/kotlin/display/gui/GuiController.kt index 610b52b..ecfc35a 100644 --- a/src/main/kotlin/display/gui/GuiController.kt +++ b/src/main/kotlin/display/gui/GuiController.kt @@ -7,7 +7,7 @@ import org.jbox2d.common.Vec2 import utility.Common.roundFloat import kotlin.math.roundToInt -class GuiController(private val drawer: Drawer) { +class GuiController(private val drawer: Drawer, val setTextInputState: () -> Unit) { private val elements = mutableListOf() @@ -21,6 +21,12 @@ class GuiController(private val drawer: Drawer) { fun checkLeftClick(location: Vec2) = elements.toList().forEach { it.handleClick(location) } + fun checkAddTextInput(text: String) = elements.filterIsInstance().forEach { it.handleAddTextInput(text) } + + fun checkRemoveTextInput() = elements.filterIsInstance().forEach { it.handleRemoveTextInput() } + + fun stopTextInput() = elements.filterIsInstance().forEach { it.stopTextInput() } + fun createMainMenu( onClickNewGame: () -> Unit, onClickSettings: () -> Unit, @@ -92,8 +98,13 @@ class GuiController(private val drawer: Drawer) { draggable = false, childElements = addRemoveButtonsList.toMutableList() ) - val playerButtons = players - .map { GuiButton(drawer, scale = playerButtonSize, title = "P${it.name}", textSize = .3f) } + val playerButtons = players.withIndex() + .map { (index, player) -> + val playerName = if (player.name.length == 1) "" else player.name + GuiInput(drawer, scale = Vec2(75f, 50f), placeholder = "Player ${index + 1}", + onClick = setTextInputState, onChange = { text -> player.name = text }) + .setTextValue(playerName) + } .let { listOf(addRemoveContainer) + it } setElementsInColumns(playerButtons, 40f) when (indexOfPlayersButtons) { @@ -108,7 +119,7 @@ class GuiController(private val drawer: Drawer) { clear() val shieldPickerWindow = GuiWindow( - drawer, Vec2(200f, -200f), Vec2(150f, 150f), title = "Player ${player.name} to pick a shield", + drawer, Vec2(200f, -200f), Vec2(150f, 150f), title = "${player.name} to pick a shield", draggable = true ) shieldPickerWindow.addChild( @@ -126,7 +137,7 @@ class GuiController(private val drawer: Drawer) { clear() val commandPanelWindow = GuiWindow( drawer, Vec2(350f, -350f), Vec2(150f, 150f), - title = "Player ${player.name}", draggable = true + title = player.name, draggable = true ) commandPanelWindow.addChildren( listOf( diff --git a/src/main/kotlin/display/gui/GuiElement.kt b/src/main/kotlin/display/gui/GuiElement.kt index c778f3b..2fc6b2e 100644 --- a/src/main/kotlin/display/gui/GuiElement.kt +++ b/src/main/kotlin/display/gui/GuiElement.kt @@ -2,8 +2,8 @@ package display.gui import display.draw.Drawer import display.graphic.Color +import display.text.TextJustify import org.jbox2d.common.Vec2 -import utility.Common import utility.Common.vectorUnit open class GuiElement( @@ -48,8 +48,8 @@ open class GuiElement( fun drawLabel(drawer: Drawer, element: GuiElementInterface) { drawer.renderer.drawText( element.title, element.offset, - Common.vectorUnit.mul(element.textSize), - element.color, false + vectorUnit.mul(element.textSize), + element.color, TextJustify.CENTER, false ) } diff --git a/src/main/kotlin/display/gui/GuiElementPhases.kt b/src/main/kotlin/display/gui/GuiElementPhases.kt index d2cbae5..5cccbb9 100644 --- a/src/main/kotlin/display/gui/GuiElementPhases.kt +++ b/src/main/kotlin/display/gui/GuiElementPhases.kt @@ -3,6 +3,7 @@ package display.gui enum class GuiElementPhases { IDLE, HOVERED, - CLICKED + CLICKED, + INPUT } diff --git a/src/main/kotlin/display/gui/GuiInput.kt b/src/main/kotlin/display/gui/GuiInput.kt new file mode 100644 index 0000000..fdfa3f4 --- /dev/null +++ b/src/main/kotlin/display/gui/GuiInput.kt @@ -0,0 +1,118 @@ +package display.gui + +import display.draw.Drawer +import display.draw.TextureEnum +import display.graphic.BasicShapes +import display.graphic.Color +import display.text.TextJustify +import org.jbox2d.common.Vec2 +import utility.Common.makeVec2 +import utility.Common.vectorUnit +import utility.toList + +class GuiInput( + drawer: Drawer, + offset: Vec2 = Vec2(), + scale: Vec2 = Vec2(200f, 50f), + placeholder: String, + textSize: Float = .15f, + color: Color = Color.WHITE.setAlpha(.7f), + private val onClick: () -> Unit = {}, + updateCallback: (GuiElement) -> Unit = {}, + val onChange: (String) -> Unit +) : GuiElement(drawer, offset, scale, placeholder, textSize, color, updateCallback) { + + private val blinkRate = 400 + private var cursorLine: FloatArray + private var buttonOutline: FloatArray + private var buttonBackground: FloatArray + private var backgroundColor = color.setAlpha(.1f) + + var inputText = "" + private val paddedScale = Vec2(scale.x - 8f, 20f) + + init { + val verticalCursorLinePoint = BasicShapes.verticalLine.chunked(2) + .flatMap { + val location = makeVec2(it[0] * paddedScale.x, it[1] * paddedScale.y) + .also { vec -> vec.x -= paddedScale.x } + location.toList() + } + cursorLine = Drawer.getLine(verticalCursorLinePoint, color, startWidth = 1.2f) + + val linePoints = BasicShapes.square.chunked(2) + .flatMap { listOf(it[0] * scale.x, it[1] * scale.y) } + buttonOutline = Drawer.getLine(linePoints, color, startWidth = 1f, wrapAround = true) + buttonBackground = Drawer.getColoredData(linePoints, backgroundColor).toFloatArray() + + calculateElementRegion(this) + } + + override fun render() { + drawer.textures.getTexture(TextureEnum.white_pixel).bind() + + when (currentPhase) { + GuiElementPhases.HOVERED -> drawer.renderer.drawShape(buttonBackground, offset, useCamera = false) + GuiElementPhases.INPUT -> { + drawer.renderer.drawShape(buttonBackground, offset, useCamera = false) + + if (System.currentTimeMillis().rem(blinkRate * 2) < blinkRate) { + drawer.renderer.drawStrip(cursorLine, offset, useCamera = false) + } + } + } + + drawer.renderer.drawStrip(buttonOutline, offset, useCamera = false) + + val paddedOffset = offset.clone().also { it.x -= paddedScale.x } + when (inputText.length) { + 0 -> drawer.renderer.drawText(title, paddedOffset, vectorUnit.mul(textSize), + color.setAlpha(.4f), TextJustify.LEFT, false) + else -> drawer.renderer.drawText(inputText, paddedOffset, vectorUnit.mul(textSize), + color, TextJustify.LEFT, false) + } + super.render() + } + + override fun handleHover(location: Vec2) { + if (currentPhase == GuiElementPhases.INPUT) return + super.handleHover(location) + } + + override fun handleClick(location: Vec2) { + when { + isHover(location) -> { + currentPhase = GuiElementPhases.INPUT + onClick() + } + else -> currentPhase = GuiElementPhases.IDLE + } + } + + fun handleAddTextInput(text: String) { + if (currentPhase == GuiElementPhases.INPUT) { + inputText += text + onChange(inputText) + } + } + + fun handleRemoveTextInput() { + if (currentPhase == GuiElementPhases.INPUT) { + inputText = inputText.dropLast(1) + onChange(inputText) + } + } + + fun stopTextInput() { + if (currentPhase == GuiElementPhases.INPUT) { + currentPhase = GuiElementPhases.IDLE + } + } + + fun setTextValue(text: String): GuiInput { + inputText = text + onChange(inputText) + return this + } + +} diff --git a/src/main/kotlin/display/gui/GuiWindow.kt b/src/main/kotlin/display/gui/GuiWindow.kt index 9dfbc9a..321023c 100644 --- a/src/main/kotlin/display/gui/GuiWindow.kt +++ b/src/main/kotlin/display/gui/GuiWindow.kt @@ -4,6 +4,7 @@ import display.draw.Drawer import display.draw.TextureEnum import display.graphic.BasicShapes import display.graphic.Color +import display.text.TextJustify import org.jbox2d.common.Vec2 import utility.Common @@ -38,6 +39,7 @@ class GuiWindow( offset.add(Vec2(0f, scale.y - 25f)), Common.vectorUnit.mul(.15f), Color.WHITE, + TextJustify.LEFT, false ) diff --git a/src/main/kotlin/display/text/Font.kt b/src/main/kotlin/display/text/Font.kt index d04a297..167b7c4 100644 --- a/src/main/kotlin/display/text/Font.kt +++ b/src/main/kotlin/display/text/Font.kt @@ -13,6 +13,7 @@ import java.awt.geom.AffineTransform import java.awt.image.AffineTransformOp import java.awt.image.BufferedImage import java.io.InputStream +import java.lang.NullPointerException import kotlin.math.hypot import java.awt.Color as AwtColor @@ -184,10 +185,11 @@ class Font constructor(font: Font = Font(MONOSPACED, BOLD, 32), antiAlias: Boole offset: Vec2, scale: Vec2, color: Color, + justify: TextJustify, useCamera: Boolean ) { - drawLetters(fontBitMapShadow, offset, scale, renderer, text, Color.BLACK, useCamera) - drawLetters(fontBitMap, offset, scale, renderer, text, color, useCamera) + drawLetters(fontBitMapShadow, offset, scale, renderer, text, Color.BLACK, justify, useCamera) + drawLetters(fontBitMap, offset, scale, renderer, text, color, justify, useCamera) } private fun drawLetters( @@ -197,18 +199,29 @@ class Font constructor(font: Font = Font(MONOSPACED, BOLD, 32), antiAlias: Boole renderer: Renderer, text: CharSequence, color: Color, + justify: TextJustify, useCamera: Boolean ) { - val glyphs = text.map { glyphs[it]!! } - val centerText = Vec2(-getTextTotalWidth(glyphs, scale) * .75f, 0f) + val glyphs = text.mapNotNull { + try { + glyphs[it] + } catch (e: NullPointerException) { + throw Exception("Could not find text character in font", e) + } + } + val justifyment = when (justify) { + TextJustify.LEFT -> Vec2() + TextJustify.CENTER -> Vec2(-getTextTotalWidth(glyphs, scale), 0f) + TextJustify.RIGHT -> Vec2() + } - var x = -glyphs[0].width * scale.x + var x = 0f//glyphs[0].width * scale.x var y = 0f glyphs.forEach { x += it.width * scale.x drawTextPosition( - texture, renderer, Vec2(x, y).add(offset).add(centerText), + texture, renderer, Vec2(x, y).add(offset).add(justifyment), scale, it, color, useCamera ) x += it.width * scale.x diff --git a/src/main/kotlin/display/text/TextJustify.kt b/src/main/kotlin/display/text/TextJustify.kt new file mode 100644 index 0000000..f310af0 --- /dev/null +++ b/src/main/kotlin/display/text/TextJustify.kt @@ -0,0 +1,7 @@ +package display.text + +enum class TextJustify { + LEFT, + CENTER, + RIGHT +} diff --git a/src/main/kotlin/engine/GameState.kt b/src/main/kotlin/engine/GameState.kt index 828163c..2e1efc9 100644 --- a/src/main/kotlin/engine/GameState.kt +++ b/src/main/kotlin/engine/GameState.kt @@ -163,10 +163,7 @@ class GameState { warheadMass, warheadRadius, textureConfig = TextureConfig(TextureEnum.metal), onWarheadCollision = { self, body -> detonateWarhead(self as Warhead, body) } ) - .let { - warheads.add(it) - it - } + .also { warheads.add(it) } } diff --git a/src/main/kotlin/engine/freeBody/FreeBody.kt b/src/main/kotlin/engine/freeBody/FreeBody.kt index 997e3b0..1523ea3 100644 --- a/src/main/kotlin/engine/freeBody/FreeBody.kt +++ b/src/main/kotlin/engine/freeBody/FreeBody.kt @@ -27,18 +27,15 @@ open class FreeBody( world: World, bodyDef: BodyDef ): Body { - val fixtureDef = FixtureDef().let { + val fixtureDef = FixtureDef().also { it.shape = shapeBox it.density = mass / (PI.toFloat() * radius.pow(2f)) it.friction = friction it.restitution = restitution - it } - return world.createBody(bodyDef).let { - it.createFixture(fixtureDef) - it - } + return world.createBody(bodyDef) + .also { it.createFixture(fixtureDef) } } fun createBodyDef( @@ -46,13 +43,12 @@ open class FreeBody( x: Float, y: Float, h: Float, dx: Float, dy: Float, dh: Float ): BodyDef { - return BodyDef().let { + return BodyDef().also { it.type = bodyType it.position.set(x, y) it.angle = h it.linearVelocity = Vec2(dx, dy) it.angularVelocity = dh - it } } diff --git a/src/main/kotlin/engine/freeBody/Vehicle.kt b/src/main/kotlin/engine/freeBody/Vehicle.kt index 227175a..234ced1 100644 --- a/src/main/kotlin/engine/freeBody/Vehicle.kt +++ b/src/main/kotlin/engine/freeBody/Vehicle.kt @@ -57,12 +57,11 @@ class Vehicle( ) .toTypedArray() shapeBox.set(vertices, vertices.size) - FixtureDef().let { + FixtureDef().also { it.shape = shapeBox it.density = mass / (PI.toFloat() * radius.pow(2f) * (fullShape.size * .5f)) it.friction = friction it.restitution = restitution - it } } .forEach { worldBody.createFixture(it) } @@ -70,10 +69,7 @@ class Vehicle( textureConfig.chunkedVertices = listOf(listOf(0f, 0f)) + fullShape + listOf(fullShape.first()) return Vehicle(player.name, Motion(), worldBody, radius, textureConfig) - .let { - player.vehicle = it - it - } + .also { player.vehicle = it } } } diff --git a/src/main/kotlin/engine/freeBody/Warhead.kt b/src/main/kotlin/engine/freeBody/Warhead.kt index f5a325a..4fe6ca9 100644 --- a/src/main/kotlin/engine/freeBody/Warhead.kt +++ b/src/main/kotlin/engine/freeBody/Warhead.kt @@ -50,10 +50,8 @@ class Warhead( val textureConfig = TextureConfig(TextureEnum.white_pixel, chunkedVertices = BasicShapes.polygon30.chunked(2)) .updateGpuBufferData() - return Particle(id, worldBody, shapeBox.radius, textureConfig).let { - particles.add(it) - it - } + return Particle(id, worldBody, shapeBox.radius, textureConfig) + .also { particles.add(it) } } @@ -89,7 +87,7 @@ class Warhead( shapeBox.vertices.map { listOf(it.x / radius, it.y / radius) } return Warhead("1", firedBy, Motion(), worldBody, radius, textureConfig) - .let { + .also { // it.textureConfig.updateGpuBufferData() it.textureConfig.gpuBufferData = it.textureConfig.chunkedVertices.flatMap { val (x, y) = it @@ -103,7 +101,6 @@ class Warhead( firedBy.warheads.add(it) worldBody.userData = FreeBodyCallback(it, onWarheadCollision) - it } } diff --git a/src/main/kotlin/game/GamePhaseHandler.kt b/src/main/kotlin/game/GamePhaseHandler.kt index c2c7975..f837928 100644 --- a/src/main/kotlin/game/GamePhaseHandler.kt +++ b/src/main/kotlin/game/GamePhaseHandler.kt @@ -7,13 +7,12 @@ import display.draw.TextureEnum import display.events.MouseButtonEvent import display.graphic.Color import display.gui.GuiController +import display.text.TextJustify import engine.GameState import engine.GameState.Companion.getContactBodies import engine.motion.Director import engine.shields.VehicleShield import org.jbox2d.common.Vec2 -import org.jbox2d.dynamics.Body -import org.jbox2d.dynamics.contacts.ContactEdge import utility.Common.getTimingFunctionEaseIn import utility.Common.getTimingFunctionEaseOut import utility.Common.vectorUnit @@ -39,7 +38,8 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer) { private var lastPhaseTimestamp = currentTime - private val guiController = GuiController(drawer) + private val guiController = GuiController(drawer, setTextInputState = { textInputIsBusy = true }) + private var textInputIsBusy = false private lateinit var exitCall: () -> Unit fun init(window: Window) { @@ -134,13 +134,13 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer) { drawer.renderer.drawText( "Animating: ${isTransitioning.toString().padEnd(5, ' ')} ${currentPhase.name}", Vec2(120f - camera.windowWidth * .5f, -10f + camera.windowHeight * .5f), - vectorUnit.mul(0.1f), Color.GREEN, false + vectorUnit.mul(0.1f), Color.GREEN, TextJustify.LEFT, false ) drawer.renderer.drawText( "${elapsedTime.div(100f).roundToInt().div(10f)} seconds", Vec2(40f - camera.windowWidth * .5f, -30f + camera.windowHeight * .5f), - vectorUnit.mul(0.1f), Color.GREEN, false + vectorUnit.mul(0.1f), Color.GREEN, TextJustify.LEFT, false ) } @@ -341,7 +341,6 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer) { guiController.update() } } - } private fun getPlayerAndMouseLocations(location: Vec2): Triple { @@ -363,18 +362,32 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer) { private fun getScreenLocation(location: Vec2): Vec2 = location.add(Vec2(-camera.windowWidth, -camera.windowHeight).mul(.5f)) - .let { - it.y *= -1f - it - } + .also { it.y *= -1f } fun keyPressEscape(event: KeyboardEvent) { + if (textInputIsBusy) { + textInputIsBusy = false + guiController.stopTextInput() + return + } when (currentPhase) { GamePhases.MAIN_MENU -> exitCall() else -> setupMainMenu() } } + fun keyPressBackspace(event: KeyboardEvent) { + if (textInputIsBusy) { + guiController.checkRemoveTextInput() + } + } + + fun inputText(text: String) { + if (textInputIsBusy) { + guiController.checkAddTextInput(text) + } + } + private fun setupMainMenu() { currentPhase = GamePhases.MAIN_MENU gameState.reset() @@ -410,6 +423,9 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer) { } private fun setupStartGame() { + gameState.gamePlayers.withIndex() + .filter { (_, player) -> player.name.length <= 1 } + .forEach { (index, player) -> player.name = "Player ${index + 1}" } guiController.clear() startNewPhase(GamePhases.NEW_GAME_INTRO) diff --git a/src/main/kotlin/game/GamePlayer.kt b/src/main/kotlin/game/GamePlayer.kt index f346bef..16c2b66 100644 --- a/src/main/kotlin/game/GamePlayer.kt +++ b/src/main/kotlin/game/GamePlayer.kt @@ -5,7 +5,7 @@ import engine.freeBody.Warhead import utility.Common.getTimingFunctionEaseIn class GamePlayer( - val name: String, + var name: String, val type: GamePlayerTypes = GamePlayerTypes.HUMAN, var vehicle: Vehicle? = null, val playerAim: PlayerAim = PlayerAim(), diff --git a/src/main/kotlin/input/InputHandler.kt b/src/main/kotlin/input/InputHandler.kt index 12523f5..d58394b 100644 --- a/src/main/kotlin/input/InputHandler.kt +++ b/src/main/kotlin/input/InputHandler.kt @@ -18,6 +18,13 @@ class InputHandler(private val gamePhaseHandler: GamePhaseHandler) { setupKeyboard(window) setupMouseMove(window) setupMouseClicks(window) + setupTextInput(window) + } + + private fun setupTextInput(window: Window) { + window.textInputEvent.takeUntil(unsubscribe).subscribe { + gamePhaseHandler.inputText(it) + } } private fun setupMouseClicks(window: Window) { @@ -41,10 +48,11 @@ class InputHandler(private val gamePhaseHandler: GamePhaseHandler) { when (it.action) { GLFW.GLFW_PRESS -> { when (it.key) { - GLFW.GLFW_KEY_SPACE -> gamePhaseHandler.pauseGame(it) +// GLFW.GLFW_KEY_SPACE -> gamePhaseHandler.pauseGame(it) GLFW.GLFW_KEY_LEFT -> gamePhaseHandler.keyPressArrowLeft(it) GLFW.GLFW_KEY_RIGHT -> gamePhaseHandler.keyPressArrowRight(it) GLFW.GLFW_KEY_ESCAPE -> gamePhaseHandler.keyPressEscape(it) + GLFW.GLFW_KEY_BACKSPACE -> gamePhaseHandler.keyPressBackspace(it) } } } diff --git a/src/main/kotlin/utility/Common.kt b/src/main/kotlin/utility/Common.kt index f47b13d..47dc789 100644 --- a/src/main/kotlin/utility/Common.kt +++ b/src/main/kotlin/utility/Common.kt @@ -75,3 +75,5 @@ object Common { (1f / (1f + exp((-(interpolateStep - .5f) * 10f)) * centerGradient)) * 1.023f - 0.0022f } + +fun Vec2.toList(): List = listOf(this.x, this.y) diff --git a/src/test/kotlin/engine/motion/DirectorTest.kt b/src/test/kotlin/engine/motion/DirectorTest.kt index 252bdf4..bc797ff 100644 --- a/src/test/kotlin/engine/motion/DirectorTest.kt +++ b/src/test/kotlin/engine/motion/DirectorTest.kt @@ -11,10 +11,10 @@ internal class DirectorTest { @Test fun distance() { val rightPointed = Director.getDistance(0f, 0f, 1f, 1f) - assertEquals(1.414f, Common.roundFloat(rightPointed, 3)) + assertEquals(1.414f, roundFloat(rightPointed, 3)) val leftPointed = Director.getDistance(0f, 0f, -1f, -1f) - assertEquals(1.414f, Common.roundFloat(leftPointed, 3)) + assertEquals(1.414f, roundFloat(leftPointed, 3)) } @Test diff --git a/src/test/kotlin/engine/physics/GravityTest.kt b/src/test/kotlin/engine/physics/GravityTest.kt index 7f1a128..ad25995 100644 --- a/src/test/kotlin/engine/physics/GravityTest.kt +++ b/src/test/kotlin/engine/physics/GravityTest.kt @@ -35,7 +35,7 @@ internal class GravityTest { // } private fun getGravityForceBetweenPlanetSatellite(sx: Float = 0f, sy: Float = 0f): Vec2 { - val world = World(Vec2(0f, 0f)) + val world = World(Vec2()) val terra = Planet.create(world, "terra", sx, sy, 0f, 0f, 0f, 0f, 100f, 10f, textureConfig = TextureConfig(TextureEnum.white_pixel)) From 5b805f4eeed30a27d2c1cf8a2cff9e80eee06099 Mon Sep 17 00:00:00 2001 From: Pierre Blaarkies Roux Date: Mon, 13 Apr 2020 15:48:40 +0200 Subject: [PATCH 08/11] Make GuiPanels draggable --- src/main/kotlin/display/gui/GuiController.kt | 17 ++-- src/main/kotlin/display/gui/GuiElement.kt | 6 +- src/main/kotlin/display/gui/GuiInput.kt | 12 +-- .../display/gui/{GuiWindow.kt => GuiPanel.kt} | 13 +++- src/main/kotlin/game/GamePhaseHandler.kt | 78 ++++++++++++------- src/main/kotlin/input/InputHandler.kt | 64 ++++++++++----- 6 files changed, 123 insertions(+), 67 deletions(-) rename src/main/kotlin/display/gui/{GuiWindow.kt => GuiPanel.kt} (83%) diff --git a/src/main/kotlin/display/gui/GuiController.kt b/src/main/kotlin/display/gui/GuiController.kt index ecfc35a..4d5512b 100644 --- a/src/main/kotlin/display/gui/GuiController.kt +++ b/src/main/kotlin/display/gui/GuiController.kt @@ -7,7 +7,7 @@ import org.jbox2d.common.Vec2 import utility.Common.roundFloat import kotlin.math.roundToInt -class GuiController(private val drawer: Drawer, val setTextInputState: () -> Unit) { +class GuiController(private val drawer: Drawer) { private val elements = mutableListOf() @@ -21,12 +21,17 @@ class GuiController(private val drawer: Drawer, val setTextInputState: () -> Uni fun checkLeftClick(location: Vec2) = elements.toList().forEach { it.handleClick(location) } + fun checkLeftClickDrag(location: Vec2, movement: Vec2) = elements.filterIsInstance() + .forEach { it.handleDrag(location, movement) } + fun checkAddTextInput(text: String) = elements.filterIsInstance().forEach { it.handleAddTextInput(text) } fun checkRemoveTextInput() = elements.filterIsInstance().forEach { it.handleRemoveTextInput() } fun stopTextInput() = elements.filterIsInstance().forEach { it.stopTextInput() } + fun textInputIsBusy(): Boolean = elements.filterIsInstance().toList().any { it.textInputIsBusy } + fun createMainMenu( onClickNewGame: () -> Unit, onClickSettings: () -> Unit, @@ -93,7 +98,7 @@ class GuiController(private val drawer: Drawer, val setTextInputState: () -> Uni ) val addRemoveButtonsList = listOf(addPlayerButton, removePlayerButton) setElementsInRows(addRemoveButtonsList) - val addRemoveContainer = GuiWindow( + val addRemoveContainer = GuiPanel( drawer, scale = playerButtonSize, color = Color.TRANSPARENT, draggable = false, childElements = addRemoveButtonsList.toMutableList() ) @@ -102,7 +107,7 @@ class GuiController(private val drawer: Drawer, val setTextInputState: () -> Uni .map { (index, player) -> val playerName = if (player.name.length == 1) "" else player.name GuiInput(drawer, scale = Vec2(75f, 50f), placeholder = "Player ${index + 1}", - onClick = setTextInputState, onChange = { text -> player.name = text }) + onChange = { text -> player.name = text }) .setTextValue(playerName) } .let { listOf(addRemoveContainer) + it } @@ -118,7 +123,7 @@ class GuiController(private val drawer: Drawer, val setTextInputState: () -> Uni fun createPlayersPickShields(player: GamePlayer, onClickShield: (player: GamePlayer) -> Unit) { clear() val shieldPickerWindow = - GuiWindow( + GuiPanel( drawer, Vec2(200f, -200f), Vec2(150f, 150f), title = "${player.name} to pick a shield", draggable = true ) @@ -135,7 +140,7 @@ class GuiController(private val drawer: Drawer, val setTextInputState: () -> Uni onClickFire: (player: GamePlayer) -> Unit ) { clear() - val commandPanelWindow = GuiWindow( + val commandPanelWindow = GuiPanel( drawer, Vec2(350f, -350f), Vec2(150f, 150f), title = player.name, draggable = true ) @@ -165,7 +170,7 @@ class GuiController(private val drawer: Drawer, val setTextInputState: () -> Uni fun createRoundLeaderboard(players: MutableList, onClickNextRound: () -> Unit) { clear() - val leaderBoardWindow = GuiWindow(drawer, Vec2(), Vec2(200f, 300f), "Leaderboard", draggable = true) + val leaderBoardWindow = GuiPanel(drawer, Vec2(), Vec2(200f, 300f), "Leaderboard", draggable = true) val playerLines = players.sortedByDescending { it.score }.map { GuiLabel( drawer, diff --git a/src/main/kotlin/display/gui/GuiElement.kt b/src/main/kotlin/display/gui/GuiElement.kt index 2fc6b2e..a63219a 100644 --- a/src/main/kotlin/display/gui/GuiElement.kt +++ b/src/main/kotlin/display/gui/GuiElement.kt @@ -26,9 +26,9 @@ open class GuiElement( override fun update() = updateCallback(this) - override fun addOffset(newOffset: Vec2) = GuiElement.addOffset(this, newOffset) + override fun addOffset(newOffset: Vec2) = addOffset(this, newOffset) - override fun updateOffset(newOffset: Vec2) = GuiElement.updateOffset(this, newOffset) + override fun updateOffset(newOffset: Vec2) = updateOffset(this, newOffset) override fun handleHover(location: Vec2) = when { isHover(location) -> currentPhase = GuiElementPhases.HOVERED @@ -61,7 +61,7 @@ open class GuiElement( fun addOffset(element: GuiElement, newOffset: Vec2) = updateOffset(element, element.offset.add(newOffset)) fun updateOffset(element: GuiElement, newOffset: Vec2) { - element.offset = newOffset + element.offset.set(newOffset) calculateElementRegion(element) } diff --git a/src/main/kotlin/display/gui/GuiInput.kt b/src/main/kotlin/display/gui/GuiInput.kt index fdfa3f4..ee41c7d 100644 --- a/src/main/kotlin/display/gui/GuiInput.kt +++ b/src/main/kotlin/display/gui/GuiInput.kt @@ -28,8 +28,10 @@ class GuiInput( private var buttonBackground: FloatArray private var backgroundColor = color.setAlpha(.1f) - var inputText = "" + private var inputText = "" private val paddedScale = Vec2(scale.x - 8f, 20f) + val textInputIsBusy + get() = currentPhase == GuiElementPhases.INPUT init { val verticalCursorLinePoint = BasicShapes.verticalLine.chunked(2) @@ -75,7 +77,7 @@ class GuiInput( } override fun handleHover(location: Vec2) { - if (currentPhase == GuiElementPhases.INPUT) return + if (textInputIsBusy) return super.handleHover(location) } @@ -90,21 +92,21 @@ class GuiInput( } fun handleAddTextInput(text: String) { - if (currentPhase == GuiElementPhases.INPUT) { + if (textInputIsBusy) { inputText += text onChange(inputText) } } fun handleRemoveTextInput() { - if (currentPhase == GuiElementPhases.INPUT) { + if (textInputIsBusy) { inputText = inputText.dropLast(1) onChange(inputText) } } fun stopTextInput() { - if (currentPhase == GuiElementPhases.INPUT) { + if (textInputIsBusy) { currentPhase = GuiElementPhases.IDLE } } diff --git a/src/main/kotlin/display/gui/GuiWindow.kt b/src/main/kotlin/display/gui/GuiPanel.kt similarity index 83% rename from src/main/kotlin/display/gui/GuiWindow.kt rename to src/main/kotlin/display/gui/GuiPanel.kt index 321023c..5eb1578 100644 --- a/src/main/kotlin/display/gui/GuiWindow.kt +++ b/src/main/kotlin/display/gui/GuiPanel.kt @@ -8,7 +8,7 @@ import display.text.TextJustify import org.jbox2d.common.Vec2 import utility.Common -class GuiWindow( +class GuiPanel( drawer: Drawer, offset: Vec2 = Vec2(), scale: Vec2 = Vec2(100f, 100f), @@ -24,6 +24,7 @@ class GuiWindow( init { childElementOffsets.putAll(childElements.map { Pair(it, it.offset.clone()) }) + calculateElementRegion(this) } override fun render() { @@ -51,7 +52,7 @@ class GuiWindow( override fun update() = childElements.forEach { it.update() } override fun addOffset(newOffset: Vec2) { - GuiElement.addOffset(this, newOffset) + addOffset(this, newOffset) calculateNewOffsets() } @@ -59,7 +60,7 @@ class GuiWindow( override fun handleClick(location: Vec2) = childElements.forEach { it.handleClick(location) } - fun calculateNewOffsets() = childElements.forEach { it.updateOffset(childElementOffsets[it]!!.add(offset)) } + private fun calculateNewOffsets() = childElements.forEach { it.updateOffset(childElementOffsets[it]!!.add(offset)) } fun addChildren(elements: List) { childElements.addAll(elements) @@ -73,4 +74,10 @@ class GuiWindow( calculateNewOffsets() } + fun handleDrag(location: Vec2, movement: Vec2) { + if (draggable && isHover(location)) { // TODO: use custom isHover() to only allow a small region as drag handle + addOffset(movement) + } + } + } diff --git a/src/main/kotlin/game/GamePhaseHandler.kt b/src/main/kotlin/game/GamePhaseHandler.kt index f837928..4bed7bb 100644 --- a/src/main/kotlin/game/GamePhaseHandler.kt +++ b/src/main/kotlin/game/GamePhaseHandler.kt @@ -38,40 +38,31 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer) { private var lastPhaseTimestamp = currentTime - private val guiController = GuiController(drawer, setTextInputState = { textInputIsBusy = true }) - private var textInputIsBusy = false + private val guiController = GuiController(drawer) + private val textInputIsBusy + get() = guiController.textInputIsBusy() private lateinit var exitCall: () -> Unit fun init(window: Window) { exitCall = { window.exit() } + when (0) { + 0 -> setupMainMenu() + 1 -> setupMainMenuSelectPlayers() + 2 -> { - setupMainMenu() + currentPhase = GamePhases.PLAYERS_PICK_SHIELDS + isTransitioning = false + gameState.reset() + gameState.gamePlayers.addAll((1..3).map { GamePlayer(it.toString()) }) + MapGenerator.populateNewGameMap(gameState) -// currentPhase = GamePhases.PLAYERS_PICK_SHIELDS -// isTransitioning = false -// gameState.gamePlayers.addAll((1..3).map { GamePlayer(it.toString()) }) -// MapGenerator.populateNewGameMap(gameState) -// gameState.gamePlayers.forEach { it.vehicle?.shield = VehicleShield() } -// gameState.playerOnTurn = gameState.gamePlayers[0] -// -// setupNextPlayersTurn() - } - - fun dragMouseRightClick(movement: Vec2) { - camera.moveLocation(movement.mulLocal(-camera.z)) - } + gameState.gamePlayers.forEach { it.vehicle?.shield = VehicleShield() } + gameState.playerOnTurn = gameState.gamePlayers.first() - fun scrollCamera(movement: Float) { - camera.moveZoom(movement * -.001f) - } - - fun pauseGame(event: KeyboardEvent) { - currentPhase = when (currentPhase) { - GamePhases.PAUSE -> GamePhases.PLAY - GamePhases.PLAY -> GamePhases.PAUSE - else -> GamePhases.PAUSE + setupNextPlayersTurn() + } + else -> throw Throwable("Enter a debug step number to start game") } - startTransition() } private fun startTransition() { @@ -199,8 +190,9 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer) { return } - gameState.gamePlayers.map { "Player ${it.name} HP:${it.vehicle!!.hitPoints.toInt()}" } - .joinToString().let { println(it) } + gameState.gamePlayers + .joinToString { "${it.name} HP:${it.vehicle!!.hitPoints.toInt()}; " } + .also { println(it) } setNextPlayerOnTurn() setupPlayerCommandPanel() @@ -300,7 +292,28 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer) { } } - fun doubleLeftClick(location: Vec2, click: MouseButtonEvent) { + fun dragMouseRightClick(movement: Vec2) { + camera.moveLocation(movement.mulLocal(-camera.z)) + } + + fun dragMouseLeftClick(location: Vec2, movement: Vec2) { + guiController.checkLeftClickDrag(getScreenLocation(location), movement) + } + + fun scrollCamera(movement: Float) { + camera.moveZoom(movement * -.001f) + } + + fun pauseGame(event: KeyboardEvent) { + currentPhase = when (currentPhase) { + GamePhases.PAUSE -> GamePhases.PLAY + GamePhases.PLAY -> GamePhases.PAUSE + else -> GamePhases.PAUSE + } + startTransition() + } + + fun doubleLeftClick(location: Vec2) { val transformedLocation = getScreenLocation(location).mul(camera.z).add(camera.location) val clickedBody = gameState.gravityBodies.find { @@ -366,7 +379,6 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer) { fun keyPressEscape(event: KeyboardEvent) { if (textInputIsBusy) { - textInputIsBusy = false guiController.stopTextInput() return } @@ -382,6 +394,12 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer) { } } + fun keyPressEnter(event: KeyboardEvent) { + if (textInputIsBusy) { + guiController.stopTextInput() + } + } + fun inputText(text: String) { if (textInputIsBusy) { guiController.checkAddTextInput(text) diff --git a/src/main/kotlin/input/InputHandler.kt b/src/main/kotlin/input/InputHandler.kt index d58394b..16f6c58 100644 --- a/src/main/kotlin/input/InputHandler.kt +++ b/src/main/kotlin/input/InputHandler.kt @@ -12,7 +12,7 @@ class InputHandler(private val gamePhaseHandler: GamePhaseHandler) { private val unsubscribe = PublishSubject.create() fun init(window: Window) { - setupDragRightClick(window) + setupDragClick(window) setupDoubleLeftClick(window) setupMouseScroll(window) setupKeyboard(window) @@ -48,11 +48,12 @@ class InputHandler(private val gamePhaseHandler: GamePhaseHandler) { when (it.action) { GLFW.GLFW_PRESS -> { when (it.key) { -// GLFW.GLFW_KEY_SPACE -> gamePhaseHandler.pauseGame(it) + // GLFW.GLFW_KEY_SPACE -> gamePhaseHandler.pauseGame(it) GLFW.GLFW_KEY_LEFT -> gamePhaseHandler.keyPressArrowLeft(it) GLFW.GLFW_KEY_RIGHT -> gamePhaseHandler.keyPressArrowRight(it) GLFW.GLFW_KEY_ESCAPE -> gamePhaseHandler.keyPressEscape(it) GLFW.GLFW_KEY_BACKSPACE -> gamePhaseHandler.keyPressBackspace(it) + GLFW.GLFW_KEY_ENTER -> gamePhaseHandler.keyPressEnter(it) } } } @@ -64,12 +65,6 @@ class InputHandler(private val gamePhaseHandler: GamePhaseHandler) { .subscribe { gamePhaseHandler.scrollCamera(it.y) } } - private fun setupDragRightClick(window: Window) { - val mouseButtonRelease = PublishSubject.create() - window.mouseButtonEvent.takeUntil(unsubscribe) - .subscribe { click -> dragRightClick(click, window, mouseButtonRelease) } - } - private fun setupDoubleLeftClick(window: Window) { var lastLeftClickTimeStamp = System.currentTimeMillis() window.mouseButtonEvent @@ -81,27 +76,56 @@ class InputHandler(private val gamePhaseHandler: GamePhaseHandler) { } .filter { (isDoubleClick, _) -> isDoubleClick } .takeUntil(unsubscribe) - .subscribe { (_, click) -> gamePhaseHandler.doubleLeftClick(window.getCursorPosition(), click) } + .subscribe { (_, click) -> gamePhaseHandler.doubleLeftClick(window.getCursorPosition()) } } - private fun dragRightClick(click: MouseButtonEvent, window: Window, mouseButtonRelease: PublishSubject) { - if (click.button == GLFW.GLFW_MOUSE_BUTTON_RIGHT && click.action == GLFW.GLFW_PRESS) { + private fun setupDragClick(window: Window) { + val mouseButtonLeftRelease = PublishSubject.create() + val mouseButtonRightRelease = PublishSubject.create() + window.mouseButtonEvent.takeUntil(unsubscribe) + .subscribe { click -> dragClick(click, window, mouseButtonLeftRelease, mouseButtonRightRelease) } + } - var startLocation: Vec2? = null - window.cursorPositionEvent.takeUntil(mouseButtonRelease).subscribe { location -> - location.x *= -1f + private fun dragClick(click: MouseButtonEvent, + window: Window, + mouseButtonLeftRelease: PublishSubject, + mouseButtonRightRelease: PublishSubject + ) { + if (click.button == GLFW.GLFW_MOUSE_BUTTON_LEFT) { + when (click.action) { + GLFW.GLFW_PRESS -> { + window.cursorPositionEvent.takeUntil(mouseButtonLeftRelease) + .subscribe { + handleMouseMovement(click.location, it) { movement -> + gamePhaseHandler.dragMouseLeftClick(click.location, movement) + } + } + } + GLFW.GLFW_RELEASE -> mouseButtonLeftRelease.onNext(true) + } + } - if (startLocation != null) { - val movement = startLocation!!.add(location.mul(-1f)) - gamePhaseHandler.dragMouseRightClick(movement) + if (click.button == GLFW.GLFW_MOUSE_BUTTON_RIGHT) { + when (click.action) { + GLFW.GLFW_PRESS -> { + window.cursorPositionEvent.takeUntil(mouseButtonRightRelease) + .subscribe { + handleMouseMovement(click.location, it) { movement -> + gamePhaseHandler.dragMouseRightClick(movement) + } + } } - startLocation = location + GLFW.GLFW_RELEASE -> mouseButtonRightRelease.onNext(true) } - } else if (click.button == GLFW.GLFW_MOUSE_BUTTON_RIGHT && click.action == GLFW.GLFW_RELEASE) { - mouseButtonRelease.onNext(true) } } + private fun handleMouseMovement(startLocation: Vec2, location: Vec2, callback: (Vec2) -> Unit) { + val movement = startLocation.add(location.mul(-1f)).also { it.x *= -1f } + callback(movement) + startLocation.set(location) + } + fun dispose() { unsubscribe.onNext(true) } From 4773a0ee3e17309f0f8ee63c446e151af34b6c1f Mon Sep 17 00:00:00 2001 From: Pierre Blaarkies Roux Date: Mon, 13 Apr 2020 18:10:29 +0200 Subject: [PATCH 09/11] Add GuiScroll list --- src/main/kotlin/display/Window.kt | 5 +- .../kotlin/display/events/MouseScrollEvent.kt | 8 ++ src/main/kotlin/display/graphic/Renderer.kt | 45 ++++-- src/main/kotlin/display/graphic/SnipRegion.kt | 5 + src/main/kotlin/display/gui/GuiButton.kt | 18 ++- src/main/kotlin/display/gui/GuiController.kt | 106 ++++++++------ src/main/kotlin/display/gui/GuiElement.kt | 36 +++-- .../kotlin/display/gui/GuiElementInterface.kt | 10 +- src/main/kotlin/display/gui/GuiInput.kt | 18 +-- src/main/kotlin/display/gui/GuiLabel.kt | 10 +- src/main/kotlin/display/gui/GuiPanel.kt | 49 +++++-- src/main/kotlin/display/gui/GuiScroll.kt | 130 ++++++++++++++++++ src/main/kotlin/display/text/Font.kt | 22 +-- src/main/kotlin/game/GamePhaseHandler.kt | 17 ++- src/main/kotlin/input/CameraView.kt | 7 + src/main/kotlin/input/InputHandler.kt | 3 +- 16 files changed, 363 insertions(+), 126 deletions(-) create mode 100644 src/main/kotlin/display/events/MouseScrollEvent.kt create mode 100644 src/main/kotlin/display/graphic/SnipRegion.kt create mode 100644 src/main/kotlin/display/gui/GuiScroll.kt diff --git a/src/main/kotlin/display/Window.kt b/src/main/kotlin/display/Window.kt index 393c9d1..72275ab 100644 --- a/src/main/kotlin/display/Window.kt +++ b/src/main/kotlin/display/Window.kt @@ -1,6 +1,7 @@ package display import display.events.MouseButtonEvent +import display.events.MouseScrollEvent import io.reactivex.subjects.PublishSubject import org.jbox2d.common.Vec2 import org.lwjgl.BufferUtils @@ -25,7 +26,7 @@ class Window(private val title: String, var width: Int, var height: Int, private val keyboardEvent = PublishSubject.create() val mouseButtonEvent = PublishSubject.create() val cursorPositionEvent = PublishSubject.create() - val mouseScrollEvent = PublishSubject.create() + val mouseScrollEvent = PublishSubject.create() val textInputEvent = PublishSubject.create() fun init() { @@ -96,7 +97,7 @@ class Window(private val title: String, var width: Int, var height: Int, private }?.let { callbacks.add(it) } GLFW.glfwSetScrollCallback(windowHandle) { _, xOffset, yOffset -> - mouseScrollEvent.onNext(makeVec2(xOffset, yOffset)) + mouseScrollEvent.onNext( MouseScrollEvent(makeVec2(xOffset, yOffset), getCursorPosition()) ) }?.let { callbacks.add(it) } GLFW.glfwSetCharCallback(windowHandle) { _, codepoint -> diff --git a/src/main/kotlin/display/events/MouseScrollEvent.kt b/src/main/kotlin/display/events/MouseScrollEvent.kt new file mode 100644 index 0000000..f949e5e --- /dev/null +++ b/src/main/kotlin/display/events/MouseScrollEvent.kt @@ -0,0 +1,8 @@ +package display.events + +import org.jbox2d.common.Vec2 + +class MouseScrollEvent( + val movement: Vec2, + val location: Vec2 +) diff --git a/src/main/kotlin/display/graphic/Renderer.kt b/src/main/kotlin/display/graphic/Renderer.kt index 2bae3f8..8320a4c 100644 --- a/src/main/kotlin/display/graphic/Renderer.kt +++ b/src/main/kotlin/display/graphic/Renderer.kt @@ -1,9 +1,9 @@ package display.graphic import Matrix4f -import input.CameraView import display.text.Font import display.text.TextJustify +import input.CameraView import org.jbox2d.common.Vec2 import org.lwjgl.opengl.GL11.* import org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER @@ -89,25 +89,28 @@ class Renderer { scale: Vec2, color: Color, justify: TextJustify = TextJustify.LEFT, - useCamera: Boolean = true - ) = font.drawText(this, text, offset, scale, color, justify, useCamera) + useCamera: Boolean = true, + snipRegion: SnipRegion? = null + ) = font.drawText(this, text, offset, scale, color, justify, useCamera, snipRegion) fun drawShape( data: FloatArray, offset: Vec2 = Vec2(), h: Float = 0f, scale: Vec2 = vectorUnit, - useCamera: Boolean = true - ) = drawEntity(data, offset, h, scale, GL_TRIANGLE_FAN, useCamera) + useCamera: Boolean = true, + snipRegion: SnipRegion? = null + ) = drawEntity(data, offset, h, scale, GL_TRIANGLE_FAN, useCamera, snipRegion) fun drawStrip( data: FloatArray, offset: Vec2 = Vec2(), h: Float = 0f, scale: Vec2 = vectorUnit, - useCamera: Boolean = true + useCamera: Boolean = true, + snipRegion: SnipRegion? = null ) = - drawEntity(data, offset, h, scale, GL_TRIANGLE_STRIP, useCamera) + drawEntity(data, offset, h, scale, GL_TRIANGLE_STRIP, useCamera, snipRegion) private fun drawEntity( data: FloatArray, @@ -115,7 +118,8 @@ class Renderer { h: Float, scale: Vec2, drawType: Int, - useCamera: Boolean + useCamera: Boolean, + snipRegion: SnipRegion? ) { begin() if (vertices.remaining() < data.size) { @@ -126,7 +130,7 @@ class Renderer { vertices.put(data) numVertices += data.size / vertexDimensionCount - setUniformInputs(offset, 0f, h, scale, useCamera) + setUniformInputs(offset, 0f, h, scale, useCamera, snipRegion) end(drawType) } @@ -165,22 +169,34 @@ class Renderer { z: Float = 0f, h: Float = 0f, scale: Vec2 = vectorUnit, - useCamera: Boolean + useCamera: Boolean, + snipRegion: SnipRegion? ) { // val uniTex = program!!.getUniformLocation("texImage") // program!!.setUniform(uniTex, 0) + val gameCamera = cameraView.getRenderCamera() + val guiCamera = Matrix4f() + glDisable(GL_SCISSOR_TEST) + + val model = Matrix4f.translate(offset.x, offset.y, z) .multiply(Matrix4f.rotate(h * Common.radianToDegree, 0f, 0f, 1f)) .multiply(Matrix4f.scale(scale.x, scale.y, 1f)) val uniModel = program.getUniformLocation("model") program.setUniform(uniModel, model) - val zoomScale = 1f / cameraView.z val view = when (useCamera) { - true -> Matrix4f.scale(zoomScale, zoomScale, 1f) - .multiply(Matrix4f.translate(-cameraView.location.x, -cameraView.location.y, 0f)) - false -> Matrix4f() + true -> gameCamera + false -> { + if (snipRegion != null) { + glScissor(cameraView.windowWidth.div(2).toInt() + snipRegion.offset.x.toInt(), + cameraView.windowHeight.div(2).toInt() + snipRegion.offset.y.toInt(), + snipRegion.scale.x.toInt(), snipRegion.scale.y.toInt()) + glEnable(GL_SCISSOR_TEST) + } + guiCamera + } } val uniView = program.getUniformLocation("view") program.setUniform(uniView, view) @@ -211,3 +227,4 @@ class Renderer { program.pointVertexAttribute(texAttribute, 2, 9 * java.lang.Float.BYTES, 7 * java.lang.Float.BYTES) } } + diff --git a/src/main/kotlin/display/graphic/SnipRegion.kt b/src/main/kotlin/display/graphic/SnipRegion.kt new file mode 100644 index 0000000..77a7fb8 --- /dev/null +++ b/src/main/kotlin/display/graphic/SnipRegion.kt @@ -0,0 +1,5 @@ +package display.graphic + +import org.jbox2d.common.Vec2 + +class SnipRegion(val offset: Vec2, val scale: Vec2) diff --git a/src/main/kotlin/display/gui/GuiButton.kt b/src/main/kotlin/display/gui/GuiButton.kt index d0c2c67..4770f08 100644 --- a/src/main/kotlin/display/gui/GuiButton.kt +++ b/src/main/kotlin/display/gui/GuiButton.kt @@ -4,6 +4,8 @@ import display.draw.Drawer import display.draw.TextureEnum import display.graphic.BasicShapes import display.graphic.Color +import display.graphic.SnipRegion +import display.text.TextJustify import org.jbox2d.common.Vec2 class GuiButton( @@ -31,27 +33,29 @@ class GuiButton( calculateElementRegion(this) } - override fun render() { + override fun render(snipRegion: SnipRegion?) { drawer.textures.getTexture(TextureEnum.white_pixel).bind() when (currentPhase) { - GuiElementPhases.HOVERED -> drawer.renderer.drawShape(buttonBackground, offset, useCamera = false) + GuiElementPhases.HOVERED -> drawer.renderer.drawShape(buttonBackground, offset, useCamera = false, + snipRegion = snipRegion) } when (currentPhase) { GuiElementPhases.CLICKED -> drawer.renderer.drawStrip( buttonOutline, offset.add(Vec2(0f, -2f)), - useCamera = false + useCamera = false, + snipRegion = snipRegion ) - else -> drawer.renderer.drawStrip(buttonOutline, offset, useCamera = false) + else -> drawer.renderer.drawStrip(buttonOutline, offset, useCamera = false, snipRegion = snipRegion) } - drawLabel(drawer, this) - super.render() + super.render(snipRegion) + drawLabel(drawer, this, TextJustify.CENTER, snipRegion) } - override fun handleClick(location: Vec2) { + override fun handleLeftClick(location: Vec2) { when { isHover(location) -> { currentPhase = GuiElementPhases.CLICKED diff --git a/src/main/kotlin/display/gui/GuiController.kt b/src/main/kotlin/display/gui/GuiController.kt index 4d5512b..82ea3da 100644 --- a/src/main/kotlin/display/gui/GuiController.kt +++ b/src/main/kotlin/display/gui/GuiController.kt @@ -2,6 +2,7 @@ package display.gui import display.draw.Drawer import display.graphic.Color +import display.text.TextJustify import game.GamePlayer import org.jbox2d.common.Vec2 import utility.Common.roundFloat @@ -11,7 +12,7 @@ class GuiController(private val drawer: Drawer) { private val elements = mutableListOf() - fun render() = elements.forEach { it.render() } + fun render() = elements.forEach { it.render(null) } fun update() = elements.forEach { it.update() } @@ -19,10 +20,12 @@ class GuiController(private val drawer: Drawer) { fun checkHover(location: Vec2) = elements.forEach { it.handleHover(location) } - fun checkLeftClick(location: Vec2) = elements.toList().forEach { it.handleClick(location) } + fun checkLeftClick(location: Vec2) = elements.toList().forEach { it.handleLeftClick(location) } - fun checkLeftClickDrag(location: Vec2, movement: Vec2) = elements.filterIsInstance() - .forEach { it.handleDrag(location, movement) } + fun checkLeftClickDrag(location: Vec2, movement: Vec2) = + elements.forEach { it.handleLeftClickDrag(location, movement) } + + fun checkScroll(movement: Vec2, location: Vec2) = elements.forEach { it.handleScroll(location, movement) } fun checkAddTextInput(text: String) = elements.filterIsInstance().forEach { it.handleAddTextInput(text) } @@ -32,6 +35,8 @@ class GuiController(private val drawer: Drawer) { fun textInputIsBusy(): Boolean = elements.filterIsInstance().toList().any { it.textInputIsBusy } + fun locationIsGui(location: Vec2): Boolean = elements.any { it.isHover(location) } + fun createMainMenu( onClickNewGame: () -> Unit, onClickSettings: () -> Unit, @@ -48,7 +53,7 @@ class GuiController(private val drawer: Drawer) { setElementsInRows(menuButtons, 40f, false) menuButtons.forEach { it.addOffset(Vec2(0f, 150f)) } - elements.add(GuiLabel(drawer, Vec2(-10f, 250f), "Volynov", .6f)) + elements.add(GuiLabel(drawer, Vec2(-10f, 250f), TextJustify.CENTER, "Volynov", .6f)) elements.addAll(menuButtons) } @@ -60,7 +65,7 @@ class GuiController(private val drawer: Drawer) { playerList: MutableList ) { clear() - elements.add(GuiLabel(drawer, Vec2(-10f, 250f), "Select Players", .2f)) + elements.add(GuiLabel(drawer, Vec2(0f, 250f), TextJustify.CENTER, "Select Players", .2f)) updateMainMenuSelectPlayers(playerList, onAddPlayer, onRemovePlayer) @@ -122,15 +127,15 @@ class GuiController(private val drawer: Drawer) { fun createPlayersPickShields(player: GamePlayer, onClickShield: (player: GamePlayer) -> Unit) { clear() - val shieldPickerWindow = + val shieldPickerPanel = GuiPanel( drawer, Vec2(200f, -200f), Vec2(150f, 150f), title = "${player.name} to pick a shield", draggable = true ) - shieldPickerWindow.addChild( + shieldPickerPanel.addChild( GuiButton(drawer, scale = Vec2(100f, 25f), title = "Pick one", onClick = { onClickShield(player) }) ) - elements.add(shieldPickerWindow) + elements.add(shieldPickerPanel) } fun createPlayerCommandPanel( @@ -140,11 +145,17 @@ class GuiController(private val drawer: Drawer) { onClickFire: (player: GamePlayer) -> Unit ) { clear() - val commandPanelWindow = GuiPanel( + val commandPanel = GuiPanel( drawer, Vec2(350f, -350f), Vec2(150f, 150f), title = player.name, draggable = true ) - commandPanelWindow.addChildren( + val weaponsList = GuiScroll(drawer, Vec2(50f, -50f), Vec2(100f, 100f)).addChildren( + (1..30).map { + GuiButton(drawer, scale = Vec2(100f, 25f), title = "Boom number $it", textSize = .15f, + onClick = { println("clicked $it") }) + } + ) + commandPanel.addChildren( listOf( GuiButton(drawer, Vec2(-100f, 0f), Vec2(50f, 25f), title = "Aim", onClick = { onClickAim(player) }), @@ -153,13 +164,17 @@ class GuiController(private val drawer: Drawer) { GuiButton(drawer, Vec2(-100f, -100f), Vec2(50f, 25f), title = "Fire", onClick = { onClickFire(player) }), - GuiLabel(drawer, Vec2(0f, 90f), getPlayerAimAngleDisplay(player), .15f, + GuiLabel(drawer, Vec2(-150f, 90f), justify = TextJustify.LEFT, title = getPlayerAimAngleDisplay(player), + textSize = .15f, updateCallback = { it.title = getPlayerAimAngleDisplay(player) }), - GuiLabel(drawer, Vec2(0f, 70f), getPlayerAimPowerDisplay(player), .15f, - updateCallback = { it.title = getPlayerAimPowerDisplay(player) }) + GuiLabel(drawer, Vec2(-150f, 70f), justify = TextJustify.LEFT, title = getPlayerAimPowerDisplay(player), + textSize = .15f, + updateCallback = { it.title = getPlayerAimPowerDisplay(player) }), + + weaponsList ) ) - elements.add(commandPanelWindow) + elements.add(commandPanel) } private fun getPlayerAimPowerDisplay(player: GamePlayer): String = @@ -170,54 +185,59 @@ class GuiController(private val drawer: Drawer) { fun createRoundLeaderboard(players: MutableList, onClickNextRound: () -> Unit) { clear() - val leaderBoardWindow = GuiPanel(drawer, Vec2(), Vec2(200f, 300f), "Leaderboard", draggable = true) + val leaderBoardPanel = GuiPanel(drawer, Vec2(), Vec2(200f, 300f), "Leaderboard", draggable = false) val playerLines = players.sortedByDescending { it.score }.map { GuiLabel( drawer, + justify = TextJustify.LEFT, title = "${it.name.padEnd(10, ' ')}${it.score.roundToInt()}".padStart(10, ' '), textSize = .2f ) } setElementsInRows(playerLines, 10f) - leaderBoardWindow.addChildren(playerLines) - leaderBoardWindow.addChild( + leaderBoardPanel.addChildren(playerLines) + leaderBoardPanel.addChild( GuiButton( - drawer, Vec2(0f, -280f), Vec2(100f, 25f), "Next Round", + drawer, Vec2(0f, -280f), Vec2(100f, 25f), "Next Match", onClick = onClickNextRound ) ) - elements.add(leaderBoardWindow) + elements.add(leaderBoardPanel) } private fun displayNumber(value: Float, decimals: Int): String = roundFloat(value, decimals).toString() - private fun setElementsInColumns(elements: List, gap: Float = 0f, centered: Boolean = true) { - val totalWidth = elements.map { it.scale.x * 2f }.sum() + gap * (elements.size - 1) - val columnSize = totalWidth / elements.size + companion object { + + fun setElementsInColumns(elements: List, gap: Float = 0f, centered: Boolean = true) { + val totalWidth = elements.map { it.scale.x * 2f }.sum() + gap * (elements.size - 1) + val columnSize = totalWidth / elements.size - elements.withIndex() - .forEach { (index, element) -> - val newXOffset = when (centered) { - true -> columnSize * (index - (elements.size - 1) * .5f) - else -> columnSize * index + columnSize * .5f + elements.withIndex() + .forEach { (index, element) -> + val newXOffset = when (centered) { + true -> columnSize * (index - (elements.size - 1) * .5f) + else -> columnSize * index + columnSize * .5f + } + element.addOffset(Vec2(newXOffset, element.offset.y)) } - element.addOffset(Vec2(newXOffset, element.offset.y)) - } - } + } + + fun setElementsInRows(elements: List, gap: Float = 0f, centered: Boolean = true) { + val totalHeight = elements.map { it.scale.y * 2f }.sum() + gap * (elements.size - 1) + val rowSize = totalHeight / elements.size + + elements.withIndex() + .forEach { (index, element) -> + val newYOffset = when (centered) { + true -> rowSize * (index - (elements.size - 1) * .5f) + else -> rowSize * index + rowSize * .5f + } * -1f + element.addOffset(Vec2(element.offset.x, newYOffset)) + } + } - private fun setElementsInRows(elements: List, gap: Float = 0f, centered: Boolean = true) { - val totalHeight = elements.map { it.scale.y * 2f }.sum() + gap * (elements.size - 1) - val rowSize = totalHeight / elements.size - - elements.withIndex() - .forEach { (index, element) -> - val newYOffset = when (centered) { - true -> rowSize * (index - (elements.size - 1) * .5f) - else -> rowSize * index + rowSize * .5f - } * -1f - element.addOffset(Vec2(element.offset.x, newYOffset)) - } } } diff --git a/src/main/kotlin/display/gui/GuiElement.kt b/src/main/kotlin/display/gui/GuiElement.kt index a63219a..c8c2a39 100644 --- a/src/main/kotlin/display/gui/GuiElement.kt +++ b/src/main/kotlin/display/gui/GuiElement.kt @@ -2,6 +2,7 @@ package display.gui import display.draw.Drawer import display.graphic.Color +import display.graphic.SnipRegion import display.text.TextJustify import org.jbox2d.common.Vec2 import utility.Common.vectorUnit @@ -22,7 +23,7 @@ open class GuiElement( protected var topRight: Vec2 = Vec2() protected var bottomLeft: Vec2 = Vec2() - override fun render() = Unit + override fun render(snipRegion: SnipRegion?) = Unit override fun update() = updateCallback(this) @@ -35,23 +36,32 @@ open class GuiElement( else -> currentPhase = GuiElementPhases.IDLE } - override fun handleClick(location: Vec2) = Unit + override fun handleLeftClick(location: Vec2) = Unit - protected fun isHover(location: Vec2): Boolean = + override fun handleLeftClickDrag(location: Vec2, movement: Vec2) = Unit + + override fun handleScroll(location: Vec2, movement: Vec2) = Unit + + fun isHover(location: Vec2): Boolean = location.x > bottomLeft.x - && location.x < topRight.x - && location.y > bottomLeft.y - && location.y < topRight.y + && location.x < topRight.x + && location.y > bottomLeft.y + && location.y < topRight.y companion object { - fun drawLabel(drawer: Drawer, element: GuiElementInterface) { - drawer.renderer.drawText( - element.title, element.offset, - vectorUnit.mul(element.textSize), - element.color, TextJustify.CENTER, false - ) - } + fun drawLabel(drawer: Drawer, + element: GuiElementInterface, + justify: TextJustify = TextJustify.CENTER, + snipRegion: SnipRegion? + ) = drawer.renderer.drawText( + element.title, + element.offset, + vectorUnit.mul(element.textSize), + element.color, + justify, + false, + snipRegion) fun calculateElementRegion(element: GuiElement) { element.bottomLeft = element.offset.add(element.scale.negate()) diff --git a/src/main/kotlin/display/gui/GuiElementInterface.kt b/src/main/kotlin/display/gui/GuiElementInterface.kt index 6bf686e..f915b45 100644 --- a/src/main/kotlin/display/gui/GuiElementInterface.kt +++ b/src/main/kotlin/display/gui/GuiElementInterface.kt @@ -1,6 +1,7 @@ package display.gui import display.graphic.Color +import display.graphic.SnipRegion import org.jbox2d.common.Vec2 interface GuiElementInterface { @@ -12,12 +13,13 @@ interface GuiElementInterface { val color: Color val updateCallback: (GuiElement) -> Unit var id: GuiElementIdentifierType - fun render() + fun render(snipRegion: SnipRegion?) fun update() + fun handleHover(location: Vec2) + fun handleLeftClick(location: Vec2) + fun handleLeftClickDrag(location: Vec2, movement: Vec2) + fun handleScroll(location: Vec2, movement: Vec2) fun addOffset(newOffset: Vec2) fun updateOffset(newOffset: Vec2) - fun handleHover(location: Vec2) - fun handleClick(location: Vec2) - } diff --git a/src/main/kotlin/display/gui/GuiInput.kt b/src/main/kotlin/display/gui/GuiInput.kt index ee41c7d..43761d7 100644 --- a/src/main/kotlin/display/gui/GuiInput.kt +++ b/src/main/kotlin/display/gui/GuiInput.kt @@ -4,6 +4,7 @@ import display.draw.Drawer import display.draw.TextureEnum import display.graphic.BasicShapes import display.graphic.Color +import display.graphic.SnipRegion import display.text.TextJustify import org.jbox2d.common.Vec2 import utility.Common.makeVec2 @@ -50,16 +51,17 @@ class GuiInput( calculateElementRegion(this) } - override fun render() { + override fun render(snipRegion: SnipRegion?) { drawer.textures.getTexture(TextureEnum.white_pixel).bind() when (currentPhase) { - GuiElementPhases.HOVERED -> drawer.renderer.drawShape(buttonBackground, offset, useCamera = false) + GuiElementPhases.HOVERED -> drawer.renderer.drawShape(buttonBackground, offset, useCamera = false, + snipRegion = snipRegion) GuiElementPhases.INPUT -> { - drawer.renderer.drawShape(buttonBackground, offset, useCamera = false) + drawer.renderer.drawShape(buttonBackground, offset, useCamera = false, snipRegion = snipRegion) if (System.currentTimeMillis().rem(blinkRate * 2) < blinkRate) { - drawer.renderer.drawStrip(cursorLine, offset, useCamera = false) + drawer.renderer.drawStrip(cursorLine, offset, useCamera = false, snipRegion = snipRegion) } } } @@ -69,11 +71,11 @@ class GuiInput( val paddedOffset = offset.clone().also { it.x -= paddedScale.x } when (inputText.length) { 0 -> drawer.renderer.drawText(title, paddedOffset, vectorUnit.mul(textSize), - color.setAlpha(.4f), TextJustify.LEFT, false) + color.setAlpha(.4f), TextJustify.LEFT, false, snipRegion) else -> drawer.renderer.drawText(inputText, paddedOffset, vectorUnit.mul(textSize), - color, TextJustify.LEFT, false) + color, TextJustify.LEFT, false, snipRegion) } - super.render() + super.render(snipRegion) } override fun handleHover(location: Vec2) { @@ -81,7 +83,7 @@ class GuiInput( super.handleHover(location) } - override fun handleClick(location: Vec2) { + override fun handleLeftClick(location: Vec2) { when { isHover(location) -> { currentPhase = GuiElementPhases.INPUT diff --git a/src/main/kotlin/display/gui/GuiLabel.kt b/src/main/kotlin/display/gui/GuiLabel.kt index 0c33450..a3d5f7c 100644 --- a/src/main/kotlin/display/gui/GuiLabel.kt +++ b/src/main/kotlin/display/gui/GuiLabel.kt @@ -2,11 +2,14 @@ package display.gui import display.draw.Drawer import display.graphic.Color +import display.graphic.SnipRegion +import display.text.TextJustify import org.jbox2d.common.Vec2 class GuiLabel( drawer: Drawer, offset: Vec2 = Vec2(), + val justify: TextJustify = TextJustify.LEFT, title: String, textSize: Float = 0f, color: Color = Color.WHITE.setAlpha(.7f), @@ -21,10 +24,9 @@ class GuiLabel( updateCallback = updateCallback ) { - override fun render() { - super.render() - - GuiElement.drawLabel(drawer, this) + override fun render(snipRegion: SnipRegion?) { + super.render(snipRegion) + drawLabel(drawer, this, justify, snipRegion) } } diff --git a/src/main/kotlin/display/gui/GuiPanel.kt b/src/main/kotlin/display/gui/GuiPanel.kt index 5eb1578..1d68d64 100644 --- a/src/main/kotlin/display/gui/GuiPanel.kt +++ b/src/main/kotlin/display/gui/GuiPanel.kt @@ -4,6 +4,7 @@ import display.draw.Drawer import display.draw.TextureEnum import display.graphic.BasicShapes import display.graphic.Color +import display.graphic.SnipRegion import display.text.TextJustify import org.jbox2d.common.Vec2 import utility.Common @@ -12,7 +13,7 @@ class GuiPanel( drawer: Drawer, offset: Vec2 = Vec2(), scale: Vec2 = Vec2(100f, 100f), - title: String = " ", + title: String = "", textSize: Float = .3f, color: Color = Color.BLACK.setAlpha(.5f), private val childElements: MutableList = mutableListOf(), @@ -27,12 +28,12 @@ class GuiPanel( calculateElementRegion(this) } - override fun render() { + override fun render(snipRegion: SnipRegion?) { drawer.textures.getTexture(TextureEnum.white_pixel).bind() drawer.renderer.drawShape(BasicShapes.square .let { Drawer.getColoredData(it, color) } .toFloatArray(), - offset, 0f, scale, useCamera = false + offset, 0f, scale, useCamera = false, snipRegion = snipRegion ) drawer.renderer.drawText( @@ -41,12 +42,13 @@ class GuiPanel( Common.vectorUnit.mul(.15f), Color.WHITE, TextJustify.LEFT, - false + false, + snipRegion ) - childElements.forEach { it.render() } + childElements.forEach { it.render(snipRegion) } - super.render() + super.render(snipRegion) } override fun update() = childElements.forEach { it.update() } @@ -56,9 +58,34 @@ class GuiPanel( calculateNewOffsets() } - override fun handleHover(location: Vec2) = childElements.forEach { it.handleHover(location) } + override fun handleHover(location: Vec2) { + if (isHover(location)) { + super.handleHover(location) + childElements.forEach { it.handleHover(location) } + } + } + + override fun handleLeftClick(location: Vec2) { + if (isHover(location)) { + super.handleLeftClick(location) + childElements.forEach { it.handleLeftClick(location) } + } + } - override fun handleClick(location: Vec2) = childElements.forEach { it.handleClick(location) } + override fun handleLeftClickDrag(location: Vec2, movement: Vec2) { + if (draggable && isHover(location)) { // TODO: use custom isHover() to only allow a small region as drag handle + childElements.forEach { it.handleLeftClickDrag(location, movement) } + super.handleLeftClickDrag(location, movement) + addOffset(movement) + } + } + + override fun handleScroll(location: Vec2, movement: Vec2) { + if (isHover(location)) { + super.handleScroll(location, movement) + childElements.forEach { it.handleScroll(location, movement) } + } + } private fun calculateNewOffsets() = childElements.forEach { it.updateOffset(childElementOffsets[it]!!.add(offset)) } @@ -74,10 +101,4 @@ class GuiPanel( calculateNewOffsets() } - fun handleDrag(location: Vec2, movement: Vec2) { - if (draggable && isHover(location)) { // TODO: use custom isHover() to only allow a small region as drag handle - addOffset(movement) - } - } - } diff --git a/src/main/kotlin/display/gui/GuiScroll.kt b/src/main/kotlin/display/gui/GuiScroll.kt new file mode 100644 index 0000000..4811a7f --- /dev/null +++ b/src/main/kotlin/display/gui/GuiScroll.kt @@ -0,0 +1,130 @@ +package display.gui + +import display.draw.Drawer +import display.draw.TextureEnum +import display.graphic.BasicShapes +import display.graphic.Color +import display.graphic.SnipRegion +import display.gui.GuiController.Companion.setElementsInRows +import org.jbox2d.common.Vec2 + +class GuiScroll( + drawer: Drawer, + offset: Vec2 = Vec2(), + scale: Vec2 = Vec2(100f, 100f), + color: Color = Color.WHITE.setAlpha(.5f), + private val childElements: MutableList = mutableListOf() +) : GuiElement(drawer, offset, scale, "", 0f, color, {}) { + + private var scrollOutline: FloatArray + private val childElementOffsets = HashMap() + + private var scrollBarPosition = 0f + private var scrollBarMin: Float = 0f + private var scrollBarMax: Float = 0f + + init { + val linePoints = BasicShapes.square + .chunked(2) + .flatMap { listOf(it[0] * scale.x, it[1] * scale.y) } + scrollOutline = Drawer.getLine(linePoints, color, startWidth = 1f, wrapAround = true) + + childElementOffsets.putAll(childElements.map { Pair(it, it.offset.clone()) }) + calculateElementRegion(this) + calculateNewOffsets() + } + + override fun render(snipRegion: SnipRegion?) { + // TODO: handle nested snipRegions, if this element is inside parent scroll + drawer.textures.getTexture(TextureEnum.white_pixel).bind() + drawer.renderer.drawStrip(scrollOutline, offset, useCamera = false, snipRegion = snipRegion) + super.render(snipRegion) + + childElements.filter { + it.offset.add(it.scale.negate()).y < offset.add(scale).y + && it.offset.add(it.scale).y > offset.add(scale.negate()).y + } + .forEach { it.render(SnipRegion(offset.add(scale.negate()), scale.mul(2f))) } + } + + override fun update() = childElements.forEach { it.update() } + + override fun addOffset(newOffset: Vec2) { + addOffset(this, newOffset) + calculateNewOffsets() + } + + override fun updateOffset(newOffset: Vec2) { + super.updateOffset(newOffset) + calculateNewOffsets() + } + + override fun handleHover(location: Vec2) { + if (isHover(location)) { + super.handleHover(location) + childElements.forEach { it.handleHover(location) } + } + } + + override fun handleLeftClick(location: Vec2) { + if (isHover(location)) { + super.handleLeftClick(location) + childElements.forEach { it.handleLeftClick(location) } + } + } + + override fun handleLeftClickDrag(location: Vec2, movement: Vec2) { + if (isHover(location)) { + addScrollBarPosition(movement.y * -.1f) + calculateNewOffsets() + super.handleLeftClickDrag(location, movement) + childElements.forEach { it.handleLeftClickDrag(location, movement) } + } + } + + override fun handleScroll(location: Vec2, movement: Vec2) { + if (isHover(location)) { + addScrollBarPosition(movement.y) + calculateNewOffsets() + } + } + + private fun addScrollBarPosition(movement: Float) { + scrollBarPosition = (scrollBarPosition + movement * 10f).coerceIn(scrollBarMin, scrollBarMax) + } + + private fun updateScrollBarRange() { + scrollBarMin = childElements.minBy { it.offset.y } + .let { childElementOffsets[it]!!.y + scale.y * 2 - it!!.scale.y } + scrollBarMax = 0f + } + + private fun calculateNewOffsets() { + childElements.forEach { + it.updateOffset(childElementOffsets[it]!! + .add(offset) + .add(Vec2(0f, scale.y - scrollBarPosition))) + } + } + + fun addChildren(elements: List): GuiScroll { + childElements.addAll(elements) + setElementsInRows(childElements, centered = false) + + childElementOffsets.putAll(elements.map { Pair(it, it.offset.clone()) }) + calculateNewOffsets() + updateScrollBarRange() + return this + } + + fun addChild(element: GuiElement): GuiScroll { + childElements.add(element) + setElementsInRows(childElements) + + childElementOffsets[element] = element.offset.clone() + calculateNewOffsets() + updateScrollBarRange() + return this + } + +} diff --git a/src/main/kotlin/display/text/Font.kt b/src/main/kotlin/display/text/Font.kt index 167b7c4..b08bc29 100644 --- a/src/main/kotlin/display/text/Font.kt +++ b/src/main/kotlin/display/text/Font.kt @@ -1,9 +1,6 @@ package display.text -import display.graphic.BasicShapes -import display.graphic.Color -import display.graphic.Renderer -import display.graphic.Texture +import display.graphic.* import org.jbox2d.common.Vec2 import org.lwjgl.system.MemoryUtil import java.awt.Font @@ -186,10 +183,11 @@ class Font constructor(font: Font = Font(MONOSPACED, BOLD, 32), antiAlias: Boole scale: Vec2, color: Color, justify: TextJustify, - useCamera: Boolean + useCamera: Boolean, + snipRegion: SnipRegion? ) { - drawLetters(fontBitMapShadow, offset, scale, renderer, text, Color.BLACK, justify, useCamera) - drawLetters(fontBitMap, offset, scale, renderer, text, color, justify, useCamera) + drawLetters(fontBitMapShadow, offset, scale, renderer, text, Color.BLACK, justify, useCamera, snipRegion) + drawLetters(fontBitMap, offset, scale, renderer, text, color, justify, useCamera, snipRegion) } private fun drawLetters( @@ -200,7 +198,8 @@ class Font constructor(font: Font = Font(MONOSPACED, BOLD, 32), antiAlias: Boole text: CharSequence, color: Color, justify: TextJustify, - useCamera: Boolean + useCamera: Boolean, + snipRegion: SnipRegion? ) { val glyphs = text.mapNotNull { try { @@ -222,7 +221,7 @@ class Font constructor(font: Font = Font(MONOSPACED, BOLD, 32), antiAlias: Boole drawTextPosition( texture, renderer, Vec2(x, y).add(offset).add(justifyment), - scale, it, color, useCamera + scale, it, color, useCamera, snipRegion ) x += it.width * scale.x } @@ -235,7 +234,8 @@ class Font constructor(font: Font = Font(MONOSPACED, BOLD, 32), antiAlias: Boole scale: Vec2, glyph: Glyph, color: Color, - useCamera: Boolean + useCamera: Boolean, + snipRegion: SnipRegion? ) { val textureWidth = texture.width.toFloat() val textureHeight = texture.height.toFloat() @@ -256,7 +256,7 @@ class Font constructor(font: Font = Font(MONOSPACED, BOLD, 32), antiAlias: Boole }.toFloatArray() texture.bind() - renderer.drawShape(data, offset, 0f, scale, useCamera) + renderer.drawShape(data, offset, 0f, scale, useCamera, snipRegion) // textures.white_pixel.bind() // renderer.drawShape(data, offset, 0f, scale, useCamera) diff --git a/src/main/kotlin/game/GamePhaseHandler.kt b/src/main/kotlin/game/GamePhaseHandler.kt index 4bed7bb..ee3501e 100644 --- a/src/main/kotlin/game/GamePhaseHandler.kt +++ b/src/main/kotlin/game/GamePhaseHandler.kt @@ -5,6 +5,7 @@ import display.Window import display.draw.Drawer import display.draw.TextureEnum import display.events.MouseButtonEvent +import display.events.MouseScrollEvent import display.graphic.Color import display.gui.GuiController import display.text.TextJustify @@ -45,7 +46,7 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer) { fun init(window: Window) { exitCall = { window.exit() } - when (0) { + when (2) { 0 -> setupMainMenu() 1 -> setupMainMenuSelectPlayers() 2 -> { @@ -53,7 +54,7 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer) { currentPhase = GamePhases.PLAYERS_PICK_SHIELDS isTransitioning = false gameState.reset() - gameState.gamePlayers.addAll((1..3).map { GamePlayer(it.toString()) }) + gameState.gamePlayers.addAll((1..3).map { GamePlayer("Player $it") }) MapGenerator.populateNewGameMap(gameState) gameState.gamePlayers.forEach { it.vehicle?.shield = VehicleShield() } @@ -204,7 +205,8 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer) { val vehiclesDestroyed = gameState.gamePlayers.count { it.vehicle!!.hitPoints > 0 } < 2 if (vehiclesDestroyed) { startNewPhase(GamePhases.END_ROUND) - guiController.createRoundLeaderboard(gameState.gamePlayers, onClickNextRound = {}) + guiController.createRoundLeaderboard(gameState.gamePlayers, + onClickNextRound = { setupMainMenuSelectPlayers() }) return true } return false @@ -300,8 +302,13 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer) { guiController.checkLeftClickDrag(getScreenLocation(location), movement) } - fun scrollCamera(movement: Float) { - camera.moveZoom(movement * -.001f) + fun scrollMouse(event: MouseScrollEvent) { + val screenLocation = getScreenLocation(event.location) + if (guiController.locationIsGui(screenLocation)) { + guiController.checkScroll(event.movement, screenLocation) + } else { + camera.moveZoom(event.movement.y * -.001f) + } } fun pauseGame(event: KeyboardEvent) { diff --git a/src/main/kotlin/input/CameraView.kt b/src/main/kotlin/input/CameraView.kt index 15f5cc6..5217306 100644 --- a/src/main/kotlin/input/CameraView.kt +++ b/src/main/kotlin/input/CameraView.kt @@ -1,5 +1,6 @@ package input +import Matrix4f import display.Window import engine.freeBody.FreeBody import org.jbox2d.common.Vec2 @@ -69,6 +70,12 @@ class CameraView(private val window: Window) { z = .05f } + fun getRenderCamera(): Matrix4f { + val zoomScale = 1f / z + return Matrix4f.scale(zoomScale, zoomScale, 1f) + .multiply(Matrix4f.translate(-location.x, -location.y, 0f)) + } + } enum class CameraPhases { diff --git a/src/main/kotlin/input/InputHandler.kt b/src/main/kotlin/input/InputHandler.kt index 16f6c58..b1fd179 100644 --- a/src/main/kotlin/input/InputHandler.kt +++ b/src/main/kotlin/input/InputHandler.kt @@ -2,6 +2,7 @@ package input import display.events.MouseButtonEvent import display.Window +import display.events.MouseScrollEvent import game.GamePhaseHandler import io.reactivex.subjects.PublishSubject import org.jbox2d.common.Vec2 @@ -62,7 +63,7 @@ class InputHandler(private val gamePhaseHandler: GamePhaseHandler) { private fun setupMouseScroll(window: Window) { window.mouseScrollEvent.takeUntil(unsubscribe) - .subscribe { gamePhaseHandler.scrollCamera(it.y) } + .subscribe { gamePhaseHandler.scrollMouse(it) } } private fun setupDoubleLeftClick(window: Window) { From aeea6dc0307a1881e575f70923c6af80cfbb0c42 Mon Sep 17 00:00:00 2001 From: Pierre Blaarkies Roux Date: Sat, 18 Apr 2020 14:54:58 +0200 Subject: [PATCH 10/11] Add vehicle colors --- src/main/kotlin/display/draw/Drawer.kt | 43 +++++++------ src/main/kotlin/display/draw/TextureConfig.kt | 14 +++-- src/main/kotlin/display/graphic/Color.kt | 62 ++++++++++++++++++- src/main/kotlin/display/gui/GuiButton.kt | 1 + src/main/kotlin/engine/GameState.kt | 4 +- src/main/kotlin/engine/freeBody/Warhead.kt | 15 +---- src/main/kotlin/game/MapGenerator.kt | 26 ++++---- src/test/kotlin/engine/physics/GravityTest.kt | 1 - 8 files changed, 117 insertions(+), 49 deletions(-) diff --git a/src/main/kotlin/display/draw/Drawer.kt b/src/main/kotlin/display/draw/Drawer.kt index d4661b5..843ddd9 100644 --- a/src/main/kotlin/display/draw/Drawer.kt +++ b/src/main/kotlin/display/draw/Drawer.kt @@ -6,6 +6,7 @@ import display.graphic.Renderer import display.text.TextJustify import engine.freeBody.FreeBody import engine.freeBody.Particle +import engine.freeBody.Vehicle import engine.physics.CellLocation import engine.physics.GravityCell import game.GamePlayer @@ -25,24 +26,24 @@ class Drawer(val renderer: Renderer) { } fun drawDebugForces(freeBody: FreeBody) { -// val x = freeBody.worldBody.position.x -// val y = freeBody.worldBody.position.y -// val accelerationX = freeBody.worldBody.m_force.x -// val accelerationY = freeBody.worldBody.m_force.y -// -// val multiplier = 2000f -// val linePoints = listOf( -// x, -// y, -// x + accelerationX * multiplier, -// y + accelerationY * multiplier -// ) -// val triangleStripPoints = BasicShapes.getLineTriangleStrip(linePoints, .2f) -// val arrowHeadPoints = BasicShapes.getArrowHeadPoints(linePoints) -// val data = getColoredData( -// triangleStripPoints + arrowHeadPoints, -// Color(0f, 1f, 1f, 1f), Color(0f, 1f, 1f, 0.0f) -// ).toFloatArray() + // val x = freeBody.worldBody.position.x + // val y = freeBody.worldBody.position.y + // val accelerationX = freeBody.worldBody.m_force.x + // val accelerationY = freeBody.worldBody.m_force.y + // + // val multiplier = 2000f + // val linePoints = listOf( + // x, + // y, + // x + accelerationX * multiplier, + // y + accelerationY * multiplier + // ) + // val triangleStripPoints = BasicShapes.getLineTriangleStrip(linePoints, .2f) + // val arrowHeadPoints = BasicShapes.getArrowHeadPoints(linePoints) + // val data = getColoredData( + // triangleStripPoints + arrowHeadPoints, + // Color(0f, 1f, 1f, 1f), Color(0f, 1f, 1f, 0.0f) + // ).toFloatArray() textures.getTexture(TextureEnum.white_pixel).bind() // renderer.drawStrip(data) @@ -56,7 +57,11 @@ class Drawer(val renderer: Renderer) { if (linePoints.size < 4) { return } - val data = getLine(linePoints, Color(0.4f, 0.7f, 1f, 0.5f), Color.TRANSPARENT, .1f, 0f) + val trailColor = when (freeBody) { + is Vehicle -> freeBody.textureConfig.color.setAlpha(.3f) + else -> Color(.4f, .7f, 1f, .5f) + } + val data = getLine(linePoints, trailColor, Color.TRANSPARENT, .1f, 0f) textures.getTexture(TextureEnum.white_pixel).bind() renderer.drawStrip(data) diff --git a/src/main/kotlin/display/draw/TextureConfig.kt b/src/main/kotlin/display/draw/TextureConfig.kt index f1f5d4f..4d52a83 100644 --- a/src/main/kotlin/display/draw/TextureConfig.kt +++ b/src/main/kotlin/display/draw/TextureConfig.kt @@ -1,10 +1,16 @@ package display.draw -import Vector2f +import display.graphic.Color +import org.jbox2d.common.Vec2 +import utility.Common.vectorUnit class TextureConfig( - var texture: TextureEnum, val scale: Vector2f = Vector2f(1f, 1f), val offset: Vector2f = Vector2f(), - var chunkedVertices: List> = listOf(), var gpuBufferData: FloatArray = floatArrayOf() + var texture: TextureEnum, + val scale: Vec2 = vectorUnit, + val offset: Vec2 = Vec2(), + var chunkedVertices: List> = listOf(), + var gpuBufferData: FloatArray = floatArrayOf(), + val color: Color = Color.WHITE ) { fun updateGpuBufferData(): TextureConfig { @@ -12,7 +18,7 @@ class TextureConfig( val (x, y) = it listOf( x, y, 0f, - 1f, 1f, 1f, 1f, + color.red, color.green, color.blue, color.alpha, (x * .5f - 0.5f) * scale.x + offset.x, (y * .5f - 0.5f) * scale.y + offset.y ) diff --git a/src/main/kotlin/display/graphic/Color.kt b/src/main/kotlin/display/graphic/Color.kt index 6e3ce0b..1900ecc 100644 --- a/src/main/kotlin/display/graphic/Color.kt +++ b/src/main/kotlin/display/graphic/Color.kt @@ -1,6 +1,18 @@ package display.graphic -class Color(val red: Float = 0f, val green: Float = 0f, val blue: Float = 0f, val alpha: Float = 0f) { +class Color { + + val red: Float + val green: Float + val blue: Float + val alpha: Float + + constructor(red: Float = 0f, green: Float = 0f, blue: Float = 0f, alpha: Float = 1f) { + this.red = getSafeValue(red) + this.green = getSafeValue(green) + this.blue = getSafeValue(blue) + this.alpha = getSafeValue(alpha) + } operator fun times(value: Float) = Color(this.red * value, this.green * value, this.blue * value, this.alpha * value) @@ -15,9 +27,18 @@ class Color(val red: Float = 0f, val green: Float = 0f, val blue: Float = 0f, va getIntToFloat(alpha) ) + constructor(hexValue: String) { + val (red, green, blue, alpha) = hexValue.dropWhile { it == '#' }.chunked(2) + this.red = getHexToFloat(red) + this.green = getHexToFloat(green) + this.blue = getHexToFloat(blue) + this.alpha = getHexToFloat(alpha) + } + fun setAlpha(newValue: Float): Color = Color(red, green, blue, newValue) companion object { + val WHITE = Color(1f, 1f, 1f, 1f) val BLACK = Color(0f, 0f, 0f, 1f) val RED = Color(1f, 0f, 0f, 1f) @@ -25,8 +46,47 @@ class Color(val red: Float = 0f, val green: Float = 0f, val blue: Float = 0f, va val BLUE = Color(0f, 0f, 1f, 1f) val TRANSPARENT = Color(0f, 0f, 0f, 0f) + val HEX = Color("#01FF6499") + val HSV = createFromHsv(.5f, 1f, .5f, 1f) + + val PALETTE8 = (0..8).map { createFromHsv(it / 7f, 1f, .5f) } + val PALETTE_TINT10 = + (0..8).map { createFromHsv(it / 7f, 1f, .8f) } + listOf(WHITE, createFromHsv(0f, 0f, .7f)) + private fun getSafeValue(value: Float) = value.coerceIn(0f, 1f) private fun getIntToFloat(value: Int) = getSafeValue(value / 255f) + + private fun getHexToFloat(value: String) = value.toShort(16).toFloat().let { getSafeValue(it / 255f) } + + fun createFromHsv(hue: Float = 0f, saturation: Float = 0f, light: Float = 0f, alpha: Float = 1f): Color { + val rgbList = hslToRgb(hue, saturation, light) + return Color(rgbList[0], rgbList[1], rgbList[2], alpha) + } + + private fun hslToRgb(hue: Float, saturation: Float, light: Float): List { + return if (saturation == 0f) { + listOf(light, light, light) + } else { + val q = if (light < .5f) light * (1 + saturation) else light + saturation - light * saturation + val p = 2 * light - q + listOf( + hueToRgb(p, q, hue + 1f / 3f), + hueToRgb(p, q, hue), + hueToRgb(p, q, hue - 1f / 3f) + ) + } + } + + private fun hueToRgb(p: Float, q: Float, t: Float): Float { + var t = t + if (t < 0f) t += 1f + if (t > 1f) t -= 1f + if (t < 1f / 6f) return p + (q - p) * 6f * t + if (t < 1f / 2f) return q + return if (t < 2f / 3f) p + (q - p) * (2f / 3f - t) * 6f else p + } + } + } diff --git a/src/main/kotlin/display/gui/GuiButton.kt b/src/main/kotlin/display/gui/GuiButton.kt index 4770f08..6b97b32 100644 --- a/src/main/kotlin/display/gui/GuiButton.kt +++ b/src/main/kotlin/display/gui/GuiButton.kt @@ -7,6 +7,7 @@ import display.graphic.Color import display.graphic.SnipRegion import display.text.TextJustify import org.jbox2d.common.Vec2 +import utility.Common.vectorUnit class GuiButton( drawer: Drawer, diff --git a/src/main/kotlin/engine/GameState.kt b/src/main/kotlin/engine/GameState.kt index 2e1efc9..d79974a 100644 --- a/src/main/kotlin/engine/GameState.kt +++ b/src/main/kotlin/engine/GameState.kt @@ -4,6 +4,7 @@ import input.CameraView import display.Window import display.draw.TextureConfig import display.draw.TextureEnum +import display.graphic.Color import engine.freeBody.* import engine.motion.Director import engine.motion.Motion @@ -160,7 +161,8 @@ class GameState { return Warhead.create( world, player, warheadLocation.x, warheadLocation.y, angle, warheadVelocity.x, warheadVelocity.y, 0f, - warheadMass, warheadRadius, textureConfig = TextureConfig(TextureEnum.metal), + warheadMass, warheadRadius, + textureConfig = TextureConfig(TextureEnum.metal, color = Color.createFromHsv(0f, 1f, .3f, 1f)), onWarheadCollision = { self, body -> detonateWarhead(self as Warhead, body) } ) .also { warheads.add(it) } diff --git a/src/main/kotlin/engine/freeBody/Warhead.kt b/src/main/kotlin/engine/freeBody/Warhead.kt index 4fe6ca9..87f0f5a 100644 --- a/src/main/kotlin/engine/freeBody/Warhead.kt +++ b/src/main/kotlin/engine/freeBody/Warhead.kt @@ -31,6 +31,7 @@ class Warhead( private val createdAt = currentTime val selfDestructTime = 45000f // TODO: player in current aiming phase could just wait out this time if they wanted to + // also influences score val damage = 100f @@ -46,7 +47,7 @@ class Warhead( val velocity = impacted.linearVelocity val bodyDef = createBodyDef(BodyType.STATIC, location.x, location.y, 0f, velocity.x, velocity.y, 0f) val worldBody = world.createBody(bodyDef) -// createWorldBody(shapeBox, 0f, radius, 0f, 0f, world, bodyDef) + // createWorldBody(shapeBox, 0f, radius, 0f, 0f, world, bodyDef) val textureConfig = TextureConfig(TextureEnum.white_pixel, chunkedVertices = BasicShapes.polygon30.chunked(2)) .updateGpuBufferData() @@ -88,17 +89,7 @@ class Warhead( return Warhead("1", firedBy, Motion(), worldBody, radius, textureConfig) .also { -// it.textureConfig.updateGpuBufferData() - it.textureConfig.gpuBufferData = it.textureConfig.chunkedVertices.flatMap { - val (x, y) = it - listOf( - x, y, 0f, - 1f, .3f, .3f, 1f, - (x * .5f - 0.5f) * .1f, - (y * .5f - 0.5f) * .1f - ) - }.toFloatArray() - + it.textureConfig.updateGpuBufferData() firedBy.warheads.add(it) worldBody.userData = FreeBodyCallback(it, onWarheadCollision) } diff --git a/src/main/kotlin/game/MapGenerator.kt b/src/main/kotlin/game/MapGenerator.kt index 5607c8f..4d1af05 100644 --- a/src/main/kotlin/game/MapGenerator.kt +++ b/src/main/kotlin/game/MapGenerator.kt @@ -1,14 +1,16 @@ package game -import Vector2f import display.draw.TextureConfig import display.draw.TextureEnum import display.graphic.BasicShapes +import display.graphic.Color import engine.GameState import engine.freeBody.Planet import engine.freeBody.Vehicle import engine.motion.Director +import org.jbox2d.common.Vec2 import org.jbox2d.dynamics.World +import utility.Common.vectorUnit import kotlin.math.PI import kotlin.math.cos import kotlin.math.sin @@ -25,11 +27,13 @@ object MapGenerator { textureConfig = TextureConfig(TextureEnum.full_moon, chunkedVertices = BasicShapes.polygon30.chunked(2)) ) + val colorStartIndex = Math.random().times(11).toInt() val vehicles = gameState.gamePlayers.withIndex().map { (index, player) -> Vehicle.create( - gameState.world, player,-10f * index + .5f * gameState.gamePlayers.size, + gameState.world, player, -10f * index + .5f * gameState.gamePlayers.size, 10f, index * 1f, 0f, 0f, 1f, 3f, radius = .75f, - textureConfig = TextureConfig(TextureEnum.metal, Vector2f(.7f, .7f), Vector2f(0f, 0f)) + textureConfig = TextureConfig(TextureEnum.metal, vectorUnit.mul(.7f), + color = Color.PALETTE_TINT10[((colorStartIndex + index) * 2).rem(11)]) ) } @@ -45,8 +49,8 @@ object MapGenerator { gameState.world, "terra", 0f, 0f, 0f, 0f, 0f, .1f, 1800f, 4.5f, .3f, textureConfig = TextureConfig( TextureEnum.marble_earth, - Vector2f(1f, 1f), - Vector2f(0f, 0f), + vectorUnit, + Vec2(), BasicShapes.polygon30.chunked(2) ) ) @@ -54,18 +58,18 @@ object MapGenerator { gameState.world, "luna", -20f, 0f, 0f, 0f, 4.4f, -.4f, 100f, 1.25f, .5f, textureConfig = TextureConfig( TextureEnum.full_moon, - Vector2f(1f, 1f), - Vector2f(0f, 0f), + vectorUnit.mul(.7f), + Vec2(), BasicShapes.polygon30.chunked(2) ) ) val alice = Vehicle.create( gameState.world, GamePlayer("alice"), -30f, 5f, 0f, -2f, 2.7f, 1f, 3f, radius = .75f, - textureConfig = TextureConfig(TextureEnum.metal, Vector2f(.7f, .7f), Vector2f(0f, 0f), listOf()) + textureConfig = TextureConfig(TextureEnum.metal, vectorUnit.mul(.7f)) ) val bob = Vehicle.create( gameState.world, GamePlayer("bob"), 25f, 0f, 0f, 2f, -3f, 0f, 3f, radius = .75f, - textureConfig = TextureConfig(TextureEnum.metal, Vector2f(.7f, .7f), Vector2f(0f, 0f), listOf()) + textureConfig = TextureConfig(TextureEnum.metal, vectorUnit.mul(.7f)) ) gameState.vehicles.addAll(listOf(alice, bob)) @@ -98,8 +102,8 @@ object MapGenerator { friction = .6f, textureConfig = TextureConfig( TextureEnum.pavement, - Vector2f(.3f, .3f), - Vector2f(it[0].rem(20f) / 10 - 1, it[1].rem(20f) / 10 - 1), + vectorUnit.mul(.3f), + Vec2(it[0].rem(20f) / 10 - 1, it[1].rem(20f) / 10 - 1), BasicShapes.polygon9.chunked(2) ) ) diff --git a/src/test/kotlin/engine/physics/GravityTest.kt b/src/test/kotlin/engine/physics/GravityTest.kt index ad25995..43db042 100644 --- a/src/test/kotlin/engine/physics/GravityTest.kt +++ b/src/test/kotlin/engine/physics/GravityTest.kt @@ -2,7 +2,6 @@ package engine.physics import display.draw.TextureConfig import display.draw.TextureEnum -import display.graphic.Texture import engine.freeBody.Planet import org.jbox2d.common.Vec2 import org.jbox2d.dynamics.World From b78d973a772b6c72ec04f750e798c3e888d1930b Mon Sep 17 00:00:00 2001 From: Pierre Blaarkies Roux Date: Sat, 18 Apr 2020 15:42:51 +0200 Subject: [PATCH 11/11] Add GuiIcon element --- build.gradle.kts | 24 ++++++----- src/main/kotlin/Main.kt | 2 +- src/main/kotlin/display/Window.kt | 16 ++++--- src/main/kotlin/display/draw/Drawer.kt | 35 +++++++++++++-- src/main/kotlin/display/draw/TextureEnum.kt | 3 +- src/main/kotlin/display/draw/TextureHolder.kt | 1 + src/main/kotlin/display/gui/GuiController.kt | 40 ++++++++++++------ src/main/kotlin/display/gui/GuiIcon.kt | 34 +++++++++++++++ src/main/kotlin/display/gui/GuiLabel.kt | 8 ++-- src/main/kotlin/display/gui/GuiPanel.kt | 2 +- src/main/kotlin/game/GamePhaseHandler.kt | 6 +-- src/main/kotlin/game/MapGenerator.kt | 14 +++--- src/main/resources/textures/icon_aim.png | Bin 0 -> 1135 bytes 13 files changed, 134 insertions(+), 51 deletions(-) create mode 100644 src/main/kotlin/display/gui/GuiIcon.kt create mode 100644 src/main/resources/textures/icon_aim.png diff --git a/build.gradle.kts b/build.gradle.kts index 586100f..012e691 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -80,7 +80,12 @@ sourceSets { tasks { withType { - kotlinOptions.jvmTarget = "11" + kotlinOptions.jvmTarget = "1.8" + } + + register("zipFolder") { + from("$buildDir/Volynov-$version") + destinationDirectory.set(File("$buildDir/")) } task("renameFolder") { @@ -95,16 +100,21 @@ tasks { doLast { File("$buildDir/libs/run.bat") .writeText("""|chcp 65001 - |java -Dfile.encoding=UTF-8 -jar ./volynov-$version-uber.jar + |java -Dfile.encoding=UTF-8 -Dorg.lwjgl.util.Debug=true -jar ./volynov-$version-uber.jar |pause """.trimMargin()) } } + register("copyTextures") { + from("$projectDir/src/main/resources") + exclude("shaders") + into("$buildDir/libs") + } + register("uberJar") { dependsOn(configurations.runtimeClasspath) - group = "build" archiveClassifier.set("uber") manifest { attributes["Main-Class"] = "MainKt" } @@ -118,7 +128,7 @@ tasks { task("package") { group = "build" - dependsOn("clean", "uberJar", "copyTextures", "createBat", "renameFolder") + dependsOn("clean", "uberJar", "copyTextures", "createBat", "renameFolder", "zipFolder") } } @@ -127,9 +137,3 @@ tasks.test { useJUnitPlatform() testLogging { events("PASSED", "SKIPPED", "FAILED") } } - -tasks.register("copyTextures") { - from("$projectDir/src/main/resources") - exclude("shaders") - into("$buildDir/libs") -} diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index 8ee5797..f4ad33a 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -6,7 +6,7 @@ import kotlin.system.exitProcess fun main() = runBlocking { try { val gameLogic: IGameLogic = AppLogic() - val gameEngine = AppRunner("Volynov", 1000, 1000, true, gameLogic) + val gameEngine = AppRunner("Volynov", 1920, 1080, true, gameLogic) gameEngine.run() } catch (exception: Exception) { exception.printStackTrace() diff --git a/src/main/kotlin/display/Window.kt b/src/main/kotlin/display/Window.kt index 72275ab..62140ef 100644 --- a/src/main/kotlin/display/Window.kt +++ b/src/main/kotlin/display/Window.kt @@ -47,10 +47,11 @@ class Window(private val title: String, var width: Int, var height: Int, private GLFW.glfwWindowHint(GLFW.GLFW_SAMPLES, 4) // anti-aliasing // Create the window - windowHandle = GLFW.glfwCreateWindow(width, height, title, MemoryUtil.NULL, MemoryUtil.NULL) - if (windowHandle == MemoryUtil.NULL) { - throw RuntimeException("Failed to create the GLFW window") - } + windowHandle = GLFW.glfwCreateWindow(width, height, title, GLFW.glfwGetPrimaryMonitor(), MemoryUtil.NULL) + // windowHandle = GLFW.glfwCreateWindow(width, height, title, MemoryUtil.NULL, MemoryUtil.NULL) + // if (windowHandle == MemoryUtil.NULL) { + // throw RuntimeException("Failed to create the GLFW window") + // } // Setup resize callback GLFW.glfwSetFramebufferSizeCallback(windowHandle) { _, width, height -> this.width = width @@ -58,9 +59,10 @@ class Window(private val title: String, var width: Int, var height: Int, private } // Get the resolution of the primary monitor - val videoMode = GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor())!! + // val videoMode = GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor())!! // Center our window - GLFW.glfwSetWindowPos(windowHandle, (videoMode.width() - width) / 2, (videoMode.height() - height) / 2) + // GLFW.glfwSetWindowPos(windowHandle, (videoMode.width() - width) / 2, (videoMode.height() - height) / 2) + GLFW.glfwMakeContextCurrent(windowHandle) // Make the OpenGL context current if (isVSync()) { // Enable v-sync GLFW.glfwSwapInterval(1) @@ -97,7 +99,7 @@ class Window(private val title: String, var width: Int, var height: Int, private }?.let { callbacks.add(it) } GLFW.glfwSetScrollCallback(windowHandle) { _, xOffset, yOffset -> - mouseScrollEvent.onNext( MouseScrollEvent(makeVec2(xOffset, yOffset), getCursorPosition()) ) + mouseScrollEvent.onNext(MouseScrollEvent(makeVec2(xOffset, yOffset), getCursorPosition())) }?.let { callbacks.add(it) } GLFW.glfwSetCharCallback(windowHandle) { _, codepoint -> diff --git a/src/main/kotlin/display/draw/Drawer.kt b/src/main/kotlin/display/draw/Drawer.kt index 843ddd9..a2b492b 100644 --- a/src/main/kotlin/display/draw/Drawer.kt +++ b/src/main/kotlin/display/draw/Drawer.kt @@ -3,6 +3,7 @@ package display.draw import display.graphic.BasicShapes import display.graphic.Color import display.graphic.Renderer +import display.graphic.SnipRegion import display.text.TextJustify import engine.freeBody.FreeBody import engine.freeBody.Particle @@ -11,6 +12,8 @@ import engine.physics.CellLocation import engine.physics.GravityCell import game.GamePlayer import org.jbox2d.common.Vec2 +import org.lwjgl.opengl.GL11 +import org.lwjgl.opengl.GL12 import utility.Common.makeVec2 import utility.Common.makeVec2Circle import utility.Common.vectorUnit @@ -94,7 +97,7 @@ class Drawer(val renderer: Renderer) { } } - fun drawPicture(textureEnum: TextureEnum, scale: Vec2 = vectorUnit, offset: Vec2 = Vec2()) { + fun drawBackground(textureEnum: TextureEnum, scale: Vec2 = vectorUnit, offset: Vec2 = Vec2()) { val texture = textures.getTexture(textureEnum).bind() val left = -texture.width / 2f @@ -107,14 +110,40 @@ class Drawer(val renderer: Renderer) { listOf( it[0], it[1], 0f, 1f, 1f, 1f, 1f, - (it[0] / 2 - 0.5f) * scale.x + offset.x, - (it[1] / 2 - 0.5f) * scale.y + offset.y + (it[0] / 2f - 0.5f) * scale.x + offset.x, + (it[1] / 2f - 0.5f) * scale.y + offset.y ) }.toFloatArray() renderer.drawShape(data, scale = vectorUnit.mul(45f)) } + fun drawIcon(textureEnum: TextureEnum, + scale: Vec2 = vectorUnit, + offset: Vec2 = Vec2(), + color: Color + ) { + val texture = textures.getTexture(textureEnum).bind() + + val left = -texture.width / 2f + val right = texture.width / 2f + val top = texture.height / 2f + val bottom = -texture.height / 2f + + val data = listOf(left, bottom, left, top, right, top, right, bottom).chunked(2) + .flatMap { + listOf( + it[0], it[1], 0f, + color.red, color.green, color.blue, color.alpha, + (it[0] / 2f - 0.5f), + (it[1] / 2f - 0.5f) + ) + }.toFloatArray() + + renderer.drawShape(data, offset, 0f, scale, useCamera = false, + snipRegion = SnipRegion(offset.add(scale.negate()), scale.mul(2f))) + } + fun drawPlayerAimingPointer(player: GamePlayer) { val playerLocation = player.vehicle!!.worldBody.position val angle = player.playerAim.angle diff --git a/src/main/kotlin/display/draw/TextureEnum.kt b/src/main/kotlin/display/draw/TextureEnum.kt index ea7166f..a679399 100644 --- a/src/main/kotlin/display/draw/TextureEnum.kt +++ b/src/main/kotlin/display/draw/TextureEnum.kt @@ -6,5 +6,6 @@ enum class TextureEnum { metal, pavement, white_pixel, - stars_2k + stars_2k, + icon_aim } diff --git a/src/main/kotlin/display/draw/TextureHolder.kt b/src/main/kotlin/display/draw/TextureHolder.kt index f359115..86c3b8a 100644 --- a/src/main/kotlin/display/draw/TextureHolder.kt +++ b/src/main/kotlin/display/draw/TextureHolder.kt @@ -14,6 +14,7 @@ class TextureHolder { hMap[TextureEnum.pavement] = Texture.loadTexture("./textures/pavement.png") hMap[TextureEnum.white_pixel] = Texture.loadTexture("./textures/white_pixel.png") hMap[TextureEnum.stars_2k] = Texture.loadTexture("./textures/stars_2k.png") + hMap[TextureEnum.icon_aim] = Texture.loadTexture("./textures/icon_aim.png") } fun getTexture(texture: TextureEnum): Texture = textureHashMap[texture].let { diff --git a/src/main/kotlin/display/gui/GuiController.kt b/src/main/kotlin/display/gui/GuiController.kt index 82ea3da..9e1edd6 100644 --- a/src/main/kotlin/display/gui/GuiController.kt +++ b/src/main/kotlin/display/gui/GuiController.kt @@ -1,11 +1,13 @@ package display.gui import display.draw.Drawer +import display.draw.TextureEnum import display.graphic.Color import display.text.TextJustify import game.GamePlayer import org.jbox2d.common.Vec2 import utility.Common.roundFloat +import utility.Common.vectorUnit import kotlin.math.roundToInt class GuiController(private val drawer: Drawer) { @@ -129,7 +131,7 @@ class GuiController(private val drawer: Drawer) { clear() val shieldPickerPanel = GuiPanel( - drawer, Vec2(200f, -200f), Vec2(150f, 150f), title = "${player.name} to pick a shield", + drawer, Vec2(710f, -340f), Vec2(250f, 200f), title = "${player.name} to pick a shield", draggable = true ) shieldPickerPanel.addChild( @@ -146,32 +148,35 @@ class GuiController(private val drawer: Drawer) { ) { clear() val commandPanel = GuiPanel( - drawer, Vec2(350f, -350f), Vec2(150f, 150f), + drawer, Vec2(710f, -340f), Vec2(250f, 200f), title = player.name, draggable = true ) val weaponsList = GuiScroll(drawer, Vec2(50f, -50f), Vec2(100f, 100f)).addChildren( - (1..30).map { - GuiButton(drawer, scale = Vec2(100f, 25f), title = "Boom number $it", textSize = .15f, - onClick = { println("clicked $it") }) + (1..5).map { + GuiButton(drawer, scale = Vec2(100f, 25f), title = "Boom $it", textSize = .15f, + onClick = { println("clicked [Boom $it]") }) } ) commandPanel.addChildren( listOf( - GuiButton(drawer, Vec2(-100f, 0f), Vec2(50f, 25f), title = "Aim", + GuiButton(drawer, Vec2(-200f, 0f), Vec2(50f, 25f), title = "Aim", onClick = { onClickAim(player) }), - GuiButton(drawer, Vec2(-100f, -50f), Vec2(50f, 25f), title = "Power", + GuiButton(drawer, Vec2(-200f, -50f), Vec2(50f, 25f), title = "Power", onClick = { onClickPower(player) }), - GuiButton(drawer, Vec2(-100f, -100f), Vec2(50f, 25f), title = "Fire", + GuiButton(drawer, Vec2(-200f, -100f), Vec2(50f, 25f), title = "Fire", onClick = { onClickFire(player) }), - GuiLabel(drawer, Vec2(-150f, 90f), justify = TextJustify.LEFT, title = getPlayerAimAngleDisplay(player), + GuiLabel(drawer, Vec2(-210f, 110f), justify = TextJustify.LEFT, + title = getPlayerAimAngleDisplay(player), textSize = .15f, updateCallback = { it.title = getPlayerAimAngleDisplay(player) }), - GuiLabel(drawer, Vec2(-150f, 70f), justify = TextJustify.LEFT, title = getPlayerAimPowerDisplay(player), + GuiLabel(drawer, Vec2(-210f, 80f), justify = TextJustify.LEFT, title = getPlayerAimPowerDisplay(player), textSize = .15f, updateCallback = { it.title = getPlayerAimPowerDisplay(player) }), - weaponsList + weaponsList, + + GuiIcon(drawer, Vec2(-230f, 110f), vectorUnit.mul(20f), texture = TextureEnum.icon_aim) ) ) elements.add(commandPanel) @@ -186,11 +191,18 @@ class GuiController(private val drawer: Drawer) { fun createRoundLeaderboard(players: MutableList, onClickNextRound: () -> Unit) { clear() val leaderBoardPanel = GuiPanel(drawer, Vec2(), Vec2(200f, 300f), "Leaderboard", draggable = false) - val playerLines = players.sortedByDescending { it.score }.map { + val playerLines = listOf(GuiLabel( + drawer, + Vec2(-50f, 100f), + justify = TextJustify.LEFT, + title = "Player Score".padStart(10, ' '), + textSize = .2f + )) + players.sortedByDescending { it.score }.map { GuiLabel( drawer, + Vec2(-50f, 100f), justify = TextJustify.LEFT, - title = "${it.name.padEnd(10, ' ')}${it.score.roundToInt()}".padStart(10, ' '), + title = "${it.name.padEnd(20, ' ')}${it.score.roundToInt()}".padStart(10, ' '), textSize = .2f ) } @@ -199,7 +211,7 @@ class GuiController(private val drawer: Drawer) { leaderBoardPanel.addChildren(playerLines) leaderBoardPanel.addChild( GuiButton( - drawer, Vec2(0f, -280f), Vec2(100f, 25f), "Next Match", + drawer, Vec2(0f, -250f), Vec2(100f, 25f), "Next Match", onClick = onClickNextRound ) ) diff --git a/src/main/kotlin/display/gui/GuiIcon.kt b/src/main/kotlin/display/gui/GuiIcon.kt new file mode 100644 index 0000000..e9581d5 --- /dev/null +++ b/src/main/kotlin/display/gui/GuiIcon.kt @@ -0,0 +1,34 @@ +package display.gui + +import display.draw.Drawer +import display.draw.TextureEnum +import display.graphic.Color +import display.graphic.SnipRegion +import org.jbox2d.common.Vec2 +import utility.Common.vectorUnit + +class GuiIcon( + drawer: Drawer, + offset: Vec2 = Vec2(), + scale: Vec2 = vectorUnit, + title: String = "", + textSize: Float = 0f, + color: Color = Color.WHITE.setAlpha(.7f), + val texture: TextureEnum = TextureEnum.white_pixel +) : GuiElement( + drawer, + offset, + scale, + title, + textSize, + color, + {} +) { + + override fun render(snipRegion: SnipRegion?) { + super.render(snipRegion) + + drawer.drawIcon(texture, scale, offset, color) + } + +} diff --git a/src/main/kotlin/display/gui/GuiLabel.kt b/src/main/kotlin/display/gui/GuiLabel.kt index a3d5f7c..1aee370 100644 --- a/src/main/kotlin/display/gui/GuiLabel.kt +++ b/src/main/kotlin/display/gui/GuiLabel.kt @@ -18,10 +18,10 @@ class GuiLabel( drawer, offset, Vec2(title.length * 16f, textSize * 100f), - title = title, - textSize = textSize, - color = color, - updateCallback = updateCallback + title, + textSize, + color, + updateCallback ) { override fun render(snipRegion: SnipRegion?) { diff --git a/src/main/kotlin/display/gui/GuiPanel.kt b/src/main/kotlin/display/gui/GuiPanel.kt index 1d68d64..fc6e81b 100644 --- a/src/main/kotlin/display/gui/GuiPanel.kt +++ b/src/main/kotlin/display/gui/GuiPanel.kt @@ -41,7 +41,7 @@ class GuiPanel( offset.add(Vec2(0f, scale.y - 25f)), Common.vectorUnit.mul(.15f), Color.WHITE, - TextJustify.LEFT, + TextJustify.CENTER, false, snipRegion ) diff --git a/src/main/kotlin/game/GamePhaseHandler.kt b/src/main/kotlin/game/GamePhaseHandler.kt index ee3501e..f05c5d0 100644 --- a/src/main/kotlin/game/GamePhaseHandler.kt +++ b/src/main/kotlin/game/GamePhaseHandler.kt @@ -46,7 +46,7 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer) { fun init(window: Window) { exitCall = { window.exit() } - when (2) { + when (0) { 0 -> setupMainMenu() 1 -> setupMainMenuSelectPlayers() 2 -> { @@ -252,7 +252,7 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer) { } private fun drawPlayPhase() { - drawer.drawPicture(TextureEnum.stars_2k) + drawer.drawBackground(TextureEnum.stars_2k) val allFreeBodies = gameState.gravityBodies allFreeBodies.forEach { drawer.drawTrail(it) } @@ -456,7 +456,7 @@ class GamePhaseHandler(private val gameState: GameState, val drawer: Drawer) { MapGenerator.populateNewGameMap(gameState) - check(gameState.gamePlayers.size > 0) { "Cannot play a game with no players." } + check(gameState.gamePlayers.size > 1) { "Cannot play a game with less than 2 players." } gameState.playerOnTurn = gameState.gamePlayers.random() } diff --git a/src/main/kotlin/game/MapGenerator.kt b/src/main/kotlin/game/MapGenerator.kt index 4d1af05..a8db1de 100644 --- a/src/main/kotlin/game/MapGenerator.kt +++ b/src/main/kotlin/game/MapGenerator.kt @@ -19,11 +19,11 @@ object MapGenerator { fun populateNewGameMap(gameState: GameState) { val terra = Planet.create( - gameState.world, "terra", 0f, 0f, 0f, 0f, 0f, .1f, 1800f, 4.5f, .3f, + gameState.world, "terra", 0f, 0f, 0f, 0f, 0f, .1f, 1600f, 4.5f, .3f, textureConfig = TextureConfig(TextureEnum.marble_earth, chunkedVertices = BasicShapes.polygon30.chunked(2)) ) val luna = Planet.create( - gameState.world, "luna", -20f, 0f, 0f, 0f, 4.8f, -.4f, 100f, 1.25f, .5f, + gameState.world, "luna", -25f, 0f, 0f, 0f, 4.8f, -.4f, 100f, 1.25f, .5f, textureConfig = TextureConfig(TextureEnum.full_moon, chunkedVertices = BasicShapes.polygon30.chunked(2)) ) @@ -39,7 +39,7 @@ object MapGenerator { gameState.vehicles.addAll(vehicles) gameState.planets.addAll(listOf(terra, luna)) - gameState.planets.addAll(createPlanets(gameState.world, 5)) + gameState.planets.addAll(createAsteroids(gameState.world, 15)) gameState.gravityBodies.forEach { it.textureConfig.updateGpuBufferData() } } @@ -74,17 +74,17 @@ object MapGenerator { gameState.vehicles.addAll(listOf(alice, bob)) gameState.planets.addAll(listOf(terra, luna)) - gameState.planets.addAll(createPlanets(gameState.world, 20)) + gameState.planets.addAll(createAsteroids(gameState.world, 20)) gameState.gravityBodies.forEach { it.textureConfig.updateGpuBufferData() } } - private fun createPlanets(world: World, count: Int): List { + private fun createAsteroids(world: World, count: Int): List { return (1..count) .withIndex() .map { (i, _) -> val ratio = (2 * PI * 0.07 * i).toFloat() - val radius = 15f + val radius = 35f floatArrayOf( (i * .04f + radius) * cos(ratio), (i * .04f + radius) * sin(ratio), @@ -93,7 +93,7 @@ object MapGenerator { } .map { val direction = Director.getDirection(-it[0], -it[1]) + PI * .5f - val speed = 7f + val speed = 4.7f Planet.create( world, "${it[2].toInt()}", it[0], it[1], 0f, cos(direction).toFloat() * speed, diff --git a/src/main/resources/textures/icon_aim.png b/src/main/resources/textures/icon_aim.png new file mode 100644 index 0000000000000000000000000000000000000000..895e8b774092a553843f8a82aabc0e36a5eef775 GIT binary patch literal 1135 zcmV-#1d#iQP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGf5C8xR5CN?ty>$Qp1O!P$K~#8N?VL}D zO;H@jA5HnwET~bG1yP8FqHHXvu~6*B1`A8df+Pz|b{4a+U@OUjgb&thn`;o$y+7Gc0wUA=AYmN8A{kx(1mk23g?7;M6flrx@hA8-r7~HKdyu z_u)49qqr5AI?a%7V{C^}VvxE7=_SUu;#MT;5G0uxB=;am#UMEcNiGJ-HAs>%NRC00 zjX`n?l5`A`Q;_6ikX(Xf5rgCqB%2r{^&nZrASnmQE(S^UZ112khT&?s5`Kbrpb1h8 z(rXO<(s`YH1!RPEa4{@73lB6mRjlxz=!`82xGL=PrkTv6l8%qwt@#dyIQV25W0?tLd+5A{eIuH51O<_IA zpfO6gR*+|KE98CmLVj;kSPoKE48jGR@F=VGG5p@9p1!OGsWJw6>Bd!VY<(=BzPPT~ zpxsIz*?y0vW^ywehdknH-9HA&FUb3}el3b^cSh7lj82|{Xd$G9UjF=mL>7$xiBQ^;Ae z1b&6V2t7lr#~?i4bqjKq?BkA*gq$HM#3&hs{8TPaO0MCKP=pGSQjC%}kgrd8b~xBI z_YhB6DaIh@+_=Y$LGV38QjI~%#r-qn;?4_O-tjp@QjS5mxbr&72;@6eg5WDi>M=^n zwPTALz2hTDE-}bMH*RvHcd8DOQw+j?E7|Nu?^L;{=N6;n3*?{LSOmGid!?!wl4}f7 zo?zrzVDD5BBk%M>~V;m5%kMN|t<>+{m^6gma@4(ByDMTKydENpK6 zOQ)kOEzo}W|AVNLbP96PjiNd@JNV(#wsdopwHuy*^C7P>--SUg_-Dw#M_D2;{hZ4Z z9#IfuD2Oo>#25-9gb+dqA%qY@2qA&S literal 0 HcmV?d00001