Skip to content

Game Engine

Overview

The GameEngine is a critical part of Ktvn. Essentially, the GameEngine ties together a Game and the environment it executes in. The GameEngine is responsible for managing game input and output. The GameEngine interface is so important because it allows a VisualNovel to be written largely without a target platform in mind. Included in the Ktvn repo are a couple of examples of GameEngine implementations.

AnsiConsoleGameEngine

Included in Ktvn is an example implementation of a GameEngine that targets an ANSI compatible console, AnsiConsoleGameEngine. Running a visual novel in a basic terminal window with limited visuals seems pretty counterproductive, but it is a clear way of demonstrating some aspects of implementing a GameEngine.

DebugGameEngine

In app-ktvn-prototyper-swing there is an example implementation of a GameEngine that targets Swing. This is used in the prototyper app. It's by no means intended as an engine to use in a production game, but serves the purpose of demonstrating some of the visual features on Ktvn and is the engine that drives the prototyper app which can be used for rough prototyping of games.

Implementing a custom GameEngine

It's important to understand the role of the GameEngine so that rich novels can be written that target any platform. In this next section I'll describe how a GameEngine can be created to target a platform.

Creating a new GameEngine

Start by creating a class and inheriting the GameEngine interface.

package com.github.benpollarduk.ktvn.example.engine

import com.github.benpollarduk.ktvn.logic.engines.GameEngine

class ExampleEngine : GameEngine {
}

Implement the interface to create stubs. We will discuss those in more detail in a bit.

package com.github.benpollarduk.ktvn.example.engine

import com.github.benpollarduk.ktvn.audio.SoundEffect
import com.github.benpollarduk.ktvn.audio.VolumeManager
import com.github.benpollarduk.ktvn.characters.Character
import com.github.benpollarduk.ktvn.characters.Emotion
import com.github.benpollarduk.ktvn.characters.Narrator
import com.github.benpollarduk.ktvn.characters.animations.Animation
import com.github.benpollarduk.ktvn.layout.Position
import com.github.benpollarduk.ktvn.layout.transitions.LayoutTransition
import com.github.benpollarduk.ktvn.logic.Answer
import com.github.benpollarduk.ktvn.logic.Flags
import com.github.benpollarduk.ktvn.logic.ProgressionController
import com.github.benpollarduk.ktvn.logic.Question
import com.github.benpollarduk.ktvn.logic.engines.GameEngine
import com.github.benpollarduk.ktvn.structure.*
import com.github.benpollarduk.ktvn.structure.transitions.SceneTransition
import com.github.benpollarduk.ktvn.text.log.Log

class ExampleEngine : GameEngine {
    override val log: Log
        get() = TODO("Not yet implemented")
    override val progressionController: ProgressionController
        get() = TODO("Not yet implemented")
    override val volumeManager: VolumeManager
        get() = TODO("Not yet implemented")

    override fun playSoundEffect(soundEffect: SoundEffect) {
        TODO("Not yet implemented")
    }

    override fun characterAsksQuestion(character: Character, question: Question) {
        TODO("Not yet implemented")
    }

    override fun narratorAsksQuestion(narrator: Narrator, question: Question) {
        TODO("Not yet implemented")
    }

    override fun getAnswerToQuestion(question: Question): Answer {
        TODO("Not yet implemented")
    }

    override fun characterSpeaks(character: Character, line: String) {
        TODO("Not yet implemented")
    }

    override fun characterThinks(character: Character, line: String) {
        TODO("Not yet implemented")
    }

    override fun characterShowsEmotion(character: Character, emotion: Emotion) {
        TODO("Not yet implemented")
    }

    override fun characterAnimation(character: Character, animation: Animation) {
        TODO("Not yet implemented")
    }

    override fun characterMoves(character: Character, from: Position, to: Position, transition: LayoutTransition) {
        TODO("Not yet implemented")
    }

    override fun narratorNarrates(narrator: Narrator, line: String) {
        TODO("Not yet implemented")
    }

    override fun enterStory(story: Story) {
        TODO("Not yet implemented")
    }

    override fun exitStory(story: Story) {
        TODO("Not yet implemented")
    }

    override fun enterChapter(chapter: Chapter, transition: ChapterTransition) {
        TODO("Not yet implemented")
    }

    override fun exitChapter(chapter: Chapter) {
        TODO("Not yet implemented")
    }

    override fun enterScene(scene: Scene, transition: SceneTransition) {
        TODO("Not yet implemented")
    }

    override fun exitScene(scene: Scene, transition: SceneTransition) {
        TODO("Not yet implemented")
    }

    override fun clearScene(scene: Scene) {
        TODO("Not yet implemented")
    }

    override fun enterStep(step: Step, flags: Flags, canSkip: Boolean, cancellationToken: CancellationToken) {
        TODO("Not yet implemented")
    }

    override fun exitStep(step: Step, flags: Flags) {
        TODO("Not yet implemented")
    }
}

Next set up the Log, ProgressionController and VolumeManager properties. These can just be instantiated with default constructors.

override val log: Log = Log()
override val progressionController: ProgressionController = ProgressionController()
override val volumeManager: VolumeManager = VolumeManager()

The Log provides all logging for in game events so that users can review the story. If you use DynamicGameConfiguration this will be automatically populated, but if you make your own GameConfiguration you will need to populate the log yourself.

The ProgressionController controls progression through the story. This allows you to access Auto and Skip modes.

The VolumeManager gives you a central point to manage all volumes for music, sound effects and other audio. The VolumeManager does not directly control the system volume, it just provides a normalised value to represent the relative volume that audio should be played at for each type of audio.

Now it is just a case of writing some code for each stub. The stubs are described below.

playSoundEffect

Occurs when a sound effect should be played.

characterAsksQuestion

Occurs when a character asks a question.

narratorAsksQuestion

Occurs when a narrator asks a question.

getAnswerToQuestion

Occurs when an answer to a question should be determined.

characterSpeaks

Occurs when a character speaks.

characterThinks

Occurs when a character thinks.

characterShowsEmotion

Occurs when a character shows emotion.

characterAnimation

Occurs when a character starts an animation.

characterMoves

Occurs when a character moves between positions in a layout.

narratorNarrates

Occurs when a narrator narrates.

enterStory

Occurs when a story is entered.

exitStory

Occurs when a story is exited.

enterChapter

Occurs when a chapter is entered.

exitChapter

Occurs when a chapter is exited.

enterScene

Occurs when a scene is entered.

exitScene

Occurs when a scene is exited.

clearScene

Occurs when a scene should be cleared.

enterStep

Occurs when a step is entered.

exitStep

Occurs when a step is exited.

Applying your GameEngine

Don't forget to apply your engine to the GameConfiguration. Unless you have implemented your own GameConfiguration this will likely be the DynamicGameConfiguration. After you have done this, you should be all set!

val configuration = DynamicGameConfiguration().also {
    it.engine = YourEngine()
}