mypro334 发表于 2022-5-25 13:55

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

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


[*]Super表示父类是UObject类特有的功能。
[*]ViewPort:游戏视口大小。比如你的电脑屏幕是1920x1080,游戏窗口占用了四分之一的屏幕。那么ViewPort就是960x540。
   int32 viewPortX, viewPortY;
   playController->GetViewportSize(viewPortX, viewPortY);

[*]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。



[*]Bug:资源在版本里没有加载出来。 打开Project Settings,检查Project/Packaging/Additional Asset Directies to Cook。因为引擎里没有被任何资源引用的资源默认是不会被打包进去的,所以用到了这类资源需要特殊处理。



[*]崩溃:剪切粘贴Button到根节点时造成的引擎百分百崩溃。经测试Text和Image并不会造成百分百崩溃。




<hr/> 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】


    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为向顶部对齐。







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

[*]修改列表控件和子物体的RenderScale皆为负数(负负得正),并在蓝图的EventConstruct事件连接(而不是PreConstruct,避免蓝图编辑时看起来是反的)。

[*]Bug:ScrollBox子项是Button时,不能滑动。

[*]把Button的TouchMethod设为Precise Tap(精确点击)





[*]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。
<hr/>关于作者

[*]水曜日鸡,喜欢ACG的游戏程序员。曾参与索尼中国之星项目《硬核机甲》的开发。 目前在某大厂做UE4项目。
CSDN博客:https://blog.csdn.net/j756915370
知乎专栏:https://zhuanlan.zhihu.com/c_1241442143220363264
游戏同行聊天群:891809847
页: [1]
查看完整版本: 【UE·总结篇】我的UE4游戏开发笔记和Bug集锦①