title: UE蓝图通信
date: 2024-11-22 14:30:00
categories:
- 虚幻蓝图
- 通信
tags: 蓝图通信
蓝图通信
关卡蓝图
关卡蓝图是关卡特有的蓝图,负责处理与该场景相关的全局逻辑。它与普通蓝图的区别在于:
- 作用范围: 关卡蓝图的逻辑仅限于当前关卡,无法跨关卡直接作用。
- 直接操控场景对象: 关卡蓝图可以直接访问放置在场景中的 Actor(例如灯光、触发器等)。
- 全局事件处理: 处理场景级的输入、关卡加载、触发事件等。
关卡本质上是由一个特定类(如 UWorld 或 ULevel)及其关联对象构建的,关卡蓝图可以看作是 C++ 中专门控制当前场景逻辑的管理类,它的主要职责是:
- 处理事件(例如玩家触发某个区域)。
- 操控场景中对象(例如灯光、音效)。
- 定义与该关卡相关的全局逻辑。
在某些游戏中,可以将关卡看作是一个状态机(State Machine)的一部分:
- 每个关卡就是状态机的一个状态。
- 切换关卡时,相当于从一个状态过渡到另一个状态。
- 每个关卡定义了在该状态下的可交互元素和逻辑。
在关卡中,各种对象(例如 Actor)可以视为独立的实例,它们通常通过 组件(Component) 进行扩展。这在 C++ 中可以类比为组合模式。
关卡蓝图并不是一个全局的父类,更是一个管理器类
接下来看实际场景:
这里希望在方块落下时,力场门关闭(可视,启用碰撞),在方块离开时,立场们打开(不可视,无碰撞)
实现:
- 在方块下落位置增加触发体积块;
- 设置关卡蓝图,连接对应逻辑。
这里想要实现的是触发体积与力场门之间的通信,因为都在同一个地图中,也就是同一个关卡中,可以直接考虑使用关卡蓝图。缺点是尽在当前关卡中适用,不能复用。
蓝图中,通过触发体积的事件-开始重叠/结束重叠进入流程。
当开始重叠时,下面的Overlapped Actor表示被重叠的Actor,即触发体积本身,Other Actor即为进入触发体积Actor,与触发体积Actor发生重叠的Actor,即为图中的紫色方块。
根据逻辑,与触发体积开始重叠的是紫色方块时,branch为true,那么就“关门”,
关门的逻辑中,设置门可见,启用门的碰撞。
那么开门也同理。
蓝图内部组件之间的通信
和之前一样的那个门,如果有很多个需要门的地方,而且有的门的触发方式不同,那么把这些蓝图一遍遍的复制粘贴是不合适的,这里面向对象思想中,可以把BP_Door作为以一个基础的门,具有基础的门的功能即属性,也就是作为一个基类,这个门基类(蓝图)中,只需要有门的开门关门方法/接口即可,其余具体特化交给子类/蓝图来实现。
因为基类中只是开关门,而触发开关门是跟随子类特化的,所以基类中的触发体积(Box Collision)可以删除,在子类中需要的时候再添加。
开始:
删除触发器后,在子类中,实现具体逻辑,这里比如,当角色进入门的collision box后开门,离开后关门。
这里涉及到父子类之间的继承复用。
组合继承
两个没有直接关系的蓝图之间要进行通信,可以考虑面向对象中的组合继承,比如有一个拉杆和一个门,拉杆的两个状态(激活/停用)想要分别调用门的Open和Close,因为拉杆是负责触发的一方,需要调用门的接口,而门并不需要知道触发的逻辑。
这里添加BP Door lever这个门的对象引用到lever中后,要指定是哪个具体的对象实例。
类型转换
如果地面上有一个触发器,当player站上去会打开门,而其他物品,类似于一个球在上面不会开门,这该怎么做?
直觉来看,直接检测触发器的other actor是否与player character相等,如果相等则open door,具体opendoor与刚才的组合中相同。
但是这有着局限性,如果游戏中只有一个玩家角色,那么 Get Player Character 是最快捷的方法。你只需要对比 Other Actor 和 Get Player Character() 的返回值是否相同即可。当玩家类是固定的,且不会有其他子类时,Get Player Character 检查是否相等是一种简化方法。
也就是说,get player只能响应玩家,不能检查其他对象类型,要求游戏中只有一个固定的玩家类。
所以这里有一个类型转换Cast To ,将触发的other actor转换为这里的具体的player类型,如果转换成功,那么就是角色,打开门,否则表示出发的不是角色类。
可以在cast之后直接判断是否cast成功,如果成功则直接开门。
但是新的问题,如果想要知道这里重叠的actor到底是什么actor,那么就要进行更多的一系列的cast
[Event BeginOverlap]
→ Other Actor → Cast To PlayerCharacter
→ 成功(True):触发玩家逻辑(如开门)
→ 失败(False):Cast To EnemyCharacter
→ 成功(True):触发敌人逻辑(如报警)
→ 失败(False):Cast To Ball
→ 成功(True):触发物理效果(如推动门)
这样可以考虑使用蓝图接口。
蓝图接口
蓝图接口(Blueprint Interface)是 Unreal Engine 中一种设计工具,用于实现类之间松耦合的通信。通过蓝图接口,不同的类(包括蓝图类和 Actor)可以实现相同的方法,而不需要直接引用对方,从而提高代码的灵活性和复用性。
定义接口
- 蓝图接口本身并不包含逻辑,只定义了方法的名称和参数。
- 实现接口的类可以定义具体的实现逻辑。
实现接口
- 任意蓝图类(Actor、组件、Widget 等)都可以实现一个或多个接口。
- 实现接口后,该类需要为接口定义的方法提供具体的实现。
调用接口
- 调用接口的方法时,调用方并不需要知道目标对象的具体类型,只需要确认其实现了接口。
如何理解?
蓝图接口是方法的集合,但它本质是一个 资产类型,通过 UE 的反射机制管理,并被蓝图或 C++ 类实现。
创建一个蓝图接口对应于在 UInterface 和 IInterface 中定义一个接口类及其纯虚函数。
蓝图接口定义的方法就是这些纯虚函数的蓝图化表示,而蓝图类通过实现这些方法来提供具体的功能。
所以创建一个蓝图接口,就相当于再某个类中(UInterface,IInterface)中创建了一个纯虚函数,使用蓝图接口,只需要在需要的蓝图类中继承,并实现这个接口。先看看如何使用。
使用蓝图接口
如果有一个trigger,当他被触发后,做出某些行为,触发由当前控制的pawn来进行Interact,此处有一个已经声明的蓝图接口,那么直接在trigger的蓝图中,继承这个接口,然后实现它,就可以调用这个接口:
接下来试着创建一个蓝图接口。当创建一个蓝图接口时,会发现是只读的,蓝图接口是只读的,你实际上只是在定义它的含义标识,以及其如何被调用,这就对应了之前的理解,此处只是在定义这个纯虚接口,不用去实现它。
理解了蓝图接口,下来创建一个接口。当前是想要在玩家与触发器交互后,触发器检测玩家是否Has Item,然后决定是否通过的一个流程。
创建接口
如果要使用蓝图接口,先创建一个蓝图接口,因为想要检测交互的对象是否有Item,所以为这个蓝图接口添加一个输入和输出。
想要的效果是,检测这个Item是触发器的工作,所以触发器获取与之交互的对象后,通过调用对方的这个接口,来获取一个结果,所以这个蓝图接口需要一个输出表示结果(Has Key?)
而输入,这里只是简单演示,所以可以直接输入一个字符串作为key,
这个蓝图接口就设计好了。
实现接口
根据这个逻辑,有了一个接口,要在需要的地方去实现它,HasItem的目标是玩家,所以在玩家的蓝图中,继承这个接口,并实现它。 具体实现:
这里的字符串值就相当于玩家物品栏的物品,此处设为"key"表示含有这个物品.
调用接口
现在,这个接口的一个实现已经完成,要在什么时候去调用它呢.这时候就回到触发器了,触发器去检测,所以要在触发器中调用它.(如果其没有实现这个接口,则不会有任何效果)
在这里我们发现,调用HasItem需要有一个target,也就是让谁去检测HasItem,所以要想办法获取这个actor.
之前部分,一个已实现的表示交互的接口Interact with的实现中,触发器只是触发了这个事件,在玩家的实现中,有这样一部分内容:
获取与互动胶囊体重叠的actor的对象,这里也有Interact With,表示用户的交互,这里如何理解呢,在获取重叠的actor之后,存在了Array中,在For each Loop之后,调用了对应的actor的Inter with接口,具体来说就是,玩家与触发器重叠后,这里获取到了触发器的actor对象引用,然后通过这个对象引用去调用触发器的交互接口.
那么这里可以调整这个接口的声明,为这个接口添加一个输入,实现中把玩家的self传入,这样就可以在触发器中获取到这个引用了.
这种使用蓝图接口比之前cast to更灵活,如果需要检查不同的类型,可以直接获取,而不需要不断地cast to来检测.不过这些都是1-1的关系,如果是多个事件触发一个行为这种情况呢?
事件分发器
事件分发器(Event Dispatcher)是 Unreal Engine 中的一个机制,用于在不同的对象之间传递和触发事件。这种机制可以让一个对象通知其他对象某个事件发生了,从而实现解耦和跨对象的通信。
事件分发器的工作原理:
- 事件分发器就像一个“广播站”,它允许一个对象(例如,玩家角色、关卡中的某个物体等)发布一个事件(例如玩家死亡、物体被击中等),而其他对象可以“订阅”这个事件并做出反应。
- 其他对象可以通过绑定(Bind)事件来接收这些通知,当事件触发时,它们会执行相应的逻辑。
它可以存在于任何游戏中的对象上,用于不同对象之间的事件通信,帮助实现更加灵活和解耦的交互逻辑。
使用事件分发器
当前的目标是,通过收集所有物品后,打开门.当前有已有一个游戏模式蓝图.
当注册可收集物品后,Collecibles Left++,收集可收集物品后,Collecibles Left--,我们 希望在屏幕上先显示出当前的可收集物品的数量,可以创建一个事件分发器.
创建一个CollectibleCountUpdated事件分发器,当这个事件分发器被调用时,所有被绑定/注册或者正在监听的事件都会接收到通知.所以在Collecibles Left++/--后调用事件分发器.
现在来创建一个事件分发器事件,然后将事件绑定在事件分发器内,(或直接分配事件,等效于创建并绑定),绑定之后,这个事件就会被分发器监听,当接收到消息后就会执行事件开始的流程
显示在UMG
打开UMG蓝图,想要将Count显示在UI上,可以通过事件分发器,由于事件分发器被定义在游戏模式蓝图,所以|获取游戏模式->Cast To GM_*** ->绑定一个自定义响应事件,在事件触发后设置UI文本即可,但是用什么来设置文本呢,可以在事件分发器的创建中添加一个输入,在调用分发器的部分传入参数.
打开大门
最后我们要打开最终的门,条件是count<=0的时候,但是在GM中好像没有办法直接获取到这个门的引用(除了get actor),蓝图接口也一样
-
在关卡蓝图中呢 倒是可以获取游戏模式蓝图的引用,将收集完成设置为一个状态,在关卡蓝图中每帧检测
-
可以使用事件分发器 当<=0时,调用事件分发器,在Final_door 中获取游戏模式,转换对应类型,注册事件分发器,打开门即可,需要注意的是,这里的事件分发器需要的是一个新的事件分发器,而不能用之前的,如果在之前的事件调用后继续调用分发器,会发生无限嵌套调用导致栈溢出.
-
还可以使用Get Actor来获取对象的引用
获取一个没有蓝图的actor
直接把这个actor的名字与other actor进行字符串之间===来判断是否是它