Files
boybook 760c2dd9ad 2.6
2025-12-01 20:59:16 +08:00

258 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
front:
hard: 进阶
time: 20分钟
---
# 代码编写基础
本节将主要介绍接口和事件之间的区别、文档的查阅以及客户端和服务端之间的通信。
<iframe src="https://cc.163.com/act/m/daily/iframeplayer/?id=632867a9e6c041f2578ca820" width="800" height="600" allow="fullscreen"/>
## 接口与事件
这里的接口与事件和逻辑编辑器的接口和事件定义一致,如果有遗忘的同学可以去[回顾](../2-逻辑编辑器基础/3-逻辑编辑器的基础概念2.html)。
那么在逻辑编辑器中,所有事件,都体现为`监听:xxxxx`,而在零件开发中,监听事件一般都是定义一个函数。
如果需要查阅所有可以监听的事件,可以在<a href="../../../../mcdocs/1-ModAPI/事件/世界.html?catalog=1">这里</a>进行查询。
例如在`PartBase`中定义以下函数就视为监听了ServerChatEvent
```python
def ServerChatEvent(self, args):
pass
```
而在零件开发中调用接口,都是以调用函数的形式来调用的。
还是这个事件,`GetParent``SetEntityAttrValue``SetEntityAttrMaxValue`,都是调用了`PartBase`的接口。
所有可以使用的接口都可以在<a href="../../../../mcguide/20-玩法开发/14-预设玩法编程/13-PresetAPI/预设管理/PresetApi.html">这里</a>进行查询。
```python
def ServerChatEvent(self, args):
if args["message"] != "更新血量":
return
parent = self.GetParent()
entityId = parent.GetEntityId()
if args["playerId"] != entityId:
return
self.SetEntityAttrValue(entityId, AttrType.HEALTH, self.health)
self.SetEntityAttrMaxValue(entityId, AttrType.HEALTH, self.maxHealth)
```
> 为什么<a href="../../../../mcguide/20-玩法开发/14-预设玩法编程/13-PresetAPI/预设对象/预设/预设基类PresetBase.html#预设基类presetbase">PartBase</a>中没有找到上方代码使用的GetParent?
>
> 因为我们是基于面向对象技术进行预设和零件开发的。PartBase继承了SdkInterface和TransformObject对象自然就可以调用来自父类的函数。
>
> 实际上我们调用的GetParent接口是来自<a href="../../../../mcguide/20-玩法开发/14-预设玩法编程/13-PresetAPI/预设对象/通用/变换对象TransformObject.html#getparent">TransformObject</a>的。这样GetParent因为零件挂载玩家预设上就能得到玩家。
## 服务器客户端通信
在之前的逻辑编辑器界面制作中,我们就已经稍微接触了一些服务器和客户端之间的通信。
当时的界面,将客户端中的内容,发送到了服务端。服务端监听,并执行命令。
这样就是一个客户端往服务端的通信。但是实际上,客户端和服务端之间,是可以双向通信的。下面将会详细介绍通信的使用方法。
### 客户端->服务端
客户端向服务端的通信主要需要在客户端调用`NotifyToServer`接口,而服务端需要在初始化的时候调用`ListenSelfEvent`接口,来监听这个事件。
#### NotifyToServer
文档说明:<a href="../../../../mcguide/20-玩法开发/14-预设玩法编程/13-PresetAPI/预设对象/零件/零件PartBase.html#notifytoserver">点我</a>
| 参数名 | 数据类型 | 说明 |
| :-------- | :------- | :------- |
| eventName | str | 事件名称 |
| eventData | object | 事件数据 |
这是一个客户端接口。`eventName`是事件名称。事件名称可以理解为这个事件的具体含义。客户端中使用这个事件名称发送到服务端,那么服务端相应的也需要使用这个事件名称来监听。`eventData`是事件的具体数据即一般事件中的args一般传入一个字典。
#### ListenSelfEvent
文档说明:<a href="../../../../mcguide/20-玩法开发/14-预设玩法编程/13-PresetAPI/预设对象/零件/零件PartBase.html#listenselfevent">点我</a>
| 参数名 | 数据类型 | 说明 |
| :-------- | :------- | :------- |
| eventName | str | 事件名称 |
| target | object | 目标 |
| func | object | 回调函数 |
这个接口,双端通用。
`eventName`就是我们NotifyToServer时所定义的事件名称。`target`为监听哪个对象的事件一般填写self。`func`为回调函数定义一个函数参数为args然后在这里传入function类型的值。
例子:
```python
class TestPartPart(PartBase):
def InitServer(self):
print "InitServer"
self.ListenSelfEvent("TestEvent", self, self.OnTestEvent)
def OnTestEvent(self, args):
print "收到来自 {} 的客户端事件".format(args["playerId"])
def SendToServer(self):
self.NotifyToServer("TestEvent", {"playerId": self.GetLocalPlayerId()})
```
这样如果在客户端调用SendToServer函数服务端就会相应的收到事件并打印消息。
### 服务端->客户端
服务端向客户端的通信主要需要在服务端调用`NotifyToClient`接口,而客户端需要在初始化的时候调用`ListenSelfEvent`接口,来监听这个事件。
#### NotifyToClient
文档说明:<a href="../../../../mcguide/20-玩法开发/14-预设玩法编程/13-PresetAPI/预设对象/零件/零件PartBase.html#notifytoclient">点我</a>
| 参数名 | 数据类型 | 说明 |
| :-------- | :------- | :------- |
| playerId | str | 玩家ID |
| eventName | str | 事件名称 |
| eventData | object | 事件数据 |
这是一个服务端接口。`playerId`为需要发送到的玩家id。其他的参数和`NotifyToServer`用法一致。
如果需要广播到所有玩家的客户端,可以使用<a href="../../../../mcguide/20-玩法开发/14-预设玩法编程/13-PresetAPI/预设对象/零件/零件PartBase.html#broadcasttoallclient">BroadcastToAllClient</a>。
#### ListenSelfEvent
ListenSelfEvent和客户端向服务端通信中的使用方法一致。
文档说明:<a href="../../../../mcguide/20-玩法开发/14-预设玩法编程/13-PresetAPI/预设对象/零件/零件PartBase.html#listenselfevent">点我</a>
| 参数名 | 数据类型 | 说明 |
| :-------- | :------- | :------- |
| eventName | str | 事件名称 |
| target | object | 目标 |
| func | object | 回调函数 |
这个接口,双端通用。
`eventName`就是我们NotifyToServer时所定义的事件名称。`target`为监听哪个对象的事件一般填写self。`func`为回调函数定义一个函数参数为args然后在这里传入function类型的值。
例子:
```python
class TestPartPart(PartBase):
def InitClient(self):
print "InitClient"
self.ListenSelfEvent("TestEvent", self, self.OnTestEvent)
def OnTestEvent(self, args):
print "收到来自服务端的事件 {}".format(args)
def SendToServer(self,playerId):
self.NotifyToClient(playerId, "TestEvent", {"msg": "test"})
```
这样如果在客户端调用SendToServer函数服务端就会相应的收到事件并打印消息。
## 课后作业
### 辨认接口和事件
辨别下方的代码是调用接口还是监听事件
1. `self.SetCommand()`
2. `def OnCommandOutputServerEvent(self, args):`
> 答案
>
> 1. 调用接口
> 2. 监听事件
### 零件开发实际操作
使用零件开发来编写一个爆炸箭的功能:所有射出的弓箭,在击中目标时都会产生爆炸。
并且使用自定义属性面板,设置爆炸范围。
#### 操作步骤
1. 创建一个玩家预设、空零件。零件命名为`ExplosionArrow`
2. 将零件挂接到玩家预设上。
3. 接下来使用PyCharm打开`ExplosionArrowPart.py`,编辑代码。
4.`__init__`下定义一个爆炸半径成员变量,方便后面制作自定义属性。
5. 监听<a href="../../../../mcdocs/1-ModAPI/事件/实体.html?key=ProjectileDoHitEffectEvent&docindex=2&type=0#projectiledohiteffectevent">ProjectileDoHitEffectEvent</a>,并获取对应位置,再调用<a href="../../../../mcguide/20-玩法开发/14-预设玩法编程/13-PresetAPI/预设对象/通用/SDK接口封装SdkInterface.html#createexplosion">CreateExplosion</a>,创建爆炸,其中爆炸半径使用成员变量,随后删除箭的实体。
代码参考:
```python
@registerGenericClass("ExplosionArrowPart")
class ExplosionArrowPart(PartBase):
def __init__(self):
PartBase.__init__(self)
self.explosionRadius = 5
self.name = "爆炸弓零件"
def ProjectileDoHitEffectEvent(self, args):
self.CreateExplosion((args["x"], args["y"], args["z"]), self.explosionRadius, True, True, args["srcId"], args["srcId"])
self.DestroyEntity(args["id"])
```
接下来设置元数据,打开`ExplosionArrowPartMeta.py`,修改`PROPERTIES`,添加一个爆炸半径变量。
代码参考:
```python
@sunshine_class_meta
class ExplosionArrowPartMeta(PartBaseMeta):
CLASS_NAME = "ExplosionArrowPart"
PROPERTIES = {
"explosionRadius": PInt(text="爆炸半径", sort=1000, default=5, group="爆炸箭头")
}
```
这样就修改完成。打开编辑器,选中爆炸弓零件,就可以看到相应的设置。
![](./images/22.png)
### 通信
利用客户端和服务端的通信系统,将玩家聊天的内容发送到客户端,并打印到日志窗口中。
#### 操作步骤
1. 新建一个空零件,命名为`NotifyTest`,并挂接在玩家预设上。
2. 编辑`NotifyTestPart.py`,监听<a href="../../../../mcdocs/1-ModAPI/事件/实体.html?key=ProjectileDoHitEffectEvent&docindex=2&type=0#projectiledohiteffectevent">ServerChatEvent</a>获取消息内容和玩家ID使用NotifyToClient将其发送给客户端事件名为`ChatToClient`数据是一个dict`{"msg": 消息内容}`
3. 在InitClient中调用ListenSelfEvent监听`ChatToClient`事件,再定义一个函数叫做`OnRecvChat`接收args作为参数打印`args["msg"]`,作为回调函数。
代码参考:
```python
@registerGenericClass("NotifyTestPart")
class NotifyTestPart(PartBase):
def __init__(self):
PartBase.__init__(self)
self.name = "通信测试"
def InitClient(self):
self.ListenSelfEvent("ChatToClient", self, self.OnRecvChat)
def OnRecvChat(self, args):
print "从服务端发来的聊天信息: {}".format(args["msg"])
def ServerChatEvent(self, args):
message = args["message"]
playerId = args["playerId"]
self.NotifyToClient(playerId, "ChatToClient", {"msg": message})
```
这样每次发消息,在日志窗口都会由客户端打印收到的消息内容。