本文节选自《LPC语言基础教程:从零学习游戏开发》,版权归@mudren,欢迎转载,但必须注明来源(mud.ren)。
前面章节讲了 exec() 外部函数可以把玩家控制的对象从A改到B,而 bind() 外部函数可以把调用一个函数的对象从 this_object() 改成指定对象,现在再讲解一个类似的外部函数 shadow。
在教程配套的 mudlib 中已经实现了一个同名指令 shadow,用法是: shadow A on B,现在来做一个测试:先输入指令 shadow /cmds/look on /cmds/test/test,然后输入 test 会发现指令 test 的作用变成了 look。
我们看看 test 指令的代码:
// test.c
int main(object me, string arg)
{
// 请在此实现你的代码
return 1;
}
代码没有相关功能,但指令的作用神奇的改变了,这就是 shadow 外部函数的作用,不改变对象代码的情况下改变对象的功能。该函数的具体说明如下:
名称
shadow() - 投影某个对象中的一个或多个函数
语法
object shadow( object ob, int flag );
描述
如果参数 `flag` 是 1 或者不指定,当前对象会投影对象 `ob`。如果参数 `flag` 是 0 ,要么返回 0 要么返回已经投影对象 `ob` 的对象。
主控对象中定义了 apply 方法 "valid_shadow()",每次执行 shadow() 外部函数时主控对象都会调用此方法,如果 valid_shadow() 返回 0 那么目标对象无法被投影,这时 shadow() 函数会返回 0 而不是被投影的对象 ob。
如果对象 a 投影对象 b,所有针对对象 b 的函数调用都会重定向到 a,只有 a 中没有定义被呼叫的函数时才会从 b 中呼叫。对象 b 中的内部方法仅限 b 自己调用,如果要通过 call_other() 呼叫 b 中的方法的话,仅对象 a 可以这样用。
请注意:如果当前对象已经投影了对象,或被对象投影,或者在任何环境中,将无法再投影任何对象,否则会报错。但是,如果对象 ob 已经被对象 A 投影了,还可以继续被对象 B 投影,结果是 B 投影 A,A 投影 ob,这也是让对象投影已投影其它对象的对象的唯一方式。另外,对象不能投影自己,即函数中参数 ob 不可以是 this_object(),否则会报错。
有二种方法可以移除投影,或者摧毁投影对象,或者摧毁被投影对象。而如果编译驱动时定义了 PACKAGE_CONTRIB,还可以使用 remove_shadow() 外部函数取消投影。在教程中可以直接使用 update 或 destruct 指令摧毁对象完成投影的移除。
利用投影,可以把一个对象隐藏在另一个对象后面,又可以像没有被隐藏一样运行。shadow() 外部函数可以在不改变相关对象代码的情况下改变对象的行为。举例来说,通过 shadow() 可以很容易的给不同玩家增加职业上的特殊能力,比如盗贼、战士、法师等。开发世界剧情任务时剧情场景的临时变化、BOSS变身等游戏玩法,都可以使用 shadow() 外部函数来实现。
最新的LPC-TEST代码中增加了怪物变身功能,就是通过shadow简单的实现的,和怪物战斗,有一些怪物在HP低于一定数值会变身为更强力的怪物,有兴趣的可以测试一下……

在游戏开发中,shadow() 函数的核心价值在于不修改原对象代码的前提下动态扩展或覆盖行为,以下是适合优先使用 shadow() 的场景总结(均为能简化逻辑、降低耦合的典型场景):
1. 角色状态/ buff 系统
- 场景:为角色临时添加状态(如中毒、加速、隐身)或 buff(如攻击力提升、伤害减免)。
- 优势:每个状态对应独立的影子对象,覆盖
heart_beat()(持续效果)、take_damage()(伤害修正)等函数,状态结束时销毁影子即可移除效果,无需在角色对象中堆砌大量条件判断。 - 示例:中毒状态的影子对象通过
heart_beat()实现每秒掉血,解毒时销毁影子,自动停止掉血逻辑。
2. 职业/身份动态扩展
- 场景:为基础角色对象添加职业特性(如战士的暴击、法师的魔法消耗减免),或临时身份(如临时切换为“卧底”获得特殊交互权限)。
- 优势:职业逻辑与基础角色对象解耦,新增职业只需编写对应的影子对象,避免基础角色代码臃肿。切换职业时,销毁旧职业影子并投影新职业影子即可。
- 示例:盗贼职业影子覆盖
move()函数实现潜行效果,切换为战士时销毁影子,恢复默认移动逻辑。
3. BOSS 多阶段战斗
- 场景:BOSS 战斗中切换阶段(如二阶段狂暴、三阶段召唤小怪),需改变技能、血量、攻击模式。
- 优势:每个阶段逻辑封装在独立影子对象中,阶段切换时投影新影子(或形成投影链),避免在单个 BOSS 对象中用大量
if-else判断阶段状态。 - 示例:BOSS 血量低于 50% 时,投影二阶段影子对象,覆盖
attack()函数提升伤害,新增summon_minion()技能。
4. 装备/物品动态效果
- 场景:装备穿戴后附加特效(如武器吸血、盔甲减伤),或物品使用后临时改变角色行为(如使用“隐身药水”获得隐身效果)。
- 优势:装备/物品效果通过影子对象实现,穿戴/使用时投影,脱下/时效结束时销毁影子,无需在角色对象中硬编码所有装备逻辑。
- 示例:魔法戒指影子覆盖
cast_spell()函数提升法术威力,戒指脱下时影子销毁,法术威力自动恢复。
5. 临时功能调试与测试
- 场景:调试时临时输出日志(如记录 NPC 对话内容)、禁用特定功能(如测试时禁用玩家回城技能)。
- 优势:无需修改原对象代码,通过影子对象覆盖目标函数添加调试逻辑,测试结束后销毁影子即可还原,不影响正式代码。
- 示例:影子对象覆盖
say()函数,将 NPC 对话同时输出到日志文件,调试完成后移除影子。
6. 剧情/任务触发的行为变更
- 场景:任务过程中临时修改 NPC 或环境行为(如任务触发后,村民变为敌对状态;机关激活后,门的交互逻辑改变)。
- 优势:剧情逻辑与基础对象分离,任务触发时投影影子对象覆盖关键函数(如
react_to_player()),任务结束后销毁影子恢复原始行为。 - 示例:任务触发后,影子对象覆盖守卫的
allow_entry()函数,拒绝玩家进入,完成任务后影子销毁,恢复通行。
7. 多层功能组合(投影链)
- 场景:多个独立效果同时作用于同一对象(如角色同时处于中毒、狂暴、装备加成状态)。
- 优势:利用投影链(如
效果C → 效果B → 原对象),每层影子负责一个功能,调用自动从外层向内层传递,避免功能间的逻辑冲突。 - 示例:中毒(掉血)、狂暴(增伤)、装备(减伤)三个影子形成链,各自逻辑独立,叠加生效。
核心适用原则
- 临时性:功能需动态启用/禁用(如状态、 buff、临时任务)。
- 独立性:功能可封装为独立模块(如职业特性、装备效果),与原对象低耦合。
- 可扩展性:需频繁新增类似功能(如多职业、多阶段),避免修改基础代码。
这些场景中,shadow() 能显著降低代码耦合度,提升可维护性,避免因直接修改原对象导致的逻辑混乱。