DEV Community

Cover image for Ktvn: A library for developing visual novels in Kotlin
Ben Pollard
Ben Pollard

Posted on

Ktvn: A library for developing visual novels in Kotlin

For a while I've been working on Ktvn, a framework for creating visual novels in Kotlin.

With Ktvn I tried to create a DSL that felt natural and clean to use. Here's an example scene:

internal fun onTheLaunchPad(): Scene {
    return scene {
        this name "On the launch pad"
        this background shuttleDay
        this music shuttleDayMusic
        this layout createLayout {
            this addLeftOf sophie
            this addRightOf toki
            this configure configuration.gameAdapter.layoutAdapter
        }
        this steps listOf(
            next {
                layout moveCenter sophie
                sophie looks normal
                sophie says "Where has that fool gotten to now?"
            },
            next { audio sfx sfxWoosh },
            next {
                layout moveLeft sophie
                layout moveRight toki
                toki looks normal
                toki says "Here I am!"
                toki says "Ready for duty!"
            },
            next {
                sophie looks happy
                sophie says "Ever the fool."
            }
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

For those interested here is a quick guide to getting started.

Adding the package to your project

You need to pull Ktvn into your project. The easiest way to do this is to add the package. The latest package and installation instructions are available here.

First Visual Novel

Once the package has been installed it's time to jump in and start building your first visual novel.

Setup

To start with create a new Console application. It should look something like this:

package com.github.benpollarduk.ktvn.gettingstarted

fun main(args: Array<String>) {

}
Enter fullscreen mode Exit fullscreen mode

Adding the GameEngine

Every Ktvn game requires a GameEngine. The only engine currently included in the Ktvn core library is AnsiConsoleGameEngine. This engine allows a visual novel to be executed on an ANSI compatible console.

val engine = AnsiConsoleGameEngine()
Enter fullscreen mode Exit fullscreen mode

Adding the GameConfiguration

Every Ktvn game requires a GameConfiguration. The configuration ties together many of the concepts required to execute a game. For nearly all applications the DynamicGameConfiguration is a good match because it allows
properties to be specified after its instantiation. Let's add one in the main function and apply the engine.

val configuration = DynamicGameConfiguration().also {
    it.engine = engine
}
Enter fullscreen mode Exit fullscreen mode

Adding a Character and a Narrator

All visual novels will require at least a Narrator or a Character to tell the story. Let's add one of each to the main function, after the engine.

val ben = Character("Ben", configuration.gameAdapter.characterAdapter)
Enter fullscreen mode Exit fullscreen mode

Here I've added a character called Ben (of course) and specified that the adapter is provided by the configuration.
The adapter allows the Character to interact with the engine.

Next I'll add a Narrator.

val narrator = Narrator(configuration.gameAdapter.narratorAdapter)
Enter fullscreen mode Exit fullscreen mode

I've specified that the adapter is provided by the configuration. The adapter allows the Narrator to interact with the engine.

Creating a basic visual novel structure

The next thing to do is to add the structure of the novel itself. We can use the DSL to simplify the process. To start with, let's add a story in the main function. The story will only contain a single chapter, which contains a single scene.

val story = story {
    this add chapter {
        this add scene {

        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Story, Chapter and Scene can all have a name, lets name the scene.

val story = story {
    this add chapter {
        this add scene {
            this name "Welcome to Ktvn"
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Each scene that contains Characters should have a layout to help with the positioning of characters. The layout needs to be configured so that the game engine can respond to character movements.

val story = story {
    this add chapter {
        this add scene {
            this name "Welcome to Ktvn"
            this layout createLayout {
                this configure configuration.gameAdapter.layoutAdapter
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Adding some steps

Each scene can contain one or more steps. Let's add a simple step to introduce the story.

val story = story {
    this add chapter {
        this add scene {
            this name "Welcome to Ktvn"
            this layout createLayout {
                this configure configuration.gameAdapter.layoutAdapter
            }
            next { narrator narrates "Welcome to the story!" }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Next, we could move the character in to view.

val story = story {
    this add chapter {
        this add scene {
            this name "Welcome to Ktvn"
            this layout createLayout {
                this configure configuration.gameAdapter.layoutAdapter
            }
            next { narrator narrates "Welcome to the story!" }
            next { layout moveLeft ben }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

And then we can make the character speak.

val story = story {
    this add chapter {
        this add scene {
            this name "Welcome to Ktvn"
            this layout createLayout {
                this configure configuration.gameAdapter.layoutAdapter
            }
            next { narrator narrates "Welcome to the story!" }
            next { layout moveLeft ben }
            next { ben says "Hi." }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

And then make the character smile.

val story = story {
    this add chapter {
        this add scene {
            this name "Welcome to Ktvn"
            this layout createLayout {
                this configure configuration.gameAdapter.layoutAdapter
            }
            next { narrator narrates "Welcome to the story!" }
            next { layout moveLeft ben }
            next { ben says "Hi." }
            next { ben looks happy }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

There is much more that can be done, but this is a simple example just to get started.

Executing the game

There is a small amount to do to make the story executable. First, it needs to be wrapped in to a VisualNovel. Then a new Game needs to be made from the VisualNovel so that the story can be executed.

val novel = VisualNovel.create(story, configuration)
val exampleGame = Game(novel)
Enter fullscreen mode Exit fullscreen mode

The GameExecutor class can execute instances of Game. Games can be executed either synchronously or asynchronously.

GameExecutor.executeAysnc(exampleGame)
Enter fullscreen mode Exit fullscreen mode

A quirk of the AnsiConsoleEngine is that we need to start processing the input from the standard input to be able to interact with the game. We will need to stop processing this input when the game execution ends. We will start processing the input when the game begins and stop when execution finishes.

GameExecutor.executeAysnc(exampleGame) {
        engine.endProcessingInput()
    }

engine.beginProcessingInput()
Enter fullscreen mode Exit fullscreen mode

Bringing it all together

That was a lot to go through, but results in only a small amount of code. It should look like this:

package com.github.benpollarduk.ktvn.getting.started

import com.github.benpollarduk.ktvn.characters.Character
import com.github.benpollarduk.ktvn.characters.Emotions.happy
import com.github.benpollarduk.ktvn.characters.Narrator
import com.github.benpollarduk.ktvn.layout.Layout
import com.github.benpollarduk.ktvn.layout.Layout.Companion.createLayout
import com.github.benpollarduk.ktvn.logic.Game
import com.github.benpollarduk.ktvn.logic.GameExecutor
import com.github.benpollarduk.ktvn.logic.VisualNovel
import com.github.benpollarduk.ktvn.logic.configuration.DynamicGameConfiguration
import com.github.benpollarduk.ktvn.logic.engines.ansiConsole.AnsiConsoleGameEngine
import com.github.benpollarduk.ktvn.structure.Chapter
import com.github.benpollarduk.ktvn.structure.Chapter.Companion.chapter
import com.github.benpollarduk.ktvn.structure.Scene
import com.github.benpollarduk.ktvn.structure.Scene.Companion.scene
import com.github.benpollarduk.ktvn.structure.Story
import com.github.benpollarduk.ktvn.structure.Story.Companion.story
import com.github.benpollarduk.ktvn.structure.steps.Then.Companion.next

fun main(args: Array<String>) {
    val engine = AnsiConsoleGameEngine()
    val configuration = DynamicGameConfiguration().also {
        it.engine = engine
    }

    val ben = Character("Ben", configuration.gameAdapter.characterAdapter)
    val narrator = Narrator(configuration.gameAdapter.narratorAdapter)

    val story = story {
        this add chapter {
            this add scene {
                this name "Welcome to Ktvn"
                this layout createLayout {
                    this configure configuration.gameAdapter.layoutAdapter
                }
                next { narrator narrates "Welcome to the story!" }
                next { layout moveLeft ben }
                next { ben says "Hi." }
                next { ben looks happy }
            }
        }
    }

    val novel = VisualNovel.create(story, configuration)
    val exampleGame = Game(novel)

    GameExecutor.executeAysnc(exampleGame) {
        engine.endProcessingInput()
    }

    engine.beginProcessingInput()
}
Enter fullscreen mode Exit fullscreen mode

Simply build and run the application and congratulations, you have a working Ktvn visual novel!

Top comments (2)

Collapse
 
gamelibgdxunity profile image
Vinh Vu

Any link to a demo? Can we play on web?

Collapse
 
alexeyjarlax profile image
Alexey_Pavlov • Edited

Сборка под Maven...?
Как твою библиотеку применить под Gradle+Kotlin, у меня в проекте нет pom.xml