Narratore
是一个 Swift 库,可用于创建和运行交互式故事和叙事游戏。
使用 Narratore
,您可以使用 DSL 创建故事,该 DSL 允许您专注于叙述,只需编写很少的代码。在 Narratore
中,**故事是一个 Swift 包**。
该库还简化了运行故事的过程,并提供了基于回调的处理程序。
这是一个使用 Narratore
定义游戏的最简示例:
import Foundation
import Narratore
// ------ Define a game setting ------ //
enum MyGame: Setting {
enum Generate: Generating {
static func randomRatio() -> Double {
Double((0...1000).randomElement()!)/1000
}
static func uniqueString() -> String {
UUID().uuidString
}
}
struct Message: Messaging {
var id: String?
var text: String
}
struct Tag: Tagging {
var value: String
init(_ value: String) {
self.value = value
}
}
struct World: Codable {
var isEnjoyable = true
}
}
// ------ Write a story ------ //
extension SceneType {
typealias Game = MyGame
}
extension MyGame: Story {
static let scenes: [RawScene<MyGame>] = [
MyFirstScene.raw,
MySecondScene_Main.raw,
MySecondScene_Other.raw,
]
}
struct MyFirstScene: SceneType {
typealias Anchor = String
var steps: Steps {
"Welcome"
"This is your new game, built with narratore".with(tags: [.init("Let's play some sound effect!")])
DO.check {
.inCase($0.world.isEnjoyable) {
.tell { "Enjoy!" }
}
}
"Now choose".with(anchor: "We could jump right here from anywhere")
DO.choose { _ in
"Go to second scene, main path".onSelect {
.tell {
"Let's go to the second scene!"
.with(id: "We can keep track of this message")
} then: {
.transitionTo(MySecondScene_Main(magicNumber: 42))
}
}
"Go to second scene, alternate path".onSelect {
.tell {
"Going to the alternate path of the second scene"
} then: {
.transitionTo(MySecondScene_Other())
}
}
}
}
}
struct MySecondScene_Main: SceneType {
var magicNumber: Int
var steps: [SceneStep<Self>] {
"Welcome to the second scene"
if magicNumber == 42 {
"The magic number is \(magicNumber)"
} else {
"The magic number doesn't look right..."
}
"Hope you'll find this useful!"
}
}
struct MySecondScene_Other: SceneType {
var steps: [SceneStep<Self>] {
"I see you chose the alternate path"
"Bad luck!"
}
}
// ------ Run the game ------ //
final class MyHandler: Handler {
typealias Game = MyGame
func handle(event: Event<MyGame>) {
if case .gameEnded = event {
print("Thanks for playing!")
}
}
func acknowledge(narration: Narration<MyGame>) async -> Next<MyGame, Void> {
for message in narration.messages {
print(message)
_ = readLine()
}
return .advance
}
func make(choice: Choice<MyGame>) async -> Next<MyGame, Option<MyGame>> {
for (index, option) in choice.options.enumerated() {
print(index, option.message)
}
while true {
guard
let captured = readLine(),
let selected = Int(captured),
choice.options.indices.contains(selected)
else {
print("Invalid input")
continue
}
return .advance(with: choice.options[selected])
}
}
func answer(request: Player<MyGame>.TextRequest) async -> Next<MyGame, Player<MyGame>.ValidatedText> {
if let message = request.message {
print(message)
}
guard let text = readLine() else {
return .replay
}
switch request.validate(text) {
case .valid(let validatedText):
return .advance(with: validatedText)
case .invalid(let optionalMessage):
if let optionalMessage {
print(optionalMessage)
}
return .replay
}
}
}
@main
enum Main {
static func main() async {
await Runner<MyGame>(
handler: MyHandler(),
status: .init(
world: .init(),
scene: MyFirstScene()
)
).start()
}
}
要了解 Narratore
每个主要组件的详细信息,请查看以下文档:
Narratore
被设计成模块化和可扩展的。事实上,每个主要组件都可以在单独的 Swift 包中定义和实现。例如:
要了解如何扩展 Narratore
并定义模块化组件,请查看 扩展 Narratore。
链接的文档逐步构建了一个基本游戏设定、一个短篇故事、一个简单的命令行运行器和一些扩展,每个组件都可以在一个名为 SimpleGame 的配套包中找到,其目的是通过构建一个可以从命令行运行的实际故事来展示 Narratore
的基本原理。
配套包的主要目的是记录 Narratore
的功能;尽管如此,它的大部分代码都是通用的和可重用的,并且可以用于创建游戏:请参阅配套包 README 以了解如何在您的项目中使用它。
感谢您查看 Narratore
,希望您玩得开心!
Narratore
需要 iOS 13
和 macOS 10.15
,并且没有第三方依赖项。
Narratore
很大程度上受到了 Ink 的启发,其最初目的是成为一个类似的故事创建引擎,但可以使用 Swift 定义故事,而不是使用标记语言。尽管如此,Ink 规范对于 Narratore
的功能是一个强大的灵感来源。