![]() |
|
如何将模组(脚本)拆分为多个 Include 文件(包含文件) - 可打印的版本 +- samp | open.mp 联机社区论坛 (https://forum.open-mp.cn) +-- 版块: SA-MP (https://forum.open-mp.cn/forumdisplay.php?fid=12) +--- 版块: 教程 (https://forum.open-mp.cn/forumdisplay.php?fid=17) +--- 主题: 如何将模组(脚本)拆分为多个 Include 文件(包含文件) (/showthread.php?tid=2) |
如何将模组(脚本)拆分为多个 Include 文件(包含文件) - siwode - 02-27-2026 本主题写给那些还没有完全理解“把模组拆分成 include(inc)文件”意味着什么的人。很多人把它理解为:从主文件(gamemode.pwn)里剪下一些代码片段,然后复制到单独的文件(name.inc)里。这个方法不能说好,反而更偏向于不好。 这种拆分代码的逻辑通常是:“这里行数很多,我把它们挪到单独文件里,这样主模组里行数就少了。”但在后续开发中,这反而会把事情搞复杂:你不得不同时打开一堆文件来回切换,还要一直记住调用顺序、每个变量/函数的作用域等等。 我会尽量解释:怎样做会更好;以及到底应该如何理解“模块化”,并且如何把这一切和模组(gamemode)配合起来。这里我所说的“模块”,指的是一个完全自治或部分自治的系统,它有自己的 API 函数用于交互,并被放在一个或多个独立的 include 文件中。 核心原则:所有交互(处理流程)发生在模组里,而计算/实现细节放在 include 文件里完成。 首先,你需要确定将会使用哪些系统。接着把这些系统分成两类:独立 与 依赖。 “独立”表示它可以脱离其他系统单独使用;“依赖”表示它需要与其他系统配合使用。在模组里连接 include 的顺序应该是:先引入 独立 系统,然后再引入 依赖 系统。 那怎么判断一个系统属于哪一类? 账号系统 是服务器上最重要的系统,没有它几乎无法想象服务器如何运行。这个系统几乎与所有其他系统都有关联。关键要理解:我们是在其他系统中调用账号系统的函数,而不是在账号系统内部去调用其他系统的函数。因此它属于 独立 系统(模块)。它应该包含什么?例如: 代码: CheckPlayerAccount(playerid) - 检查账号是否存在(是否已注册)我们来详细看看 CheckPlayerAccount。它应该用在哪里、做什么?我们在 OnPlayerConnect 中调用它。在这个函数里,我们向数据库发送 SELECT 查询,根据玩家名字查找玩家信息。 立刻会有个问题:名字从哪里获取、存在哪里?你当然可以在 OnPlayerConnect 里用 GetPlayerName 去取名字,但玩家名字本质上属于“账号信息”,对吧?既然如此,与其在 OnPlayerConnect 里调用 GetPlayerName,不如把这一步移动到 CheckPlayerAccount 里更合理。 代码: [gamemode.pwn]代码: [account.inc]再说两句数组 account_name 和宏 GetPlayerAccountName。既然我们在写一个独立系统,最好让它拥有自己的 API 函数,以及用来存储信息的变量/数组。专门写一个返回 字符串 的函数并不是最佳实践,所以我们用 new 声明数组 account_name,让它可以在其他 include 中被访问。然后我们做了一个“看起来像函数”的宏:GetPlayerAccountName。 那为什么要用宏/函数,而不是直接访问变量/数组? 首先是为了避免未来自己犯错;同时提升可读性,避免以“不正确的方式”使用变量/数组。另一个便利是:所有 API 函数都能在命名上有自己的“根”,例如 Account,让你一眼知道这个函数属于哪个系统。需要强调:为每个变量都做一个函数/宏不是硬性规则,只是建议;有时直接用会更合理。 发送查询后,我们在 OnCheckPlayerAccount 中等待结果。现在要决定:这个函数应该写在模组里还是 include 里?这里我们把它写在 include 里。 在数组 account_name 后添加: 代码: [account.inc]在宏 GetPlayerAccountName 后添加: 代码: [account.inc]现在创建 OnCheckPlayerAccount: 代码: [account.inc]那么 OnPlayerConnected(playerid, bool: status) 是什么?在哪里用?我们传入的参数是什么? 这个函数表示:我们已经向数据库查询并拿到了该玩家的响应。参数 status 表示玩家是否已注册。 如果找到了任何信息,就加载并用 true 调用 OnPlayerConnected;否则用 false。这个函数的使用应该在 gamemode.pwn 中,但它的 forward 声明应写在 include 中。 代码: [account.inc]代码: [gamemode.pwn]这样一来,我们就完成了:从账号系统调用函数,在 include 内部完成处理,然后把结果回传到模组。 模组 - include / include - 模组 的交互关系应该大体都遵循这种思路。 ![]() 再说 依赖 系统,例如 派系。流程类似,只不过我们允许在该系统里调用其他系统的函数,比如账号系统:拿到玩家 ID 或名字后再做后续处理。 ![]() 本质上就是:把该系统的所有动作都放进它的 include 里,而把得到的结果交给模组进行处理。 如果你很难正确分配动作、尤其是遇到作用域问题,那通常就说明这段代码应该挪回模组中。 还可以谈谈更复杂的系统:当一个系统使用不止一个 include 时,可以创建一个单独的文件夹,把需要的 include 都放进去。 比如你有一个背包(inventory)系统:它需要保存玩家基础数据、UI 相关数据、以及与玩家交互的函数。你可以创建 inventory 文件夹,并创建 core 和 ui 两个 include。 ui 里放绘制 UI 所需的一切,而 core 放主要数据与交互逻辑。 如果在模组里这样引入: 代码: #include "../modules/inventory/core.inc"会出现问题:在 core 里无法访问 ui 的函数。 如果这样引入: 代码: #include "../modules/inventory/ui.inc"又会出现相反的问题:在 ui 中无法获取 core 中的数据。 怎么解决?当然可以把一切都合成一个 include,让常量和函数彼此可见,但这里换个方式:把 ui include 在 core include 内部引入。 代码: [core.inc]而在模组中只保留对 core include 的引入。这样就把系统的一个 include 的引入 “隐藏”在另一个(主)include 里,作用域问题也就不存在了。 再补充一点:凡是只在 include 内部使用的变量/函数,应该用 static 来创建,避免它们在其他地方被随意调用。 另外,也可以在函数名前面加下划线 “_” 来明确表示:该函数不对其他 include 暴露;例如 OnCheckPlayerAccount 可以写成 _OnCheckPlayerAccount。 当你有一个“由多个 include 组成”的系统(如背包),并且你需要某个函数在所有被引入的 include 中可见、但在模组中不可见时,单纯用 static 不行(因为 static 会导致其他 include 也看不到)。这里有个绕过方法: 代码: [core.inc]我们先把函数做成全局的,然后再“隐藏”它。由于宏是在 include 的末尾定义的,所以它对模组生效,而对 include 本身不生效。 同时宏在调用解析上优先于函数,因此通过这个宏,我们把对 TestFunction 的调用重定向到一个不存在的函数 _TestFunction。这样如果在模组里调用 TestFunction,就会报错:该函数不存在。 再补充:还有一种方法,可以在不同 include 中使用相同的函数名,但该函数在模组中无法被调用。可能不太常用,不过示例如下: 代码: [core_1.inc]代码: [core_2.inc]最后我个人建议:给自己的系统写注释,这样未来你能更清晰地知道:该系统哪些内容能在模组里使用。 我通常会在引入之前加一个提示: 代码: /*然后像这样引入 include: 代码: #include "../modules/account.inc" |