This commit is contained in:
boybook
2025-12-01 20:59:16 +08:00
parent 12738a142c
commit 760c2dd9ad
5535 changed files with 21070 additions and 2021 deletions

View File

@@ -0,0 +1,134 @@
---
front: https://nie.res.netease.com/r/pic/20210730/ee109f39-8987-46e0-9fe7-40ebb23060fa.png
hard: 入门
time: 20分钟
selection: true
---
# 自定义NPC的基本行为
现在已经基本具备海滨小岛的内容不过还缺乏生机岛上只有玩家自己是不行的所以让我们来给小岛上添加各种各样的NPC吧。在添加NPC前需要了解什么是[自定义生物](../../10-addon教程/第07章自定义生物/课程01.认识自定义生物.md),当我们创建一个基本的自定义生物后,就该把它变换一个“形态”,让其成为可以闲逛、交易、且生机勃勃的海滨岛民。
![0](./images/0.png)
## 创建一个自定义生物
我们打开这个实体的行为文件,添加一些最基本的行为,让他们可以出现在地图上。除了基本的跳跃、开门、行走、碰撞体积、移动速度、闲逛外,还添加了一些稍微特殊的行为:
- 继承原版村民的特性,使其更加“真实”。
- 给NPC命名并且永久显示在头上。
- 让NPC永远不会消失。
> 如果对自定义生物完全不了解,建议点击文章开头的“自定义生物”超链接 深入了解。
```json
{
"format_version": "1.13.0",
"minecraft:entity": {
"description": {
"identifier": "farm:animal_shop",
"is_spawnable": true,
"is_summonable": true,
"is_experimental": false,
"runtime_identifier": "minecraft:villager" //可以理解为继承某一个生物的特性,这里指向原版村民(攻击生物会飘出生气的粒子),如果你想完全自定义一个生物,不建议添加,直接删除此行即可。
},
"components": {
"minecraft:nameable": {
"allow_name_tag_renaming":true, //是否可用命名牌改名
"always_show":true //是否永久显示
},
"minecraft:annotation.open_door": {
},
"minecraft:jump.static": {
},
"minecraft:can_climb": {
},
"minecraft:persistent":{}, //使生物永远不会消失
"minecraft:collision_box": {
"width": 0.8,
"height": 2
},
"minecraft:movement": {
"value": 0.25
},
"minecraft:navigation.walk": {
"can_path_over_water": true,
"can_pass_doors": true,
"can_open_doors": true,
"avoid_water": true
},
"minecraft:movement.basic": {
},
"minecraft:health": {
"value": 5,
"max": 5
},
"minecraft:behavior.random_stroll": {
"priority": 7,
"speed_multiplier": 1
},
"minecraft:behavior.look_at_player": {
"priority": 8,
"look_distance": 6,
"probability": 0.02
},
"minecraft:behavior.random_look_around": {
"priority": 9
},
"minecraft:physics": {
}
},
"component_groups": {
},
"events": {
}
}
}
```
除了打开行为文件修改,还可以从**新版关卡编辑器**中直接添加,比起修改文件,后者更方便直观,所有的行为都有详细的介绍。为了更好且便利的使用,接下来简单介绍一下基本功能
![2](./images/1.gif)
图中用红色框出的部分从左至右依次是:**资源配置**、**资源管理**和**属性窗口**,所有创建的自定义内容都可以在配置中找到,点击配置就可以在右侧属性中自由修改,也可以在下方资源管理窗口找到相应的文件查看。
![3](./images/2.png)
点击左下角资源管理中选择新建-配置-实体,可以选择覆盖一个原版生物的模板,也可以创建空生物,所有的行为都由我们自己来添加,这里我们选择 "空"。
![3](./images/3.gif)
新建一个实体后,在左侧的配置窗口就可以看到了,点击打开并修改其属性,这里重点介绍。
- 整体模板:选择一个原版生物并继承其所有的行为和资源
- 配套文件 - 行为包json仅继承某一原版生物的行为部分行为、AI等
- 配套文件 - 资源包json仅继承某一原版生物的资源部分模型、动画等
- 配套文件 - 语言文件:修改生物、物品等的文字描述
- 基础属性 - 标识符:用此来定义生物,不可以与其它生物重复(前缀:名称)
- 基础属性 - 自然生成:生物是否可在世界中自然生成
- 基础属性 - 召唤生成:在创造物品栏中出现该生物的刷怪蛋
- 行为包组件:创建生物的行为,可添加多个组件(主要使用)
因为我们创建的是空,所以整体模板遗迹配套文件都为空,跳过这些,直接添加行为包组件
<img src="./images/4.png" alt="4" style="zoom:115%;" />
添加需要基本行为组件后,点击某个组件的加号,还可以继续添加或修改更精细的行为,以“可被命名”举例,点击后面的加号,添加可以被命名且永久显示。
![5](./images/5.png)
基本的行为添加完后,在编辑器中点击**开发测试**进入到游戏,这里需要注意,如果点击开发测试进入游戏修改内容,地图是不会有变化的,点击**编辑**进入地图编辑器才行,如果有特殊需要,也可以把修改后的测试存档导出再重新导入到编辑器中。
![42](./images/42.png)
把NPC生成到地图中并且使用命名牌改名试一下这里有个小细节改完名字后可以回到行为包中将**可以被命名牌修改名称**改为**False**这样NPC的名字就锁定为目前修改的名称了。
![6](./images/6.gif)

View File

@@ -0,0 +1,49 @@
---
front: https://nie.res.netease.com/r/pic/20210730/ee109f39-8987-46e0-9fe7-40ebb23060fa.png
hard: 入门
time: 20分钟
---
# 为NPC设置工作区域
当所有的NPC准备就绪后需要让他们回到自己的工作岗位并且不能乱跑满足这个条件可以有如下解决方案
- 直接删除下方的的移动行为,使其只能待在原地 这种方法最简单粗暴将NPC放置在哪就一直会在原地非常安全但我们的NPC也就失去了“生机”非常死板不是很推荐此方法
```json
"minecraft:movement": {
"value": 0.25
},
"minecraft:navigation.walk": {
"can_path_over_water": true,
"can_pass_doors": true,
"can_open_doors": true,
"avoid_water": true
},
"minecraft:movement.basic": {
},
```
- 删除NPC闲逛的行为使其只能走去睡觉但是白天他就会一直待在醒来的地方
```json
"minecraft:behavior.random_stroll": {
"priority": 7,
"speed_multiplier": 1
},
```
- 最好的方法也最简单,就是利用方块将其封在一个固定的区域内,让其可以在区域内闲逛
![4](./images/7.gif)
除了以上的方法,肯定还有更好的方案等待发掘,本篇教程点到为止,为大家提供简单的解决方案。

View File

@@ -0,0 +1,748 @@
---
front: https://nie.res.netease.com/r/pic/20210730/ee109f39-8987-46e0-9fe7-40ebb23060fa.png
hard: 进阶
time: 60分钟
---
# 给NPC添加对应的交易表
现在我们的NPC已经有了基本的行为并且会在自己的区域内等待玩家的光临接下来我们需要给NPC赋予“灵魂”使其成为一个真正的商人可以兑换各式各样的物品实现的方法也有很多种本篇我们将介绍两种实现的方法你可以选择更适合自己或者更适合地图的一种。
<iframe src="https://cc.163.com/act/m/daily/iframeplayer/?id=6152b9cfb8a81f8fa07dc89f" height="600" width="800" allow="fullscreen" />
## 使用Add-on添加交易表组件
第一种方法比较简单,直接在生物的行为文件中添加如下组件即可
```json
"minecraft:economy_trade_table": { //让NPC有交易表
"display_name": "entity.villager.anmial_shop", //交易表上显示的内容
"table": "trading/custom/animal_shop.json", //交易表的路径位置
"new_screen": true //是否使用新版的交易界面
}
```
然后根据写下的路径位置"trading/custom/animal_shop.json",新建交易表,交易表的路径和名称必须一致,否则是无效的。由于我们目前还没有添加交易的物品,如作物种子、家具等,所以我们先随便添加一条交易来进行演示。
```json
{
"tiers": [
{
"groups": [ //组
{
"num_to_select": 1, //从下方列表中选择1条出现在交易表中
"trades": [
{
"wants": [ //交易所需
{
"item": "minecraft:gold_ingot", //物品名称(金锭)
"quantity": 1 //数量
}
],
"gives": [ //交易所得
{
"item": "minecraft:spawn_egg:10", //物品名称(刷怪蛋:鸡)
"quantity": 1 //数量
}
]
}
]
}
]
}
]
}
```
完成以上内容后接下来进入到游戏右键NPC
![11](./images/11.gif)
可以看到,打开交易表后上面有一串名字,这里就对应行为组件里的**"display_name"**,我们可以对其直接进行修改:
```json
"minecraft:economy_trade_table": {
"display_name": "交易表", //直接对其修改
"table": "trading/custom/animal_shop.json",
"new_screen": true
}
```
![12](./images/12.png)
## 使用界面编辑器制作交易表的UI
我们这次地图的交易货币并不是存放在背包的而是用数据存储没有实体所以就不能采用第一种Add-on的方式所以为了实现NPC的交易功能必须使用UI制作可交互的界面所以我们将使用界面编辑器制作一个简易的合成表界面
![13](./images/13.png)
新建一个新的UI并尽量输入一个比较独特且有意义的名字这样在后续使用的时候更容易调试也不易冲突
![14](./images/14.png)
新建一个UI后左上可以看到控件结构我们添加的所有UI控件都将在这里点击某一控件可以在右侧的属性中进行修改调整每次创建新的UI界面都会有一个名为**main**的主画布一般为默认就好建议除主画布main外把所有的UI空间都改为有意义且容易辨识的名称
接下来在主画布下添加一个**面板控件**,将尺寸设置为合成表合适的大小,再添加**图片控件**,大小跟随上一个面板控件并添加贴图背景;
![15](./images/15.png)
随后依次添加多个**图片控件**用来当作滚动列表、关闭按钮、购买按钮以及商品信息的背景并调整到合适的大小和贴图以使UI更加美观如果在添加UI控件时出现这种层级顺序错误的情况可以将上方**自动设定层级**选项关闭,再逐一调整控件的层级即可。
![16](./images/16.png)
铺好所有背景后,依次添加需要的控件,每个控件要放到相应的背景下。
![17](./images/17.png)
点击滚动列表控件,可以在属性中看到**内容**,这里指的是滚动列表里应该放些什么,合成表需要有很多的商品,所以这里需要添加一排按钮用来选择商品,我们添加新的画布用来存储按钮模板。
![18](./images/18.png)
为了能让按钮一个个的排列,我们还需要单独创建一个网格,把按钮放进网格里并设置需要的数量。
![19](./images/19.png)
最后将网格添加到滚动列表里就获得一个完整的合成表UI了
![20](./images/20.png)
## 使用MODSDK实现交易功能
1. 新建一个py文件导入ScreenNode类新增Main类并继承ScreenNode
2. 在ClietnSystem客户端的**UiInitFinished**事件中初始化UI
3. 在ServerSystem服务端中利用**PlayerAttackEntityEvent**事件获得特定NPC和玩家的id
4. 设定玩家在点击此NPC时传达创建合成表UI的事件给客户端。
```python
class FarmClientSystem(ClientSystem):
def __init__(self, namespace, systemName):
super(FarmClientSystem, self).__init__(namespace, systemName)
namespace = clientApi.GetEngineNamespace()
system_name = clientApi.GetEngineSystemName()
self.ListenForEvent(namespace, system_name,
'UiInitFinished', self, self.ui_init)
self.ListenForEvent("FarmMod", "ServerSystem", "create_shop_ui",
self, self.Create_Shop_UI)
def Create_Shop_UI(self,event):
self.ui = clientApi.PushScreen("Farm","new_shop")
def ui_init(self,args):
clientApi.RegisterUI("Farm","new_shop","Script_NeteaseModw7ijjGNn.uiscreen.FarmUIScreen","new_shop.main")
```
```python
class FarmServerSystem(ServerSystem):
def __init__(self, namespace, systemName):
ServerSystem.__init__(self, namespace, systemName)
self.ListenForEvent("FarmMod", "ClientSystem", "buy_item",
self,self.PlayerBuyItem)
self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "PlayerAttackEntityEvent",
self,self.PlayerAttack)
def PlayerAttack(self,args):
print "攻击了"
entityid = args["victimId"]
self.playername = args["playerId"]
if entityid == "-120259084268":
self.NotifyToClient(self.playername,"create_shop_ui", args)
```
```python
class FarmUIScreen(ScreenNode):
def __init__(self, namespace, name, param):
ScreenNode.__init__(self, namespace, name, param)
```
接下来我们点击编辑器的**开发测试**进入游戏点击对应id的NPC即可打开合成表UI不过此时还不能对UI进行任何交互接下来我们简单修改ui文件并绑定py函数实现一系列功能。
在ui文件的关闭按钮控件下修改 **"$pressed_button_name"** 这样在py中给函数添加绑定的装饰器可以直接响应按钮的交互在函数内添加关闭UI的接口。
> 需要删除ui文件按钮内的 "button_mappings" : []
```json
"close_button@common.button" : {
"$control_alpha" : 1.0,
"$default_texture" : "textures/ui/close_button_default",
"$hover_texture" : "textures/ui/close_button_default_light",
"$is_new_nine_slice" : false,
"$label_color" : [ 1, 1, 1 ],
"$label_font_scale_factor" : 1.0,
"$label_font_size" : "large",
"$label_layer" : 3,
"$label_offset" : [ 0, 0 ],
"$label_text" : "",
"$nine_slice_buttom" : 0,
"$nine_slice_left" : 0,
"$nine_slice_right" : 0,
"$nine_slice_top" : 0,
"$nineslice_size" : [ 0, 0, 0, 0 ],
"$pressed_button_name" : "%uiscreen.clicked_close_button", //修改按下的按钮的名称
"$pressed_texture" : "textures/ui/close_button_default_light",
"$texture_layer" : 2,
"alpha" : 1.0,
"anchor_from" : "center",
"anchor_to" : "center",
"bindings" : [
{
"binding_collection_name" : "",
"binding_condition" : "always_when_visible",
"binding_type" : "collection_details"
}
],
"clip_offset" : [ 0, 0 ],
"clips_children" : false,
"controls" : [
{
"default@new_shop.default" : {}
},
{
"hover@new_shop.hover" : {}
},
{
"pressed@new_shop.pressed" : {}
}
],
"draggable" : "not_draggable",
"enabled" : true,
"is_handle_button_move_event" : true,
"layer" : 1,
"max_size" : [ 0, 0 ],
"min_size" : [ 0, 0 ],
"offset" : [ 0, 0 ],
"propagate_alpha" : false,
"size" : [ "100.0%+0.0px", "100.0%+0.0px" ],
"visible" : true
},
```
```python
#关闭按钮的绑定函数,按钮按下再松开后触发
@ViewBinder.binding(ViewBinder.BF_ButtonClickUp)
def clicked_close_button(self,args):
print "按关闭了!"
clientApi.PopScreen()
```
![26](./images/26.gif)
然后我们需要绑定滚动列表按钮的**button_label**,也就是对按钮上的文字进行修改,并且点击按钮后会在商品信息处显示对应信息:
1. 创建一个列表变量,在里面添加文字和描述
2. ui文件的button_label中添加集合绑定数组用装饰器绑定函数修改按钮文字
3. 利用导入的模块截取按下按钮的具体index
4. 给玩家点击的按钮返回对应的描述
首先创建一个列表变量,在里面添加多个按钮上需要显示的文字和描述,因为目前还没有添加作物和种子,就先随便写一些东西用作测试:
```python
# coding=utf-8
self.item_button_text = [
{
"itemtext": "点击选择:小麦", # 商品的名称,显示在按钮上
"information": "品种:小麦\n生长周期3\n价格5", # 商品的详细信息展示在UI右侧
},
{
"itemtext": "点击选择:茼蒿",
"information": "品种:茼蒿\n生长周期3\n价格10"
},
{
"itemtext": "点击选择:玉米",
"information": "品种:玉米\n生长周期3\n价格15"
},
{
"itemtext": "点击选择:小麦",
"information": "品种:小麦\n生长周期3\n价格5"
},
{
"itemtext": "点击选择:小麦",
"information": "品种:小麦\n生长周期3\n价格5"
},
{
"itemtext": "点击选择:小麦",
"information": "品种:小麦\n生长周期3\n价格5"
},
{
"itemtext": "点击选择:小麦",
"information": "品种:小麦\n生长周期3\n价格5"
},
{
"itemtext": "点击选择:苹果",
"information": "品种:苹果\n生长周期3\n价格25"
}
]
```
在button_label控件也就是按钮上的文字添加一个绑定并且给绑定函数返回的值赋予在“text”上这样就可以实现获取集合中按钮的index值并返回文字同时还要在按钮的控件下添加集合的变量名称
```json
"button_label" : {
"alpha" : "$control_alpha",
"color" : "$label_color",
"font_scale_factor" : "$label_font_scale_factor",
"font_size" : "$label_font_size",
"font_type" : "smooth",
"layer" : "$label_layer",
"max_size" : [ "100%", "100%" ],
"offset" : "$label_offset",
"shadow" : false,
"text" : "#text",
"text_alignment" : "center",
"type" : "label",
"bindings" : [
{
"binding_collection_name" : "$shop_grid_collection_name", //绑定的集合变量名
"binding_name":"#item_button_text", //绑定名称,用在装饰器上
"binding_name_override":"#text", //对应上面的#text
"binding_condition" : "visible", //绑定条件:显示
"binding_type" : "collection" //绑定类型:集合
}
]
},
```
```json
"button@common.button" : {
"$control_alpha" : 1.0,
"$default_texture" : "textures/ui/pocket_button_default",
"$hover_texture" : "textures/ui/pocket_button_hover",
"$is_new_nine_slice" : false,
"$label_color" : [ 1, 1, 1 ],
"$label_font_scale_factor" : 1.0,
"$label_font_size" : "large",
"$label_layer" : 3,
"$label_offset" : [ 0, 0 ],
"$label_text" : "",
"$nine_slice_buttom" : 0,
"$nine_slice_left" : 0,
"$nine_slice_right" : 0,
"$nine_slice_top" : 0,
"$nineslice_size" : [ 0, 0, 0, 0 ],
"$shop_grid_collection_name" : "shop_grid", //集合变量的名称
"$pressed_button_name" : "%uiscreen.clicked_item_button",
"$pressed_texture" : "textures/ui/pocket_button_pressed",
"$texture_layer" : 2,
"alpha" : 1.0,
"anchor_from" : "center",
"anchor_to" : "center",
"bindings" : [
{
"binding_collection_name" : "",
"binding_condition" : "always_when_visible",
"binding_type" : "collection_details"
}
],
"clip_offset" : [ 0, 0 ],
"clips_children" : false,
"controls" : [
{
"default@new_shop.default" : {}
},
{
"hover@new_shop.hover" : {}
},
{
"pressed@new_shop.pressed" : {}
},
{
"button_label@new_shop.button_label" : {}
}
],
"draggable" : "not_draggable",
"enabled" : true,
"is_handle_button_move_event" : true,
"layer" : 1,
"max_size" : [ 0, 0 ],
"min_size" : [ 0, 0 ],
"offset" : [ 0, 0 ],
"propagate_alpha" : false,
"size" : [ "100.0%+0.0px", "100.0%+0.0px" ],
"visible" : true
},
```
```python
#集合绑定字符串,给网格内的按钮添加对应的文字
@ViewBinder.binding_collection(ViewBinder.BF_BindString,"shop_grid","#item_button_text")
def binding_item_button_text(self,index):
print index
return self.item_button_text[index]["itemtext"] #返回此函数对应变量index的文字
```
导入re模块并使用**compile**函数和**findall**函数截取玩家实际点击按钮的index值并且在ui文件中修改这个按钮控件的**"$pressed_button_name"**,使装饰器绑定这个按钮:
```json
"button@common.button" : {
"$control_alpha" : 1.0,
"$default_texture" : "textures/ui/pocket_button_default",
"$hover_texture" : "textures/ui/pocket_button_hover",
"$is_new_nine_slice" : false,
"$label_color" : [ 1, 1, 1 ],
"$label_font_scale_factor" : 1.0,
"$label_font_size" : "large",
"$label_layer" : 3,
"$label_offset" : [ 0, 0 ],
"$label_text" : "",
"$nine_slice_buttom" : 0,
"$nine_slice_left" : 0,
"$nine_slice_right" : 0,
"$nine_slice_top" : 0,
"$nineslice_size" : [ 0, 0, 0, 0 ],
"$shop_grid_collection_name" : "shop_grid",
"$pressed_button_name" : "%uiscreen.clicked_item_button", //修改按下的按钮的名称
"$pressed_texture" : "textures/ui/pocket_button_pressed",
"$texture_layer" : 2,
"alpha" : 1.0,
"anchor_from" : "center",
"anchor_to" : "center",
"bindings" : [
{
"binding_collection_name" : "",
"binding_condition" : "always_when_visible",
"binding_type" : "collection_details"
}
],
"clip_offset" : [ 0, 0 ],
"clips_children" : false,
"controls" : [
{
"default@new_shop.default" : {}
},
{
"hover@new_shop.hover" : {}
},
{
"pressed@new_shop.pressed" : {}
},
{
"button_label@new_shop.button_label" : {}
}
],
"draggable" : "not_draggable",
"enabled" : true,
"is_handle_button_move_event" : true,
"layer" : 1,
"max_size" : [ 0, 0 ],
"min_size" : [ 0, 0 ],
"offset" : [ 0, 0 ],
"propagate_alpha" : false,
"size" : [ "100.0%+0.0px", "100.0%+0.0px" ],
"visible" : true
},
```
```python
#按钮的绑定函数按下时通过re函数计算出按下的是滚动列表中的第几个按钮
@ViewBinder.binding(ViewBinder.BF_ButtonClickUp)
def clicked_item_button(self,args):
print "按下啦"
buttonpath = args["ButtonPath"].split('/')[-2] #使用导入的re模块截取按钮的index数
reg = re.compile(r'\d+')
button_index = reg.findall(buttonpath)
if button_index:
self.clicked_button_index = int(button_index[0])-1 #获取到的index数保存在此变量中
```
当玩家点击某个按钮时就会获取具体的index值同时就可以为这个按钮添加描述当然information对应的ui控件也需要添加绑定
```python
#绑定字符串,给商品信息添加对应的描述
@ViewBinder.binding(ViewBinder.BF_BindString,"#shop_information")
def binding_shop_information(self):
if self.clicked_button_index == -1: #如果点击按钮的index是-1则返回一个空字符串
return ""
return self.item_button_text[self.clicked_button_index]["information"] #返回对应index的描述信息
```
```json
"item_information" : {
"alpha" : 1.0,
"anchor_from" : "top_left",
"anchor_to" : "top_left",
"clip_offset" : [ 0, 0 ],
"clips_children" : false,
"color" : [ 1, 1, 1 ],
"enabled" : true,
"font_scale_factor" : 1.0,
"font_size" : "normal",
"font_type" : "smooth",
"layer" : 1,
"line_padding" : 0.0,
"max_size" : [ 0, 0 ],
"min_size" : [ 0, 0 ],
"offset" : [ 5, 5 ],
"propagate_alpha" : false,
"shadow" : false,
"size" : [ "75.0%+0.0px", "75.0%+0.0px" ],
"text" : "#text",
"text_alignment" : "left",
"type" : "label",
"visible" : true,
"bindings" : [
{
"binding_name":"#shop_information", //用在装饰器上绑定
"binding_name_override":"#text", //对应上面的#text
"binding_condition" : "always_when_visible" //绑定条件:总是显示
}
]
},
```
完成以上步骤后合成表UI已经有了大致的内容
![35](./images/35.gif)
现在我们来添加最后也是最关键的内容通过交易表UI进行购买首先通过装饰器和修改ui文件绑定购买的按钮在绑定的函数下写相关的逻辑
1. 如果玩家没有选择左侧的某样物品则返回
2. 获取玩家现在有的钱数并对比商品价格
3. 如果玩家的钱可以购买则继续,如果不能则返回
4. 购买成功后向服务端通信,给予玩家购买的物品
代码如下:
```python
# -*- coding: utf-8 -*-
self.item_button_text = [
{
"itemtext":"点击选择:小麦", #商品的名称,显示在按钮上
"information":"品种:小麦\n生长周期3\n价格5", #商品的详细信息展示在UI右侧
"coin": 5, #购买商品的价格
"itemname":"minecraft:grass" #用于读取实际商品以给予玩家
},
{
"itemtext": "点击选择:茼蒿",
"information": "品种:茼蒿\n生长周期3\n价格10",
"coin": 5,
"itemname": "minecraft:grass"
},
{
"itemtext": "点击选择:玉米",
"information": "品种:玉米\n生长周期3\n价格15",
"coin": 5,
"itemname": "minecraft:grass"
},
{
"itemtext": "点击选择:小麦",
"information": "品种:小麦\n生长周期3\n价格5",
"coin": 5,
"itemname": "minecraft:grass"
},
{
"itemtext": "点击选择:小麦",
"information": "品种:小麦\n生长周期3\n价格5",
"coin": 5,
"itemname": "minecraft:grass"
},
{
"itemtext": "点击选择:小麦",
"information": "品种:小麦\n生长周期3\n价格5",
"coin": 5,
"itemname": "minecraft:grass"
},
{
"itemtext": "点击选择:小麦",
"information": "品种:小麦\n生长周期3\n价格5",
"coin": 5,
"itemname": "minecraft:grass"
},
{
"itemtext": "点击选择:苹果",
"information": "品种:苹果\n生长周期3\n价格25",
"coin": 5,
"itemname": "minecraft:grass"
}
]
```
先给商品的列表变量添加价格和实际商品的名称用于接下来的逻辑判断和物品发放;
```json
"buy_button@common.button" : {
"$control_alpha" : 1.0,
"$default_texture" : "textures/ui/pocket_button_default",
"$hover_texture" : "textures/ui/pocket_button_hover",
"$is_new_nine_slice" : false,
"$label_color" : [ 1, 1, 1 ],
"$label_font_scale_factor" : 1.0,
"$label_font_size" : "large",
"$label_layer" : 3,
"$label_offset" : [ 0, 0 ],
"$label_text" : "点击购买",
"$nine_slice_buttom" : 0,
"$nine_slice_left" : 0,
"$nine_slice_right" : 0,
"$nine_slice_top" : 0,
"$nineslice_size" : [ 0, 0, 0, 0 ],
"$pressed_button_name" : "%uiscreen.buy_button_clicked", //和前面的按钮一样,修改按下按钮的名称
"$pressed_texture" : "textures/ui/pocket_button_pressed",
"$texture_layer" : 2,
"alpha" : 1.0,
"anchor_from" : "center",
"anchor_to" : "center",
"bindings" : [
{
"binding_collection_name" : "",
"binding_condition" : "always_when_visible",
"binding_type" : "collection_details"
}
],
"clip_offset" : [ 0, 0 ],
"clips_children" : false,
"controls" : [
{
"default@new_shop.default" : {}
},
{
"hover@new_shop.hover" : {}
},
{
"pressed@new_shop.pressed" : {}
},
{
"buy_button_label@new_shop.buy_button_label" : {}
}
],
"draggable" : "not_draggable",
"enabled" : true,
"is_handle_button_move_event" : true,
"layer" : 1,
"max_size" : [ 0, 0 ],
"min_size" : [ 0, 0 ],
"offset" : [ 0, 0 ],
"propagate_alpha" : false,
"size" : [ "100.0%+0.0px", "100.0%+0.0px" ],
"visible" : true
},
```
修改购买按钮ui文件的**$pressed_button_name**用于在py文件中绑定
```python
class FarmUIScreen(ScreenNode):
def __init__(self, namespace, name, param):
ScreenNode.__init__(self, namespace, name, param)
self.clientsystem = clientApi.GetSystem("FarmMod", "ClientSystem") #获取客户端实例
self.clicked_button_index = -1 #按钮的index值
self.coin = 0 #定义玩家的钱数
self.item_button_text = [
{
"itemtext":"点击选择:小麦", #商品的名称,显示在按钮上
"information":"品种:小麦\n生长周期3\n价格5", #商品的详细信息展示在UI右侧
"coin": 5, #购买商品的价格
"itemname":"minecraft:grass" #用于读取实际商品以给予玩家
},
{
"itemtext": "点击选择:茼蒿",
"information": "品种:茼蒿\n生长周期3\n价格10",
"coin": 5,
"itemname": "minecraft:grass"
},
{
"itemtext": "点击选择:玉米",
"information": "品种:玉米\n生长周期3\n价格15",
"coin": 5,
"itemname": "minecraft:grass"
},
{
"itemtext": "点击选择:小麦",
"information": "品种:小麦\n生长周期3\n价格5",
"coin": 5,
"itemname": "minecraft:grass"
},
{
"itemtext": "点击选择:小麦",
"information": "品种:小麦\n生长周期3\n价格5",
"coin": 5,
"itemname": "minecraft:grass"
},
{
"itemtext": "点击选择:小麦",
"information": "品种:小麦\n生长周期3\n价格5",
"coin": 5,
"itemname": "minecraft:grass"
},
{
"itemtext": "点击选择:小麦",
"information": "品种:小麦\n生长周期3\n价格5",
"coin": 5,
"itemname": "minecraft:grass"
},
{
"itemtext": "点击选择:苹果",
"information": "品种:苹果\n生长周期3\n价格25",
"coin": 5,
"itemname": "minecraft:grass"
},
]
@ViewBinder.binding(ViewBinder.BF_ButtonClickUp)
def buy_button_clicked(self,args):
print "点击购买了"
if self.clicked_button_index == -1:
print "玩家还没选物品"
return
price = self.item_button_text[self.clicked_button_index]['coin']
print "他想买的东西价值:",price
print "你有的钱数:",self.coin
if self.coin >= price:
self.coin -= price
print "买完以后你还剩:",self.coin
#向服务端通信将玩家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 "你买不起"
```
使用装饰器绑定函数注意此时的函数名和上方ui文件的**$pressed_button_name**必须一致;获取客户端实例并在玩家购买完成后通知服务端发放物品;
```python
class FarmServerSystem(ServerSystem):
def __init__(self, namespace, systemName):
ServerSystem.__init__(self, namespace, systemName)
self.ListenForEvent("FarmMod", "ClientSystem", "buy_item",
self,self.PlayerBuyItem)
self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "PlayerAttackEntityEvent",
self,self.PlayerAttack)
def PlayerBuyItem(self, args):
print "玩家买到了"
player_id = args['playerid'] #传过来的玩家id
item_name = args['buy_item'] #传过来的实际商品名称
serverApi.GetEngineCompFactory().CreateItem(player_id).SpawnItemToPlayerInv( #发放物品
{
'newItemName': item_name,
'count': 1
},
player_id
)
```
```python
def Create_Shop_UI(self,event):
self.ui = clientApi.PushScreen("Farm","new_shop")
self.ui.coin = 100 #修改这个ui实例的硬币数量
```
在创建合成表时先将玩家的钱数设定为100然后进入到游戏中测试一下
![41](./images/41.gif)

View File

@@ -0,0 +1,123 @@
---
front: https://nie.res.netease.com/r/pic/20210730/ee109f39-8987-46e0-9fe7-40ebb23060fa.png
hard: 入门
time: 20分钟
---
# 给NPC设置睡觉的床
现在我们已经有一个任劳任怨并且可以和我们交易的商人啦但是商人们只能工作不能休息也太惨了吧所以我们需要让NPC们和玩家一样拥有自然的作息而不是一个冷冰冰的NPC这样也可以一定程度上增加游戏的代入感让玩家更好的享受乐趣。
![8](./images/8.png)
## 让NPC绑定属于自己的床
每个NPC都要有一个自己的床不然他们就要开始互相抢床导致混乱这就麻烦了。我们只需要在NPC行为里面添加一个行为组件就行了。
```json
"minecraft:dweller": {
"dwelling_type": "village", //类型
"dweller_role": "inhabitant", //角色因为这张地图的NPC都是无职业的如果想要添加给NPC添加职业可以参考原版Villager_v2的行为组件
"update_interval_base": 20, //基础更新间隔
"update_interval_variant": 10, //变种更新间隔
"can_find_poi": true //是否可以寻找poi
}
```
进入到游戏中试一下把NPC放在床的附近当他和床同时发出绿色粒子的时候就成功啦现在这个床就属于这个NPC了当然我们玩家还是可以把他的床抢过来。
![9](./images/9.png)
## 让NPC到床上睡觉
现在NPC已经有属于自己的床了不过他们还不会去睡觉接下来再加一个行为组件
```json
"minecraft:behavior.sleep": {
"speed_multiplier": 1.5, //去睡觉时的移动速度倍增
"sleep_collider_height": 0.3, //睡觉时生物碰撞箱的高度
"sleep_collider_width": 1.0, //睡觉时生物碰撞箱的宽度
"sleep_y_offset": 0.6, //睡觉时生物Y轴的偏移量
"timeout_cooldown": 10.0 //发生意外起床后,再过多长时间可以再次睡觉
}
```
添加这个行为组件后,生物就会跑去睡觉啦,当然别忘了留下可以移动的行为组件。
这里还有一个小问题,如果我们给生物添加基本的睡觉行为,那么当生物找到床以后不管是白天还是晚上都会一直睡觉,所以我们需要利用**事件**和**组件组**配合让NPC只有在晚上的时候才会睡觉。
有关components、events与component_groups三者之间的关系可以参考往期教程[定义生物行为的三种结构](https://g.126.fm/04a9tkE)。
首先我们添加一个可以检测时间的组件,分别在白天和晚上触发不同的事件:
```json
"minecraft:scheduler": {
"min_delay_secs": 0, //最小延迟数
"max_delay_secs": 10, //最大延迟数
"scheduled_events" : [ //调度事件
{
"filters": { //过滤器(第一个)
"all_of": [ //满足所有条件
{ "test": "hourly_clock_time", "operator": ">=", "value": 0 }, //当事件大于等于0时代表白天开始了
{ "test": "hourly_clock_time", "operator": "<", "value": 12000 } //小于12000时代表白天还没有结束
]
},
"event": "minecraft:work" //触发事件"minecraft:work"
},
{
"filters": { //过滤器(第二个)
"all_of": [ //满足所有条件
{ "test": "hourly_clock_time", "operator": ">=", "value": 12000 }, //大于等于12000代表夜晚开始了
{ "test": "hourly_clock_time", "operator": "<", "value": 24000 } //小于24000代表夜晚还没有结束
]
},
"event": "minecraft:sleep" //触发事件"minecraft:sleep"
}
]
},
```
加上这个行为组件后,白天的时候就会触发**work**的事件,晚上就会触发**sleep**的事件接下来添加这两个事件分别在触发work事件的时候让村民起床在触发sleep事件的时候让村民睡觉。
```json
"events": { //事件所有的事件都放在这下面和components行为组件在同一排
"minecraft:sleep": { //当满足过滤器(第二个)时触发的事件
"remove": { "component_groups": [ "villager_work" ] }, //移除"villager_work"组件组
"add": { "component_groups": [ "villager_sleep" ] }
}, //添加"villager_sleep"组件组
"minecraft:work": { //当满足过滤器(第一个)时触发的事件
"remove": { "component_groups": [ "villager_sleep" ] }, //移除"villager_sleep"组件组
"add": { "component_groups": [ "villager_work" ] } //添加"villager_work"组件组
}
}
```
```json
"component_groups": { //组件组,触发事件后可随意移除和添加一组或多个组
"villager_sleep": { //与事件一起看,当满足过滤器(第二个)时就会添加这个组件组,而这个组件组下只有一个组件,就是去睡觉
"minecraft:behavior.sleep": {
"priority": 3,
"goal_radius": 1.5,
"speed_multiplier": 1.5,
"sleep_collider_height": 0.3,
"sleep_collider_width": 1.0,
"sleep_y_offset": 0.6,
"timeout_cooldown": 10.0
}
},
"villager_work": { //当满足过滤器第一个时就会添加这个组件组并且移除睡觉的组件组这时候生物就会起床现在这个组件组里什么都没有所以NPC起床后也没什么变化
}
},
```
把上面的内容添加到NPC的行为文件里以后我们的商人就会在白天起床晚上去睡觉啦。进入游戏来看一下实际效果
![10](./images/10.gif)