文档和工具

这篇教程将会详细讲述如何创造自己的第一个Mod,针对的是没有Mod开发基础的同学。

有两种方式来创造第一个Mod:第一种是通过MC Studio的模板功能快速构建Mod,适合边做边学;第二种方式则是手动创建Mod的每个文件,这种方式能够加深我们对Mod的基础理解,不过相应的花费的时间比较长。下面我们两种方式都会介绍,建议先用第一种方式建立对Mod的基础概念,碰到不懂的地方再从第二种方式中寻找答案。

一、通过MC Studio快速构建Mod

下面将按步骤介绍:下载和安装MC Studio,选择Mod模板,运行Mod,查看和修改Mod文件。

(一)下载和安装MC Studio

从我的世界开发工具网站(http://mc.163.com/mcstudio/index.html)下载MC Studio.

img

安装后,使用网易通行证账号登录,然后在“发布”页面注册成为我的世界中国版开发者,就可以使用MC Studio的所有功能了。

img

(二)选择Mod模板

在“新建作品”分页,将鼠标移至“入门脚本模板”,点击浮现的按钮“新建”,就会复制一个该模板,复制后的Mod显示在“C++作品”的AddOn分页中。

img

(三)运行Mod

我们先把由模板复制而来的Mod改个名字。在“C++作品”的AddOn分页上,鼠标移动到我们刚刚复制的入门脚本模板,依次点击“更多”、“配置”。

img

img

在配置界面,将名字修改为“第一个Mod”。

img

将鼠标移到“第一个Mod”上,点击“开发测试”,在弹出的配置界面中直接点击“开始”。

img

然后MC Studio会自动打开“Mod PC开发包”,并运行指定的Mod(在这里也就是“第一个Mod”)。Mod PC开发包是专门用于开发Mod的我的世界客户端。如果是第一次运行,会先下载Mod PC开发包,需等待一段时间。

进入Mod PC开发包后,我们可以试一下Mod的效果,在聊天栏中输入“钻石剑”并回车,就会发现在背包里多了一把钻石剑。这就是这个Mod所实现的效果。

img

(四)查看和修改Mod文件

现在我们去看看Mod下有哪些文件。在“第一个Mod”上依次点击“更多”、“打开目录”,就能看到这个Mod对应的文件。

img

“f1c382210c344b9e9d6c16c9ad53d8b7”这个文件夹就是“第一个Mod”的内容,里面的“tutorialBehaviorPack_eb1977ff”是行为包,“tutorialResourcePack_b4860ae1”是资源包。

img

实现根据聊天信息添加物品的逻辑就在行为包tutorialScripts中的py文件里。

img

在tutorialServerSystem.py文件里,我们可以找到实际起作用的逻辑代码。

img

示例代码中有详细的注释,可以在其基础上做一些微小的调整,然后进行“开发测试”看看效果如何。相关的接口可以在http://mc.163.com/mcstudio/modapi/2-2-1.html查询。如果想了解代码文件的组织规范,可以查看第二种构建方式中代码部分的介绍。

二、手动构建Mod

上面介绍了第一种创建Mod的方式,下面介绍第二种方式,将按这个步骤进行:如何构建Mod的文件夹目录,如何填写配置文件,如何创建Python脚本,如何将资源导入到游戏中进行测试。

(一)如何构建文件夹目录

1.首先需要创建一个目录作为Mod根目录,取名尽量个性化,可以使用英文或拼音,但是不能使用中文。这里我们将其命名为tutorialMod:

img

2.在tutorialMod文件夹中创建behavior和resource文件夹

img

3.在开发Mod的过程中可能会有修改文件后缀名,查看隐藏文件的需求。设置方式如下:

img

4.在tutorialBehaviorPack文件夹中创建entities目录,该目录必须存在,不过文件夹内容可以为空;创建用来标识该Pack的文件manifest.json(先创建manifest.txt,再修改文件名为manifest.json)。

img

5.在tutorialResourcePack文件夹中创建textures文件夹用来存放图片,创建ui文件夹用来存放UI配置文件,创建manifest.json用来标识该Pack。

img

(二)如何填写配置文件

配置文件是指上文中出现的manifest.json。

1.UUID是用于识别包体唯一性的标识符,用于系统区分开我们和别人的资源包、行为包。获取方式有2种: 一是使用网站https://www.uuidgenerator.net/来获取,直接复制下来使用,每次刷新可以获得新的UUID;二是使用python内置的模块uuid来获取,分布如下图所示:

img

img

2.Behavior中的manifest.json,其参数含义参考《Mod入门简介》文档。

					{
						"format_version": 1,
						"header": {
								"description": "This is a tutorial mod behavior_pack by @Hugo",
								"name": "TutorialMod",
								"uuid": "56adf3c7-f4a5-4fe5-bceb-77aa1d6e2253",
								"version": [0, 0, 1]
						},
						"modules": [
								{
										"description": "This is a tutorial mod behavior_pack by @Hugo",
										"type": "data",
										"uuid": "a7f0c804-1004-434e-b999-2109b005ff6e",
										"version": [0, 0, 1]
								}
						]
					}
          

3.Resource中的manifest.json

					{
							"format_version": 1,
							"header": {
									"description": "This is a tutorial mod resource_pack by @Hugo",
									"name": "TutorialMod",
									"uuid": "a47597f7-a547-4d44-8557-88941352a703",
									"version": [0, 0, 1]
							},
							"modules": [
									{
											"description": "This is a tutorial mod resource_pack by @Hugo",
											"type": "resources",
											"uuid": "e0f32853-fa36-4984-af4a-c826d299a8e2",
											"version": [0, 0, 1]
									}
							]
					}
					

(三)创建脚本目录和代码

恭喜你,完成了上面的步骤之后,我们的Mod已经是一个可以被加载的Mod了,但是这个Mod是一个空壳没有逻辑,在创建我们的逻辑部分之前,我们先创建脚本目录。

1.创建脚本目录tutorialScripts放在Behavior目录下,并在tutorialScripts目录下创建两个空白py文件,一个为__init__.py,一个为modMain.py。__init__.py是为了让python将tutorialScripts这个目录当成一个可以被import的module,modMain.py则是我们Mod的入口文件,里面包含入口函数和我们要执行的一些初始化操作。

img

四、书写我们的功能代码

1.modMain.py这个文件作为引擎加载的入口文件,里面涉及到引擎SDK的函数可以在API文档中查找详细的解释。这里补充说明我们为什么需要system。system是引擎封装好的系统,可以在system中调用封装好的注册监听事件Event的方法和创建并更新Componet组件,以完成一个个特定的功能任务。(system可以理解为一个工厂,Component可以理解成一个个工具)

2.这里我们需要创建2个py文件,分别代表我们的客户端和服务端系统system,tutorialServerSystem.py和tutorialClientSystem.py.

img

3.我们将tutorialScripts文件夹拖到Sublime编辑器中,熟悉PyCharm的建议使用PyCharm进行开发。

img

4.打开modMain.py,开始编写Mod代码

为什么要分客户端和服务端呢?可以理解为服务端的职责是负责整个游戏的流程和逻辑运行并同步数据给客户端,客户端的职责负责传递输入数据(例如点击界面按钮等)和表现服务端传来的数据(UI、特效等)

modMain.py

                    # -*- coding: utf-8 -*-
                    # 上面这行是让这个文件按utf-8进行编码,这样就可以在注释中写中文了
                    
                    # 这行是import到MOD的绑定类Mod,用于绑定类和函数
                    from common.mod import Mod
                    # 这行import到的是引擎服务端的API模块
                    import server.extraServerApi as serverApi
                    # 这行import到的是引擎客户端的API模块
                    import client.extraClientApi as clientApi
                    
                    # 用Mod.Binding来绑定MOD的类,引擎从而能够识别这个类是MOD的入口类
                    @Mod.Binding(name = "TutorialMod", version = "0.0.1")
                    class TutorialMod(object):
                    
                        # 类的初始化函数
                        def __init__(self):
                            print "===== init tutorial mod ====="
                    
                        # InitServer绑定的函数作为服务端脚本初始化的入口函数,通常是用来注册服务端系统system和组件component
                        @Mod.InitServer()
                        def TutorialServerInit(self):
                            print "===== init tutorial server ====="
                            # 函数可以将System注册到服务端引擎中,实例的创建和销毁交给引擎处理。第一个参数是MOD名称,第二个是System名称,第三个是自定义MOD System类的路径
                            # 取名名称尽量个性化,不能与其他人的MOD冲突,可以使用英文、拼音、下划线这三种。
                            serverApi.RegisterSystem("TutorialMod", "TutorialServerSystem", "tutorialScripts.tutorialServerSystem.TutorialServerSystem")
                    
                        # DestroyServer绑定的函数作为服务端脚本退出的时候执行的析构函数,通常用来反注册一些内容,可为空
                        @Mod.DestroyServer()
                        def TutorialServerDestroy(self):
                            print "===== destroy tutorial server ====="
                        
                        # InitClient绑定的函数作为客户端脚本初始化的入口函数,通常用来注册客户端系统system和组件component
                        @Mod.InitClient()
                        def TutorialClientInit(self):
                            print "===== init hugo fps client ====="
                            # 函数可以将System注册到客户端引擎中,实例的创建和销毁交给引擎处理。第一个参数是MOD名称,第二个是System名称,第三个是自定义MOD System类的路径
                            # 取名名称尽量个性化,不能与其他人的MOD冲突,可以使用英文、拼音、下划线这三种。
                            clientApi.RegisterSystem("TutorialMod", "TutorialClientSystem", "tutorialScripts.tutorialClientSystem.TutorialClientSystem")
                        
                        # DestroyClient绑定的函数作为客户端脚本退出的时候执行的析构函数,通常用来反注册一些内容,可为空
                        @Mod.DestroyClient()
                        def TutorialClientDestroy(self):
                            print "===== destroy hugo fps client ====="
                    

5.打开tutorialServerSystem.py开始编写代码,代码实现的功能:当玩家在聊天界面输入“钻石xx”时,给予相应的物品给输入信息的玩家。

5.1 这里的核心我们用到了ServerChatEvent这个事件,它回调的参数有哪些呢,我们参考一下文档《Mod SDK文档》,发现这个事件的返回参数有3个:

img

5.2 我们在MC的中文wiki上https://minecraft-zh.gamepedia.com/%E5%89%91查找到相应物品的Id名称是什么,例如钻石剑Id是 minecraft:diamond_sword

img

5.3我们需要创建物品的API是什么呢,参考《Mod SDK文档》

img

img

tutorialServerSystem.py

                    # -*- coding: utf-8 -*-

                    # 获取引擎服务端API的模块
                    import server.extraServerApi as serverApi
                    # 获取引擎服务端System的基类,System都要继承于ServerSystem来调用相关函数
                    ServerSystem = serverApi.GetServerSystemCls()
                    # 获取引擎模块game
                    import common.game as game
                    
                    # 在modMain中注册的Server System类
                    class TutorialServerSystem(ServerSystem):
                    
                        # ServerSystem的初始化函数
                        def __init__(self, namespace, systemName):
                            # 首先调用父类的初始化函数
                            super(TutorialServerSystem, self).__init__(namespace, systemName)
                            print "===== TutorialServerSystem init ====="
                            # 初始时调用监听函数监听事件
                            self.ListenEvent()
                    
                        # 监听函数,用于定义和监听函数。函数名称除了强调的其他都是自取的,这个函数也是。
                        def ListenEvent(self):
                            # 在自定义的ServerSystem中监听引擎的事件ServerChatEvent,回调函数为OnServerChat
                            self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "ServerChatEvent", self, self.OnServerChat)
                    
                        # 反监听函数,用于反监听事件,在代码中有创建注册就对应了销毁反注册是一个好的编程习惯,不要依赖引擎来做这些事。
                        def UnListenEvent(self):
                            self.UnListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "ServerChatEvent", self, self.OnServerChat)
                    
                        # 监听ServerChatEvent的回调函数
                        def OnServerChat(self, args):
                            print "==== OnServerChat ==== ", args
                            # 生成掉落物品
                            # 当我们输入的信息等于右边这个值时,创建相应的物品
                            if args["message"] == "钻石剑":
                                # 创建Component,用来完成特定的功能,这里是为了创建Item物品
                                comp = game.GetServer().CreateComponent(serverApi.GetLevelId(), "Minecraft", "item")
                                # 给这个Component赋值,参数参考《MODSDK文档》
                                comp.addItems = [("minecraft:diamond_sword", 1, 0, True, {"to": "inventory", "playerId": args["playerId"]})]
                                # 这一步很重要,它告诉系统需要更新这个Component,继而完成响应的功能
                                self.NeedsUpdate(comp)
                            elif args["message"] == "钻石镐":
                                comp = game.GetServer().CreateComponent(serverApi.GetLevelId(), "Minecraft", "item")
                                comp.addItems = [("minecraft:diamond_pickaxe", 1, 0, True, {"to": "inventory", "playerId": args["playerId"]})]
                                self.NeedsUpdate(comp)
                            elif args["message"] == "钻石头盔":
                                comp = game.GetServer().CreateComponent(serverApi.GetLevelId(), "Minecraft", "item")
                                comp.addItems = [("minecraft:diamond_helmet", 1, 0, True, {"to": "inventory", "playerId": args["playerId"]})]
                                self.NeedsUpdate(comp)
                            elif args["message"] == "钻石胸甲":
                                comp = game.GetServer().CreateComponent(serverApi.GetLevelId(), "Minecraft", "item")
                                comp.addItems = [("minecraft:diamond_chestplate", 1, 0, True, {"to": "inventory", "playerId": args["playerId"]})]
                                self.NeedsUpdate(comp)
                            elif args["message"] == "钻石护腿":
                                comp = game.GetServer().CreateComponent(serverApi.GetLevelId(), "Minecraft", "item")
                                comp.addItems = [("minecraft:diamond_leggings", 1, 0, True, {"to": "inventory", "playerId": args["playerId"]})]
                                self.NeedsUpdate(comp)
                            elif args["message"] == "钻石靴子":
                                comp = game.GetServer().CreateComponent(serverApi.GetLevelId(), "Minecraft", "item")
                                comp.addItems = [("minecraft:diamond_boots", 1, 0, True, {"to": "inventory", "playerId": args["playerId"]})]
                                self.NeedsUpdate(comp)
                            else:
                                print "==== Sorry man ===="
                    
                        # 函数名为Destroy才会被调用,在这个System被引擎回收的时候会调这个函数来销毁一些内容
                        def Destroy(self):
                            print "===== TutorialServerSystem Destroy ====="
                            # 调用上面的反监听函数来销毁
                            self.UnListenEvent()
                    

tutorialClientSystem.py

                    # -*- coding: utf-8 -*-

                    # 获取客户端引擎API模块
                    import client.extraClientApi as clientApi
                    # 获取客户端system的基类ClientSystem
                    ClientSystem = clientApi.GetClientSystemCls()
                    
                    # 在modMain中注册的Client System类
                    class TutorialClientSystem(ClientSystem):
                    
                        # 客户端System的初始化函数
                        def __init__(self, namespace, systemName):
                            # 首先初始化TutorialClientSystem的基类ClientSystem
                            super(TutorialClientSystem, self).__init__(namespace, systemName)
                            print "==== TutorialClientSystem Init ===="
                    
                        # 函数名为Destroy才会被调用,在这个System被引擎回收的时候会调这个函数来销毁一些内容
                        def Destroy(self):
                            pass
                    

五、加载到游戏中测试

有了上面的代码之后,我们的Mod基本已经完成了,后续在上传到开发者平台之前,我们需要把Mod加载到游戏中进行自测。

1.创建addon.json,放在Minecraft.Windows.exe同一目录下

img

addon.json

“install”中的路径是我们第一步创建Mod的根目录

“use”中的[]内容"IoXvXNuEAAA="是哪儿来的呢?是我们在开发包中创建的一个地图(运行Minecraft.Windows.exe,进入游戏,创建地图),"IoXvXNuEAAA="是这个地图文件夹的名字。"F:/MOD/tutorialMod"是我们要加载的Mod路径。

img

                    {
                        "install" : [
                            "F:/MOD/tutorialMod"
                        ],
                    
                        "uninstall" : [
                            
                        ],
                    
                        "use" : {
                            "IoXvXNuEAAA=" : ["tutorialMod"]
                        },
                    
                        "unuse" : {
                            
                        }
                    }
                    

配置完毕addon.json后,我们进入游戏进行测试,打开Minecraft.Windows.exe,进入我们上面填写地图Id的那个地图,如果不清楚是哪个,可以看一下这个文件夹下面的levelname.txt

img

进入游戏后,按如下方式测试:

在聊天窗口中输入“钻石剑”,然后我们就获取到一把钻石剑,继而可以分别测试我们Mod中完成的功能(输入钻石剑、钻石镐、钻石头盔、钻石胸甲、钻石护腿、钻石靴子),都可以获取到相应的物品。

img

img

六、上传提审

测试完毕后,我们需要把我们的Mod上传到开发者平台,这样其他玩家才能玩到我们的Mod。

首先需要注册成为我的世界中文版开发者(通过MC Studio内的“发布”界面或者网页https://mcdev.webapp.163.com/#/login注册),然后在前面提到的这两个地方选择“发布新组件”,上传的Mod都是zip格式的压缩文件,将tutorialMod压缩成tutorialMod.zip。至于其他信息,遵循平台的说明和指引一步步填写即可。

img

如果发布的组件是面向手机版(PE)的,那么在发布页填写好相关信息,点击“保存”后,就可以在手机测试版启动器(在开发者平台上可下载,用开发者账号登录)的资源中心看到自己的组件,可下载到手机进行测试,看看手机上的效果是否符合自己的预期。

在组件提交审核并通过审核后,就可以发布组件,让千千万万的玩家玩到自己的组件作品了。