This commit is contained in:
boybook
2025-12-01 20:59:16 +08:00
parent 12738a142c
commit 760c2dd9ad
5535 changed files with 21070 additions and 2021 deletions

View File

@@ -0,0 +1,131 @@
# 更高效的进行双端通信
> 本篇教程获得第一期知识库必看教程奖。
>
> 获奖作者:红石公司。
## 前言
在我的世界模组和网络服的开发过程中,经常需要进行 服务端与客户端 ,或其他系统之间的通信。
一般的我们使用Notify与Broadcast事件进行通信。 但是每次新增一个通信事件都需要使用ListenForEvent接口进行监听。
大部分开发者都将ListenForEvent的调用写在`__init__`里,这就导致无法热更新,需要退出游戏重新进入,十分影响开发效率。
**学习本教程你将掌握:**
- 如何进行通信事件的热更新。
- 如何将调用远程系统变得像调用自身一样方便。
**效果图示:**
- 在服务端发送消息时,向客户端发送事件 并获取返回值。
![img](./images/0_0.png)
![img](./images/0_1.png)
![img](./images/0_2.png)
**学习本教程的前提:**
- 熟悉Python并进行过模组开发。
- 掌握通信事件`NotifyToServer``NotifyToClient``BroadcastToAllClient`的用法。
## 原理分析解释
根据效果图示可以看到,服务端直接调用了一个未经创建的方法名,这个方法名对应了客户端的方法。在客户端通过某种方式获取到此方法并成功执行。
客户端的return再次返回到服务端并触发回调函数。
由此可得三个技术点:
1. 使用未创建的方法。
2. 动态获取实例方法。
3. 回调的实现。
### 使用未创建的方法
通过重写`__getattr__`方法可以截获正在调用的方法名。
![img](./images/0_3.png)
在此返回一个其他方法,将不会引起报错,我将其称为”万能方法“。并且可以将截获的方法名保存下来,在”万能方法“真正被调用时,可以获得:
1. 刚才保存的方法名。
2. 传入的参数。
这两个集齐了我们通信函数所需的全部参数。可以正常进行通信。如下图所示:
![img](./images/0_4.png)
### 动态获取方法
python可以通过内置函数 getattr(obj, attr_name) 获取某个对象的属性值,返回值是属性本身,可以直接使用。
![img](./images/0_5.png)
由于调用不存在的方法名会引起报错,所以这里的`getattr`在实际使用时要放在try语句内。
如此,在双端建立的固定通信函数用于发送与接收事件,参数里传入`attr_name,` 在远程端接收到事件后,`getattr`解析`attr_name`为本地方法,即可直接使用。
### 回调的实现
回调本质上也是一次通信,不同的是,不是通过`getattr`方法名获取方法对象,而是一个本身就存在的函数或方法对象,比如一个嵌套函数里的子函数,这通过`getattr`是抓不到的。
所以我们需要检测一个关键字,我这里选用`back`
为了远程端返回回调后,本地端能够顺利找到回调函数对象,我们使用`uuid`模块的`uuid.uuid1().hex`获取一个回调id保存在本地端同时加入通信参数里。
远程端收到事件并执行后检测到参数里有一个back字段就将 执行结果 与 回调id重新通过通信事件发回本地端。
本地端由回调id从刚才的缓存里取出函数对象并执行。到此一个回调过程完成。但是禁止套娃
以上就是通信的热更与直接执行、接受回调 所涉及到的技术难点。
示例代码在这里:[下载地址](https://share.weiyun.com/wGawl7J1)。
示例结果图示:
![img](./images/0_6.png)
![img](./images/0_7.png)
![img](./images/0_8.png)
demo只简单展示了客户端与服务端的双向通信仅作为功能预览不具备实用性。还需要各位开发者根据自己的实际情况以及编程习惯开发自己的通信系统。
## 拓展
研究透彻后,可以尝试解决以下拓展问题:
- 服务端向所有客户端发送事件,该如何修改代码。
- 客户端或服务端,向本端的其他系统发送事件,该如何修改代码。
- UI或其他自定义逻辑类中如何不通过 服务端/客户端 中继,直接使用通信。
解决上述问题后,你的通信系统将趋于完善,这将大大加快你的开发进度,提升你的开发效率。(本工作室《我的世界工具盒子》模组,就是采用此方式来应对高频的通信需求。)
本人是编程新手,也是开发新手,教程如有什么纰漏,表述不当,还请大家指出改进。 若有哪里不明白的地方,可以在评论区提问,我会及时解答的。

View File

@@ -0,0 +1,59 @@
# 制作倒计时和CD
> 本篇教程获得第一期知识库必看教程奖。
>
> 获奖作者:凉寂蜀黍。
前段时间看见有萌新使用time.sleep()来做计时,这样会导致整个程序停摆。
今天给萌新们分享一下如何用帧数事件来做倒计时和cd。本教程针对萌新大佬们(轻喷)也可以分享一下自己的方法。
**适用对象:**
- 刚接触开发、不熟悉api的开发萌新。
- 有一定python基础但不多的人。
- 不需要任何json基础 。
## 原理其实很简单
首先在代码最前方定义以下代码:
```python
#格式为"玩家id":剩余时间帧数(int)
dictcd = {}
```
之所以要用字典是因为在服务端中要考虑多玩家的情况要把每个玩家的数据进行隔离。客户端可以直接创建int类型的cd值不需要字典。
接着在需要用到cd的地方写逻辑若cd值为0了就可以让技能起效并重置cd为900帧(一秒30帧) 。
![img](./images/1_0.png)
若cd值不为0提示冷却中。
![img](./images/1_1.png)
然后在OnScriptTickSever事件中遍历字典dictcd中的键值对把每个不为0的cd值减一(客户端也有相关事件)。
![img](./images/1_2.png)
![img](./images/1_3.png)
最后在cd=1时即可执行cd到期对应的行为。(之所以在cd=1时执行是因为cd=0会长期存在cd=1在每次计时中只会触发一次) 。
![img](./images/1_4.png)
若对你有帮助,请评论区留言让我看到,我还会继续做下去。
若有问题或建议,也请评论区留言或私信哦。

View File

@@ -0,0 +1,52 @@
# 获取2个角度之间的最短旋转间隔
> 本篇教程获得第一期知识库优秀教程奖。
>
> 获奖作者:素笺淡墨。
一般最常见的角度应该划分为0 ---> 360360 ---->0180----> 0 <—— -180。
在这种情况下,假设,我们以三百六十为基准。-350到360之间如果只是单纯的相减返回的肯定不是最快的距离。
毕竟-350其实就相当于-10而三百六十就相当于0。他们之间的最近距离一定是十。
在这种情况下我们需要获取最近的距离我们一定要先把所有的角度让它小于180并且 >-180。
也就是a代表角度 rx或者ry。
```python
if a>180:
a+=-360
if a<-180:
a+=360
```
至于为什么要这样,是因为如果这个当前的角度超过一百八十,那他一定是绕到了另一半的区域了。因为我上面的取值范围是 -180 ----- 0 ----- 180。
在这种情况下180度和-180度实际是相等的。
也就是在一个圆圈内180和-180这两个点它们重叠了。
这两个点要是有任何一方超过一百八这个数字,那么他就会到达另一边。
也就是说-181其实就等于179。这样子就能确保他们一定会处于一百八十以内。
做完这一步之后,你会发现-179度和180度这两个角度如果进行相减还是无法获得最近的距离。
因为按照我上面所说这两个角度他们的相差一定是一。
在这种情况下我们还需要进行一步处理那就是如果这两个角度他们之间的相差大于一百八十那就代表这两个角度之间一定存在一个正和一个负数。因为正常情况下按照我上面那一步把角度限制为一百八十的话0----180最大就是一百八十。
那么我们就把第一个角度转换成第二个角度的同类型,也就是如果第二个角度它是负数那么我们就把第一个角度转换成负数。如果第二个角度是正数第一个角度是负数,那么我们就把第一个角度转换成正数。
```python
def func(a, b):
if abs(a-b)>180:
if b>0:
a+=360
else:
a+=-360
return (a,b)
```
这个时候返回的ab两个值你再将它们相减就会得到一个最近的角度间隔。

View File

@@ -0,0 +1,23 @@
# 事件参数的快速输入
> 本篇教程获得第二期知识库必看教程奖。
>
> 获奖作者:无维心天。
使用vscode的用户代码片段快速的输入事件函数参数。
![img](./images/3_0.png)
![img](./images/3_1.png)
即可享受代码片段。
![img](./images/3_2.png)
代码片段地址:[vscode_code_snippet.json · 无维心天/网易我的世界 mod_api - Gitee.com](https://gitee.com/wuwei_xintian/netease_mc_mod_api/blob/master/vscode_code_snippet.json)。

View File

@@ -0,0 +1,54 @@
# vscode安装Python2.7开发环境
>本篇教程获得第二期知识库必看教程奖。
>
>获奖作者Zzz.。
主要是为了自动补全和格式化。
1. 安装好python2.7python3以及对应的pip。
2. vscode安装python插件。
新版本不支持python2.7, 需要手动下载安装老版本。
[https://marketplace.visualstudio.com/_apis/public/gallery/publishers/ms-python/vsextensions/python/2021.12.1559732655/vspackage](https://marketplace.visualstudio.com/_apis/public/gallery/publishers/ms-python/vsextensions/python/2021.12.1559732655/vspackage)。
![img](./images/4_0.png)
安装完了记得关闭插件自动更新。
![img](./images/4_1.png)
3. 安装black。
python格式化工具很多, 我建议按照我的教程安装black这个格式化工具。black新版本不支持python2.7, 需要安装旧版: python3 -m pip install black[python2]==21.12b0。**注意是python3 -m pip 不是 python2.7 -m pip.. python2.7装不了black** 。安装完了重启一个命令行窗口, 输入`black --version`验证是否成功安装。
4. 安装ModSDK补全库。
使用python2.7安装补全库: `python2.7 -m pip install mc-netease-sdk`
5. 配置vscode。
编辑setting.json。
![img](./images/4_2.png)
添加下图没打码的内容。
![img](./images/4_3.png)
`/home/student/.local/lib/python2.7/site-packages`替换成你自己对应的目录:`python2.7 -m pip show mc-netease-sdk`
6. 验证。
vscode随便打开一个.py文件格式化。

View File

@@ -0,0 +1,62 @@
# 简化物品信息字典并用于存储
> 本篇教程获得第二期知识库优秀教程奖。
>
> 获奖作者MI4C。
开发者可以通过各种事件或接口获取物品信息字典,物品信息字典的介绍可以看[物品信息字典](https://mc.163.com/dev/mcmanual/mc-dev/mcguide/20-%E7%8E%A9%E6%B3%95%E5%BC%80%E5%8F%91/10-%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5/1-%E6%88%91%E7%9A%84%E4%B8%96%E7%95%8C%E5%9F%BA%E7%A1%80%E6%A6%82%E5%BF%B5.html#%E7%89%A9%E5%93%81%E4%BF%A1%E6%81%AF%E5%AD%97%E5%85%B8)。
在这个字典中包含了普通的信息例如newItemName、count、userData在事件中想要获取userData需要调用GetUserDataInEvent接口旧版本的信息例如itemName远古版本的信息例如itemId、modId、modItemId部分现代事件和接口不会包含远古信息用于接口内部识别物品的信息例如isDiggerItem。
![img](./images/5_0.png)
在实际开发中部分开发者可能会发现物品信息字典中的信息在userData的值中重复出现了这其实是为了方便开发者对物品的部分通用属性进行修改而不用了解和修改userData。其他存在于userData中的信息也可以通过特定接口进行修改例如SetAttackDamageSetItemLayerSetItemTierLevel。
![img](./images/5_1.png)
## 简化物品信息字典
如果要将物品信息进行存储那么这些在userData外的无用信息我们就可以进行剔除只保留关键的信息。
据本人测试仅需保留物品信息字典中的newItemNamenewAuxValuecountuserData即可将物品的信息全部保留下来。
可以通过新建一个字典,并将关键信息拷贝至新字典,即可实现物品信息字典的简化。
## 用于存储
我们可以将物品信息字典存储在非常多的位置例如可以将其存储于另一个物品的userData中可以将其存储于实体或世界的自定义数据ExtraData可以将其储存于方块实体BlockEntityData或TileEntityCustomData甚至可以将其存储于本地配置文件中ConfigData
这些存储虽然方便但是在读取的时候可能会破坏userData中的数据所以这里推荐将物品信息字典转换为字符串再进行存储。
json模块是可以在游戏环境使用的模块之一其中json.dumps可以将python对象转换成json字符串json.loads可以将json字符串转换成python对象。
![img](./images/5_2.png)
## 从已存储的数据中读取
上述已介绍了json模块中的loads方法由于引擎使用的是py2所以我们从json字符串中转出来的python对象还需要将unicode类型转换为str类型。
(这里我们用递归和生成式来对对象进行处理) 。
![img](./images/5_3.png)
## 用简化后的物品信息字典重新生成物品
虽然大部分信息都储存于itemDict的userData中但是 **是否显示在手上** 这个属性却只读取itemDict这一层级的数据所以我们需要下述操作
```python
if itemDict['userData']:
itemDict['showInHand'] = itemDict['userData'].get('display', {}).get('ShowInHand', {}).get('__value__', False)
```
这样使用简化后的物品信息字典,也可以生成正确的物品了。

View File

@@ -0,0 +1,61 @@
# MODSDKGenerator 1.0.0
>本篇教程获得第二期知识库优秀教程奖。
>
>获奖作者MaShuGG。
**请务必看完本篇再下载使用!**
**未经授权,禁止搬运,可以复制该频道链接!**
**状态:**
Windows可用开源持续更新有bug请指出。
**简介:**
官方的零件可以直接创建并写代码不过大多数人还是喜欢用modsdk
但是mcstudio的配置代码创建起来还是比较麻烦名字服务端客户端modConfig等等大约要用两分钟
MODSDKGenerator 1.0.0 可以让你输入mod名字就创建scripts直接开始写代码。
**使用方式:**
下载我们的zip包我们并没有制作exe包你需要Python来执行它点击start.bat即可开始创建输入行为包目录路径和mod名字
mod名字标准示例:demoTest,一般可以小驼峰命名,你也可以自定义。
![img](./images/6_0.png)
**视频教程:**
**特别说明!!!:**
1. 创建的scripts是官方的推荐代码如果看不懂请自行学习。
2. 创建的modConfig有一些UI变量可以方便的更改和复制创建UI如果你没有此需求删掉即可。
![img](./images/6_1.png)
3. 创建的服务端和客户端中监听了两个事件,上面的是监听引擎事件,下面的是监听异端事件,可以方便的更改和复制,在创建的第一时间监听事件,如果你没有此需求,删掉即可。
![img](./images/6_2.png)
**下载:**
微云网盘:[https://share.weiyun.com/oCJpq6xs](https://share.weiyun.com/oCJpq6xs) 。

View File

@@ -0,0 +1,21 @@
# 快速mod开发模板
>本篇教程获得第二期知识库优秀教程奖。
>
>获奖作者:日月潭。
更新内容:见下方字体加粗部分
下载地址:[快速mod开发模板_网易我的世界论坛 (netease.com)](https://mc.netease.com/thread-954716-1-1.html)
将文件夹解压到行为包即可直接进去写代码无需进行配置modMain修改import语句等麻烦操作。
你只需重命名解压出来的文件夹即可它将作为mod名字把ModName文件夹重命名成个性化mod名防止冲突
为了方便代码的复制粘贴本脚本自带两个小功能客户端UI加载完成后在聊天栏提示以及发送信息在聊天栏复读一次。对应监听事件系统通讯两大功能。
**为了方便双端数据通信本脚本封装了CallClientCallAllClientCallServer等函数** 允许客户端直接调用服务器函数服务器调用一个或所有客户端函数免去ListenForEvent的麻烦。
创建UI的相关代码也已经写好并作为注释快捷键一键去除注释即可使用。 **如果你不了解UI端怎么调用客户端函数uiname.py里提供了最简单的办法。**

View File

@@ -0,0 +1,120 @@
# 踩坑总结
>本篇教程获得第一期知识库优秀教程奖。
>
>获奖作者:素笺淡墨。
我会记录在开发测试中遇到的疑难杂症。
1. 使用调试工具查看手机上的日志时必须要保证电脑和手机连接同一个WiFi。
并且,如果是第一次连接:
- 最好是连上同一个WiFi的同时重启一下手机的客户端登录。
- 然后在手机客户端登录进去之后直接点击客户端IP连接。
这里需要注意一点 客户端IP是WiFi的IP地址不是路由器地址。
有些手机。 IP地址是乱码。
![img](./images/8_0.png)
![img](./images/8_1.png)
必须要像我这样直接把IP设置弄为静态才能看到。
2. 某个动画不加载或者对某个动画使用molang时在电脑上没有问题但是到了手机上却发现设置好像无效。
- 这个问题困扰了我差不多 三个小时,后面通过排除法发现。
- 我在其他的动画文件定义了同名的动画名。
- 也就是我在动画文件a而定义了动画名bbb。
然后又在动画文件B定义了动画名bbbb。在电脑端运行的时候动画文件B覆盖了动画文件a。但在手机端运行的时候动画文件a覆盖了动画文件。这就出现了我设置的是动画文件B可是却被覆盖导致设置失败。
3. 字符串被API设置过后没有显示这个字符串。
- 比如我使用`SetName`(设置生物名字) 或者`SetCommand("/say xxxx")` (广播字符串到所有玩家的聊天栏)。
在这种情况下如果字符串是aaaa。可当我用这个字符串设置的时候就会导致某种原因他无法设置成功。这个时候我们需要使用str()将它重新转换成字符串。
- 这个问题一般经常出现在需要获取zh_cn.lang文件里面的文本信息。
- 然后通过这个文本信息和其他字符串连接在一起。
4. 定时器包括定服务端或者客户端的事件根据我的测试他们应该是异步调用。也就是说假设有一个函数aa()。你通过定时器调用了这个函数aa。但可能还没执行完又会被其他事件调用 。
5. 当你上传的包里面有player.entity.json这个文件名的时候会出现错误码你需要把它改成player.json。
6. 配方。输出的物品,它的特殊值不能为-1 不然会报错。
7. molang变量。不能有大写字母不然会出问题。
8. 当你给一个生物调用寻路组件的时候,如果你离这个生物太远,哪怕服务端已经加载不到了,但他还是会触发寻路组件返回的结果。
9. 千万不要在寻路组件返回的方法里再次调用寻路组件,不然会出问题。
10. 当动画里有post lerp_mode 这种东西的时候可能会导致动画在游戏内播放异常。
![img](./images/8_2.png)
11. 判断这个区块是否加载应该使用 CheckChunkState 。而不是使用getblocknew来判断 有时候没加载的区域它会返回空气。
12. 使用SaveExtraData 保存数据的时候。
- 数据里千万不要有tuple 类型 ,也不要有自己定义的类对象。
- 浮点数最好尽量转换成int。
13. 服务端和客户端之间的广播事件,使用这广播事件发送数据的时候,千万不要传,自己定义的对象,或者对象方法。还有就是字典。千万不要保存他自己。不然会出现自己引用自己,导致进入递归。
14. 读取json文件时你会发现有时候这个 json文件明明格式是正确的但却始终读取失败。这是因为某些文件的前三个字符是隐藏字符。你需要在读取这个文件后把前面三个字符给过滤掉 然后再把它转换成json对象。
15. 物品标识符冒号前面的并不能用于区分。和其他模组的物品,冒号后面的才能区分。比如`aa:gg``bb:gg`在游戏里某些逻辑上系统会认为他们是一样的物品。
16. 使用`GetAttrValue`这个API获取速度时如果你对这个速度使用int()想把它转换成整数。那么你会发现无效,必须要把它变成字符串然后遍历这个字符串截取小数点前面的所有字符才可以变成整数。
17. 获取玩家背包的物品时要注意不是所有的物品都有aux这个key。有时候你会获取到一个`{“itemName":minecraft:air"}`
18. 使用静态类成员时要注意如果他只是基础变量int float这些的话最好只用于访问而不是动态修改如果需要动态修改请让它等于对象或者字典列表等。不然会出现本地测试时另一个客户端会初始化这个静态类成员。也就是说如果你有一个静态类成员a=0 那么你在脚本中动态修改为一的时候如果本地的另一个玩家进来那么他就会重新将这个类成员a等于零。
19. 如果你想让一个物品不要在玩家输入/give的时候显示这个物品的标识符那么你除了把他的创造栏分类改为none的同时一定要把它注册到创造栏背包里面 ,要不然就无效。
20. 当你的UI里面有输入框这个控件的时候不需要的时候一定要把这个UI给卸载掉注意是卸载不是隐藏 。要不然就会出现你无法对游戏转向。
21. 一个实体刚生成的时候你在客户端是获取不到他的任何信息的。
22. 客户端的`GetPos`等于None的时候并不一定能百分百保证这个实体已经死亡或者离玩家很远建议使用IsEntityAlive。
23. 每进行一次维度传送 游戏都会对UI重新初始化一次。所以请不要把一些重要的数据存放在客户端的UI文件里面。
24. 当一个UI处于隐藏状态的时候你是没办法获取这个UI的任何信息包括对它设置任何数据。也不一定会全部生效。
25. 调用给玩家添加模型的api时在二点一版本的时候我发现了一个问题。如果存在两个玩家a和b。然后你要给这两个玩家添加一个模型cc。当这两个玩家离得太远的时候你给这两个玩家添加模型cc就会出现在玩家a的客户端里玩家B没有CC模型。因为它添加失败了 。在玩家B的客户端里玩家a没有CC模型。同样失败了 。只有当这两个玩家可以互相看到对方的模型时你在给这两个玩家添加模型CC才会成功。当然这是我在二点一版本发现的。我不知道现在改了没有这也是我一直不敢用这个API方法的原因。
26. 当按钮层级等于零的时候,可能会对监听事件失效,也就是点击这个按钮没反应。
27. 维度切换完成后的事件触发并不代表这个时候玩家他已经可以看到被传送维度的方块了。也就是说如果玩家他传送到了末地触发这个事件的时候玩家的界面仍在显示那个加载中。想获取玩家加载完成后应该在那个UI监听框架那里每传送一次维度都会重新触发那个UI框架完成事件。
28. 从一个维度切换到另一个维度时从一个维度切换到另一个维度时如果你是指定坐标Y轴需要加3。比如你在地狱的40 4 40 有一个木头这个木头的周围都是空气。你想让玩家站在这个木头上面那么你从主世界传送过来的时候应该设置的坐标是40 43 40不然玩家会从这个木头上掉下去。
29. 记录一个细思极恐的问题一般玩家加入游戏时我是通过监听UI框架是否加载完成然后向服务端发送这个玩家的ID在服务端保存这个玩家ID。但当时我没有意识到切换维度会重新触发这个监听UI框架事件。也就是说如果玩家传送一次维度那么就会初始化掉这个玩家在服务端的数据。因为我是在这个监听框架下发的添加玩家广播事件。如果各位有像我一样需要在这个 UI框架加载完成事件下面做广播通信的要注意了。
30. 使用添加物品到玩家背包的Api时日志出现了这个错误的时候。
![img](./images/8_3.png)
请检查你的extrald只有他这个值不是字符串的时候就会出现这个错误。
31. 记录文件打开失败的一个错误。
![img](./images/8_4.png)
我对某个文件读取时出现了错误,我以为是其他应用占用但是我在关机重启之后还是出现了这个问题,后来我发现,在文件的属性这里我选中了只读。
![img](./images/8_5.png)