Skip to content

16 · 完整作品 ​

本章组装一个完整的短篇视觉小说《灯塔守望者》,综合运用前面学到的所有功能。

游戏概览 ​

《灯塔守望者》 是一个 5-10 分钟可通关的短篇故事:

  • 剧情:旅人在雨夜来到一座灯塔,遇到守塔人
  • 分支:两个选择导致两个不同结局
  • 覆盖功能:场景、对话、分支、变量、NVL、内联标记、模板、过渡

剧情结构 ​

标题画面
  └─ 序章(NVL 叙事)
       └─ 灯塔门口
            ├─ 敲门 → 好感+1
            └─ 推门 → 好感+0
                 └─ 灯塔内部(遇见守塔人)
                      ├─ 留下 → 结局:守望者
                      └─ 离开 → 结局:远行者

完整源码 ​

以下是完整 .story 文件,也可在 examples/final-game/final-game.story 找到。

dsl
// == 全局变量初始化 ==
define "player.name" "旅人" once
define "story.met_keeper" false once
define "story.trust" 0 once
define "story.helped" false once
define "story.ending" "" once

// == 角色定义 ==
character "narrator" name="旁白" color="#AAAAAA"
character "keeper" name="守塔人" color="#88CCFF" screen="center"
character "player" name="旅人" color="#FFD700"
character "system" name="系统" color="#88FF88"

// == 标题画面 ==
scene "title_main" type=menu
  image "Images/door_zoom.jpg" x=0 y=0 width=100% height=100% opacity=0.4
  text "灯塔守望者" x=50% y=15% size=56 color="#FFD700" halign=center
  button "开始故事" x=50% y=45% width=240 height=48 color="#88CCFF" nav="prologue" halign=center
  button "退出" x=50% y=60% width=160 height=40 color="#FF8888" cmd="do_exit" halign=center

// == 序章:NVL 叙事 ==
label prologue:
  set "story.progress" 1
  bgm "Media/bgm_main.wav" volume=0.6
  transition "fade" duration=1.5

  nvl
  say "雨夜。{p}" speaker="narrator" template="fullscreen"
  say "你沿着泥泞的小路前行,远处灯塔的光芒在雨幕中若隐若现。" speaker="narrator" template="fullscreen"
  say "这便是{b}{color=#FFD700}灯塔守望者{/color}{/b}的故事开始之处。" speaker="narrator" template="fullscreen"
  nvl exit

  transition "fade" duration=1.0
  navigate "lighthouse_door"

// == 第一章:灯塔门口 ==
scene "lighthouse_door"
  set "story.progress" 2
  bg_switch "Images/door_zoom.jpg" transition=fade duration=0.8
  text "第一章 · 灯塔" x=50% y=10% size=32 color="#FFD700" halign=center

label door_arrive:
  say "你站在灯塔的门前,雨水顺着衣角滴落。" speaker="narrator"
  say "门虚掩着,透出一丝暖黄色的光。" speaker="narrator"
  menu "你要怎么做?"
    "敲门" -> knock_door
    "直接推门进去" -> enter_door

label knock_door:
  set "story.trust" {story.trust + 1}
  say "你轻轻敲了敲门。" speaker="narrator"
  say "请进——门没锁。" speaker="keeper"
  navigate "inside_lighthouse"

label enter_door:
  say "你推开门走了进去。" speaker="narrator"
  say "哦?不请自来的客人吗。" speaker="keeper"
  navigate "inside_lighthouse"

// == 灯塔内部 ==
scene "inside_lighthouse"
  image "Images/door_zoom.jpg" x=0 y=0 width=100% height=100% opacity=0.2
  text "灯塔内部" x=50% y=10% size=28 color="#FFD700" halign=center

label meet_keeper:
  set "story.met_keeper" true
  say "灯塔内部出乎意料地温暖。" speaker="narrator"
  say "壁炉里的火噼啪作响,一位老人坐在摇椅上。" speaker="narrator"
  say "欢迎,{player.name}。我等你很久了。" speaker="keeper"
  say "你……你怎么知道我的名字?" speaker="player"
  say "灯塔守望者知道每一个迷途之人的名字。" speaker="keeper"
  say "你要记住——光不灭,希望就不灭。" speaker="keeper"
  say "你愿意留下来吗?" speaker="keeper"

  menu "你的回答是——"
    "我愿意留下来" -> choose_stay
    "我还有自己的旅程" -> choose_leave

// == 结局一:留下 ==
label choose_stay:
  set "story.helped" true
  set "story.trust" {story.trust + 2}
  say "我愿意留下来。" speaker="player"
  say "很好。从今以后,你便是这座灯塔的守望者。" speaker="keeper"
  transition "fade" duration=2.0
  navigate "ending_stay"

// == 结局二:离开 ==
label choose_leave:
  say "谢谢你的好意,但我还有自己的旅程要走。" speaker="player"
  say "我理解。每个人都有自己的路要走。" speaker="keeper"
  set "story.trust" {story.trust + 1}
  transition "fade" duration=2.0
  navigate "ending_leave"

// == 结局画面 ==
scene "ending_stay" type=menu
  set "story.ending" "守望者"
  image "Images/door_zoom.jpg" x=0 y=0 width=100% height=100% opacity=0.5
  text "结局 · 守望者" x=50% y=15% size=48 color="#FFD700" halign=center

label ending_stay_show:
  nvl
  say "很多年后。" speaker="narrator" template="fullscreen"
  say "又是一个雨夜,你坐在那把摇椅上,望着窗外的海。" speaker="narrator" template="fullscreen"
  say "灯塔的光穿过雨幕,照亮远方的小路。" speaker="narrator" template="fullscreen"
  say "{b}{color=#FFD700}「欢迎回家。」{/color}{/b}" speaker="narrator" template="fullscreen"
  nvl exit
  navigate "ending_menu"

scene "ending_leave" type=menu
  set "story.ending" "远行者"
  image "Images/door_zoom.jpg" x=0 y=0 width=100% height=100% opacity=0.5
  text "结局 · 远行者" x=50% y=15% size=48 color="#FFD700" halign=center

label ending_leave_show:
  nvl
  say "你带着那盏铜灯踏上了旅途。" speaker="narrator" template="fullscreen"
  say "无论多黑的夜,那盏灯始终亮着,温暖而稳定。" speaker="narrator" template="fullscreen"
  say "{b}{color=#FFD700}而前方,还有更广阔的世界等着你。{/color}{/b}" speaker="narrator" template="fullscreen"
  nvl exit
  navigate "ending_menu"

// == 结局菜单 ==
scene "ending_menu" type=menu
  image "Images/door_zoom.jpg" x=0 y=0 width=100% height=100% opacity=0.4
  text "故事结束" x=50% y=12% size=40 color="#FFD700" halign=center
  text "你达成了结局:{story.ending}" x=50% y=20% size=20 color="#CCCCCC" halign=center
  text "好感度:{story.trust}" x=50% y=26% size=16 color="#888888" halign=center
  button "再玩一次" x=50% y=45% width=200 height=44 color="#88CCFF" nav="title_main" halign=center
  button "退出游戏" x=50% y=60% width=160 height=40 color="#FF8888" cmd="do_exit" halign=center

功能覆盖检查 ​

功能使用位置
scene + type=menu标题画面、结局菜单
scene + type=game灯塔门口、灯塔内部
text + 变量插值标题、结局显示
button + nav所有导航按钮
button + cmd退出按钮
image + opacity所有场景背景
define ... once全局变量初始化
character + screen=守塔人绑定 center 模板
say + template=NVL 叙事用 fullscreen
say + 内联标记{b}{color=#FFD700}...{/color}{/b}
say + {p}NVL 段落暂停
say + 变量插值{player.name}
nvl / nvl exit序章和结局的 NVL 叙事
menu门口选择、去留选择
navigate所有跳转
set好感度、结局标记
set + 复合赋值story.trust += 2(示例中等价写法)
bgm序章背景音乐
bg_switch + transition场景背景切换
transition "fade"场景过渡

运行方法 ​

  1. 将 final-game.story 复制到项目的 Stories/ 目录
  2. 修改 Stories/title/title_main.story,添加导航按钮:
dsl
button "完整示例" x=50% y=80% nav="title_main_final" halign=center

或将 final-game.story 中的 title_main 场景重命名为其他名字,作为入口。

  1. 运行项目

扩展建议 ​

这个示例可以作为基础,继续扩展:

  • 添加更多分支:在门口增加"离开"选项
  • 添加战斗系统:用 C# 实现 SimpleBattle 场景
  • 添加存档点:在关键选择前加 save "auto"
  • 添加 CG 解锁:结局时 gallery_unlock "ending_stay"
  • 添加多语言:在 Lang/ 目录添加翻译文件
  • 自定义对话框:实现自己的 IDialogBox 模板

恭喜! ​

你已完成灵泛引擎的完整教程!现在你可以:

  • 📖 查阅 API 参考 快速查找关键字
  • 🍳 浏览 进阶专题 学习实战技巧
  • 🚀 开始制作你自己的游戏

Happy writing! ✨

基于 .NET 10 + Avalonia 12 · Apache-2.0 License