first commit
0
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/README.md
Normal file
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/0.png
Normal file
|
After Width: | Height: | Size: 6.2 MiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/1.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/10.gif
Normal file
|
After Width: | Height: | Size: 20 MiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/10.png
Normal file
|
After Width: | Height: | Size: 711 KiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/11.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/12.png
Normal file
|
After Width: | Height: | Size: 698 KiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/13.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/14.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/15.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/16.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/17.png
Normal file
|
After Width: | Height: | Size: 1008 KiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/18.png
Normal file
|
After Width: | Height: | Size: 470 KiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/2.png
Normal file
|
After Width: | Height: | Size: 330 KiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/3.gif
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/3.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/4.gif
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/4.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/5.gif
Normal file
|
After Width: | Height: | Size: 649 KiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/5.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/6.gif
Normal file
|
After Width: | Height: | Size: 805 KiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/6.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/7.gif
Normal file
|
After Width: | Height: | Size: 15 MiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/7.png
Normal file
|
After Width: | Height: | Size: 360 KiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/8.gif
Normal file
|
After Width: | Height: | Size: 26 MiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/8.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/9.gif
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/images/9.png
Normal file
|
After Width: | Height: | Size: 680 KiB |
49
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/课程01.规划地图的功能与区域.md
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
front: https://nie.res.netease.com/r/pic/20210730/ee109f39-8987-46e0-9fe7-40ebb23060fa.png
|
||||
hard: 入门
|
||||
time: 10分钟
|
||||
---
|
||||
|
||||
# 规划地图的功能与区域
|
||||
|
||||
一个理想的海岛农场最终会以下图的方式进行罗列。地图一共分为两块岛屿,9个独立建筑。
|
||||
|
||||
**游戏的通关目标是,在100天的时间内赚到25万金币和获得全部14种家具。**
|
||||
|
||||

|
||||
|
||||
## 农场之家
|
||||
|
||||
农场之家会分为修复前与修复后两种状态,在初期时需要通过消耗**10个木头和5个石头**才能得到修复。玩家会在初期就拥有系统赠送的**石稿**、**石斧**和**石锄**,并且可以**重复领取**。**石稿**可以帮助玩家挖掘石材,**石斧**用来帮助玩家挖掘木材,而**石锄**主要用来复原家门口的耕地和收获庄稼。木材与石材则作为**修复畜牧场和农场之家**的固定材料。
|
||||
|
||||
## 畜牧场
|
||||
|
||||
畜牧场分为修复前与修复后两种状态,在初期时需要通过消耗**20个木头、10个石头与5000金币**才能得到修复。在游戏期间,这里会作为畜牧动物的生活地点。
|
||||
|
||||
## 新手关卡
|
||||
|
||||
作为引导玩家了解地图的基础玩法的场景,通过完成NPC的小游戏解锁主岛屿。
|
||||
|
||||
## 图书馆
|
||||
|
||||
玩家可以在图书馆里获得地图玩法指引书。指引书使用原版书与笔的格式,方便我们将玩法要点记录下来供玩家参考。
|
||||
|
||||
## 畜牧店
|
||||
|
||||
玩家可以在畜牧店找牧场商人购买生物蛋,在畜牧场内的围栏后面点击地面可以召唤出动物。
|
||||
|
||||
## 杂货店
|
||||
|
||||
玩家可以在杂货店购买种子,种子可以用来播种作物,收获的农产品是玩家主要的经济来源。
|
||||
|
||||
## 收购商人
|
||||
|
||||
每天日落时会来返玩家之家的 **回收箱**内收购一天的农副产品。
|
||||
|
||||
## 家具店
|
||||
|
||||
玩家可以在家具店内购买家具,部分家具具有特殊的能力。
|
||||
|
||||
## 服装店
|
||||
|
||||
玩家可以在服装店内购买服装,打扮个性的自己。
|
||||
115
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/课程02.规划不可变动区域.md
Normal file
@@ -0,0 +1,115 @@
|
||||
---
|
||||
front: https://nie.res.netease.com/r/pic/20210730/ee109f39-8987-46e0-9fe7-40ebb23060fa.png
|
||||
hard: 进阶
|
||||
time: 40分钟
|
||||
---
|
||||
|
||||
# 规划不可变动区域
|
||||
|
||||
不可变动区域意为地图中不可破坏、不可放置方块的区域。由于**素材地图**在前期处理时,已将世界中的全部群系设置为海洋群系,世界缺少让玩家修复资源点、建筑的材料。若场景遭到玩家无意的破坏,这一行为容易导致整体的玩法流程、美观性大打折扣。为了防止这样的行为带来的糟糕影响,我们需要对主要建筑、地形、或者资源点做无法被破坏、无法在上面加盖方块的功能。
|
||||
|
||||
同时,在游戏过程中,玩家需要基本的工具和食物才能生存。玩家获取金币的途径除了通过种植庄稼,还可以通过挖掘场景中资源点的**木材石头**来取得。因此我们需要提供玩家一个基础的**工具食物箱**,以使玩家能够在容错率更高的情况下顺利度过前期游戏。
|
||||
|
||||
## 使用Function文件固定玩家的游戏模式为冒险模式
|
||||
|
||||
> 在过去制作玩法地图时,对于游戏模式的处理上,命令方块作者们常会使用一个循环型命令方块设置周遭玩家的游戏模式。现在使用行为包中的function文件可以达到同样的效果,并将工作模式放入行为包中,达到更加隐蔽的效果。
|
||||
|
||||
在MC STUDIO编辑器中,一张玩法地图工程会由编辑器自动生成行为包和资源包的工程文件夹。Function文件会在行为包根目录下的functions文件夹内生效,若在该文件夹目录内新建一个tick.json文件,它可以命令function文件内的指令在世界加载后循环运行。
|
||||
|
||||
首先在电脑的任意一处新建一个以**.mcfunction**为结尾的function文件。这里推荐使用任意一种携带mcfunction智能补全的第三方编辑器,示例里,我们会用到**Visual Studio Code**(简称:vs code)。在开始工作前,需要我们在扩展区域下载Blockception's Minecraft Bedrock Development插件。
|
||||
|
||||

|
||||
|
||||
可以直接拖拉文件图标至编辑器页面内,编辑器将自动吞入并打开。简单输入其中一个指令,后续的参数就会智能提示并按enter或tab键自动补全。
|
||||
|
||||

|
||||
|
||||
与在游戏中的对输入框和命令方块打出指令时需要斜杠不同的是,在function文件内无需加入**/**符号。同时命令的执行顺序会**从上往下按顺序执行**,若function文件内的指令被循环执行,则**命令执行到末尾时,会从文件头重新跑起**。
|
||||
|
||||
将玩家设置成冒险模式,可以防止玩家破坏和加盖方块,因此我们首先只需在function文件内写入以下指令,来判断玩家是否为非冒险模式,是则将他们的游戏模式转变成冒险模式。
|
||||
|
||||
```
|
||||
execute @a[m=!adventure] ~~~ gamemode adventure @s
|
||||
```
|
||||
|
||||
接着新建functions文件夹,并将function文件放入其中,在同目录下新建tick.json文件。
|
||||
|
||||
<img src="./images/4.png" alt="4" style="zoom:160%;" />
|
||||
|
||||
在tick.json文件中打入以下JSON内容,以"values"为键,它接受一个以function文件名为主的数组。function文件里面的命令就会像循环型命令方块一样,在每一个游戏逻辑帧下执行一遍。
|
||||
|
||||
```json
|
||||
{
|
||||
"values": [
|
||||
"gamemode" //gamemode是gamemode.mcfunction的文件名
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
最后使用编辑器打开地图工程,在资源管理栏下点击折叠的行为包文件夹,在右侧的窗口预览区域导入functions文件夹即可。
|
||||
|
||||

|
||||
|
||||
## 使用minecraft:can_destroy定制带有破坏特定方块能力的农具
|
||||
|
||||
在开启冒险模式后,玩家虽然无法任意放置方块与破坏方块,但玩法地图里依然需要为玩家提供必要的农具,以便收集资源点的素材,以及当玩家不慎踩到耕地时,还能够拥有一把锄头将耕地复原回去。在give指令里添加minecraft:can_destroy标签可以让使用者在冒险模式下依旧有挖掘特定方块的能力:
|
||||
|
||||
```
|
||||
/give @s stone_pickaxe 1 0 {"minecraft:can_destroy": {"blocks":["log", "log2"]}}
|
||||
# 给予玩家一个满耐久度的石斧,blocks对应的数组支持多个自定义方块和原版方块,但自定义方块需要写全namespace:identifier格式的方块名称域。
|
||||
```
|
||||
|
||||
**原版的锄头会允许使用者无视等级权限,将全部的草类方块变为耕地**。为了防止玩家对于地形进行无节制的开垦,在下一个章节中,我们会开始学习如何使用Mod来阻止玩家做出这一行为。
|
||||
|
||||
## 增加工具食物箱
|
||||
|
||||
在地图编辑器内,我们选择选取模式,并保持单选模式。
|
||||
|
||||
<img src="./images/8.png" alt="8" style="zoom:200%;" />
|
||||
|
||||
接着点选地面,并拖动Y轴将选格向上提升一格。
|
||||
|
||||

|
||||
|
||||
选择填充功能,对格子内的区域进行箱子方块填充。
|
||||
|
||||

|
||||
|
||||
我们需要提前记下起始坐标,这里以[72,66,81]为例。通过使用scoreboard指令新增一个chest计分项,我们可以设计一个每隔1200帧往箱子内添加农具的指令集。即每一帧都会给chest计数递增1,通过目标选择器的scores参数来判断玩家的chest分数是否达到1200,以执行一次填充工具食物到箱子里的动作。最后再将玩家分数重置为0,等待下一个周期后再执行重复的工作。详细指令如下:
|
||||
|
||||
```
|
||||
scoreboard objectives add chest dummy "箱子"
|
||||
scoreboard players set @a[m=!2] chest 0
|
||||
execute @a[m=!2] ~~~ gamemode 2 @s
|
||||
scoreboard players add @a[m=2] chest 1
|
||||
execute @a[m=2,scores={chest=1200..}] ~~~ replaceitem block 72 66 81 slot.container 0 stone_axe 1 0 {"minecraft:can_destroy": {"blocks": ["log"]}}
|
||||
execute @a[m=2,scores={chest=1200..}] ~~~ replaceitem block 72 66 81 slot.container 1 stone_pickaxe 1 0 {"minecraft:can_destroy": {"blocks": ["stone"]}}
|
||||
execute @a[m=2,scores={chest=1200..}] ~~~ replaceitem block 72 66 81 slot.container 2 stone_hoe 1 0 {"minecraft:can_destroy": {"blocks": ["自定义植物方块名称"]}}
|
||||
execute @a[m=2,scores={chest=1200..}] ~~~ replaceitem block 72 66 81 slot.container 3 bread 10
|
||||
scoreboard players set @a[m=2,scores={chest=1200}] chest 0
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 为建筑增加内饰
|
||||
|
||||
玩法地图中会使用到的建筑模板都没有自带精装内饰。现在我们以服装店为例添加一部份内饰:
|
||||
|
||||
首先我们切换至地图编辑器,我们将功能切换为选取,并打开多选模式。
|
||||
|
||||
<img src="./images/6.png" alt="6" style="zoom:190%;" />
|
||||
|
||||
点选房梁与房顶间的空隙,并逐个往上拖。
|
||||
|
||||
|
||||

|
||||
|
||||
最后用灯作为材质预设,并选择填充功能对选取的区域进行统一填充。
|
||||
|
||||
|
||||

|
||||
|
||||
由于基岩版存在部分方块是需要用物品才能放置出来的。当前地图编辑器暂未支持放置物品与方块处于分离状态的方块类型。我们通过点击地图编辑器右上方的**游戏模式**退出自由摄像状态,就能够打开背包并在世界里直接放置方块。这里以放置一张床为例:
|
||||
|
||||
|
||||

|
||||
546
docs/mconline/20-玩法地图教程/第04章:发挥创意构建场景/课程03.规划可轻微变动区域.md
Normal file
@@ -0,0 +1,546 @@
|
||||
---
|
||||
front: https://nie.res.netease.com/r/pic/20210730/ee109f39-8987-46e0-9fe7-40ebb23060fa.png
|
||||
hard: 进阶
|
||||
time: 70分钟
|
||||
---
|
||||
|
||||
# 规划可轻微变动区域
|
||||
|
||||
小岛上的农场之家会为玩家提供一处开辟好的农田供玩家种植庄稼,同时地图还会涉及多个供玩家采集木头、石头、的资源点。它们可以帮助玩家有足够的材料修复农场设施。在上一章节中,我们为玩家提供了一个固定刷新的工具食物箱。借助原版游戏的玩法系统,我们初步为玩家营造了一个可持续生产的生产环境。但这里也同时存在一些问题:
|
||||
|
||||
- 锄头可以无视等级权限开垦所有泥土。
|
||||
- 木头、石头资源点被玩家挖空后无法再生。
|
||||
- 玩家需要通过回收资源解锁农场之家和畜牧场。
|
||||
|
||||
为了解决以上问题,使用ModSDK框架可以帮助我们非常迅速地解决以上问题。让我们来看看怎么做吧!
|
||||
|
||||
<iframe src="https://cc.163.com/act/m/daily/iframeplayer/?id=6152b923b8a81f8fa07dc899" height="600" width="800" allow="fullscreen" />
|
||||
|
||||
## 接入Mod环境
|
||||
|
||||
编辑器会在地图工程内新建Mod脚本文件夹,文件夹会以**Script_NeteaseMod**加上一串随机字符组成。
|
||||
|
||||
<img src="./images/11.png" alt="11" style="zoom:135%;" />
|
||||
|
||||
> - modMain.py文件是该Mod脚本的主入口,一个脚本文件夹的主目录下有且只能有一个入口文件,我们也只能在这里注册自定义的服务端与客户端系统。
|
||||
> - 自定义系统是个类,在没有学习到Python编程中面向对象的环节前,您可以简单理解为自创一个系统入口。这个入口会依据跑在服务端还是客户端的环境下,被挂接到由@Mod.InitServer()或Mod.InitClient()装饰的函数里。在客户端上运行则使用clientApi.RegisterSystem接口,在服务端上运行则使用serverApi.RegisterSystem接口。
|
||||
> - RegisterSystem接受3个参数,第一个参数是该Mod的唯一通信标识符。第二个是自定义系统名称。第三个是自定义系统类的路径。唯一通信标识符和自定义系统名称建议使用可读性强、令人印象深刻且独一无二的名字,这会帮助游戏引擎能够更好地分辨出来自不同Mod的各种系统入口。使用重复性高的名字可能会导致脚本引擎在注册自定义系统时无法辨识来源而造成Mod加载失败。路径参数会合并文件路径与系统类的名字,以下图代码为例:若系统类在py文件里用**Main**作名称,系统文件以**blockListene**r为py文件名,并且它还被包裹在Mod主目录的server文件夹内。则路径会以**Script_Netease{随机字符串}.server.blockListener.Main**来排序。
|
||||
|
||||
```python
|
||||
# -*- coding: UTF-8 -*-
|
||||
from mod.common.mod import Mod
|
||||
import mod.server.extraServerApi as serverApi
|
||||
import mod.client.extraClientApi as clientApi
|
||||
|
||||
|
||||
@Mod.Binding(name="NeteaseModw7ijjGNn", version="0.1")
|
||||
class NeteaseModw7ijjGNn(object):
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@Mod.InitClient()
|
||||
def NeteaseModw7ijjGNnClientInit(self):
|
||||
# type: () -> None
|
||||
"""Mod被挂载时,在这里注册自定义MOD客户端系统"""
|
||||
pass
|
||||
|
||||
@Mod.InitServer()
|
||||
def NeteaseModw7ijjGNnServerInit(self):
|
||||
# type: () -> None
|
||||
"""Mod被挂载时,在这里注册自定义MOD服务端系统"""
|
||||
serverApi.RegisterSystem("FarmMod", "ServerBlockListenerServer",
|
||||
"Script_NeteaseModw7ijjGNn.server.blockListener.Main")
|
||||
pass
|
||||
|
||||
@Mod.DestroyClient()
|
||||
def NeteaseModw7ijjGNnClientDestroy(self):
|
||||
# type: () -> None
|
||||
"""Mod被卸下时,销毁自定义MOD客户端系统"""
|
||||
pass
|
||||
|
||||
@Mod.DestroyServer()
|
||||
def NeteaseModw7ijjGNnServerDestroy(self):
|
||||
# type: () -> None
|
||||
"""Mod被卸下时,销毁自定义MOD客户端系统"""
|
||||
pass
|
||||
```
|
||||
|
||||
> - 自定义系统内可以监听原版事件和自定义Mod事件。
|
||||
> - 根据服务端与客户端的区别 ,我们在<a href="../../../mcdocs/1-ModAPI/事件/世界.html" rel="noopenner"> 模组SDK文档 </a>用相应的原版事件来定义一个回调函数。回调函数内会返回这个事件传递的数据信息,通过对数据的提取、类型判断、创建接口,可以实现丰富的玩法逻辑。
|
||||
> - 在自定义系统类里,我们也可以将常用的代码块用函数封装,实现更高的开发效率。
|
||||
|
||||
## 阻止玩家对其他区域做出耕地行为
|
||||
|
||||
在地形大观里,一共有三种方块会被锄头耕耘,它们分别是**草地**、**泥土**和**土径**。其中**泥土**是允许玩家还耕的方块类型。
|
||||
|
||||
我们使用**ServerBlockUseEvent**事件来监听玩家交互方块的行为,并在玩家手持石锄时阻止他们进一步操作。
|
||||
|
||||

|
||||
|
||||
为了阻止玩家能够任意翻弄草地和土径,我们需要将他们加入监听方块被交互的白名单里,并在事件中取消石锄与方块的交互行为。完整代码如下:
|
||||
|
||||
```python
|
||||
# -*- coding: UTF-8 -*-
|
||||
from mod.server.system.serverSystem import ServerSystem
|
||||
from mod.common.minecraftEnum import ItemPosType
|
||||
import mod.server.extraServerApi as serverApi
|
||||
|
||||
# 自定义Mod服务端系统类
|
||||
class Main(ServerSystem):
|
||||
|
||||
def __init__(self, namespace, system_name):
|
||||
# 继承父类
|
||||
ServerSystem.__init__(self, namespace, system_name)
|
||||
namespace = serverApi.GetEngineNamespace()
|
||||
system_name = serverApi.GetEngineSystemName()
|
||||
# 监听交互方块事件
|
||||
self.ListenForEvent(namespace, system_name,
|
||||
'ServerBlockUseEvent', self, self.using_item)
|
||||
# 根据文档描述,原版方块需要通过添加进交互方块的白名单内才能触发ServerBlockUseEvent
|
||||
block_comp = serverApi.GetEngineCompFactory().CreateBlockUseEventWhiteList(serverApi.GetLevelId())
|
||||
# 在地图的方块结构里,一共受到锄头影响的两种地形方块是
|
||||
self.blocked_list = ["minecraft:grass", "minecraft:grass_path"]
|
||||
for block_name in self.blocked_list:
|
||||
# 加入白名单
|
||||
block_comp.AddBlockItemListenForUseEvent(block_name)
|
||||
|
||||
# 交互方块事件
|
||||
def using_item(self, event):
|
||||
# 获取玩家ID
|
||||
player_id = event['playerId']
|
||||
# 创建玩家的物品接口
|
||||
item_comp = serverApi.GetEngineCompFactory().CreateItem(player_id)
|
||||
# 获取玩家手持物品信息
|
||||
carried_item = item_comp.GetPlayerItem(ItemPosType.CARRIED, 0)
|
||||
# 获取事件里交互的方块类型
|
||||
block_name = event['blockName']
|
||||
# 判断方块类型是否是土径或草地,并判断玩家手持物品是否是石锄
|
||||
if carried_item and carried_item['newItemName'] == 'minecraft:stone_hoe' and block_name in self.blocked_list:
|
||||
# 取消交互
|
||||
event['cancel'] = True
|
||||
```
|
||||
|
||||
最后,我们将编辑器界面切换至地图编辑器,通过游戏模式功能进入内嵌的游戏环境。现在使用锄头无法耕耘泥土和土径,但玩家还能将泥土还原成耕地!
|
||||
|
||||

|
||||
|
||||
## 循环可再生的资源点
|
||||
|
||||
获取金币是玩家解锁更多家具、衣服、扩大农业规模的重要途径。直接的经济来源来自玩家进行农业生产活动的收益。但农产品的成熟存在着一个客观地生长周期,作物会随着游戏随机刻的递进而提升生长阶段,这可能会使玩家觉得时间过得很枯燥。因此,加入木头与石材的资源点设定,是一种改变玩家游戏节奏的方式。我们为玩家提供额外的金币获取渠道的同时,也让他们能够更加有充实感地利用时间。
|
||||
|
||||
首先是使用地图编辑器预制资源区域。点击笔刷功能。
|
||||
|
||||

|
||||
|
||||
若**笔刷预设面板**处于折叠状态,可以点击与其他面板的连接区域进行拉伸。这里我们使用**圆预设**,默认使用高度5,长度5,宽度5的大小。
|
||||
|
||||

|
||||
|
||||
在**混合设置**里对**笔刷**的立体区域方块进行成分设置。点击**添加成分**按钮,将设定调整为笔刷形状会混合50%石头和50%木头。
|
||||
|
||||

|
||||
|
||||
在教程里,我们只设置5个资源点,并用选取工具资源点的最左下角的坐标记录下来。最后再通过保存结构的方式将资源点保存至本地行为包内,并使用ModSDK定时重置它们。
|
||||
|
||||
> **结构**与**素材**的区别在于:前者是我的世界基岩版的通用格式,而后者是MCSTUDIO所保存的模板格式。开发者和玩家可以通过分享结构,在游戏里或者使用ModSDK生成出来。而素材大多情况下是围绕着MCSTUDIO去使用的。但两者功能都是将地图建筑作为模板,方便我们在改变场景方块的时候能够直接调用它们。
|
||||
|
||||
```python
|
||||
# -*- coding: UTF-8 -*-
|
||||
from mod.server.system.serverSystem import ServerSystem
|
||||
from mod.common.minecraftEnum import ItemPosType
|
||||
import mod.server.extraServerApi as serverApi
|
||||
|
||||
|
||||
class Main(ServerSystem):
|
||||
|
||||
def __init__(self, namespace, system_name):
|
||||
# 继承父类
|
||||
ServerSystem.__init__(self, namespace, system_name)
|
||||
namespace = serverApi.GetEngineNamespace()
|
||||
system_name = serverApi.GetEngineSystemName()
|
||||
# 监听交互方块事件
|
||||
# ...........
|
||||
self.resources_pos = [
|
||||
# 资源点1的起始坐标
|
||||
(73, 64, 57),
|
||||
# 资源点2的起始坐标
|
||||
(51, 63, 101),
|
||||
# 资源点3的起始坐标
|
||||
(82, 68, 136),
|
||||
# 资源点4的起始坐标
|
||||
(198, 65, 102),
|
||||
# 资源点5的起始坐标
|
||||
(82, 68, 136)
|
||||
]
|
||||
# 结构名称,以行为包根目录/structures内的[文件夹名称:结构名称]为格式
|
||||
self.resource_identifier = 'design:resource'
|
||||
# 添加一个60秒重置资源点的定时任务
|
||||
game_comp = serverApi.GetEngineCompFactory().CreateGame(serverApi.GetLevelId())
|
||||
game_comp.AddRepeatedTimer(60.0, self.resource_placed)
|
||||
|
||||
# 交互方块事件
|
||||
def using_item(self, event):
|
||||
# .....
|
||||
pass
|
||||
|
||||
def resource_placed(self):
|
||||
# 创建放置结构的接口
|
||||
game_comp = serverApi.GetEngineCompFactory().CreateGame(serverApi.GetLevelId())
|
||||
for pos in self.resources_pos:
|
||||
# 放置资源点结构
|
||||
game_comp.PlaceStructure(None, pos, self.resource_identifier)
|
||||
|
||||
```
|
||||
|
||||

|
||||
|
||||
## 加入农场之家和畜牧场的修复方案
|
||||
|
||||
首先,在海面上拖出完整的农场之家和畜牧场建筑模板,并将它们保存成结构并去除空气方块。之后使用Delete快捷键直接删除。这么做可以最大程度减少临时建筑对地形造成的影响(如吃掉部分方块,把草地退回成泥土)。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
接着,我们使用地图编辑器,在两个待修复的独立建筑旁利用游戏模式放置木牌,为木牌写上单独的文字提示。
|
||||
|
||||

|
||||
|
||||
接下来我们就可以用ModSDK来监听玩家点击木牌,根据玩家的资源储量和木牌内容来决定是否有条件修复相应建筑!以下是详细代码与注释:
|
||||
|
||||
```python
|
||||
# -*- coding: UTF-8 -*-
|
||||
from mod.server.system.serverSystem import ServerSystem
|
||||
from mod.common.minecraftEnum import ItemPosType
|
||||
import mod.server.extraServerApi as serverApi
|
||||
|
||||
|
||||
class Main(ServerSystem):
|
||||
|
||||
def __init__(self, namespace, system_name):
|
||||
# 继承父类
|
||||
ServerSystem.__init__(self, namespace, system_name)
|
||||
namespace = serverApi.GetEngineNamespace()
|
||||
system_name = serverApi.GetEngineSystemName()
|
||||
# 监听交互方块事件
|
||||
self.ListenForEvent(namespace, system_name,
|
||||
'ServerBlockUseEvent', self, self.using_item)
|
||||
# 根据文档描述,原版方块需要通过添加进交互方块的白名单内才能触发ServerBlockUseEvent
|
||||
block_comp = serverApi.GetEngineCompFactory().CreateBlockUseEventWhiteList(serverApi.GetLevelId())
|
||||
# 在地图的方块结构里,一共受到锄头影响的两种地形方块是
|
||||
self.blocked_list = ["minecraft:grass", "minecraft:grass_path"]
|
||||
for block_name in self.blocked_list:
|
||||
# 加入白名单
|
||||
block_comp.AddBlockItemListenForUseEvent(block_name)
|
||||
# 非常重要!告示牌的方块实体ID是minecraft:standing_sign而不是minecraft:sign
|
||||
block_comp.AddBlockItemListenForUseEvent("minecraft:standing_sign:*")
|
||||
# 储存资源点坐标
|
||||
self.resources_pos = [
|
||||
(73, 64, 57),
|
||||
(51, 63, 101),
|
||||
(82, 68, 136),
|
||||
(198, 65, 102),
|
||||
(82, 68, 136)
|
||||
]
|
||||
# 结构名称
|
||||
self.resource_identifier = 'design:resource'
|
||||
# 添加一个60秒重置资源点的定时任务
|
||||
game_comp = serverApi.GetEngineCompFactory().CreateGame(serverApi.GetLevelId())
|
||||
game_comp.AddRepeatedTimer(60.0, self.resource_placed)
|
||||
|
||||
# 交互方块事件
|
||||
def using_item(self, event):
|
||||
# 获取玩家ID
|
||||
player_id = event['playerId']
|
||||
# 创建玩家的物品接口
|
||||
item_comp = serverApi.GetEngineCompFactory().CreateItem(player_id)
|
||||
# 获取玩家手持物品信息
|
||||
# .....
|
||||
# 获取事件里交互的方块类型
|
||||
block_name = event['blockName']
|
||||
x = event['x']
|
||||
y = event['y']
|
||||
z = event['z']
|
||||
# 判断方块类型是否是土径或草地,并判断玩家手持物品是否是石锄
|
||||
# ......
|
||||
# 判断是否是木牌
|
||||
if block_name == 'minecraft:standing_sign':
|
||||
# 创建方块信息接口
|
||||
block_comp = serverApi.GetEngineCompFactory().CreateBlockInfo(player_id)
|
||||
# 获取文告示牌字
|
||||
text = block_comp.GetSignBlockText((x, y, z))
|
||||
if '升级小屋' not in text:
|
||||
return
|
||||
# 木头数量
|
||||
log_count = 10
|
||||
# 石头数量
|
||||
stone_count = 5
|
||||
# 获取玩家背包的全部物品
|
||||
item_dict_list = item_comp.GetPlayerAllItems(ItemPosType.INVENTORY)
|
||||
# 通过枚举列表内的信息,遍历列表下标与物品信息
|
||||
for index, item_dict in enumerate(item_dict_list):
|
||||
# 如果物品是木头且剩余量大于0
|
||||
if item_dict and item_dict['itemName'] == 'minecraft:log' and log_count > 0:
|
||||
# 将木头数量赋值一个临时变量
|
||||
temp = item_dict['count']
|
||||
# 该槽的木头数量扣去余额数量
|
||||
temp -= log_count
|
||||
# 如果该槽的木头数量不足以吃掉全部的木头剩余量
|
||||
if temp < 0:
|
||||
# 设置该槽的木头数量为0,即代表该槽为空
|
||||
item_dict['count'] = 0
|
||||
# 扣去临时贮存的木头数量
|
||||
log_count -= temp
|
||||
# 直接跳过后面代码进入下一次循环
|
||||
continue
|
||||
# 否则,扣除对应槽位的木头数量
|
||||
item_dict['count'] = temp
|
||||
# 清零木头所需剩余量
|
||||
log_count = 0
|
||||
# 如果物品是石头且剩余量大于0
|
||||
if item_dict and item_dict['itemName'] == 'minecraft:stone' and stone_count > 0:
|
||||
# 将木头数量赋值一个临时变量
|
||||
temp = item_dict['count']
|
||||
# 该槽的石头数量扣去余额数量
|
||||
temp -= log_count
|
||||
# 如果该槽的石头数量不足以吃掉全部的木头剩余量
|
||||
if temp < 0:
|
||||
# 扣去临时贮存的木头数量
|
||||
item_dict['count'] = 0
|
||||
# 扣去临时贮存的石头数量
|
||||
stone_count -= temp
|
||||
# 直接跳过后面代码进入下一次循环
|
||||
continue
|
||||
# 否则,扣除对应槽位的石头数量
|
||||
item_dict['count'] = temp
|
||||
# 清零石头所需剩余量
|
||||
stone_count = 0
|
||||
# 如果log_count非0且stone_count非0,则放置修复的农场之家,并将木牌清除
|
||||
if not log_count and not stone_count:
|
||||
"""
|
||||
使用字典推导式,下方等价于
|
||||
item_dict_map = {}
|
||||
for index in range(len(item_dict_list)):
|
||||
item_dict_map[(ItemPosType.INVENTORY, index)] = item_dict_list[index]
|
||||
"""
|
||||
item_dict_map = {(ItemPosType.INVENTORY, index): item_dict_list[index] for index in range(len(item_dict_list))}
|
||||
# 设置玩家的全部槽内物品
|
||||
item_comp.SetPlayerAllItems(item_dict_map)
|
||||
game_comp = serverApi.GetEngineCompFactory().CreateGame(serverApi.GetLevelId())
|
||||
# 放置家
|
||||
game_comp.PlaceStructure(None, (76, 66, 80), 'design:home')
|
||||
# 清除木牌
|
||||
block_comp.SetBlockNew((x, y, z), {
|
||||
'name': 'minecraft:air'
|
||||
}, 0)
|
||||
|
||||
def resource_placed(self):
|
||||
# 创建放置结构的接口
|
||||
# ...
|
||||
pass
|
||||
|
||||
```
|
||||
|
||||
可以看到,同样在下次判断玩家点击升级方块时是否满足升级要钱时,如果将判定代码块用函数封装起来,可以使得代码更加简洁,减少重复代码。这里我们用函数对一部分代码进行封装。
|
||||
|
||||
```python
|
||||
# -*- coding: UTF-8 -*-
|
||||
from mod.server.system.serverSystem import ServerSystem
|
||||
from mod.common.minecraftEnum import ItemPosType
|
||||
import mod.server.extraServerApi as serverApi
|
||||
|
||||
|
||||
class Main(ServerSystem):
|
||||
|
||||
def __init__(self, namespace, system_name):
|
||||
# ....
|
||||
pass
|
||||
|
||||
# 交互方块事件
|
||||
def using_item(self, event):
|
||||
# .....
|
||||
pass
|
||||
|
||||
def resource_placed(self):
|
||||
# ...
|
||||
pass
|
||||
|
||||
def can_upgrade_structure(self, player_id, requirement):
|
||||
# type: (str, dict) -> (bool, list)
|
||||
"""
|
||||
:param player_id: 玩家ID
|
||||
:param requirement: 物品需求,例->{"minecraft:log": 10, "minecraft:stone": 5}
|
||||
:return (bool, list): 是否可以升级建筑,玩家背包信息
|
||||
"""
|
||||
# 创建玩家的物品接口
|
||||
item_comp = serverApi.GetEngineCompFactory().CreateItem(player_id)
|
||||
# 获取玩家背包的所有物品
|
||||
item_dict_list = item_comp.GetPlayerAllItems(ItemPosType.INVENTORY)
|
||||
# 通过枚举列表内的信息,遍历列表下标与物品信息
|
||||
for index, item_dict in enumerate(item_dict_list):
|
||||
# 如果该槽存在物品且物品在所需物品字典内,并且所需物品对应的数量大于0时
|
||||
if item_dict and item_dict['itemName'] in requirement and requirement[item_dict['itemName']] > 0:
|
||||
temp = item_dict['count']
|
||||
# 该槽物品数量扣去所需物品剩余数量
|
||||
temp -= requirement[item_dict['itemName']]
|
||||
# 如果该槽的物品数量不足以吃掉所需物品剩余数量
|
||||
if temp < 0:
|
||||
# 设置该槽的物品数量为0,即代表该槽为空
|
||||
item_dict['count'] = 0
|
||||
# 扣去临时贮存的物品数量
|
||||
requirement[item_dict['itemName']] -= temp
|
||||
# 直接跳过后面代码进入下一次循环
|
||||
continue
|
||||
# 否则,扣除对应槽位的物品数量
|
||||
item_dict['count'] = temp
|
||||
# 清零所需物品
|
||||
requirement[item_dict['itemName']] = 0
|
||||
# 返回是否满足升级条件,以及清零所需物品后的背包情况
|
||||
return not all(requirement.values()), item_dict_list
|
||||
|
||||
```
|
||||
|
||||
最后附上完整代码:
|
||||
|
||||
```python
|
||||
# -*- coding: UTF-8 -*-
|
||||
from mod.server.system.serverSystem import ServerSystem
|
||||
from mod.common.minecraftEnum import ItemPosType
|
||||
import mod.server.extraServerApi as serverApi
|
||||
|
||||
|
||||
class Main(ServerSystem):
|
||||
|
||||
def __init__(self, namespace, system_name):
|
||||
# 继承父类
|
||||
ServerSystem.__init__(self, namespace, system_name)
|
||||
namespace = serverApi.GetEngineNamespace()
|
||||
system_name = serverApi.GetEngineSystemName()
|
||||
# 监听交互方块事件
|
||||
self.ListenForEvent(namespace, system_name,
|
||||
'ServerBlockUseEvent', self, self.using_item)
|
||||
# 根据文档描述,原版方块需要通过添加进交互方块的白名单内才能触发ServerBlockUseEvent
|
||||
block_comp = serverApi.GetEngineCompFactory().CreateBlockUseEventWhiteList(serverApi.GetLevelId())
|
||||
# 在地图的方块结构里,一共受到锄头影响的两种地形方块是
|
||||
self.blocked_list = ["minecraft:grass", "minecraft:grass_path"]
|
||||
for block_name in self.blocked_list:
|
||||
# 加入白名单
|
||||
block_comp.AddBlockItemListenForUseEvent(block_name)
|
||||
# 非常重要!告示牌的方块实体ID是minecraft:standing_sign而不是minecraft:sign
|
||||
block_comp.AddBlockItemListenForUseEvent('minecraft:standing_sign:*')
|
||||
# 储存资源点坐标
|
||||
self.resources_pos = [
|
||||
(73, 64, 57),
|
||||
(51, 63, 101),
|
||||
(82, 68, 136),
|
||||
(198, 65, 102),
|
||||
(82, 68, 136)
|
||||
]
|
||||
# 结构名称
|
||||
self.resource_identifier = 'design:resource'
|
||||
# 添加一个60秒重置资源点的定时任务
|
||||
game_comp = serverApi.GetEngineCompFactory().CreateGame(serverApi.GetLevelId())
|
||||
game_comp.AddRepeatedTimer(60.0, self.resource_placed)
|
||||
|
||||
# 交互方块事件
|
||||
def using_item(self, event):
|
||||
# 获取玩家ID
|
||||
player_id = event['playerId']
|
||||
# 创建玩家的物品接口
|
||||
item_comp = serverApi.GetEngineCompFactory().CreateItem(player_id)
|
||||
# 获取玩家手持物品信息
|
||||
carried_item = item_comp.GetPlayerItem(ItemPosType.CARRIED, 0, True)
|
||||
# 获取事件里交互的方块类型
|
||||
block_name = event['blockName']
|
||||
x = event['x']
|
||||
y = event['y']
|
||||
z = event['z']
|
||||
# 判断方块类型是否是土径或草地,并判断玩家手持物品是否是石锄
|
||||
if carried_item and carried_item['newItemName'] == 'minecraft:stone_hoe' and block_name in self.blocked_list:
|
||||
# 取消交互
|
||||
event['cancel'] = True
|
||||
# 判断是否是告示牌
|
||||
if block_name == 'minecraft:standing_sign':
|
||||
block_comp = serverApi.GetEngineCompFactory().CreateBlockInfo(player_id)
|
||||
text = block_comp.GetSignBlockText((x, y, z))
|
||||
# 需求列表
|
||||
requirement = {}
|
||||
# 结构名称
|
||||
structure_name = ''
|
||||
# 结构放置位置
|
||||
structure_pos = ()
|
||||
# 小屋升级方块坐标
|
||||
if '升级小屋' in text:
|
||||
requirement = {'minecraft:log': 10, 'minecraft:stone': 5}
|
||||
structure_name = 'design:home'
|
||||
structure_pos = (76, 66, 80)
|
||||
# 畜牧场升级方块坐标
|
||||
elif '升级畜牧场' in text:
|
||||
requirement = {'minecraft:log': 20, 'minecraft:stone': 10}
|
||||
structure_name = 'design:farm'
|
||||
structure_pos = (169, 66, 83)
|
||||
result, items = self.can_upgrade_structure(player_id, requirement)
|
||||
# 是否满足要求
|
||||
if result and structure_pos and structure_name and requirement:
|
||||
"""
|
||||
使用字典推导式,下方等价于
|
||||
item_dict_map = {}
|
||||
for index in range(len(item_dict_list)):
|
||||
item_dict_map[(ItemPosType.INVENTORY, index)] = item_dict_list[index]
|
||||
"""
|
||||
item_dict_map = {(ItemPosType.INVENTORY, index): items[index] for index in range(len(items))}
|
||||
item_comp = serverApi.GetEngineCompFactory().CreateItem(player_id)
|
||||
# 设置玩家的全部槽内物品
|
||||
item_comp.SetPlayerAllItems(item_dict_map)
|
||||
game_comp = serverApi.GetEngineCompFactory().CreateGame(serverApi.GetLevelId())
|
||||
# 放置家
|
||||
game_comp.PlaceStructure(None, structure_pos, structure_name)
|
||||
# 清除木牌
|
||||
block_comp.SetBlockNew((x, y, z), {
|
||||
'name': 'minecraft:air'
|
||||
}, 0, 0)
|
||||
|
||||
def resource_placed(self):
|
||||
# 创建放置结构的接口
|
||||
game_comp = serverApi.GetEngineCompFactory().CreateGame(serverApi.GetLevelId())
|
||||
for pos in self.resources_pos:
|
||||
# 放置资源点结构
|
||||
game_comp.PlaceStructure(None, pos, self.resource_identifier)
|
||||
|
||||
def can_upgrade_structure(self, player_id, requirement):
|
||||
# type: (str, dict) -> (bool, list)
|
||||
"""
|
||||
:param player_id: 玩家ID
|
||||
:param requirement: 物品需求,例->{"minecraft:log": 10, "minecraft:stone": 5}
|
||||
:return (bool, list): 是否可以升级建筑,玩家背包信息
|
||||
"""
|
||||
# 创建玩家的物品接口
|
||||
item_comp = serverApi.GetEngineCompFactory().CreateItem(player_id)
|
||||
# 获取玩家背包的所有物品
|
||||
item_dict_list = item_comp.GetPlayerAllItems(ItemPosType.INVENTORY)
|
||||
# 通过枚举列表内的信息,遍历列表下标与物品信息
|
||||
for index, item_dict in enumerate(item_dict_list):
|
||||
# 如果该槽存在物品且物品在所需物品字典内,并且所需物品对应的数量大于0时
|
||||
if item_dict and item_dict['itemName'] in requirement and requirement[item_dict['itemName']] > 0:
|
||||
temp = item_dict['count']
|
||||
# 该槽物品数量扣去所需物品剩余数量
|
||||
temp -= requirement[item_dict['itemName']]
|
||||
# 如果该槽的物品数量不足以吃掉所需物品剩余数量
|
||||
if temp < 0:
|
||||
# 设置该槽的物品数量为0,即代表该槽为空
|
||||
item_dict['count'] = 0
|
||||
# 扣去临时贮存的物品数量
|
||||
requirement[item_dict['itemName']] -= temp
|
||||
# 直接跳过后面代码进入下一次循环
|
||||
continue
|
||||
# 否则,扣除对应槽位的物品数量
|
||||
item_dict['count'] = temp
|
||||
# 清零所需物品
|
||||
requirement[item_dict['itemName']] = 0
|
||||
# 返回是否满足升级条件,以及清零所需物品后的背包情况
|
||||
return not all(requirement.values()), item_dict_list
|
||||
|
||||
```
|
||||
|
||||

|
||||