# 开始添加家具方块

除了农作物外,我们还需要继续制作家具,也是地图中重要的玩法之一;同样使用新自定义方块的方法,不过需要额外给不同的家具添加不同的功能。

3

# 学会设置方块的转向

当我们制作自定义方块的时候,如果方块模型的四个方向都是一样的,那方块在什么方向放置就不重要了;但如果是家具方块,如沙发,当玩家放下的时候,沙发的靠背朝前那就不太合适了,所以我们需要设置方块的转向,使每次放置都能保证方块的正面是适合的;

我们需要在方块中添加rotation的属性[0,1,2,3]对应四个方向:东南西北。然后添加四个组合,利用**"minecraft:rotation"**组件对应这四个方向的旋转角度,并且在放置方块的时候获取玩家朝向的角度,然后根据角度的不同修改方块的rotation属性,以rotation属性为条件添加组合,代码如下:

{
    "format_version": "1.16.100",
	"minecraft:block": {
		"description": {
			"identifier": "farm:water_tap",
            "register_to_creative_menu": true,
            "properties": {
                "farm:rotation":[0,1,2,3] //旋转属性
			}

		},
        "permutations": [
            {
              "condition": "query.block_property('farm:rotation') == 0", //当下方获取到的玩家角度是2时(2-2=0)North
              "components": {
                "minecraft:rotation": [ //设置方块旋转角度
                  0,
                  180,
                  0
                ]
              }
            },
            {
              "condition": "query.block_property('farm:rotation') == 1", //当下方获取到的玩家角度是2时(3-2=1)South
              "components": {
                "minecraft:rotation": [ //设置方块旋转角度
                  0,
                  0,
                  0
                ]
              }
            },
            {
              "condition": "query.block_property('farm:rotation') == 2", //当下方获取到的玩家角度是2时(4-2=2)West
              "components": {
                "minecraft:rotation": [ //设置方块旋转角度
                  0,
                  270,
                  0
                ]
              }
            },
            {
              "condition": "query.block_property('farm:rotation') == 3", //当下方获取到的玩家角度是2时(5-2=3)East
              "components": {
                "minecraft:rotation": [ //设置方块旋转角度
                  0,
                  90,
                  0
                ]
              }
            }
          ],
        "components": {
			"minecraft:loot": "loot_tables/blocks/water_tap.json",
			"minecraft:explosion_resistance": 1,
            "minecraft:pick_collision": {
                "origin": [-8, 0, -8],
                "size": [16, 10, 16]
            },
            "minecraft:geometry": "geometry.water_tap",
            "minecraft:block_light_absorption": 0,
            "minecraft:material_instances": {
                 "*": {
                    "texture": "farm:water_tap",
                    "render_method": "alpha_test",
                    "ambient_occlusion": false
                 }
            },
            "minecraft:on_player_placing": { //玩家放置方块的时候触发事件
                "event": "farm:set_rotation"
              },
            "minecraft:entity_collision": {
                "origin": [-8, 0, -8],
                "size": [16, 10, 16]
            }
		},
        "events": {
            "farm:set_rotation": {
                "set_block_property": { //设置方块属性 
                  "farm:rotation": "query.cardinal_facing_2d-2" 
                    //使用表达式获取玩家的方向(North=2.0, South=3.0, West=4.0, East=5.0, Undefined=6.0)
                    //因为上方的方块属性是从0开始的,所以获取到的数字需要-2,才能对应上方的方块属性
                }
            }
        }

	}
}
24

# 制作可以拼在一起的桌子

这里需要用到中国版自定义模型方块的写法,微软的1.16+自定义方块写法暂时还无法制作此功能;认识中国版自定义方块

给桌子方块添加基本的组件后,还需要用到**"netease:connection"**用来定义这个方块的拼接属性。

{
  "format_version": "1.10.0",
  "minecraft:block": {
   "description": {
     "identifier": "farm:connect_table",
	 "register_to_creative_menu": true,
	 "is_experimental": false
   },
   "components": {
	  "minecraft:block_light_absorption":{
		"value": 0
	  },
	  "netease:render_layer": {
	    "value": "alpha"
	  },
	  "netease:solid": {
	    "value": false
	  },
	  "netease:connection": {
		"blocks": ["farm:connect_table"]
	  }
    }
  }
}

然后打开桌子的模型文件,我们需要进行修改以实现桌子拼接时,部分模型块会消失,如桌子四周的挡板:

{
    "cubes": [···], 
    "enable": "!query.is_connect(2)", //用表达式判断方块连接面的方向(连接面为北时启用这个部分的模型,因为前面加了!,所以为不启用这部分的模型)
    								  // 0-down面,1-up面,2-north面,3-south面,4-west面,5-east面
    "name": "n", //这个部分是桌子的背面挡板
    "parent": "table",
    "pivot": [···],
    "rotation": [···]
},

使用上面的方法依次将东南西北四个方向的挡板都添加,点击编辑器的开发测试进入游戏,就可以从下方看到当桌子连接起来的时候,侧面的挡板消失了;

25

26

相比桌子的挡板,桌角并不是在某一侧,而是两个方向的夹角,所以只判断某一个方向的连接是不行的,需要两个方向都有桌子进行链接的时候才消失:

{
    "cubes": [···],
    "enable": "!query.is_connect(5) && !query.is_connect(3)", //桌子的东面和南面都有桌子时这个部分的模型不启用
    														  // 0-down面,1-up面,2-north面,3-south面,4-west面,5-east面
    "name": "east_south", //东面和南面夹角的桌角
    "parent": "table",
    "pivot": [···],
    "rotation": [···]
},

把所有的桌脚都设置好后,进入游戏是这样的

27

# 制作带有开关的装饰方块

开关可以添加方块属性来进行判断,给台灯和电视都添加一个属性,值为[0,1],对应开和关,然后利用MODSDK来切换方块属性:

{
	"format_version": "1.16.100",
	"minecraft:block": {
		"description": {
			"identifier": "farm:acacia_table_lamp", //台灯
			"register_to_creative_menu": true,
			"properties": {
				"farm:open_light" : [0, 1] //添加属性用作开灯的判断
			}

		},
		"permutations": [
			{
				"condition": "query.block_property('farm:open_light') == 1", //当属性open_light为1时
				"components": {
					"minecraft:block_light_emission": 1 //方块发光

				}
			}
		],
		"components": {···},
		"events": {···}

	}
}
import time
class Main(ServerSystem):

    def __init__(self, namespace, system_name):
        ServerSystem.__init__(self, namespace, system_name)
        self.interact_cooldown = {} #创建一个时间戳变量,用于控制玩家交互方块的冷却时间,以免造成极短时间内多次交互的情况
        self.ListenForEvent(namespace, system_name,'ServerBlockUseEvent', self, self.using_item)

    def using_item(self, event):
        # 获取玩家ID
        player_id = event['playerId']
        # 获取事件里交互的方块类型
        block_name = event['blockName']
        # 创建获取方块信息的接口
        blockstatecomp = serverApi.GetEngineCompFactory().CreateBlockState(serverApi.GetLevelId())
        # 获取交互方块的坐标
        x = event['x']
        y = event['y']
        z = event['z']
		# 所有台灯的列表
        table_lamp_list = ["farm:acacia_table_lamp", "farm:bigoak_table_lamp", "farm:birch_table_lamp",
                           "farm:crimson_table_lamp", "farm:jungle_table_lamp", "farm:oak_table_lamp",
                           "farm:spruce_table_lamp", "farm:warped_table_lamp"]
		
        #如果交互的方块在台灯列表呢
        if block_name in table_lamp_list:
            #获取交互的方块信息
            state = blockstatecomp.GetBlockStates((x, y, z), 0)
            #如果玩家id不在时间戳变量内
            if player_id not in self.interact_cooldown:
                #给时间戳变量添加一个玩家id和时间戳的键值
                self.interact_cooldown[player_id] = time.time()
                #如果方块的属性open_light等于0,台灯还没有打开
                if state['farm:open_light'] == 0:
                    #修改台灯的open_light属性为1
                    state['farm:open_light'] = 1
                    #设置修改后的台灯属性
                    blockstatecomp.SetBlockStates((x, y, z), state, 0)
                #反之,方块属性open_light等于1,台灯如果已经时开启的了
                else:
                    #修改台灯的open_light属性为0
                    state['farm:open_light'] = 0
                    #设置修改后的台灯属性
                    blockstatecomp.SetBlockStates((x, y, z), state, 0)
                            
			#如果当前的时间减去存储在时间戳变量内的时间大于0.1
            elif time.time() - self.interact_cooldown[player_id] > 0.1:
                #逻辑与上方的if一致
                if state['farm:open_light'] == 0:
                    state['farm:open_light'] = 1
                    blockstatecomp.SetBlockStates((x, y, z), state, 0)
                else:
                    state['farm:open_light'] = 0
                    blockstatecomp.SetBlockStates((x, y, z), state, 0)
            #更新时间戳变量中的时间
            self.interact_cooldown[player_id] = time.time()
30

电视和台灯同理,只需要简单修改一些判断条件即可:

{
	"format_version": "1.16.100",
	"minecraft:block": {
		"description": {
			"identifier": "farm:logo_tv", //电视
			"register_to_creative_menu": true,
			"properties": {
				"farm:open_tv": [0,1] //添加属性用作开电视的判断
			}

		},
		"permutations": [
			{
				"condition": "query.block_property('farm:open_tv') == 1", //当属性open_tv为1时
				"components": {
					"minecraft:material_instances": { //修改方块的贴图
						"*": {
							"texture": "farm:logo_tv_open",
							"render_method": "alpha_test",
							"ambient_occlusion": false
						}
					}
				}
			}
		],
		"components": {···},
		"events": {···}

	}
}
class Main(ServerSystem):
    def __init__(self, namespace, system_name):
        ServerSystem.__init__(self, namespace, system_name)
        self.ListenForEvent(namespace, system_name,'ServerBlockUseEvent', self, self.using_item)
        
    def using_item(self, event):
        if block_name == "farm:logo_tv":
            state = blockstatecomp.GetBlockStates((x, y, z), 0)
            if player_id not in self.interact_cooldown:
                self.interact_cooldown[player_id] = time.time()
                print state
                if state['farm:open_tv'] == 0:
                    state['farm:open_tv'] = 1
                    blockstatecomp.SetBlockStates((x, y, z), state, 0)
                else:
                    state['farm:open_tv'] = 0
                    blockstatecomp.SetBlockStates((x, y, z), state, 0)
            elif time.time() - self.interact_cooldown[player_id] > 0.1:
                if state['farm:open_tv'] == 0:
                    state['farm:open_tv'] = 1
                    blockstatecomp.SetBlockStates((x, y, z), state, 0)
                else:
                    state['farm:open_tv'] = 0
                    blockstatecomp.SetBlockStates((x, y, z), state, 0)
            self.interact_cooldown[player_id] = time.time()

# 制作自定义床方块

自定义床方块比较复杂,因为床需要和史蒂夫一样长,也就是两格方块左右,但是自定义方块的碰撞箱最大就是16x16,所以需要两个方块拼接成一个床,我们需要使用modsdk来控制床的放置和破坏:

class Main(ServerSystem):
    def __init__(self, namespace, system_name):
        ServerSystem.__init__(self, namespace, system_name) # 监听玩家放置方块前后和破坏方块的事件
        self.ListenForEvent(namespace, system_name,'EntityPlaceBlockAfterServerEvent', self, self.place_block)
        self.ListenForEvent(namespace, system_name,'ServerPlayerTryDestroyBlockEvent', self, self.player_destroy_block
        self.ListenForEvent(namespace, system_name,'ServerEntityTryPlaceBlockEvent', self, self.try_place_block)

    # 玩家放置方块成功后触发
    def place_block(self, args):
        # 现代床的下半部分
        modern_bed_tail = {
            'name': 'farm:modern_bed_tail',
            'aux': 0
        }
        # 通过事件获取到放置的方块名、方块坐标、玩家id、放置方块的世界id、
        x = args["x"]
        y = args["y"]
        z = args["z"]
        blockname = args["fullName"]
        playerid = args["entityId"]
        worldid = args["dimensionId"]
        # 创建方块相关的接口
        blockstatecomp = serverApi.GetEngineCompFactory().CreateBlockState(serverApi.GetLevelId())
        blockcomp = serverApi.GetEngineCompFactory().CreateBlockInfo(serverApi.GetLevelId())
        # 如果放置的方块是farm:modern_bed_head(现代床的上半部分)
        if blockname == "farm:modern_bed_head":
            # 获取这个方块的属性
            block_state = blockstatecomp.GetBlockStates((x, y, z), worldid)
            # 通过坐标获取到这个方块东西南北的方块信息
            east_block_state = blockstatecomp.GetBlockStates((x + 1, y, z), worldid)
            west_block_state = blockstatecomp.GetBlockStates((x - 1, y, z), worldid)
            south_block_state = blockstatecomp.GetBlockStates((x, y, z + 1), worldid)
            north_block_state = blockstatecomp.GetBlockStates((x, y, z - 1), worldid)
		 	# 通过if判断上半部分的放置方向
            if block_state["farm:rotation"] == 0:
                # 在上半部分的方块方向的反方向放置下半部分方块
                blockcomp.SetBlockNew((x, y, z + 1), modern_bed_tail, 0, worldid)
                block_state['farm:rotation'] = 0
                blockstatecomp.SetBlockStates((x, y, z + 1), block_state, worldid)
            elif block_state["farm:rotation"] == 1:
                blockcomp.SetBlockNew((x, y, z - 1), modern_bed_tail, 0, worldid)
                block_state['farm:rotation'] = 1
                blockstatecomp.SetBlockStates((x, y, z - 1), block_state, worldid)
            elif block_state["farm:rotation"] == 2:
                blockcomp.SetBlockNew((x + 1, y, z), modern_bed_tail, 0, worldid)
                block_state['farm:rotation'] = 2
                blockstatecomp.SetBlockStates((x + 1, y, z), block_state, worldid)
            elif block_state["farm:rotation"] == 3:
                blockcomp.SetBlockNew((x - 1, y, z), modern_bed_tail, 0, worldid)
                block_state['farm:rotation'] = 3
                blockstatecomp.SetBlockStates((x - 1, y, z), block_state, worldid)

    # 玩家破坏方块时触发
    def player_destroy_block(self, args):
        # 方块信息字典(空气)
        air = {
            'name': 'minecraft:air',
            'aux': 0
        }
        # 方块列表(现代床的上半部和下半部分)
        block_list = ['farm:modern_bed_head', 'farm:modern_bed_tail']
        # 通过事件获取破坏的方块名称、坐标、玩家id和世界id
        blockname = args['fullName']
        x = args['x']
        y = args['y']
        z = args['z']
        blockname = args['fullName']
        playerid = args['playerId']
        worldid = args['dimensionId']
        # 创建方块相关的接口
        blockstatecomp = serverApi.GetEngineCompFactory().CreateBlockState(serverApi.GetLevelId())
        blockcomp = serverApi.GetEngineCompFactory().CreateBlockInfo(serverApi.GetLevelId())
		# 如果方块名是farm:modern_bed_head(现代床的上半部分)
        if blockname == "farm:modern_bed_head":
            # 获取这个方块的属性
            block_state = blockstatecomp.GetBlockStates((x, y, z), worldid)
            # 用if判断方块的旋转属性并同时破坏反方向一格的方块(反方向就是下半部分)
            if block_state['farm:rotation'] == 0:
                blockcomp.SetBlockNew((x, y, z + 1), air, 0, worldid)
            elif block_state['farm:rotation'] == 1:
                blockcomp.SetBlockNew((x, y, z - 1), air, 0, worldid)
            elif block_state['farm:rotation'] == 2:
                blockcomp.SetBlockNew((x + 1, y, z), air, 0, worldid)
            elif block_state['farm:rotation'] == 3:
                blockcomp.SetBlockNew((x - 1, y, z), air, 0, worldid)
        # 如果方块名是farm:modern_bed_tail(现代床的下半部分)
        elif blockname == "farm:modern_bed_tail":
            # 获取这个方块的属性           
            block_state = blockstatecomp.GetBlockStates((x, y, z), worldid)
            # 用if判断方块的旋转属性并同时破坏反方向一格的方块(反方向就是上半部分)
            if block_state['farm:rotation'] == 0:
                blockcomp.SetBlockNew((x, y, z - 1), air, 0, worldid)
            elif block_state['farm:rotation'] == 1:
                blockcomp.SetBlockNew((x, y, z + 1), air, 0, worldid)
            elif block_state['farm:rotation'] == 2:
                blockcomp.SetBlockNew((x - 1, y, z), air, 0, worldid)
            elif block_state['farm:rotation'] == 3:
                blockcomp.SetBlockNew((x + 1, y, z), air, 0, worldid)
    
    # 玩家尝试放置方块时触发
    def try_place_block(self,args):
        # 通过事件获取方块的坐标、名称、玩家id、世界id
        x = args["x"]
        y = args["y"]
        z = args["z"]
        blockname = args["fullName"]
        playerid = args["entityId"]
        worldid = args["dimensionId"]
        # 创建方块相关接口
        blockinfocomp = serverApi.GetEngineCompFactory().CreateBlockInfo(serverApi.GetLevelId())
        # 获取getplayerrot的返回值
        playerrot = self.getplayerrot(playerid)
        # 获取消息相关的接口
        msgcomp = serverApi.GetEngineCompFactory().CreateMsg(playerid)
        # 如果放置的方块时farm:modern_bed_head(现代床的上半部分)
        if blockname == "farm:modern_bed_head":
            # 如果玩家的视角是2
            if playerrot == 2:
                # 获取视角方向的方块信息字典
                south_block = blockinfocomp.GetBlockNew((x,y,z+1), worldid)
                # 如果方块的名字不是空气(说明有方块阻挡)
                if south_block['name'] != "minecraft:air":
                    # 取消放置方块
                    args['cancel'] = True
                    # 给尝试放置的玩家提醒
                    msgcomp.NotifyOneMessage(playerid, "附近有方块遮挡,无法放置方块", "§c")
            # 下面的if分支同上,只是获取的玩家视角角度不同
            elif playerrot == 3:
                west_block = blockinfocomp.GetBlockNew((x - 1,y,z), worldid)
                if west_block['name'] != "minecraft:air":
                    args['cancel'] = True
                    msgcomp.NotifyOneMessage(playerid, "附近有方块遮挡,无法放置方块", "§c")
            elif playerrot == 0:
                north_block = blockinfocomp.GetBlockNew((x,y,z-1), worldid)
                if north_block['name'] != "minecraft:air":
                    args['cancel'] = True
                    msgcomp.NotifyOneMessage(playerid, "附近有方块遮挡,无法放置方块", "§c")
            elif playerrot == 1:
                east_block = blockinfocomp.GetBlockNew((x+1,y,z), worldid)
                if east_block['name'] != "minecraft:air":
                    args['cancel'] = True
                    msgcomp.NotifyOneMessage(playerid, "附近有方块遮挡,无法放置方块", "§c")

    # 获取玩家视角角度的函数
    def getplayerrot(self,playerid):
        # 获取玩家角度
        rot = serverApi.GetEngineCompFactory().CreateRot(playerid).GetRot()
        # 根据不同角度返回不同的数字用于判断
        if 135.0 < rot[1] <= 180.0:
            return 2
        elif 90.0 < rot[1] <= 135.0:
            return 1
        elif 45.0 < rot[1] <= 90.0:
            return 1
        elif 0.0 < rot[1] <= 45.0:
            return 0
        elif -45.0 < rot[1] <= 0.0:
            return 0
        elif -90.0 < rot[1] <= -45.0:
            return 3
        elif -135.0 < rot[1] <= -90.0:
            return 3
        elif -180.0 < rot[1] <= -135.0:
            return 2
        else:
            return 0

床方块的放置和破坏逻辑写完后,我们还需要对其进行交互功能相关的逻辑,使玩家可以设置出生点并睡在床上跳过一段时间:

class Main(ServerSystem):

    def __init__(self, namespace, system_name):
        ServerSystem.__init__(self, namespace, system_name)
        # 监听方块交互时间
        self.ListenForEvent(namespace, system_name,'ServerBlockUseEvent', self, self.using_item)
        # 时间戳变量
        self.interact_cooldown = {}
        
    
    def using_item(self, event):
        # 创建 计时器接口
        timercomp = serverApi.GetEngineCompFactory().CreateGame(serverApi.GetLevelId())
        # 创建玩家行为的接口
        playercomp = serverApi.GetEngineCompFactory().CreatePlayer(player_id)
        # 创建获取方块信息的接口
        blockstatecomp = serverApi.GetEngineCompFactory().CreateBlockState(serverApi.GetLevelId())
        # 创建使用指令的接口
        commandcomp = serverApi.GetEngineCompFactory().CreateCommand(serverApi.GetLevelId())
        # 如果交互的方块是现代床
        if block_name == "farm:modern_bed_head":
            # 获取方块属性
            state = blockstatecomp.GetBlockStates((x, y, z), 0)
            # 存储一些需要的参数
            camera_event = {'state': state, 'x': x, 'y': y, 'z': z, 'playerid': player_id}
            # 设置玩家重生点
            commandcomp.SetCommand("spawnpoint @s ~ ~ ~")
            # 如果方块的旋转属性为2(判断方块的放置方向并且根据方向设置玩家的视角)
            if state['farm:rotation'] == 2:
                # 使用指令播放玩家睡觉的动画
                commandcomp.SetCommand("playanimation @s east_sleep")
                # 使用指令将玩家传送到床上
                commandcomp.SetCommand("tp @s {} {} {}".format(x,y+0.5,z))
                # 设置玩家无法移动
                playercomp.SetPlayerMovable(False)
                # 传输事件到客户端,锁定玩家视角到固定位置
                self.NotifyToClient(player_id, "camera_lock", camera_event)
                # 添加一个4秒的计时器
                timercomp.AddTimer(4.0,self.standup,player_id,)
            #同上if,只是判断不同的旋转角度播放不同方向的玩家动画和视角
            elif state['farm:rotation'] == 0:
                commandcomp.SetCommand("tp @s {} {} {}".format(x,y+0.5,z))
                commandcomp.SetCommand("playanimation @s west_sleep humanoid_base_pose 0")
                playercomp.SetPlayerMovable(False)
                self.NotifyToClient(player_id, "camera_lock", camera_event)
                timercomp.AddTimer(4.0,self.standup,player_id,)
            elif state['farm:rotation'] == 1:
                commandcomp.SetCommand("tp @s {} {} {}".format(x,y+0.5,z))
                commandcomp.SetCommand("playanimation @s north_sleep humanoid_base_pose 0")
                playercomp.SetPlayerMovable(False)
                self.NotifyToClient(player_id, "camera_lock", camera_event)
                timercomp.AddTimer(4.0,self.standup,player_id,)
            elif state['farm:rotation'] == 3:
                commandcomp.SetCommand("tp @s {} {} {}".format(x,y+0.5,z))
                commandcomp.SetCommand("playanimation @s south_sleep humanoid_base_pose 0")
                playercomp.SetPlayerMovable(False)
                self.NotifyToClient(player_id, "camera_lock", camera_event)
                timercomp.AddTimer(4.0,self.standup,player_id,)
    
    # 计时器触发的函数
    def standup(self,player_id):
        # 获取相关接口
        timecomp = serverApi.GetEngineCompFactory().CreateTime(serverApi.GetLevelId())
        playercomp = serverApi.GetEngineCompFactory().CreatePlayer(player_id)
        # 设置玩家可以移动
        playercomp.SetPlayerMovable(True)
        # 从游戏开始经过的总帧数
        passedTime = timecomp.GetTime()
        # 当前游戏天内的帧数
        timeOfDay = passedTime % 24000
        # 从游戏开始经过的游戏天数
        day = passedTime / 24000
        # 使用时间戳,避免多次执行的情况
        if player_id not in self.interact_cooldown:
            self.interact_cooldown[player_id] = time.time()
            # 根据当前时间+12000
            timecomp.SetTime(passedTime + 12000)
            print day
        elif time.time() - self.interact_cooldown[player_id] > 1:
            timecomp.SetTime(passedTime + 12000)
            self.interact_cooldown[player_id] = time.time()
            print day
class FarmClientSystem(ClientSystem):
    def __init__(self, namespace, systemName):
        super(FarmClientSystem, self).__init__(namespace, systemName)
		# 监听由服务端传输过来的事件
        self.ListenForEvent("FarmMod", "ServerBlockListenerServer", "camera_lock",self, self.Camera_Lock)
        
    def Camera_Lock(self,event):
        # 获取传输过来的参数:方块的坐标和属性
        x = event['x']
        y = event['y']
        z = event['z']
        state = event['state']
        # 判断方块的旋转属性
        if state['farm:rotation'] == 0:
            # 锁定玩家视角到特定的位置
            cameracomp.LockCamera((x,y+1,z), (-40, 0))
            # 添加一个4s的计时器
            timercomp.AddTimer(4.0, self.Camera_UnLock, event, )
        elif state['farm:rotation'] == 2:
            cameracomp.LockCamera((x,y+1,z), (-40, -90))
            timercomp.AddTimer(4.0, self.Camera_UnLock, event, )
        elif state['farm:rotation'] == 3:
            cameracomp.LockCamera((x,y+1,z), (-40, -270))
            timercomp.AddTimer(4.0, self.Camera_UnLock, event, )
        elif state['farm:rotation'] == 1:
            cameracomp.LockCamera((x,y+1,z), (-40, -180))
            timercomp.AddTimer(4.0, self.Camera_UnLock, event, )

	# 计时器触发的函数
    def Camera_UnLock(self,event):
        # 解锁玩家的视角
        cameracomp.UnLockCamera()
        

现在,我们已经有了一个较完整的现代床,在编辑器中点击开发测试进入到游戏中试一下:

31

# 制作可以回收道具的垃圾桶

这个功能只需要使用MODSDK即可做到,不需要对自定方块的组件进行复杂修改,代码如下:

class Main(ServerSystem):
    def __init__(self, namespace, system_name):
        ServerSystem.__init__(self, namespace, system_name)
        self.ListenForEvent(namespace, system_name,
                            'ServerItemUseOnEvent', self, self.using_block) #监听ServerItemUseOnEvent事件
        
        
    def using_block(self,args):
        trash_can_list = ['farm:acacia_trash_can', 'farm:bigoak_trash_can', 'farm:birch_trash_can',
                          'farm:crimson_trash_can', 'farm:jungle_trash_can', 'farm:oak_trash_can',
                          'farm:spruce_trash_can', 'farm:warped_trash_can'] #所有的垃圾桶方块
        playerid = args['entityId'] #事件中获取到的玩家id
        blockname = args['blockName'] #事件中获取到的玩家交互方块的identifier
        itemname = args['itemDict'] #事件中获取到的物品信息字典
        item_comp = serverApi.GetEngineCompFactory().CreateItem(playerid) #创建CreateItem接口
        
        if blockname in trash_can_list and itemname: #如果交互的方块在trash_can_list的列表中并且玩家手里有物品
            args['ret'] = True #取消手中物品的使用(这个可以防止玩家手里的物品或方块被放置到地上从而无法清除)
            carried_item = item_comp.GetPlayerItem(ItemPosType.CARRIED, 0, True) #获取玩家手中的物品信息
            carried_item['count'] = 0 #将获取到的物品的数量设置为0
            item_comp.SetPlayerAllItems({(ItemPosType.CARRIED, 0): carried_item}) #重新设置玩家手里的物品


在ServerSystem中只需要监听ServerItemUseOnEvent事件,并且添加一些简单的逻辑即可,不过这个事件建议在客户端同时监听并取消物品的使用,所以在ClientSystem中也需要一些代码:

class FarmClientSystem(ClientSystem):

    def __init__(self, namespace, systemName):
        super(FarmClientSystem, self).__init__(namespace, systemName)
        self.ListenForEvent(namespace, system_name,
                            'ClientItemUseOnEvent', self, self.using_item) #监听事件
        
    def using_item(self,args):
        trash_can_list = ['farm:acacia_trash_can', 'farm:bigoak_trash_can', 'farm:birch_trash_can',
                          'farm:crimson_trash_can', 'farm:jungle_trash_can', 'farm:oak_trash_can',
                          'farm:spruce_trash_can', 'farm:warped_trash_can'] #所有的垃圾桶方块
        blockname = args['blockName'] #事件中获取到的玩家交互方块的identifier
        itemname = args['itemDict'] #事件中获取到的物品信息字典
        if blockname in trash_can_list and itemname: #如果交互的方块在trash_can_list的列表中并且玩家手里有物品
            args['ret'] = True #取消手中物品的使用
            
29

# 制作可以榨取果汁的榨汁机

榨汁机工作需要用到我们收获的农作物:柠檬、菠菜、玉米,分别对应:柠檬汁、菠菜汁和玉米汁

给榨汁机添加多个属性分别代表 “判断榨汁机运行”、“榨玉米汁“、”榨柠檬汁“、”榨菠菜汁“;然后添加对应的组合,通过事件来进行调控,不过仅用自定义方块的组件内容无法制作全部的功能,我们还需要使用MODSDK;

{
	"format_version": "1.16.100",
	"minecraft:block": {
		"description": {
			"identifier": "farm:juicer",
			"register_to_creative_menu": true,
			"properties": {
				"farm:run":[0,1], //判断榨汁机有没有在运行中
				"farm:juicer_has_corn":[0,1,2], //榨玉米汁的过程
				"farm:juicer_has_lemon":[0,1,2], //榨柠檬汁的过程
				"farm:juicer_has_spinach":[0,1,2] //榨菠菜汁的过程
			}

		},
		"permutations": [
			{
				"condition": "query.block_property('farm:juicer_has_corn') == 1", //满足条件,添加下方组件(此时榨汁机在榨汁中:玉米)
				"components": {
					"minecraft:material_instances": { //修改榨汁机的贴图
						"*": {
							"texture": "farm:juicer_corn",
							"render_method": "alpha_test",
							"ambient_occlusion": false
						}
					},
					"minecraft:ticking": { //添加计时器,3秒后运行下方事件(不重复运行)
						"range": [3.0, 3.0],
						"looping": false,
						"on_tick": {
							"event": "farm:corn_juice_start",
							"target": "self"
						}
					}
				}
			},
			{
				"condition": "query.block_property('farm:juicer_has_corn') == 2", //满足条件,添加下方组件(此时榨汁机榨完了:玉米)
				"components": {
					"minecraft:material_instances": { //修改榨汁机贴图
						"*": {
							"texture": "farm:juicer_corn_bottle",
							"render_method": "alpha_test",
							"ambient_occlusion": false
						}
					}
				}
			},
			{
				"condition": "query.block_property('farm:juicer_has_spinach') == 1", //满足条件,添加下方组件(此时榨汁机在榨汁中:菠菜)
				"components": {
					"minecraft:material_instances": { //修改榨汁机贴图
						"*": {
							"texture": "farm:juicer_spinach",
							"render_method": "alpha_test",
							"ambient_occlusion": false
						}
					},
					"minecraft:ticking": { //添加计时器,3秒后运行下方事件(不重复运行)
						"range": [3.0, 3.0],
						"looping": false,
						"on_tick": {
							"event": "farm:spinach_juice_start",
							"target": "self"
						}
					}
				}
			},
			{
				"condition": "query.block_property('farm:juicer_has_spinach') == 2", //满足条件,添加下方组件(此时榨汁机榨完了:菠菜)
				"components": {
					"minecraft:material_instances": { //修改榨汁机贴图
						"*": {
							"texture": "farm:juicer_spinach_bottle",
							"render_method": "alpha_test",
							"ambient_occlusion": false
						}
					}
				}
			},
			{
				"condition": "query.block_property('farm:juicer_has_lemon') == 1", //满足条件,添加下方组件(此时榨汁机在榨汁中:柠檬)
				"components": {
					"minecraft:material_instances": { //修改榨汁机贴图
						"*": {
							"texture": "farm:juicer_lemon",
							"render_method": "alpha_test",
							"ambient_occlusion": false
						}
					},
					"minecraft:ticking": { //添加计时器,3秒后运行下方事件(不重复运行)
						"range": [3.0, 3.0],
						"looping": false,
						"on_tick": {
							"event": "farm:lemon_juice_start",
							"target": "self"
						}
					}
				}
			},
			{
				"condition": "query.block_property('farm:juicer_has_lemon') == 2", //满足条件,添加下方组件(此时榨汁机榨完了:柠檬)
				"components": {
					"minecraft:material_instances": { //修改榨汁机贴图
						"*": {
							"texture": "farm:juicer_lemon_bottle",
							"render_method": "alpha_test",
							"ambient_occlusion": false
						}
					}
				}
			}
		],
		"components": {
			"minecraft:loot": "loot_tables/blocks/juicer.json",
			"minecraft:explosion_resistance": 1,
			"minecraft:pick_collision": {
				"origin": [-8, 0, -8],
				"size": [16, 16, 16]
			},
			"minecraft:material_instances": { //榨汁机普通情况下的贴图
				"*": {
					"texture": "farm:juicer",
					"render_method": "alpha_test",
					"ambient_occlusion": false
				}
			},
			"minecraft:geometry": "geometry.juicer", //榨汁机的模型
			"minecraft:block_light_absorption": 0,
			"minecraft:entity_collision": {
				"origin": [-8, 0, -8],
				"size": [16, 16, 16]
			}
		},
		"events": {
			"farm:corn_juice_start":{ 
				"set_block_property": { //设置属性juicer_has_cron为2(从运行榨汁机到榨完汁)
					"farm:juicer_has_corn": 2
				}
			},
			"farm:spinach_juice_start":{ //设置属性juicer_has_spinach为2(从运行榨汁机到榨完汁)
				"set_block_property": {
					"farm:juicer_has_spinach": 2
				}
			},
			"farm:lemon_juice_start":{ //设置属性juicer_has_lemon为2(从运行榨汁机到榨完汁)
				"set_block_property": {
					"farm:juicer_has_lemon": 2
				}
			}
		}

	}
}

上面的自定义方块内容,可以实现榨汁的过程(3秒)并且根据状态切换榨汁机的贴图,开始榨汁并且拿走果汁的功能就需要MODSDK来实现:

class Main(ServerSystem):

    def __init__(self, namespace, system_name):
        ServerSystem.__init__(self, namespace, system_name)
        self.ListenForEvent(namespace, system_name,
                            'ServerBlockUseEvent', self, self.using_item)
        
    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']
        # 创建获取方块信息的接口
        blockstatecomp = serverApi.GetEngineCompFactory().CreateBlockState(serverApi.GetLevelId())
        # 创建生成掉落物的接口
        drop_comp = serverApi.GetEngineCompFactory().CreateItem(serverApi.GetLevelId())
        # 获取交互方块的坐标
        x = event['x']
        y = event['y']
        z = event['z']
        # 创建一个存储果汁物品信息的变量
        juice = [
            {
                'newItemName': 'farm:corn_juice',
                'count': 1,
                'newAuxValue': 0,
            },
            {
                'newItemName': 'farm:lemon_juice',
                'count': 1,
                'newAuxValue': 0,
            },
            {
                'newItemName': 'farm:spinach_juice',
                'count': 1,
                'newAuxValue': 0,
            }
        ]



        #如果交互的方块是榨汁机
        if block_name == "farm:juicer":
            #获取坐标位置方块的属性
            state = blockstatecomp.GetBlockStates((x, y, z), 0)
            #如果手里有东西
            if carried_item:

                #如果方块的属性 has_corn和run都为0 并且手里的是玉米
                if state['farm:juicer_has_corn'] == 0 and state['farm:run'] == 0 and carried_item['newItemName'] == "farm:corn_item":
                    #将has_corn和run改为1,并且将手里的玉米-1
                    state['farm:juicer_has_corn'] = 1
                    state['farm:run'] = 1
                    blockstatecomp.SetBlockStates((x, y, z), state, 0)
                    carried_item['count'] -= 1
                    item_comp.SetPlayerAllItems({(ItemPosType.CARRIED, 0): carried_item})

                #如果方块的属性 has_lemon和run都为0 并且手里的是柠檬
                elif state['farm:juicer_has_lemon'] == 0 and state['farm:run'] == 0 and carried_item['newItemName'] == "farm:lemon_item":
                    #将has_lemon和run改为1,并且将手里的柠檬-1
                    state['farm:juicer_has_lemon'] = 1
                    state['farm:run'] = 1
                    blockstatecomp.SetBlockStates((x, y, z), state, 0)
                    carried_item['count'] -= 1
                    item_comp.SetPlayerAllItems({(ItemPosType.CARRIED, 0): carried_item})

                #如果方块的属性 has_spinach和run都为0 并且手里的是菠菜
                elif state['farm:juicer_has_spinach'] == 0 and state['farm:run'] == 0 and carried_item['newItemName'] == "farm:spinach_item":
                    #将has_spinach和run改为1,并且将手里的菠菜-1
                    state['farm:juicer_has_spinach'] = 1
                    state['farm:run'] = 1
                    blockstatecomp.SetBlockStates((x, y, z), state, 0)
                    carried_item['count'] -= 1
                    item_comp.SetPlayerAllItems({(ItemPosType.CARRIED, 0): carried_item})

                #当手里有东西时并且榨玉米汁工作完
                elif state['farm:juicer_has_corn'] == 2:
                    #生成掉落物并且将has_corn和run恢复为0
                    drop_comp.SpawnItemToLevel(juice[0], 0, (x, y+1, z))
                    state['farm:juicer_has_corn'] = 0
                    state['farm:run'] = 0
                    blockstatecomp.SetBlockStates((x, y, z), state, 0)

                #当手里有东西时并且榨柠檬汁工作完
                elif state['farm:juicer_has_lemon'] == 2:
                    #生成掉落物并且将has_lemon和run恢复为0
                    drop_comp.SpawnItemToLevel(juice[1], 0, (x, y+1, z))
                    state['farm:juicer_has_lemon'] = 0
                    state['farm:run'] = 0
                    blockstatecomp.SetBlockStates((x, y, z), state, 0)

                #当手里有东西时并且榨菠菜汁工作完
                elif state['farm:juicer_has_spinach'] == 2:
                    #生成掉落物并且将has_spinach和run恢复为0
                    drop_comp.SpawnItemToLevel(juice[2], 0, (x, y+1, z))
                    state['farm:juicer_has_spinach'] = 0
                    state['farm:run'] = 0
                    blockstatecomp.SetBlockStates((x, y, z), state, 0)

            #如果手里没东西
            else:
                #如果榨玉米汁工作完
                if state['farm:juicer_has_corn'] == 2:
                    drop_comp.SpawnItemToLevel(juice[0], 0, (x, y+1, z))
                    state['farm:juicer_has_corn'] = 0
                    state['farm:run'] = 0
                    blockstatecomp.SetBlockStates((x, y, z), state, 0)

                # 如果榨柠檬汁工作完
                elif state['farm:juicer_has_lemon'] == 2:
                    #生成掉落物并且将has_lemon和run恢复为0
                    drop_comp.SpawnItemToLevel(juice[1], 0, (x, y+1, z))
                    state['farm:juicer_has_lemon'] = 0
                    state['farm:run'] = 0
                    blockstatecomp.SetBlockStates((x, y, z), state, 0)

                #如果榨菠菜汁工作完
                elif state['farm:juicer_has_spinach'] == 2:
                    #生成掉落物并且将has_spinach和run恢复为0
                    drop_comp.SpawnItemToLevel(juice[2], 0, (x, y+1, z))
                    state['farm:juicer_has_spinach'] = 0
                    state['farm:run'] = 0
                    blockstatecomp.SetBlockStates((x, y, z), state, 0)
        else:
            pass

MODSDK和自定义方块组件的相互配合,完成这个榨汁机的全部功能,点击编辑器的开发测试进入到游戏中测试一下:

28

# 制作可以坐的方块

可以坐下的椅子和前面的现代床实现的方法差不多,并且没有那么的复杂,我们只需要传送玩家到椅子上并且播放坐下的动画即可,不过我们可以添加更多有意思的功能,比如在玩家坐下的时候可以每三秒回复玩家的半格血量:

class Main(ServerSystem):

    def __init__(self, namespace, system_name):
        ServerSystem.__init__(self, namespace, system_name)
        # 判断坐下的变量
        self.sit = 0
        # 时间戳变量
        self.interact_cooldown = {}
        # 监听方块交互的事件
        self.ListenForEvent(namespace, system_name,'ServerBlockUseEvent', self, self.using_item)
        
    def using_item(self, event):
        # 创建 计时器接口
        timercomp = serverApi.GetEngineCompFactory().CreateGame(serverApi.GetLevelId())
        # 创建使用指令的接口
        commandcomp = serverApi.GetEngineCompFactory().CreateCommand(serverApi.GetLevelId())
        # 创建使用指令的接口
        commandcomp = serverApi.GetEngineCompFactory().CreateCommand(serverApi.GetLevelId())
        # 事件获取的方块名称和坐标
        block_name = event['blockName']
        x = event['x']
        y = event['y']
        z = event['z']
        # 所以椅子名称的列表
        chair_list = ["farm:acacia_chair","farm:bigoak_chair","farm:birch_chair","farm:crimson_chair",
                      "farm:jungle_chair","farm:oak_chair","farm:spruce_chair","farm:warped_chair"]
        
        # 如果交互的方块名称给列表内
        if block_name in chair_list:
            # 通过时间戳避免短时间内多次交互的情况
            if player_id not in self.interact_cooldown:
                self.interact_cooldown[player_id] = time.time()
                # 如果sit为0(说明玩家未坐下)
                if self.sit == 0:
                    # 使用指令播放玩家坐下的动画
                    commandcomp.SetCommand("playanimation @s sit_chair")
                    # 传送玩家到椅子上
                    commandcomp.SetCommand("tp @s {} {} {}".format(x,y-0.1,z))
                    # 设置玩家无法移动
                    playercomp.SetPlayerMovable(False)
                    # 创建消息接口并提醒玩家再次点击可以站起
                    msgcomp = serverApi.GetEngineCompFactory().CreateMsg(player_id)
                    msgcomp.NotifyOneMessage(player_id, "再次点击椅子以站起", "§f")
                    # 创建一个3秒的循环计时器
                    self.healtimer = timercomp.AddRepeatedTimer(3.0,self.player_heal,player_id)
                    # 修改坐下的变量为1
                    self.sit = 1
                # 如果变量为1(此时玩家在坐着)
                elif self.sit == 1:
                    # 设置玩家可以移动
                    playercomp.SetPlayerMovable(True)
                    # 取消循环的计时器
                    timercomp.CancelTimer(self.healtimer)
                    # 播放玩家普通
                    commandcomp.SetCommand("playanimation @s humanoid_base_pose")
                    # 修改坐下的变量为0
                    self.sit = 0
                    
            # 时间戳判断,与上方if逻辑一致
            elif time.time() - self.interact_cooldown[player_id] > 0.5:
                if self.sit == 0:
                    commandcomp.SetCommand("playanimation @s sit_chair")
                    commandcomp.SetCommand("tp @s {} {} {}".format(x,y-0.1,z))
                    playercomp.SetPlayerMovable(False)
                    msgcomp = serverApi.GetEngineCompFactory().CreateMsg(player_id)
                    msgcomp.NotifyOneMessage(player_id, "再次点击椅子以站起", "§f")
                    self.healtimer = timercomp.AddRepeatedTimer(3.0, self.player_heal, player_id)
                    self.sit = 1
                elif self.sit == 1:
                    playercomp.SetPlayerMovable(True)
                    timercomp.CancelTimer(self.healtimer)
                    commandcomp.SetCommand("playanimation @s humanoid_base_pose")
                    self.sit = 0
                self.interact_cooldown[player_id] = time.time()

    # 计时器循环触发的函数
    def player_heal(self,playerid):
        # 获取玩家属性的接口
        attcomp = serverApi.GetEngineCompFactory().CreateAttr(playerid)
        # 获取玩家当前的血量
        playerheal = attcomp.GetAttrValue(serverApi.GetMinecraftEnum().AttrType.HEALTH)
        # 设置玩家当前的血量并+1
        attcomp.SetAttrValue(serverApi.GetMinecraftEnum().AttrType.HEALTH, playerheal+1)
        

椅子的逻辑相对简单,我们点击编辑器的开发测试,进入到游戏中测试一下:

32

# 制作其它装饰方块

接下来我们继续填充装饰性的方块家具:马克杯、床头柜、水龙头、熊玩偶(纯装饰),马桶、沙发、浴缸(重复功能)。纯装饰的家具方块只需要添加属性使其可以跟随玩家视角旋转即可,一些重复功能的家具只需要利用上方的逻辑进行简单的修改即可。

马桶模仿椅子的逻辑,在判断方块的部分修改为马桶方块即可:

class Main(ServerSystem):
    def __init__(self, namespace, system_name):
        ServerSystem.__init__(self, namespace, system_name)
        self.ListenForEvent(namespace, system_name,'ServerBlockUseEvent', self, self.using_item)

    def using_item(self, event):
        # 如果交互的方块是farm:close_stool(马桶)
        if block_name == "farm:close_stool":
            if player_id not in self.interact_cooldown:
                self.interact_cooldown[player_id] = time.time()
                if self.sit == 0:
                    commandcomp.SetCommand("tp @s {} {} {}".format(x, y - 0.1, z))
                    commandcomp.SetCommand("playanimation @s sit_chair")
                    playercomp.SetPlayerMovable(False)
                    msgcomp = serverApi.GetEngineCompFactory().CreateMsg(player_id)
                    msgcomp.NotifyOneMessage(player_id, "再次点击马桶以站起", "§f")
                    self.sit = 1
                elif self.sit == 1:
                    playercomp.SetPlayerMovable(True)
                    commandcomp.SetCommand("playanimation @s humanoid_base_pose")
                    self.sit = 0
            elif time.time() - self.interact_cooldown[player_id] > 0.5:
                if self.sit == 0:
                    commandcomp.SetCommand("tp @s {} {} {}".format(x, y - 0.1, z))
                    commandcomp.SetCommand("playanimation @s sit_chair")
                    playercomp.SetPlayerMovable(False)
                    msgcomp = serverApi.GetEngineCompFactory().CreateMsg(player_id)
                    msgcomp.NotifyOneMessage(player_id, "再次点击马桶以站起", "§f")
                    self.sit = 1
                elif self.sit == 1:
                    playercomp.SetPlayerMovable(True)
                    commandcomp.SetCommand("playanimation @s humanoid_base_pose")
                    self.sit = 0
                self.interact_cooldown[player_id] = time.time()

沙发坐下的逻辑也与椅子、马桶相似,不过需要设置沙发在同行摆放的时候使其可以拼接,参照桌子的拼接方法,在沙发的两侧添加拼接:

{
    "cubes": [···],
    "enable": "!query.is_connect(4)",
    "name": "left",
    "parent": "sofa",
    "pivot": [···],
    "rotation": [···]
},
{
    "cubes": [···],
    "enable": "!query.is_connect(5)",
    "name": "right",
    "parent": "sofa",
    "pivot": [···],
    "rotation": [···]
}

浴缸的功能也是大同小异:放置和破坏同时对上半、下半部分生效,交互可以泡澡,每隔3秒增加玩家经验值。需要进行修改的也就只有方块的判断以及循环计时器的触发函数:

class Main(ServerSystem):
    def __init__(self, namespace, system_name):
        ServerSystem.__init__(self, namespace, system_name)
        self.ListenForEvent(namespace, system_name,'ServerBlockUseEvent', self, self.using_item)
        
    def using_item(self, event):
        # 如果交互的方块是浴缸上半部分或下半部分
        # 判断内的逻辑和现代床、椅子、沙发类似(传送玩家到方块位置、设置玩家无法移动、播放动画、添加循环触发的计时器)
        if block_name == "farm:bathtub_head" or block_name == "farm:bathtub_tail":
            # 循坏触发的计时器,每三秒触发一次player_exp函数
            # ···
            self.healtimer = timercomp.AddRepeatedTimer(3.0,self.player_exp,player_id,)
        
    def player_exp(self,playerid):
        # 创建玩家经验接口
        expcomp = serverApi.GetEngineCompFactory().CreateExp(playerid)
        # 给玩家添加10点经验
        expcomp.AddPlayerExperience(10)

到此,所以的家具都已经制作完成,用它们来装饰地图吧:

33

https://nie.res.netease.com/r/pic/20210730/ee109f39-8987-46e0-9fe7-40ebb23060fa.png

进阶

80分钟

学会设置方块的转向

制作可以拼在一起的桌子

制作带有开关的装饰方块

制作自定义床方块

制作可以回收道具的垃圾桶

制作可以榨取果汁的榨汁机

制作可以坐的方块

制作其它装饰方块