虚幻C++入门个人笔记——UMG网络
Posted hoppingg
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了虚幻C++入门个人笔记——UMG网络相关的知识,希望对你有一定的参考价值。
UMG系统
虚幻运动图形界面设计器Unreal Motion Graphics UI Designer。主要用来创建UI元素,例如游戏中的HUD,菜单,展示动画UI等
UMG的核心是控件,这些控件是一系列预先制作的函数,可用于构建界面(如按钮、复选框、滑块、进度条等)。这些控件在专门的控件蓝图中编辑,该蓝图使用两个选项卡进行构造:设计器(Designer)选项卡允许界面和基本函数的可视化布局,而图表(Graph)选项卡提供所使用控件背后的功能
控件
控件是UMG系统中提供给用户用来交互的单元体。它包括输入交互(个别控件无输入)与展示交互。主要目的是将数据逻辑以合理的方式展示给用户,或是从用户处获得合理的输入,用于控制数据等。
虚幻中的UMG构建界面均是从控件开始着手进行的,虚幻中提供了大量的控件以满足我们的设计需求,同时也支持我们自定义控件来完成特殊的需求定制
创建UMG
蓝图构建UMG
虚幻中的UMG构建可以直接在内容浏览器中右键进行构建,构建内容为控件蓝图,蓝图的基本操作单元。可以通过创建控件(实例化一个UMG控件)和Add to Viewport(添加到视图渲染序列)(Add to PlayerScreen是联网用的)两个节点进行显示在用户窗口
锚点
位置结构
在我们制作UI时,应该尽量考虑UI针对不同的视口比例下的对齐关系,应保证在多种比例视口中都能得到非常规整的对齐方式。因此,依靠布局来管理控件成为了制作UI时的不二之选
在多种布局中,控件和控件的位置关系是有规则的(横向,纵向,格子,自由),我们将控件对齐时用到的参考位置称之为锚。
用来定义控件在画布面板上的预期位置,并在不同的屏幕尺寸上维持这一位置
控件的锚点受制于空间的父级容器特点,并不是所有控件都会具备锚点特性
相对和绝对关系
在构建设计UI时,我们尽量避免使用绝对关系,这可能会导致视口比例发生变化时,控件被遮挡或被隐藏。我们构建空间位置关系时,应尽量依赖父类容器控件的特点,使用相对位置关系,这样可以更好地帮助我们设计UI,并且达到最优信息展示的目的
相对位置关系,锚点对齐时使用其他控件位置作为参考,参考控件移动,自身也发生移动
绝对位置关系,以坐标为参考依据,不受视口变化影响,永远在视口的对应坐标位置
锚点预设方式
锚点产生与所在的父类容器控件类型有关,大体分为三种
自由容器插槽方式
9点方向对齐
横向三拉伸
纵向三拉伸
双方向拉伸
堆叠容器插槽方式
左对齐,右对齐,中对齐,横向填充
上对齐,下对齐,中对齐,纵向填充
槽
虚幻中的槽是与锚点配合,构建了一套控件对齐解决方案。槽决定了控件的位置结果。槽会根据控件所在的父节点控件类型而定。如果需要动态调整控件在视口中的位置,需要先获取到槽后再进行
DPI
DPI时设计中差异化用户界面体验中的重要组成部分。DPI主要描述了每英寸的像素数量。在UI设计中,低分辨率和高分辨率的显示效果是不同的。但是适配终端时,我们需要考虑多终端的适配问题,为了解决此类问题,虚幻中指定了以DPI为基准的缩放规则。这使得UMG支持与分辨率无关的UI设计,即UI可以不依照分辨率的变更而变得无法使用
DPI设置
DPI设置可以在项目设置-User Interface(用户界面)中找到
DPI的缩放规则
最短边-该选项将基于窗口的最短边来评估缩放曲线(最常用的设置)
最长边-基于窗口的最长边来评估缩放曲线
水平-基于窗口的X轴来评估缩放曲线
垂直-基于窗口的Y轴来评估缩放曲线
样式图片填充
九宫格填充法,把一张图片分成3*3的九份,四个角的位置不拉伸,拉伸其他位置的不大会影响图片纹理的地方(横纵向,中间是斜着拉伸,如果不想要中间则将其绘制为边界,要中间则盒体,图片为整张图片进行拉伸)
自定义控件
在虚幻中,我们编写的所有控件均可以被当作另一个控件的子内容使用,这使得我们可以灵活地去编写多样的控件内容。将整体的UI进行拆解,然后构建称为多个独立地控件,再进行组合。可以更加方便地完成控件之间地关联和扩展
Named Slot命名的插槽
在编写控件时,我们可以使用Named Slot将一部分空间进行预留,当构建的控件被当作其他控件的子控件时,预留空间将被显示。从而可以达到在另一个控件中继续向已经构建好的控件中添加内容
1.创建一个自定义控件
2.在里面加Named Slot插槽(下面不要加东西)
3.调整位置
4.在另一个控件中添加上述的自定义控件,在Named Slot下面加自己想要的东西
Menu Anchor菜单锚
用来构建弹出菜单区域,将弹出内容对齐到区域中(对齐方式多种),需要构建自定义的控件进行使用。如需要显示菜单,需要调用Open节点,关闭调用Close节点。注意弹出菜单与MenuAnchor无关联关系,如需要进行关联,需要额外编写逻辑
步骤:
1.加MenuAnchor
2.加按钮
3.创建按钮点击时事件,并连上MenuAnchor的Toggle Open节点(切换开启)
4.在设计器的MenuAnchor中的菜单轴点中的菜单类设置为你想要弹出的自定义控件类
5.设置放置位置
List View列出视图
用来帮助我们构建更加高效的列表缓冲UI。在传统的List中,存在严重的性能消耗问题(多个信息面板中只有数据部分存在差异,其他渲染逻辑相同,但是却构建了更多的渲染面板对象),List View从设计之初考虑使用单一渲染单元体,将数据进行高效缓存,以增加了列表面板展示的高效性。我们可以通过更加简单的方式构建高效的滚动列表界面。例如,好友列表、商品列表等
List View列出视图构建过程
1.提供入口自定义控件
构建自定义控件类,然后在图表中选择类设置,添加接口User Object List Entry
2.设置ListView展示控件
将构建好的组件设置到List View中
3.构建展示数据存储对象结构
构建数据记录对象,继承自Object,并向对象中添加数据成员
4.添加展示数据到ListView中
借助Construct(事件构造)函数,将构建好的数据填充到List View中。注意For循环只是为了测试,增加更多的数据元素
forloop-从类构建对象(类选Data类,Outer连Self)-设置Data类中的数据成员-(ListView的)添加项目(Item连Data类)
5.更新面板数据
在自定义控件的图表面板中,将展示面板对象中继承的接口函数On List Item Object Set(事件列表项目对象集上)进行显式重写,然后将函数中传递的参数数据转换到声明的数据记录对象,从中获取Num变量值(或是你要的数据)(需要先将其在设计器中选中数据类型,细节面板上的Is Variable狗勾上),最后进行UI更新工作
事件列表项目对象集上-类型转换Data类-设置
3D UI
Widget组件
虚幻中提供了以重方便构建空间UI的解决方案,借助组件Widget(控件组件)用来将UMG控件展示在空间中(World和Screen),借助组件Widget Interaction(控件交互组件)来完成3D交互
模拟点击事件
借助组件Widget Interaction进行模拟交互,完成点击事件交互。注意控件的响应一般是响应抬起事件
模拟键盘事件
按键输入模拟Press Key/Release Key为输入框进行输入模拟,如同Send Key Char这不是当敲击来使用,而是当作字符输入来使用
输入字符Send Key Char向界面输入文本内容
1.新建一个Actor蓝图类,在里面加Widget控件组件
2.点击控件 在控件细节 用户界面 控件类设置为你需要的控件
3.放置到场景中 实例化
4.在角色上加Widget Interaction控件交互组件放到吊臂下面,交互距离改大一点
5.下图模拟事件
UMG数据绑定
数据绑定时一定要确定对象是否为空,例如使用对象的数据绑定,但对象在创建出来的时候可能会为空
三种方法
1.绑定函数(会实时调用更新UI)
2.绑定数据(会实时调用更新UI)
3.写逻辑(在图表里写新函数,在逻辑触发时调用该函数设置数据变化)
自定义控件交互
构建调度器暴露外部事件
在编写控件时,如果在控件中构建了调度器,那么当此控件被当作其他控件的子控件时,调度器将会被当做事件直接暴露到另一个控件中。可以直接在细节面板中选中实现控件
暴露样式
有时我们需要将自定义控件中的控件样式暴露到外部,让使用此控件的人来决定控件样式。那么我们可以在控件中直接构建控件样式属性,我们可以直接点击暴露属性。当此控件被添加到其他控件中时,属性将能在细节面板中看到,然后我们在自定义的控件中,将样式应用即可
构建属性,选择类型,并将属性暴露到外部,当控件添加到其他面板时可以看到控件样式设定,启动时设置样式
UI动画
UI设计器面板直接点加动画
然后只要有一个菱形右上角一个加号的属性值都可以K帧
支持自动关键帧,支持曲线调整
调用:在UI图表里某个响应事件后(如Button点击后)直接拖出动画变量,加播放动画
控件拖拽
拖拽逻辑
步骤:
1.构建拖拽数据对象
拖拽数据是用来将控件从已在位置向其他位置转移时的参考。控件在拖拽时并没有真正的离开父容器,而是只在抬起时决定是否离开(用户误操作)。在拖拽过程中,拖拽数据帮助进行了有效的桥接
创建蓝图类DragdropOperation,用于在拖拽过程中进行拖拽交互,拖拽交互数据均来自此类
在其中新建一个Vector2D变量来记录鼠标位置偏移
2.开启拖拽检测
开启拖拽检测
当鼠标或是其他事件发生时,可以在控件中调用监听拖拽产生,当产生拖拽时才开始执行对应的逻辑
在被拖拽的控件中重写OnMouseButtonDown(按下鼠标按钮时),在其中连接节点Detect Drag if Pressed(按下时侦测拖动),Mouse Event连Pointer Event,下面鼠标左键
目的:当控件受到点击事件时,进行拖拽检测,如果使用的是鼠标左键,则响应拖拽事件
3.响应抬起事件
3.1如果希望控件拖到其他控件上,需要先将对方控件的上层容器可视性开启可视,以便能够正常地接受鼠标拖拽事件,并将事件传递给子控件
3.2响应拖拽检测
当产生拖拽时,需要重写拖拽函数,完成响应,并且构建拖拽数据
在被拖拽的控件中重写On Drag Detected(发现拖动时),并用拖动操作数据连上右边,可以用系统的,创建拖放操作(但鼠标偏移会失效),用自己的
Payload用于在响应抬起时,进行数据交互,Visual是拖拽时想要显示的虚拟体(都连Self)
4.检测是否存在拖拽
重写OnDrop(放置时)函数
注意:由于DPI影响,GetScreenSpacePostion是有缩放的(基准比率为1920*1080,是1:1),所以屏幕小的时候,获取的位置会被放大,使用ATL转换到当前控件坐标,由于转换过程中没有忽略DPI,所以在设置的时候需要去掉Remove DPIScale选项
5.完成拖拽结果
C++中完成控件的操作和交互
获取编辑器创建控件
获取方案需要控件是在C++中进行构建
1.通过名称获取控件
例子
//重载本地构造函数
void UUserWidget_MainUI::NativeConstruct()
Super::NativeConstruct();
ButtonClick= Cast<UButton>(GetWidgetFromName(TEXT("Button_1")));
if (ButtonClick)
ButtonClick->OnClicked.AddDynamic(this, &UUserWidget_MainUI::ButtonClickEvent);
void UUserWidget_MainUI::ButtonClickEvent()
UCanvasPanelSlot* CPSlot = Cast<UCanvasPanelSlot>(ButtonClick->Slot);
if (CPSlot)
//按下按钮后按钮向右位移50
CPSlot->SetPosition(FVector2D(CPSlot->GetPosition().X + 50, CPSlot->GetPosition().Y));
UE_LOG(LogTemp, Log, TEXT("aaa"));
2.通过宏绑定控件(控件类型和名称必须和蓝图添加的一致,并且在蓝图中必须添加同名同类型控件)
UPROPERTY(BlueprintReadWrite,meta=(BindWidget))
class UButton* NativeButton;
获取编辑器创建动画
动画获取和控件有很大区别,我们不能直接通过名称的方式或是宏绑定方式,但是我们可以通过以下两种方法获得
1.在C++中构建动画对象参数,并使用宏进行说明,在蓝图中进行设置
在蓝图中进行绑定(BPAnimation是在蓝图中构建)
UPROPERTY(BlueprintReadWrite)
class UWidgetAnimation* WidgetAnim;
if (WidgetAnim)
PlayAnimation(WidgetAnim);
记得在蓝图里Set以下WidgetAnim;
2.通过反射机制,直接读取蓝图中添加的成员参数
运行时态获取类的内容叫反射,通过文本串的方式获取到类的成员
void UUserWidget_MainUI::NativeConstruct()
Super::NativeConstruct();
//获取当前类中的成员对象指针链表
UProperty* Property = GetClass()->PropertyLink;
while (Property)//只要成员属性链表中还有就继续循环
//只会获取成员内容是成员属性的对象
if (Property->GetClass()==UObjectProperty::StaticClass())
//将其转换为成员属性
UObjectProperty* ObjectProperty = Cast<UObjectProperty>(Property);
//如果是动画的成员属性
if (ObjectProperty->PropertyClass==UWidgetAnimation::StaticClass())
//拿到成员属性有UWidgetAnimation的UObject对象
UObject* ObjAni = ObjectProperty->GetObjectPropertyValue_InContainer(this);
//转换为该UObject对象的WidgetAnimation成员属性
UWidgetAnimation* WAni = Cast<UWidgetAnimation>(ObjAni);
//如果其名字是这个
if (WAni->GetDisplayName().ToString()==TEXT("Animation01"))
//设置成员属性class UWidgetAnimation* WidgetAnim
WidgetAnim = WAni;
break;
//成员属性等于链表中的下一个成员属性
Property = Property->PropertyLinkNext;
UE网络
帧同步和状态同步
(1条消息) 状态同步和帧同步_choudan8888的博客-CSDN博客_帧同步和状态同步
网络游戏构成必要条件
1.终端用户
2.联网硬件环境
3.伺服器(服务器)
4.数据传输通信协议
虚幻网络
虚幻4使用标准的Listen-Server体系结构。这意味着,服务器是权威的,所有数据必须先从客户端发送到服务器。然后服务器验证数据并根据代码做出反应
主机有:GameMode,GameState,PlayerController,PlayerState
客机有:GameState,PlayerController,PlayerState
GameMode在UE网络中,只有主机持有一份(游戏规则只应服务器有一份,否则会作弊)
GameState在UE网络中,所有终端均持有并且相同(游戏状态双方都要看到)
PlayerController在UE网络中,所有终端均持有,但每个终端并不相同(每个终端操控自己的角色)
PlayerState在UE网络中,所有终端均持有,但每个终端并不相同(记录每个玩家的状态)
Actor的类默认值里如果勾选了客户端上的网络加载,那么就会在开始时同步各个终端
Client端修改Actor信息无法同步到其他终端,但服务器可以修改
UE中的网络对象归属分类
Server Only-这些对象仅存于服务器上 AGameMode
Server&Clients-这些对象存在于服务器和所有客户端上 AGameState/APlayerState/APawn
Server&OwningClient-这些对象仅存在于服务器和自身客户端上 APlayerController
Owning Client Only-这些对象仅存在于自己客户端 AHUD/UMG Widgets
角色类型标记属性
每个Actor中都存在两个属性用来裁定当前Actor在服务器/客户端上的身份类型
Get Local Role我看我自己是啥样的身份
Get Remote Role别人看我是啥样的身份
两个属性存在于Actor上,用于标记Actor在网络上的复制关系:
1.Actor的主控权在谁手里(拥有主动权方才可进行复制)(role类型是authority)
2.当前Actor是否被复制(类型是None则没有被复制)
3.复制的模式(复制模式两种:一种是模拟Simulated,一种是自主Autonomous)
网络角色类型
Simulated
由服务器进行数据发送,当前终端进行操控模拟,操控来源于服务器(这个人的运动全都是服务器下发)
Autonomous
由当前终端实例进行操控,操控来源于真人(这个人的运动是玩家和服务器的综合模拟)
Authority
服务器端存在标记,表明当前Actor存在于服务器
LocalRole类型分布 | ||
服务器 | 客户端 | |
Simulate | 不存在 | 服务器在当前引擎实例中模拟的玩家角色 |
Autonomous | 不存在 | 当前引擎实例中由真人操控的角色 |
Authority | 服务器拥有复制权限的权威性 | 不存在 |
RemoteRole类型分布 | ||
服务器 | 客户端 | |
Simulate | 当前Actor在远端是模拟的身份 | 不存在 |
Autonomous | 当前Actor在远端有自主权操控权 | 不存在 |
Authority | 不存在 | 服务器拥有复制权限的权威性 |
行为同步
Remote Procedure Call
RPC,远端调用,指在本机上调用函数,但在其他机器上远程执行的函数。RPC函数可以允许客户端或服务器通过网络连接相互发送消息
RPC执行分三种模式
服务端执行(Server)在客户端调用,在服务器端执行(只有LocalRole类型是Authority或Autonomous才可调用)
客户端执行(Client)在服务器端调用,在客户端执行(只有LocalRole类型是Authority才可调用)
所有终端执行(Multicast)在服务器端调用,在所有终端执行(只有LocalRole类型是Authority才可调用)
RPC调用注意事项
1.他们必须从Actor上调用
2.Actor必须被复制
3.如果RPC是从服务器调用并在客户端上执行,则只有实际拥有这个Actor的客户端才会执行函数
4.如果RPC是从客户端调用并在服务器上执行,客户端就必须拥有调用RPC的Actor(客户端的LocalRole类型是Autonomous)
5.多播RPC则是个例外:
-如果他们是从服务器调用,服务器将在本地和所有已连接的客户端上执行它们
-如果他们是从客户端调用,则只在本地而非服务器上执行
-现在我们有了一个简单的多播事件限制机制:在特定Actor的网络更新期内,多波函数将不会复制两次以上。按长期计划,我们会对此进行改善,同时更好的支持跨通道流量管理与限制
操控角色之间的身份
服务端 | 客户端 | |
Simulated | 不存在 | 服务器在当前引擎实例中模拟的对象 |
Autonomous | 不存在 | 当前引擎实例具有操控权的对象 |
Authority | 在服务器存在的角色 | 不存在 |
RPC调用情况分析
勾选了复制的Actor的拥有权是服务器
从服务器上调用RPC | ||||
Actor所有权 | 未复制 | NetMulticast(组播) | Server(在服务器上运行) | Client(在拥有的客户端上运行) |
Client-Owned Actor | 在服务器上运行 | 在服务器和所有客户端上运行 | 在服务器上运行 | 在Actor的所属客户端上运行 |
Server-Owned Actor | 在服务器上运行 | 在服务器和所有客户端上运行 | 在服务器上运行 | 在服务器上运行 |
Unowned Actor | 在服务器上运行 | 在服务器和所有客户端上运行 | 在服务器上运行 | 在服务器上运行 |
从客户端上调用RPC | ||||
Actor所有权 | 未复制 | NetMulticast(组播) | Server(在服务器上运行) | Client(在拥有的客户端上运行) |
Owned by Invoking Client | 在执行调用的客户端上运行 | 在执行调用的客户端上运行 | 在服务器上运行 | 在执行调用的客户端上运行 |
Owned by a Different Client | 在执行调用的客户端上运行 | 在执行调用的客户端上运行 | 丢弃 | 在执行调用的客户端上运行 |
Server-Owned Actor | 在执行调用的客户端上运行 | 在执行调用的客户端上运行 | 丢弃 | 在执行调用的客户端上运行 |
Unowned Actor | 在执行调用的客户端上运行 | 在执行调用的客户端上运行 | 丢弃 | 在执行调用的客户端上运行 |
如果你想做的效果是触发式的,比如每个角色到那里都会开门,就直接写碰撞盒子触发,没有必要写同步,如果你想做的效果是随机的,那么就要用Switch Has Authority这个宏来调用一个带随机参数的事件,Actor为复制,事件的复制类型也要改
只有那些在其他玩家需要观察或者影响玩家逻辑情况下才需要广播同步
别人不去依赖的数据就不用广播
RPC总结
1.调用时必须保证Actor具有操控权(客户端需要写一个Server的SetOwner(用get controller连)事件),如果在服务器端则可以直接调用
2.如果客户端想要广播某一事件,必须先调用具备服务器运行函数,由服务器再进行广播
3.RPC在蓝图中需要借助自定义事件进行使用
4.RPC传递Actor对象时,需要保证Actor在网络上是被复制的,勾选了复制的Actor的拥有权是服务器
5.RPC默认情况下是不可靠的(为了放置网络饱和,在频繁通讯时会出现丢失),需要根据设计情况开启Reliable
6.RPC调用需要注意调用Actor的从属关系和调用位置
属性同步
首先Actor必须满足在网络上被复制,设置的参数需要开启复制,参数的修正必须在服务器端修改,才可以在网络上同步
蓝图参数同步有两种方式Replicated,RepNotify
Replicated,同步数据,但没有通知,无法直接通过参数修改驱动逻辑(需要Tick时刻更新数据)
RepNotify,同步数据,并生成通知函数,进行更新通知(向所有终端通知,满足相关性)
蓝图中属性复制条件
属性复制被注册后,无法进行取消。所有属性默认的复制条件是:不发生变化不进行复制。我们可以使用UE提供的条件进行修正,通过条件的约束设定可以有效地节约网络宽带
已提供的条件:
1.COND_InitialOnly该属性仅在初始数据组尝试发送 玩家名字
2.COND_OwnerOnly该属性仅发送至actor的所有者 还剩多少子弹
3.COND_SkipOwner该属性将发送至除所有者之外的每个连接
4.COND_SimulatedOnly该属性仅发送至模拟actor
5.COND_AutonomousOnly该属性仅发送给自主actor
6.COND_SimulatedOrPhysics该属性将发送至模拟或bRepPhysics Actor
7.COND_InitialOrOwner该属性将发送初始数据包,或者发送至Acotr所有者
8.COND_Custom该属性没有特定条件,但需要通过SetCustomIsActiveOverride得到开启/关闭能力
网络关联性
UE网络模块考虑到通信带宽问题,针对场景中的所有Actor数据更新制定了高效的同步方案,根据Actor的网络关联性(Network Relevancy),进行数据更新,这样可以有效规避无用数据更新造成通信带宽压力
关联性用途:用来解决同步过程中无关数据的传递带宽的压力,用来筛选有意义内容进行同步,表现同步信息是否同步给目标,默认的时候是使用距离来进行关联——网络剔除距离平方(服务器不会剔除,客户端上才有体现)
关联性原则
1.AlwaysRelevant,相关性最大,勾选后,所有终端永远会收到此Actor同步信息(信号弹,补给)
2.NetUseOwnerRelevancy,如果有拥有者Owner,则使用所有者的相关性(包括剔除距离约定)。此检查在第一项之后完成(枪)
3.OnlyRelevantToOwner,只与当前所有者相关,同步数据只发给所有者。不发送任何人,非所有者不同步
4.Actor出现依附关系,则关联性取决于基础的Actor相关性
5.如果Actor不可见(bHiden==true)并且Root Component没有碰撞,则不具备相关性
6.采集距离(Net Cull Distance Squared)在采集范围内具备关联性
7.约束层级关系(如RPC勾选了Reliable则关联性失效)
联网切换地图
虚幻的切换关卡有三种
1.完全切换关卡
2.关卡流
3.开放性世界
完全切换关卡
有缝切换(会卡在原地)
主机先断开连接,无法带数据
Execute Console Command(执行控制台命令)
ServerTravel/Game/路...径/地图名字?listen
?listen的意思是切换到的目标地图会被当作新的服务器地图来使用
裁定告诉服务器,让所有人移动到那个关卡去(必须在服务端调用)
步骤:
打包项目,
打包完后
将当前终端改成服务器模式open/Game/路...径/地图...名字?listen
例子:open/Game/Maps/Main?listen
把自己电脑连到服务器上 open 127.0.0.1
//127.0.0.1是默认当前电脑的IP,如果项目拷到了别人电脑上就直接输他的IP就好了
退出输Exit
OpenLevel
不会再次构建连接模式,只是一个人去了那个关卡
主机会把所有人踢下线自己过去,客户机会自己断开连接自己过去
会报错
无缝切换(尽量使用无缝模式,体验更好)
在切换关卡时所有主机保持连接,可以带数据
在Gamemode中类设置里勾选使用无缝漫游(开启了这个指令后就没法再使用ServerTravel指令了只能用OpenLevel)
携带数据属性过关卡
PlayerController,PlayerState
PlayerState里重载函数CopyProperties,先调用其父类函数,然后转换到自己的PlayerState然后再设置你需要传递的参数,这样在关卡变换时数据就完成了传递
记得在Beginplay里再把数据设置上去
关卡流
解决切换关卡时会把别人也带过去的问题
Load Stream Level接获取所有playerstart类has tag然后瞬移
关卡流送体积(可以在关卡窗口的详细信息中进行关卡流送体积的绑定,当摄像机在的时候就会加载关卡流,摄像机离开就会卸载关卡流)
开放性世界
world machine
world creator
1.导地形
2.瓦片每次加载4张地图瓦片,角色超出一定距离自动卸载(需要先将瓦片地图导入进来,需要是一个新文件夹,里面是一张地图、一个文件夹装瓦片资源)
在UE网络中使用C++
RPC
在CPP中我们使用RPC的复杂度要高于在蓝图中,在CPP中我们需要借助宏UFUNCTION进行函数表及,实现远程调用,标记方式为Server、Client、NetMulticast
RPC的函数除了_Validate不能有返回值,因为其为异步通信
可靠性为网络波动时该函数是否必定会执行
On Server
标记函数时必须要标记函数RPC的可靠性(Reliable或Unreliable),并且需要实现对应函数名加_Implementation后缀的函数,将逻辑放入后面的函数中。在客户端调用的函数需要加入验证操作,用来检测输入数据的准确性(加_Validate的定义的return true的函数)
//.h中
UFUNCTION(Server,Reliable,WithValidation)
void ServerChangeNum(); //不用定义,调用时调用此函数
void ServerChangeNum_Implementation(); //调用逻辑写在其定义里
bool ServerChangeNum_Validate(); //return false会直接踢出服务器
On Client
其只能在服务器调用
标记函数时必须要标记函数RPC的可靠性(Reliable,Unreliable),并且需要实现对应函数名加_Implementation后缀的函数,将逻辑放入后面的函数中
//.h中
UFUNCTION(Client,Reliable)
void ClientChangeName(); //不用定义,调用时用这个
void ClientChangeName_Implementation(); //需要定义在里面写调用时的逻辑
NetMulticast
//.h中
UFUNCTION(NetMulticast,Reliable)
void MulticastChangeName(); //不用定义,调用时用这个
void MulticastChangeName_Implementation(); //需要定义在里面写调用时的逻辑
参数同步
参数同步需要将参数注册到复制参数列表,借助UPROPERTY宏进行标记,并且参数同步操作必须在服务器端进行修正,客户端直接修改无法达到同步目的,需要借助RPC里的Server
UPROPERTY(Replicated) 标记参数为复制参数
UPROPERTY(ReplicateUsing=函数名) 标记参数为复制参数,复制操作会回调函数(向除去服务器外所有终端进行通知,必须满足相关性)(蓝图中时服务端客户端都会调用)
UPROPERTY(Replicated,BlueprintReadOnly)
int32 TNum;
UPROPERTY(ReplicatedUsing=NotifyTNum2Change)
int32 TNum2;
UFUNCTION()
void NotifyTNum2Change();
标记完成后需要绑定到复制参数列表
//.h中
//同步的逻辑绑定需要在该函数定义里执行
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
//.cpp中GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const定义里
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(A类型名,TNum); //Replicated的属性TNum
DOREPLIFETIME(A类型名,TNum2); //ReplicatedUsing的属性TNum2
所有需要同步的参数都需要绑定到复制参数列表,以便引擎可以方便地进行管理,绑定需要实现函数GetLifetimeReplicatedProps,并使用宏DOREPLIFETIME进行注册
为复制参数增加条件
在进行绑定参数过程中,我们可以通过宏DOREPLIFETIME_CONDITION进行条件添加,可以方便优化,增加对属性复制的控制
DOREPLIFETIME_CONDITION(A类型名,参数名,COND_InitialOnly);
已提供的条件:
1.COND_InitialOnly该属性仅在初始数据组尝试发送 玩家名字
2.COND_OwnerOnly该属性仅发送至actor的所有者 还剩多少子弹
3.COND_SkipOwner该属性将发送至除所有者之外的每个连接
4.COND_SimulatedOnly该属性仅发送至模拟actor
5.COND_AutonomousOnly该属性仅发送给自主actor
6.COND_SimulatedOrPhysics该属性将发送至模拟或bRepPhysics Actor
7.COND_InitialOrOwner该属性将发送初始数据包,或者发送至Acotr所有者
8.COND_Custom该属性没有特定条件,但需要通过SetCustomIsActiveOverride得到开启/关闭能力
虚幻引擎UI组件开发快速入门UMG/Slate
Unreal 的 UMG 提供了相当多的UI Widget,并允许你在蓝图中做很多事情。但是,有时可能仍需要创建自定义Widget来填补空白。这篇文章中将介绍使用自定义渲染创建Slate Widget的基础知识。
UMG Widget涉及两个类:
- 一个 UWidget 派生类,可以在编辑器中与之交互
- 一个 SCompoundWidget 类,处理低级功能和渲染
让我们看一个展示切片的简单UMG Widget,它具有给定的起始角度和圆弧大小。我们可以控制颜色和不透明度,并且可以选择使用图像来渲染它:
1、最小UMG Widget类
首先,我们将添加没有任何功能的类,并确保我们看到新的小部件出现在编辑器面板中。
这是一个 UMG 小部件的最小化头文件:
UCLASS()
class CUSTOMWIDGET_API USlice : public UWidget
GENERATED_BODY()
public:
virtual void ReleaseSlateResources(bool bReleaseChildren) override;
protected:
virtual TSharedRef<SWidget> RebuildWidget() override;
TSharedPtr<SSlateSlice> MySlice;
;
对应的CPP文件如下:
void USlice::ReleaseSlateResources(bool bReleaseChildren)
MySlice.Reset();
TSharedRef<SWidget> USlice::RebuildWidget()
MySlice = SNew(SSlateSlice);
return MySlice.ToSharedRef();
请注意,在创建新UMG Widget时,始终必须实现这两个方法 - RebuildWidget负责构建 Slate Widget, ReleaseSlateResources负责清理资源。
2、最小Slate Widget类
这是 UMG Widget类引用的 Slate 类:
class CUSTOMWIDGET_API SSlateSlice : public SCompoundWidget
public:
SLATE_BEGIN_ARGS(SSlateSlice)
SLATE_END_ARGS()
void Construct(const FArguments& InArgs);
;
以及对应的CPP文件:
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSlateSlice::Construct(const FArguments& InArgs)
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION和END_SLATE_FUNCTION_BUILD_OPTIMIZATION经常在Construct函数周围使用,因为它最终可能会产生一个非常复杂的表达式,编译器可能会花费大量时间来尝试优化。这些宏关闭并再次打开优化。
3、传递Widget属性
为了让我们的UMG Widget绘制切片,需要添加一些属性来确定它的外观。首先,我们将Brush、Angle和ArcSize添加到 UMG Widget:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Appearance")
FSlateBrush Brush;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Appearance")
float Angle;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Appearance")
float ArcSize;
我们还需要在 Slate 类中添加类似的属性。它们都作为参数添加到 SLATE_BEGIN_ARGS / SLATE_END_ARGS宏中,以及类中的常规成员变量:
SLATE_BEGIN_ARGS(SSlateSlice)
SLATE_ARGUMENT(FSlateBrush*, Brush)
SLATE_ARGUMENT(float, Angle)
SLATE_ARGUMENT(float, ArcSize)
SLATE_END_ARGS()
...
protected:
FInvalidatableBrushAttribute Brush;
float Angle;
float ArcSize;
RebuildWidget方法需要将这些属性从 UMG Widget传递给 Slate Widget:
TSharedRef<SWidget> USlice::RebuildWidget()
MySlice = SNew(SSlateSlice)
.Brush(&Brush)
.Angle(Angle)
.ArcSize(ArcSize);
return MySlice.ToSharedRef();
Slate Widget现在将这些属性作为Construct方法的参数获取:
void SSlateSlice::Construct(const FArguments& InArgs)
Brush = FInvalidatableBrushAttribute(InArgs._Brush);
Angle = InArgs._Angle;
ArcSize = InArgs._ArcSize;
在类头中定义的每个SLATE_ARGUMENT作为成员添加到FArguments结构中,并在其名称前加上下划线。Construct方法只是将这些值复制到类成员变量中,以便稍后在渲染Widget时使用它们。
4、Widget渲染
现在我们终于可以添加OnPaint方法了,它处理Widget的实际渲染。
在这一点上,我不会详细介绍所有参数 - 以下是我们在以下方法中引用的参数:
- AllottedGeometry为我们提供了Widget的位置和大小
- OutDrawElements接收用于渲染Widget的绘制元素
- nWidgetStyle为我们提供了应该应用的颜色和不透明度
FSlateDrawElement 是渲染 Slate Widget的主力。它具有绘制框、线、文本、样条线等的方法。我们将在这里使用顶点(在 2D 空间中)和索引来渲染任意网格。
我们需要为顶点填充 FSlateVertex 结构的 TArray,以及定义从顶点构建的三角形的 SlateIndex (uint32) 索引值的 TArray。
为了渲染切片,我们在中心添加一个顶点,然后沿弧添加边缘顶点。然后索引缓冲区使用中心和一对连续的边顶点定义三角形。每个顶点的颜色是通过使用通过小部件样式传入的颜色和不透明度以及小部件的整体颜色和不透明度来调制画笔颜色来计算的。
如果画笔使用图像,我们也可以为顶点设置 UV 坐标——我暂时不考虑它。
int32 SSlateSlice::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect,
FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle,
bool bParentEnabled) const
const FVector2D Pos = AllottedGeometry.GetAbsolutePosition();
const FVector2D Size = AllottedGeometry.GetAbsoluteSize();
const FVector2D Center = Pos + 0.5 * Size;
const float Radius = FMath::Min(Size.X, Size.Y) * 0.5f;
const FSlateBrush* SlateBrush = Brush.GetImage().Get();
FLinearColor LinearColor = ColorAndOpacity.Get() * InWidgetStyle.GetColorAndOpacityTint() * SlateBrush->GetTint(InWidgetStyle);
FColor FinalColorAndOpacity = LinearColor.ToFColor(true);
const int NumSegments = FMath::RoundToInt(ArcSize / 10.0f);
TArray<FSlateVertex> Vertices;
Vertices.Reserve(NumSegments + 3);
// Add center vertex
Vertices.AddZeroed();
FSlateVertex& CenterVertex = Vertices.Last();
CenterVertex.Position = Center;
CenterVertex.Color = FinalColorAndOpacity;
// Add edge vertices
for (int i = 0; i < NumSegments + 2; ++i)
const float CurrentAngle = FMath::DegreesToRadians(ArcSize * i / NumSegments + Angle);
const FVector2D EdgeDirection(FMath::Cos(CurrentAngle), FMath::Sin(CurrentAngle));
const FVector2D OuterEdge(Radius*EdgeDirection);
Vertices.AddZeroed();
FSlateVertex& OuterVert = Vertices.Last();
OuterVert.Position = Center + OuterEdge;
OuterVert.Color = FinalColorAndOpacity;
TArray<SlateIndex> Indices;
for (int i = 0; i < NumSegments; ++i)
Indices.Add(0);
Indices.Add(i);
Indices.Add(i + 1);
const FSlateResourceHandle Handle = FSlateApplication::Get().GetRenderer()->GetResourceHandle( *SlateBrush );
FSlateDrawElement::MakeCustomVerts(
OutDrawElements,
LayerId,
Handle,
Vertices,
Indices,
nullptr,
0,
0
);
return LayerId;
这允许我们以不同的大小和颜色渲染切片:
5、更新Widget属性
为了使编辑器正常工作,我们需要在 Slate 小部件中的属性在 UMG 小部件中发生更改时更新它们。我们通过重写SynchronizeProperties函数来做到这一点:
void USlice::SynchronizeProperties()
Super::SynchronizeProperties();
MySlice->SetBrush(&Brush);
MySlice->SetAngle(Angle);
MySlice->SetArcSize(ArcSize);
Slate 小部件需要以下 setter 函数:
void SSlateSlice::SetBrush(FSlateBrush* InBrush)
Brush.SetImage(*this, InBrush);
void SSlateSlice::SetAngle(float InAngle)
Angle = InAngle;
void SSlateSlice::SetArcSize(float InArcSize)
ArcSize = InArcSize;
现在,只要在编辑器中更改任何属性,切片就会立即更新。
6、设置Widget类别
除非另行指定,否则Widget在 UMG 设计器的Widget面板中显示为未分类。要设置类别,请添加对GetPaletteCategory函数的覆盖:
#if WITH_EDITOR
const FText USlice::GetPaletteCategory()
return LOCTEXT("CustomPaletteCategory", "My custom category!");
#endif
7、后续工作
本文涉及的源码可以从 GitHub 库中下载。
在能够与 Unreal 中的常规 UMG Widget相提并论之前,仍有许多工作要做。例如,属性不支持动画,也不支持属性绑定。我也没有讨论鼠标事件——UMG(和 Slate)中的所有Widget都是矩形的,因此在不规则形状的Widget上正确处理鼠标需要一些技巧。
实现自定义Widget的一个潜在原因是优化 - 例如,实现一个组合多个元素呈现的单个Widget可能会更快 - 我将在不久的将来探讨这一点。
原文链接:Slate Widget开发教程 — BimAnt
以上是关于虚幻C++入门个人笔记——UMG网络的主要内容,如果未能解决你的问题,请参考以下文章