diff --git a/mcguide/20-玩法开发/15-自定义游戏内容/2-自定义方块/1-JSON组件.md b/mcguide/20-玩法开发/15-自定义游戏内容/2-自定义方块/1-JSON组件.md index f6ef45e..1b9dd2b 100644 --- a/mcguide/20-玩法开发/15-自定义游戏内容/2-自定义方块/1-JSON组件.md +++ b/mcguide/20-玩法开发/15-自定义游戏内容/2-自定义方块/1-JSON组件.md @@ -38,8 +38,7 @@ time: 分钟 | ----------------------- | ---- | ------ | ------------------------------------------------------------ | | identifier | str | | 包括命名空间及物品名。需要全局唯一。建议使用mod名称作为命名空间 | | register_to_create_menu | bool | false | 是否注册到创造栏 | -| category | str | Nature | 注册到创造栏的分类,可选的值有:
Construction
Nature
Equipment
Items
None | -| base_block | str | | 继承的特殊方块类型,可选的值有:
mob_spawner 自定义刷怪箱
portal 自定义传送门
custom_crop_block 自定义农作物
custom_heavy_block 自定义重力方块
liquid 自定义静态流体方块
flowing_liquid 自定义动态流体方块
netease_container 自定义容器 | +| category | str | Nature | 注册到创造栏的分类,可选的值有:
Construction
Nature
Equipment
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:六面向方块 | diff --git a/mcguide/20-玩法开发/20-物理玩法开发/1-物理的使用指南.md b/mcguide/20-玩法开发/20-物理玩法开发/1-物理的使用指南.md new file mode 100644 index 0000000..f6a5de6 --- /dev/null +++ b/mcguide/20-玩法开发/20-物理玩法开发/1-物理的使用指南.md @@ -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` 来调整,但是这个方法笔者并没有使用过,所以需要开发者们自行思考研究 diff --git a/mcguide/20-玩法开发/20-物理玩法开发/picture/img.png b/mcguide/20-玩法开发/20-物理玩法开发/picture/img.png new file mode 100644 index 0000000..113dc9b Binary files /dev/null and b/mcguide/20-玩法开发/20-物理玩法开发/picture/img.png differ diff --git a/mcguide/20-玩法开发/20-物理玩法开发/picture/img_1.png b/mcguide/20-玩法开发/20-物理玩法开发/picture/img_1.png new file mode 100644 index 0000000..0df77d4 Binary files /dev/null and b/mcguide/20-玩法开发/20-物理玩法开发/picture/img_1.png differ diff --git a/mcguide/20-玩法开发/20-物理玩法开发/picture/img_2.png b/mcguide/20-玩法开发/20-物理玩法开发/picture/img_2.png new file mode 100644 index 0000000..2dbe43d Binary files /dev/null and b/mcguide/20-玩法开发/20-物理玩法开发/picture/img_2.png differ diff --git a/mcguide/20-玩法开发/20-物理玩法开发/picture/img_3.png b/mcguide/20-玩法开发/20-物理玩法开发/picture/img_3.png new file mode 100644 index 0000000..9fb6654 Binary files /dev/null and b/mcguide/20-玩法开发/20-物理玩法开发/picture/img_3.png differ diff --git a/mcguide/20-玩法开发/20-物理玩法开发/picture/img_4.png b/mcguide/20-玩法开发/20-物理玩法开发/picture/img_4.png new file mode 100644 index 0000000..eff1fe1 Binary files /dev/null and b/mcguide/20-玩法开发/20-物理玩法开发/picture/img_4.png differ diff --git a/mcguide/20-玩法开发/20-物理玩法开发/picture/img_5.png b/mcguide/20-玩法开发/20-物理玩法开发/picture/img_5.png new file mode 100644 index 0000000..1c13364 Binary files /dev/null and b/mcguide/20-玩法开发/20-物理玩法开发/picture/img_5.png differ diff --git a/mcguide/9-规范开发/5-多人联机适配规范.md b/mcguide/9-规范开发/5-多人联机适配规范.md index 396a151..fc1e291 100644 --- a/mcguide/9-规范开发/5-多人联机适配规范.md +++ b/mcguide/9-规范开发/5-多人联机适配规范.md @@ -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()): 当你遵循上述开发原则,即客户端和服务端分离,您开发的模组基本天然兼容多人联机和我的山头的环境 - + \ No newline at end of file