Skip to content

Commit

Permalink
Make game playable
Browse files Browse the repository at this point in the history
  • Loading branch information
srikavin committed Jan 9, 2020
1 parent 999710f commit e553610
Show file tree
Hide file tree
Showing 16 changed files with 254 additions and 47 deletions.
2 changes: 1 addition & 1 deletion core/src/me/srikavin/fbla/game/FBLAGame.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class FBLAGame : ApplicationAdapter() {
val assetManager = AssetManager()
val batch = SpriteBatch()

val physicsWorld = com.badlogic.gdx.physics.box2d.World(Vector2(0f, -16f), true)
val physicsWorld = com.badlogic.gdx.physics.box2d.World(Vector2(0f, -23f), true)

val generator = FreeTypeFontGenerator(Gdx.files.internal("assets/fonts/Kenney Pixel.ttf"))
val parameter = FreeTypeFontGenerator.FreeTypeFontParameter()
Expand Down
20 changes: 19 additions & 1 deletion core/src/me/srikavin/fbla/game/Util.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package me.srikavin.fbla.game

import com.badlogic.gdx.utils.Array
import me.srikavin.fbla.game.minigame.Minigame
import kotlin.reflect.KProperty

/**
* Type alias to avoid mixing up Kotlin Arrays with Gdx Arrays
Expand All @@ -10,4 +12,20 @@ typealias GdxArray<T> = Array<T>
/**
* Type alias to maintain type safety with Artemis-ODB entity identifiers
*/
typealias EntityInt = Int
typealias EntityInt = Int


/**
* Utility class to allow kotlin classes that delegate to map properties for type safety reasons
*/
class MapTriggerDelegate(val name: String) {
operator fun getValue(mapTriggerProperties: Minigame.MapTriggerProperties, property: KProperty<*>): String {
return mapTriggerProperties.properties.get(name)?.toString()
?: throw RuntimeException("Minigame trigger without `$name`!")

}

operator fun setValue(mapTriggerProperties: Minigame.MapTriggerProperties, property: KProperty<*>, value: String) {
mapTriggerProperties.properties.put(name, value)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import me.srikavin.fbla.game.minigame.Minigame

/**
* Component containing a minigame
*
* @see me.srikavin.fbla.game.ecs.system.MinigameSystem
* @see me.srikavin.fbla.game.ecs.system.MinigameRenderSystem
*/
class MinigameComponent : Component() {
var minigame: Minigame? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ class DialogueSystem : BaseEntitySystem() {
if (mapper[e] == dialogueManager.component) {
dialogueManager.component = null
}
mapper[e].channel.close()
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion core/src/me/srikavin/fbla/game/ecs/system/InputSystem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import me.srikavin.fbla.game.graphics.player_foot_fixture_id
import me.srikavin.fbla.game.physics.ContactListenerManager

private const val MAX_HORIZONTAL_VELOCITY = 7f
private val JUMP_IMPULSE = Vector2(0.0f, 25.0f)
private val JUMP_IMPULSE = Vector2(0.0f, 30.0f)
private val LEFT_FORCE = Vector2(-50f, 0.0f)
private val RIGHT_FORCE = Vector2(50f, 0.0f)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class MinigameRenderSystem : IteratingSystem() {
override fun inserted(entities: IntBag) {
for (i in 0 until entities.size()) {
val e: EntityInt = entities[i]
minigameMapper[e].minigame?.initalize(skin, stage)
minigameMapper[e].minigame?.initialize(skin, stage)
?: info { "Minigame not initialized upon creation: ${minigameMapper[e]}" }
}
}
Expand All @@ -65,6 +65,9 @@ class MinigameRenderSystem : IteratingSystem() {
batch.projectionMatrix = camera.combined

minigame.render(camera, batch, stage)

stage.act(Gdx.graphics.deltaTime)
stage.draw()
}
}

Expand Down
4 changes: 2 additions & 2 deletions core/src/me/srikavin/fbla/game/ecs/system/PhysicsSystem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ class PhysicsSystem(var physicsWorld: World, private val contactManager: Contact
val footBox = FixtureDef().apply {
// this.isSensor = true
this.shape = PolygonShape().apply {
setAsBox(0.5f, 0.05f, Vector2(0f, -1f), 0f)
setAsBox(0.55f, 0.05f, Vector2(0f, -1f), 0f)
}
friction = 1f
friction = .9f
}

val fixture = physics.body.createFixture(footBox)
Expand Down
68 changes: 48 additions & 20 deletions core/src/me/srikavin/fbla/game/map/MapLoader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ private const val COLLISION_LAYER_NAME = "Collision"
private const val TRIGGER_LAYER_NAME = "Trigger"
private const val MAP_SCALE_FACTOR = 1 / 32f

typealias TriggerProcessor = (mapObject: RectangleMapObject, type: String, path: String) -> Unit

/**
* Responsible for loading maps and their associated assets as well as spawning players. Tiled Maps that are to be loaded
* may contain any of the following layers:
Expand All @@ -50,7 +52,21 @@ class MapLoader {
private val playerAnimations = spritesheetLoader.loadAsespriteSheet("assets/graphics/characters/David.png",
"assets/graphics/characters/David.json")
private val coinSprite: TextureRegion = TextureRegion(Texture(Gdx.files.internal("assets/graphics/entity/coinGold.png")))
private val defaultTriggerProcessor: TriggerProcessor = { mapObject, _, path ->
error { throw RuntimeException("Spawn is of type ${mapObject.javaClass.name} instead of RectangleMapObject in $path") }
}

enum class UnloadType {
/**
* Unloads all objects in a level
*/
ALL,
/**
* Unloads all objects without the component []
*/
NonMinigame,
NONE
}

/**
* Creates a player entity with the given world and given position
Expand All @@ -61,14 +77,14 @@ class MapLoader {
val e = world.createEntity().edit()
.add(PhysicsBody().apply {
shape = PolygonShape().apply {
setAsBox(.6f, 1f)
setAsBox(.6f, .9f)
}
restitution = 0.1f
density = 1f
friction = 0.1f
})
.add(PlayerControlled())
.add(SpriteOffset(Vector2(-.75f, -1f)))
.add(SpriteOffset(Vector2(-.75f, -1.1f)))
.add(Transform().apply { position = pos })
.add(SwitchableAnimation().apply { animations = playerAnimations; currentState = "Stand" })
.add(FixedRotation())
Expand Down Expand Up @@ -102,19 +118,28 @@ class MapLoader {
*
* @param world The world to create entities in
* @param path The path to load maps from
* @param unload Whether or not to unload previous maps
* @param customTriggerProcessor A [TriggerProcessor] to handle triggers unknown to this MapLoader
*
* @return The id of the created [MapComponent]
*/
fun loadMap(world: World, path: String): EntityInt {
// Unload previously loaded maps
val entities = world.aspectSubscriptionManager[Aspect.all()].entities
val loadedMapMapper = world.getMapper(MapComponent::class.java)

for (i in 0 until entities.size()) {
if (loadedMapMapper.has(entities[i])) {
loadedMapMapper[entities[i]].map.disposeSafely()
fun loadMap(world: World, path: String, unload: UnloadType = UnloadType.ALL,
customTriggerProcessor: TriggerProcessor = defaultTriggerProcessor): EntityInt {
if (unload == UnloadType.ALL || unload == UnloadType.NonMinigame) {
// Unload previously loaded maps
val entities = world.aspectSubscriptionManager[Aspect.all()].entities
val loadedMapMapper = world.getMapper(MapComponent::class.java)
val minigameMapper = world.getMapper(MinigameComponent::class.java)

for (i in 0 until entities.size()) {
if (unload == UnloadType.NonMinigame && minigameMapper.has(entities[i])) {
continue
}
if (loadedMapMapper.has(entities[i])) {
loadedMapMapper[entities[i]].map.disposeSafely()
}
world.delete(entities[i])
}
world.delete(entities[i])
}

val map = TmxMapLoader().load(path, TmxMapLoader.Parameters().apply {
Expand Down Expand Up @@ -142,34 +167,38 @@ class MapLoader {
for (mapObject: MapObject in triggerLayer.objects) {
if (mapObject is RectangleMapObject) {
info { "Processing Trigger at ${mapObject.rectangle.x},${mapObject.rectangle.y} in $path" }
when (mapObject.properties?.get("type")) {
"spawn" -> {
val type = mapObject.properties?.get("type").toString()
when {
type == "spawn" -> {
playerPosition.x = mapObject.rectangle.x
playerPosition.y = mapObject.rectangle.y
spawnTriggerFound = true
}
"coin" -> {
type == "coin" -> {
val pos = Vector2()
createCoin(world, mapObject.rectangle.getPosition(pos).scl(MAP_SCALE_FACTOR))
info { "Making coin at $pos" }
}
else -> {
TriggerType.values().any { it.name == type.toUpperCase() } -> {
val rect = mapObject.rectangle

world.createEntity().edit()
.add(Transform())
.add(Transform().apply { position = rect.getCenter(recycledVector2).scl(MAP_SCALE_FACTOR).cpy() })
.add(PhysicsBody().apply {
shape = PolygonShape().apply {
setAsBox(rect.width * MAP_SCALE_FACTOR * .5f, rect.height * MAP_SCALE_FACTOR * .5f,
rect.getCenter(recycledVector2).scl(MAP_SCALE_FACTOR), 0f)
recycledVector2.setZero(), 0f)
}
type = BodyDef.BodyType.StaticBody
this.type = BodyDef.BodyType.StaticBody
})
.add(MapTrigger().apply {
type = TriggerType.valueOf(mapObject.properties.get("type").toString().toUpperCase())
this.type = TriggerType.valueOf(type.toUpperCase())
properties = mapObject.properties
})
}
else -> {
customTriggerProcessor(mapObject, type, path)
}
}
} else {
error { throw RuntimeException("Spawn is of type ${mapObject.javaClass.name} instead of RectangleMapObject in $path") }
Expand Down Expand Up @@ -265,7 +294,6 @@ class MapLoader {
info { fixtureDefs.toString() }
info { collisionLayer.objects.count.toString() }
editor.add(PhysicsBody(fixtureDefs, BodyDef.BodyType.StaticBody, 0f, 0.2f, 0f))
editor.entity

return mapEntity
}
Expand Down
71 changes: 67 additions & 4 deletions core/src/me/srikavin/fbla/game/minigame/Minigame.kt
Original file line number Diff line number Diff line change
@@ -1,27 +1,89 @@
package me.srikavin.fbla.game.minigame

import com.artemis.World
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.OrthographicCamera
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.maps.MapProperties
import com.badlogic.gdx.scenes.scene2d.Stage
import com.badlogic.gdx.scenes.scene2d.ui.Skin
import me.srikavin.fbla.game.MapTriggerDelegate
import me.srikavin.fbla.game.map.MapLoader

/**
* The baseclass that all minigames inherit from. This class handles level transitions and communications with outside
* systems. The field [mapProperties] is available to subclasses as a type-safe version of MapProperties.
*/
abstract class Minigame {
/**
* Stores whether the minigame is currently being played by the user.
*/
var active: Boolean = false
private set

private var nextLevel: String = ""

/**
* If a new map is loaded by a subclass, make sure to set [MapLoader.loadMap] with [MapLoader.UnloadType.NonMinigame]
* to avoid unloading this minigame instance.
*/
protected lateinit var mapLoader: MapLoader

/**
* Reset the minigame to its initial conditions. This will always be called before a minigame is made active.
* The entity-component-system world that can be used to create new entities.
*/
abstract fun reset(properties: MapProperties)
protected lateinit var world: World

/**
* A type safe version of the MapProperties provided within the trigger object in the map.
*/
protected lateinit var mapProperties: MapTriggerProperties

class MapTriggerProperties(val properties: MapProperties) {
val type: String by MapTriggerDelegate("type")
val subtype: String by MapTriggerDelegate("subtype")
val minigameType: String by MapTriggerDelegate("minigame_type")
val nextLevel: String by MapTriggerDelegate("next_level")
}

/**
* Reset the minigame to its initial conditions. This will always be called before [initialize].
*/
fun reset(properties: MapProperties, world: World, mapLoader: MapLoader) {
this.mapProperties = MapTriggerProperties(properties)
this.nextLevel = mapProperties.nextLevel
this.mapLoader = mapLoader
this.world = world

resetMinigame(properties)
}

/**
* Reset the minigame to its initial conditions. This will be called by [reset]
*/
protected abstract fun resetMinigame(properties: MapProperties)

/**
* Any inital UI initialization should occur here. The stage will not be modified outside of the minigame while it
* is active.
*/
abstract fun initalize(skin: Skin, stage: Stage)
fun initialize(skin: Skin, stage: Stage) {
active = true

initializeMinigame(skin, stage)
}

/**
* Immediately ends the minigame and transitions to the next level.
*/
fun endMinigame() {
active = false
Gdx.app.postRunnable {
mapLoader.loadMap(world, "assets/maps/$nextLevel")
}
}

protected abstract fun initializeMinigame(skin: Skin, stage: Stage)

/**
* The stage will not be modified outside of the minigame while the minigame remains active. No references to the
Expand Down Expand Up @@ -49,4 +111,5 @@ abstract class Minigame {
open fun allowPlayerMovement(): Boolean {
return false
}
}
}

11 changes: 6 additions & 5 deletions core/src/me/srikavin/fbla/game/minigame/MinigameManager.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package me.srikavin.fbla.game.minigame

import me.srikavin.fbla.game.minigame.dropcatch.DropcatchMinigame
import me.srikavin.fbla.game.minigame.quiz.QuizMinigame

class MinigameManager {
private val minigames = mapOf<String, Minigame>(
"quiz" to QuizMinigame()
// "dialogue"
// "buttonmash"
// "dropcatch"
private val minigames = mapOf(
"quiz" to QuizMinigame(),
"dropcatch" to DropcatchMinigame(),
"dialogue" to QuizMinigame(),
"buttonmash" to QuizMinigame()
)

fun getMinigame(name: String): Minigame {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package me.srikavin.fbla.game.minigame.dropcatch

import com.artemis.Component

enum class DropcatchItemType {
GOOD,
BAD
}

class DropcatchItemComponent : Component() {
lateinit var type: DropcatchItemType
}
Loading

0 comments on commit e553610

Please sign in to comment.