Cobblemon NPC的写法
呀~ 哈喽!这里是方块宝可梦翻译群的杂役——惯宣!最近在方可梦的翻译群里,对于NPC用法的讨论可谓是炸开了锅!由于Wiki上没有详细说明NPC的功能和数据包的写法,导致这部分内容实在晦涩难懂。伴随着讹误与谣传,经过群内好事老哥的不懈努力之下,我们才终于得以将其本源面貌大致还原了出来。
现总结如下。
版本:Cobblemon-fabric-1.7.1+1.21.1
基本内容 |
生成NPC
使用命令'/npcspawn <文件> [等级]'或者'/npcspawnat'生成NPC。当前版本里,默认有4个NPC。分别是:ai_test、kitchen_sink、sacchi和standard。
朝向NPC使用命令'/npcdelete'可以删除NPC。
编辑NPC
朝向NPC使用命令'/npcedit'可以编辑NPC。NPC的默认设置不会显示在界面中,需要点击左下角的行为按钮以配置NPC的功能。
以下是比较重要的行为:
| 行为 | 说明 |
|---|---|
| 移动&搜索 核心 | 移动行为的前置,启用之后其他移动配置才能生效。 |
| Kitchen Sink | 显示全部移动行为配置。 |
| 对话选择 | 编辑右键NPC后打开的对话。 |
| 可配置资源标识符 | 通过资源包的注册表加载模型。 |
| 玩家纹理 | 通过游戏名称使用玩家的皮肤。 |
| Configurable Party (可配置同行队伍) | 编辑NPC的同行宝可梦。 |
例如,为ai_test的对话填入'sacchi_interaction',资源标识符填入'cobblemon:sacchi'。就会拥有和生成出来的sacchi一样的功能。
NPC默认使用其文件名作为模型注册名(即资源标识符),如果注册名不存在,NPC就会和宝可梦一样显示为替身玩偶。注册方法见下文。
对话
对话目前只能通过数据包进行更改。可以使用命令打开对话。如:'/opendialogue example @s' 就是一只老鼠宝可梦(用铁剑)亲切地教导你Cobblemon的对话中具有什么样的功能。
当前版本里,默认有4个对话(实际是3个,sacchi_healed是sacchi_interaction调用的对话)。
在NPC中,ai_test和standard使用了默认的npc-example对话,右键他们可以选择进行对战、交换或取消。
由于npc-example中的对战是双打对战,玩家和NPC都需要有两只以上的宝可梦才可以对战;而交换选项的可选择性被固定为false,完全就是一个摆设。
以上,就是目前Cobblemon模组中自带的全部NPC和对话。
数据包 |
NPC文件的格式
NPC文件位于data/cobblemon/npcs内。在文件中出现过的项目如下。其中,除了"hitbox"、"names"和"presets"之外的项目都可以不写入。
JSON:
{
"hitbox": "player", // 碰撞箱
"names": [ ... ], // 名字
"presets": [ ... ], // 预设
"variation": {}, // 改变模型
"config": [], // 配置选项
"interaction": { ... }, // 交互事件
"canDespawn": false, // 可以不消失
"battleConfiguration": { "canChallenge": true }, // NPC可以被挑战
"autoHealParty": false, // 自动治疗同行队伍
"skill": 5,
"party": { ... }, // 默认同行队伍
"ai": [ ... ]
}
可以在interaction中直接指定NPC默认的交互事件。
JSON:
"interaction": {
"type": "dialogue",
"dialogue": "cobblemon:npc-example"
},
party具有两类写法。simple和'/npcedit'中的格式一致:
JSON:
"party": {
"type": "simple",
"pokemon": [
"spiritomb",
"porygonz"
]
}
JSON:
"party": {
"type": "pool",
"minPokemon": 4,
"maxPokemon": 6,
"isStatic": true,
"pool": [
{
"pokemon": "weedle",
"weight": 10,
"selectableTimes": 2,
"npcLevels": "1-8"
},
{
"pokemon": "caterpie",
"weight": 5,
"npcLevels": "1-8",
"selectableTimes": 2
}
]
}
注册模型
注册表位于资源包的assets\cobblemon\bedrock\npcs\variations文件夹内。将新的注册表扔进这个位置就可以读取了。
JSON:
{
"name": "cobblemon:npc", // 注册名
"order": 0,
"variations": [
{
"aspects": [], // 样子、通过对话中的角色调用
"poser": "cobblemon:standard", // 调用的姿势路径
"model": "cobblemon:trainer.geo", // 调用的模型路径
"texture": "cobblemon:textures/npcs/standard/trainer.png", // 调用的纹理路径
"layers": []
}
]
}
JSON:
"aspects": [ "model-default" ],
此外,Cobblemon的trainer模型和一般的steve、alxe模型不太一样,还额外包括了精灵球的模型。
Molang是什么
| 【?】Molang是一种基于表达式的类脚本语言,旨在使用简单的类脚本语言在较低层次的系统中不脱离数据驱动实现复杂的行为。 |
● 网易我的世界开发者文档
● 微软我的世界开发者文档(全英文)
● YSM文档
在下文里,你至少需要知道"query.function_name"是一个查询函数,用于访问实体属性;而"variable.variable_name"(变量)、"temp.variable_name"(暂时储存)和"context.variable_name"(上下文)是一些自定义变量,用于读写信息。
其缩写分别是:"q.function_name"、"v.variable_name"、"t.variable_name"和"c.variable_name"。
对话文件的格式
对话文件位于data\cobblemon\dialogues内。在文件中出现过的项目如下:
JSON:
{
"escapeAction": "q.dialogue.set_page('quitter')", // 按Esc退出时执行、可无
"initializationAction": "c.npc.set_chatting();", // 开始交互时执行的初始化内容、可无
"speakers": { ... }, // 对话角色
"pages": [ ... ] // 分页
}
JSON:
"initializationAction": [
"c.npc.set_chatting();",
"v.healer = c.npc.find_nearby_block('cobblemon:healing_machine');",
" ... "
],
speakers
JSON:
"speakers": {
"pikachu": { // 角色
"name": "Mouse Pokémon", // 名字
"face": {
"type": "artificial", // 对话角色类型
"modelType": "pokemon", // 模型类型
"identifier": "cobblemon:pikachu", // 资源标识符
"isLeftSide": false, // 是否在左侧
"aspects": [ // 使用的样子
"shiny"
]
}
},
"player": {
"face": "q.player.face(true);", // 调用玩家的脸。括号中的true表示是否在左边。NPC则是"q.npc.face();"。
"name": {
"type": "expression", // 使用Molang表达式的格式
"expression": "q.player.username" // 直接调用玩家的名字。一般使用"q.npc.name"调用NPC的名字。
},
"gibber": {} // 对话滚动出现,无则直接出现。
}
},
pages
JSON:
"pages": [
{
"id": "page1",
"speaker": "pikachu", // 这一页对话的对话角色
"lines": [
"Well you see, this is a dialogue.",
"You can have multiple pages, and each page can have multiple lines."
]
},
{
"id": "page2",
"speaker": "pikachu",
"clientActions": [ // 右键NPC时运行
"q.face.play_animation('win');"
],
"lines": [
"On an unrelated note, did you know that I'm deeply afraid of iron swords? Anyway, cya!"
],
"escapeAction": "q.dialogue.input('');", // 在当前页面按esc退出时执行
"textColor": "FFFFFF", // 文字颜色
"background": "cobblemon:textures/gui/dialogue/dialogue_box_info.png", // 聊天框底图
"input": { ... } // 输出
},
]
JSON:
"lines": [
{
"type": "expression",
"expression": "'Nice to meet you, ' + q.player.username + '! Welcome to the world of dialogues!'"
}
]
其中,"input"与"action"会在玩家点击当前页面或选项时执行表达式。
input
没有"input"时将会自动打开下一页对话。"input"可以用于直接输出Molang表达式:
JSON:
"input": "q.dialogue.set_page(v.landing_page);" // 打开页面,此处"v.landing_page"是个自定义变量。
JSON:
"input": [
"t.data = q.player.data();",
"t.data.scared_npc = true;",
"q.player.save_data();",
"q.dialogue.close();" // 关闭对话
]
"input"也可以用于输出更复杂的内容。
1.在当前对话产生选项:
JSON:
"input": {
"type": "option",
"vertical": false, // 选项排列是否垂直并列
"timeout": { // 倒数、可无
"delay": 6, // 秒
"action": " ... "
},
"options": [
{
"text": "Yes", // 显示的文本
"value": "yes", // 选项的值
"action": [ ... ]
},
{ ... },
{ ... },
{
"text": "Sword again!",
"value": "sword2",
"isVisible": "q.player.main_held_item.is_of('minecraft:iron_sword')",
"isSelectable": "q.player.data.scared_npc == true;",
"action": [
"v.player_response = 'How about you die... again! Muahahaha!';",
"v.next_page = 'sword-again';",
"q.dialogue.set_page('player-surrogate');"
]
}
]
}
"isSelectable"指是否可选,此处用于满足条件时可选,如果写入"false"则必定不可选。
上文使用了自定义变量,作为后文的输出值。这是个很好的例子:
JSON:
{
"id": "player-surrogate",
"speaker": "player",
"lines": [ { "type": "expression", "expression": "v.player_response;" } ],
"input": "q.dialogue.set_page(v.next_page);"
}
2.让当前对话自动继续:
JSON:
"input": {
"type": "auto-continue",
"delay": 3, // 秒
"allowSkip": false, // 允许跳过
"showTimer": false, // 显示进度条,可无,无则显示
"action": "q.dialogue.set_page('sword-again-reaction');"
}
3.在当前对话写入文本:
JSON:
"input": {
"type": "text",
"timeout": { // 倒数、可无
"delay": 10, // 秒
"action": "q.dialogue.set_page('too-slow');"
},
"action": [
"q.dialogue.set_page('name-speak');",
"t.data = q.player.data();" // 同步变量
]
}
值得注意的Molang写法
换页就懒得说了,上文有。在"input"或"action"写入这条可以令NPC与进行交互的玩家开始对战。
JSON:
"q.npc.start_battle(q.player, 'double');"
同理,这条是令NPC为玩家治疗宝可梦(需要治疗仪)。
JSON:
"c.npc.run_action_effect('npc_heal_player_pokemon');"
这条则是重新打开对话,'sacchi_healed'是对话文件名。
JSON:
"v.callback_dialogue = 'sacchi_healed';"
而这,是一行三元运算符嵌套。
JSON:
"lines": [ { "type": "expression",
"expression": "v.reaction == 'parry' ? 'You parry his attack and kill him.' : (v.reaction == 'stand still' ? 'He kills you.' : 'You drop your sword and he kills you.')"
}],
逻辑是条件A成立则输出A,否则输出嵌套;嵌套内条件B成立则输出B,否则输出C。
后话
其实这些内容不是很难,早在半年前就有人在B站发出成果演示视频来了。真正的难处在于... 我们一无所知。这几天,群里探讨NPC功能的过程几乎是绕了一大圈弯路,我们天真地以为在游戏里就是不能编辑NPC的同行队伍、皮肤模型、对话内容等等等等。甚至我们在连'/opendialogue'都不知道的情况下,一步一步单纯靠着看文件搞清楚了各个盲点。
尤其是默认NPC不能对战居然是因为默认是双打对战!这种曲折经历,真是令人哭笑不得。因此,为了避免大家重蹈我们的覆辙,或是说期望看到这里的后来者,能够带着我们的一份心继续做出更好的作品,故而我将这篇教程写在这里。
2026年1月12日 惯宣


