本随笔用于记录我在学习过程中觉得对开发很有用的杂事,包括一些开发技巧、编程思想、计算机知识、项目组织技巧等等杂七杂八的东西,本随笔会不定期更新。
配置文件在项目中的作用也很重要,使用配置文件的话可以避免在项目内硬编码,而且可以让使用者通过更改配置文件来改动项目里的功能模块,实现不改动代码的情况下完成多种应用场景,比如说数据库的连接数据保存在配置文件里,项目通过读取配置文件来设置连接的数据库,这样通过更改配置文件就可以实现连接不同的数据库而不需要去改动项目的代码。
UNIX时间戳是以格林尼治时间(UTC±0)为标准定义的从1970年1月1日00时00分00秒到当前时间的秒数,北京时间和UTC事件相差八个小时,在进行和时间相关的计算时需要注意将北京时间转换为格林尼治时间。
降低功能模块耦合度是非常重要的,通过降低耦合度,当某个功能模块出现问题需要修改的时候,我们可以直接修改出问题的模块而不必在意其他被引用的模块,低耦合度组织方法非常适用于维护项目。
应该用一种简明且高效的方式来记录目录结构取代杂乱的目录结构,同时文件(UE4里应该称为资产(Asserts))也应该用一种简明高效的方式来为每个文件命名,目录结构和文件名称应该能够最直白的表明该文件(资产)的作用。
在程序运行过程中遇到判断的情况(c++的if语句,UE4蓝图的Branch节点等),不要把判断失败的情况留空,也就是说如果当条件判断失败之后应该做一些善后的工作,就算是一段简单的提示也行,而不是要完全留空。
项目运行时的层次必须分明。例如在项目里有一个读取数据库数据的模块和一个显示登陆界面的模块,那么数据读取的模块就一定要在登陆界面模块正确执行之后才开始运行,比如登陆成功且选择了正确的数据源之后才开始读取,而不是刚启动项目之后就开始运行数据读取模块;
作为服务器使用的模块应该尽可能地布置在可以长期运行的计算机上(例如商业性服务器、不断电的计算机)而不是个人电脑上,要让该服务器尽最大可能地可以被随时访问。
对于一些并行启动会有冲突的程序(例如安装程序),应该添加一个检测模块,该模块用于检测是否有另外一个相同的程序在运行,这样可以避免运行同一个程序造成的不良后果(例如一个在执行安装另一个在执行卸载。
在一个项目的进行过程中,最好准备一台电脑用于非个人目的,例如上文提到过的服务器安装,以及类似UE4这种可以设置共享DDC功能的软件,这样方便所有参与项目开发的人员使用同一个共享文件或服务器。
在编写用户手册的时候,要详细说明每一个模块的功能、每一个API实现的功能以及每个参数的意义,还有所有依赖库的版本号(如果有依赖库的话)。
涉及到网络通信的模块应该设置“连接超时警告”功能和“自动尝试重新连接”功能。
目录结构里应该避免相似功能的目录,比如说“MOD”文件夹和“ASSERT”文件夹。
对于一些提出的问题。回答要尽可能做到详细,也就是尽可能回答问题提出者最想要的答案。
在开发过程中要考虑到各个界面显示之间的关联性,比如说刚开始的登录界面在登陆成功了之后需不需要保留、主界面和其他界面(历史回看界面、航班回看界面等)之间的关系:是否需要保存、是否可以从其他界面回到主界面上等。
按钮等可以和用户交互的控件需要编写功能来防止用户多次点击之后造成的不良后果。
项目打包的时候在“Output Log”窗口里有一段名为“Warning/Error Summary (Unique only)”的信息段,在这段信息段里会显示打包过程中哪里出了问题(Warning以黄色字体显示、Error以红色字体显示),可以很快速地定位错误 。
在一些后台运行的程序中(例如打包程序、守护程序之类),健全的输出日志功能是很有必要的,尽可能地为每一步操作都设置一个日志输出功能,无论是正确运行或是错误运行,错误运行还要写明是什么地方出错了,这样可以让用户清楚地了解到后台发生了什么。
UE4 C++函数参数的修饰符不同,在蓝图系统里映射出来的功能也会有相应的变化,由此产生了一个重要的前提:不能使用指针作为参数。
修饰符对应于蓝图中的功能为:
c++函数普通参数映射为蓝图函数的输入变量;
c++函数常量参数映射为蓝图函数的输入变量;
c++函数引用参数映射为蓝图函数的输出变量;
c++函数常量引用参数映射为蓝图函数的输入变量;
本质上就是提供了一种方法可以让函数有多个返回值,普通引用变量由于可以其特性,传递给函数且在函数内修改之后会反映到原来的变量上,在代码层面实现了多个返回值的功能,因此可以看出来UE4将普通引用参数映射成了蓝图函数的输出变量,其他修饰符都是输入变量;
配置文件的JSON格式需要提前讨论好,这样有助于在后续的开发过程中减少改代码的问题产生。
有模块A和文档B,其中模块A提供了某个文件格式的处理方法,其在文档B中有详细的描述,但是在项目新版本中模块A的该方法被弃用了,我们需要及时更新文档B指明该方法在当前及之后的版本中已经被启用,需要想一个高效的方法可以当模块A被改变之后提示到用户需要更改文档B 。
项目名后面应该跟上对应引擎版本号,这样后期好区分当前项目对应的引擎版本
若可使用前置声明,而非头文件,请使用前置声明。
包含时尽量细粒化。例如,勿包含Core.h,而在核心中包含需要定义的特定头文件。
在文件末尾留下空白行。所有.cpp和.h文件应包含空白行,以便和gcc兼容。
避免循环相同的多余运算。将常用子表达式从循环中移出,以避免冗余计算。
在某些情况下,使用静态变量来避免函数调用间的整体多余运算(如反复生成局部变量)。
使用中间变量来简化复杂表达式。若含有复杂表达式,将其拆分为指定至中间变量的子表达式将更易理解(该子表达式的的命名描述了其在父表达式中的意义)。
指针与引用应仅含一个空格,该空格位于指针/引用右侧。使用在文件中查找可方便快速地找到特定类型的所有指针和引用(如“int* Name”或“int& Name”)。
避免在函数调用中使用匿名文字。建议使用描述其含义的命名常量:
// 旧样式
Trigger(TEXT("Soldier"), 5, true);.
// 新样式
Trigger(ObjectName, CooldownInSeconds, bVulnerableDuringCooldown);
由于无需查找函数声明即可理解目的,因此此操作可协助普通读者快速理解。
创建新插件的时候,在插件的.uplugin文件里该插件的类型会是“Developer”,该类型指定编辑器只会在编辑器模式和开发模式里被启用,项目被打包发布时并不会打包该插件,因此在项目里用到插件的地方会全部失效,把插件的类型改成“Runtime”即可在打包过程中打包该插件,顺便一说插件类型还有一种是“Editor”,即只能在编辑器模式里使用的插件。
除了内容浏览器内的各种资产需要分类给文件夹以外,“世界大纲视图(World Outliner)”内存在的资产也需要在世界大纲视图里以文件夹的形式分类,分类规则还需要后续制定。
尽量将一个功能给细分化,例如程序需要保存一个数据,保存路径里包含了未创建的文件夹,这时候应该给予用户提示该文件夹不存在,并且可以让用户选择是否在找不到文件夹的时候自动创建文件夹,然后默认是不创建仅提示,目的就是为了防止输入文件夹名字时候意外输错而导致不小心新创建了文件夹让数据保存到了错误的地方。
类(无论是蓝图类还是C++类)实例的名字不要和类类型的名字一样,实例对象的名字应该和类类型名字有所区分。
蓝图接口和事件调度器都是用来实现蓝图之间通信的工具,但是使用蓝图接口需要知道被调用对象,使用事件调度器需要知道主动调用对象(以将事件调度器和被调用事件绑定)。
当一个程序正在安装文件的时候,会因为一些原因在安装到一半时被迫退出,这个时候目录里会残留着程序执行了一半之后的无用文件,本来可以被下载或安装程序清理掉,但是现在因为中途被迫中止而不能被清理,因此我们可以额外设计一个程序,该额外程序可以管理下载程序或者安装程序的所有过程,就算安装程序中途崩溃退出,该额外程序也可以清理残留的文件,同样,该额外程序还可以监控安装程序和卸载程序的运行情况,防止一边安装一边卸载的情况发生。又因为该额外程序是在安装程序之前运行,因此可以保证如果安装程序是中途退出,则一定会清理余下的文件。
在程序设计之前和设计的过程中,要尽可能的考虑到程序运行过程中会遇到的问题,越全面越好,并且要给这些问题提供解决的方法,防止一些未解决的问题对后续程序的开发产生不良的影响。
项目使用到网络的话需要多方面去检测网络连通状态,最好是从一台拥有独立IP且从没有运行过该程序的电脑上测试项目。
在Windows系统里已经被设置为可共享的文件夹,可以在Linux系统里通过“mount”命令来进行挂载从而可以访问共享文件夹:mount -o username=,password=。
在Linux系统里安装samba服务并设置好对应的配置(配置内容待去理解),就可以在Windows的资源管理器的通过地址栏输入“\”来进行访问。
在UE4的项目里面,Actor的点击事件需要由启动了“点击事件(Click Events)”的Player Controller才能启用,而默认情况下Player Controller是禁用该设定的(猜想是为了提升性能),为了让Actor能够响应点击事件,需要手动启动“点击事件”。
关于两个Widget控件之间如何定义渲染顺序的问题(即两个完全不同的UMG之间的Z Order),在节点“Add to Viewport”上点击下拉框,就可以显示出ZOrder的选项,ZOrder的值越大,则渲染层级越靠后,也就是说显示的时候会更靠近玩家的界面。
节点“FInterp To”表示的含义是从“Current”开始,将“Target”分为“Delta Time”份,每一份的增长速度为“Interp Speed”,加上当前的值“Current”,即为当前的返回值。可以如下数学表达式来说明:
x = Current + (Target - Current) * DeltaTime * InterpSpeed
其中X即为返回的值,且X的值一定处于Target和Current之间。
UE4引擎默认的DDC存储路径为:C:\Users<UserName>\AppData\Local\UnrealEngine\Common,该DDC不会自动删除,而且会占用很大的硬盘空间,所以除非是必要的,经常清除DDC有利于释放C盘的空间。
关于更改UE4项目分辨率的问题,当打包的构建配置选择的是“Shipping”时,在蓝图内使用的“Set Screen Resolution”函数可以发挥作用,但是在“Debug”和“DebugGame”选项时却不能够发挥作用,因为类似分辨率这样的选项是由配置文件“/Config/GameUserSettings.ini”来管理的,在这里可以更改对应的配置。
UE4里坐标系分为“世界坐标系(World Location)”和“局部坐标系(Local Location)”,世界坐标系的原点是以世界原点为坐标原点的,在该坐标系下进行的变形都是以世界原点为基础;局部坐标系是以某个父类组件或控件的轴心点为坐标原点,在该坐标系下进行的变形都是以该父类为原点进行变形。
如果某个UMG是被嵌套在了另一个UMG里面,然后在前者里面涉及到的全部的控件都是基于本身这个UMG空间范围来设计的,那么在获取位置信息的时候可以使用“Slot as Canvas panel”这个节点获取被绘制的尺寸,这样就在因为父UMG的布局改变了导致该子UMG的尺寸改变的情况下,也不会导致子UMG里面的布局被打乱。
编程过程中对于占用到内存的模块,及时释放被占用的内存是非常重要的,在一些小程序上不明显,但是在大型程序中如果没能够及时释放被占用的内存,系统内存就会面临被用完的情况。
在使用VS构建项目之后,如果要运行带有动态链接库的程序,则需要将相应的动态库(DLL文件)复制张粘贴到对应的可执行程序(exe文件)下,但是这个过程只能够手动执行,目前为了能够实现自动化复制和粘贴操作,合适的方法是右键项目->属性(Properties)->设置属性(Configuration Properties)->构建事件(Build Events)->命令行(Command Line)里设置对应的命令,该命令使用的是DOS命令。还有另外一种方法:选择当前工程,右击"属性" -> "配置属性" -> "调试",在"工作目录"设置dll的路径,这种方法适合于需要在编辑器里面测试程序构建好后运行的结果。
VS提供了主动复制CPP运行库的功能(但是不包括先前所说的第三方库),只需要在“项目(Project)”->“属性(Properties)”->“配置属性(Configuration Properties)”->“高级(Advanced)”中的“高级属性(Advanced Properties)”目录下,将“复制CPP运行库到输出目录(Copy cpp runtime to outdir)”设置为“是(Yes)”即可,这样在每次构建完成之后VS会自动将需要用到的运行库(例如msvcp140d.dll)复制到输出目录下,但是需要特别注意 的是,如果启用了“构建事件(Build Events)”中的“构建后事件(Post-Build Event)”,则该功能将会无效。
UE4提供了一套涉及范围特别广泛的工具来使用户开发游戏,这些工具包括游戏逻辑、材质效果、音响音效、动画、画面效果、粒子效果等等,这些高效且强大的工具也带来了诸如入门难的问题,想要一次性全部学完然后开始项目的话会很困难,因此我个人建议我自己首先确定一个自己想要做的小应用,然后在实现这个应用的过程中去学习我所需要的功能,在应用中学习,在学习中实践,利用这种方法的话应该会比直接理论学习效果更好一些。
合理安排任务是非常重要的,将一个项目分工成合理的小项目,一方面适合不同专业的人完成各自的工作,另一方面也适合每个部分集合成一个完整的项目。
关卡蓝图里面可以直接绑定被定义在Actor蓝图类内的事件调度器。即在Actor蓝图里面定义的事件调度器,如果该Actor已经被放置在关卡里面,则在该关卡的关卡蓝图里可以直接通过输入事件调度器的名字来查找到对应的事件调度器并直接定义绑定的内容。
在UE4引擎里,部分设定并不能够在编辑器视口里被直接观察到,例如在世界场景构成(World Composition)里被分配给指定图层的关卡,其关卡距离只能在游戏启动时候才会起作用,在编辑器状态下所有关卡都是可见状态,而不受关卡距离的影响。
在UE4里使用任何虚幻资产,需要首先将其加载到内存里才能够使用(例如编辑其属性等等),在代码里的表现为如果直接使用find_asset方法去寻找在内容浏览器里面的资产时,并不会找到该资产,因为该资产并没有被加载到内存里面,需要首先调用load_asset方法加载资产之后才能够调用find_asset方法找到该资产。
今天学习了如何在VS里面创建和使用动态库(DLL)的方法。创建动态库的方法有两种,声明“__declspec(dllexport)”标识符和定义模块定义文件(.def文件),前者的方法需要在函数声明或者类声明之前标示,后者则是在一个单独的文件里面列出所有需要导出的函数或类,这两种方法各有各的优缺点,可以择一使用。生成的二进制文件除了以dll为后缀名的动态库文件外,还有一个以lib为后缀名的导出函数声明文件(和静态库文件是同一个后缀名,但是性质不一样),供其他vs程序使用的时候首先需要提供lib文件所在的路径,然后是lib文件的全名,这样可以让程序通过编译和链接,然后是将DLL文件放到生成的exe项目文件同级目录下即可使用。前一种生成动态库的方法在现在的编程中更加常见,但是需要注意的是, 在头文件里用“__declspec(dllexport)”定义导出的类和函数可以在使用动态库的过程中可以不添加“__declspec(dllimport)”标识符,但是会造成一些额外的寻址过程,造成程序体积增加,因此最好是通过宏定义“#ifdef”和“#ifndef”的方法来定义这两个标识符。
在UE4项目中,对于“平行生成”的情况需要谨慎处理,所谓“平行生成”情况,就是指如果有复数个类需要互相引用到对方,那么对于这些类的生成步骤以及获取所需类对象的步骤需要有额外的操作,例如说有一个类A,一个类B,类A有十个实例对象,类B有一个实例对象,类A中的每一个实例对象都需要获取到类B的对象,类B的实例对象也需要获取到全部十个的类A实例对象,那么在互相获取到地方之前就不要进行后面的步骤,以防出现空指针的情况出现,因此使用“Valid”节点来判断是否获取到所需的对象就变得非常重要。
在打开UE4项目之后发现蓝图A里面的引用全部掉了,然后在项目外通过复制粘贴所需的资产之后,回到蓝图A里发现引用还是没有引用上,这个时候只需要右键蓝图A的资产,然后在Asset Actions分类里选择Reload即可重新搜索引用。
在对某个问题下结论了之后,最好再根据这个问题多分析分析,因为当前结论可能会是错误的结论,例如本来是代码的问题,我个人却把车辆重叠和突然间运行速度变慢的问题归结为CPU多线程处理太慢的问题,实际该问题是线性插值导致的问题。
在读取了DDP之后如果生成新的DDC,新DDC会被放到Local节点指定的位置上。
在配置文件里设置的节点除了几个默认的以外(Local、Shared等),可以自定义设置节点,当自定义设置节点之后可以实现分别读取DDP,比如说地形关卡单独生成一个DDP,建筑关卡单独生成一个DDP,然后可以让用到的部门去读取单个DDP,而不需要把这两个关卡的DDC打包成唯一的一个DDP。
DDC的Local(还有其他)节点设置里面有个“ReadOnly”选项,如果将该选项设置为true,则在项目运行过程中生成的所有DDC都不会被保存到本地上,因此造成的情况就是每一次启动都要重新生成DDC,将该选项设置为false即可让运行过程中生成的DDC保存到本地上。
打包后的项目不会运行构造脚本(Construction Script)里面的逻辑,猜测是因为该构造脚本仅仅只会在Editor模式下使用到。 (该问题还没有得到实际的证实,因此在这里仅作一次记录)
打发行包的项目也可以像打开发包的项目一样通过设置配置文件来更改项目,只是其存放配置文件的路径在:C:\Users\<用户名>\AppData\Local\<项目名>\Saved\Config\<打包明台名称>
。
当我们的项目需要使用一个Actor的组播函数时,且网络情况不良好的情况下,我们在生成这个Actor之后立即使用组播函数的话会让这个函数的“组播”功能失败,也就是会变成只有服务端调用了函数而客户端没有调用的情况,解决方法可以通过“延迟调用”来解决,例如使用定时器、使用Tick隔数帧后再调用等。总而言之就是要加长“生成Actor”到“调用组播函数”之间的间隔。
在使用C++动态生成MediaPlayer相关的资产并在项目里播放视频的时候,需要在MediaTexture使用SetMediaPlayer函数设置到播放器之后调用UpdateResource函数才不会报“check(bOk)”的错误。
UE提供的TreeView控件(包括ListView控件)有时候会出现“需要按两次下才能导航到正确按钮”的问题发生,发生这种问题的原因其实是没有使用View控件的“SetSelectedItem”这类函数为控件设置已选择的项,从而造成内部的导航不正确。所以在动态设置导航的时候我们可以在SetFocus之后再SetSelectedItem,从而让View控件的导航能够正确运行。
UE提供的TreeView控件(包括ListView控件)在源码里面的Slate控件里面将引擎的导航接管了(即重写了“OnNavigation”函数),因此Widget原本的导航函数(即能够设置输入Up、Down时进行的行为)不会被调用的,再加上View控件自己的导航仅仅只是会根据View里生成的数据数组,然后根据下标去寻找下一个\上一个下标,然后检测合法性,合法性通过就对对应下标的数据进行导航操作,并不会去检测对应下标Displayed Widget的Enable或者Focusable进行检测,所以在部分功能上View控件自带的导航会不满足需要(例如项目里面的任务UI的任务分类,如果使用手柄进行导航的话就会直接导航进还没有解锁当时显示出来的每周任务上)。如果说不想在引擎源码上进行重构,则可以在每一次导航到一个按钮的时候(例如在函数OnFocusReceived里面)去对导航方向上所有的按钮进行一次Enabled可用性检测,然后找到可以用的按钮然后再进行Focus和SetSelectedItem。
现在项目里面通过客户端与服务器互相发送心跳信息来检测网络连接是否正常,如果客户端在五秒内没有接收到服务器发送过来的心跳信息的话则报网络连接错误。但是这个功能存在一个问题,就是如果程序员在调试过程中在代码里打断点的话,中间暂停的时候还是会被计算到心跳过程中,这个时候当程序员取消断点并运行项目的时候,检测逻辑会把打断点那段时间也计算在心跳检测中,而且很有可能会超时然后报网络连接错误。解决方法就是可以使用“WITH_EDITOR”宏来暂时关闭该功能,或者在本地代码上把报错调用的委托暂时注释掉。
运行时按下~键,输入p.visualizeMovement =1可以将玩家的运动信息可视化。
UMG中的Construct事件并不是在“Create Widget”之后立马执行的,而是在添加到玩家视图(AddToViewport、添加到其他已经显示的UMG上面等)的时候才会被调用。事件“OnInitialized”才真正实现了在“Create Widget”时候被调用的“初始化函数”的功能。需要注意的是 ,如果使用“Construct Object”去生成一个UMG的话,是不会调用“OnInitialized”事件的,而只会在添加到玩家视图时调用“Construct”事件。可以得出如下规律:
如果使用“CreateWidget”节点生成UMG的话,会调用UMG的“OnInitialized”事件,然后在“AddToViewport”的时候依次调用“PreConstruct”和“Construct”事件。
如果使用“ConstructObject”节点生成UMG的话,不会 调用“OnInitialized”事件,但同样会在“AddToViewport”的时候依次调用“PreConstruct”和“Construct”事件。
UMG中在Designer窗口布局时需要注意,在左下角的“继承树(Hierarchy)”中越靠下的控件越在后 生成,也就是说一个控件的行为会受到其上级控件的影响。例如有控件A和控件B,控件B为A的子控件,这个时候如果控件AB两者的可视性都为“Visible”且尺寸都一致的话,控件A很有可能会拦截到本来应该给控件B的响应。需要注意 的是并不一定要为子控件才会有这样的影响,就算是同级的控件,排列在上方的控件也会拦截传给排列在下方的控件的事件。
之前项目遇到一个问题就是在部分电脑上不能播放开头动画,解决方法是把视频源文件(也就是mp4文件)的分辨率调低一些,当前分辨率为3840*2160,猜测是分辨率太大了导致部分电脑解码出现问题。
使用UE4(UE4.27)自带的媒体框架时需要多用API进行很多组合尝试可能才会实现自己需要的功能。例如如果需要实现Open媒体源之后让纹理播放第一帧而不是白屏,需要在“OnMediaOpened”回调中先“Pause”然后再“Rewind”,而不是通常的直接“Rewind”再“Pause”。还有因为媒体可能会打开失败或者打开需要时间,因此最好使用回调(如上面的“OnMediaOpened”)来监测媒体状态而不是用循环Delay。
UE4(UE4.27)自带的媒体播放器在PS5平台上表现不是很好(甚至在PC平台上也并没有优秀的表现),因此可以使用引擎自带的插件“Electra Player”作为视频播放的解决方案,在插件页面勾选“Electra Player”之后(其他两个插件也会在重启后自动勾选上),在File Media Source的页面里面把编码器选为“ElectraPlayer”(或者设置成“Automatic”也可以,引擎会优先选择“ElectraPlayer”作为解码器)即可,后续播放的视频将会以“ElectraPlayer”的解码器进行播放,经过简短的测试——例如项目里的开场PV在用原解码器时如果播放之时处于低帧率状态,点击编辑器之后视频播放依然会进行卡顿,但是使用新解码就不会出现这样的问题——之后,可以有效的解决之前存在的问题,甚至是可以设置第一帧画面:让视频默认播放,然后绑定MediaPlayer的委托OnMediaResumed,最后在回调事件里执行暂停逻辑,即可实现让视频停止在第一帧的功能。
当处在编辑器时,如果发现鼠标移动到图标显示ToolTip、两个显示器中其中一个窗口移动到了另一个显示器发生帧数下降的问题时,可以通过把N卡控制面板里面的“最大帧速率”设置为“关”来解决。
使用UnLua时候最好不要用到Lua本身的全局变量,因为可能会出问题,当前项目就因为使用了全局变量之后导致图文教程高亮框结束之后一直在循环调用,造成卡死的假象。
API“Set Focus to Game Viewport”可以让当前玩家的导航Focus到Game Viewport上,应该可以解决玩家没有Widget去Focus时候的问题。
在UE4项目中,如果同时存在SSR和Reflection Capture时,会优先使用SSR,因为SSR的效果更准确,但是缺点是如果屏幕不存在物体的话则SSR会失效。
蓝图节点“Switch Has Authority”通常用来判断当前运行进程是DS服务器还是客户端,如果进程是DS服务器则会输出“Authority”(假设Actor是在服务器上生成的),是客户端则输出“Remote”,但是需要注意 输出“Remote”的进程不一定 就是玩家启动的客户端,也可能是 另外的玩家启用的客户端在玩家客户端上模拟的结果,也就是通常的“Simulated Proxy”,这种情况也会输出“Remote”,这就造成了如果使用玩家的“BeginPlay”函数去生成UI时,如果只是通过“Switch Has Authority”节点来进行判断的话,本地玩家上的Role为“Autonomous Proxy”,网络玩家的Role为“Simulated Proxy”,都同样可以生成UI,会导致在本地玩家的客户端上生成了两个 UI,这个时候只需要使用“Get Local Role”来进行判断是否为“Autonomous Proxy”即可让玩家只在自己的客户端上生成UI。
如上所说,即使Actor蓝图里该节点返回“Authority”也并不说明当前进程就一定 是服务器,因为如果一个Actor是在客户端上生成,那么在客户端的Actor蓝图里调用“Switch Has Authority”也依然会返回“Authority”,因此节点“Switch Has Authority”更多是表达一种“是否拥有此Actor权限”的说明。
如果需要让一个UI拦截鼠标事件不再往下传播的话,可以实现该UI的“OnMouseButtonDown”事件并返回“Handled”变量,这样的话可以让鼠标事件被该UI截获从而不在往下转播,也就不会造成突然鼠标消失了的情况。
“TeleportTo”方法(蓝图节点名称为“Teleport”)可以让物体“瞬移” 到指定位置,该瞬移并不在意起始点到目标点之间是否存在阻挡,但是如果目标点存在阻挡的话,则该方法会尝试着微调 物体目标点位置让物体不会被阻挡(不过该微调很小),微调后依然存在阻挡的话则不会让物体进行移动,除非勾选上“NoCheck”选项。“SetActorLocation”方法默认则会让物体直接移动到目标点,即使存在阻挡,使用“Sweep”选项则会检测起始点到目标点之间的阻挡,该方法会让物体在第一次接触到阻挡时停下。
在开发网络项目的时候,如果有需要记录物体的初始信息(例如物体初始位置)的时候,最好不要在BeingPlay里面记录初始信息,最好在Construct脚本里记录,因为网络同步存在延迟,Server上的物体记录了正确的位置信息然后开始移动一段距离之后,客户端的物体可能才刚创建完并执行BeginPlay,这个时候记录的初始信息是错误的,所以最好在Construct脚本里保存初始信息。
有时候UI表现会有“圆角”、“切角”的要求,这个时候可以使用“Retainer Box”控件搭配材质来实现。除了圆角效果,还可以让子控件的所有元素实现类似渐变等材质都可以实现的效果。
Dynamic Entry Box的功能和ListView、TreeView的效果一样,可以动态创建条目,使用方法上前者比后几者简便,也因此没有做过性能优化,适合做需要有列表功能但是又不需要ListView时的场景。
在声明函数的参数时,按照UE的编码标准,最好在买个输入参数前加“In”前缀,目的是防止当参数名字和成员变量名词重复时可以避免编译器错误。即使c++允许函数声明时的参数名和定义时的参数名可以不一致,但是函数声明和定义时都需要加“in”或“out”前缀。
不像其他类型的委托绑定函数,BindRaw
或AddRaw
绑定的原始c++对象因为没有反射系统,所以不能够通过IsBound()
函数来判断对象是否存在。这个时候可以使用UE的智能指针TSharedPtr来指向该原始c++对象,然后再使用BindSP
或AddSP
来添加委托。
BindStatic
或AddStatic
可以用来绑定函数静态成员或原始c++全局函数。
复制函数(ReplicatedUsing指定的函数)一共有多种形式,其中就有无参及一个参数的形式,其中需要注意的是,一个参数形式的赋值函数传送进来的值并不是 更新后的新值,而是改变之前的旧值,如果用户不需要复制变量的旧值的话,则可以使用无参的形式。
在设置ViewTarget时,由于Actor和PlayerController分别覆写了CalCamera函数,因此如果玩家生成一个带摄像机组件的Pawn时可以正确获取到摄像机位置,但是设置带摄像机组件的PlayerController时并不会获取到正确位置。
使用UE4的控件ComboBox时,可以通过其定义的委托“OnGenerateWidget”来自定义生成想要在下拉菜单里使用的Widget,从而自定义想要的下拉菜单项样式。
在使用Enhanced Input系统时,Input Action(IA)或Input Mapping Context(IMC)都可以设置Trigger和Modifier,如果IA和IMC都设置了Trigger,则只有IMC里的Trigger起效;如果IA和IMC都设置了Modifier,则这两个Modifier会“叠加”,也就是首先应用IMC的Modifier,然后把结果传给IA的Modifier进行二次处理。
使用Git Checkout源码时候需要注意是否在“release”分支而不是“master”分支,因为引擎源码默认使用的是“Release”分支,如果检出到“master”分支上可能会没有我们需要的东西。例如插件CommonUI。
在开发UMG时,Horizontal Box在添加子控件的时候默认是会向右变宽的,这个时候我们可以通过设置“对齐(Alignment)”属性的X为1,这样就能让该控件右对齐,然后添加子控件时则可以实现向左变宽。Vertical Box同理。
蓝图继承蓝图时需要注意事件图标里默认的几个事件是未启用状态 (如Tick、Construct等),如果没有在子蓝图里把这几个事件激活的话,是不会执行父蓝图的逻辑的。
某些时候AssetRegistry的函数GetAssets会搜索不到原本有资产的文件夹(例如在Standalone模式或Launch启动项目时),这个时候可以使用AssetRegistry的函数ScanPathsSynchronous来执行重新搜索的功能,之后再执行GetAssets函数就能获取到对应的蓝图资源了。(该条建议还是ChatGPT4告诉我的……)
Actor同步变量时客户端接收同步后的变量有可能 在BeginPlay前也有可能在BeginPlay后,也就是说某个同步变量VA,会在BeginPlay里从本地读表设置,这个时候如果我们的网络环境比较好,则同步会发生在BeginPlay之前,这个时候同步了变量但是因为BeginPlay又会从本地读表并赋值,会把同步过来的值给覆盖掉。如果同步变量设置成“InitiaOnly”的话,该变量就再也不会被同步成正确的值了,这部分需要注意一下。
UserWidget上面的函数“OnMouseButtonDown”可以用来实现“鼠标右键”类型的功能,同时可以通过重载该函数来防止鼠标滚轮鼠标按键事件继续往下传播的问题。
蓝图纯函数(Blueprint Pure function)虽然说可以不需要连接执行引脚,但是该类型函数的每个输出引脚被连接的节点被激活时,都会执行该纯函数,例如某个纯函数返回一个结构体和一个bool值用于判断是否合法,则判断bool值返回值时会指定一次该纯函数,返回结构体时会再次 执行一次该纯函数,因此需要注意如果该纯函数查询量过大的时候最好将其返回值保存下来供后续逻辑使用,而不是每次都要执行一次该纯函数。
posted @
2022-05-31 14:51
U_N_Owen
阅读(
1445 )
评论()
编辑
收藏
举报