news 2026/10/21 18:55:00

FNF模组开发实战:从零搭建“vs 享利(但是Pico)”完整流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FNF模组开发实战:从零搭建“vs 享利(但是Pico)”完整流程

做 FNF 模组开发时,最容易被卡住的往往不是美术,而是角色接入和谱面调通这两步。尤其当你做的还是“换主角”这种玩法变体,比如把原版按 Pico 的战斗视角重新设计一遍,就会发现角色 XML、Lua 逻辑、动画帧名、谱面 JSON 这四块必须同时对齐,任何一环脱节都会直接黑屏或者爆 Null。这篇教程就以“fnf vs 享利(但是Pico)”作为实战项目,从环境搭建、角色配置、脚本逻辑到谱面编排,完整拆一套可复现的模组制作流程。新手可以按步骤攒出第一个可玩 Mod,有基础的开发者也能从配置规范和排错清单里找到可复用的经验。

1. 背景与核心概念

1.1 什么是 FNF 模组

Friday Night Funkin'(简称 FNF)是一款基于节奏对战玩法的开源游戏,玩家控制的角色需要在节拍内按下对应方向的音符,和对手轮流演唱,更像一场带演出效果的音游 PK。游戏本体使用 HaxeFlixel 引擎开发,底层是 Haxe 语言加 OpenFL 渲染,天然具备比较好的跨平台能力和社区扩展性。

“模组”(Mod)就是在这个基础上做二次创作。它可以是新增一首歌、一个新角色、一张新背景,也可以是完全替换整套玩法。FNF 的模组生态非常活跃,大量社区创作者通过替换美术资源、谱面数据和脚本逻辑,做出了各种“如果某某角色出场会怎样”的平行世界对战。

本文要做的“fnf vs 享利(但是Pico)”,本质属于“换主角 + 新对战角色”的复合型 Mod。玩家将使用 Pico 的立绘、动画和技能动作,去迎战自定义角色“享利”。这种做法的核心不是写游戏引擎,而是把 FNF 的角色资源规范、动画配置和谱面文件理解透。

1.2 Pico 角色定位与原版差异

Pico 是 FNF 宇宙里的经典角色,也是 Pico's School 系列的代表人物。在 FNF 主线里,Pico 以朋友和助战者的身份登场,有自己的主题曲“Pico”,形象特征是红色贝雷帽、严肃表情和比较利落的攻击动作。战斗中他的出牌节奏和待机动画都有自己的节奏感,改造成玩家操作角色时,需要注意几个细节:

  • 待机动画和唱歌动画的帧率要匹配 BPM。
  • 他的音符攻击动作不适合直接套用男友的默认帧,否则会显得“换皮不换骨”。
  • 作为常驻对战角色时,需要给他设计完整的四方向音符反馈,不能只做上下左右某一个方向。

“享利”是这次 Mod 里的对战对象,可以理解成一个自定义 NPC 角色。它的立绘、动画、语音都不来自 FNF 原版,需要创作者自行绘制或者二创整理。所以在制作时,我们要把他的角色包按照 FNF 的规范组织好,并让 Pico 的脚本逻辑能够正确读取和响应。

1.3 模组开发的基本流程

一次完整的 FNF 模组制作通常分成下面几个阶段:

  1. 确定玩法:是原曲换角色,还是全新曲目加全新对手。
  2. 素材准备:立绘、待机图、动画帧、背景图、精灵表(Sprite Sheet)。
  3. 角色接入:把素材裁剪、命名,并让游戏引擎能识别角色动画。
  4. 谱面制作:编辑歌曲的音符落点,形成 JSON 谱面文件。
  5. 脚本逻辑:如果需要自定义对话、阶段动画或特殊机制,用 Lua 脚本实现。
  6. 本地测试:反复调整节奏、动画帧和难度曲线。
  7. 发布打包:整理成规范的 Mod 目录,方便玩家一键安装。

整个流程里,环境搭建是第一道门槛,也是很多人一开始就放弃的地方。下面先看环境怎么准备。

2. 环境准备与版本说明

2.1 搭建 HaxeFlixel 开发环境

FNF 本体基于 HaxeFlixel,所以做模组需要对 Haxe 工具链有基本了解。Haxe 是一门可编译为多平台代码的编程语言,FNF 主要使用它的 JavaScript 和 C++ 导出能力。

安装顺序建议如下:

  1. 安装 Haxe。
  2. 安装 OpenFL。
  3. 安装 HaxeFlixel。
  4. 安装 Visual Studio Code,并装好 Haxe 扩展和 Lua 扩展。
  5. 安装 Git,方便拉取引擎源码和后续版本管理。

命令行安装方式是:

# 安装 Haxe 后,在终端执行 haxelib install openfl haxelib install flixel haxelib install flixel-addons haxelib install flixel-ui

需要注意的是,Haxe 库的版本会随着 FNF 引擎版本变化而不同。官方仓库的project.xml或haxelib.json会写明具体依赖版本,直接按它来装最稳妥。不同版本的 FNF 源码对 Haxe 版本的要求也有差异,建议优先使用引擎作者锁定的版本,不要盲目装最新版。

2.2 获取引擎源码

FNF 的模组开发通常有两条路线:

  • 直接基于原版 FNF 源码改。
  • 基于社区增强引擎 Psych Engine 开发。

原版源码的优点是“原汁原味”,但开发和测试体验对新手不够友好;社区引擎通常已经内置了角色配置读取、Lua 脚本支持、谱面自动加载等功能,非常适合做“新角色 + 新歌曲”类 Mod。

本文示例优先使用 Psych Engine 这类社区引擎。选择原因是:

  • 角色可直接通过characters/目录下的 XML 配置。
  • 谱面 JSON 放在songs/目录后会自动识别。
  • Lua 脚本支持自定义多数对战事件。
  • 内置 Debug 菜单,方便测试角色动画和谱面。

获取方式:

git clone https://github.com/your-engine-source/psych-engine.git cd psych-engine

把仓库拉下来之后,用 Visual Studio Code 打开工程,执行编译命令前先确认依赖已经安装完成。具体编译命令在引擎文档里会写,本文演示以常见命令为例。

2.3 素材与工具准备

做角色素材时,最常用的是 Aseprite 或 Photoshop。Aseprite 更适合做像素风精灵表,Photoshop 更擅长处理大图和逐帧拆分。本文不限定工具,但建议满足以下条件:

  • 输出图片为透明背景 PNG。
  • 单帧尺寸统一,比如 150×150 像素。
  • 动画帧按行排列,生成一张完整的精灵表。
  • XML 中的帧名和图片中的裁切区域一一对应。

谱面制作可以使用 FNF 社区常见的谱面编辑器,也可以直接用文本编辑 JSON。后面对着 JSON 结构慢慢改也能用,只是效率低一些。如果歌曲节奏比较复杂,建议先用编辑器可视化排布,再导出 JSON。

3. 模组核心文件与配置解析

3.1 目录结构说明

一个典型的 FNF 模组目录长这样:

your-mod/ ├── data/ │ └── songs/ │ └── henry-pico/ │ ├── henry-pico.json │ └── henry-pico.easy.json ├── images/ │ ├── characters/ │ │ ├── pico/ │ │ │ ├── pico.xml │ │ │ └── pico.png │ │ └── henry/ │ │ ├── henry.xml │ │ └── henry.png │ └── ui/ ├── songs/ │ └── henry-pico/ │ ├── Inst.ogg │ └── Voices.ogg ├── scripts/ │ └── henry-pico.lua └── mods.json

不同引擎的目录命名可能有差异,比如 Psych Engine 会把音乐放在songs/下,角色放在images/characters/下。模块化的好处是,资源加载时引擎能根据名字自动匹配文件。

3.2 角色 XML 配置解析

FNF 的角色动画系统依赖 XML 文件来描述精灵表的切分方式。一个角色 XML 通常长这样:

<?xml version="1.0" encoding="UTF-8"?> <TextureAtlas imagePath="pico.png"> <SubTexture name="pico idle0000" x="0" y="0" width="150" height="150"/> <SubTexture name="pico idle0001" x="150" y="0" width="150" height="150"/> <SubTexture name="pico singLEFT0000" x="300" y="0" width="150" height="150"/> <SubTexture name="pico singDOWN0000" x="450" y="0" width="150" height="150"/> <SubTexture name="pico singUP0000" x="600" y="0" width="150" height="150"/> <SubTexture name="pico singRIGHT0000" x="750" y="0" width="150" height="150"/> </TextureAtlas>

重点在于:

  • name是动画帧的唯一标识,引擎根据它加载对应动画。
  • x、y、width、height表示这张小图在大图中的位置。
  • imagePath指向同目录下的 PNG 文件。

常见的坑是 XML 里帧名和代码中调用的动画名不一致。比如你在 Lua 里写playAnim('henry left'),但 XML 里是henry singLEFT0000,就必然触发找不到动画的报错。

3.3 动画帧与命中框

FNF 角色的每个动画是由若干连续帧组成。XML 只定义了资源边界,动画节奏是由代码或脚本控制的。引擎通常默认每 24 帧切一次动画,也就是每秒播放 24 帧。

做“vs 享利”这种对战角色时,要保证待机动画的节奏和歌曲 BPM 对齐。比如歌曲 BPM 是 120,那么每拍 0.5 秒,如果待机动画只有 4 帧,每帧停留 1/8 秒就会显得特别跳。一般处理方式是让待机动画有呼吸感,而不是一直高频抖动。

命中框(hitbox)参数决定音符落到角色身上时,攻击动画是否有效。这部分通常不写在 XML 里,而是在角色初始化代码或 Lua 脚本中配置。对新手来说,先保证动画能正常播放,再去调命中框手感。

3.4 Lua 脚本实现对战逻辑

Lua 是 FNF 模组中做自定义逻辑的主要手段。Psych Engine 对 Lua 的支持是:只要在scripts/目录下放同名.lua文件,就会在加载歌曲时自动执行。

最简单的脚本结构:

-- 文件路径:scripts/henry-pico.lua function onCreate() -- 初始化逻辑,比如修改角色位置 debugPrint('Mod loaded!') end function onBeatHit() -- 每个节拍触发一次 -- 可以在这里做闪白、换表情等效果 end function onStepHit() -- 每个步进触发一次 end function onSongStart() -- 歌曲开始时触发 end function onUpdate(elapsed) -- 每帧更新,适合做平滑过渡 end function opponentNoteHit(id, direction, noteType, isSustainNote) -- 对手命中音符时触发 end

这些回调函数在不同引擎版本中可能存在命名差异。比如有的引擎用onNoteMiss,有的用onPlayerMiss。最稳妥的做法是打开引擎源码里的 Lua 文档,确认当前版本支持哪些回调。

3.5 谱面 JSON 格式

FNF 的谱面 JSON 很长,但核心结构稳定。一个简化示例:

{ "song": { "notes": [ { "sectionNumber": 0, "mustHitSection": true, "bpm": 120, "sectionNotes": [ [0, 1, 0, 500], [0, 2, 1, 400], [1, 0, 0, 300] ] } ], "player1": "pico", "player2": "henry", "song": "Henry Pico", "bpm": 120, "speed": 2.0 } }

关键字段含义:

  • sectionNotes数组内每个元素:第一个数字是节拍步数,第二个是方向(0 左,1 下,2 上,3 右),第三个是长度(0 表示普通音符,大于 0 表示长条),第四个是秒数偏移。
  • player1和player2分别对应玩家角色和对手角色,值必须和characters/下配置的角色名一致。
  • speed是谱面滚动速度,数值越大音符下落越快。

谱面 JSON 最容易出错的地方是sectionNumber的切分与mustHitSection的状态切换。如果某个小节没标对,音符会在玩家侧和对手侧之间乱跳。

4. 实战:制作“vs 享利(但是Pico)”

4.1 创建项目结构

打开引擎源码目录后,找到mods/或assets/的对应位置,先创建本次 Mod 的独立文件夹。推荐结构如下:

mods/henry-pico/ ├── data/songs/henry-pico/ ├── images/characters/henry/ ├── images/characters/pico/ ├── songs/henry-pico/ └── scripts/

使用命令行创建:

mkdir -p mods/henry-pico/{data/songs/henry-pico,images/characters/henry,images/characters/pico,songs/henry-pico,scripts}

创建完成之后,再把已经画好的角色素材放进去。实际工作中,我建议不要直接在源码目录里改,先做好独立文件夹,再通过引擎的 Mod 加载机制来测试,这样不会污染主工程,也方便整体打包发布。

4.2 添加角色配置

把 Pico 和享利的角色包分别放入images/characters/。目录里至少包含:

  • pico.png
  • pico.xml
  • henry.png
  • henry.xml

以享利的 XML 为例,如果他的动画帧是 6 组,每组 2 帧,那 XML 里就要写 12 条 SubTexture。一条正确的帧定义如下:

<SubTexture name="henry idle0001" x="300" y="0" width="150" height="150"/>

接着在谱面 JSON 中把player1写成pico,player2写成henry。这样引擎启动战斗时,就会自动加载两个角色的素材。

有一个细节比较重要:角色的 JSON 配套文件。部分引擎会额外读取characters/角色名.json,里面配置角色缩放、偏移、初始位置、颜色等参数。如果缺少这个文件,角色可能会显示在舞台正中间,或者大小明显不对。下面是一个极简版:

{ "animations": [ { "anim": "idle", "prefix": "henry idle", "fps": 24, "loop": true }, { "anim": "singLEFT", "prefix": "henry singLEFT", "fps": 24, "loop": false } ], "position": [500, 350], "scale": 1.0, "healthbar_colors": [160, 80, 80] }

这个文件的字段名在不同引擎里有差异,但prefix与动画名的对应关系是通用的。前缀要和 XML 的name字段匹配,比如henry idle能匹配到henry idle0000和henry idle0001。

4.3 编写对战脚本

本次 Mod 想实现的效果是:开战前有一段自定义对话,战斗中当享利血量低于一半时,背景闪红一次并播放特殊音效。

把这些效果全部写进 Lua 脚本。

-- 文件路径:scripts/henry-pico.lua function onCreate() makeLuaText('introText', 'Henry vs Pico', 500, 200, 300) screenCenter('introText', 'x') addLuaText('introText') end function onBeatHit() if curBeat == 0 then removeLuaText('introText', true) end end function onUpdate(elapsed) local hp = getProperty('health') if curBeat > 96 and hp < 0.4 then setProperty('defaultCamZoom', 1.1) setProperty('bgFlash.alpha', 0.6) end end function opponentNoteHit(id, direction, noteType, isSustainNote) if noteType == 'Henry Special' then triggerEvent('Play Sound', 'henry-special', 0) end end

脚本中用到getProperty、setProperty、makeLuaText、triggerEvent等函数名,在 Psych Engine 的 Lua API 中属于高频常用函数。具体参数名可能因版本不同稍有变化,如果运行报错,优先查看引擎自带的 Lua 文档或现有案例脚本。

4.4 配置歌曲与谱面

歌曲文件需要放到songs/henry-pico/下。音频一般包含两个文件:

  • Inst.ogg:纯伴奏。
  • Voices.ogg:人声。

如果本模组使用 Pico 原版素材,人声部分需要自行处理或者只保留伴奏。当Voices.ogg缺失时,对战会变成没有演唱感的纯乐器模式,代码一般不报错,但体验打折。

谱面 JSON 中的小节数要能和歌曲长度对上。假设歌曲是 4 分钟、BPM 120,那大致有 192 个小节。如果你只写了 10 个小节的谱面,游戏会在 10 个小节之后出现无音符可打的情况,只能干等歌曲结束。因此谱面编辑最好在可视化工具中完成,再导入 JSON。

4.5 进入测试与验证

保存所有文件后,回到 Visual Studio Code 终端,执行编译命令:

lime test html5

如果没有接外设,HTML5 模式最方便。编译完成后浏览器会启动游戏,然后在自由游玩菜单里选中Henry Pico这首歌。如果顺利,应该看到:

  • Pico 出现在玩家侧,待机动画正常循环。
  • 享利出现在对手侧,开场表情正常。
  • 音符按谱面下落,命中时角色有对应的唱歌动画。
  • 血量条正常扣减,胜负判定可以正常结束。

如果只看到黑屏或者字符乱窜,不要慌,大部分情况都能通过日志或控制台输出定位。比如 Haxe 编译时的报错会直接指出第几行调用了不存在的变量,Lua 脚本的debugPrint输出也能帮助定位执行顺序。

5. 常见问题与排查思路

5.1 角色显示异常,图片拉伸或透明

问题现象常见原因解决思路
角色变成大块色斑PNG 非透明背景检查素材导出时是否裁剪正确
角色太小或太大角色 JSON 中 scale 配置异常调整 scale 为合理值,通常 0.7~1.2
角色位置偏离舞台position 设置超出舞台范围基于 1280×720 分辨率调试
角色全身发黑XML 的 SubTexture 坐标错误重新核对大图的切图坐标

5.2 谱面加载失败或闪退

常见原因有三个:音频文件格式不兼容、JSON 里字段拼写错误、player1角色名不存在。

排查顺序建议:

  1. 先单独播放Inst.ogg和Voices.ogg,确认音频本身有效。
  2. 用在线 JSON 校验工具检查谱面文件格式。
  3. 打开源码日志,查看是角色加载失败还是谱面数据缺失。

一个容易被忽视的坑是路径大小写。Linux 环境下,Henry与henry可能会被当成不同路径,但 Windows 不区分。发布前统一小写命名,能省掉大量跨平台问题。

5.3 动画不播放,角色一直保持第一帧

这通常是因为 XML 帧名前缀和动画配置前缀不匹配。比如 XML 里写的是:

<SubTexture name="henry idle0000" .../>

但角色 JSON 里写的是:

{"anim": "idle", "prefix": "henry idle "}

注意prefix末尾有无空格,以及引擎是否自动补零。稳妥做法是让 XML 里的帧名严格按角色名 动画名0000这一规律命名,不要夹带多余字符。

5.4 Lua 脚本报错

Lua 报错在控制台通常以Lua Error开头。高频问题包括:

  • 使用了当前引擎不存在的回调函数名。
  • 调用了getProperty时传入的路径写错。
  • 在onUpdate里每帧创建新的文本对象,导致内存暴涨。

快速定位方法:在关键回调前加debugPrint('here'),逐步缩小范围。脚本类问题大多不是语法难,而是函数名和属性路径没对齐。

6. 最佳实践与工程建议

6.1 素材命名规范

角色动画帧的命名直接决定开发效率。建议统一为:

  • 待机:角色名 idle0000开始
  • 左唱:角色名 singLEFT0000开始
  • 下唱:角色名 singDOWN0000开始
  • 上唱:角色名 singUP0000开始
  • 右唱:角色名 singRIGHT0000开始

这样写角色 JSON 时,前缀规则一目了然,后续换角色时也只需要整体替换。

6.2 谱面与歌曲节奏对齐策略

做谱面时不要把注意力全放在音符密度上。先定 BPM,再定小节数,最后填音符。比较稳妥的流程是:

  1. 用 DAW 确认歌曲 BPM。
  2. 在谱面编辑器中设定 BPM 和偏移量。
  3. 先用四拍一个音的方式完成整首歌骨架。
  4. 再逐步细化,加入长条和组合键。

如果歌曲中途有变速或停顿,需要拆成多个 BPM 区间。FNF 谱面 JSON 允许不同小节指定不同 BPM,但写起来很容易乱,建议用可视化编辑器处理。

6.3 模组兼容性

社区引擎更新很快,一个 Mod 可能今天能跑,明天引擎升级后角色 JSON 的字段名变了就报错。外部依赖较少的 Mod 更容易长期维护。尽量把逻辑写在 Lua 脚本而非改引擎源码,这样即使引擎升级,只要 Lua API 没大改,脚本还能继续用。

6.4 发布与版权

发布到 GameBanana 或 GitHub 时,注意素材来源。如果用了别人现成的 Pico 素材,需要在简介中标注原作者或确认二创许可。自制素材则建议附上简单的创作声明。FNF 模组社区整体比较开放,但不能因此忽视基本版权规范。

7. 总结与学习路线

7.1 本文要点回顾

通过“fnf vs 享利(但是Pico)”这个实例,核心掌握了四件事:

  • FNF 模组的目录结构和资源加载逻辑。
  • 角色 XML、角色 JSON、精灵表三者的配合方式。
  • Lua 脚本处理自定义战斗事件。
  • 谱面 JSON 的简易结构和对齐思路。

这些内容不只适用于本文角色,换成任何新角色、新歌曲,都能按同样流程推进。

7.2 下一步可以学什么

如果你打算继续深入 FNF 模组开发,可以往这几个方向扩展:

  • 学习 Haxe 语言基础,直接修改引擎源码。
  • 学习事件系统,比如镜头抖动、屏幕滤镜、弹窗对话、动画事件触发器。
  • 研究 FNF 原版歌曲的谱面结构,分析它们如何设计难度曲线。
  • 尝试把音游逻辑迁移到其他 HaxeFlixel 项目。

平时遇到问题,先看引擎自带的示例、官方文档和社区源码,比到处问人更高效。模组开发本质上是一个“素材管道 + 脚本控制”的过程,只要把资源规范吃透,后面做再复杂的角色都会顺手很多。希望这篇教程能帮你少踩几个坑,早日做出自己的第一个可玩 Mod。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 16:56:11

FNF模组开发实战:腐化重构v4 Pico周技术拆解

“周五夜放克”&#xff08;Friday Night Funkin&#xff0c;简称 FNF&#xff09;是一款以“节奏对战 手绘动画 音乐叙事”为核心的同人音游。它之所以能形成庞大的模组生态&#xff0c;不仅因为游戏本身好玩&#xff0c;更因为它给玩家留了一条“低门槛做内容”的通道&…

作者头像 李华
网站建设 2026/10/21 11:24:54

AFSim 2.9.0 Linux编译指南:从环境配置到wizard验证

简介&#xff1a;这份PDF文档面向需要在Linux平台编译AFSim仿真工具集的开发者与研究人员&#xff0c;针对AFSim 2.9.0版本在Ubuntu 22.04.4 LTS下的完整编译流程进行记录与整理&#xff0c;适合具备一定命令行操作经验、希望从源码构建AFSim的中高级用户参考。资源包内仅含1个…

作者头像 李华
网站建设 2026/10/18 12:43:13

工业机器人工具坐标系标定算法:四点法、六点法与自动标定实战

简介&#xff1a;这份资源聚焦工业机器人工具坐标系标定算法&#xff0c;面向从事机器人控制、自动化集成与运动学研究的工程师及高校师生&#xff0c;用于解决TCP位置与TCF姿态标定中的精度与效率问题。内容围绕最小二乘法拟合TCP位置、坐标变换计算TCF姿态展开&#xff0c;涵…

作者头像 李华
网站建设 2026/10/19 2:47:30

开源项目本地部署信任审计:从代码审计到运行隔离的完整检查清单

如果只从标题看&#xff0c;【下谁】TRUST ME 更像是一句网络梗。但在技术语境里&#xff0c;它问的其实是很现实的问题&#xff1a;你要下载谁的代码&#xff1f;凭什么信任它&#xff1f;每一个开源项目、每一个“一键启动包”都在对你喊 TRUST ME&#xff0c;但信任不是靠口…

作者头像 李华
网站建设 2026/10/19 0:54:35

多维度URL信任评分工具TRUST ME:用Python实现链接可信度检测

在日常开发里&#xff0c;你是不是也经常遇到这种情况&#xff1a;下载依赖包的时候&#xff0c;镜像源地址好几个&#xff0c;不知道该信哪个&#xff1b;群里转发来的一个内测链接&#xff0c;看着挺真&#xff0c;点进去却发现不是官方域名&#xff1b;GitHub 上搜索同名的开…

作者头像 李华
网站建设 2026/10/21 8:21:45

DeepSeek R1推理模型实战:从选型到提示语避坑全指南

简介&#xff1a;这是清华大学新闻与传播学院新媒体研究中心元宇宙文化实验室团队出品的DeepSeek学习指南&#xff0c;面向普通用户与开发者&#xff0c;帮助快速上手并进阶。压缩包为1个PDF文件&#xff0c;大小3.69MB&#xff0c;便于离线查阅。内容从DeepSeek是什么、能够做…

作者头像 李华