【UE·总结篇】我的UE4游戏开发笔记和Bug集锦①

这系列文章记录了我平常实战中对于UE4引擎、开发工具、代码中遇到的问题的解决方法、遇到的坑和使用上的一些技巧,类似于学生时代的笔记本和错题集。每条单独拿出来都不足以支撑一篇博客的量,所以把这些零碎的经验汇总成一篇篇文章。计划出成一个系列,这一篇总结了今年上半年来的工作经验。
Lua使用的是SLua,UE4版本4.26。


Lua

  • os.time() 当前时间戳。

  • os.time({ year = 2021, month = 7, day = 8, hour = 19, min = 0, sec = 0 }) 自定义时间戳。

  • os.difftime(time1, time2) time1 - time2 计算时间戳差距,返回值是秒。

  • 把秒数转化为类似"09:11:22"的时间字符串的优雅写法。

    • math.modf 返回整数和小数部分,有两个返回值,这里只拿第一个
    • string.format("%02d",xxx) 不足两位的整数补足0。%d表示数值变量,%s表示字符串变量
    function GetTimeText(remainSec)
    	local secsOneDay = 24 * 60 * 60
    	local secsOneHour = 60 * 60
    	local remainDay = math.modf(remainSec / secsOneDay)
    	local remainHour = math.modf((remainSec % secsOneDay) / secsOneHour)
    	local remainMin = math.modf((remainSec - remainDay * secsOneDay - remainHour * secsOneHour) / 60)
    	local remainSec = remainSec - remainDay * secsOneDay - remainHour * secsOneHour - remainMin * 60
    	local timeText = string.format("%s:%s:%s",string.format("%02d", remainHour),string.format("%02d", remainMin),string.format("%02d", remainSec))
    	return timeText
    end
    
  • lua 排序table.sort()的注意事项:两个值相等时必须返回false。table.sort实现原理是快排,如果返回true会导致不必要的交换。

    local array = {9,15,9,222,10}
    
    table.sort(array, function(a, b)
        return a < b
    end)
    
  • umg的SetVisiblity封装,避免每次都去c++里复制粘贴,自己拼还容易拼错。(Hidden用到的情况非常少所以没有加)

    function SetWidgetVisible(widget, visible, bHitTest)
    	if bHitTest == nil then
    		bHitTest = false
    	end
    	if bHitTest then
    		widget:SetVisibility(visible and UE4.ESlateVisibility.Visible or UE4.ESlateVisibility.Collapsed)
    	else
    		widget:SetVisibility(visible and UE4.ESlateVisibility.SelfHitTestInvisible or UE4.ESlateVisibility.Collapsed)
    	end
    end
    

C++

  • 最佳IDE:Rider For Unreal 。不能debug的问题:【Rider For Unreal Debugging】。VS风格下的常用快捷键:

    • 全展开Ctrl+M+X。全折叠Ctrl+M+A
    • 全局搜索变量、方法名、类名Ctrl+T
    • 全局搜索任意字符串Ctrl+Shift+F
    • 查找引用Shift+F12
    • .h文件和.cpp文件切换Ctrl+K+O
  • 委托Delegate。【Unreal Engine 4:学习笔记(十)Delegate & Event】用的比较多的是DECLARE_DYNAMIC_MULTICAST_DELEGATE。注意C++绑定方法时必须是UFUNCTION

  • 代码里绑定UMG的Animation,Transient关键字不能少:

    UPROPERTY(BlueprintReadOnly, Transient, meta = (BindWidgetAnim))
    	UWidgetAnimation*  MyAnim;//名称和蓝图下的动画同名
    
  • 在for循环里遍历字典修改元素的写法,必须加 &表示引用 :

    for (auto& Item : MyDict)
    {
    }
    
  • 将世界坐标转化为某个transform的相对坐标:Transform.InverseTransformPositionNoScale(FVector position)。反之是Transform.TransformPosition(FVector position)

  • 将世界坐标转化为UI空间下坐标:UWidgetLayoutLibrary::ProjectWorldLocationToWidgetPositionProjectWorldLocationToWidgetPosition(APlayerController* PlayerController, FVector WorldLocation, FVector2D& ScreenPosition, bool bPlayerViewportRelative)

  • 保证分母为0也能安全不出错的除法 UKismetMathLibrary::SafeDivide


UE4

  • Super表示父类是UObject类特有的功能。
  • ViewPort:游戏视口大小。比如你的电脑屏幕是1920x1080,游戏窗口占用了四分之一的屏幕。那么ViewPort就是960x540。
    
    
  • DesignScreenSize:UI设计时的屏幕基准大小。比如我们设计UI时都是基于1920x1080的大小进行布局,游戏运行时的视口大小如果完全符合那么万事大吉,如果不一样则进行一定规则的缩放。【UE4 dpi scale 研究总结
    float designSizeX = viewPortX / UWidgetLayoutLibrary::GetViewportScale(this);
    float designSizeY = viewPortY / UWidgetLayoutLibrary::GetViewportScale(this);
    
  • Resolution:游戏运行的分辨率。如果是窗口模式,那么分辨率和视口大小是相等的。如果是全屏就不一定。比如你电脑1920x1080的全屏模式,依然可以把游戏分辨率选择为1280x720,此时的Resolution就是1280x720。【How to get current screen size/resolution?
     FVector2D Result = FVector2D( 1, 1 );
    
     Result.X = GSystemResolution.ResX;
     Result.Y = GSystemResolution.ResY;
    
  • 问题:如何给粒子死亡绑定事件? ParticleSystem.OnParticleDeath.AddDynamic。同时粒子需要有Event Generator,事件类型为Death。
    image
  • Bug:资源在版本里没有加载出来。 打开Project Settings,检查Project/Packaging/Additional Asset Directies to Cook。因为引擎里没有被任何资源引用的资源默认是不会被打包进去的,所以用到了这类资源需要特殊处理。image
  • 崩溃:剪切粘贴Button到根节点时造成的引擎百分百崩溃。经测试Text和Image并不会造成百分百崩溃。
    imageimage

UMG开发

  • 问题:如何在UE4里让UMG有不同层级?

    • 核心思路:调用CanvasPanelSlot的SetZOrder
    UCanvasPanelSlot* slot = Cast<UCanvasPanelSlot>(MyUserWidget);
    slot->SetZOrder(MyOrder);
    
    • 首先生成一个RootPanel,然后所有需要有层级的界面都是这个RootPanel的子物体。
    • ZOrder的值根据UI所在功能划分
  • 问题:如何计算A控件中心相对于B控件的位置? 如图,需要计算中间这个Button距离VerticalBox的底部的距离。使用FGeometry。 注意控件的可见性不能是Hidden或者Collapsed。【How to get UMG widget absolute position in UE4
    image

    FGeometry verticalBoxGeometry = MyVerticalBox->GetCachedGeometry();
    FGeometry indicatorGeometry = MyButton->GetCachedGeometry();
    auto localPosition = verticalBoxGeometry.AbsoluteToLocal(indicatorGeometry.GetAbsolutePosition()) + indicatorGeometry.GetAbsoluteSize() / 2.0f;
    auto verticalBoxSize = verticalBoxGeometry.GetAbsoluteSize();
    return verticalBoxSize.Y - localPosition.Y;
    
  • 问题:如何将控件保持渲染对象大小不变而占用更少的布局? 比如需要在10x10的布局大小下显示100x100的图片。把要显示的控件放在Overlay下,调整Overlay的Padding和Transform的Translation。如图是一个在Y轴上占布局空间为0但是图片完整显示的案例,其中调整的Overlay为向顶部对齐。
    image
    image
    image

  • 问题:如何让列表倒着添加元素(从右往左,从下往上)?

    • 修改列表控件和子物体的RenderScale皆为负数(负负得正),并在蓝图的EventConstruct事件连接(而不是PreConstruct,避免蓝图编辑时看起来是反的)。
  • Bug:ScrollBox子项是Button时,不能滑动。

    • 把Button的TouchMethod设为Precise Tap(精确点击)
      image
  • Bug:带参数富文本替换导致的bug。如“<字体>玩家等级{PlayerLevel}</>”中的PlayerLevel替换成玩家等级。

    • 错误做法:更新UI的时候GetText(),查找PlayerLevel,替换玩家等级。这样做第一次是对的,第二次时再GetText()时已经没有PlayerLevel了。
    • 正确做法:初始化时GetText()记录在一个数据结构,更新时从数据结构里拿。
  • TreeView。使用教程:【UE4(UI)第七十三课Tree View树形结构

    • TreeView的展开收起功能是通过绑定子物体(后简称Item)的Slate事件OnMouseButtonClick来实现的,如果Item有Button则可能会冲突
    • 如果要实现点击某个Item收起其他Item的功能在OnItemExpansionChanged事件调用SetItemExpansion,但是要注意调用SetItemExpansion以后收起的Item也会触发OnItemExpansionChanged事件,这里容易变成死循环。解决思路是调用SetItemExpansion前记录当前选择的是哪个Item,然后和OnItemExpansionChanged给的Item进行比较。
  • 列表型控件(ListView、TileView等)的滑动条因为很丑且不能调,通常来说都要隐藏掉。在PreConstruct事件里调用SetScrollbalVisibility。


后记

其实从今年年初到现在我一直从事UI相关的工作,也就是俗称的拼图仔。要知道,在客户端程序的江湖上一直流传着一句话:“拼图、打包、接sdk是三大垃圾活”。因为这三种活对于个人提升帮助不大同时又是几乎每个团队需要做的事情。所以大部分时间对于自己工作的内容产生不了多大的兴趣。直到最近对自己半年来的工作进行复盘,发现自己其实还有很多的地方需要提升。自己的开发效率和bug率其实都还有改进的空间,比如自己有时候查C++代码的bug时需要编译好多次才能最终修好bug,而因为项目较大每次编译完成到运行游戏验证bug至少要5分钟,来来回回就浪费了非常多的时间。综上对自己工作的反思是产生这篇文章的契机之一。
第二个契机是自去年以来一直有写文章的习惯,想要写那种“高大上”的有技术深度的文章(比如“对xxx源码拆解”,“对xxx系统深入研究”。。。写完还能装逼不是)。然而日常工作内容大部分都很简单,靠这些是写不出来的。思来想去,为什么一定要死磕那种文章呢?不如把工作中遇到的问题总结总结,虽然这种文章不够装逼,但也是自己工作经验的体现,说不定还能帮助同样是拼图仔的UE新手程序员。


关于作者

  • 水曜日鸡,喜欢ACG的游戏程序员。曾参与索尼中国之星项目《硬核机甲》的开发。 目前在某大厂做UE4项目。

CSDN博客:https://blog.csdn.net/j756915370
知乎专栏:https://zhuanlan.zhihu.com/c_1241442143220363264
游戏同行聊天群:891809847

猜你喜欢

转载自blog.csdn.net/j756915370/article/details/119183395