# 开始添加家具方块
除了农作物外,我们还需要继续制作家具,也是地图中重要的玩法之一;同样使用新自定义方块的方法,不过需要额外给不同的家具添加不同的功能。
# 学会设置方块的转向
当我们制作自定义方块的时候,如果方块模型的四个方向都是一样的,那方块在什么方向放置就不重要了;但如果是家具方块,如沙发,当玩家放下的时候,沙发的靠背朝前那就不太合适了,所以我们需要设置方块的转向,使每次放置都能保证方块的正面是适合的;
我们需要在方块中添加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,才能对应上方的方块属性
}
}
}
}
}
# 制作可以拼在一起的桌子
这里需要用到中国版自定义模型方块的写法,微软的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": [···]
},
使用上面的方法依次将东南西北四个方向的挡板都添加,点击编辑器的开发测试进入游戏,就可以从下方看到当桌子连接起来的时候,侧面的挡板消失了;
相比桌子的挡板,桌角并不是在某一侧,而是两个方向的夹角,所以只判断某一个方向的连接是不行的,需要两个方向都有桌子进行链接的时候才消失:
{
"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": [···]
},
把所有的桌脚都设置好后,进入游戏是这样的
# 制作带有开关的装饰方块
开关可以添加方块属性来进行判断,给台灯和电视都添加一个属性,值为[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()
电视和台灯同理,只需要简单修改一些判断条件即可:
{
"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()
现在,我们已经有了一个较完整的现代床,在编辑器中点击开发测试进入到游戏中试一下:
# 制作可以回收道具的垃圾桶
这个功能只需要使用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 #取消手中物品的使用
# 制作可以榨取果汁的榨汁机
榨汁机工作需要用到我们收获的农作物:柠檬、菠菜、玉米,分别对应:柠檬汁、菠菜汁和玉米汁
给榨汁机添加多个属性分别代表 “判断榨汁机运行”、“榨玉米汁“、”榨柠檬汁“、”榨菠菜汁“;然后添加对应的组合,通过事件来进行调控,不过仅用自定义方块的组件内容无法制作全部的功能,我们还需要使用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和自定义方块组件的相互配合,完成这个榨汁机的全部功能,点击编辑器的开发测试进入到游戏中测试一下:
# 制作可以坐的方块
可以坐下的椅子和前面的现代床实现的方法差不多,并且没有那么的复杂,我们只需要传送玩家到椅子上并且播放坐下的动画即可,不过我们可以添加更多有意思的功能,比如在玩家坐下的时候可以每三秒回复玩家的半格血量:
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)
椅子的逻辑相对简单,我们点击编辑器的开发测试,进入到游戏中测试一下:
# 制作其它装饰方块
接下来我们继续填充装饰性的方块家具:马克杯、床头柜、水龙头、熊玩偶(纯装饰),马桶、沙发、浴缸(重复功能)。纯装饰的家具方块只需要添加属性使其可以跟随玩家视角旋转即可,一些重复功能的家具只需要利用上方的逻辑进行简单的修改即可。
马桶模仿椅子的逻辑,在判断方块的部分修改为马桶方块即可:
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)
到此,所以的家具都已经制作完成,用它们来装饰地图吧:
https://nie.res.netease.com/r/pic/20210730/ee109f39-8987-46e0-9fe7-40ebb23060fa.png
进阶
80分钟