Tutorial zh_CN - MemeMayhem/ModExamples GitHub Wiki
这个教程教你如何一步步做出自己的超级键盘侠模组。
每个超级键盘侠的模组都存在一个文件目录下面。这个目录包括了模组所有的数据、图像和脚本文件。要创建一个新模组,先找到本地模组的存储目录%USERPROFILE%\AppData\LocalLow\Cr3 Studio\Meme Mayhem\Mods
(如果你用的是Linux系统,比如Steam Deck,路径可以参考这个issue),然后在下面创建一个新文件目录。如果Mods
目录不存在,可以手动创建。
这个教程里我们的新模组就叫First Mod
,所以我们给新创建的文件目录也叫这个名字。创建好目录之后在再添加一个manifest.json
文本文件,内容如下:
{
"name": "First Mod",
"description": "This is our first Meme Mayhem Mod."
}
给你的模组做一张封面图,存放在模组目录下,文件名是thumbnail.png
。模组上传到Steam创意工坊之后,这张将成为你的模组在Steam页面上的封面图。
创建好这两个文件之后目录结构如下:
Meme Mayhem\Mods
+ First Mod
+ manifest.json
+ thumbnail.png
这样这个模组就可以在游戏里加载了。现在这个模组还没有任何功能,在接下来的步骤里我们会一一加上。
重启游戏后按Ctrl + F8
打开模组管理界面。如果模组有被加载,界面上会有一行显示First Mod
和一些可以进行的操作。点击"查看文件"可以打开模组目录。
如果你的模组没有出现在模组管理界面,点击"打开本地模组目录"然后看看新建的模组是不是在打开的目录下面。改完模组目录结构后需要重启游戏来重新加载模组。当你的模组被成功加载后,后续你可以用Ctrl + F5
来重载模组。这样做比重启游戏要快,但是只有在模组已经被成功加载后才会生效。
模组通过脚本来实现功能。超级键盘侠使用Lua脚本语言,模组的全部功能也都需要用Lua脚本来实现。
打开模组目录,创建一个路径是additions\scripts\Trigger.lua.txt
的文本文件:
MOD_MANAGER:AddMod(function(Api)
Api:CopyCharacter("naysayer", {
id = "my_character",
display_name = "第一个Mod角色",
short_description = "这是我第一个mod创建的角色。",
})
end)
上面的脚本调用MOD_MANAGER:AddMod
来注册模组,然后在创建了一个新的角色。这个新角色从游戏本体的"naysayer"拷贝生成,这里我们只修改了角色的名字。
按Ctrl + F5
重载模组,然后点击"玩Mod",之后我们就可以看见新创建的角色了。
虽然名字变了,但是角色的样子与玩法与游戏本体角色并没有区别。下面我们会一步步完善这个角色。
模组可以添加图片文件来修改角色形象。
打开模组目录,创建一个目录additions\textures
,然后把角色要用的图片放进去。图片名最好只包含英文小写字母和下划线。文件类型支持PNG和JPG。
放好图片后更新一下模组脚本,添加下面的内容:
MOD_MANAGER:AddMod(function(Api)
Api:CopyCharacter("naysayer", {
id = "my_character",
display_name = "第一个Mod角色",
short_description = "这是我第一个mod创建的角色。",
+ icon = DCEI.Texture("my_character_small"),
+ icon_low_resolution = DCEI.Texture("my_character_small"),
+ icon_high_resolution = DCEI.Texture("my_character_large"),
})
end)
新增的两个图片文件是my_character_small.png
和my_character_large.png
。注意在脚本文件中引用时不要加文件后缀。
按Ctrl + F5
重载模组后这个角色的形象就更新了:
如果开始战斗,你就会发现战斗界面的角色形象还是和原来的一样:
要修改战斗时的角色游戏,你还需要增加两个JSON文件:
additions\data\Unit.json
内容如下:
{
"units": {
"COMBAT Unit First Mod Character":
{
"baseUnit": "_COMBAT Missile"
}
}
}
additions\data\Actor.json
内容如下:
{
"actors": {
"COMBAT Unit First Mod Character" : {
"parent": "_COMBAT Unit Mob",
"unitActor": {
"resource": {
"name": "my_character_large"
},
"modelScale": 0.5
}
}
}
}
然后修改模组脚本:
MOD_MANAGER:AddMod(function(Api)
Api:CopyCharacter("naysayer", {
id = "my_character",
display_name = "第一个Mod角色",
short_description = "这是我第一个mod创建的角色。",
icon = DCEI.Texture("my_character_small"),
icon_low_resolution = DCEI.Texture("my_character_small"),
icon_high_resolution = DCEI.Texture("my_character_large"),
+ unit = DCEI.Unit("COMBAT Unit First Mod Character"),
})
end)
改完再进战斗角色形象就更新了:
超级键盘侠里表情就是子弹。接下来我们给新创的角色加一个自己特有的表情子弹。
往additions\textures
添加想要用的表情图片my_power_rock.png
,然后在模组脚本里注册新表情:
MOD_MANAGER:AddMod(function(Api)
+ Api:RegisterMissile("my_power_rock", {
+ id = "my_power_rock",
+ display_name = "我的强力石头",
+ icon = DCEI.Texture("my_power_rock"),
+ damage = function(attack_data, caster)
+ return 200
+ end
+ })
Api:CopyCharacter("naysayer", {
id = "my_character",
display_name = "第一个Mod角色",
short_description = "这是我第一个mod创建的角色。",
icon = DCEI.Texture("my_character_small"),
icon_low_resolution = DCEI.Texture("my_character_small"),
icon_high_resolution = DCEI.Texture("my_character_large"),
unit = DCEI.Unit("COMBAT Unit First Mod Character"),
+ attack_ids = {
+ "my_power_rock",
+ "my_power_rock",
+ },
})
end)
上面的脚本新加了一个叫我的强力石头
的表情子弹,这个子弹拥有超高的伤害。游戏开始时角色的弹夹里我们放了两个这样的表情子弹。游戏里所有自带表情子弹参看:Missiles
按Ctrl + F5
重载模组就可以看到角色拥有了新的子弹:
但是战斗中发射的子弹还是原来的小石头。和角色一样,我们也需要修改additions/data/Unit.json
和additions/data/Actor.json
来修改子弹在战斗中的形象。
additions/data/Unit.json
:
{
"units": {
+ "COMBAT Missile My Powerful Rock":
+ {
+ "baseUnit": "_COMBAT Missile"
+ },
"COMBAT Unit First Mod Character":
{
"baseUnit": "_COMBAT Missile"
}
}
}
additions/data/Actor.json
:
"actors": {
+ "COMBAT Missile My Powerful Rock": {
+ "parent": "_COMBAT Missile Simple",
+ "unitActor": {
+ "resource": {
+ "name": "my_power_rock"
+ },
+ "modelScale": 2
+ }
+ },
"COMBAT Unit First Mod Character" : {
改完还是需要在脚本里引用:
Api:RegisterMissile("my_power_rock", {
id = "my_power_rock",
display_name = "我的强力石头",
icon = DCEI.Texture("my_power_rock"),
+ missile = DCEI.SimpleUnit("COMBAT Missile My Powerful Rock"),
damage = function(attack_data, caster)
return 100
end
})
然后我们就能用大石头砸对手了:
我们还可以给子弹添加音效。加一个my_power_rock_hit.wav
文件到additions\sounds
然后在脚本里引用就行:
Api:RegisterMissile("my_power_rock", {
id = "my_power_rock",
display_name = "我的强力石头",
icon = DCEI.Texture("my_power_rock"),
missile = DCEI.SimpleUnit("COMBAT Missile My Powerful Rock"),
+ sounds = {
+ DCEI.Sound("my_power_rock_hit")
+ },
damage = function(attack_data, caster)
return 200
end
})
新的子弹虽然伤害高,但是数量固定也不受其他属性加成。要想在游戏里走得更远,还需要加能力包和抗压神器和子弹配合。
超级键盘侠里每打败一个敌人就可以从3个随机选项里选一个能力包。如果我们想在游戏过程中获取更多的"My Powerful Rock",需要添加一个相应的能力包。
在模组脚本里添加新的能力包:
+ Api:RegisterPerk("my_power_perk", {
+ id = "my_power_perk",
+ display_name = "强力能力",
+ description = "获得一块强力石头",
+ icon = DCEI.Texture("my_power_rock"),
+ perk_type = "missile",
+ rarity = "legendary",
+ attacks = {
+ my_power_rock = 1,
+ },
+ })
Api:CopyCharacter("naysayer", {
id = "my_character",
display_name = "第一个Mod角色",
short_description = "这是我第一个mod创建的角色。",
icon = DCEI.Texture("my_character_small"),
icon_low_resolution = DCEI.Texture("my_character_small"),
icon_high_resolution = DCEI.Texture("my_character_large"),
unit = DCEI.Unit("COMBAT Unit First Mod Character"),
attack_ids = {
"my_power_rock",
"my_power_rock",
},
+ perk_chains = {
+ {
+ "my_power_perk",
+ },
+ {
+ "gain_attack",
+ "attack_quest",
+ "damage_to_health",
+ "flex_on_flex",
+ },
+ },
上面除了新加的能力包,我们还添加了一些游戏本身就有的。一个角色至少要有4个可选能力包,如果少于游戏里同时展示的数目,可能会导致游戏出错。一般一个角色会有很多可选能力包。这里我们为了简单只加了这几个。所有游戏本体的能力包都可以在这里使用,完整列表参见:Perks。
重开游戏后就能选我们新加的能力包了:
除了增加角色的基本属性和添加表情子弹,能力包还可以通过脚本函数来实现各种特殊效果。
这里我们创建了一个新的能力包,并给予它一个特殊效果:
Api:RegisterPerk("small_rock_on_power_rock", {
id = "small_rock_on_power_rock",
display_name = "石 中 石",
description = "当我的强力石头击中目标时,投掷一块普通石头",
icon = DCEI.Texture("my_power_rock"),
perk_type = "perk",
rarity = "common",
}, function(combat_unit)
local name = "small_rock_on_power_rock"
combat_unit.Attack:RegisterOnMissileImpactCallback(name, function(level, attack_data, caster, target)
if attack_data.missile_id == "my_power_rock" then
for i = 1, level do
combat_unit.Attack:NewMissileAttack(target, "attack_rock")
end
end
end)
end)
这个新的能力包注册了一个子弹命中的回调函数,每当我们新创建的子弹命中敌人时,会发射一个普通的石头。
把这个能力包添加到角色的perk_chains
里就可以在游戏中选择这个能力包了:
perk_chains = {
{
"my_power_perk",
+ "small_rock_on_power_rock",
},
{
"gain_attack",
"attack_quest",
"damage_to_health",
"flex_on_flex",
},
},
抗压神器和能力包非常类似,主要不同在于获取方式不一样。抗压神器一般是角色自带或者打败精英敌人和获取。
下面的脚本创建了一个新的抗压神器,然后加给了我们的新角色:
+ Api:RegisterRelic("my_power_cookie", {
+ id = "my_power_cookie",
+ display_name = "超能饼干",
+ description = "受到任何表情符号攻击时,获得1" .. Api.GameMechanicTags.TAG.attack .. "。攻击速度减半。",
+ icon = DCEI.Texture("smh_cookie"),
+ rarity = "permanent",
+ modify_attributes = {
+ attack_speed = -0.5,
+ },
+ }, function(combat_unit)
+ local name = "my_power_cookie"
+ combat_unit.Attack:RegisterOnMissileHitCallback(name, function(level, attack_data, caster, target)
+ for i = 1, level do
+ combat_unit:ModifyAttribute("attack", 1, false)
+ end
+ end)
+ end)
Api:CopyCharacter("naysayer", {
id = "my_character",
display_name = "第一个Mod角色",
short_description = "这是我第一个mod创建的角色。",
icon = DCEI.Texture("my_character_small"),
icon_low_resolution = DCEI.Texture("my_character_small"),
icon_high_resolution = DCEI.Texture("my_character_large"),
unit = DCEI.Unit("COMBAT Unit First Mod Character"),
+ relics = {
+ "my_power_cookie",
+ },
拥有这个新神器的角色每次受到攻击都会增加1点力量(打败当前敌人后增量清零)。
游戏内置神器列表可以参见:Relics。
如果需要将新的神器加入商店和boss的掉落列表,可以在最后加一个参数设置为true
如 Api:RegisterRelic(name, config, callback, true)
到这里我们的角色就基本完整了。
添加一个头目敌人和添加一个角色比较类似,只需要在脚本里添加:
Api:RegisterBoss({
id = "my_boss",
display_name = "我的Boss",
unit = DCEI.Unit("COMBAT Unit First Mod Character"),
icon = DCEI.Texture("my_character_small"),
icon_high_resolution = DCEI.Texture("my_character_large"),
cosmetic_data = {
ultimate_id = "ultimate_basketball",
},
attributes = {
health_maximum = 10000,
attack = 40
},
perks = {
attack_rock = 30,
attack_speed = 2,
},
lines = {
battle_start = {
{
"嘿,准备被碾压吧!",
"你不是我的对手!",
}
},
on_start = {
{
"让战斗开始吧!",
"时候展示我的实力了!",
}
},
on_ultimate = {
{
"见证我的终极力量!",
"感受我的终极攻击之怒!",
}
},
on_win = {
{
"哈哈,胜利是我的!",
"你从未有过胜算!",
}
},
on_lose = {
{
"下次我会报仇的!",
"这只是暂时的挫折!",
}
},
}
})
除了一些角色也有的基本属性,敌人还有一些垃圾话可以配置。这里我们为了简单直接复用了和新建角色一样的图片素材。如果要让这个敌人长得不一样,参考角色形象的部分修改就可以了。
注册之后这个敌人并不会出现,我们还需要修改关卡的头目设置:
Api:SetCampaignBossPool("my_character", {
"my_boss",
"miniboss_pop_kat",
"miniboss_kideo",
"miniboss_stone",
"miniboss_zuck",
"my_boss",
})
注意这里必须指定6个头目,不能多也不能少。 中间4个我们复用了现有的,第一个和最后一个是我们新加的。所有游戏内置头目参见:Bosses。
按Ctrl + F5
重载模组后就可以和我们的自建头目用表情战斗了:
游戏中的随机选择事件也可以修改。往additions\textures
添加想要用的表情图片my_choice_dog.png
,然后用下面的代码添加一个新的随机事件:
Api:RegisterChoice({
id = "my_choice",
display_name = "狗",
image = DCEI.Texture("my_choice_dog"),
description = "一只狗向你奔来,看起来要咬你。你会怎么做?",
options = {
{
flavor = "逃跑",
description = "+200 " .. Api.GameMechanicTags.TAG.health,
aftermath_narrative = "你逃离了狗!",
aftermath_description = "你多年来从未跑得这么快,感觉很棒。+200 " .. Api.GameMechanicTags.TAG.health,
modify_attributes = {
health_maximum = 200,
},
},
{
flavor = "扔石头",
description = "+1 " .. Api.GameMechanicTags.ATTACK_ICON.rock .. ", -100 " .. Api.GameMechanicTags.TAG.health,
aftermath_narrative = "你捡起了一块石头,但狗在你扔之前咬了你。",
aftermath_description = "很疼,但你觉得这个" .. Api.GameMechanicTags.ATTACK_ICON.rock .. "还挺趁手。",
modify_attributes = {
health_maximum = -100,
},
gain_relics = {
single_rock = 1,
},
}
}
})
随机事件可以修改角色属性,也可以给于角色抗压神器,也可以同时都有。
添加的事件会加到事件随机池里,这里为了方便展示我们直接修改随机池:
Api:SetChoicePool("my_character", {
"my_choice",
})
按Ctrl + F5
重载模组再打到某些关卡就可以触发这个自定的事件了。
游戏内置事情完整列表参见:Choices。
进阶
可以自定义某些事件只会在某些波次之间触发
Api:RegisterWaveRequiredChoices(choice_id, min_wave, max_wave)
其中min_wave
和max_wave
(包含)是波次的范围,可以设置为nil,来表示没有上限或者下限
也可以自定义某些波次只会出现这个事件
Api:RegisterWaveSpecialChoices(choice_id, min_wave, max_wave, limit_characters:table<string>)
其中min_wave
和max_wave
(包含)是波次的范围,不能为nil,如果两个相等则只会在这一波次出现,否则范围波次内都只会出现这个事件
limit_characters
是一个角色id的列表,只有这些角色会触发这个事件,如果为空则所有角色都会触发(不建议这么做,因为会影响其他mod)
如果在该波次发现了多个满足条件的RegisterWaveSpecialChoices
,则会从这些事件中随机选择一个
详情请参考模组样例中的Advanced-Mod1
事件的选项支持某些限制条件
可以在options
的entry中添加requirement
字段
目前支持的限制条件有:
options = {
...
{
...
-- 限制需要有该神器,只能限制一个
requirement = {
relic = "trolley_switch",
},
},
{
...
-- 限制需要有该能力,只能限制一个
requirement = {
perk = "gain_attack",
},
},
{
...
-- 限制需要有该稀有度的神器,只能限制一个稀有度
requirement = {
relic_rarity = "epic",
},
},
{
...
-- 限制需要有该属性,可以限制多个,多个必须同时满足才可解锁
requirement = {
attributes = {
attack = 30,
},
},
},
{
...
-- 限制需要选择过这些事件的这些选项才可以解锁
requirement = {
choice_selected = {
["my_choice_id"] = { 1 },
},
},
},
}
模组可以添加更多图片文件来在战斗中修改角色形象(比如小祥的变脸,塔菲的切换)
打开模组目录additions\textures
,然后把角色要用的其他图片放进去。图片名最好只包含英文小写字母和下划线。文件类型支持PNG和JPG。
新增的两个图片文件是my_character_angry.png
和my_character_cry.png
。注意在脚本文件中引用时不要加文件后缀。
然后要修改战斗时的角色,你还需要修改之前的Actor JSON文件:
additions\data\Actor.json
内容如下:
{
"actors": {
"COMBAT Unit First Mod Character" : {
"parent": "_COMBAT Unit Mob",
"unitActor": {
"resource": {
"name": "my_character_large"
},
"events": [
{
"actorTerm": {
"onCustomEvent": {
"identifier": "Transfer_Angry"
}
},
"actions": [
{
"setModel": {
"type": "Sprite",
"name": "my_character_angry"
}
}
]
},
{
"actorTerm": {
"onCustomEvent": {
"identifier": "Transfer_Cry"
}
},
"actions": [
{
"setModel": {
"type": "Sprite",
"name": "my_character_cry"
}
}
]
},
],
"modelScale": 0.5
}
}
}
}
然后修改模组脚本,在你想改变形象时调用:
-- event_name 就是刚刚在actor里定义的"Transfer_Angry"或者"Transfer_Cry"
-- 这里的unit是真正的unit实体,所以如果你用的caster引用那就需要用caster.unit
Api:SendActorEvent(unit, event_name)
现在我们的模组所有的功能都有了,玩起来像这样:
first_mod.mp4
如果你在某一步需要问题,可以下载我们做好的完整模组来帮助检查错误在什么地方。
做好模组之后就可以发布到Steam创意工坊和其他玩家分享了:
- 在游戏里按"Ctrl + F8"打开模组管理界面。
- 找到你要发布的模组,点击对应的"上传到
"按钮。
- 游戏会弹出一个填写更新说明的界面,可以不填。
- 点击更新说明窗口的"开始上传"按钮。
- 上传完成后Steam会生成一个唯一的标识ID,游戏会把这个ID存在
manifest.json
文件里。 - 上传之后要注意保存
manifest.json
文件,里面的标识ID在之后的更新都需要用到。 - 当
manifest.json
文件有标识ID后,模组管理界面会出现一个"在查看"的按钮,点击可以在Steam中访问模组页面。
- 你可以在Steam模组页面给模组添加更多的图片。
- 最后你可以在Steam模组页面把模组的可见性设置为公开,这样其他人就可以使用这个模组了。
我们还提供了一些API用来获取游戏中使用的数据,以便模组开发者使用游戏本体的对象。以下这些API会打印数据到日志,日志存储路径为模组的根目录(Ctrl + F8, 然后点击打开本地模组目录
)。
MOD_MANAGER:AddMod(function(Api)
Api:PrintCharacters()
Api:PrintAttributes()
Api:PrintMissiles()
Api:PrintPerks()
Api:PrintRelics()
Api:PrintChoices()
Api:PrintBosses()
end)
这个教程只包括了超级键盘侠模组的最基本用法。更多模组用法可以参考我们提供的模组样例。