8 Commits

Author SHA1 Message Date
allcontributors[bot]
31d219956f docs: update .all-contributorsrc 2026-01-09 08:04:55 +00:00
allcontributors[bot]
df4bece89b docs: update README.md 2026-01-09 08:04:54 +00:00
MCNeteaseDevs
77ba105452 Merge pull request #40 from sevenstars0/patch-3
Update 30-UI说明文档.md
2026-01-09 16:02:43 +08:00
kwiilh
439216893d Github双端同步 2026-01-09 15:55:00 +08:00
MCNeteaseDevs
074cf234bd Fix typo in Mod SDK documentation section 2025-12-30 11:18:07 +08:00
MCNeteaseDevs
64282118f6 添加修改SDK文档流程说明 2025-12-30 11:17:14 +08:00
sevenstars
d68587065c Update 30-UI说明文档.md
增加了scroll_view中对于$touch变量的说明
2025-12-30 10:47:57 +08:00
MCNeteaseDevs
688a85ffff Merge pull request #38 from MCNeteaseDevs/all-contributors/add-sevenstars0
docs: add sevenstars0 as a contributor for doc
2025-12-30 10:36:20 +08:00
12 changed files with 289 additions and 15 deletions

View File

@@ -34,6 +34,15 @@
"contributions": [
"doc"
]
},
{
"login": "Nacalpha",
"name": "Nacalpha",
"avatar_url": "https://avatars.githubusercontent.com/u/128111367?v=4",
"profile": "https://github.com/Nacalpha",
"contributions": [
"doc"
]
}
]
}

View File

@@ -18,6 +18,22 @@
## 贡献指南
**开发指南/教学课程文档**与**Mod SDK文档**修改流程不一致,接下来将会分别介绍
### Mod SDK文档修改
1. 点击仓库中**Issues** - 点击**New Issue** - 选择**SDK文档修改**
2. 按照模版依次填写以下内容:
- 标题:为了方便其他开发者查找相同修改,请写明**接口名称与修改部分**。例如StopCustomMusicById添加备注
- 接口名称:填写需要修改的接口名称或链接
- 接口类型:选择接口的类型,共有服务端、客户端、双端三种选项
- 修改内容:详细描述修改的接口文档内容
- 其他补充信息:如有其他信息可以补充在这里
- 开发者信息:将会用于贡献致谢展示与奖品发放
3. 完成后点击Create。修改内容会由官方进行审核审核通过后会在**两周之内**同步至官网
### 开发指南/教学课程修改
1. Fork 本项目。点击 Fork 按钮创建一个新的派生项目到自己的工作区Create a new fork
2. 克隆派生
@@ -74,6 +90,7 @@ git push --set-upstream origin your-branch-name
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MCNeteaseDevs"><img src="https://avatars.githubusercontent.com/u/186891705?v=4?s=100" width="100px;" alt="MCNeteaseDevs"/><br /><sub><b>MCNeteaseDevs</b></sub></a><br /><a href="#doc-MCNeteaseDevs" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sevenstars0"><img src="https://avatars.githubusercontent.com/u/148792757?v=4?s=100" width="100px;" alt="sevenstars"/><br /><sub><b>sevenstars</b></sub></a><br /><a href="#doc-sevenstars0" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LiangJi23333"><img src="https://avatars.githubusercontent.com/u/102958768?v=4?s=100" width="100px;" alt="LiangJi23333"/><br /><sub><b>LiangJi23333</b></sub></a><br /><a href="#doc-LiangJi23333" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Nacalpha"><img src="https://avatars.githubusercontent.com/u/128111367?v=4?s=100" width="100px;" alt="Nacalpha"/><br /><sub><b>Nacalpha</b></sub></a><br /><a href="#doc-Nacalpha" title="Documentation">📖</a></td>
</tr>
</tbody>
</table>

View File

@@ -1767,7 +1767,15 @@ touch_path = scroll_view_path + "/scroll_touch/scroll_view/panel/background_and_
mouse_path = scroll_view_path + "/scroll_mouse/scroll_view/stack_panel/background_and_viewport/scrolling_view_port/scrolling_content"
```
在PC端进行游戏时按F11可以切换鼠标和触摸屏两种操作模式而手机端通常只有触摸屏这一种操作模式。不同的操作模式scroll_view的scrolling_content会生成在不同的路径下触摸屏使用touch_path获得scrolling_content的绝对路径而鼠标控制使用mouse_path获得。
在PC端进行游戏时按F11可以切换鼠标和触摸屏两种操作模式而手机端通常只有触摸屏这一种操作模式。不同的操作模式scroll_view的scrolling_content会生成在不同的路径下触摸屏使用touch_path获得scrolling_content的绝对路径而鼠标控制使用mouse_path获得。如果不想让路径随操作模式变化,可以指定$touch变量为true此时路径将固定为touch_path。
```json
"scroll_view0@common.scrolling_panel": {
// 手动指定$touch变量为true
"$touch": true
...
}
```
### grid
@@ -2641,4 +2649,4 @@ def OnRichTextCreateFinishCallback(self):
##### 使用事项
- 富文本是Python实现的一个复合控件在生成富文本后会有部分数据缓存在RichTextItem实例中因此不支持使用clone接口复制已创建完成的富文本但是clone没有调用过readRichText的富文本是可行的。
- 我们在内部会缓存路径与RichTextItem实例的映射使用GetRichTextItem接口传入相同路径返回的RichTextItem实例也会是相同的。
- 我们在内部会缓存路径与RichTextItem实例的映射使用GetRichTextItem接口传入相同路径返回的RichTextItem实例也会是相同的。

View File

@@ -38,8 +38,7 @@ time: 分钟
| ----------------------- | ---- | ------ | ------------------------------------------------------------ |
| identifier | str | | 包括命名空间及物品名。需要全局唯一。建议使用mod名称作为命名空间 |
| register_to_create_menu | bool | false | 是否注册到创造栏 |
| category | str | Nature | 注册到创造栏的分类,可选的值有:<br/>Construction<br/>Nature<br/>Equipment<br/>Items<br/>None |
| base_block | str | | 继承的特殊方块类型,可选的值有:<br/>mob_spawner 自定义刷怪箱<br/>portal 自定义传送门<br/>custom_crop_block 自定义农作物<br/>custom_heavy_block 自定义重力方块<br/>liquid 自定义静态流体方块<br/>flowing_liquid 自定义动态流体方块<br/>netease_container 自定义容器 |
| category | str | Nature | 注册到创造栏的分类,可选的值有:<br/>Construction<br/>Nature<br/>Equipment<br/>Items |
## components
@@ -122,7 +121,7 @@ time: 分钟
### minecraft:destructible_by_explosion
可用于控制爆炸抗性。原版方块的爆炸抗性见[官方wiki](https://minecraft-zh.gamepedia.com/%E7%88%86%E7%82%B8#.E7.88.86.E7.82.B8.E6.8A.97.E6.80.A7)
可用于控制挖掘所需的时间。该值的含义与[官方wiki](https://minecraft-zh.gamepedia.com/%E6%8C%96%E6%8E%98#.E6.96.B9.E5.9D.97.E7.A1.AC.E5.BA.A6)的“硬度”一致
- 如果设置为 `true`,方块会采用默认的爆炸抗性。
- 如果设置为 `false`,方块将不可被爆炸破坏。
@@ -197,7 +196,7 @@ time: 分钟
| type | string | | 必须设置,用于控制生成的生物类型 |
- 原生生物type为"minecraft:Namespaced ID",如"minecraft:parrot"Namespaced ID可参考[官方wiki](https://zh.minecraft.wiki/w/%E5%9F%BA%E5%B2%A9%E7%89%88%E6%95%B0%E6%8D%AE%E5%80%BC/%E5%AE%9E%E4%BD%93ID)中各Mob的详细信息。
- 微软自定义生物type为"minecraft:entity"中"description"的"identifier"项,可参考[自定义生物文档](../../3-自定义生物/01-自定义基础生物.md)及[CustomBlocksMod](../../../13-模组SDK编程/60-Demo示例.md#CustomBlocksMod)中的customblocks_test_mobspawner1.json。
- 微软自定义生物type为"minecraft:entity"中"description"的"identifier"项,可参考[自定义生物文档](../3-自定义生物/01-自定义基础生物.md)及[CustomBlocksMod](../../13-模组SDK编程/60-Demo示例.md#CustomBlocksMod)中的customblocks_test_mobspawner1.json。
- 自定义刷怪箱方块的base_block需要设为mob_spawner。
@@ -213,7 +212,6 @@ time: 分钟
| value | bool | | 必须设置,用于设置无法摆放在水源和流水方块中 |
- 可以在方块的loottable中设置被水流摧毁后的掉落物
- 当**base_block**字段为**custom_crop_block**的时候,会自动注册此组件,无需再次注册。
@@ -332,9 +330,9 @@ time: 分钟
主要用于[多面向](./2-功能.md#duomianxiang)的功能
| 键 | 类型 | 默认值 | 解释 |
| ---- | ------ | ------ | --------------------------------------------------- |
| type | string | | direction四面向方块facing_direction六面向方块 |
| 键 | 类型 | 默认值 | 解释 |
| ---- | ------ | ------ | ----------------------------------------------------- |
| type | string | | direction四面向方块facing_direction六面向方块 |
<span id="netease:fuel "></span>

View File

@@ -0,0 +1,242 @@
# 物理的使用指南
## 使用准则与免责声明
### 使用准则
1. 仅限第三方用户生成内容UGC使用
2. 只有创作者创建的自定义实体才能与物理刚体绑定原版实体无法与PhysX创建的刚体绑定
3. 物理身件可以与其他游戏物件(包括原生游戏物件)产生互动,但是原生游戏物件与其他原生游戏物件之间的互动方式及物理碰撞不会被改变
4. 当两个碰撞同时发生时,核心游戏的物理引擎具有最高优先级
只有满足上述条件,您的模组才能符合要求进行上架
### 免责声明
物理相关api与特性可能会在将来调整并且不向前兼容。请随时做好调整的准备
但是,每次调整都会做出充分的预告和调整内容公告,方便你对您的模组进行最新内容的兼容。
## 准备工作
由于我们的物理引擎是用的 NVIDIA PhysX您可以先前往NVIDIA官网下载PhysX Visual Debugger方便后续调试。
[https://developer.nvidia.com/physx-visual-debugger](https://developer.nvidia.com/physx-visual-debugger)
![img.png](./picture/img_2.png)
## 全局配置
在使用物理引擎之前,您需要在模组的 behavior_packs 行为包中新增 physx_setting.json 全局配置文件
![img.png](./picture/img.png)
文件里面需要设置成
```json
{
"enable": true
}
```
保存后,您的模组可以正常使用 PhysX 物理引擎了。
## 开始创作
现在我会通过demo来向你介绍physx能做的内容
### 创建自定义刚体
使用物理内容的之前,您首先需要创建一个新的物理刚体,物理刚体**必须**绑定在`自定义实体`上,且创建刚体的方法只能在 `AddEntityServerEvent` 事件中使用
```python
# 创建一个自定义刚体
comp = serverApi.GetEngineCompFactory().CreatePhysx(entityId)
comp.CreatePxActor()
```
现在你已经为这个 entityId 的实体添加了一个物理刚体
> 注意: 刚体创建数量不得大于 8192 虽然Physx限制是31767但是我们需要预留一些刚体阈值给到其他实体和客户端内容
> 注意:必须要在 AddEntityServerEvent 事件中创建刚体,否则会报错
> 注意:实体必须绑定在 自定义实体上,不得绑定在原版实体上
### 添加包围盒
目前这个刚体暂时没有任何功能和属性,为了让它能够正常工作,我们需要给刚体添加一些属性
首先需要添加包围盒,包围盒是刚体的基本属性,你只有添加了包围盒,才能让刚体与世界实体进行交互
添加包围盒的方法是 AddBoxGeometry ,参数方法如下
```python
# localTransform tuple(float,float,float) 盒子中心相对实体原点的偏移
# halfX float 表示盒子长度的一半
# halfY float 表示盒子高度的一半
# halfZ float 表示盒子宽度的一半
# staticFriction float 静摩擦系数
# dynamicFriction float 动摩擦系数默认为e 注意此摩擦力设置为0并不代表刚体与原生游戏交互也是 0 摩擦力
# restitution float 弹性恢复系数默认为0
# eventMask int PxEventMask枚举用于监听碰撞事件默认为PxRigidBodyFlag.Null即不需要碰撞事件
# userData None或str 可记录自定义数据长度不超过20默认为None
comp.AddBoxGeometry((0, 0.9, 0), 0.3, 0.9, 0.3, 0.05, 0.05, 0, PxEventMask.Server | PxEventMask.Found, None)
```
PxEventMask 枚举目前有六个
1. PxEventMask.Server 服务端事件
2. PxEventMask.Client 客户端事件
3. PxEventMask.Lost 结束碰撞事件
4. PxEventMask.Found 开始碰撞事件
5. PxEventMask.Found_Detail 碰撞事件的详细信息
6. PxEventMask.Null 该刚体不附加任何事件
此时你已经成功为你的刚体创建好了包围盒
当你启动游戏时,如果你此时提前打开了 PhysX Visual Debugger那么刚体的包围盒就会显示出来了
![img.png](./picture/img_3.png)
红色代表世界的原生方块、绿色和黄色代表刚体的包围盒
![img.png](./picture/img_4.png)
### 添加约束和运动
如果你想给刚体添加一些约束,比如刚体的运动锁定某个轴体,您需要添加动态约束
```python
# 下面的方法是规定此刚体只会沿着 X Y Z 轴运动
comp.SetRigidDynamicLockFlags(PxRigidDynamicLockFlag.eLOCK_ANGULAR_X | PxRigidDynamicLockFlag.eLOCK_ANGULAR_Y | PxRigidDynamicLockFlag.eLOCK_ANGULAR_Z)
```
PxRigidDynamicLockFlag 枚举目前有六个
1. PxRigidDynamicLockFlag.eLOCK_ANGULAR_X 锁定沿着 X 轴运动
2. PxRigidDynamicLockFlag.eLOCK_ANGULAR_Y 锁定沿着 Y 轴运动
3. PxRigidDynamicLockFlag.eLOCK_ANGULAR_Z 锁定沿着 Z 轴运动
4. PxRigidDynamicLockFlag.eLOCK_LINEAR_X 锁定沿着 X 轴旋转
5. PxRigidDynamicLockFlag.eLOCK_LINEAR_Y 锁定沿着 Y 轴旋转
6. PxRigidDynamicLockFlag.eLOCK_LINEAR_Z 锁定沿着 Z 轴旋转
如果你希望您的刚体具有运动的属性你需要将刚体设置为运动学刚体
那什么叫做有运动的属性
比如你想要手动控制刚体的运动那么你需要将刚体设置为运动学刚体
包括做成刚体风车刚体传送带等行为
```python
# 目前PxRigdiBodyFlag 仅支持PxRigidBodyFlag.eKINEMATIC
comp.SetRigidBodyFlag(PxRigidBodyFlag.eKINEMATIC, True)
```
### AI控制
利用实体创建刚体您需要对AI进行调整
否则在客户端会出现悬空的情况
```python
comp = serverApi.GetEngineCompFactory().CreateControlAi(entityId)
comp.SetBlockControlAi(False)
```
### 做一个简单的风车
对此我们可以通过上述内容做一个简单的风车,我们计划是配合方块调色板 + 物理引擎来做一个简单的风车
机械动力的风车是一个比较不错的参考对象
我们仿照机械动力的风车,来做一个中国版物理引擎的风车
首先根据上面介绍的内容,我们依次创建刚体,设置包围盒
但是原版Java版机械动力的风车可以自由组合方块所以我们需要一个刚体对应一个方块。目前规划是创建一个长度5格的刚体
可以做一个for循环
```python
for i in range(2):
for j in range(-2,3):
comp.AddBoxGeometry((j, 0.5 + i, 0), 0.5, 0.5, 0.5, 0.05, 0.05, 0,
PxEventMask.Server | PxEventMask.Client | PxEventMask.Found | PxEventMask.Found_Detail | PxEventMask.Lost,
%s,%s,0'%(j, i))
```
此时就会创建一个总长度 5 格的刚体包围盒,并且每个刚体对应一个方块
因为这个刚体会有旋转的效果,所以我们需要给刚体添加一个运动属性
```python
# 目前PxRigdiBodyFlag 仅支持PxRigidBodyFlag.eKINEMATIC
comp.SetRigidBodyFlag(PxRigidBodyFlag.eKINEMATIC, True)
```
因为刚体旋转是通过传入一个四元数并对四元数调整来达到目标的
所以我们需要创建一个四元数组
```python
# 这里建议用一个成员变量来做存储,方便删除实体后对数据进行调整和删除
self.mSkeletons[entityId] = Quaternion(0, 0, 0, 1)
```
因为旋转是一个连续的过程所以我们需要重写Update方法以达到每tick刷新我们的旋转数据
```python
# 这里我们对 self.mSkeletons 进行遍历,获取刚刚存储好的四元数,修改的同时传入刚体运动方法
for entityId, v in self.mSkeletons.iteritems():
# 旋转
q = v * Quaternion.AngleAxis(1, Vector3.Up())
# 修改变量值
self.mSkeletons[entityId] = q
# 设置为运动学目标
comp = serverApi.GetEngineCompFactory().CreatePhysx(entityId)
# 运动
comp.SetKinematicTarget(None, q.ToTuple())
```
此时刚体就会每tick旋转角度
但是目前做的内容仅仅只是对刚体进行了旋转,客户端没有表现,我们还需要用方块调色板对客户端进行调试
首先创建一个调色板
```python
dataDict = {'extra': {}, 'void': False, 'actor': {}, 'volume': (1, 5, 2), 'common': {('minecraft:grass_block', 0): [4, 9], ('minecraft:glass', 0): [1, 6], ('minecraft:quartz_block', 0): [3, 8], ('minecraft:white_wool', 0): [0, 5], ('minecraft:oak_planks', 0): [2, 7]}, 'eliminateAir': True}
comp = compFactory.CreateBlock(levelId)
palette = comp.GetBlankBlockPalette()
palette.DeserializeBlockPalette(dataDict)
blockGeometryComp = compFactory.CreateBlockGeometry(levelId)
geometryName = blockGeometryComp.CombineBlockPaletteToGeometry(palette,"my_block_geometry")
```
然后需要将调色板绑定在实体上,但是绑定刚体是欧拉角,也就是说我们还需要将四元数转成欧拉角
好在ModSDK提供了这个简单的方法物理引擎也有获取客户端刚体的四元数数据
```python
actorRenderComp = compFactory.CreateActorRender(entityId)
comp = compFactory.CreatePhysx(entityId)
q = Quaternion(comp.GetQuaternion())
# AddActorBlockGeometry 在 3.7 新增了一个参数, 会控制先旋转后偏移还是先偏移后旋转。默认是False
# 为了做风车,我们设置为 True也就是先旋转再偏移
actorRenderComp.AddActorBlockGeometry(geometryName, (-2, 0, 0), q.EulerAngles().ToTuple(), 0True)
self.mSkeletons.add(entityId)
```
此时刚体的方块调色板就已经绑定在实体上了,但是这个我们查看 PhysX Visual Debugger 时,发现刚体变动了,但是调色板方块没有变化
所以我们也要对客户端表现进行调整
同样需要重写 Update 方法每tick修改方块调色板的旋转角度
```python
def Update(self):
for entityId in self.mSkeletons:
comp = compFactory.CreatePhysx(entityId)
q = comp.GetQuaternion()
if q:
q = Quaternion(q)
actorRenderComp = compFactory.CreateActorRender(entityId)
actorRenderComp.SetActorBlockGeometryRotation("my_block_geometry", q.EulerAngles().ToTuple())
```
当全部完成后,就可以做到风车围绕着一个自定义实体进行旋转,然后推动实体进行移动了
并且你也可以站在这个刚体上,但是站在刚体上,刚体并不会带着玩家一起移动,开发者们可以自行思考解决办法来处理这个问题!
![img_5.png](./picture/img_5.png)
### 思考
虽然我们现在是在用 Update 的方式去动态的调整刚体旋转和外观旋转
但是进入游戏后会发现,用此方法来旋转会有一点的顿挫感,虽然影响不是很大,但如果你需要追求细致,可能还不算太完美
或许还可以通过 `GameRenderTickEvent` 来调整,但是这个方法笔者并没有使用过,所以需要开发者们自行思考研究

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 KiB

View File

@@ -11,11 +11,11 @@ selection: true
最近,在山头测试中,我们发现很多开发者的模组明明在单人游戏运行是非常正常的,但在山头、多人联机环境下就会出现功能无效或者异常的情况
只要遵循 ServerSystem ClientSystem 严格分离 的原则,大部分兼容性问题都能自然消失。
后面我们发现,实际上只要遵循 ServerSystem ClientSystem 分离的原则。基本上就能保证开发的模组兼容山头服和多人联机
## 开发范例
> 下方代码为错误示范。仅供参考和通俗化解释,并不能实际运行!
> 下方代码仅供参考和通俗化解释,并不能实际运行!
**modClient.py**
```python
@@ -36,12 +36,12 @@ class ModClient(clientApi.GetClientSystemCls()):
**modServer.py**
```python
import mod.client.extraClientApi as serverApi
from xxx.xxx.xxx import ModClient #错误引用客户端侧
from xxx.xxx.xxx import ModClient
class ModServer(serverApi.GetServerSystemCls()):
def ServerRegister(self):
ModClient.NoClientApiRegister() #在多人游戏出现功能无效或者异常
ModClient.NoClientApiRegister()
print("register successfully")
# TODO
```
@@ -56,4 +56,4 @@ class ModServer(serverApi.GetServerSystemCls()):
当你遵循上述开发原则,即客户端和服务端分离,您开发的模组基本天然兼容多人联机和我的山头的环境