--- front: https://nie.res.netease.com/r/pic/20220408/0c3f2c46-fb70-4102-820b-3ed513469dc5.png hard: 进阶 time: 60分钟 selection: true --- # 使用自定义数据记录数据 《海滨小岛》有三个决定游戏进度的数据:钱数、家具数量和游戏天数;我们需要针对这三种数据进行监听和存储,所以我们需要使用特定的接口来存储、获取这三种数据,并利用UI将数据可视化,使玩家可以实时看到数据的变化。 在新手引导结束后,统一存储数据: ```python leveldatacomp = serverApi.GetEngineCompFactory().CreateExtraData(serverApi.GetLevelId()) class FarmServerSystem(ServerSystem): def __init__(self, namespace, systemName): ServerSystem.__init__(self, namespace, systemName) #新手引导结束,开始游戏触发的函数 def start_game_data(self,playerid): # 存储硬币初始数量 player_data_coin = leveldatacomp.SetExtraData("player_coin",0) # 存储家具初始数量 player_data_furniture = leveldatacomp.SetExtraData("player_furniture",0) # 存储初始游戏天数 player_data_day = leveldatacomp.SetExtraData("player_day",0) # 存储玩家id player_data_id = leveldatacomp.SetExtraData("player_id",playerid) ``` ## 编写追踪玩家交易行为的功能 我们需要根据玩家“得到钱”和“花费钱”这两个情况下实时变更数据,所以在玩家和NPC交易时添加通信事件修改数据: NPC交易相关的教程内容在[第五章](../第05章:设置NPC的基本状态和交易表/课程01.自定义NPC的基本行为.md) ```python leveldatacomp = serverApi.GetEngineCompFactory().CreateExtraData(serverApi.GetLevelId()) class FarmServerSystem(ServerSystem): def __init__(self, namespace, systemName): ServerSystem.__init__(self, namespace, systemName) # 监听事件PlayerAttackEntityEvent self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "PlayerAttackEntityEvent", self,self.PlayerAttack) # 监听由客户端传来的事件(玩家购买商品) self.ListenForEvent("FarmMod", "ClientSystem", "buy_item", self,self.PlayerBuyItem) # 提前获取到的商人id self.animal_shop_id = "-120259084268" # 点击NPC时触发 def PlayerAttack(self,args): # 玩家id self.playername = args["playerId"] # 点击的NPCid entityid = args["victimId"] # 如果点击的npc和存储的商人id一致 if entityid == self.animal_shop_id: # 存储需要传输的参数:玩家id、商人id、获取钱数 event = {"playerid":args["playerId"] , "entityid" : entityid, "player_coin": leveldatacomp.GetExtraData("player_coin")} # 发送事件到客户端 self.NotifyToClient(self.playername,"create_shop_ui", event) # 玩家点击交易表购买按钮并成功购买后触发的函数 def PlayerBuyItem(self, args): # 通过传过来的参数保存交易后剩下的钱数 leveldatacomp.SetExtraData("player_coin",args["coin"]) #传过来的玩家id player_id = args['playerid'] #传过来的实际商品名称 item_name = args['buy_item'] # 发放物品 serverApi.GetEngineCompFactory().CreateItem(player_id).SpawnItemToPlayerInv( { 'newItemName': item_name, 'count': 1 }, player_id ) ``` ```python class FarmClientSystem(ClientSystem): def __init__(self, namespace, systemName): super(FarmClientSystem, self).__init__(namespace, systemName) # 监听由ServerSystem发送的事件 self.ListenForEvent("FarmMod", "ServerSystem", "create_shop_ui",self, self.Create_Shop_UI) # 提前获取到的商人id self.animal_shop_id = "-120259084268" def Create_Shop_UI(self,event): if event["entityid"] == self.animal_shop_id: # 创建交易表UI self.ui = clientApi.PushScreen("Farm","new_shop") # 通过UI实例修改变量(商品) self.ui.item_button_text = self.animal_shop_item_button_text # 将事件传送过来的玩家钱数设置到ui里 self.ui.coin = event["player_coin"] ``` ```python class FarmUIScreen(ScreenNode): def __init__(self, namespace, name, param): ScreenNode.__init__(self, namespace, name, param) self.coin = 0 #玩家钱数的变量 # 按钮绑定 @ViewBinder.binding(ViewBinder.BF_ButtonClickUp) def buy_button_clicked(self,args): if self.clicked_button_index == -1: print "玩家还没选物品" return # 获取商品价格 price = self.item_button_text[self.clicked_button_index]['coin'] # 如果玩家钱数大于等于商品价格 → 相减 → 传送事件至服务端 if self.coin >= price: self.coin -= price #向服务端通信,将玩家id,剩余钱数,以及购买的实际物品作为参数传送过去 self.clientsystem.NotifyToServer("buy_item",{"playerid":clientApi.GetLocalPlayerId(),"coin":self.coin,"buy_item" :self.item_button_text[self.clicked_button_index]["itemname"]}) else: print "你买不起" ``` 绝大部分的功能逻辑都在第五章制作交易表的时候介绍过,只需要在此基础上添加一些获取和存储数据的接口即可;当然,只进不出是不行的,我们还需要制作能够获得钱的功能 **“回收商人会在傍晚回收箱子内的物品并获得钱”** : ```json { "format_version": "1.13.0", "minecraft:entity": { "description": { "identifier": "farm:acquirer", //回收商人 "is_spawnable": true, "is_summonable": true, "is_experimental": false, "runtime_identifier": "minecraft:villager" }, "components": { "minecraft:scheduler": { //在特定时间触发事件 "min_delay_secs": 0, "max_delay_secs": 10, "scheduled_events" : [ { "filters": { "all_of": [ { "test": "hourly_clock_time", "operator": ">=", "value": 12000 }, { "test": "hourly_clock_time", "operator": "<", "value": 13000 } ] }, "event": "minecraft:work" //触发的事件 } ] } }, "component_groups": { }, "events": { "minecraft:work":{ } } } } ``` 给商人的行为文件添加特定时间触发事件的组件,利用MODSDK监听该商人在触发事件的时候执行功能逻辑: ```python leveldatacomp = serverApi.GetEngineCompFactory().CreateExtraData(serverApi.GetLevelId()) class FarmServerSystem(ServerSystem): def __init__(self, namespace, systemName): ServerSystem.__init__(self, namespace, systemName) # 监听事件EntityDefinitionsEventServerEvent self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(),'EntityDefinitionsEventServerEvent', self, self.Player_Event) # 提前获取的回收商人id self.acquirer_id = "-983547510779" def Player_Event(self,args): # 如果触发事件的生物id是回收商人 if args["entityId"] == self.acquirer_id: # 使用寻路接口让回收商人走到箱子旁 movecomp = serverApi.GetEngineCompFactory().CreateMoveTo(self.acquirer_id) movecomp.SetMoveSetting((72,66,82), 1.5, 2000, self.acquirer_callback) # 寻路结束的回调函数 def acquirer_callback(self,entityid ,result): # 如果回收商人到达地点 if result == 0: # 所有可回收商品的名称和价格 goods_list = {"farm:spinach_item":8,"farm:whiteradish_item":4,"farm:peas_item":3, "farm:lemon_item":6,"farm:eggplant_item":4,"farm:crown_dasiy_item":4, "farm:corn_item":6,"farm:banana_item":6,"farm:bambooshoot_item":6, "minecraft:log":2,"minecraft:cobblestone":2,"minecraft:egg":8, "minecraft:wool":15,"minecraft:leather":15} # 创建接口 chestitemcomp = serverApi.GetEngineCompFactory().CreateItem(serverApi.GetLevelId()) chestslotcomp = serverApi.GetEngineCompFactory().CreateChestBlock(serverApi.GetLevelId()) # 创建变量(箱子的槽数和最后结算的价格) count = 0 add_price = 0 # 循环0-26,对应小箱子的槽数 for item in range(0,27): # 获取当前槽位的物品信息字典 itemdict = chestitemcomp.GetContainerItem((72,66,81), count, 0) # 如果有东西 if itemdict: # 如果该槽位的物品名在商品列表中 if itemdict["newItemName"] in goods_list: # 物品数量*物品的价格 += 到变量中 add_price += itemdict["count"] * goods_list.get(itemdict["newItemName"]) # 设置该槽位的物品数量为0 chestslotcomp.SetChestBoxItemNum(None, (72,66,81), count, 0, 0) # 箱子的槽数+=1 count += 1 # 获取当前玩家的钱数 now_coin = leveldatacomp.GetExtraData("player_coin") # 把当前的钱数和上面结算的最终钱数相加再存储 leveldatacomp.SetExtraData("player_coin",now_coin + add_price) # 使用寻路接口让回收商人再回到家中 movecomp = serverApi.GetEngineCompFactory().CreateMoveTo(self.acquirer_id) movecomp.SetMoveSetting((132,71,96), 1.5, 2000, self.acquirer_callback_home) def acquirer_callback_home(self,entityid,result): print result ``` ## 编写追踪玩家生存天数的功能 在玩家结束新手引导正式开始游戏时可以在函数中重置一下游戏的时间: ```python class FarmServerSystem(ServerSystem): def __init__(self, namespace, systemName): ServerSystem.__init__(self, namespace, systemName) # 新手引导结束,开始游戏触发的函数 def start_game_data(self,playerid): # 创建时间的接口 timecomp = serverApi.GetEngineCompFactory().CreateTime(serverApi.GetLevelId()) # 设置世界时间为0 timecomp.SetTime(0) ``` 获取当前世界的时间可以直接获取Molang或者使用相关接口: ```python # 通过query.day获取当前世界的天数 clientApi.GetEngineCompFactory().CreateQueryVariable(clientApi.GetLocalPlayerId()).GetMolangValue('query.day') #或 # 创建时间的接口 comp = serverApi.GetEngineCompFactory().CreateTime(levelId) # 获取当前的时间 passedTime = comp.GetTime() # 获取当前的天数 day = passedTime / 24000 ``` ## 编写追踪玩家持有家具数量的功能 所有的家具都是由自定义方块制作的,所以我们只需要监听玩家放置和破坏方块这两个事件即可完成对家具数量的追踪: ```python class FarmServerSystem(ServerSystem): def __init__(self, namespace, systemName): ServerSystem.__init__(self, namespace, systemName) # 监听EntityPlaceBlockAfterServerEvent事件 self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), 'EntityPlaceBlockAfterServerEvent', self, self.Place_Furniture) # 监听ServerPlayerTryDestroyBlockEvent事件 self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), 'ServerPlayerTryDestroyBlockEvent', self, self.Destroy_Furniture) # 使用中国版自定义方块方法制作的家具列表 self.netease_block_list = ["farm:connect_table", "farm:sofa_black", "farm:sofa_blue", "farm:sofa_brown", "farm:sofa_cyan", "farm:sofa_gray", "farm:sofa_light_blue", "farm:sofa_light_cyan", "farm:sofa_light_green", "farm:sofa_light_gray", "farm:sofa_orange", "farm:sofa_pink_red", "farm:sofa_purple", "farm:sofa_red", "farm:sofa_white", "farm:sofa_yellow"] # 放置方块时触发 def Place_Furniture(self, args): # 通过事件获取的方块坐标、名称 x = args['x'] y = args['y'] z = args['z'] blockname = args['fullName'] # 创建方块状态接口 blockstatecomp = serverApi.GetEngineCompFactory().CreateBlockState(serverApi.GetLevelId()) # 获取放置的方块状态 blockstate = blockstatecomp.GetBlockStates((x, y, z), 0) # 如果farm:rotation不在blockstate并且方块名称不在列表中就返回(所有的家具都有farm:rotation属性,所以可以用这个来判断是不是家具) # 不过通过中国版自定义方块的方法制作的家具并没有属性,所以需要额外给这些家具创建一个列表用于判断条件 # 当然也可以将所有的家具都放在一个列表里进行判断 if "farm:rotation" not in blockstate and blockname not in self.netease_block_list: return # 如果是家具的话就会继续执行,这时将家具的数据+=1 leveldatacomp.SetExtraData("player_furniture", leveldatacomp.GetExtraData("player_furniture") + 1) # 破坏方块时触发 def Destroy_Furniture(self, args): # 通过事件获取的方块坐标、名称 x = args['x'] y = args['y'] z = args['z'] blockname = args['fullName'] player_id = args['playerId'] # 创建方块状态接口 blockstatecomp = serverApi.GetEngineCompFactory().CreateBlockState(serverApi.GetLevelId()) # 获取放置的方块状态 blockstate = blockstatecomp.GetBlockStates((x, y, z), 0) # 下方的逻辑和放置家具时一致,不过是将数据从+=1改为-+1 if "farm:rotation" not in blockstate and blockname not in self.netease_block_list: return leveldatacomp.SetExtraData("player_furniture", leveldatacomp.GetExtraData("player_furniture") - 1) event = {"player_data_furniture": leveldatacomp.GetExtraData("player_furniture")} self.NotifyToClient(leveldatacomp.GetExtraData("player_id"), "re_dataui", event) ```