[{"data":1,"prerenderedAt":1907},["ShallowReactive",2],{"blog-page-17":3,"blog-count":1906},[4,704,831,1356,1672],{"id":5,"title":6,"body":7,"date":681,"description":13,"extension":682,"meta":683,"navigation":686,"path":698,"seo":699,"stem":700,"tags":701,"__hash__":703},"blogs\u002F_legacy\u002F2017\u002F2017-07-30-ue4-profiling-preview.md","初探UE4中的Profiling",{"type":8,"value":9,"toc":633},"minimark",[10,14,17,34,37,42,45,52,55,58,61,66,69,77,81,84,87,90,94,97,102,107,110,113,116,119,122,125,129,132,137,140,144,147,152,156,159,164,168,176,182,185,188,191,195,198,201,204,207,213,216,221,224,251,259,262,267,271,274,277,280,283,286,290,293,296,301,304,309,312,318,321,324,327,332,335,338,341,344,347,350,353,356,359,363,366,369,372,376,379,382,385,389,392,396,402,405,408,411,414,419,422,425,428,431,435,438,444,449,457,460,464,467,470,473,477,480,483,487,490,496,499,505,508,512,515,518,522,525,528,531,534,537,544,547,550,553,557,560,563,567,570,574,577,580,583,586,590,593,596,600,603,606,609,612,615,618,621,624,627,630],[11,12,13],"p",{},"Profililng是成品制作过程中非常重要的一个步骤，通过Profiling才能提高运行效率使得作品达到用户能够运行从程度。",[11,15,16],{},"UE4本身有提供用于Profiling的工具，但是要正确的将其用于优化却需要经过一些学习。在掌握基础之后，要很好的完成优化，需要的是更多的实践所累积的经验了。",[11,18,19,20,27,28,33],{},"本文的主要内容来自对Tech Art Aid的[",[21,22,26],"a",{"href":23,"rel":24},"https:\u002F\u002Fwww.youtube.com\u002Fplaylist?list=PLF8ktr3i-U4A7vuQ6TXPr3f-bhmy6xM3S",[25],"nofollow","Profiling系列视频","]的总结和官方的[",[21,29,32],{"href":30,"rel":31},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FEngine\u002FPerformance\u002Findex.html",[25],"性能分析文档","]。",[11,35,36],{},"不过由于Profiling本身的覆盖范围较广，所以这里也只是记录刚刚开始接触的内容。同时，由于并没有很深入的了解过渲染相关的知识，文中有的术语可能存在翻译不正确的现象：D",[38,39,41],"h2",{"id":40},"scalability","Scalability",[11,43,44],{},"UE4中有提供所谓的可延展性功能，这个功能可以用于快速的调整引擎的工作性能-效率平衡。",[11,46,47],{},[48,49],"img",{"alt":50,"src":51},"image","\u002Fwp-content\u002Fuploads\u002F2017\u002F07\u002Fimage_thumb.png",[11,53,54],{},"但是这个功能并不是为优化而设计的，虽然其中使用到的一些设置可以用于优化，但是Scalability本身更多的是为了让开发者为用户提供一个快速的配置。",[11,56,57],{},"通常在游戏配置界面中能够看到的配置就是从Scalability入手的。",[11,59,60],{},"可以在蓝图中看到一些类似这样的函数",[11,62,63],{},[48,64],{"alt":50,"src":65},"\u002Fwp-content\u002Fuploads\u002F2017\u002F07\u002Fimage_thumb-1.png",[11,67,68],{},"社区中也有共享简单的游戏配置的实现，在设置窗口中可以对不同的渲染等级进行预览，以评估游戏在目标渲染等级上的表现。",[11,70,71,72,33],{},"更多的关于Scalability的内容可以参考[",[21,73,76],{"href":74,"rel":75},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FEngine\u002FPerformance\u002FScalability\u002Findex.html",[25],"官方文档",[38,78,80],{"id":79},"profiling","Profiling",[11,82,83],{},"通常性的优化目标就是提高帧率，降低每帧消耗时间。由于现代的CPU通常比较强大，只要不是在游戏逻辑中进行复制的物理模拟或者高强度的AI逻辑运算，性能的瓶颈都会出在与渲染相关的地方。",[11,85,86],{},"在进行优化的时候需要注意的是，要在项目配置中关闭Smooth Frame Rate，否则没有办法很好的对性能进行分析。对于打包优化，官方的建议是至少在Development以上的级别下进行，因为Debug下有很多东西非常的影响性能。",[11,88,89],{},"同时关闭垂直同步在某些情况下也是必要的，使用r.VSync指令就可以了，在打包版本中也可以使用命令行参数-NoVSync。",[91,92,93],"h3",{"id":93},"常用指令",[11,95,96],{},"官方的stat指令集中提供了大量的用于性能优化的指令，其中比较常用的是通用的问题定位指令，在找到性能瓶颈之后，可以进一步的使用相关的指令进行问题的查看。",[98,99,101],"h4",{"id":100},"stat-unit","stat unit",[11,103,104],{},[48,105],{"alt":50,"src":106},"\u002Fwp-content\u002Fuploads\u002F2017\u002F07\u002Fimage_thumb-2.png",[11,108,109],{},"使用stat unit就可以粗略的对问题的来源进行定位。",[11,111,112],{},"为了差分渲染与游戏逻辑的性能消耗，可以通过r.SetRes或者r.ScreenPercentage来有效的降低渲染消耗来辅助定位。",[11,114,115],{},"这里面Frame与stat fps输出的帧时间是一样的，其他的三项则对应不同的性能瓶颈：",[11,117,118],{},"Game为CPU游戏线程，如果是这里有问题的话通常就是游戏逻辑本身设计太消耗性能了。",[11,120,121],{},"Draw为CPU渲染线程，这里是负责向GPU发送DrawCall的，已经是渲染相关的优化。",[11,123,124],{},"GPU就是GPU的帧时间了，完全的渲染相关。",[98,126,128],{"id":127},"stat-scenerendering","stat SceneRendering",[11,130,131],{},"指令可以用于更加详细的对性能瓶颈进行分析",[11,133,134],{},[48,135],{"alt":50,"src":136},"\u002Fwp-content\u002Fuploads\u002F2017\u002F07\u002Fimage_thumb-3.png",[11,138,139],{},"通过寻找性能消耗较大的pass，对其进行针对性的优化就好了。",[98,141,143],{"id":142},"stat-gpu","stat gpu",[11,145,146],{},"则可以用于查看gpu上的性能消耗比例",[11,148,149],{},[48,150],{"alt":50,"src":151},"\u002Fwp-content\u002Fuploads\u002F2017\u002F07\u002Fimage_thumb-4.png",[98,153,155],{"id":154},"startfirestat-stopfile","StartFire\u002Fstat stopfile",[11,157,158],{},"这组指令可以将性能分析文件生成到Saved\u002FProfiling文件夹中。这些文件可以使用Front End中的分析器进行加载并分析：",[11,160,161],{},[48,162],{"alt":50,"src":163},"\u002Fwp-content\u002Fuploads\u002F2017\u002F07\u002Fimage_thumb-5.png",[98,165,167],{"id":166},"gpu-visualizer","GPU Visualizer",[11,169,170,171,175],{},"在优化中最常使用的工具还是GPU Visualizer，按快捷键",[172,173,174],"span",{},"Ctrl+Shift+,","就会出来了。",[11,177,178],{},[48,179],{"alt":180,"src":181},"SNAGHTMLbe577a","\u002Fwp-content\u002Fuploads\u002F2017\u002F07\u002FSNAGHTMLbe577a_thumb.png",[11,183,184],{},"性能优化从这里入手的话就比较直观一些。",[91,186,187],{"id":187},"渲染优化",[11,189,190],{},"渲染上会产生的瓶颈通常分为三大类别，Pixel-bound、Vertex-bound、Memory-bound。",[98,192,194],{"id":193},"pixel-bound","Pixel-bound",[11,196,197],{},"Translucent的材质物体的大规模使用，半透明粒子都会导致在像素渲染级别上出现瓶颈。",[11,199,200],{},"在材质的使用上，Opaque的性能是最高的，因为可以有效的进行Z-buffer裁剪，其次是Masked材质，性能消耗最高的是Translucent。尤其是场景内出现大规模的半透明物体叠加的时候，就会产生大量的绘制负担。",[11,202,203],{},"在场景布局上，利用Opaque的物体进行遮蔽，尽量的避免镜头内出现大量半透明物体，有效的利用LOD都是非常重要的。",[11,205,206],{},"还有一个方面就是Quad Overdraw，这个似乎是由于GPU的渲染机制引起的，直接引用视频中看到的图片：",[11,208,209],{},[48,210],{"alt":211,"src":212},"clip_image001","\u002Fwp-content\u002Fuploads\u002F2017\u002F07\u002Fclip_image001_thumb.png",[11,214,215],{},"在优化上，要尽量避免屏幕空间内出现小而长的三角形，将其分割为更多的三角形反而更加有效。同时，将屏幕内足够小的物体进行LOD，也可以有效的避免性能消耗。对于Foliage上的叶子，可以使用Particle Trimming来避免过度绘制，据说Speed Tree是自带这个功能的，不过由于没有使用过所以无法确认。particle trimming如其名，在粒子的优化中也有作用。",[11,217,218],{},[48,219],{"alt":50,"src":220},"\u002Fwp-content\u002Fuploads\u002F2017\u002F07\u002Fimage_thumb-6.png",[11,222,223],{},"不过没有实际使用过这个功能，按照介绍的话，性能提升还是比较明显的：",[225,226,227,230,233,236,239,242,245,248],"blockquote",{},[11,228,229],{},"Original: 100%",[11,231,232],{},"Aligned rect: 69.23%",[11,234,235],{},"Optimized 3 verts: 70.66%",[11,237,238],{},"Optimized 4 verts: 60.16%",[11,240,241],{},"Optimized 5 verts: 55.60%",[11,243,244],{},"Optimized 6 verts: 53.94%",[11,246,247],{},"Optimized 7 verts: 52.31%",[11,249,250],{},"Optimized 8 verts: 51.90%",[11,252,253,254,33],{},"这个数据来自于[",[21,255,258],{"href":256,"rel":257},"http:\u002F\u002Fwww.humus.name\u002Findex.php?page=Comments&ID=266",[25],"这里",[11,260,261],{},"Quared OverDraw可以通过调整为检查模式来对场景中的物体进行检查",[11,263,264],{},[48,265],{"alt":50,"src":266},"\u002Fwp-content\u002Fuploads\u002F2017\u002F07\u002Fimage_thumb-7.png",[98,268,270],{"id":269},"vertex-bound","Vertex-bound",[11,272,273],{},"通常受到影响的就是场景中三角形的数量，如果印象没有错的话，当前UE4内部所有的面应该都是三角形的。会在这里产生瓶颈的还有阴影投射与曲面细分，关于曲面细分，官方有建议是尽量不要使用，在建模阶段直接进行细分是更加具有效率的。",[11,275,276],{},"UV Seam和Hard Edges会额外的增加计算的顶点计算的负担，要尽量避免减少使用的频度。",[11,278,279],{},"同时Morph Target与WolrdPostionOffset也会产生更多的顶点计算，Skinned Mesh也是如此，不过这些方面，除了WorldPostionOffset可以尽量避免使用之外，只能从游戏逻辑本身的设计上入手了呢。",[11,281,282],{},"如果有使用LandScape的话，尽量减少LandScape的面数可以很好的提高性能。",[11,284,285],{},"为了避免过量的顶点计算负担，对于远景物体尽量使用BillBoard或者Imposter meshes、Skyboxe texture来代替3D物体。",[98,287,289],{"id":288},"memory-bound","Memory-bound",[11,291,292],{},"如果有大量的材质使用了不同的贴图，导致Texture Sample的数量爆炸的话，就会自然的变成瓶颈。",[11,294,295],{},"UE4有使用Texture Streaming，如果存储空间爆炸了的话，就会出现贴图模糊的情况，这时候可以使用Stat Streaming指令进行分析。",[11,297,298],{},[48,299],{"alt":50,"src":300},"\u002Fwp-content\u002Fuploads\u002F2017\u002F07\u002Fimage_thumb-8.png",[11,302,303],{},"对于使用相同的材质的情况，可以通过材质中的设定让他们共享Texture。同时在材质中启用GPU本身支持的压缩可以有效的减少存储空间的占用，尽量的使用Texture Packing也是非常的重要的。载入的时候尽量使用Mip级别较低的图片，可以有效的减少存储占用。在材质的贴图使用中，尽量的进行优化的配置也非常的重要。",[11,305,306],{},[48,307],{"alt":50,"src":308},"\u002Fwp-content\u002Fuploads\u002F2017\u002F07\u002Fimage_thumb-9.png",[11,310,311],{},"对于不需要太精细的贴图可以限制其最大尺寸",[11,313,314],{},[48,315],{"alt":316,"src":317},"clip_image0014","\u002Fwp-content\u002Fuploads\u002F2017\u002F07\u002Fclip_image0014_thumb.png",[11,319,320],{},"另外，光照贴图等也是被算作贴图占用存储空间的，因此也有在这里产生瓶颈的可能性。因此尽量的调低光照贴图的分辨率可以很好的提高性能。",[11,322,323],{},"使用Alt+0\u002FLight Map Density可以对场景中的光照贴图密度进行分析。",[11,325,326],{},"在窗口>统计总也能进一步的对当前的存储使用状况进行分析",[11,328,329],{},[48,330],{"alt":50,"src":331},"\u002Fwp-content\u002Fuploads\u002F2017\u002F07\u002Fimage_thumb-10.png",[98,333,334],{"id":334},"前向渲染与延迟渲染",[11,336,337],{},"UE4面向VR可以区分前向渲染与延迟渲染，两方各有优劣，在优化上也会有所不同。引擎采用的前向渲染方式是Cluster Forward Shading(Forward+)，详细的技术细节并没有关注过，不过通常前向渲染的光照计算成本会高些，尤其是光照数量较多时会产生明显的性能下降。",[11,339,340],{},"使用缓存区可视化功能，可以对延迟渲染使用的缓存进行查看。",[38,342,343],{"id":343},"按通道优化",[11,345,346],{},"优化的过程，通常是在玩家通常的地点放置观察摄像，然后通过这个固定点使用GPU Visualizer进行优化。",[11,348,349],{},"在使用GPU Visualizer时，会看到各个通道的性能消耗比例，这样就可以针对不同的通道进行优化了。",[11,351,352],{},"在打包模式下按下GPU Visualizer后，不会出现界面，但是相关数据会被记录到Log中。",[11,354,355],{},"在GPU Visualizer中对帧时间消耗进行排序可以帮助快速找到当前消耗的瓶颈，不过有的pass的消耗可能是不得已的，这里大概就是需要经验的地方了。pass根据工作方式大体上分别工作于屏幕空间和运算空间，像是PostProcess这样的后期处理以及延迟渲染的光照都是工作在屏幕空间的。而像是Z-Buffer的生成这一类的操作则是通过运算空间进行的。一般工作在屏幕空间的通道受到渲染分辨率的影响就会比较大，例如SSAO、AA等。而在运算空间的Pass就会受到Mesh的数量、面数、shader的复杂度的影响。",[91,357,358],{"id":358},"光照",[98,360,362],{"id":361},"lightcompositiontasks_prelighting","LightCompositionTasks_PreLighting",[11,364,365],{},"这个通道被SSAO以及非D-Buffer类型的Decals使用。",[11,367,368],{},"这里的性能消耗在屏幕空间上，通过在PostProcess中降低AO的半径和消退距离可以减少其产生的运算负担。",[11,370,371],{},"同时，非D-Buffer类的Decals也会对其产生影响。",[98,373,375],{"id":374},"composition-after-lighting","Composition After Lighting",[11,377,378],{},"只影响Subsurface Profile的Shading Model的SSS效果的通道。",[11,380,381],{},"由于工作于屏幕空间，减少其在屏幕中的占比就可以有效的减少消耗。比如使用LOD之类的。",[11,383,384],{},"使用Subsurface的Shading Model或者Matcap之类的来进行替代也可以降低这里的消耗。",[98,386,388],{"id":387},"compute-light-grid","Compute Light Grid",[11,390,391],{},"用于计算光照相关性的pass，减少动态光照的数量就可以减低消耗。",[98,393,395],{"id":394},"lights","Lights",[11,397,398],{},[48,399],{"alt":400,"src":401},"clip_image0016","\u002Fwp-content\u002Fuploads\u002F2017\u002F07\u002Fclip_image0016_thumb.png",[11,403,404],{},"延迟渲染的光照是基于DBuffer在屏幕空间呢进行演算的，因此受屏幕分辨率的影响也比较大。",[11,406,407],{},"无论是静态光照还是动态光照，都会受到光照本身的数量以及范围的影响。",[11,409,410],{},"而动态光照的效率额外的受到光照影响到的面数的影响。",[11,412,413],{},"光照的优化应该尽量的减少光照范围的重叠，避免大范围的动态光照，关闭没有太大必要性物体的动态阴影投射。光照的重叠等的查看可以通过调整视图",[11,415,416],{},[48,417],{"alt":50,"src":418},"\u002Fwp-content\u002Fuploads\u002F2017\u002F07\u002Fimage_thumb-11.png",[11,420,421],{},"来进行有效的排查。",[11,423,424],{},"光照类型的性能消耗是点光源>直线光源>聚光灯，在使用光照时，如果能用IES或者光照函数进行替代的话，不要使用很多个光源组合成类似的效果。",[11,426,427],{},"同时光照函数通常的性能消耗大于IES，其消耗受到光照材质本身的复杂度的影响比较大。",[11,429,430],{},"在对光照范围进行优化时，可以通过关闭Use Inverse Squared Falloff，并自己设置Light Falloff Exponent来达到减小光照范围达到类似光照效果。",[98,432,434],{"id":433},"filter-translucent-volume","Filter Translucent Volume",[11,436,437],{},"这是半透明物体的光照计算时会用到的pass",[11,439,440],{},[48,441],{"alt":442,"src":443},"clip_image0018","\u002Fwp-content\u002Fuploads\u002F2017\u002F07\u002Fclip_image0018_thumb.png",[225,445,446],{},[11,447,448],{},"带光照的半透明物体的光照大多数来源于一系列面向视锥体的cascade处理过的体积贴图。 这样在体积内的任意点，光照均为单次，但缺点就是体积贴图的分辨率比较低，而且从观察者角度来说，只涵盖了有限的深度范围。",[11,450,451,452,33],{},"详细的说明可以参考官方文档：[",[21,453,456],{"href":454,"rel":455},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FEngine\u002FRendering\u002FLightingAndShadows\u002FLitTranslucency\u002Findex.html",[25],"带光照的半透明物体",[11,458,459],{},"这里产生了瓶颈的话要检查光照的数目、范围和影响到的物体的数量。",[98,461,463],{"id":462},"shadowdepths","ShadowDepths",[11,465,466],{},"这个生成通过光源进行阴影投射的深度数据的pass。",[11,468,469],{},"作用与这里的消耗主要受到开启了投影的光的数目、动态光照影响的面数、以及阴影的质量的影响。",[11,471,472],{},"阴影的质量可以通过Sg.shadow quality进行全局的调节。",[98,474,476],{"id":475},"shadow-projection","Shadow Projection",[11,478,479],{},"实际的阴影投射，工作于屏幕空间，所以受分辨率影响，同时也会受到投影的光照数量和范围的影响。",[91,481,482],{"id":482},"基础通道",[98,484,486],{"id":485},"prepass-dom_","PrePass DOM_",[11,488,489],{},"EarlyZPass，对非透明物体进行的早期的深度计算。",[11,491,492],{},[48,493],{"alt":494,"src":495},"clip_image00110","\u002Fwp-content\u002Fuploads\u002F2017\u002F07\u002Fclip_image00110_thumb.png",[11,497,498],{},"数据似乎被用于遮蔽计算，如果不使用Dbuffer Decals的话可以关掉。虽然视频中是这样建议的，但是早期的深度计算可以在BasePass之前进行遮蔽计算，能让basepass以及之后所有的通道的计算减少很多。而且即便在这里不进行深度计算，会影响这里的运算量的变量依然会作用与后面的深度计算阶段，因此关闭EarlyZPass还是需要多做考虑的。另外要使用DBuffer Decals的话必须使用Opaque and masked的zpass计算，否则应该会出现奇怪的现象。",[11,500,501],{},[48,502],{"alt":503,"src":504},"clip_image00112","\u002Fwp-content\u002Fuploads\u002F2017\u002F07\u002Fclip_image00112_thumb.png",[11,506,507],{},"性能上受到非透明物体的面数的影响，同时根据上面的选项不同也受到Masked的材质的影响。",[98,509,511],{"id":510},"hzb","HZB",[11,513,514],{},"Hierarchical Z-Buffer，用于计算HZB遮蔽，同时也会被屏幕空间内的射线演算使用，例如屏幕空间反射计算、AO等。同时被用于Mip的设置。",[11,516,517],{},"受屏幕空间的大小影响。据官方描述，HZB拥有较高的固定性能消耗，每个物体所造成的消耗较小。可以通过r.HZBOcclusion来调整运算的类型。",[98,519,521],{"id":520},"base-pass","Base Pass",[11,523,524],{},"对非透明的物体进行演算并填充到GBuffer，使用缓冲区可视化模式可以在视图中看到效果。几乎所有的延迟渲染都受到其影响，因此才叫基础通道。",[11,526,527],{},"其计算结果包括base color, metallic, specular, roughness, normal, sss profile，并且Decals、Fog以及Velocity的计算也在此处。",[11,529,530],{},"其开销受到屏幕空间尺寸、物体数量、面数、Decals的数量、Shader的复杂度，生成的过程中包含光照贴图的推送，因此也会受到光照贴图的大小的影响。",[11,532,533],{},"可以通过Stat rhi指令检查各种贴图和triangle的消耗。",[11,535,536],{},"另外，前向渲染的光照也在这里进行，此时光照的数量也会影响到这里的消耗。",[98,538,540],{"id":539},"translucency",[541,542,543],"strong",{},"Translucency",[11,545,546],{},"半透明的材质以及光照演算，通过Stat gpu中的Translucency and Translucent Lighting可以进一步查看。",[11,548,549],{},"消耗受到屏幕空间大小以及屏幕内的半透明物体的数量影响，半透明物体的光照计算要尽量减少过度绘制。以及避免过多的需要进行半透明光照计算的光的数量。",[91,551,552],{"id":552},"其他",[98,554,556],{"id":555},"particle-simulationinjection","Particle Simulation\u002FInjection",[11,558,559],{},"粒子模拟，这里只展示GPU粒子的消耗，性能主要受粒子数量以及是否开启了基于深度的粒子碰撞影响。",[11,561,562],{},"粒子的优化主要通过LOD以及设计上的优化进行。",[98,564,566],{"id":565},"post-process","Post process",[11,568,569],{},"UE4的后期处理功能比较多，AA、DOF、自动曝光以及很多其他的功能都在其中。每种PP特效都会产生额外的性能消耗，如果使用了PP材质的话，其复杂度也会影响性能。",[98,571,573],{"id":572},"relection-envirionment","Relection Envirionment",[11,575,576],{},"反射捕捉控件的计算缓存",[11,578,579],{},"可以将显示模式调整为Reflections来查看各个控件对缓存的影响",[11,581,582],{},"通常的建议是，放一个大范围的低精度反射捕捉，然后在需要的地方尽量不重叠的放置高精度的捕捉控件。",[11,584,585],{},"影响性能的主要就是捕捉控件的数量及范围，也受屏幕空间的大小影响。",[98,587,589],{"id":588},"render-velocities","Render Velocities",[11,591,592],{},"速度主要用于TAA以及Motion Blur，受到移动物体的数量以及其面数的影响。",[11,594,595],{},"主要的优化策略是使用LOD。",[98,597,599],{"id":598},"screen-space-reflections","Screen Space Reflections",[11,601,602],{},"屏幕空间反射通过以下连个指令来进行调节：",[11,604,605],{},"r.ssr.maxroughness 0.0-1.0",[11,607,608],{},"r.ssr.quality 0..4",[11,610,611],{},"其中Maximum roughness决定着计算的范围的大小。",[38,613,614],{"id":614},"第三方工具",[11,616,617],{},"要使用第三方工具进行Profiling，一般需要打包。在进行捕捉之前，打开Toggle draw event的话可以让UE4提供更多的信息给第三方Profiling工具，捕捉完之后则使用同一个命令关闭就好。",[11,619,620],{},"第三方工具有Intel的GraphicsPerformance Analyzers、AMD的GPU PerfStudio以及开源的RenderDoc等。",[11,622,623],{},"使用第三方工具可以提供一些在硬件层次上获得的细节，在使用UE4本身的Profiling工具无法定位问题时可以进行尝试。",[38,625,626],{"id":626},"总结",[11,628,629],{},"总体而言，Profiling是一个复杂的过程。由于是面向最终用户的，需要花费很多精力在上面。",[11,631,632],{},"通常的策略包括LOD、裁剪、避免重叠以及低性能的近似替代等，很多的问题还需要在实践中才能发现和解决。",{"title":634,"searchDepth":635,"depth":636,"links":637},"",2,3,[638,639,655,679,680],{"id":40,"depth":635,"text":41},{"id":79,"depth":635,"text":80,"children":640},[641,649],{"id":93,"depth":636,"text":93,"children":642},[643,645,646,647,648],{"id":100,"depth":644,"text":101},4,{"id":127,"depth":644,"text":128},{"id":142,"depth":644,"text":143},{"id":154,"depth":644,"text":155},{"id":166,"depth":644,"text":167},{"id":187,"depth":636,"text":187,"children":650},[651,652,653,654],{"id":193,"depth":644,"text":194},{"id":269,"depth":644,"text":270},{"id":288,"depth":644,"text":289},{"id":334,"depth":644,"text":334},{"id":343,"depth":635,"text":343,"children":656},[657,666,672],{"id":358,"depth":636,"text":358,"children":658},[659,660,661,662,663,664,665],{"id":361,"depth":644,"text":362},{"id":374,"depth":644,"text":375},{"id":387,"depth":644,"text":388},{"id":394,"depth":644,"text":395},{"id":433,"depth":644,"text":434},{"id":462,"depth":644,"text":463},{"id":475,"depth":644,"text":476},{"id":482,"depth":636,"text":482,"children":667},[668,669,670,671],{"id":485,"depth":644,"text":486},{"id":510,"depth":644,"text":511},{"id":520,"depth":644,"text":521},{"id":539,"depth":644,"text":543},{"id":552,"depth":636,"text":552,"children":673},[674,675,676,677,678],{"id":555,"depth":644,"text":556},{"id":565,"depth":644,"text":566},{"id":572,"depth":644,"text":573},{"id":588,"depth":644,"text":589},{"id":598,"depth":644,"text":599},{"id":614,"depth":635,"text":614},{"id":626,"depth":635,"text":626},"2017-07-30","md",{"layout":684,"status":685,"published":686,"author":687,"author_login":689,"author_email":690,"wordpress_id":691,"wordpress_url":692,"date_gmt":693,"excerpt":694},"post","publish",true,{"display_name":688,"login":689,"email":690,"url":634},"风铃","flinkor","flinkor@foxmail.com",1845,"\u002F\u002F?p=1845","2017-07-30 08:10:34 +0000",{"type":8,"value":695},[696],[11,697,13],{},"\u002F2017-07-30-ue4-profiling-preview",{"title":6,"description":13},"_legacy\u002F2017\u002F2017-07-30-ue4-profiling-preview",[702,80],"UE4","WoD3ma-Xm1UBlc5-o8OYia9aLOKDskWvsGgKObwEBA0",{"id":705,"title":706,"body":707,"date":815,"description":711,"extension":682,"meta":816,"navigation":686,"path":825,"seo":826,"stem":827,"tags":828,"__hash__":830},"blogs\u002F_legacy\u002F2017\u002F2017-06-24-ue4-compile-errors-log.md","UE4错误笔记",{"type":8,"value":708,"toc":810},[709,712,715,719,722,725,730,733,736,741,744,749,753,756,764,767,770,780,784,787,792,801,804],[11,710,711],{},"记录了最近遇到的几个编译错误。",[11,713,714],{},"当前使用的UE4版本为4.15.3。",[38,716,718],{"id":717},"c4530","C4530",[11,720,721],{},"这个错误是尝试在UE4中使用Try\u002FExcept引起的。",[11,723,724],{},"报错如下：",[225,726,727],{},[11,728,729],{},"warning C4530: C++ exception handler used, but unwind semantics are not enabled. Specify \u002FEHsc",[11,731,732],{},"UE4默认的情况下不允许使用Exception。",[11,734,735],{},"AnswerHub有回答说要在build.cs中设置：",[225,737,738],{},[11,739,740],{},"UEBuildConfiguration.bForceEnableExceptions = true;",[11,742,743],{},"但是由于新版本的变更，这个属性变成只读了，现在需要这样设置：",[225,745,746],{},[11,747,748],{},"bEnableExceptions = true;",[38,750,752],{"id":751},"c2039","C2039",[11,754,755],{},"某种意义上的老朋友，报错如下：",[225,757,758,761],{},[11,759,760],{},"1>E:\\UEPro\\New_UI\\Plugins\\EasyLog\\Source\\EasyLog\\Private\\LogHolder.cpp(28): error C2039: “CreateDirectoryW”: 不是“IPlatformFile”的成员",[11,762,763],{},"1> f:\\epic\\ue_4.15\\engine\\source\\runtime\\core\\public\\GenericPlatform\u002FGenericPlatformFile.h(160): note: 参见“IPlatformFile”的声明",[11,765,766],{},"UE4中这种错误报进引擎内部的，一般都是因为定义冲突引起的，需要针对包含关系进行排查。",[11,768,769],{},"这种与Win Api相关的，通常是由于UE4中对Windows.h的兼容引起的。有时也会报出DWORD未定义这样的错误。可以尝试使用引擎提供的帮助包含：",[771,772,777],"pre",{"className":773,"code":775,"language":776},[774],"language-text","#include \"AllowWindowsPlatformTypes.h\"\n\n#include \"something_about_windows.h\"\n\n#include \"HideWindowsPlatformTypes.h\"\n","text",[778,779,775],"code",{"__ignoreMap":634},[38,781,783],{"id":782},"c4596","C4596",[11,785,786],{},"这个错误其实是VS2017的版本更新的新功能造成的，报错如下：",[225,788,789],{},[11,790,791],{},"error C4596: 'Blablabla': illegal qualified name in member declaration",[11,793,794,795,800],{},"详细的原因可以查看MSDN关于[",[21,796,799],{"href":797,"rel":798},"https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fcpp\u002Fbuild\u002Freference\u002Fpermissive-standards-conformance",[25],"permissive","]的文档，这个功能是为了让代码更加符合标准以提高代码的可移植性。",[11,802,803],{},"修正方式文档中已经有提供，通常都是类似这样的修改：",[771,805,808],{"className":806,"code":807,"language":776},[774],"template \u003Ctypename T_Ptr, typename Pred>\n\nclass RegistryWithPred : public AbstractRegistry\u003CT_Ptr, std::vector\u003CT_Ptr*>> {\n\npublic:\n- typedef typename RegistryWithPred\u003CT_Ptr, Pred>::iterator iterator;\n- typedef typename RegistryWithPred\u003CT_Ptr, Pred>::const_iterator const_iterator;\n+ typedef typename \u002F*RegistryWithPred\u003CT_Ptr, Pred>::*\u002Fiterator iterator;\n+ typedef typename \u002F*RegistryWithPred\u003CT_Ptr, Pred>::*\u002Fconst_iterator const_iterator;\n\nRegistryWithPred(void) {\n\n}\n",[778,809,807],{"__ignoreMap":634},{"title":634,"searchDepth":635,"depth":636,"links":811},[812,813,814],{"id":717,"depth":635,"text":718},{"id":751,"depth":635,"text":752},{"id":782,"depth":635,"text":783},"2017-06-24",{"layout":684,"status":685,"published":686,"author":817,"author_login":689,"author_email":690,"wordpress_id":818,"wordpress_url":819,"date_gmt":820,"excerpt":821},{"display_name":688,"login":689,"email":690,"url":634},1796,"\u002F\u002F?p=1796","2017-06-24 11:58:07 +0000",{"type":8,"value":822},[823],[11,824,711],{},"\u002F2017-06-24-ue4-compile-errors-log",{"title":706,"description":711},"_legacy\u002F2017\u002F2017-06-24-ue4-compile-errors-log",[702,829],"Error","N8GHUqThYgjHtwWVx0yo1qhwm9aqaEhhIWiClvRAHzo",{"id":832,"title":833,"body":834,"date":815,"description":838,"extension":682,"meta":1341,"navigation":686,"path":1350,"seo":1351,"stem":1352,"tags":1353,"__hash__":1355},"blogs\u002F_legacy\u002F2017\u002F2017-06-24-unrealpak-note.md","UnrealPak的使用笔记",{"type":8,"value":835,"toc":1318},[836,839,846,850,853,856,987,990,994,997,1007,1011,1014,1017,1021,1024,1028,1031,1034,1038,1041,1044,1046,1050,1053,1056,1059,1063,1066,1068,1071,1074,1077,1080,1083,1086,1089,1092,1095,1098,1101,1104,1107,1110,1113,1116,1119,1125,1128,1134,1137,1143,1146,1154,1160,1163,1169,1172,1178,1181,1184,1187,1191,1199,1202,1206,1212,1215,1219,1222,1228,1231,1234,1237,1240,1243,1250,1253,1256,1259,1265,1268,1274,1277,1283,1286,1289,1295,1298,1301,1304,1307,1314],[11,837,838],{},"UE4的打包可以通过编辑器直接进行，但是当要实现一些特殊的目标，例如热更新等时，就需要自己使用UnrealPak进行才行。",[11,840,841,842,845],{},"当前使用UE4版本4.16，所有打包",[541,843,844],{},"仅","在Shiping模式下测试过。",[38,847,849],{"id":848},"unrealpak","UnrealPak",[11,851,852],{},"UnrealPak在整个打包过程中位于最后面的一个过程，在编辑器中打包的话可以通过输出判断打包的各个阶段。在编译、烘焙之后，如果有勾选Use Pak这个选项的话，引擎就会尝试将以上两个过程中生成的内容打包到Pak中去。",[11,854,855],{},"UnrealPak.exe位于引擎目录下Engine\u002FWin64中，运行程序，在错误提示中有给出基本的使用方法：",[771,857,861],{"className":858,"code":859,"language":860,"meta":634,"style":634},"language-shell shiki shiki-themes github-light-high-contrast github-dark monokai","UnrealPak \u003CPakFilename> -Test\nUnrealPak \u003CPakFilename> -List\nUnrealPak \u003CPakFilename> -Extract \u003CExtractDir>\nUnrealPak \u003CPakFilename> -Create=\u003CResponseFile> [Options]\nUnrealPak \u003CPakFilename> -Dest=\u003CMountPoint>\nUnrealPak GenerateKeys=\u003CKeyFilename>\nUnrealPak GeneratePrimeTable=\u003CKeyFilename> [-TableMax=\u003CN>]\nUnrealPak \u003CPakFilename1> \u003CPakFilename2> -diff\nUnrealPak -TestEncryption\n\nOptions:\n-blocksize=\u003CBlockSize>\n-bitwindow=\u003CBitWindow>\n-compress\n-encrypt\n-order=\u003COrderingFile>\n-diff (requires 2 filenames first)\n-enginedir (specify engine dir for when using ini encryption configs)\n-projectdir (specify project dir for when using ini encryption configs)\n-encryptionini (specify ini base name to gather encryption settings from)\n-encryptindex (encrypt the pak file index, making it unusable in unrealpak without supplying the key)\n","shell",[778,862,863,870,875,880,885,891,897,903,909,915,921,927,933,939,945,951,957,963,969,975,981],{"__ignoreMap":634},[172,864,867],{"class":865,"line":866},"line",1,[172,868,869],{},"UnrealPak \u003CPakFilename> -Test\n",[172,871,872],{"class":865,"line":635},[172,873,874],{},"UnrealPak \u003CPakFilename> -List\n",[172,876,877],{"class":865,"line":636},[172,878,879],{},"UnrealPak \u003CPakFilename> -Extract \u003CExtractDir>\n",[172,881,882],{"class":865,"line":644},[172,883,884],{},"UnrealPak \u003CPakFilename> -Create=\u003CResponseFile> [Options]\n",[172,886,888],{"class":865,"line":887},5,[172,889,890],{},"UnrealPak \u003CPakFilename> -Dest=\u003CMountPoint>\n",[172,892,894],{"class":865,"line":893},6,[172,895,896],{},"UnrealPak GenerateKeys=\u003CKeyFilename>\n",[172,898,900],{"class":865,"line":899},7,[172,901,902],{},"UnrealPak GeneratePrimeTable=\u003CKeyFilename> [-TableMax=\u003CN>]\n",[172,904,906],{"class":865,"line":905},8,[172,907,908],{},"UnrealPak \u003CPakFilename1> \u003CPakFilename2> -diff\n",[172,910,912],{"class":865,"line":911},9,[172,913,914],{},"UnrealPak -TestEncryption\n",[172,916,918],{"class":865,"line":917},10,[172,919,920],{"emptyLinePlaceholder":686},"\n",[172,922,924],{"class":865,"line":923},11,[172,925,926],{},"Options:\n",[172,928,930],{"class":865,"line":929},12,[172,931,932],{},"-blocksize=\u003CBlockSize>\n",[172,934,936],{"class":865,"line":935},13,[172,937,938],{},"-bitwindow=\u003CBitWindow>\n",[172,940,942],{"class":865,"line":941},14,[172,943,944],{},"-compress\n",[172,946,948],{"class":865,"line":947},15,[172,949,950],{},"-encrypt\n",[172,952,954],{"class":865,"line":953},16,[172,955,956],{},"-order=\u003COrderingFile>\n",[172,958,960],{"class":865,"line":959},17,[172,961,962],{},"-diff (requires 2 filenames first)\n",[172,964,966],{"class":865,"line":965},18,[172,967,968],{},"-enginedir (specify engine dir for when using ini encryption configs)\n",[172,970,972],{"class":865,"line":971},19,[172,973,974],{},"-projectdir (specify project dir for when using ini encryption configs)\n",[172,976,978],{"class":865,"line":977},20,[172,979,980],{},"-encryptionini (specify ini base name to gather encryption settings from)\n",[172,982,984],{"class":865,"line":983},21,[172,985,986],{},"-encryptindex (encrypt the pak file index, making it unusable in unrealpak without supplying the key)\n",[11,988,989],{},"整个程序大致的功能就是可以解压和打包Pak文件：",[91,991,993],{"id":992},"generatekeys","GenerateKeys",[11,995,996],{},"生成加密Key到文件中，RSA用于对Pak进行签名。",[998,999,1000,1004],"ul",{},[1001,1002,1003],"li",{},"P：用于RSA的素数p",[1001,1005,1006],{},"Q：用于RSA的素数q",[91,1008,1010],{"id":1009},"generateprimetable","GeneratePrimeTable",[11,1012,1013],{},"生成素数表，",[11,1015,1016],{},"TableMax：生成表的停止（最大）数值，默认值10000",[91,1018,1020],{"id":1019},"testencryption","TestEncryption",[11,1022,1023],{},"测试加密算法的执行，并不检测具体的Key和文件。",[91,1025,1027],{"id":1026},"test","TEST",[11,1029,1030],{},"对Pak文件进行检测，会对每一个文件进行SHA1校验。",[11,1032,1033],{},"signed：Pak是否已签名",[91,1035,1037],{"id":1036},"list","List",[11,1039,1040],{},"列出文件。",[11,1042,1043],{},"SizeFilter：文件的大小过滤器，会对大于这个大小的文件进行总Size的统计。",[11,1045,1033],{},[91,1047,1049],{"id":1048},"diff","Diff",[11,1051,1052],{},"对比两个Pak文件",[11,1054,1055],{},"nouniques：通过指定nouniquesfile1、nouniquesfile2，是否列出Unique的文件",[11,1057,1058],{},"signed：Pak是否已签名，不能分开指定。",[91,1060,1062],{"id":1061},"extract","Extract",[11,1064,1065],{},"解包。",[11,1067,1033],{},[91,1069,1070],{"id":1070},"构建",[11,1072,1073],{},"blocksize：块大小，MB或KB单位",[11,1075,1076],{},"bitwindow：压缩的窗口大小，这个值越大压缩的效果越好，同时消耗的性能也越高。",[11,1078,1079],{},"patchpaddingalign：是否在进行Patch时启用对齐，不patch时对齐时自动的。",[11,1081,1082],{},"encryptindex：加密索引，使得文件索引在不提供key的情况下无法解密。",[11,1084,1085],{},"order：文件读取顺序列表，这个order在cook时会自动生成，一般不用自己提供。uexp会被加上序列值1 \u003C\u003C 29和ubulk会被加上1 \u003C\u003C 30。没有提供order的其他文件将获得 (1 \u003C\u003C 28)。",[11,1087,1088],{},"sign：对Pak进行签名的RSA的key",[98,1090,1091],{"id":1091},"create",[11,1093,1094],{},"文件列表，可在其中单独指定compress和encrypt，但是如果在命令行中指定了的话，就会被强制使用到所有文件。可以是目录或是使用*通配符。",[11,1096,1097],{},"generatepatch：生成Patch |同时可使用 TempFiles来指定临时目录",[11,1099,1100],{},"compress：压缩",[11,1102,1103],{},"encrypt：加密",[98,1105,1106],{"id":1106},"dest",[11,1108,1109],{},"替换Pak中的MountPoint。",[91,1111,1112],{"id":1112},"加密和压缩",[11,1114,1115],{},"Pak采用RSA进行签名，AES进行加密，SHA1进行校验。",[11,1117,1118],{},"加密的流程没有测试，加密的参数可以在ini文件Encryption中进行设置，要使用ini配置的话，需要指定-encryptionini。或者，可以直接在命令行中提供加密参数：",[771,1120,1123],{"className":1121,"code":1122,"language":776},[774],"-Sign=filename\n\n",[778,1124,1122],{"__ignoreMap":634},[11,1126,1127],{},"或者",[771,1129,1132],{"className":1130,"code":1131,"language":776},[774],"-sign=0x123456789abcdef+0x1234567+0x12345abc\n",[778,1133,1131],{"__ignoreMap":634},[11,1135,1136],{},"其中第一个数是Exponent，第二个数是Module，第三个数是公钥的Exponent",[771,1138,1141],{"className":1139,"code":1140,"language":776},[774],"-aes=blabla\n",[778,1142,1140],{"__ignoreMap":634},[11,1144,1145],{},"来提供AES加密秘钥。",[11,1147,1148,1149,1153],{},"当前的ProjectLauncher打包，会自动添加-encryptionini，但是只要没有提供Encryption.ini，或者[",[21,1150,1152],{"href":1151},"\u002F2017-06-06-ue4-config-usage\u002F#i","Ini配置","]不正确，就不会进行加密。配置文件的内容应该是类似这样的：",[771,1155,1158],{"className":1156,"code":1157,"language":776},[774],"[Core.Encryption]\nSignPak=True\nEncryptPak=True\nrsa.publicexp=blablabla\nrsa.privateexp=blablabla\nrsa.modulus=blablabla\naes.key=blablabla\n",[778,1159,1157],{"__ignoreMap":634},[11,1161,1162],{},"由于没有实际测试过，所以不是很明确Encryption的配置能否被打包后的程序自动加载。如果出现了问题，可以检查下CoreDelegate中的：",[771,1164,1167],{"className":1165,"code":1166,"language":776},[774],"static FPakEncryptionKeyDelegate& GetPakEncryptionKeyDelegate();\nstatic FPakSigningKeysDelegate& GetPakSigningKeysDelegate();\n",[778,1168,1166],{"__ignoreMap":634},[11,1170,1171],{},"引擎在Mount时会从这两个Delegate中获取加密相关的信息，在CoreDelegate的Cpp中有提供",[771,1173,1176],{"className":1174,"code":1175,"language":776},[774],"void RegisterEncryptionKey(const char* InEncryptionKey)\n{\n  FCoreDelegates::GetPakEncryptionKeyDelegate().BindLambda([InEncryptionKey]() { return InEncryptionKey; });\n}\n\nvoid RegisterPakSigningKeys(const char* InExponent, const char* InModulus)\n{\n  static FString Exponent(ANSI_TO_TCHAR(InExponent));\n  static FString Modulus(ANSI_TO_TCHAR(InModulus));\n\nFCoreDelegates::GetPakSigningKeysDelegate().BindLambda([](FString& OutExponent, FString& OutModulus)\n{\n  OutExponent = Exponent;\n  OutModulus = Modulus;\n});\n}\n",[778,1177,1175],{"__ignoreMap":634},[11,1179,1180],{},"如果没有办法成功注册解密的话，可以尝试模仿引擎代码进行注册来调试一下。",[11,1182,1183],{},"不过事实上这些功能如果只是打包的话基本不用深究，在使用Project Launcer进行打包时，可以看到编辑器对UnrealPak的调用，将其复制出来，然后进行相应的修改就可以了。",[11,1185,1186],{},"在进行测试的时候需要注意，UE4对于Asset的加载是有要求的，如果是Shiping烘焙的内容，是无法在编辑器中加载的，必须统一内容的打包格式。",[38,1188,1190],{"id":1189},"mount","Mount",[11,1192,1193,1194,1198],{},"Pak在运行时通过Mount将内容读取到游戏中，如果不希望Pak文件被自动加载的话，可以不将其放到[",[21,1195,1197],{"href":1196},"\u002F2017-05-01-ue4-patch-release-dlc\u002F#Maps","自动加载目录","]中。",[11,1200,1201],{},"手动加载Pak文件时，通常采用的Mount方法有两种。",[91,1203,1205],{"id":1204},"手动mount","手动Mount",[771,1207,1210],{"className":1208,"code":1209,"language":776},[774],"EL_LOG(TEXT(\"[AShipIssueGameModeBase] LoadPakMount(): %s\"), *PakPath);\nFString SaveContentDir = PakPath;\nUE_LOG(LogTemp, Log, TEXT(\"[AShipIssueGameModeBase]  ----------- LoadPak: %s --------------\"), *SaveContentDir);\n\n\u002F\u002F获取当前使用的平台,这里使用的是WIN64平台\nIPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();\n\u002F\u002F初始化PakPlatformFile\nFPakPlatformFile* PakPlatformFile = new FPakPlatformFile();\nPakPlatformFile->Initialize(&PlatformFile, TEXT(\"\"));\nFPlatformFileManager::Get().SetPlatformFile(*PakPlatformFile);\n\n\u002F\u002F获取Pak文件\nFPakFile PakFile(&PlatformFile, *SaveContentDir, false);\n\n\u002F\u002F设置pak文件的Mount点.\nFString MountPoint(\"..\u002F..\u002F..\u002F[ProjectName]\u002F\");\nUE_LOG(LogTemp, Log, TEXT(\"[AShipIssueGameModeBase] - Mount Path: %s\"), *MountPoint);\nEL_LOG(TEXT(\"[AShipIssueGameModeBase] Mount Path: %s\"), *MountPoint);\n\nPakFile.SetMountPoint(*MountPoint);\n\u002F\u002F对pak文件mount到前面设定的MountPoint\nif (PakPlatformFile->Mount(*SaveContentDir, 0, *MountPoint))\n{\n  UE_LOG(LogTemp, Log, TEXT(\"[AShipIssueGameModeBase] - Mount Success\"));\n  EL_LOG(TEXT(\"[AShipIssueGameModeBase] Mount Success\"));\n\n}\n",[778,1211,1209],{"__ignoreMap":634},[11,1213,1214],{},"这个方法可以手动指定MountPoint，但是通常情况下，除非很确定自己打包了什么，否则很容易丢失各个Asset之间的引用。",[91,1216,1218],{"id":1217},"coredelegate","CoreDelegate",[11,1220,1221],{},"这个是最近的版本添加的帮助函数，比上面的方法更加的简单实用。",[771,1223,1226],{"className":1224,"code":1225,"language":776},[774],"if (FCoreDelegates::OnMountPak.IsBound())\n{\n  if (FCoreDelegates::OnMountPak.Execute(PakPath, 4, nullptr))\n  {\n     EL_LOG(TEXT(\"[AShipIssueGameModeBase] LoadPakDelegate(): OnMountPak.Execute Successful.\"));\n     GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT(\"OnMountPak.Execute Successful.\"));\n  }\n  else\n  {\n    EL_LOG(TEXT(\"[AShipIssueGameModeBase] LoadPakDelegate(): OnMountPak.Execute Falied.\"));\n    GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT(\"OnMountPak.Execute Falied.\"));\n  }\n}\nelse\n{\n  EL_LOG(TEXT(\"[AShipIssueGameModeBase] LoadPakDelegate(): OnMountPak.IsBound() Falied\"));\n  GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT(\"OnMountPak.IsBound() Falied\"));\n}\n",[778,1227,1225],{"__ignoreMap":634},[11,1229,1230],{},"使用这个方法会将内容Mount到内容根目录，引擎内部加载Pak文件也是使用的这个方法。",[38,1232,1233],{"id":1233},"路径与引用",[11,1235,1236],{},"一般情况下使用同一个项目打包的Pak文件，只要打包正确，进行Mount都不会有什么问题。",[11,1238,1239],{},"而如果使用其他项目进行打包的话，可能会存在一些引用丢失的问题。通常的表现是，使用Mount后的路径可以打开地图，但是地图内物品的材质等关联属性全部都丢失了。",[11,1241,1242],{},"这个问题通常都是路径问题引起的，另外，在Cook目录中会有AssetRegistry，这个文件是UE用来保存各个Asset之间的引用的，在网上有看到Mount时要自己加载并注册的描述，但是目前版本测试下似乎是会自动加载的。至少在没有主动加载AssetRegistry的情况下，引用已经是正常的了，但是如果出现了引用丢失的问题，可以从这个方面进行一下检查。而且使用CoreDelegate进行Mount的话，是不需要关心这个的。",[11,1244,1245,1246,1249],{},"在打包时主要的路径问题来自于打包时的处理机制，UnrealPak会尝试从所有待打包的文件中提取公共的目录作为根目录。而这个根目录之后会被挂在MountPoint之下，那么如果配置有不正确的话，必然会丢失引用。如果引用丢失了的话，对打包的根目录检查也是必要的。在使用",[778,1247,1248],{},"-create=\u003CResponsFile>","进行打包时，可以在每个文件列的后面指定打包的目标目录，可以一定程度的上解决这个问题。",[91,1251,1252],{"id":1252},"实际使用",[11,1254,1255],{},"注意：这里记录的是目前项目中使用的路径，并不代表这是一个“好”的方法。因为UnrealPak自主打包无论如何都不会优雅，如果没有紧急的打包需求，建议等官方的Chunk机制等一系列打包功能完善之后直接使用~",[11,1257,1258],{},"首先，对目标项目ShipIssue进行打包，从Log中拷贝UnrealPak的记录：",[771,1260,1263],{"className":1261,"code":1262,"language":776},[774],"D:\\Code\\UE_4.16\\Engine\\Binaries\\Win64\\UnrealPak.exe F:\\Ue_Patch\\1.0_nomap\\WindowsNoEditor\\ShipIssue\\Content\\Paks\\ShipIssue-WindowsNoEditor.pak -create=\"C:\\Users\\Administrator\\AppData\\Roaming\\Unreal Engine\\AutomationTool\\Logs\\D+Code+UE_4.16\\PakList_ShipIssue-WindowsNoEditor.txt\" -encryptionini -enginedir=\"D:\\Code\\UE_4.16\\Engine\" -projectdir=\"E:\\GameDev\\ShipIssue\" -platform=Windows -abslog=\"C:\\Users\\Administrator\\AppData\\Roaming\\Unreal Engine\\AutomationTool\\Logs\\D+Code+UE_4.16\\PakLog_ShipIssue-WindowsNoEditor.log\" -installed -order=E:\\GameDev\\ShipIssue\\Build\\WindowsNoEditor\\FileOpenOrder\\CookerOpenOrder.log -UTF8Output -multiprocess -patchpaddingalign=2048\n",[778,1264,1262],{"__ignoreMap":634},[11,1266,1267],{},"然后，将要打包的项目FreePatch进行Cook之后，对拷贝的命令修改：",[771,1269,1272],{"className":1270,"code":1271,"language":776},[774],"D:\\Code\\UE_4.16\\Engine\\Binaries\\Win64\\UnrealPak.exe F:\\Ue_Patch\\1.0_nomap\\WindowsNoEditor\\ShipIssue\\Content\\Paks\\Free.pak -create=\"E:\\PakScript\\list.txt\" -encryptionini -enginedir=\"D:\\Code\\UE_4.16\\Engine\" -projectdir=\"E:\\GameDev\\ShipIssue\" -platform=Windows -abslog=\"C:\\Users\\Administrator\\AppData\\Roaming\\Unreal Engine\\AutomationTool\\Logs\\D+Code+UE_4.16\\PakLog_ShipIssue-WindowsNoEditor.log\" -installed -order=E:\\GameDev\\FreePatch\\Build\\WindowsNoEditor\\FileOpenOrder\\CookerOpenOrder.log -UTF8Output -multiprocess -patchpaddingalign=2048\n",[778,1273,1271],{"__ignoreMap":634},[11,1275,1276],{},"Order指令的文件可以直接使用Cook出来的，list.txt直接写：",[771,1278,1281],{"className":1279,"code":1280,"language":776},[774],"\"E:\\GameDev\\FreePatch\\Saved\\Cooked\\WindowsNoEditor\\FreePatch\\*\" \"..\u002F..\u002F..\u002FShipIssue\u002F*\" –compress\n",[778,1282,1280],{"__ignoreMap":634},[11,1284,1285],{},"目标目录写成目标项目的目录，这样的话打包出来的pak文件在自动加载和使用CoreDelegate加载后都能保持正确的引用。",[11,1287,1288],{},"如果保留原项目名",[771,1290,1293],{"className":1291,"code":1292,"language":776},[774],"\"E:\\GameDev\\FreePatch\\Saved\\Cooked\\WindowsNoEditor\\FreePatch\\*\" \"..\u002F..\u002F..\u002FFreePatch\u002F*\" –compress\n",[778,1294,1292],{"__ignoreMap":634},[11,1296,1297],{},"需要Mount时自己将MountPoint指定到..\u002F..\u002F..\u002FShipIssue\u002F，自动加载和CoreDelegate加载会加载到..\u002F..\u002F..\u002FFreePatch\u002F导致无法读取到地图中物体引用的材质。",[11,1299,1300],{},"因此并不推荐这样做~",[11,1302,1303],{},"通常情况下，只要mount之后的路径在\u002FShipIssue\u002F下的结构与原本在Content\u002F下的结构相同，材质之类的引用就不会丢失。丢失的情况，一般从路径方面检查下先比较好。",[11,1305,1306],{},"更新：由于Pak的加载确实有些迷，经常会有朋友发邮件或者留言来问我。但是从打包到加载的过程中的可变量太多，我也无法准确的知道每个人遇到的问题。所以在这里将测试打包用到的代码分离成一个插件。",[11,1308,1309,1310,33],{},"其实插件本身并不复杂，只是在使用UFS的API遍历访问而已。主要的作用是可以方便对加载过程的理解和问题的排查。详情请参考[",[21,1311,1313],{"href":1312},"\u002Fpage\u002Fue4-pak-file-view\u002F","UE4文件查看插件",[1315,1316,1317],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}",{"title":634,"searchDepth":635,"depth":636,"links":1319},[1320,1334,1338],{"id":848,"depth":635,"text":849,"children":1321},[1322,1323,1324,1325,1326,1327,1328,1329,1333],{"id":992,"depth":636,"text":993},{"id":1009,"depth":636,"text":1010},{"id":1019,"depth":636,"text":1020},{"id":1026,"depth":636,"text":1027},{"id":1036,"depth":636,"text":1037},{"id":1048,"depth":636,"text":1049},{"id":1061,"depth":636,"text":1062},{"id":1070,"depth":636,"text":1070,"children":1330},[1331,1332],{"id":1091,"depth":644,"text":1091},{"id":1106,"depth":644,"text":1106},{"id":1112,"depth":636,"text":1112},{"id":1189,"depth":635,"text":1190,"children":1335},[1336,1337],{"id":1204,"depth":636,"text":1205},{"id":1217,"depth":636,"text":1218},{"id":1233,"depth":635,"text":1233,"children":1339},[1340],{"id":1252,"depth":636,"text":1252},{"layout":684,"status":685,"published":686,"author":1342,"author_login":689,"author_email":690,"wordpress_id":1343,"wordpress_url":1344,"date_gmt":1345,"excerpt":1346},{"display_name":688,"login":689,"email":690,"url":634},1790,"\u002F\u002F?p=1790","2017-06-24 11:37:17 +0000",{"type":8,"value":1347},[1348],[11,1349,838],{},"\u002F2017-06-24-unrealpak-note",{"title":833,"description":838},"_legacy\u002F2017\u002F2017-06-24-unrealpak-note",[702,849,1354],"Pak","Jr-XOewnXJ3-UJQa19DQu02xnktkiNhN_su7wcAU86Y",{"id":1357,"title":1358,"body":1359,"date":1656,"description":1363,"extension":682,"meta":1657,"navigation":686,"path":1666,"seo":1667,"stem":1668,"tags":1669,"__hash__":1671},"blogs\u002F_legacy\u002F2017\u002F2017-06-06-ue4-config-usage.md","UE4中Config的使用",{"type":8,"value":1360,"toc":1649},[1361,1364,1367,1370,1373,1376,1411,1414,1455,1458,1465,1468,1471,1477,1480,1486,1489,1495,1498,1501,1504,1510,1521,1524,1527,1533,1536,1539,1559,1566,1569,1572,1575,1578,1581,1584,1587,1593,1596,1599,1605,1608,1614,1617,1620,1623,1629,1632,1634,1637,1640],[11,1362,1363],{},"UE4中本身有一套非常成熟的INI文件配置机制，对于简单的属性配置，可以直接进行借用。",[11,1365,1366],{},"当前使用的UE4版本为4.15.2。",[38,1368,1369],{"id":1369},"引擎配置",[11,1371,1372],{},"整个Config机制在引擎中最常见的地方就是引擎的配置本身了，项目配置与引擎配置，Localize的配置……这些都是通过这个机制进行的。",[11,1374,1375],{},"引擎自带的配置分类有以下这些：",[225,1377,1378,1381,1384,1387,1390,1393,1396,1399,1402,1405,1408],{},[11,1379,1380],{},"Compat（兼容性）",[11,1382,1383],{},"DeviceProfiles（设备概述文件）",[11,1385,1386],{},"Editor（编辑器）",[11,1388,1389],{},"EditorGameAgnostic（编辑器游戏不可知论）",[11,1391,1392],{},"EditorKeyBindings（编辑器按键绑定）",[11,1394,1395],{},"EditorUserSettings（编辑器用户设置）",[11,1397,1398],{},"Engine(引擎)",[11,1400,1401],{},"Game(游戏)",[11,1403,1404],{},"Input（输入）",[11,1406,1407],{},"Lightmass",[11,1409,1410],{},"Scalability（可扩展性）",[11,1412,1413],{},"配置文件是有加载优先的，按照官方文档提供的顺序，越靠后的值会覆盖前面的值。",[225,1415,1416,1419,1422,1432,1438,1447],{},[11,1417,1418],{},"Engine\u002FConfig\u002FBase.ini",[11,1420,1421],{},"Engine\u002FConfig\u002FBaseEngine.ini",[11,1423,1424,1425,1428,1429,1431],{},"Engine\u002FConfig\u002F",[172,1426,1427],{},"Platform","\u002F",[172,1430,1427],{},"Engine.ini",[11,1433,1434,1437],{},[172,1435,1436],{},"ProjectDirectory","\u002FConfig\u002FDefaultEngine.ini",[11,1439,1440,1442,1443,1428,1445,1431],{},[172,1441,1436],{},"\u002FConfig\u002F",[172,1444,1427],{},[172,1446,1427],{},[11,1448,1449,1451,1452,1454],{},[172,1450,1436],{},"\u002FSaved\u002FConfig\u002F",[172,1453,1427],{},"\u002FEngine.ini",[11,1456,1457],{},"这个加载次序是以Engine配置为基础的。",[11,1459,1460,1461,33],{},"关于这部分的详细可以参照[",[21,1462,76],{"href":1463,"rel":1464},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FProgramming\u002FBasics\u002FConfigurationFiles\u002Findex.html",[25],[38,1466,1467],{"id":1467},"自主调用",[11,1469,1470],{},"使用GConfig就可以对这个配置系统中的值进行查询和写入，要对配置进行读取的话可以使用：",[771,1472,1475],{"className":1473,"code":1474,"language":776},[774],"if(!GConfig) return;\n\nFString ValueReceived;\n  GConfig->GetString(\n  TEXT(\"\u002FScript\u002FEngine.WorldInfo\"),\n  TEXT(\"GlobalDefaultGameType\"),\n  ValueReceived,\n  GGameIni\n);\n",[778,1476,1474],{"__ignoreMap":634},[11,1478,1479],{},"而写入的话，可以使用：",[771,1481,1484],{"className":1482,"code":1483,"language":776},[774],"if (!GConfig) return;\nGConfig->SetInt(\n  TEXT(\"sectionname\"),\n  TEXT(\"key\"),\n  100,\n  GGameUserSettingsIni\n  );\nGConfig->Flush(false, GGameUserSettingsIni);\n",[778,1485,1483],{"__ignoreMap":634},[11,1487,1488],{},"这里使用的Get\u002FSet是一系列函数，对应引擎内部的不同值，第四个参数是配置的名称，与上面给出的配置分类是对应的，应该，没有仔细的考证过。可以看到GGameUserSettingsIni附近有很多这样的定义：",[771,1490,1493],{"className":1491,"code":1492,"language":776},[774],"FString                GEngineIni;                                                    \u002F* Engine ini filename *\u002F\n\n\u002F** Editor ini file locations - stored per engine version (shared across all projects). Migrated between versions on first run. *\u002F\nFString                GEditorIni;                                                    \u002F* Editor ini filename *\u002F\nFString                GEditorKeyBindingsIni;                                        \u002F* Editor Key Bindings ini file *\u002F\nFString                GEditorLayoutIni;                                            \u002F* Editor UI Layout ini filename *\u002F\nFString                GEditorSettingsIni;                                            \u002F* Editor Settings ini filename *\u002F\n\n\u002F** Editor per-project ini files - stored per project. *\u002F\nFString                GEditorPerProjectIni;                                        \u002F* Editor User Settings ini filename *\u002F\n\nFString                GCompatIni;\nFString                GLightmassIni;                                                \u002F* Lightmass settings ini filename *\u002F\nFString                GScalabilityIni;                                            \u002F* Scalability settings ini filename *\u002F\nFString                GHardwareIni;                                                \u002F* Hardware ini filename *\u002F\nFString                GInputIni;                                                    \u002F* Input ini filename *\u002F\nFString                GGameIni;                                                    \u002F* Game ini filename *\u002F\nFString                GGameUserSettingsIni;                                        \u002F* User Game Settings ini filename *\u002F\n",[778,1494,1492],{"__ignoreMap":634},[11,1496,1497],{},"其实这种用法，使用起来相对有些麻烦，而且会把自己的配置和引擎的配置混在一起，其实还有更加简单的使用这个配置的方法：",[38,1499,1500],{"id":1500},"扩展使用",[11,1502,1503],{},"这个特性是可以扩展给自己定义的类使用的，在UClass定义时制定Config即可，像是这样的感觉：",[771,1505,1508],{"className":1506,"code":1507,"language":776},[774],"UCLASS(config=GameUserSettings, configdonotcheckdefaults)\nclass ENGINE_API UGameUserSettings : public UObject\n",[778,1509,1507],{"__ignoreMap":634},[11,1511,1512,1513,1516,1517,1520],{},"其实上面那些自带的引擎配置就是在引擎中预先定义好的",[172,1514,1515],{},"名称","-",[172,1518,1519],{},"配置文件名","对应组。",[11,1522,1523],{},"这里面在定义时，config=custom，这样自己定义配置文件名称也是可以的。如果不想自己的配置和引擎的配置混在一起变得乱七八糟的话，这是一个很好的选择。",[11,1525,1526],{},"这样的好处是所有标记为UProperty的属性加上config标记都会被自动载入，在执行SaveConfig后就可以直接保存，不需要和配置的逻辑打交道。定义时就像这样的感觉：",[771,1528,1531],{"className":1529,"code":1530,"language":776},[774],"UPROPERTY(config, EditAnywhere, Category=\"MouseProperties”)\nuint32 bEnableFOVScaling:1;\n",[778,1532,1530],{"__ignoreMap":634},[11,1534,1535],{},"在文件名的分配上，如果使用custom的话，配置文件就会被保存到custom.ini的命名规则下。如果一时之间找不到配置文件，可以使用这个文件名进行搜索。",[11,1537,1538],{},"这一系列的配置还有其他几个选项：",[225,1540,1541,1544,1547,1550,1553,1556],{},[11,1542,1543],{},"perObjectConfig",[11,1545,1546],{},"使得配置变为针对每个Object的实例，而不是每个Class存一个配置块。",[11,1548,1549],{},"configdonotcheckdefaults",[11,1551,1552],{},"配置了这个的话，config在读取的时候就不会去读取default配置。",[11,1554,1555],{},"defaultconfig",[11,1557,1558],{},"配置只保存在default中",[11,1560,1561,1562,1565],{},"这里有一个概念就是Default，Default配置的读取是比较靠前的，通常保存在项目目录Config\u002F",[172,1563,1564],{},"Custom","Default.ini中。这里有一个在测试时需要注意的，如果SaveConfig的值与Default值相同的话，那么就会不进行保存，该行配置会消失，因为与默认值一样了。",[11,1567,1568],{},"在进行配置文件操作和设定时，可以使用UpdateDefaultConfigFile()来写入到default值，而通常的配置可以使用SaveConfig()进行保存，因为SaveConfig是会自动进行Flush的，而另一个UpdateGlobal ConfigFile则局限性较强，但它可以确保写入路径是指向用户配置的。",[11,1570,1571],{},"要注意的是，如果在Shipping打包模式下，那么可以让用户和我们自己进行配置的ini文件将不会出现在项目目录中个，而是在\"用户目录\\AppData\\Local\\rg_unreal\\Saved\\Config\\WindowsNoEditor[custom].ini\"中。",[11,1573,1574],{},"如果觉得这样有些Low的话，我们就需要绕回去：",[38,1576,1577],{"id":1577},"自定义路径",[11,1579,1580],{},"仔细查看Config系统的源码，我们可以看到，ini的加载是在UObject中进行指定的。如果没有定义PerObjectConfig的话，配置是在CDO阶段就完成了的。如果定义了PerObject的话则会在UObject:: PostLoad中进行配置加载。",[11,1582,1583],{},"但是系统本身的GetConfigName是无法被重载的，好在引擎有暴露其他的接口，我们要对其使用只要自己在BeginPlay之类的事件中自己进行读写就可以了。",[11,1585,1586],{},"其实要使用的函数在上面已经用到了，只是在调用的时候使用的是默认参数罢了。",[771,1588,1591],{"className":1589,"code":1590,"language":776},[774],"SaveConfig(CPF_Config, *ConfPath);\n",[778,1592,1590],{"__ignoreMap":634},[11,1594,1595],{},"这样调用的话就可以将当前类中的配置保存到指定的路径去了，不用指定后缀名Ini也是可以的。",[11,1597,1598],{},"不过，出于保险起见，最好是在Save之后，刷新一下缓存",[771,1600,1603],{"className":1601,"code":1602,"language":776},[774],"GConfig->Flush(false, ConfPath)\n",[778,1604,1602],{"__ignoreMap":634},[11,1606,1607],{},"要读取的话也是同理，先刷新缓存",[771,1609,1612],{"className":1610,"code":1611,"language":776},[774],"GConfig->Flush(true, ConfPath)\n",[778,1613,1611],{"__ignoreMap":634},[11,1615,1616],{},"其实如果配置的读写都是在引擎内完成的话，是没必要在读取的时候刷缓存的。",[11,1618,1619],{},"但如果有在引擎外部修改配置文件，并希望配置“实时”的反映到读取中的话，就需要在读取前刷新一下缓存。",[11,1621,1622],{},"读取的话使用",[771,1624,1627],{"className":1625,"code":1626,"language":776},[774],"ReloadConfig(NULL, *ConfPath)\n",[778,1628,1626],{"__ignoreMap":634},[11,1630,1631],{},"就可以了，这里使用到的ConfPath都是FString。",[38,1633,626],{"id":626},[11,1635,1636],{},"基本上配置存取使用官方的配置类就可以了，这里有一点很强大的是，似乎所有可以UPROPERTY化的引擎定义变量都可以写入到Config中，已经测试过TArray和TMap可以正常的进行读写。",[11,1638,1639],{},"但是像是UObject这类的并没有进行测试……不过引擎的配置中有大量的使用UAsset的引用，应当是毫无问题的。",[11,1641,1642,1643,1648],{},"不过，配置文件虽然有读写功能，但是它的定位本身并不适合用来存档。如果有这方面的需求请使用[",[21,1644,1647],{"href":1645,"rel":1646},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FGameplay\u002FSaveGame\u002Findex.html",[25],"SaveGame类","]，蓝图与C++皆可使用。",{"title":634,"searchDepth":635,"depth":636,"links":1650},[1651,1652,1653,1654,1655],{"id":1369,"depth":635,"text":1369},{"id":1467,"depth":635,"text":1467},{"id":1500,"depth":635,"text":1500},{"id":1577,"depth":635,"text":1577},{"id":626,"depth":635,"text":626},"2017-06-06",{"layout":684,"status":685,"published":686,"author":1658,"author_login":689,"author_email":690,"wordpress_id":1659,"wordpress_url":1660,"date_gmt":1661,"excerpt":1662},{"display_name":688,"login":689,"email":690,"url":634},1777,"\u002F\u002F?p=1777","2017-06-06 12:12:22 +0000",{"type":8,"value":1663},[1664],[11,1665,1363],{},"\u002F2017-06-06-ue4-config-usage",{"title":1358,"description":1363},"_legacy\u002F2017\u002F2017-06-06-ue4-config-usage",[702,1670],"Config","5PE0D-8QY1nKGtyz-G-TzQvrRF7I9D4LT4XxU3vOAks",{"id":1673,"title":1674,"body":1675,"date":1891,"description":1679,"extension":682,"meta":1892,"navigation":686,"path":1901,"seo":1902,"stem":1903,"tags":1904,"__hash__":1905},"blogs\u002F_legacy\u002F2017\u002F2017-06-05-ue4-transition-log.md","UE4项目迁移笔记",{"type":8,"value":1676,"toc":1886},[1677,1680,1683,1686,1690,1693,1699,1702,1705,1711,1714,1720,1723,1726,1730,1733,1736,1739,1745,1748,1754,1757,1760,1763,1769,1772,1778,1781,1787,1790,1796,1799,1802,1806,1809,1812,1818,1821,1824,1833,1836,1841,1844,1847,1853,1856,1859,1862,1868,1871,1877,1880,1883],[11,1678,1679],{},"UE4本身的版本升级比较频繁，这就会导致有时候手里的或是网上下载的项目需要进行版本迁移。",[11,1681,1682],{},"通常情况下，如果是在小的版本之间迁移，尤其是跟着官方的步伐进行更新的话，都不会遇到什么大问题。但是如果在现在版本是4.16的情况下，要对4.7版本的项目进行迁移的话，就会有很多问题。",[11,1684,1685],{},"这次遇到的问题就是从网上下载的示例项目版本太旧，需要进行大的版本迁移。原本以为不会有什么太多麻烦，不想却还是花了很多时间，因此在这里记录一下。",[38,1687,1689],{"id":1688},"build-rules","Build Rules",[11,1691,1692],{},"对项目进行编译，非常幸运，首先只遇到了几个Warning：",[771,1694,1697],{"className":1695,"code":1696,"language":776},[774],"1>E:\\Ues\\GAS\\Source\\GAS\\GAS.Build.cs : warning : Module constructors should take a ReadOnlyTargetRules argument (rather than a TargetInfo argument) and pass it to the base class constructor from 4.15 onwards. Please update the method signature.\n1>E:\\Ues\\GAS\\Source\\GAS.Target.cs : warning : SetupBinaries() is deprecated in the 4.16 release. From the constructor in your .target.cs file, use ExtraModuleNames.Add(\"Foo\") to add modules to your target, or set LaunchModuleName = \"Foo\" to override the name of the launch module for program targets.\n1>E:\\Ues\\GAS\\Source\\GASEditor.Target.cs : warning : SetupBinaries() is deprecated in the 4.16 release. From the constructor in your .target.cs file, use ExtraModuleNames.Add(\"Foo\") to add modules to your target, or set LaunchModuleName = \"Foo\" to override the name of the launch module for program targets.\n",[778,1698,1696],{"__ignoreMap":634},[11,1700,1701],{},"UE4最近的两次版本升级中对BuildRule进行了变更，所以在这里只要按照提示进行修改就可以了。只是官方的提示多少有些简略，实际操作是类似这样的。",[11,1703,1704],{},"*.build.cs",[771,1706,1709],{"className":1707,"code":1708,"language":776},[774],"public class GAS : ModuleRules\n{\n-    public GAS(TargetInfo Target)\n+    public GAS(ReadOnlyTargetRules Target) : base(Target)\n",[778,1710,1708],{"__ignoreMap":634},[11,1712,1713],{},"*.Target.cs",[771,1715,1718],{"className":1716,"code":1717,"language":776},[774],"public class GASTarget : TargetRules\n{\n-    public GASTarget(TargetInfo Target)\n+    public    GASTarget(TargetInfo Target) : base(Target)\n     {\n        Type = TargetType.Game;\n-    }\n-\n-    \u002F\u002F\n-    \u002F\u002F TargetRules interface.\n-    \u002F\u002F\n-\n-    public override void SetupBinaries(\n-          TargetInfo Target,\n-          ref List\u003CUEBuildBinaryConfiguration> OutBuildBinaryConfigurations,\n-          ref List\u003Cstring> OutExtraModuleNames\n-          )\n-    {\n-          OutExtraModuleNames.Add(\"GAS\");\n+          ExtraModuleNames.Add(\"GAS\");\n",[778,1719,1717],{"__ignoreMap":634},[11,1721,1722],{},"要留意的是，TargetRule有两个配置文件，一个是给Game的一个是给Editor的。",[11,1724,1725],{},"改了之后就没有提示了。",[38,1727,1729],{"id":1728},"iwyu","IWYU",[11,1731,1732],{},"既然升级了BuildRules，那就自然会要使用官方在4.15新推出的号称减少50%编译时间的IWYU了。",[11,1734,1735],{},"这个技术似乎是优化了预编译头的处理机制，但是使用起来需要自己进行定义和设置，并不是升到4.15以上就能全面享用的优势呢。",[11,1737,1738],{},"首先要做的是到*.build.cs中，在ModuleRules中添加一行，类似这样：",[771,1740,1743],{"className":1741,"code":1742,"language":776},[774],"\u002F\u002F Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.\n\nusing UnrealBuildTool;\n\npublic class GAS : ModuleRules\n{\n    public GAS(ReadOnlyTargetRules Target) : base(Target)\n    {\n        PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;\n\n       \u002F\u002F Some other code def\n\n    }\n}\n",[778,1744,1742],{"__ignoreMap":634},[11,1746,1747],{},"添加这行修改预编译头机制后，编译必然会遇到很多类似这样的错误：",[771,1749,1752],{"className":1750,"code":1751,"language":776},[774],"1>E:\\Ues\\GAS\\Source\\GAS\\DamageExecution.cpp(1): error : Expected DamageExecution.h to be first header included.\n1>E:\\Ues\\GAS\\Source\\GAS\\GASAttributeSet.cpp(1): error : Expected GASAttributeSet.h to be first header included.\n1>E:\\Ues\\GAS\\Source\\GAS\\GASBlueprintLibrary.cpp(1): error : Expected GASBlueprintLibrary.h to be first header included.\n",[778,1753,1751],{"__ignoreMap":634},[11,1755,1756],{},"新的PCH机制要求cpp文件必须首先包含自己的头文件，按照提示一个一个对源文件进行修改就好了。",[11,1758,1759],{},"此外还会遇到很多类型未定义以及由此引发的其他错误，如果有VA的话，可以对应着进行添加包含。要注意的是，连FString这样的基础类型都会有这个问题，所以还是颇为防不胜防。",[11,1761,1762],{},"官方推荐的是在项目的头文件中包含",[771,1764,1767],{"className":1765,"code":1766,"language":776},[774],"#include “CoreMinimal.h”\n",[778,1768,1766],{"__ignoreMap":634},[11,1770,1771],{},"当然必要的时候可能需要包含",[771,1773,1776],{"className":1774,"code":1775,"language":776},[774],"#include “EngineMinimal.h”\n",[778,1777,1775],{"__ignoreMap":634},[11,1779,1780],{},"如果项目中使用到了UEngine的话，可以选择包含",[771,1782,1785],{"className":1783,"code":1784,"language":776},[774],"#include “Engine\u002FEngine.h”\n",[778,1786,1784],{"__ignoreMap":634},[11,1788,1789],{},"但是，实际上，如果对引擎比较深的地方的类进行了继承的话，最后还是得包含",[771,1791,1794],{"className":1792,"code":1793,"language":776},[774],"#include \"Engine.h\"\n",[778,1795,1793],{"__ignoreMap":634},[11,1797,1798],{},"要不然可能会被报错到引擎的源代码里面去。",[11,1800,1801],{},"进行包含调整之后，基本就编译通过了。",[38,1803,1805],{"id":1804},"load-module-error","Load Module Error",[11,1807,1808],{},"原本以为编译通过了就万事大吉了，没想到在运行项目时却遇到了无法打开的问题……",[11,1810,1811],{},"使用VS进行调试运行，会看到这样的错误：",[771,1813,1816],{"className":1814,"code":1815,"language":776},[774],"[2017.06.04-02.48.31:217][ 0]LogModuleManager:Warning: ModuleManager: Unable to load module 'E:\u002FUes\u002FGAS\u002FBinaries\u002FWin64\u002FUE4Editor-GAS-Win64-DebugGame.dll' because the file couldn't be loaded by the OS.\n",[778,1817,1815],{"__ignoreMap":634},[11,1819,1820],{},"首先当然是怀疑dll文件不存在，但是实际上dll文件是存在的，那么问题出在哪里了呢？",[11,1822,1823],{},"求助万能的Google，终于找到了调试的方法。",[11,1825,1826,1827,1832],{},"这个时候需要使用微软官方的Debugging Tools for Windows，按照微软官方描述该工具被包含在WDK和Windows SDK中，因此直接",[21,1828,1831],{"href":1829,"rel":1830},"http:\u002F\u002Fgo.microsoft.com\u002Ffwlink\u002Fp?LinkID=271979",[25],"install the Windows SDK","，在安装的时候只勾选Debugging Tools for Windows就好了。",[11,1834,1835],{},"安装完成之后打开其中的一个名为Global Flags的工具，进行配置：",[11,1837,1838],{},[48,1839],{"alt":211,"src":1840},"\u002Fwp-content\u002Fuploads\u002F2017\u002F06\u002Fclip_image001_thumb-1.png",[11,1842,1843],{},"只要选择Show Loader Snaps就好了，保存选项，重启电脑。",[11,1845,1846],{},"然后重新在VS中调试运行项目，在上一次遇到错误的地方就可以看到：",[771,1848,1851],{"className":1849,"code":1850,"language":776},[774],"2264:05b0 @ 00793859 - LdrpSearchPath - ENTER: DLL name: UE4Editor-GameplayAbilities.dll\n2264:05b0 @ 00793859 - LdrpComputeLazyDllPath - INFO: DLL search path computed: D:\\Code\\UE_4.16\\Engine\\Binaries\\Win64;E:\u002FUes\u002FGAS\u002FBinaries\u002FWin64;C:\\Windows\\SYSTEM32;C:\\Windows\\system;C:\\Windows;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;D:\\Tools\\Git\\cmd;C:\\Program Files (x86)\\Windows Live\\Shared;D:\\Tools\\SVN\\bin;D:\\Code\\Python\\Scripts\\;D:\\Code\\Python\\;C:\\Users\\Administrator\\AppData\\Local\\Microsoft\\WindowsApps;;D:\\Tools\\Microsoft VS Code\\bin\n2264:05b0 @ 00793859 - LdrpResolveDllName - ENTER: DLL name: D:\\Code\\UE_4.16\\Engine\\Binaries\\Win64\\UE4Editor-GameplayAbilities.dll\n2264:05b0 @ 00793859 - LdrpResolveDllName - RETURN: Status: 0xc0000135\n2264:05b0 @ 00793859 - LdrpResolveDllName - ENTER: DLL name: E:\u002FUes\u002FGAS\u002FBinaries\u002FWin64\\UE4Editor-GameplayAbilities.dll\n2264:05b0 @ 00793859 - LdrpResolveDllName - RETURN: Status: 0xc0000135\n2264:05b0 @ 00793859 - LdrpResolveDllName - ENTER: DLL name: C:\\Windows\\SYSTEM32\\UE4Editor-GameplayAbilities.dll\n2264:05b0 @ 00793859 - LdrpResolveDllName - RETURN: Status: 0xc0000135\n2264:05b0 @ 00793859 - LdrpResolveDllName - ENTER: DLL name: C:\\Windows\\system\\UE4Editor-GameplayAbilities.dll\n2264:05b0 @ 00793859 - LdrpResolveDllName - RETURN: Status: 0xc0000135\n2264:05b0 @ 00793859 - LdrpResolveDllName - ENTER: DLL name: C:\\Windows\\UE4Editor-GameplayAbilities.dll\n2264:05b0 @ 00793875 - LdrpResolveDllName - RETURN: Status: 0xc0000135\n2264:05b0 @ 00793875 - LdrpResolveDllName - ENTER: DLL name: C:\\Windows\\system32\\UE4Editor-GameplayAbilities.dll\n2264:05b0 @ 00793875 - LdrpResolveDllName - RETURN: Status: 0xc0000135\n2264:05b0 @ 00793875 - LdrpResolveDllName - ENTER: DLL name: C:\\Windows\\UE4Editor-GameplayAbilities.dll\n2264:05b0 @ 00793875 - LdrpResolveDllName - RETURN: Status: 0xc0000135\n2264:05b0 @ 00793875 - LdrpResolveDllName - ENTER: DLL name: C:\\Windows\\System32\\Wbem\\UE4Editor-GameplayAbilities.dll\n2264:05b0 @ 00793875 - LdrpResolveDllName - RETURN: Status: 0xc0000135\n2264:05b0 @ 00793875 - LdrpResolveDllName - ENTER: DLL name: C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\UE4Editor-GameplayAbilities.dll\n2264:05b0 @ 00793875 - LdrpResolveDllName - RETURN: Status: 0xc0000135\n2264:05b0 @ 00793875 - LdrpResolveDllName - ENTER: DLL name: D:\\Tools\\Git\\cmd\\UE4Editor-GameplayAbilities.dll\n2264:05b0 @ 00793875 - LdrpResolveDllName - RETURN: Status: 0xc0000135\n2264:05b0 @ 00793875 - LdrpResolveDllName - ENTER: DLL name: C:\\Program Files (x86)\\Windows Live\\Shared\\UE4Editor-GameplayAbilities.dll\n2264:05b0 @ 00793875 - LdrpResolveDllName - RETURN: Status: 0xc0000135\n2264:05b0 @ 00793875 - LdrpResolveDllName - ENTER: DLL name: D:\\Tools\\SVN\\bin\\UE4Editor-GameplayAbilities.dll\n2264:05b0 @ 00793875 - LdrpResolveDllName - RETURN: Status: 0xc0000135\n2264:05b0 @ 00793875 - LdrpResolveDllName - ENTER: DLL name: D:\\Code\\Python\\Scripts\\UE4Editor-GameplayAbilities.dll\n2264:05b0 @ 00793875 - LdrpResolveDllName - RETURN: Status: 0xc0000135\n2264:05b0 @ 00793875 - LdrpResolveDllName - ENTER: DLL name: D:\\Code\\Python\\UE4Editor-GameplayAbilities.dll\n2264:05b0 @ 00793875 - LdrpResolveDllName - RETURN: Status: 0xc0000135\n2264:05b0 @ 00793875 - LdrpResolveDllName - ENTER: DLL name: C:\\Users\\Administrator\\AppData\\Local\\Microsoft\\WindowsApps\\UE4Editor-GameplayAbilities.dll\n2264:05b0 @ 00793875 - LdrpResolveDllName - RETURN: Status: 0xc0000135\n2264:05b0 @ 00793875 - LdrpResolveDllName - ENTER: DLL name: D:\\Tools\\Microsoft VS Code\\bin\\UE4Editor-GameplayAbilities.dll\n2264:05b0 @ 00793875 - LdrpResolveDllName - RETURN: Status: 0xc0000135\n2264:05b0 @ 00793875 - LdrpSearchPath - RETURN: Status: 0xc0000135\n2264:05b0 @ 00793875 - LdrpProcessWork - ERROR: Unable to load DLL: \"UE4Editor-GameplayAbilities.dll\", Parent Module: \"E:\\Ues\\GAS\\Binaries\\Win64\\UE4Editor-GAS.dll\", Status: 0xc0000135\n“UE4Editor.exe”(Win32): 已卸载“E:\\Ues\\GAS\\Binaries\\Win64\\UE4Editor-GAS.dll”\n2264:05b0 @ 00793875 - LdrpLoadDllInternal - RETURN: Status: 0xc0000135\n2264:05b0 @ 00793875 - LdrLoadDll - RETURN: Status: 0xc0000135\n[2017.06.04-03.19.55:171][ 0]LogModuleManager:Warning: ModuleManager: Unable to load module 'E:\u002FUes\u002FGAS\u002FBinaries\u002FWin64\u002FUE4Editor-GAS.dll' because the file couldn't be loaded by the OS.\n",[778,1852,1850],{"__ignoreMap":634},[11,1854,1855],{},"错误原因原来是UE4Editor-GameplayAbilities.dll找不到了，但是这个是引擎提供的内容，为什么会找不到呢？",[11,1857,1858],{},"费了一阵脑筋之后才想起来，似乎从某个版本开始，GamePlayAbility由引擎的Module转变为了Plugin，目的是为了避免不需要这个系统的项目包含不必要的内容。",[11,1860,1861],{},"因此直接对项目文件进行编辑，将下面的内容：",[771,1863,1866],{"className":1864,"code":1865,"language":776},[774],"{\n    \"FileVersion\": 3,\n    \"EngineAssociation\": \"\",\n    \"Category\": \"\",\n    \"Description\": \"\",\n    \"Modules\": [\n    {\n        \"Name\": \"GAS\",\n        \"Type\": \"Runtime\",\n        \"LoadingPhase\": \"Default\",\n        \"AdditionalDependencies\": [\n            \"GameplayAbilities\",\n            \"Engine\"\n        ]\n    }\n    ]\n\n}\n",[778,1867,1865],{"__ignoreMap":634},[11,1869,1870],{},"改为：",[771,1872,1875],{"className":1873,"code":1874,"language":776},[774],"{\n    \"FileVersion\": 3,\n    \"EngineAssociation\": \"4.16\",\n    \"Category\": \"\",\n    \"Description\": \"\",\n    \"Modules\": [\n        {\n            \"Name\": \"GAS\",\n            \"Type\": \"Runtime\",\n            \"LoadingPhase\": \"Default\",\n            \"AdditionalDependencies\": [\n                \"Engine\"\n            ]\n        }\n    ],\n    \"Plugins\": [\n        {\n            \"Name\": \"GameplayAbilities\",\n            \"Enabled\": true\n        }\n    ]\n}\n",[778,1876,1874],{"__ignoreMap":634},[11,1878,1879],{},"重新编译，这样一来终于能够运行了。",[11,1881,1882],{},"不过Module的加载错误因项目而异，遇到这种错误还是必须使用GFlag进行调试才行。",[11,1884,1885],{},"最后，不要忘记在GFlag中把调试选项关掉，要不然调试运行速度会很感人的。",{"title":634,"searchDepth":635,"depth":636,"links":1887},[1888,1889,1890],{"id":1688,"depth":635,"text":1689},{"id":1728,"depth":635,"text":1729},{"id":1804,"depth":635,"text":1805},"2017-06-05",{"layout":684,"status":685,"published":686,"author":1893,"author_login":689,"author_email":690,"wordpress_id":1894,"wordpress_url":1895,"date_gmt":1896,"excerpt":1897},{"display_name":688,"login":689,"email":690,"url":634},1771,"\u002F\u002F?p=1771","2017-06-05 13:38:21 +0000",{"type":8,"value":1898},[1899],[11,1900,1679],{},"\u002F2017-06-05-ue4-transition-log",{"title":1674,"description":1679},"_legacy\u002F2017\u002F2017-06-05-ue4-transition-log",[702],"0iGe3VdYh10THWts3bUSuhKTRSuDSFHgXdb-IH4CPnk",222,1788763183517]