未追回的呓语
盖力维
未追回的呓语
Whispers Unreclaimed
烟尘记 — 前传
Mistrail Dustale — Prequel

目录
项目简介:
未追回的呓语 [ Whispers-Unreclaimed ]
烟尘记 · 前传 [ Mistrail Dustale — Prequel ]
本项目由两个热爱幻想的学生独立制作完成。
我们于 2022年9月12日 创立本项目,于 2025年8月26日 圆满完成此项目,实际开发时间总计约一年。

图为两位制作者合影,左侧为220303,右侧为Rebillion
制作人员分工:
- 220303 (盖力维)
- 程序编写
- UI及美工
- 测试发行
- 后期维护
- 其他所有
- Rebillion (李任)
- 剧情创作
- Logo 设计
- 音乐制作
本项目分为两部分:
互动小说引擎:
此部分完全由我完成,基于
C#、.NET 8.0、WPF实现。本项目很可能是全网第一个WPF互动小说引擎。
基于自创的简单脚本格式和特定目录结构实现了剧情分支功能 (任意数量分支) ,只需要简单的
.txt文本即可创造出几乎无限复杂度的剧情选择分支系统。支持选项的条件输出,根据用户游玩历史决定当下的可选项。
基于
Unicode字符集实现了全字符输出,脚本格式不会对文字创作者在剧情或选项的文本中插入的字符做出任何限制。利用
WPF中Textblock相关控件的印刷级输出和排版,实现了在不同字体、字号、颜色、粗体、斜体、下划线、删除线等属性下的逐字符输出。实现了游戏页面设计,
UI设计,图片以及动画设计。剧情创作:
此部分由Rebillion同志完全完成。
如果你发现了Bug或是对某些代码实现有更好的改进意见,可以在issue板块留下评论,我会在闲暇时查看。
如果本项目中有代码帮到了你,或是你喜欢这个作品,希望你能为仓库点亮一颗star,那将是对我们莫大的鼓励。
项目开源协议:
CC BY-NC 4.0
Canonical URL:https://creativecommons.org/licenses/by-nc/4.0/
Creative Commons Attribution-NonCommercial 4.0 International
This license requires that reusers give credit to the creator. It allows reusers to distribute, remix, adapt, and build upon the material in any medium or format, for noncommercial purposes only.
详见 LICENSE 页面。
游戏教程:
游戏中内置了教程页面,将在第一次再开始界面点击或按下任意键时进入,内容如下:
您好!
这里是《未追回的呓语》的教程页面。
注意:本游戏中键盘操作需要您使用英文输入法。
在下次进入游戏时,您将处于”开始界面“,本教程页面将不再自动出现,您可以在 ”开始界面“ 中按下 ' shift '+' / ' 来切换到此教程页面。
进入游戏时窗口默认为沉浸式全屏,在任意页面中,您都可以通过按下 ' F12 ' 进入或退出沉浸式全屏。
注意:若您的显示器长宽比例远大于16:9 (1920:1080),那么沉浸式全屏的游戏效果将会非常糟糕,建议您按F12退出该模式,并调整窗口到合适的长宽比例。
在 "开始页面" 中,您可以通过按下 ' shift '+' : ' 进入 "设置页面" 。
在 "设置页面" 中,您可以调整程序字体大小、剧情字体大小、背景音乐音量。
注意:点击 "保存设置" 时,合法的输入将会被写入,而不合法的输入将不会被写入。此外,没有 "恢复默认设置" 功能。
在 "设置页面" 中,您可以通过按下 ' shift '+' : ' 返回。
在 "开始页面" 中,您可以通过按下 ' shift '+' A ' 进入 "存档管理页面" 。
在 "存档管理页面" 中,您可以通过在存档列表中选择存档来将其置为 "活动存档" ,您也可以新建存档或删除选中的存档。
在 "存档管理页面" 中,您可以通过按下 ' shift '+' A ' 返回。
在 "开始页面" 中,您可以通过按下其他任意键或鼠标点击屏幕进入 "活动存档" 开始游戏,若没有任何存档被选为 "活动存档" ,您将进入 "新建存档页面" 。
在 "游戏页面" 中,您可以通过按下 ' shift '+' : ' 进入 "设置页面" 。
在 "设置页面" 中,您可以通过按下 ' shift '+' : ' 返回。
在 "游戏页面" 中,您可以通过按下 ' Esc ' 进入 "暂停页面" 。
在 "暂停页面" 中,您可以选择保存当前游戏进度,退出到 "开始页面" ,或直接退出游戏至桌面。
注意:未保存的游戏进度会在退出后丢失,但这也给了您一次从头再来的机会,不是吗 ......
在 "暂停页面" 中,您可以通过按下 ' Esc ' 返回。
在 "游戏页面" 中,您可以通过按下其他任意键或鼠标点击来获取新的剧情或选项。
在 "游戏页面" 中,您可以鼠标点击选项按钮或键盘按下对应数字键 (从1记起) 来做出选择。
在 "游戏页面" 中,您可以用鼠标滚轮浏览曾经的剧情。
注意:记忆是会消失的,您只能回溯有限的剧情。
注意:本游戏不支持回档,请慎重对待每一个选择...
现在,请按下任意键或点击鼠标,您即将进入 "新建存档页面" ......
未来计划:
第二阶段开发
- 编写自定义用户控件,替换掉原有的默认控件
- 增加音频播放功能
- 制作
Start页面内嵌视频与动画 - 催Rebillion同志交稿
-
程序背景音和游戏背景音制作 - Logo制作
未来发展
可能不会完成了- 分离游戏引擎,作为单独的项目,本项目只作为
《未追回的呓语》游戏本身。 - 游戏引擎跨平台移植:采用基于.NET 的跨平台框架,有可能是
WPF的上游Avalonia框架
- 分离游戏引擎,作为单独的项目,本项目只作为
基本架构:
WPF界面
窗体
MainWindows
页面
Start
Tutorial
Rebillion qoutes
New start
Game
Pause
Trace
Option
Failure
Credit
后台类库
游戏逻辑类
烟尘记游戏
交互效果及控件类
MagicCornerBorder_Rebillion_qoutes/Trace/Option/Pause
ParticleCanvas
Button_animation
Page_frame
Music_player
全局数据类
Data
Rebillion_qoute
Save
Option
打包发布脚本
Build-Whispers-Unreclaimed.ps1
开发设计:
MainWindows:
作为游戏的主窗口,也是唯一的窗口,所有的游戏界面都作为页面在其上切换,采用Frame框架来承载页面,并且隐藏导航栏。它的构造函数包括初始化Frame等全局内容和加载Start页面。
交互相关:
Window_KeyDown(object sender, KeyEventArgs e):用户按下 F12 时进入或退出沉浸式全屏。
MainWindow.xaml:添加蒙版层,用于展示页面切换动画以及屏蔽动画发生时的输入。
Start页面:
用户一进入游戏,就会处在Start页面上,Start页面会显示封面,可以接受输入并跳转到Rebillion qoutes页面,New start页面,Trace页面,Option页面。
交互逻辑:
鼠标:点击屏幕任意区域进入游戏。
键盘:
输入
shift+;进入Option页面。输入
shift+a进入Trace页面。输入
shift+/进入Tutorial页面按其余任意键进入游戏。
进入游戏:如果什么都存档都没有,则进入
New_start页面创建新的存档;如果有存档,则进入Rebillion qoutes页面加载存档
Start():初始化背景、水波纹、聚焦键盘输入Start_game():执行 进入游戏 逻辑,会有两种情况,一是什么都存档都没有,则进入New_start界面创建新的存档;二是有存档,那么Option文件中必然有save_choose值,则进入Ribillion_qoutes页面。Page_KeyDown(object sender, KeyEventArgs e):根据用户输入进入Trace页面,Option页面,或调用Start_game()开始游戏Page_MouseDown(object sender, MouseButtonEventArgs e):调用Start_game()开始游戏
背景:
背景图是一张左右可以无缝衔接的48:9的图片,通过两个并列的Image控件实现 (用户视角的) 横向循环播放。全部在Start.xaml.cs中实现。
scaled_width:图片的宽度(根据窗口高度动态改变)speed:移动速度,单位: 像素/秒offset:图片偏移量(表示移动到什么位置了),始终是负值(因为是左移)last_update:上次移动图片的时间,下次移动时要用InitializeAnimation():初始化动画Image_update_dimensions():处理改变窗口大小后图片的大小及位置变化UpdatePosition(object sender, EventArgs e):更新图片位置
水面波纹效果:
在用户的鼠标周围呈现出水波纹,通过将HLSL编译为GPU可执行的Shader,并集成到WPF渲染管线来实现。
water_ripple.hlsl:定义了波纹的效果,采用指数衰减并设置相应参数。- 在
烟尘记.csproj中定义与生成事件,使用Windows SDK中的fxc.exe将其编译成water_ripple.ps。 - 与
Filesystem中的其他文件不同,water_ripple.plsl不包含在生成内容中,water_ripple.ps被设为内嵌设为资源。
- 在
water_ripple_effect.cs:与water_ripple.ps对接,提供C#代码访问water_ripple.ps的接口Start.xaml.cs:实现有关波纹生成和跟踪鼠标移动的逻辑。rippleTimer:定时器startTime:波纹开始时间Water_ripple_start():初始化水波动画Watter_ripple_update_dimensions():窗口大小改变时,改变水波shader的宽高比
Start.xaml:给两个Image附上Effect来展现水波纹以及参数设定。Amplitude:振幅Frequency:频率Speed:速度
New start页面:
创建新的存档。用户可以输入新存档名字,然后选择创建新存档。也可以选择返回Start页面。
新建存档,设置存档名字然后进入存档。
Rebillion qoutes页面:
输出李任语录。
后台加载Nodes,如果加载时间不够看完语录就等6秒。
Game页面:
游戏界面,所有游戏操作都在此页面进行。总的来说,程序会轮流输出剧情(逐字输出)和选项,用户做出选择后又会输出新的剧情,用户的历史选择会影响当下可以做出的选择。
鼠标:
点击选项按钮做出选择。
点击点击屏幕任意区域输出剧情(仅在第一次进入
Game页面时需要)。
键盘:
输入
shift+;进入Option页面。输入
Esc进入Pause页面。输入数字键做出选择(前提是有数字对应的选项)。
输入其余任意键输出剧情(仅在第一次进入
Game页面时需要)。
Pause页面:
玩家可以保存进度,退出到Start页面,直接退出游戏(退出到桌面)。
键盘:
输入
Esc返回Game页面。
注意保存是立即写入文件的持久化保存,且不可撤销。
注意未保存直接退出会丢失所有本次游玩的进度。
Trace页面:
按名称排序显示存档列表,用户可以选择一个存档作为活动存档(改变了Options中的Save_choose并立即写入Options文件),下次进入游戏时会默认进入活动存档。
游戏结局时会展示Trace页面。
键盘:
输入
Esc返回Start页面 (对于游戏结局时显示的Trace页面而言,是重新回到Start页面)。
Option页面:
玩家可以更改Options中的Plot_font_size,Plot_print_speed,Music_volume,并保存(立即写入Options文件)。
Plot_font_size:字体大小
Plot_print_speed:逐字输出速度
Music_volume:介于0-10000间的整数。
键盘:
输入
Shift+:返回上一个页面 (Game或Start)。
注意:未保存的Options更改将在离开Option页面时丢失。
注意:不合法的Options更改在保存时不会写入Options文件,若此时离开Option页面,原数据不会丢失。
Failure页面:
玩家死亡后的页面。
提示玩家重新开始。
点击屏幕任意区域或按键盘任意键返回Start页面。
Credit页面:
当游戏走到结局之后,显示的致谢页面。
包括下部预告,制作人员介绍、合影、发言等内容。
由于游戏有多结局,第二次及以后进入此页面后提示语会发生变化,可以按Esc跳过此页面回到Start页面。
用Rectangle作为上下边框,用StackPanel作为容器,将内容依次排开,用Textblock打空格占位 (有点敷衍但好用)。
在后台代码中设置动画,用滚动时间控制速度。
页面内容播放完毕后,点击屏幕任意区域或按键盘任意键返回Start页面。
烟尘记游戏类:
名字是历史遗留原因,具有纪念性意义。
实例类,其实例代表一局戏,在游戏开始时创建,游戏结束时销毁。
Story_directory_path:存放剧情和选项的目录。
Text_style:文字风格结构体。
Text_fragment:风格相同的文字片段。
Choice:Choice结构,含有Content,Jump,condition,分别代表选项文字,选项要跳转到的Node编号,选项可用的条件。Choice(string content, int jump, List<string> condition)是构造函数,Check(List<string> check_record)是自带的方法,用于根据用户游玩历史判断选项是否可用。
Node结构:含有Plot,Choices,分别代表节点的剧情内容和选项列表,public Node(List<Text_fragment> plot, List<Choice> choices) 是构造函数。
Nodes:全局唯一的剧情节点列表,包含所有剧情及选项。
Jump:读取自某个存档Save,代表接下来要输出的Node编号。
Record:读取自某个存档Save,代表该存档过去的游戏历史。
Nodes_read_in():读取所有剧情及选项,在游戏启动时使用。
Choose(int input):根据玩家的选择切换到下一个Node。
Save_game()函数:用于保存进度到程序内的Saves。
烟尘记游戏(Data.Save save)构造函数:给定一个存档Save,可以构造一个烟尘记游戏的实例。
Library.cs 类库:
准确的来说Library.cs只是个普通C#代码文件,因为我尝试过使用分离项目的类库,若类库引用主程序会造成循环依赖,但其中的Data类里面的Main_window需要引用主程序中的MainWindow来将其全局化,所以不能这样构建项目。
包含所有全局类,包括:Button_animation,Music_player,Page_frame 这几个UI效果与交互类;Data,Rebillion Qoute,Save,Option 这几个全局数据类。
MagicCornerBorder_Rebillion_qoutes/Trace/Option/Pause类:
绘制页面边框。
以MagicCornerBorder_Rebillion_qoutes类为例说明,它包含边框渐隐线和角落花纹,坦诚地说角落花纹画的效果非常差。
MarginFromEdge:控件边缘到装饰线的外边距。
OffsetFromCorner:角落花纹向内的偏移量。
StrokeBrush / StrokeThickness:画笔颜色、粗细。
CornerGeometry():预生成的角落几何图案,实在是画的不好,我将来会改进的。
OnRender():绘制角落花纹和四条渐变边线。
DrawFadingLine():工具函数,画一条线段,颜色从 StrokeBrush 渐变到透明,线条端点圆润。用于画渐变边线。
OnRenderSizeChanged():尺寸改变时重绘。
MagicCornerBorder_Trace/Option/Pause类只有边框渐隐线,没有角落花纹,边框渐隐线代码同上。
Button_animation类
ParticleColorScheme:粒子动画主题色结构体。
Animation(Button button, ParticleColorScheme colors):播放并耐心等待按钮的粒子动画完成。
ParticleCanvas类
使用 RadialGradientBrush 绘制带渐变的小圆点,营造粒子散射的效果。
Particle:单个粒子的属性模板。
particles:当前所有活动的粒子集合。
rand:随机数生成器。
animating:是否正在执行动画。
durationSeconds:粒子生命周期默认长度。
StartParticles(int count, Color colorStart, Color colorEnd, Rect buttonRect, double maxDistance, double duration) :初始化并启动一批粒子。
OnRendering(object sender, EventArgs e):每一帧更新粒子状态。
OnRender(DrawingContext dc):绘制当前所有粒子。
GetRandomPointOnBorder(Rect rect, Random rand):随机返回按钮边框上的一个点(上/右/下/左四条边中随机一条)。
Music_player类
Music_directory_path:存放背景音乐的位置。
music_player:全局唯一静态音乐播放器,保存MainWindow中的Global_media_element。
Current_music_page_group:当前页面所属的音乐播放页面分组的记录。
Start(string Current_music_page_group)静态函数:初始化Global_music,在程序启动时使用,Change_music(object sender, NavigationEventArgs e):页面切换事件的处理程序,负责根据不同的音乐播放页面分组更换背景音乐。
Page_frame 类:
Navigate_permit:类内部防抖变量。
black_mask;:存放对Mainwindow的动画蒙版对象的引用。
page_frame:存放对Mainwindow的Frame对象的引用。
Page_in_storyboard:页面切入的导航动画。
Page_out_storyboard:页面切出的导航动画。
Navigation_animation_Initialize(Frame page_frame, Rectangle black_mask):导航动画初始化函数。
AwaitStoryboardAsync(Storyboard storyboard):把故事板动画封装为可等待的Task(当然,直接使用了black_mask引用)。
Navigate(object content):带有切页动画的导航到新一页逻辑。
Go_back():带有切页动画的返回上一页逻辑。
Data类:
存放全局静态变量 (实际上也可以说是杂项全局静态变量)。
Filesystem_directory_path:游戏所有文件的根目录
Image_directory_path:存放背景图片的位置
Main_window:为MainWindow生成的一个全局对象,使得在其他Page和类中也能访问到MainWindow中的内容:
- 通过
Data.Main_window.global_media_element访问MainWindow中的音乐播放器。 - 通过
Data.Main_window.Page_frame.Navigated += Change_music监听Frame导航来更改音乐。 - 通过
Data.Main_window.Close();调用关闭代码。
Rebillion_qoute类
类名没有s是因为要避让Rebillion_qoutes这个Page类
Rebillion_qoutes_file_path:存放Rebillion Qoutes.txt的位置
rebillion_qoutes:全局唯一的李任语录(string类型数组)
Read_in()静态函数:读取李任语录文件,在游戏启动时使用。
Save类
Saves_directory_path:存放存档的位置
save结构:含有Name,Jump,Record,分别代表存档的名字,玩到的剧情Node编号,历史选择。其中Name是属性,因为要被依赖到WPF界面上显示。
saves:全局唯一的存档列表,包含玩家创建的多个存档。
Read_in()静态函数:读取存档文件,在游戏启动时使用。
Write_out()静态函数:写入存档文件,在保存存档时使用。
Option类
Options_file_path:存放Option.txt的位置
Save_choose:默认选择的存档。
Plot_font_size:字体大小。
Plot_print_speed:文字输出速度。
Music_volume:背景音乐音量大小。
start_picture_speed:开始屏幕图片滚动速度。
tutorial_completed:教程完成标志位。
credit_completed:结局完成标志位,用于在用户第二次及以后进入Credit页面后改变提示语,同时提供Esc跳过。
Read_in()静态函数:用来读取置文件,在游戏启动时使用。
Wrtie_out()全局静态函数:写入设置文件,在保存设置时使用。
Build-Whispers-Unreclaimed.ps1打包发布脚本:
Powershell 打包发布脚本。
注意:该脚本中应用了我电脑上的绝对文件地址,迁移时需注意替换!
利用 .NET CLI 发布三个版本:
Dependent-win:仅游戏,需要玩家的电脑上有.NET8.0或更高版本的.NET Runtime方可运行。Independent-win-x86-32:自包含版本,无需任何.NET Runtime也可独立运行,适用于x86架构32位Windows操作系统。Independent-win-x86-64:自包含版本,无需任何.NET Runtime也可独立运行,适用于x86架构64位Windows操作系统。
将程序的 Filesystem 替换为发布版本 (发布时会自动使用代码目录的 Filesystem,该版本被我用做实验)。
利用 7zip 压缩程序文件夹。
历史回顾:
2025年8月26日13:12 发布感言
220303:
现在,我宣布:《未追回的呓语》 项目圆满完成!
我对本项目的定义是: 一个小型实验性项目,但是我人生中第一个纯粹的独立研发项目。 我将永远铭记它。
本项目名为 《未追回的呓语》[ Whispers-Unreclaimed ] 即 《<烟尘记>·前传》 ,由我和 Rebillion 同志共同开发。
很多内容都已经在这份Readme的其他部分体现出来了,那么就在这里说一说项目的开发历史和Rebillion的小传吧……
本项目始于一腔热血。初二那年, Rebillion 找到我,说要做一款纯文字游戏,名叫 《烟尘记》 。当时的我不过写了两年的 C# ,刚刚学会 WPF 框架。于是我负责项目中所有技术相关部分。
从专业角度来说, WPF 不是制作互动文字游戏最好的框架,我后来也逐渐有了全栈开发的能力,但是我仍然始终选择WPF,因为这是我接触的第一个、也是使用时间最长、了解最深的桌面软件开发框架,对我而言有着特殊的意义。
在漫长的开发期间,我不断地学习技术,从一本 《C#入门经典》 到整整六十三本藏书,我几乎学完了计算机科学与技术本科阶段的全部专业课程,也做了一些其他项目,同时还要保持不错的文化课成绩,因此 《未追回的呓语》 的技术开发总计大概只有一年时间。
在开发的最初阶段,我对 WPF 尚且不是非常熟悉,所以一开始我是用控制台应用程序做一些实验 (听起来很搞笑) ,至今我还保留着这些小程序。
经过一些学习和实验之后,我开始用 WPF 制作游戏引擎。我并不是一个文字游戏爱好者(至今仍不是),所以我并没有玩过多少文字游戏,更没有考察过他们都是如何制作的,再加上全网也没人用 WPF 做 纯文字AVG游戏引擎 ,于是我的"闭门造车"之路正式开始。
基本的问题搞定了,就该真正迈出第一步了,也就是和 Rebillion 讨论游戏的外观、敲定剧情分支语法等重要协作部分…. **但是!**在我努力工作的时候, Rebillion 一直在消极怠工……**他拖稿成性!**不但音乐、美工等部分一问三不知,甚至连文字都没写多少!于是我不断催促、鼓励甚至于大发雷霆……终于!我还是一个人默默地设计了所有的方案,包括文字语法、游戏外观、交互模式等内容……甚至还要考虑到他少得可怜的智慧,仅用 .txt 文本文件作为文字剧情载体(而不是 xml 之类的专业格式)……
即使我不断进行项目工作的统筹和计划,他的消极怠工还是频繁导致项目难以进行!我无数次想过放弃这个项目,但每次在进行艰难的思想斗争后还是坚持继续催他写稿,最终才使得项目不至于因为文字工作停滞而半路夭折。因此我除了是技术员之外,也是当之无愧的项目领导人。
说回代码这边,与其说我在“迈出第一步”这件事上遇到了困难,不如说我在“迈出每一步”时都遇到了困难。整个游戏引擎是完全由我一人开发的,所以我需要把控方方面面的细节,从游戏有几个页面到他们之间切换的逻辑、从设计剧情分支语法到实现逐字输出、从保证无差别实现 Unicode 字符集到解决奇奇怪怪的卡顿,没有那一部分的实现是显而易见的。并且我对于项目架构的整体把控缺乏经验,外加有点强迫症,结果就是废弃代码一堆又一堆……
终于,在我坚持不懈的努力下,游戏的大体框架”立起来了“。回首一看,顿时发现最初的代码经过三轮大重构无数次小优化早就一行都不剩了,唯一一丝当年的踪迹就是那个 烟尘记游戏 类,这也是为什么它始终没有被我并入 Library.cs 里。在同龄人里,我的计算机技术也早已能被尊称一声“大佬”,我终于可以云淡风轻地挥挥手,告别泥泞的过去。似乎程序架构师的本职工作已经完成,但是嘛…..我还没替 Rebillion 干活呢!哈哈哈我&^%$#@&!>^%$#&@$….
那我又要帮他干什么呢?**当然是美工了!**因为我们两个人都缺乏美工技能,身边又没有合适的人选 (我曾尝试过找身边的人合作,但最终都没成功),经费更是直接为 0 !于是…..我只能赶鸭子上架,强行充当 UI及美工设计师。
几乎所有的美工内容都是我在 Visual Studio 2022 里用代码强行写出来的 (配套的 Blend 在国内几乎没有教程),难度呈几何倍数增长,而效果却是一落千丈。图片都是用豆包画的 (因为付费的 AI 用不起),反复生成、用上所有运气才能得到一张也许根本不能用的图片。
比如 Start 页面里勉强无缝衔接的背景、动态生成的水波纹; Game 页面里随机变幻的星空、划过的的流星等等。这些每一项都要花费我一周以上的时间 (假期) 来不断地学习、思考、尝试,最终才能达到现在的效果……
然而对于某一些的美工,我实在是无能为力。 Rebillion_qoutes 页面的边框很简陋吧; Pause 页面的布局很糟糕吧……这些我都知道,但我已经尽力做到最好了,也许将来的更新里会弄吧。
讲着讲着,时间轴就走到了今天,项目的开发阶段已经结束,我和 Rebillion 也马上就将升入高三,没有时间继续开发和完善细节。Rebillion拖稿的坏毛病终于导致了最为严重的后果——他只写出了《烟尘记》的前传部分。我对此无比气愤,但又无可奈何。(详见Rebillion的发布感言)
作为唯一负责技术的开发者,我完全地明白这款文字游戏还远远不够完善,甚至可以说还远不到能够发行的水平,细节方面的不足更是多如牛毛,我在此深表歉意。
我并不是一个很自信的人,很多时候,我自己都觉得这个项目前后一万多行的代码不过是一堆毫无价值的垃圾,但我不愿意只用结果评价一切。在开发过程中,我从毫无经验开始,不断学习新的技术,阅读新的书籍,优化设计结构,用更好的、更精简的实现替换掉原有的臃肿代码,光是大幅度重构所丢弃的代码就有 4000 多行,超过了总代码量的三分之一。一行行的代码见证了我的成长,陪伴我走过了计算机科学的入门时光,我需要给它们一个满意的交代、给过去的自己一个满意的交待。
最终,经Rebilliion建议,我宣布项目更名为《未追回的呓语》,作为《烟尘记》的前传发行。仅以此,纪念我逝去的青春岁月……
在游戏即将发行时,我写下了这些文字,此时我17岁。 我明白有许多人比我更加优秀,有许多人在更年轻的时候就取得了更优异的成绩, 我也明白我对于WPF的了解和使用还远不够深入,许多实现细节都不怎么优雅, 但请允许我在此自豪一次:
每个少年都有一个开发游戏的梦想,我很幸运,把梦想变成了现实!
感谢你能看到这里,如果Rebillion能够改正拖稿的毛病,那么在不远的将来,我们将会开发新的项目《烟尘记》[Mistrail Dustale] 本传,那是一个纯文字RPG类型游戏,更加宏大也更加复杂。 我们一定会在美工方面做得更好 (无论是自己学习还是找擅长的人加入团队)。 同时,我也会尽可能地精进自己的代码能力,希望可以制作出令人满意的游戏效果。 如果你愿意,可以关注我的Github账号220303,有朝一日,一个名叫 Mistrail-Dustale 的仓库将会在那里出现。
我们未来再见!
Rebillion:
首次在github上发表感言。
许久之前,我决定制作游戏时,想着做一个既有世界观也有可玩性的作品。
但是写着写着,发现工作量越来越大,于是懒病复发,卧床不起,这一来就是三年。
《烟尘记》的构思经历了很长时间,制作却只用了数月。当时定名为《前记》,但由于220303追求新鲜感,写成的作品在他的强烈呼吁下改名为《未追回的呓语》。这没办法,毕竟发行商(勉强算是吧)是他。
总之,2025年8月26日这天,我们的游戏发布了!
我们已经等这一天很久了。希望游戏的质量不会让玩家失望吧。
抒情的话,写在游戏里了。
欢迎各位游玩。
PS:上次交稿时我曾经试过直接以开发者的身份发表感言,但是给220303造成了麻烦。(他在虚拟世界也有洁癖)毕竟我是文字工作者,对程序领域缺乏了解。
2025年8月10日21:07
直到今天,Rebillion同志终于发来"喜讯",他已经完成一些简单的文本剧情,剩下的就是格式校对和适配。
由于Rebillion同志的个人原因,他只创作了了 《烟尘记》 的前传部分,故而本项目的剧情内容仅限于前传部分。他对这一不负责任的态度深表歉意(但是我觉得这毫无价值),并表示会继续创作 《烟尘记》 的本传部分。
由于我和Rebillion都要上高三了,时间非常紧张,所以本项目的开发即将结束了,我不得不宣布项目更名为 《未追回的呓语》 。
除了本项目的 Release ,我还会尝试在 itch.io 等平台上发布 《未追回的呓语》 ,但受限于美工的缺失,大概率无法发布(如 itch.io 需要有宣传图)。
我在将来会进行 《烟尘记》 本传的开发,但那将是另一个全新的、复杂程度不可同日而语的项目,我的最终目标是一个 RPG 的内核的文字游戏,具有高自由度,并用文字作为交互形式,我觉得这才是纯文字游戏的最终形态.
此外,Rebillion同志已经将两个背景音乐都制作完成,我也已经完成适配。
现在剩下的任务只有美工部分,但我们可以说毫无美工能力,Rebillion做的软件图标就是依托答辩,他甚至至今仍未给Start页面制作Logo!(他在几个月前就承诺很快完成这一工作) 我对此深感无能为力,接下来会尝试用代码生成一个Logo,但效果嘛…我尽量好吧…… 欸!我太难了,Game页面的背景全是我拿代码硬写的 (天知道有多难!),其他的页面美工约等于没有….我记住了,将来再开发软件,团队里一定要有个美工!
最后,我必须要强烈谴责Rebillion同志,如果没有他,这个项目早就完成了!他在这三年里只做了约两个月的工作,甚至连一个Logo都没有做出来!我TM*********!
2025年6月2日21:27
本次更新是有史以来更新量最大的一次。
经过半个月的高强度开发,我已经完成了项目的 95% 。
这半个月里我把所有的休假时间全部用来开发本项目。
举个例子,今天,昨天,前天,这三天端午节假期,我什么都没干,全力开发本项目,每天干 12 个小时以上。
我经历了许多失败,更改了许多 Bug ,做了数不清的重构。
VS2022 的 Git 组件显示我所有的项目文件全都有更新(当然 .gitignore 不变),单是 更改数 就足足由 106 个,这些更改几乎都是添加或重写了几十上百行代码。
我现在甚至没有力气将他们一一说清,下面简要介绍一下重大更新内容。详细的变更可以看 git commit记录。
彻底重写整个文字相关逻辑,利用
WPF中Textblock相关控件的印刷级输出和排版,实现了在不同字体、字号、颜色、粗体、斜体、下划线、删除线等属性下的逐字符输出。重新定义原创的文字工作脚本语法,增加诸多功能的同时,Rebillion同志仅需使用简单的
.txt文件和特定的目录结构即可完成复杂的剧情分支创作与字符属性设置。重构了切页动画的逻辑,采用了自己组合的
Page_frame类,完美实现了切页防抖。为所有页面的按钮编写
Style以及点击粒子动画,并为不同页面设计不同的主题色资源。为
Trace页面的Listbox制作Style。为所有需要的
Textblock等控件以及各个Style中的文本 添加Viewbox来保持视觉效果。制作整个
Tutorial页面。制作整个
Credit页面。为需要的页面实现简单的边框 (这个我还不满意,后面还会改)。
接下来,就是漫长的等待 Rebillion 同志完成剧情创作和美工了。
2025年5月18日21:01
到现在我又完成了一个目标,成功写出了全局页面切换动画,也就是说所有页面之间的切换已经搞定了!我和AI讨论了好久,也采用过他们的好几版不同的方案实验修改了很久,但都因为反复尝试还是无法修改的致命缺陷被我一票否决。好在我在这一过程中了解了相关内容,最终直接自己原创了一版出来,完美符合要求!
文档见MainWindows的页面切换动画部分。
2025年5月18日14:54
今天是个值得纪念的日子!
截止到现在,我彻底完成了Start页面的美工部分,包括背景和水波纹。
背景:我大概用了10小时左右用豆包制作背景图,背景图要求长宽比为48:9并且左右能无缝衔接。由于图片长宽比过大 ,豆包无法编辑,好在能生成,所以我只能反复开启新对话生成新图片,其中最难的就是左右无缝衔接,在百张以上的图片只有三张还算勉强过关,我选择了左右衔接最好的那一张 (诚然它的画面和颜色不是最好的) 。
水波纹:我一开始采用了canvas控件+WPF动画的方式动态生成会扩散和消失的圆圈来模仿水波纹,最终确实实现了该效果,但是跟我想要的水面波纹效果一点都不像,所以我果断选择重新设计。我选择通过Pixel Shader像素着色器 (2.0版本)来动态实现水面波纹效果,我编写water_ripple.hlsl并使用Windows SDK中的fxc.exe将其编译成water_ripple.ps,之后使用water_ripple_effect.cs与之对接,然后在Start.xaml和Start.xaml.cs中使用。期间经历了多次发现Bug和修复Bug,最终我胜利完成目标!
文档见Start页面的背景部分和水面波纹效果部分。
2025年5月8日13:24
今天完成了一项大工程!
把原来Data类中的剧情相关的内容移动到烟尘记游戏类中,因为剧情相关的数据结构和方法与音乐、选项、存档相关的数据结构和方法本质上是完全不同的,剧情相关的数据结构和方法附属于游戏实例,它们的生命周期也和游戏实例是一样的,这样改动之后封装模块更为合理,减少了烟尘记游戏和Data类之间不必要的耦合。
把李任语录相关内容改成Rebillion_qoute类 (没有s是因为要避让Rebillion_qoutes这个Page类),移出Data类;把选项相关内容改成Option静态类,移出Data类;把存档相关内容改成Save静态类,移出Data类;音乐相关内容改成Music_player静态类,移出Data类。这样就把同一模块中的数据结构和方法很好地封装了起来,并且静态类也更好地体现了它们全局唯一的特性。我把它们放在了一个新的Library.cs文件中,原有的Data类现在仅用于储存一些零散的全局静态变量,比如Main_window等。
只是挨个更改调用好难啊,哪怕有VS 2022的辅助也还是累的要死!
我还优化了Game中生成按钮的逻辑,反正check都破坏Nodes了,也不差再破坏一点,重开游戏的时候重新加载就是了。
我还更改了Pause页面,Option页面,New_start页面的布局方式,从Margin硬布局改为用Grid内的Column和Row定义动态布局,目前所有页面的布局都已经改用了这种方式定义,效果很好。
从今天开始,把每次的更改内容写在历史里,而不是Git commit里面。
2025年5月5日 12:14
完成背景音乐播放功能,大幅度编写文档,增加开发设计部分。
2022年10月3日 13:55
Rebillion同志注册了Github账号,加入了这个仓库,从形式上正式成为了烟尘记项目的联合开发者。
2024年9月21日 16:28
这个项目的第一阶段已经完成,我将它推送到了Github上我的第一个仓库里(私人),并写下了这篇ReadMe。
在这两年中,我遇到了数不清的问题,大大小小,我也从一个照着书打代码200行能出20个报错的初级程序员变成了一个至少写过近两万行代码,读过多本技术书籍的入门级程序员.
确实是入门级,我要学的东西还有太多了,自己都感觉学不完。
2022年9月12日 21:49
我发出了我人生中第八条B站动态,宣告我和Rebillion共同成立一个名为《烟尘记》的长期合作项目。
这是一个文字游戏,我主要负责其中的程序开发部分,那个时候我才刚刚读完人生中第一本计算机书籍《C#入门经典》。
然后,我就下定决心投入到项目的开发中去。