Project1
标题:
黑暗圣剑传说RGSS脚本研究
[打印本页]
作者:
madbasara
时间:
2026-3-1 20:27
标题:
黑暗圣剑传说RGSS脚本研究
写在前面:本文撰写的版本是黑暗圣剑传说ver1.0 ,根据水友提供的资料早在2005年5月29日,1.0正式版已对外发布。在启动exe后,游戏开始页面显示黑暗圣剑传说Final版
后续可能还有更新,貌似在2006年5月8日发布过新版本,请自行甄别各自的游戏版本,如果看下文发现跟自己的工程对不上,那就说明版本对不上。
本文将按照脚本编辑器左侧列表的顺序依次解释每个脚本,包括概述、核心代码的含义、柳柳自定义的脚本这三大部分,旨在帮助曾经像我一样,不懂代码,自己更没有写过一行代码的编程小白理解这个经典项目。
我在大学2012年左右第一次接触柳柳的这个上古作品,对于里面的脚本与其说一知半解,说是一头雾水更合适。一晃眼,12年的游戏策划生涯过去了,还是没有手搓代码的能力,但是vibe coding经验积累了不少,于是打算花时间认真回看一下这款ruby语言 RGSS脚本的RMXP研发出来的入坑作。
致敬始终坚持走在这条路上初心未改的朋友。
正片开始
[size=16.0000pt]
Game_Temp
游戏运行时的临时记事本
。
1. 这个类是干嘛的?
它的注释写得很清楚:
“在没有存档的情况下,处理临时数据的类”。
策划视角的理解:
想象您在玩《黑暗圣剑传说》。
·
您打开菜单,又关掉
—— 这个“菜单打开过”的状态,需要一个地方记一下。
·
您和
NPC 说话,显示了一半文本,按了确定 —— “接下来要显示哪行字”需要一个地方记一下。
·
您踩了一个地雷,进入战斗
—— “敌人的队伍ID是多少”、“能不能逃跑”这些信息,需要一个地方记一下。
这些信息
不需要存档
(下次读档不需要记住菜单刚才是不是开着),但
当下
必须记下来,否则游戏逻辑就乱套了。
$game_temp
这个全局变量,就是这本**“临时记事本”**。
2. 关键代码解析
[size=14.0000pt]定义和初始化默认值
·
常见默认值
:
o
开关类
:通常是
false
(关)。比如
@in_battle = false
(默认不在战斗中)。
o
数字类
:通常是
0
。比如
@battle_troop_id = 0
。
o
文本类
:通常是
nil
(空)或
""
(空字符串)。
o
特殊类
:
@choice_start = 99
。这个
99
是 RMXP 的一个惯例,表示“选择项还没开始”或者“无效值”。
3. 《黑暗圣剑传说》的特色修改
这就是您要逐行阅读的意义所在了。请注意看这两行,这是柳柳(作者)加进去的
“私货”
:
[size=14.0000pt]attr_accessor :mgbuy_calling 调用购买魔法
[size=14.0000pt]attr_accessor :$book1
[size=14.0000pt]attr_accessor :$book2
[size=14.0000pt]attr_accessor :$book3
[size=14.0000pt]解析:
[size=14.0000pt]:mgbuy_calling:
[size=14.0000pt]
原版
RMXP 默认只有 shop_calling(调用普通商店)和 name_calling(调用取名画面)。
[size=14.0000pt]
这里多了一个
“购买魔法”的标志。说明这个游戏里有一个独立的系统,玩家可以在特定地点购买魔法,而不是买道具。
[size=14.0000pt]
当脚本检测到
$game_temp.mgbuy_calling = true 时,画面就会跳转到那个特殊的“魔法商店”界面。
[size=14.0000pt]:$book1 等:
[size=14.0000pt]
这里的
$ 符号命名有点奇怪(通常属性变量用 @ 开头,如 @book1),但 Ruby 允许这样写,这实际上是在类内部直接定义了全局变量 $book1 的访问器。
[size=14.0000pt]
这显然是用来记录游戏里的特定收集进度。比如
“大地之书”、“火焰之书”是否已获得。
[size=14.0000pt]
把它放在
Game_Temp 里,意味着这些数据可能不需要存档,或者作者是为了方便在全局随时调用。
[size=14.0000pt]4. 总结
[size=14.0000pt]这段代码作为脚本阅读的第一课非常典型。
[size=14.0000pt]
[size=14.0000pt]
结构:定义类
-> 定义属性 -> 初始化默认值。
[size=14.0000pt]作用:它是游戏状态流转的中转站。
[size=14.0000pt]
[size=14.0000pt]Game_System
[size=14.0000pt]
Game_System
就是游戏的
“系统管家”**。
它负责掌管那些
需要存档保存
的全局设置(比如禁止存档、计时器、窗口皮肤),以及所有的
音频播放
。
·
Game_Temp
:用完即弃的草稿纸(如
“当前对话框的内容”)。
·
Game_System
:随身携带的档案袋(如
“主角的名字”、“总共存档了几次”、“当前BGM是什么”)
《黑暗圣剑传说》的特色修改
A. 双计时器系统
原版
RMXP 默认只有 1 个计时器(显示在屏幕上的倒计时)。
这意味着游戏里可能存在
两套倒计时机制
。比如:
·
计时器
1:显示给玩家看的“限时任务倒计时”。
·
计时器
2:隐藏的系统计时器(比如“种下的种子多久成熟”、“Buff还剩多久”)。
B. 复杂的 BGM (背景音乐) 控制系统
这是整段代码里最长、也是最
“硬核”的部分。作者重写了
bgm_play
方法。
原版逻辑很简单:
让你放啥就放啥
。
但作者加了一个全局开关
$playing_self_BGM
。
[size=12.0000pt]1.
如果
$playing_self_BGM == true
(正在播放自定义音乐)
:
[size=12.0000pt]2.
·
此时地图想切换
BGM? ->
拒绝切换
!只把新
BGM的名字记在
$map_bgm
里,但音响继续放当前的自定义音乐。
·
目的
:防止场景切换(比如走进新地图)打断正在播放的剧情音乐或小游戏音乐。
[size=12.0000pt]3.
如果
$playing_self_BGM == false
(正常模式)
:
[size=12.0000pt]4.
·
正常播放
BGM。
策划含义
:
这说明游戏里有
强制覆盖背景音乐
的特殊场景。
·
比如:进入一个
“回忆杀”剧情,或者玩一个“音乐小游戏”。此时地图BGM必须被屏蔽,不能因为玩家乱跑就变调了。
C. 音量调整 (细节)
注意看
se_play
方法:
Audio.se_play("Audio/SE/" + se.name, se.volume * 0.72, se.pitch)
作者把音量乘以了
0.72
。
策划含义
:
这通常是为了
混音平衡
。可能原版的音效太吵,容易盖过
BGM,作者统一把所有音效音量压低了 28%,让听感更舒适。这就是脚本修改带来的精细化控制。
D.被注释掉的双手武器
#
attr_accessor :two_handed_weapons 被注释掉的双手武器效果
调试用放在
Game_System 是“可以工作”的合理方案,但并非“设计最优”的方案。 它是特定开发阶段(追求快速实现)和特定框架限制(RMXP脚本结构)下的产物
合理的架构思维:每个类应有一个清晰的职责。
Game_System 应该专注于运行时状态管理(如音乐、计时器、系统开关),而静态的游戏规则数据应尽可能与具体的数据对象(如武器、角色)关联。
那么柳柳为什么
为何
把它
放在
Game_System?
1. 它是“系统级规则”的全局开关
Game_System 类的核心职责是处理系统附属数据和全局设置
双手武器的判定规则,在某种程度上确实属于
“游戏规则”层面的系统设置,类似“禁止存档”、“禁止遇敌”等标志。
合理性:它希望定义一个全局生效的规则,任何场景、任何角色装备武器时,都可以查询
$game_system.two_handed_weapons 来判断。
问题:这种设计将
“游戏规则数据”与“系统状态数据”混在了一起。Game_System 变成了既管理BGM、计时器,又管理游戏机制规则的“大杂烩”类,违反了面向对象设计中的单一职责原则。
2. 历史与开发成本的考量
RMXP的脚本架构是预设的,对于初学者或独立制作人来说,修改引擎核心数据类(如 RPG::Weapon)的门槛和风险都较高。
快速实现:在
Game_System 中添加一个数组,可以最快速地实现功能。开发初期,制作人可能更关注“让游戏跑起来”。
后续调整:随着项目复杂,发现这种设计难以维护(例如,要修改双手武器判定逻辑,需要到处查找调用),于是将其注释掉,这本身就是一次重构的尝试。
作者:
madbasara
时间:
2026-3-3 13:44
Game_Switches
Game_Switches 这个类,专门用来管理 RPG Maker 里的开关(Switches)。 是最底层的基础设施,通常比较稳定,没必要就不动。
这段代码是原版 RMXP 的标准代码,柳柳在这里没有进行明显的修改。
但是,有一个细节值得策划们注意,这体现了 RMXP 引擎本身的限制:
硬编码的上限 5000:
代码里两次出现了 if switch_id <= 5000。
这意味着这个游戏的开关数量上限被锁死在 5000 个。
Game_Variables
Game_Variables 类与之前的 Game_Switches 结构高度相似,本质上是对底层 数组 的一层封装。
从架构设计的角度来看,它实现了以下机制:
数据容器封装:
它将 Ruby 原生的数组 @data 封装为一个具备游戏逻辑含义的“变量池”。通过重写 [](读取)和 []=(写入)方法,接管了所有对游戏变量的存取操作。这是典型的门面模式,为底层数据结构提供了统一的访问接口。
惰性初始化与默认值策略:
这是策划需要特别关注的设计点。
在 def [](variable_id) 方法中:
if variable_id <= 5000 and @data[variable_id] != nil
return @data[variable_id]
else
return 0 # 关键逻辑
end
引擎预设了一个规则:任何未被赋值的变量,其初始值默认为 0。
这意味着脚本无需在游戏启动时为几千个变量一一赋初值(这会消耗大量内存),而是采用“用到时再判断”的策略。当策划在事件中引用一个从未设置过的变量时,系统不会报错,而是安全地返回 0。这大大降低了逻辑配置的容错成本。
硬边界约束:
if variable_id <= 5000 设定了数据边界。引擎将变量的寻址空间限制在 0-5000 之间,防止越界访问导致的内存溢出或程序崩溃。这是一个基于性能权衡的配额管理机制。
本段代码完全沿用 RMXP 引擎默认脚本,柳柳无任何自定义修改。
作者:
madbasara
时间:
2026-3-3 16:52
Game_SelfSwitches
Game_SelfSwitches 类负责管理游戏中的独立开关,是 RMXP 事件编辑器的基础功能,用于控制如“宝箱已开”、“NPC对话过一次”等局部状态。
由于其功能单一、逻辑封闭,且与地图事件系统(Game_Map、Game_Event)耦合紧密,通常不需要在底层进行扩展。柳柳在此处未做修改,说明游戏内的局部事件触发逻辑均使用了标准功能,没有引入特殊的独立开关判定机制。
Game_Screen
Game_Screen 类是游戏视觉表现的状态机。它本身不负责“画”出图像,而是负责计算和管理所有画面特效的当前状态。
本段代码完全沿用 RMXP 默认脚本,无任何功能性修改。涉及到底层渲染逻辑,属于引擎的“地基”。除非要重写整个战斗系统或引入全新的渲染管线,否则修改此处风险极高且收益不大。
柳柳未对此处进行修改,说明游戏中的天气、色调、震动等表现手段均使用了引擎原生功能,未引入特殊的视觉需求。
我们可以更深入的从系统架构来看,这段代码的的职责。
特效状态管理:
它并不直接调用显卡绘图,而是维护了一组描述画面状态的“参数值”。例如,色调、闪烁颜色、震动偏移量。它将这些数值实时计算好,供底层的 Sprite 类读取并渲染。
时间插值机制:
请关注 update 方法中的这段代码:
@tone.red = (@tone.red * (d - 1) + @tone_target.red) / d
这是经典的线性插值算法。
当策划在事件指令里设置“渐变色调 2 秒”时,系统不会瞬间变色,而是利用这个公式,在每一帧计算当前应有的颜色值,从而实现平滑的过渡效果。无论是天气变化、色调渐变还是画面闪烁,都依赖于此。
图片资源池的双轨制管理:
这是最值得注意的设计细节。 if $game_temp.in_battle
for i in 51..100
@pictures
.update
end
else
for i in 1..50
@pictures
.update
end
end
系统硬编码将 100 个图片槽位划分为两个区域:
* 1-50 号图片:专用于地图场景。进入战斗后会自动停止更新,处于“冻结”状态。
* 51-100 号图片:专用于战斗场景。平时不更新,战斗开始后激活。
欢迎光临 Project1 (https://rpg.blue/)
Powered by Discuz! X3.1