Swift wrapper for the Ink narrative scripting language. Based on InkJS.
- Apple and Linux platforms (using JXKit). If you want to use the JavaScriptCore version (Apple platforms only), use release 0.0.4.
- Loading compiled Ink stories
loadStory(json: String)
as well as Ink directlyloadStory(ink: String)
; - Basic flow: continue story
continueStory()
and choiceschooseChoiceIndex(_ index: Int)
; - Moving to knots/stitches
moveToKnitStitch(_ knot: String, stitch: String? = nil)
; - Tag support. Read
currentTags
variable; - Setting and getting variable values (supports strings, 32-bit integers and doubles);
- Loading and saving state
stateToJSON()
andloadState(_ jsonDataString: String)
; - On Apple platforms: Combine integration (subscribe to state changes, observe variables).
JXKit on Linux requires JavaScriptCore libraries, that are part of WebKit. For Ubuntu 20.04 and newer:
sudo apt install libwebkit2gtk-4.0-dev
- When using the built in Ink compiler, don't use JavaScript tokens that can confuse string definitions, like `.
Use 'File' -> 'Swift Packages' -> 'Add Package Dependency...' to add the package to your project.
Add InkSwift as a dependency to Package.swift
:
let package = Package(
name: "test_swiftpm", // choose your own name
platforms: [
.macOS(.v10_15),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(url: "https://github.com/maartene/InkSwift.git", from: "2.0.0")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "test_swiftpm",
dependencies: ["InkSwift"]),
.testTarget(
name: "test_swiftpmTests",
dependencies: ["test_swiftpm"]),
]
)
Start by creating a InkStory
let story = InkStory()
Then load a story from a Ink JSON (you can use Inklecate or Inky to convert an .ink file to a .json file.):
let storyJSON = ... //
story.loadStory(json: storyJSON)
Alternatively, you can load a story from Ink directly. Not however, that this might hinder performance for users.
story.loadStory(ink: "Hello, World!")
You can create a very basic command line 'player' using just a few lines of code:
// As long as the story can continue (either because there is more text or there are options you can choose)), keep the loop going
while story.canContinue || story.options.count > 0 {
// Print the current text to the console/terminal
print(story.currentText)
// If you can continue the story, we wait for input before continuing.
if story.canContinue {
print("Press 'enter' to continue")
_ = readLine()
story.continueStory()
}
// If there are options, show the options and wait for player to choose
else if story.options.count > 0 {
// print every option
for option in story.options {
print("\(option.index). \(option.text)")
}
print("What is your choice?")
// wait for input from player
if let choice = readLine() {
// try and convert input to an index.
if let index = Int(String(choice.first ?? "a")) {
// choose the selected option index
story.chooseChoiceIndex(index)
}
}
}
}
// no more content, story is done.
print("Story done!")
InkStory conforms to the ObservableObject
protocol. This makes using it in Combine possible and SwiftUI very easy. A simple example SwiftUI view that can play an Ink story would contain:
Add
import InkSwift
to ContentView.swift
Add the following property to your ContentView:
@StateObject var story = InkStory()
Note: change the filename to load to your own JSON file. Don't forget to add it to the project.
func loadStory() {
guard let url = Bundle.main.url(forResource: "test.ink", withExtension: "json") else {
fatalError("Could not find ink story file.")
}
guard let storyJSON = try? String(contentsOf: url) else {
fatalError("Could not load story file.")
}
story.loadStory(json: storyJSON)
}
var body: some View {
VStack {
Text(story.currentText)
if story.canContinue {
Button("Continue") {
story.continueStory()
}
}
ForEach(story.options, id: \.index) { option in
Button(option.text) {
story.chooseChoiceIndex(option.index)
}
}
}.padding()
.onAppear {
loadStory()
}
}
- Griff added support for choice tags.
- The Ink runtime uses the official Ink Javascript port InkJS
- The Compiler tests use The Intercept as an example story to compile
- Uses JSONEquality in some of the tests
- Cross-platform JavaScript runtime JXKit