OlivaStory For OlivOS 自定义故事路线图编写指南
For Ver.3.3.24(1074)
世界是属于每一个人的。要创造一个充满逻辑并尊重每一个人的世界。
——《Новый Элемент Расселения》A.D.1960 Москва
开始使用
OlivaStoryCore
是在OlivaDice
的3.3.24
版本中同步发布的新一代文游引擎,它的设计初衷是为了让骰主能够更加方便地进行文游的设计,以文游的方式进行带团,以及游玩一些类似向火独行
的文游,为骰主提供了更多的自由度。
该模块需要加载一种故事路线图
扩展文件,这种文件通常使用Json
或者Json5
编写。
指令设计
OlivaStoryCore
提供了以下两个指令,可以用于故事的基础启动和结束:
.story [故事名称]
用于启动一个故事。如:.story 向火独行
.story end
用于结束当前故事。
此外,你还可以在有对应故事选项的前提下,直接输入对应序号来选择选项,如:
Bot:
你勇敢地抵抗,但这男人的块头和决心还是压服了你。最后他打出一记重拳,你摔倒在地,眼前顿时一片黑暗。选项如下:
1 - 继续
2 - 就此放弃用户:
2
加载地址
对于一般使用,你只需要将你编写或者收集的故事路线图
文件放到plugin\data\OlivaStory\unity\extend\story
目录下即可,这个目录会在你第一次运行时生成,重载插件后将会将你的文件加载进去。
故事路线图
一个简单的故事路线图的文件格式如下
{
{
"name": "故事名称",
"author": "lunzhiPenxil",
"version": "1.0.0",
"svn": 1,
"ingress": "1",
"description": "故事描述",
"story": [
{
"flag": "1",
"text": "节点故事描述",
"type": null, // 节点类型,null为普通节点,没有此项时默认为null
"selection": [
{
"text": "选项1",
"toType": "jump",
"to": "2"
},
{
"text": "选项2",
"toType": "jump",
"to": "3",
"hideNote": "知晓秘密" // 隐藏选项条件
}
// ... 各个选项
]
}
// ... 各个故事节点
]
}
}
基本信息
可以看到,故事路线图文件是一个object
,主要由两部分组成:故事信息
和故事节点
,其中除了story
以外的内容都是故事信息
,具体对照如下:
名称 | 类型 | 说明 |
---|---|---|
name | string | 故事名称 |
author | string | 作者 |
version | string | 版本号 |
svn | number | svn版本号 一个递增的整数版本号 用于识别更新递进关系 |
ingress | string | 入口节点 开始故事时将会从这个节点开始 对应节点的 flag |
description | string | 故事描述 |
story | array | 故事节点 详情请看后文 |
故事节点
story
是一个array
,里面包含了所有的故事节点
,每一个故事节点都是一个object
,其中有如下:
名称 | 类型 | 说明 |
---|---|---|
flag | string | 节点标识 用于标识该节点 它应当在众多节点之间保持唯一 PS: 在 selection 中使用toType:jump 时,to 的值就是这个 |
text | string | 节点故事描述 |
type | string | 节点类型 默认为 null ,null 为普通节点其它值请看后文 |
selection | array | 节点选项 详情请看后文 |
各节点类型
type
字段用于标识节点的类型,目前支持的类型有:
名称 | 说明 |
---|---|
null | 普通节点 |
"end" | 结局节点 当玩家选择了这个选项时,故事将会结束 |
"ra" | 检定节点 这种类型的节点要求玩家使用一次 ra 指令才能继续将会根据检定结果选择 selection 中的选项 |
null | 普通节点
这是最基本的节点类型,它没有任何特殊的功能,只是一个普通的节点,当玩家选择了这个选项时,将会向玩家提供你所列出的选项,当玩家选择了某个选项,故事将会继续进行。
{
"flag": "节点2",
"text": "你勇敢地抵抗,但这男人的块头和决心还是压服了你。最后他打出一记重拳,你摔倒在地,眼前顿时一片黑暗。",
"selection": [
{
"text": "继续",
"toType": "jump",
"to": "节点108"
},
{
"text": "就此放弃",
"toType": "jump",
"to": "节点456"
}
]
}
回显如下:
你勇敢地抵抗,但这男人的块头和决心还是压服了你。最后他打出一记重拳,你摔倒在地,眼前顿时一片黑暗。
选项如下:
1 - 继续
2 - 就此放弃
"end" | 结局节点
这种节点是整个故事的结局,可以有很多个,所以它将没有selection
。
此外,你可以使用endFlag
字段来唯一的标识这个结局。
额外的,如果你写了一个type:null
的普通节点,但是缺少selection
或为空,那么这个节点也将被视为结局节点。
{
"flag": "节点65",
"text": "火舌舔上你的双腿。你的眼中充满泪水。你被烟幕笼罩了;\n\
火焰先你一步,你失去意识,被烈焰烧死!【剧终】。",
"type": "end",
"endFlag": "bad_end_1"
}
回显如下:
火舌舔上你的双腿。你的眼中充满泪水。你被烟幕笼罩了;
火焰先你一步,你失去意识,被烈焰烧死!【剧终】。道路的尽头
"ra" | 检定节点
这种节点要求玩家使用一次ra
指令才能继续,而不是直接选择选项,它将会根据检定结果选择selection
中的选项。
你依然可以使用selection
列出这个节点可以跳转的选项。
但是这些选项需要跟随raMap
所列出的条件,当条件满足时,将会自动选择这个条件对应的选项。
raMap
是一个array
,里面包含了所有的检定条件
,每一个检定条件都是一个object
,其中有如下:
名称 | 类型 | 说明 |
---|---|---|
para | string | 检定条件 使用 {raRollValue} 表示检定结果使用 {raSkillValue} 表示技能值 |
selection | number | 选项序号 当检定条件满足时,将会自动选择这个选项 序号从 0 开始 |
其中para
字段的值是一个string
,它是一个python
的表达式(使用eval
函数实现),你可以使用以下变量{变量名}
的方式引入变量,可以使用的变量有如下:
名称 | 类型 | 说明 |
---|---|---|
raRollValue | number | 检定掷骰结果值 |
raSkillValue | number | 技能值 |
当正常情况下,para
表达式应当返回一个bool
类型的值,判断将会从上往下依次进行,当这个值为True
时,它将会选择selection
中对应的选项,并且忽略它以下的判定条件,所以建议将最可能发生的情况放在最前面,并且在最后放置一个True
条件,以防止所有条件都不满足时的情况。
此外需要注意,raMap
中的节点的selection
是对应实际的selection
中的自上而下顺序,并且是从0
开始的。
{
"flag": "节点65-2",
"text": "火舌舔上你的双腿。你的眼中充满泪水。你被烟幕笼罩了;\n\
也许是你的想象,但你认为你感觉到铁链有一点点松动。\n\
你用尽全力挣扎,全然不顾铁链在你的手腕上拴得有多么牢固。\n\
进行一次“力量”检定。\n\
使用[.ra 力量]进行检定,请确保使用该指令前已经录入该技能……",
"type": "ra",
"raSkillName": "力量",
"raMap": [
{
"para": "{raRollValue}<={raSkillValue}",
"selection": 0
},
{
"para": "True",
"selection": 1
}
],
"selection": [
{
"text": "成功",
"toType": "jump",
"to": "节点93"
},
{
"text": "失败",
"toType": "jump",
"to": "节点77"
}
]
},
回显如下:
火舌舔上你的双腿。你的眼中充满泪水。你被烟幕笼罩了;
也许是你的想象,但你认为你感觉到铁链有一点点松动。
你用尽全力挣扎,全然不顾铁链在你的手腕上拴得有多么牢固。
进行一次“力量”检定。
使用[.ra 力量]进行检定,请确保使用该指令前已经录入该技能……。使用[.ra]指令完成检定以继续
节点选项
selection
是一个array
,里面包含了所有的节点选项
,每一个节点选项都是一个object
,其中有如下:
名称 | 类型 | 说明 |
---|---|---|
text | string | 选项描述 |
toType | string | 选项跳转类型 目前仅有 jump ,为跳转到指定节点 |
to | string | 选项跳转目标 当 toType 为jump 时有效,表示选择该选项时跳转到的下一个节点 |
隐藏选项
你可以使用hideNote
字段来隐藏某个选项,当玩家满足了这个条件时,这个选项将会被解除隐藏,否则即使玩家知道这个选项,也无法选择它。
在故事节点中使用
在故事节点中使用hideNote
字段,那么玩家在经过这个节点后,就会在本轮故事中携带着这个字段中所标示的所有线索了,这将会影响到后续玩家面对选择时的效果。
{
"flag": "节点159",
"text": "杰哥在阴影中停顿了一下:“你有以前那些人没有过的东西。也许你能挺过去。如果你想知道更多,今天晚上再来找我。九点钟。”他举起一根枯瘦的手指。“不要被人跟踪了。”\n\
杰哥又用袖子抹了抹鼻子。“走吧。他们一直监视着我。还有,阿伟?别想着逃走。你成功不了的。”\n\
你退回阳光下。阳光刺目,你十分地动摇。\n\
你发现了一个秘密。今天晚些时候,会有文字提示你去继续之前的约定。到那时,你可以前往。",
"hideNote": ["与杰哥的约定","你心神不宁"],
"selection": [
{
"text": "继续",
"toType": "jump",
"to": "节点160"
}
]
}
同时,回显不会有任何变化,如果有需要,请在文本中提示玩家,他们现在获得了线索。
在节点选项中使用
在节点选项中使用hideNote
字段,如果玩家携带着对应的线索,那么这个选项将不会被隐藏,否则玩家看不到这个选项,即使玩家知道这个选项,也无法选择它。
{
"flag": "节点180",
"text": "天色渐晚,你回到自己家,简单地吃了一顿晚饭。你反常地沉默。吃饭时,似乎有一种你难以理解的急迫感。\n\
如果你之前和人有约,现在是时候赴约了。\n\
你想要……",
"selection": [
{
"text": "到外面眺望星空",
"toType": "jump",
"to": "节点131"
},
{
"text": "[秘密]赴约",
"hideNote": "与杰哥的约定",
"toType": "jump",
"to": "节点200"
},
{
"text": "[秘密]发疯",
"hideNote": "你心神不宁",
"toType": "jump",
"to": "节点220"
}
]
}
那么当玩家没有经历过节点159
时,将会看到如下回显:
天色渐晚,你回到自己家,简单地吃了一顿晚饭。你反常地沉默。吃饭时,似乎有一种你难以理解的急迫感。
如果你之前和人有约,现在是时候赴约了。
你想要……选项如下:
1 - 到外面眺望星空
而当玩家确实经历过节点159
时,他们将携带对应的线索,将会看到如下回显:
天色渐晚,你回到自己家,简单地吃了一顿晚饭。你反常地沉默。吃饭时,似乎有一种你难以理解的急迫感。
如果你之前和人有约,现在是时候赴约了。
你想要……选项如下:
1 - 到外面眺望星空
2 - [秘密]赴约
3 - [秘密]发疯
建议不要将hideNote
用于type:ra
的节点,因为这样会导致玩家无法进行检定,从而无法继续游戏。
同时,如果在一次故事中,玩家的故事运行到这个节点时,你的选项中没有任何一个选项在玩家当前所持有的线索下可以被显示,那么这个节点将会被视为type:end
的节点,玩家的故事也会直接结束,请好好利用这个特性。