Tutorial zh_CN - MemeMayhem/ModExamples GitHub Wiki

English Version

这个教程教你如何一步步做出自己的超级键盘侠模组。

创建模组

每个超级键盘侠的模组都存在一个文件目录下面。这个目录包括了模组所有的数据、图像和脚本文件。要创建一个新模组,先找到本地模组的存储目录%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和一些可以进行的操作。点击"查看文件"可以打开模组目录。

Mod Manager UI

如果你的模组没有出现在模组管理界面,点击"打开本地模组目录"然后看看新建的模组是不是在打开的目录下面。改完模组目录结构后需要重启游戏来重新加载模组。当你的模组被成功加载后,后续你可以用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",之后我们就可以看见新创建的角色了。

First Mod Character

虽然名字变了,但是角色的样子与玩法与游戏本体角色并没有区别。下面我们会一步步完善这个角色。

更换角色形象

模组可以添加图片文件来修改角色形象。

打开模组目录,创建一个目录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.pngmy_character_large.png。注意在脚本文件中引用时不要加文件后缀。

Ctrl + F5重载模组后这个角色的形象就更新了:

New Character Look

如果开始战斗,你就会发现战斗界面的角色形象还是和原来的一样:

alt text

要修改战斗时的角色游戏,你还需要增加两个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)

改完再进战斗角色形象就更新了:

New Character Battle

添加表情子弹

超级键盘侠里表情就是子弹。接下来我们给新创的角色加一个自己特有的表情子弹。

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重载模组就可以看到角色拥有了新的子弹:

My Power Rock

但是战斗中发射的子弹还是原来的小石头。和角色一样,我们也需要修改additions/data/Unit.jsonadditions/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
    })

然后我们就能用大石头砸对手了:

Big Black Rock

我们还可以给子弹添加音效。加一个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

重开游戏后就能选我们新加的能力包了:

My Powerful Perk

能力包效果

除了增加角色的基本属性和添加表情子弹,能力包还可以通过脚本函数来实现各种特殊效果。

这里我们创建了一个新的能力包,并给予它一个特殊效果:

    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)

到这里我们的角色就基本完整了。

Overpowered Cookie

添加头目敌人

添加一个头目敌人和添加一个角色比较类似,只需要在脚本里添加:

    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重载模组后就可以和我们的自建头目用表情战斗了:

My Boss

添加选择事件

游戏中的随机选择事件也可以修改。往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_wavemax_wave(包含)是波次的范围,可以设置为nil,来表示没有上限或者下限

也可以自定义某些波次只会出现这个事件

Api:RegisterWaveSpecialChoices(choice_id, min_wave, max_wave, limit_characters:table<string>)

其中min_wavemax_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.pngmy_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创意工坊

做好模组之后就可以发布到Steam创意工坊和其他玩家分享了:

  1. 在游戏里按"Ctrl + F8"打开模组管理界面。
  2. 找到你要发布的模组,点击对应的"上传到Steam"按钮。
  3. 游戏会弹出一个填写更新说明的界面,可以不填。
  4. 点击更新说明窗口的"开始上传"按钮。
  5. 上传完成后Steam会生成一个唯一的标识ID,游戏会把这个ID存在manifest.json文件里。
  6. 上传之后要注意保存manifest.json文件,里面的标识ID在之后的更新都需要用到。
  7. manifest.json文件有标识ID后,模组管理界面会出现一个"在Steam查看"的按钮,点击可以在Steam中访问模组页面。
  8. 你可以在Steam模组页面给模组添加更多的图片。
  9. 最后你可以在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)

这个教程只包括了超级键盘侠模组的最基本用法。更多模组用法可以参考我们提供的模组样例

制作模组的一些常见问题在FAQ里有解答。如果你的问题在模组样例FAQ都找不到答案,欢迎向我们提问

⚠️ **GitHub.com Fallback** ⚠️