Skip to content

Commit

Permalink
Add triggers
Browse files Browse the repository at this point in the history
Improve documentation
Improve map loading
Add more DialogueCallables
Update assets version
Add contact listeners
Add proper jumping
  • Loading branch information
srikavin committed Jan 8, 2020
1 parent 627432c commit 49903d6
Show file tree
Hide file tree
Showing 36 changed files with 759 additions and 133 deletions.
10 changes: 4 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,13 @@ For more technical information, read `technical_readme.md`.
* [Gradle](https://gradle.org/) - used to automate builds and manage dependencies
* [Git SCM](https://git-scm.org) - used for version control
* [Kotlin](http://kotlinlang.org/) - used to write readable and ergonomic code
* [Tiled](https://www.mapeditor.org/) - used to create flexible levels
* [Tiled](https://www.mapeditor.org/) - used to create flexible and reusable levels
* [Aseprite](https://www.aseprite.org/) - used to design art in a retro pixelart style

### Dependencies Used
* [Kotlin](http://kotlinlang.org/) - used for null-safety and language features
* [libgdx](https://libgdx.badlogicgames.com/) - used as a game programming and graphics framework

## Screenshots

TODO




TODO
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

buildscript {
ext.kotlinVersion = '1.3.61'
repositories {
Expand Down
18 changes: 17 additions & 1 deletion core/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
apply plugin: "kotlin"
apply plugin: 'org.jetbrains.dokka'

sourceCompatibility = 1.7
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'

sourceSets.main.java.srcDirs = [ "src/" ]
buildscript {
dependencies {
classpath "org.jetbrains.dokka:dokka-gradle-plugin:0.10.0"
}

repositories {
jcenter()
}
}

dokka {
outputFormat = 'html'
outputDirectory = "$buildDir/dokka"
}

sourceSets.main.java.srcDirs = ["src/"]
33 changes: 22 additions & 11 deletions core/src/me/srikavin/fbla/game/FBLAGame.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,41 @@ import com.badlogic.gdx.scenes.scene2d.ui.Skin
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.ObjectMap
import com.badlogic.gdx.utils.viewport.ExtendViewport
import ktx.log.info
import me.srikavin.fbla.game.dialogue.callable.DialogueMeeting
import me.srikavin.fbla.game.ecs.component.DialogueComponent
import me.srikavin.fbla.game.ecs.system.*
import me.srikavin.fbla.game.map.MapLoader
import me.srikavin.fbla.game.physics.ContactListenerManager

const val cameraScale = 45f

class FBLAGame : ApplicationAdapter() {
lateinit var camera: OrthographicCamera
lateinit var world: World

override fun resize(width: Int, height: Int) {
info { "$width x $height : ${height.toFloat() / width}" }
camera.viewportHeight = cameraScale * (height.toFloat() / width)
camera.viewportWidth = cameraScale

camera.zoom = 1f
camera.update()
}

override fun create() {
val cameraScale = 45f
camera = OrthographicCamera(cameraScale, cameraScale * (9f / 16f))
camera.position.x = 0f
camera.position.y = cameraScale * (9f / 16f) * 0.75f

camera.zoom = 1f
camera.update()

val assetManager: AssetManager = AssetManager()
val assetManager = AssetManager()
val batch = SpriteBatch()


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


val generator = FreeTypeFontGenerator(Gdx.files.internal("assets/fonts/Kenney Pixel.ttf"))
val parameter = FreeTypeFontGenerator.FreeTypeFontParameter()
parameter.size = 48
Expand All @@ -68,20 +76,26 @@ class FBLAGame : ApplicationAdapter() {
root.top().right()
root.debug = true

val gameState = GameState(0)

val listenerManager = ContactListenerManager()
assetManager.setLoader(TiledMap::class.java, TmxMapLoader(InternalFileHandleResolver()))

val mapLoader = MapLoader()

val config = WorldConfigurationBuilder()
.with(InputSystem(listenerManager),
PhysicsSystem(physicsWorld, listenerManager),
TriggerSystem(listenerManager),
CameraFollowSystem(),
PlayerAnimationSystem(),
RenderSystem(),
BackgroundRenderSystem(),
EntityRenderSystem(),
MinigameSystem(),
MinigameRenderSystem(),
DialogueSystem(),
UISystem(),
TriggerSystem(listenerManager),
PhysicsDebugSystem(physicsWorld)
)
.with(TagManager())
Expand All @@ -92,16 +106,13 @@ class FBLAGame : ApplicationAdapter() {
.register(skin)
.register(stage)
.register(root)
.register(gameState)
.register(mapLoader)

world = World(config)

assetManager.setLoader(TiledMap::class.java, TmxMapLoader(InternalFileHandleResolver()))

val mapLoader = MapLoader(assetManager, world)
mapLoader.loadMap("assets/maps/level1.tmx")
mapLoader.loadMap(world, "assets/maps/level1.tmx")

world.createEntity().edit().add(DialogueComponent().apply { script = DialogueMeeting() })

}

override fun render() {
Expand Down
3 changes: 3 additions & 0 deletions core/src/me/srikavin/fbla/game/GameState.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package me.srikavin.fbla.game

data class GameState(var score: Int)
86 changes: 79 additions & 7 deletions core/src/me/srikavin/fbla/game/dialogue/DialogueCallable.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,60 @@ package me.srikavin.fbla.game.dialogue

import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ClosedReceiveChannelException
import kotlinx.coroutines.channels.sendBlocking
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import ktx.log.info

/**
* The base class that all dialogues inherit from. This class manages communication with a [DialogueManager] through
* Kotlin Rendezvous [Channel]s. All inheritable functions are blocking and will not return until a response packet
* is received from the DialogueManager.
*/
abstract class DialogueCallable {
private lateinit var channel: Channel<DialoguePacket>

fun say(str: String): Unit {
/**
* Sends a request to display a string to the user as if a game character is saying it. An artificial delay is added
* to provide time between multiple calls to this function.
*
* This function returns after the entire text has been displayed to the user and an additional delay has passed.
*
* @param str The message to display to the user
*
* @see SayDialoguePacket
* @see ResumeDialoguePacket
*/
fun say(str: String) {
channel.sendBlocking(SayDialoguePacket(str))

runBlocking {
when (val packet = channel.receive()) {
is ResumeDialoguePacket -> return@runBlocking
else -> throw RuntimeException("Received ${packet.javaClass.name} instead of ResumeDialoguePacket")
}
}

runBlocking {
delay(750)
}
}

/**
* Sends a request to receive a response from the user based on the given list of choices. This call blocks until
* the user has chosen a response.
*
* @param responses The list of possible responses to provide the user
* @return The index of the response that the user chose in [responses]
*
* @see RequestResponseDialoguePacket
* @see ReceiveResponseDialoguePacket
*/
fun getResponse(responses: List<String>): Int {
channel.sendBlocking(RequestResponseDialoguePacket(responses))

return runBlocking {
when (val packet = channel.receive()) {
is ReceiveResponseDialoguePacket -> return@runBlocking packet.index
Expand All @@ -33,8 +64,18 @@ abstract class DialogueCallable {
}
}

/**
* Sends a request to update the score for this dialogue instance. This call blocks until the request has been
* acknowledged by the DialogueManager.
*
* @param delta The relative score to add (or subtract) from the player's current score
*
* @see UpdateScoreDialoguePacket
* @see ResumeDialoguePacket
*/
fun updateScore(delta: Int) {
channel.sendBlocking(UpdateScoreDialoguePacket(delta))

runBlocking {
when (val packet = channel.receive()) {
is ResumeDialoguePacket -> return@runBlocking
Expand All @@ -43,8 +84,18 @@ abstract class DialogueCallable {
}
}

/**
* Sends a request to receive the current score for this dialogue instance. This call blocks until the request has been
* serviced by the DialogueManager.
*
* @return The player's current score
*
* @see GetScoreDialoguePacket
* @see ScoreDialoguePacket
*/
fun getScore(): Int {
channel.sendBlocking(GetScoreDialoguePacket)

return runBlocking {
when (val packet = channel.receive()) {
is ScoreDialoguePacket -> return@runBlocking packet.score
Expand All @@ -53,16 +104,37 @@ abstract class DialogueCallable {
}
}

/**
* Starts running the dialogue script and uses the given callable to communicate with a DialogueManager (or another
* class that follows the requirements of a DialogueManager). This call will not block and will be run on a
* coroutine thread.
*
* @param channel The channel to use to communicate requests and responses with a DialogueManager
*/
fun start(channel: Channel<DialoguePacket>) {
this.channel = channel
GlobalScope.launch {
run()
delay(750)
val score = getScore()
say("Score: $score")
channel.sendBlocking(EndDialoguePacket)
try {
GlobalScope.launch {
// Run the script
run()

delay(750)

// Display the score gained (or lost) by the user
val score = getScore()
say("Score: $score")

// Tell the DialogueManager that we're done
channel.sendBlocking(EndDialoguePacket)
}
} catch (e: ClosedReceiveChannelException) {
info(e) { "Channel closed" }
}
}

/**
* Runs the dialogue script. This function should only interact with the superclass [DialogueCallable] without
* reading or modifying any external data to avoid concurrency issues.
*/
protected abstract fun run()
}
39 changes: 31 additions & 8 deletions core/src/me/srikavin/fbla/game/dialogue/DialogueManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,34 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.rafaskoberg.gdx.typinglabel.TypingAdapter
import com.rafaskoberg.gdx.typinglabel.TypingLabel
import kotlinx.coroutines.channels.sendBlocking
import ktx.actors.onClickEvent
import me.srikavin.fbla.game.dialogue.callable.*
import me.srikavin.fbla.game.ecs.component.DialogueComponent

class DialogueManager(private val stage: Stage, skin: Skin) {
private val dialogueRoot: Table
private val dialogueTextContainer: Container<TypingLabel>
private val dialogueOptionsTable: Table
private val dialogueOptionsTable: Table = Table(skin)
private val dialogueText: TypingLabel

companion object {
private val dialogues = mapOf(
"meeting" to DialogueMeeting(),
"make_chapter" to DialogueMakeChapter(),
"job_interview" to DialogueJobInterview(),
"speech" to DialogueSpeech(),
"letter_rec" to DialogueLetterRec()
)

fun getDialogueCallable(name: String): DialogueCallable {
return dialogues.getValue(name)
}
}


var component: DialogueComponent? = null

init {
dialogueOptionsTable = Table(skin)
dialogueOptionsTable.center().bottom()

dialogueText = TypingLabel("", skin)
Expand All @@ -40,26 +56,27 @@ class DialogueManager(private val stage: Stage, skin: Skin) {
}
}


stage.addActor(dialogueRoot)
}

private fun handleDialoguePacket(component: DialogueComponent, packet: DialoguePacket) {
println(packet.javaClass.name)
when (packet) {
is RequestResponseDialoguePacket -> {
println(packet.options)
dialogueOptionsTable.clear()
val width = (stage.width - (100f * (5 - packet.options.size))) / packet.options.size
println(width)
packet.options.forEachIndexed { index, s ->
val label = dialogueOptionsTable.add("[${index + 1}] $s").pad(10f).width(width)
label.actor.setWrap(true)
label.actor.setFontScale(0.5f)
label.actor.onClickEvent { _, _ ->
component.waitingForResponse = false
component.channel.offer(ReceiveResponseDialoguePacket(index))
}
}
component.waitingForResponse = true
}
is SayDialoguePacket -> {
println(packet.message)
dialogueOptionsTable.clear()
dialogueText.setText(packet.message)
dialogueText.restart()
Expand All @@ -68,7 +85,6 @@ class DialogueManager(private val stage: Stage, skin: Skin) {
component.channel.sendBlocking(ScoreDialoguePacket(component.score))
}
is UpdateScoreDialoguePacket -> {
println(packet.delta)
component.score += packet.delta
component.channel.sendBlocking(ResumeDialoguePacket)
}
Expand Down Expand Up @@ -99,7 +115,14 @@ class DialogueManager(private val stage: Stage, skin: Skin) {


fun update() {
val component = this.component ?: return
val component = this.component

if (component == null) {
dialogueOptionsTable.clear()
dialogueText.setText("")
return
}


handleKeyPress()
val packet = component.channel.poll()
Expand Down
Loading

0 comments on commit 49903d6

Please sign in to comment.