[{"data":1,"prerenderedAt":1703},["ShallowReactive",2],{"page-UE4-11":3,"page-count-UE4":1702},[4,824,1065,1305,1389],{"id":5,"title":6,"body":7,"date":801,"description":13,"extension":802,"meta":803,"navigation":338,"path":817,"seo":818,"stem":819,"tags":820,"__hash__":823},"blogs\u002F_legacy\u002F2017\u002F2017-03-25-ue4-time-mange.md","UE4时间相关函数",{"type":8,"value":9,"toc":789},"minimark",[10,14,17,20,25,28,33,36,66,69,72,75,188,191,194,197,205,208,236,239,281,285,288,552,555,562,565,569,572,578,584,587,591,594,600,611,619,623,626,632,639,642,681,689,711,714,736,740,743,749,752,755,758,761,767,773,776,782,785],[11,12,13],"p",{},"在进行游戏构建时，我们通常会用到和时间相关的逻辑，在此稍微做下小结。",[11,15,16],{},"当前UE4版本为4.15。",[11,18,19],{},"基本上游戏引擎都是以Tick为中心运转的，表现在玩家面前就是帧率了，UE4也是如此。",[21,22,24],"h2",{"id":23},"tick","Tick",[11,26,27],{},"tick是引擎的心跳，引擎中很多逻辑就是借助的tick机制自动注册的，例如UMG的动画播放功能就是tick驱动的。",[29,30,32],"h3",{"id":31},"c中使用","C++中使用",[11,34,35],{},"在蓝图中要使用tick的话只要从灰色的tick上拖出调用就可以了，但是在C++中使用的时候需要注意的是，在默认的情况下，tick是关闭的，必须自己开启才行。",[37,38,43],"pre",{"className":39,"code":40,"language":41,"meta":42,"style":42},"language-cpp shiki shiki-themes github-light-high-contrast github-dark monokai","PrimaryActorTick.bCanEverTick = true; \n","cpp","",[44,45,46],"code",{"__ignoreMap":42},[47,48,51,55,59,63],"span",{"class":49,"line":50},"line",1,[47,52,54],{"class":53},"s0idv","PrimaryActorTick.bCanEverTick ",[47,56,58],{"class":57},"sLXdl","=",[47,60,62],{"class":61},"stP0Q"," true",[47,64,65],{"class":53},";\n",[11,67,68],{},"另外由于tick本身是在每帧上调用的，出于效率考虑，要尽量避免往上添加复杂的逻辑，例如寻找1000以内的素数之类的。",[11,70,71],{},"还有一个比较少动到的地方是ShouldTickIfViewportsOnly，这个和编辑器的Tick策略关系比较大，但是在实际游戏中也可以使用。",[11,73,74],{},"主要的差分来自于LevelTick的类型",[37,76,78],{"className":39,"code":77,"language":41,"meta":42,"style":42},"\u002F** Type of tick we wish to perform on the level *\u002F\nenum ELevelTick\n{\n\u002F** Update the level time only. *\u002F\nLEVELTICK_TimeOnly = 0,\n\u002F** Update time and viewports. *\u002F\nLEVELTICK_ViewportsOnly = 1,\n\u002F** Update all. *\u002F\nLEVELTICK_All = 2,\n\u002F** Delta time is zero, we are paused. Components don't tick. *\u002F\nLEVELTICK_PauseTick = 3,\n};\n",[44,79,80,86,97,103,109,125,131,144,150,163,169,182],{"__ignoreMap":42},[47,81,82],{"class":49,"line":50},[47,83,85],{"class":84},"sZdD5","\u002F** Type of tick we wish to perform on the level *\u002F\n",[47,87,89,93],{"class":49,"line":88},2,[47,90,92],{"class":91},"sGZGq","enum",[47,94,96],{"class":95},"sfUV7"," ELevelTick\n",[47,98,100],{"class":49,"line":99},3,[47,101,102],{"class":53},"{\n",[47,104,106],{"class":49,"line":105},4,[47,107,108],{"class":84},"\u002F** Update the level time only. *\u002F\n",[47,110,112,116,119,122],{"class":49,"line":111},5,[47,113,115],{"class":114},"s7EbQ","LEVELTICK_TimeOnly",[47,117,118],{"class":57}," =",[47,120,121],{"class":61}," 0",[47,123,124],{"class":53},",\n",[47,126,128],{"class":49,"line":127},6,[47,129,130],{"class":84},"\u002F** Update time and viewports. *\u002F\n",[47,132,134,137,139,142],{"class":49,"line":133},7,[47,135,136],{"class":114},"LEVELTICK_ViewportsOnly",[47,138,118],{"class":57},[47,140,141],{"class":61}," 1",[47,143,124],{"class":53},[47,145,147],{"class":49,"line":146},8,[47,148,149],{"class":84},"\u002F** Update all. *\u002F\n",[47,151,153,156,158,161],{"class":49,"line":152},9,[47,154,155],{"class":114},"LEVELTICK_All",[47,157,118],{"class":57},[47,159,160],{"class":61}," 2",[47,162,124],{"class":53},[47,164,166],{"class":49,"line":165},10,[47,167,168],{"class":84},"\u002F** Delta time is zero, we are paused. Components don't tick. *\u002F\n",[47,170,172,175,177,180],{"class":49,"line":171},11,[47,173,174],{"class":114},"LEVELTICK_PauseTick",[47,176,118],{"class":57},[47,178,179],{"class":61}," 3",[47,181,124],{"class":53},[47,183,185],{"class":49,"line":184},12,[47,186,187],{"class":53},"};\n",[11,189,190],{},"其中，如果编辑器不是在RealTime模式（Ctrl+R打开）的话，就会是LEVELTICK_TimeOnly ，如果是的话就是LEVELTICK_ViewportsOnly 。一般不是实时模式的话，一些依赖于Tick表现的效果就不会更新，例如旧的粒子特效。",[11,192,193],{},"当LEVELTICK_ViewportsOnly 时，只有与角色有相关性的东西才会Tick。而对于在游戏中需要设定达到这个效果的话，可以设置UWorld的bPlayersOnly，这样的话就会强制将Tick类型变为LEVELTICK_ViewportsOnly 。",[11,195,196],{},"原始的过滤发生在如下的定义：",[37,198,203],{"className":199,"code":201,"language":202},[200],"language-text","void AActor::TickActor( float DeltaSeconds, ELevelTick TickType, FActorTickFunction& ThisTickFunction )\n{\n  \u002F\u002Froot of tick hierarchy\n\n  \u002F\u002F Non-player update.\n  const bool bShouldTick = ((TickType!=LEVELTICK_ViewportsOnly) || ShouldTickIfViewportsOnly());\n  if(bShouldTick)\n  {\n    \u002F\u002F If an Actor has been Destroyed or its level has been unloaded don't execute any queued ticks\n    if (!IsPendingKill() && GetWorld())\n    {\n      Tick(DeltaSeconds);    \u002F\u002F perform any tick functions unique to an actor subclass\n    }\n  }\n}\n","text",[44,204,201],{"__ignoreMap":42},[11,206,207],{},"在C++中使用时，如果遇到Tick函数在编辑模式下不会运行的情况，这时可以尝试对以下函数进行重载：",[37,209,211],{"className":39,"code":210,"language":41,"meta":42,"style":42},"virtual bool ShouldTickIfViewportsOnly() const override;\n",[44,212,213],{"__ignoreMap":42},[47,214,215,218,221,225,228,231,234],{"class":49,"line":50},[47,216,217],{"class":57},"virtual",[47,219,220],{"class":91}," bool",[47,222,224],{"class":223},"scAT4"," ShouldTickIfViewportsOnly",[47,226,227],{"class":53},"() ",[47,229,230],{"class":57},"const",[47,232,233],{"class":57}," override",[47,235,65],{"class":53},[11,237,238],{},"然后直接返回True。",[37,240,242],{"className":39,"code":241,"language":41,"meta":42,"style":42},"bool AMyActor::ShouldTickIfViewportsOnly() const\n{\n return true;\n}\n",[44,243,244,263,267,276],{"__ignoreMap":42},[47,245,246,249,252,255,258,260],{"class":49,"line":50},[47,247,248],{"class":91},"bool",[47,250,251],{"class":95}," AMyActor",[47,253,254],{"class":53},"::",[47,256,257],{"class":223},"ShouldTickIfViewportsOnly",[47,259,227],{"class":53},[47,261,262],{"class":57},"const\n",[47,264,265],{"class":49,"line":88},[47,266,102],{"class":53},[47,268,269,272,274],{"class":49,"line":99},[47,270,271],{"class":57}," return",[47,273,62],{"class":61},[47,275,65],{"class":53},[47,277,278],{"class":49,"line":105},[47,279,280],{"class":53},"}\n",[29,282,284],{"id":283},"tickgroup","TickGroup",[11,286,287],{},"UE4内部对Tick进行了分组管理，TickGroup之间按照先后顺序进行调用，其在引擎内部的定义为：",[37,289,291],{"className":39,"code":290,"language":41,"meta":42,"style":42},"UENUM(BlueprintType)\nenum ETickingGroup\n{\n\u002F** 在物理模拟开始之前的TickGroup *\u002F\nTG_PrePhysics UMETA(DisplayName=\"Pre Physics\"),\n\n\u002F** 用于开始物理模拟的特殊的TickGroup *\u002F\nTG_StartPhysics UMETA(Hidden, DisplayName=\"Start Physics\"),\n\n\u002F** 与物理模拟并行的TickGroup *\u002F\nTG_DuringPhysics UMETA(DisplayName=\"During Physics\"),\n\n\u002F** 用于接受物理模拟的特殊TickGroup *\u002F\nTG_EndPhysics UMETA(Hidden, DisplayName=\"End Physics\"),\n\n\u002F**在物理模拟（包括衣料材质演算）后运行的TickGroup *\u002F\nTG_PostPhysics UMETA(DisplayName=\"Post Physics\"),\n\n\u002F** 所有的更新操作完成后的TickGroup *\u002F\nTG_PostUpdateWork UMETA(DisplayName=\"Post Update Work\"),\n\n\u002F** 包含所有被延迟到最后的TickGroup *\u002F\nTG_LastDemotable UMETA(Hidden, DisplayName = \"Last Demotable\"),\n\n\u002F** 并非TickGroup的伪TickGroup，用于处理新生成的物体 *\u002F\nTG_NewlySpawned UMETA(Hidden, DisplayName=\"Newly Spawned\"),\n\nTG_MAX,\n\n};\n",[44,292,293,301,308,312,317,334,340,345,365,369,374,388,392,398,417,422,428,443,448,454,469,474,480,499,504,510,529,534,542,547],{"__ignoreMap":42},[47,294,295,298],{"class":49,"line":50},[47,296,297],{"class":223},"UENUM",[47,299,300],{"class":53},"(BlueprintType)\n",[47,302,303,305],{"class":49,"line":88},[47,304,92],{"class":91},[47,306,307],{"class":95}," ETickingGroup\n",[47,309,310],{"class":49,"line":99},[47,311,102],{"class":53},[47,313,314],{"class":49,"line":105},[47,315,316],{"class":84},"\u002F** 在物理模拟开始之前的TickGroup *\u002F\n",[47,318,319,322,325,327,331],{"class":49,"line":111},[47,320,321],{"class":53},"TG_PrePhysics UMETA(",[47,323,324],{"class":114},"DisplayName",[47,326,58],{"class":57},[47,328,330],{"class":329},"sZnmU","\"Pre Physics\"",[47,332,333],{"class":53},"),\n",[47,335,336],{"class":49,"line":127},[47,337,339],{"emptyLinePlaceholder":338},true,"\n",[47,341,342],{"class":49,"line":133},[47,343,344],{"class":84},"\u002F** 用于开始物理模拟的特殊的TickGroup *\u002F\n",[47,346,347,350,353,356,358,360,363],{"class":49,"line":146},[47,348,349],{"class":53},"TG_StartPhysics UMETA(",[47,351,352],{"class":114},"Hidden",[47,354,355],{"class":53},", ",[47,357,324],{"class":114},[47,359,58],{"class":57},[47,361,362],{"class":329},"\"Start Physics\"",[47,364,333],{"class":53},[47,366,367],{"class":49,"line":152},[47,368,339],{"emptyLinePlaceholder":338},[47,370,371],{"class":49,"line":165},[47,372,373],{"class":84},"\u002F** 与物理模拟并行的TickGroup *\u002F\n",[47,375,376,379,381,383,386],{"class":49,"line":171},[47,377,378],{"class":53},"TG_DuringPhysics UMETA(",[47,380,324],{"class":114},[47,382,58],{"class":57},[47,384,385],{"class":329},"\"During Physics\"",[47,387,333],{"class":53},[47,389,390],{"class":49,"line":184},[47,391,339],{"emptyLinePlaceholder":338},[47,393,395],{"class":49,"line":394},13,[47,396,397],{"class":84},"\u002F** 用于接受物理模拟的特殊TickGroup *\u002F\n",[47,399,401,404,406,408,410,412,415],{"class":49,"line":400},14,[47,402,403],{"class":53},"TG_EndPhysics UMETA(",[47,405,352],{"class":114},[47,407,355],{"class":53},[47,409,324],{"class":114},[47,411,58],{"class":57},[47,413,414],{"class":329},"\"End Physics\"",[47,416,333],{"class":53},[47,418,420],{"class":49,"line":419},15,[47,421,339],{"emptyLinePlaceholder":338},[47,423,425],{"class":49,"line":424},16,[47,426,427],{"class":84},"\u002F**在物理模拟（包括衣料材质演算）后运行的TickGroup *\u002F\n",[47,429,431,434,436,438,441],{"class":49,"line":430},17,[47,432,433],{"class":53},"TG_PostPhysics UMETA(",[47,435,324],{"class":114},[47,437,58],{"class":57},[47,439,440],{"class":329},"\"Post Physics\"",[47,442,333],{"class":53},[47,444,446],{"class":49,"line":445},18,[47,447,339],{"emptyLinePlaceholder":338},[47,449,451],{"class":49,"line":450},19,[47,452,453],{"class":84},"\u002F** 所有的更新操作完成后的TickGroup *\u002F\n",[47,455,457,460,462,464,467],{"class":49,"line":456},20,[47,458,459],{"class":53},"TG_PostUpdateWork UMETA(",[47,461,324],{"class":114},[47,463,58],{"class":57},[47,465,466],{"class":329},"\"Post Update Work\"",[47,468,333],{"class":53},[47,470,472],{"class":49,"line":471},21,[47,473,339],{"emptyLinePlaceholder":338},[47,475,477],{"class":49,"line":476},22,[47,478,479],{"class":84},"\u002F** 包含所有被延迟到最后的TickGroup *\u002F\n",[47,481,483,486,488,490,492,494,497],{"class":49,"line":482},23,[47,484,485],{"class":53},"TG_LastDemotable UMETA(",[47,487,352],{"class":114},[47,489,355],{"class":53},[47,491,324],{"class":114},[47,493,118],{"class":57},[47,495,496],{"class":329}," \"Last Demotable\"",[47,498,333],{"class":53},[47,500,502],{"class":49,"line":501},24,[47,503,339],{"emptyLinePlaceholder":338},[47,505,507],{"class":49,"line":506},25,[47,508,509],{"class":84},"\u002F** 并非TickGroup的伪TickGroup，用于处理新生成的物体 *\u002F\n",[47,511,513,516,518,520,522,524,527],{"class":49,"line":512},26,[47,514,515],{"class":53},"TG_NewlySpawned UMETA(",[47,517,352],{"class":114},[47,519,355],{"class":53},[47,521,324],{"class":114},[47,523,58],{"class":57},[47,525,526],{"class":329},"\"Newly Spawned\"",[47,528,333],{"class":53},[47,530,532],{"class":49,"line":531},27,[47,533,339],{"emptyLinePlaceholder":338},[47,535,537,540],{"class":49,"line":536},28,[47,538,539],{"class":114},"TG_MAX",[47,541,124],{"class":53},[47,543,545],{"class":49,"line":544},29,[47,546,339],{"emptyLinePlaceholder":338},[47,548,550],{"class":49,"line":549},30,[47,551,187],{"class":53},[11,553,554],{},"在蓝图中有提供修改的接口，默认情况下的TickGroup是PrePhysics。",[11,556,557],{},[558,559],"img",{"alt":560,"src":561},"image","\u002Fwp-content\u002Fuploads\u002F2017\u002F03\u002Fimage_thumb.png",[11,563,564],{},"在C++中可以额外的添加Tick函数，使得对象可以获得额外的其他TickGroup的Tick，但是在设计上意义并不是很大。",[29,566,568],{"id":567},"prerequisite","Prerequisite",[11,570,571],{},"在同一个TickGroup中的Tick似乎是根据优先调度进行的，其顺序近似随机。在特殊的需求下，UE4还提供了前置Tick Actor的设置，它可以保证两个Actor之间的tick按照顺序被调用。",[11,573,574],{},[558,575],{"alt":576,"src":577},"H13GGJPBRDQ5V0","\u002Fwp-content\u002Fuploads\u002F2017\u002F03\u002FH13GGJPBRDQ5V0DB0O4XN.png",[11,579,580],{},[558,581],{"alt":582,"src":583},"QSKL3GNA7SKY","\u002Fwp-content\u002Fuploads\u002F2017\u002F03\u002FQSKL3GNA7SKYM_JGEC_thumb.png",[11,585,586],{},"这个函数只影响当前Actor的tick的执行次序，其子组件的tick并不受影响，如有需要需另行指定。",[21,588,590],{"id":589},"timeline","TimeLine",[11,592,593],{},"时间轴是相当有效的在蓝图中对数值进行时间演变的方式，可以在很多地方被应用。例如打开房门或者是渐变材质的颜色等。",[11,595,596],{},[558,597],{"alt":598,"src":599},"91ZMJYQTWY","\u002Fwp-content\u002Fuploads\u002F2017\u002F03\u002F91PCXG9493HZMJYQTWY_thumb.png",[11,601,602,603,610],{},"功能一目了然，不明了的地方可以参照[",[604,605,609],"a",{"href":606,"rel":607},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FEngine\u002FBlueprints\u002FUserGuide\u002FTimelines\u002Findex.html",[608],"nofollow","官方文档","]。",[11,612,613,614,610],{},"TimeLine也可以在C++中使用，但是实际应用的场景似乎不大，如有遇到可以参考这篇[",[604,615,618],{"href":616,"rel":617},"https:\u002F\u002Fwiki.unrealengine.com\u002FTimeline_in_c%2B%2B",[608],"社区文档",[21,620,622],{"id":621},"timer","Timer",[11,624,625],{},"UE4提供了定时运行的Timer供使用，在游戏逻辑上还是有经常被用到的。",[11,627,628],{},[558,629],{"alt":630,"src":631},"D8TY","\u002Fwp-content\u002Fuploads\u002F2017\u002F03\u002FD8TYNNII5VXAS_F28XHO_thumb.png",[11,633,634,635,610],{},"使用起来需要连接事件或者提供函数名，详细的可以参考[",[604,636,609],{"href":637,"rel":638},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FGameplay\u002FHowTo\u002FUseTimers\u002Findex.html",[608],[11,640,641],{},"在C++中使用Timer也相对简单，使用TimerManager提供的接口函数就可以了。",[37,643,645],{"className":39,"code":644,"language":41,"meta":42,"style":42},"FTimerHandle DummyHandle;\n\nGetWorld()->GetTimerManager()->SetTimer( DummyHandle, StartPulseSurveyIconDelegate, SurveyPulseTimeInterval, false );\n",[44,646,647,652,656],{"__ignoreMap":42},[47,648,649],{"class":49,"line":50},[47,650,651],{"class":53},"FTimerHandle DummyHandle;\n",[47,653,654],{"class":49,"line":88},[47,655,339],{"emptyLinePlaceholder":338},[47,657,658,661,664,667,669,672,675,678],{"class":49,"line":99},[47,659,660],{"class":223},"GetWorld",[47,662,663],{"class":53},"()->",[47,665,666],{"class":223},"GetTimerManager",[47,668,663],{"class":53},[47,670,671],{"class":223},"SetTimer",[47,673,674],{"class":53},"( DummyHandle, StartPulseSurveyIconDelegate, SurveyPulseTimeInterval, ",[47,676,677],{"class":61},"false",[47,679,680],{"class":53}," );\n",[11,682,683,684,688],{},"第二个参数是一个",[604,685,687],{"href":686},"\u002F2015-08-16-ue4-event-dispatcher-and-delegate\u002F","Delegate","，定义如下：",[37,690,692],{"className":39,"code":691,"language":41,"meta":42,"style":42},"DECLARE_DELEGATE(FTimerDelegate);\n\nFTimerDelegate StartPulseSurveyIconDelegate;\n",[44,693,694,702,706],{"__ignoreMap":42},[47,695,696,699],{"class":49,"line":50},[47,697,698],{"class":223},"DECLARE_DELEGATE",[47,700,701],{"class":53},"(FTimerDelegate);\n",[47,703,704],{"class":49,"line":88},[47,705,339],{"emptyLinePlaceholder":338},[47,707,708],{"class":49,"line":99},[47,709,710],{"class":53},"FTimerDelegate StartPulseSurveyIconDelegate;\n",[11,712,713],{},"使用到的第一个参数为FTimerHandle，是用来标识并事后交付给TimerManager用于操作指定Timer的，如果没有这个需求，可以直接定义一个Dummy。",[37,715,717],{"className":39,"code":716,"language":41,"meta":42,"style":42},"GetWorld()->GetTimerManager().UnPauseTimer(SampleTimerHandle);\n",[44,718,719],{"__ignoreMap":42},[47,720,721,723,725,727,730,733],{"class":49,"line":50},[47,722,660],{"class":223},[47,724,663],{"class":53},[47,726,666],{"class":223},[47,728,729],{"class":53},"().",[47,731,732],{"class":223},"UnPauseTimer",[47,734,735],{"class":53},"(SampleTimerHandle);\n",[29,737,739],{"id":738},"delay","Delay",[11,741,742],{},"延迟执行节点，在蓝图中有时会用到的逻辑。Delay相当于一个不重复执行的Timer，因此在C++中要使用这种功能的话，就是用Timer来了。",[11,744,745],{},[558,746],{"alt":747,"src":748},"RPNTM","\u002Fwp-content\u002Fuploads\u002F2017\u002F03\u002FRPNT_K6CC@5L6HMONU7M_thumb.png",[11,750,751],{},"其中上方的那个Delay节点提供的功能是：当还在倒数时被调用到，就会重新开始倒数。",[21,753,754],{"id":754},"速度调节",[11,756,757],{},"UE4有提供调节步进的接口函数，这个功能通常名为slow mode，功能上类似于FPS游戏中经常出现的子弹时间效果。",[11,759,760],{},"这个系列的函数提供两种类型的控制，一种是全局的，一种是针对某个特定物体的。",[11,762,763],{},[558,764],{"alt":765,"src":766},"ZB08UAW","\u002Fwp-content\u002Fuploads\u002F2017\u002F03\u002FZB08UAWFXEIJ5F@GM_thumb.png",[11,768,769],{},[558,770],{"alt":771,"src":772},"DP71R","\u002Fwp-content\u002Fuploads\u002F2017\u002F03\u002FDP7MMNR3LR14QVF81R_thumb.png",[11,774,775],{},"全局的时间调节也可以使用控制台指令打开：",[11,777,778],{},[558,779],{"alt":780,"src":781},"P2YPAQQ7","\u002Fwp-content\u002Fuploads\u002F2017\u002F03\u002FP39OU9IC2Y52GHPAQQ7_thumb.png",[11,783,784],{},"这个功能相对简单，时间设定的细节需要到具体使用的时候调节为上。",[786,787,788],"style",{},"html pre.shiki code .s0idv, html code.shiki .s0idv{--shiki-default:#0E1116;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .sLXdl, html code.shiki .sLXdl{--shiki-default:#A0111F;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .stP0Q, html code.shiki .stP0Q{--shiki-default:#023B95;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}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);}html pre.shiki code .sZdD5, html code.shiki .sZdD5{--shiki-default:#66707B;--shiki-dark:#6A737D;--shiki-sepia:#88846F}html pre.shiki code .sGZGq, html code.shiki .sGZGq{--shiki-default:#A0111F;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sfUV7, html code.shiki .sfUV7{--shiki-default:#702C00;--shiki-default-text-decoration:inherit;--shiki-dark:#B392F0;--shiki-dark-text-decoration:inherit;--shiki-sepia:#A6E22E;--shiki-sepia-text-decoration:underline}html pre.shiki code .s7EbQ, html code.shiki .s7EbQ{--shiki-default:#023B95;--shiki-dark:#79B8FF;--shiki-sepia:#F8F8F2}html pre.shiki code .scAT4, html code.shiki .scAT4{--shiki-default:#622CBC;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .sZnmU, html code.shiki .sZnmU{--shiki-default:#032563;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}",{"title":42,"searchDepth":88,"depth":99,"links":790},[791,796,797,800],{"id":23,"depth":88,"text":24,"children":792},[793,794,795],{"id":31,"depth":99,"text":32},{"id":283,"depth":99,"text":284},{"id":567,"depth":99,"text":568},{"id":589,"depth":88,"text":590},{"id":621,"depth":88,"text":622,"children":798},[799],{"id":738,"depth":99,"text":739},{"id":754,"depth":88,"text":754},"2017-03-25","md",{"layout":804,"status":805,"published":338,"author":806,"author_login":808,"author_email":809,"wordpress_id":810,"wordpress_url":811,"date_gmt":812,"excerpt":813},"post","publish",{"display_name":807,"login":808,"email":809,"url":42},"风铃","flinkor","flinkor@foxmail.com",1571,"\u002F\u002F?p=1571","2017-03-25 03:26:22 +0000",{"type":8,"value":814},[815],[11,816,13],{},"\u002F2017-03-25-ue4-time-mange",{"title":6,"description":13},"_legacy\u002F2017\u002F2017-03-25-ue4-time-mange",[821,822],"UE4","Time","8vy4gJatwGajxVpI01uxbY-PZbVt-kezVlysdvuWkgE",{"id":825,"title":826,"body":827,"date":1046,"description":831,"extension":802,"meta":1047,"navigation":338,"path":1059,"seo":1060,"stem":1061,"tags":1062,"__hash__":1064},"blogs\u002F_legacy\u002F2015\u002F2015-09-17-ue4-paper-2d-overview.md","UE4中Paper2D初探",{"type":8,"value":828,"toc":1037},[829,832,835,843,847,850,853,856,859,865,868,873,876,881,884,889,892,897,900,905,908,913,916,921,924,927,934,937,940,943,946,953,956,959,962,965,970,974,977,980,983,986,989,992,995,1000,1003,1007,1010,1013,1016,1019,1022,1025,1028],[11,830,831],{},"Paper 2D是UE4提供的基于精灵的2D系统，可以用于构建2D和2D\u002F3D混合游戏。",[11,833,834],{},"当前UE4版本4.10.2；",[11,836,837,838,842],{},"部分内容参考的[",[604,839,609],{"href":840,"rel":841},"https:\u002F\u002Fwww.google.com.hk\u002Furl?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&uact=8&ved=0ahUKEwj7yprKvc7SAhVFlJQKHfgCA1AQFggaMAA&url=%68%74%74%70%73%3a%2f%2f%64%6f%63%73%2e%75%6e%72%65%61%6c%65%6e%67%69%6e%65%2e%63%6f%6d%2f%6c%61%74%65%73%74%2f%43%48%4e%2f%45%6e%67%69%6e%65%2f%50%61%70%65%72%32%44%2f&usg=AFQjCNG1WlLQvG_q8TSg44GLR4WEJ0ATdA",[608],"]版本标识为UE4.9。目前Paper 2D的动画系统是基于帧动画的，社区讨论中虽然有加入2D骨骼动画系统的讨论，但是目前的正式引擎版本中并没有这样的功能。",[21,844,846],{"id":845},"sprite","Sprite",[11,848,849],{},"Sprite是帧动画的基本单位，它来源于对Texture的裁剪，形成动画的一个帧并最终用于FlipBook中。",[11,851,852],{},"Sprite可以从Texture创建也可以创建之后指定Texture，同时UE4支持导入从Texture Packer和Adobe Flash CS6生成的帧动画。",[29,854,855],{"id":855},"属性",[11,857,858],{},"Sprite的属性相对比较简单：",[11,860,861],{},[862,863,864],"strong",{},"Source Texture",[11,866,867],{},"当前精灵所使用的2D图像",[11,869,870],{},[862,871,872],{},"Source UV",[11,874,875],{},"裁剪的起始点在原图形上的偏离，像素单位",[11,877,878],{},[862,879,880],{},"Source Dimension",[11,882,883],{},"裁剪的大小，像素单位",[11,885,886],{},[862,887,888],{},"Default Material",[11,890,891],{},"精灵的默认材质，系统提供的为Unlit Masked的材质，这个材质可以在Flipbook中进行覆盖设置。",[11,893,894],{},[862,895,896],{},"Pixels per unit",[11,898,899],{},"设置像素与引擎内部单位之间的缩放比例，用于调整精灵在UE世界中的大小。在Paper2D的插件设置中有全局设定，也可以对每个精灵进行指定。",[11,901,902],{},[862,903,904],{},"Pivot Mode",[11,906,907],{},"中心点位置。设置为Custom的话可以进行精细的设置。",[11,909,910],{},[862,911,912],{},"Sockets",[11,914,915],{},"和3D动画中的Sockets功能时类似的，提供一个接口。",[11,917,918],{},[862,919,920],{},"Sprite Collision Domain",[11,922,923],{},"精灵碰撞模式，可以选择无碰撞、2D或者3D。",[11,925,926],{},"属性中有些详细的可以在使用的时候进行调整测试。",[11,928,929,930,933],{},"有一个属性为Additional Textures，从引擎的描述上看是用于为精灵提供额外的贴图的，当前文档中没有给出描述。要使用的话似乎需要对默认材质进行修改，有使用需求的可以参考",[47,931,932],{},"这里","的讨论。",[11,935,936],{},"精灵的材质可以进行很多的调整和自定义，从Flipbook过来的Sprite Color属性在材质中是Vertext Color的输入项。",[29,938,939],{"id":939},"碰撞检测",[11,941,942],{},"整个Paper2D中可以选择的碰撞方式为2D和3D两种，3D碰撞使用的引擎内通用的PhysX，而2D碰撞使用的是Box2D。当前Box2D仍然处于实验阶段，有一些功能是不完整的，文档支持也不够完整。Box2D的支持也只能到Win32和Win64两个平台，要使用额外的功能还要在项目设置中打开bEnable2DPhysics开关。总体而言，目前不是很适合使用。",[11,944,945],{},"同时，在Sprite的编辑器中可以选择对碰撞形态进行调整，除了引擎提供的几种模式之外，可以自行指定。",[11,947,948,949,952],{},"各个碰撞模式的不同可以看官方的",[47,950,951],{},"图例","，相当的直观。",[11,954,955],{},"使用上的建议是使用默认的简单图形进行碰撞，如果需要使用精细的碰撞的话可以使用Sockets来外挂碰撞体来进行碰撞检测，逐帧进行精细的碰撞检测是相当消耗系统运算力的。",[29,957,958],{"id":958},"区域调整",[11,960,961],{},"在Sprite编辑器中可以对裁剪的区域进行调整，也有提供一些快捷的功能。不过一般情况下应该在TexturePacker之类的软件中对这些功能进行调整会更方便一些。",[11,963,964],{},"区域调整功能更重要的是对渲染区域进行调整，将有的精灵中多余的空白部分排除到渲染之外的话，可以很好的降低渲染压力。",[11,966,967],{},[558,968],{"alt":42,"src":969},"\u002Fwp-content\u002Fuploads\u002F2017\u002F03\u002FphpykKLq1.1453384829.png",[21,971,973],{"id":972},"flipbook","FlipBook",[11,975,976],{},"FlipBook是帧动画的管理类，将Sprite组织成动画进行播放。FlipBook中的属性并不多：",[11,978,979],{},"Frames Per Seconds为每秒帧率；",[11,981,982],{},"Default Material为所有动画帧使用的材质；",[11,984,985],{},"Key Frames中存储的是每一个用到的关键帧的精灵的属性，可以调整的目前只有Frame Run，为帧的持续帧数；",[11,987,988],{},"Collision Source为碰撞模式选择，可以选择关闭碰撞、逐帧碰撞或者使用第一帧进行碰撞。",[11,990,991],{},"实际运用中更加重要的是FlipBook Components，这个蓝图类提供了很多方便的对Flipbook进行操作的接口。",[11,993,994],{},"在使用中如果开启物理出现了翻转出平面之类的不合理现象的话，需要对坐标进行锁定:",[11,996,997],{},[558,998],{"alt":42,"src":999},"\u002Fwp-content\u002Fuploads\u002F2017\u002F03\u002Fphp7XeP65.1453384926.png",[11,1001,1002],{},"其他的在使用上并没有什么太多的复杂的地方，唯一的麻烦死目前并没有Paper2D的动画状态机，需要在BP中使用事件自行进行动画的更新控制。",[21,1004,1006],{"id":1005},"tileset-tilemap","TileSet& TileMap",[11,1008,1009],{},"瓦片地图功能，当前在UE4中为实验功能。如果不使用的话可以不用太详细研究的感觉。",[11,1011,1012],{},"UE4支持从Tiled进行瓦片地图的导入。",[11,1014,1015],{},"瓦片地图的原理本身比较简单，TileSet与TileMap中的属性设置也都非常的直观。",[11,1017,1018],{},"Tile本身的碰撞是在TileSet中进行设置和调整的，在TileMap中也有开关进行碰撞类型的选择，Sprite Collision",[11,1020,1021],{},"Domain的含义与Sprite中是一样的。TileSet编辑器中也可以对每一个Tile进行一些独立的调整。",[11,1023,1024],{},"TileMap本身支持多层地图，功能上还是比较完整的。",[11,1026,1027],{},"有一个属性需要注意，Sepration Per Layer，这个属性是用于调整每一层地图之间的距离的，在多层地图中，相当于通常的Z-Order设置的位置，可以为一些地图特效提供帮助。",[11,1029,1030,1031,1036],{},"最后，在C++中使用Paper2D的功能当前并未完全开发，官方的建议是自己参考相关文档，例如[",[604,1032,1035],{"href":1033,"rel":1034},"https:\u002F\u002Fwww.google.com.hk\u002Furl?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&uact=8&ved=0ahUKEwi8rtmcwM7SAhWIm5QKHTJCAacQFggaMAA&url=%68%74%74%70%73%3a%2f%2f%64%6f%63%73%2e%75%6e%72%65%61%6c%65%6e%67%69%6e%65%2e%63%6f%6d%2f%6c%61%74%65%73%74%2f%49%4e%54%2f%41%50%49%2f%50%6c%75%67%69%6e%73%2f%50%61%70%65%72%32%44%2f%55%50%61%70%65%72%46%6c%69%70%62%6f%6f%6b%43%6f%6d%70%6f%6e%65%6e%74%2f%69%6e%64%65%78%2e%68%74%6d%6c&usg=AFQjCNHCRxbIjTBa0auVoc68-WMegXvSIg",[608],"UPaperFlipbookComponent的文档","]进行相关操作。",{"title":42,"searchDepth":88,"depth":99,"links":1038},[1039,1044,1045],{"id":845,"depth":88,"text":846,"children":1040},[1041,1042,1043],{"id":855,"depth":99,"text":855},{"id":939,"depth":99,"text":939},{"id":958,"depth":99,"text":958},{"id":972,"depth":88,"text":973},{"id":1005,"depth":88,"text":1006},"2015-09-17",{"layout":804,"status":805,"published":338,"author":1048,"author_login":1049,"author_email":1050,"author_url":1051,"wordpress_id":1052,"wordpress_url":1053,"date_gmt":1054,"excerpt":1055},{"display_name":1049,"login":1049,"email":1050,"url":1051},"chaoshikari","chaoshikari@gmail.com","\u002F",1486,"\u002F\u002F?p=1486","2015-09-17 12:42:51 +0000",{"type":8,"value":1056},[1057],[11,1058,831],{},"\u002F2015-09-17-ue4-paper-2d-overview",{"title":826,"description":831},"_legacy\u002F2015\u002F2015-09-17-ue4-paper-2d-overview",[821,1063],"Paper2d","RyVgAPhmb0BSN6mth_bfpap0N4j-E_ekUqpbEXBflh0",{"id":1066,"title":1067,"body":1068,"date":1289,"description":1072,"extension":802,"meta":1290,"navigation":338,"path":1299,"seo":1300,"stem":1301,"tags":1302,"__hash__":1304},"blogs\u002F_legacy\u002F2015\u002F2015-09-12-ue4-ai-and-behavior-trees.md","UE4中AI与行为树",{"type":8,"value":1069,"toc":1272},[1070,1073,1076,1079,1082,1085,1088,1097,1100,1104,1107,1110,1114,1117,1125,1129,1132,1137,1140,1143,1148,1151,1154,1159,1162,1165,1169,1172,1179,1183,1186,1189,1192,1195,1202,1206,1209,1212,1216,1219,1222,1226,1229,1232,1240,1244,1247,1250,1253,1257,1260,1263,1266,1269],[11,1071,1072],{},"UE4的AI系统使用行为树作为中心进行，同时搭配场景查询系统帮助AI快速获取周围环境状况。",[11,1074,1075],{},"在UE4中，Pawn是可以由玩家或者AI控制的基础类。通常使用的Character类就是对Pawn类的一个具体实现。",[11,1077,1078],{},"就像玩家通过PlayerController控制Character一样，AI则通过AIController来控制Character。",[11,1080,1081],{},"AIController为AI对Pawn进行操作的核心类，但是AI的主要逻辑是在行为树中进行的。",[21,1083,1084],{"id":1084},"行为树",[11,1086,1087],{},"UE4的行为树实现是事件驱动的，相比逐帧的有限状态机之类的实现，能够更好的控制AI运行的成本。",[11,1089,1090,1091,1096],{},"官方有提供行为树的入门指南，按照",[604,1092,1095],{"href":1093,"rel":1094},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FEngine\u002FAI\u002FBehaviorTrees\u002FQuickStart\u002Findex.html",[608],"官方指南","一步一步操作的话，就能初步熟悉行为树的工作原理了。",[11,1098,1099],{},"行为树的节点分为Composite、Task、Decorator、Service这四种。还有一个比较特殊的节点为Root，是行为树执行的起点。其本身是没有属性的，选中Root节点展示的是行为树本身的属性。",[29,1101,1103],{"id":1102},"blackboard","Blackboard",[11,1105,1106],{},"根据官方描述，Blackboard是AI的“记忆”。其中以键值的形式存储各种数值、对象以供行为树使用。",[11,1108,1109],{},"对于行为树而言，Blackboard类是非常重要的类，它是用于行为树本身与“外界”进行交互的媒介之一，同时也是各个节点之间进行数据交互的中介存储器。",[29,1111,1113],{"id":1112},"task","Task",[11,1115,1116],{},"实际任务执行节点，可以通过蓝图或代码进行自定义，官方的入门指南中有对Task进行定义的例子。",[11,1118,1119,1120,1124],{},"引擎自带了一些基本的Task节点可供使用，功能包括移动AI、延时等待以及进行场景查询等。详细的自带节点可以参考",[604,1121,609],{"href":1122,"rel":1123},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FEngine\u002FAI\u002FBehaviorTrees\u002FNodeReference\u002FTasks\u002Findex.html",[608],"。",[29,1126,1128],{"id":1127},"composite","Composite",[11,1130,1131],{},"对任务进行组合的节点，目前只有三种：",[11,1133,1134],{},[862,1135,1136],{},"Selector",[11,1138,1139],{},"从左到右执行子节点，直到遇到一个子节点返回成功为止，此时返回成功，其他情况都返回失败。",[11,1141,1142],{},"直观的看，就是选择器，从节点中由左向右找到第一个能够成功执行的子节点，如果没有的话就返回失败。",[11,1144,1145],{},[862,1146,1147],{},"Sequence",[11,1149,1150],{},"从左到右执行子节点，直到某一个子节点返回失败，此时返回失败，其他情况则返回成功，例如全部执行完成则返回成功。",[11,1152,1153],{},"总体而言，就是按序对子节点进行执行，但是如果有一个子节点失败的话，就放弃并返回失败。",[11,1155,1156],{},[862,1157,1158],{},"Simple Parallel",[11,1160,1161],{},"在节点的子任务执行的同时，在后台运行另一个子任务。",[11,1163,1164],{},"可以选择属性来控制是否要等待后台的任务完成，还是在主任务完成时直接结束后台任务。",[29,1166,1168],{"id":1167},"decorator","Decorator",[11,1170,1171],{},"装饰器，在Task或者Composite中使用，为节点的执行添加条件。",[11,1173,1174,1175,1124],{},"可以自己进行定义，官方也提供了较多的基础装饰器。包括冷却、限时、Blackboard值检测、强制成功等许多常用功能。详细的可以参照",[604,1176,609],{"href":1177,"rel":1178},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FEngine\u002FAI\u002FBehaviorTrees\u002FNodeReference\u002FDecorators\u002Findex.html",[608],[29,1180,1182],{"id":1181},"service","Service",[11,1184,1185],{},"服务，只能被附加到Composite节点上。当该Composite被执行时，会按照设定的频率不断的执行。",[11,1187,1188],{},"通常用于更新Blackboard的值，就像官方的指南中，用来更新玩家的位置和引用。",[21,1190,1191],{"id":1191},"场景查询系统",[11,1193,1194],{},"在行为树中，AI可以通过Run EQS Query的Task来对周围的环境进行查询以形成判定。",[11,1196,1197,1198,1124],{},"官方也为场景查询系统的入门提供了指南，可以参考",[604,1199,932],{"href":1200,"rel":1201},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FEngine\u002FAI\u002FEnvironmentQuerySystem\u002FQuickStart\u002Findex.html",[608],[29,1203,1205],{"id":1204},"nav-mesh","Nav Mesh",[11,1207,1208],{},"Nav Mesh是场景查询系统运行的基础，但是即便不使用Run EQS Query节点，Nav Mesh也是必须的。因为Move to之类的需要寻路的行为树节点也是需要Nav Mesh的生成数据的。",[11,1210,1211],{},"通过按P键可以切换导航网格的生成结果预览，绿色的地方就是生成了路径的地方。",[29,1213,1215],{"id":1214},"eqs-testing-pawn","EQS Testing Pawn",[11,1217,1218],{},"用于对EQS进行调试的节点，将其拖放到Nav Mesh生成区域的相应的位置，就会在该处执行属性中设置的场景查询。",[11,1220,1221],{},"结果的展示通过彩色的球体来表现，蓝色的球体表示失败的或是返回false的布尔查询，由绿到红的颜色区间则对应返回数值的区间变化。",[29,1223,1225],{"id":1224},"generators","Generators",[11,1227,1228],{},"EQS的基础节点，用于查询的基础的生成器。",[11,1230,1231],{},"生成器的结果被称为Items，包括返回的实际的Actor以及生成的位置。",[11,1233,1234,1235,1239],{},"当前的几种自带的生成器可以参考",[604,1236,609],{"href":1237,"rel":1238},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FEngine\u002FAI\u002FEnvironmentQuerySystem\u002FNodeReference\u002Findex.html#generators",[608],"。生成器是可以通过蓝图或者C++自行定义的。",[29,1241,1243],{"id":1242},"tests","Tests",[11,1245,1246],{},"在Generators中使用的节点，对Generators生成的Items进行进一步的测试。以便筛选出想要的结果。",[11,1248,1249],{},"Tests只能通过C++进行定义，当前并没有蓝图实现的方法。",[11,1251,1252],{},"在上面的文档的地址中就能看到当前系统自带的测试节点。",[29,1254,1256],{"id":1255},"contexts","Contexts",[11,1258,1259],{},"在Generators和Tests中可能会被用到的上下文。在EQS的指南中，就有为Distance测试提供的PlayerContext，方便EQS通过与玩家的距离进行评分。",[11,1261,1262],{},"可以通过蓝图或者C++进行自定义。",[21,1264,1265],{"id":1265},"总结",[11,1267,1268],{},"总体而言，UE4提供的AI系统的逻辑性还是比较清晰的。理解了其逻辑之后，要在游戏中添加AI就比较简单了。",[11,1270,1271],{},"不过要在行为树的基础上实现复杂的AI逻辑还是需要经验和想象力的。",{"title":42,"searchDepth":88,"depth":99,"links":1273},[1274,1281,1288],{"id":1084,"depth":88,"text":1084,"children":1275},[1276,1277,1278,1279,1280],{"id":1102,"depth":99,"text":1103},{"id":1112,"depth":99,"text":1113},{"id":1127,"depth":99,"text":1128},{"id":1167,"depth":99,"text":1168},{"id":1181,"depth":99,"text":1182},{"id":1191,"depth":88,"text":1191,"children":1282},[1283,1284,1285,1286,1287],{"id":1204,"depth":99,"text":1205},{"id":1214,"depth":99,"text":1215},{"id":1224,"depth":99,"text":1225},{"id":1242,"depth":99,"text":1243},{"id":1255,"depth":99,"text":1256},{"id":1265,"depth":88,"text":1265},"2015-09-12",{"layout":804,"status":805,"published":338,"author":1291,"author_login":1049,"author_email":1050,"author_url":1051,"wordpress_id":1292,"wordpress_url":1293,"date_gmt":1294,"excerpt":1295},{"display_name":1049,"login":1049,"email":1050,"url":1051},1519,"\u002F\u002F?p=1519","2015-09-12 02:38:57 +0000",{"type":8,"value":1296},[1297],[11,1298,1072],{},"\u002F2015-09-12-ue4-ai-and-behavior-trees",{"title":1067,"description":1072},"_legacy\u002F2015\u002F2015-09-12-ue4-ai-and-behavior-trees",[821,1303],"AI","5eQFa3YTr9UdzBaVHIG9Y9lyglACrrJq_KH9tiZFmOU",{"id":1306,"title":1307,"body":1308,"date":1372,"description":1312,"extension":802,"meta":1373,"navigation":338,"path":1382,"seo":1383,"stem":1384,"tags":1385,"__hash__":1388},"blogs\u002F_legacy\u002F2015\u002F2015-09-11-uwidget-wrap-swidget-to-umg.md","UWidget封装SWidget到UMG",{"type":8,"value":1309,"toc":1370},[1310,1313,1316,1319,1322,1325,1328,1331,1337,1340,1343,1349,1352,1355,1358,1361,1364,1367],[11,1311,1312],{},"为了使用UMG中的一些高级或便利特性，需要将制作好的Slate控件封装到UWidget中去。",[11,1314,1315],{},"当前UE4版本4.8.3。",[11,1317,1318],{},"将Slate封装到UMG中去有很多的好处，因为在代码中对需要重用的控件进行不断的重新布局是一件非常繁琐的事情。同时，UMG也有在3D空间中进行显示这样的高级功能。",[11,1320,1321],{},"由于UMG本身就是对Slate的封装，所以这个过程可以参照UE4的源代码进行研究。",[11,1323,1324],{},"这里所做的是尝试对上一回所做的文件目录树进行封装。",[11,1326,1327],{},"直接上代码",[11,1329,1330],{},"CListViewWidget.h",[37,1332,1335],{"className":1333,"code":1334,"language":202},[200],"#pragma once\n#include \"CListViewWidget.generated.h\"\nUCLASS()\nclass TEST_MP_API UCListViewWidget : public UWidget\n{\n  GENERATED_UCLASS_BODY()\npublic:\n\n#if WITH_EDITOR\n  \u002F\u002F UWidget interface\n  virtual const FSlateBrush* GetEditorIcon() override;\n  virtual const FText GetPaletteCategory() override;\n  virtual void OnCreationFromPalette() override;\n  \u002F\u002F End UWidget interface\n#endif\n\nprotected:\n  \u002F\u002F UWidget interface\n  virtual TSharedRef\u003CSWidget> RebuildWidget() override;\n  virtual void OnBindingChanged(const FName& Property) override;\n  \u002F\u002F End of UWidget interface\n\nprotected:\n  TSharedPtr\u003Cclass SDDFileTree> MyFileTree;\n};\n",[44,1336,1334],{"__ignoreMap":42},[11,1338,1339],{},"封装类直接继承自UWidget即可，剩下的就是对UWidget中的函数进行实现。",[11,1341,1342],{},"CListViewWidget.cpp",[37,1344,1347],{"className":1345,"code":1346,"language":202},[200],"#include \"Test_mp.h\"\n#include \"SDDFileTree.h\"\n#include \"CListViewWidget.h\"\n\n\u002F** GENERATED_UCLASS_BODY() *\u002F\nUCListViewWidget::UCListViewWidget(const FObjectInitializer& ObjectInitializer)\n: Super(ObjectInitializer)\n{\n  bIsVariable = false;\n}\n\n\u002F** \u003C编辑器属性 *\u002F\n#if WITH_EDITOR\n\n\u002F\u002F \u003C图标\nconst FSlateBrush* UCListViewWidget::GetEditorIcon()\n{\n  return FUMGStyle::Get().GetBrush(\"Widget.TextBlock\");\n}\n\n\u002F\u002F \u003C分类目录\nconst FText UCListViewWidget::GetPaletteCategory()\n{\n  return NSLOCTEXT(\"Testmp\", \"Custom\", \"Ch_Custom\");\n}\n\n\u002F\u002F \u003C创建时的默认值\nvoid UCListViewWidget::OnCreationFromPalette()\n{\n;\n}\n\n#endif\n\nTSharedRef\u003CSWidget> UCListViewWidget::RebuildWidget()\n{\n  MyFileTree = SNew(SDDFileTree).OwnerHUD(nullptr);\n\n  return MyFileTree.ToSharedRef();\n}\n\nvoid UCListViewWidget::OnBindingChanged(const FName& Property)\n{\n  Super::OnBindingChanged(Property);\n\n  if (MyFileTree.IsValid())\n  {\n    ;\n  }\n}\n",[44,1348,1346],{"__ignoreMap":42},[11,1350,1351],{},"在所有的函数中，RebuildWidget()是最重要的。",[11,1353,1354],{},"参看UWidget的源代码可以发现，所有对被封装的SWidget的引用都是通过这个函数获得的，因此在这里面写上SWidget对应的构造生成就可以了。",[11,1356,1357],{},"OnBindingChanged函数是用于属性代理通知的，这里是简单的封装，没有提供属性代理，因此直接return也是可以的。",[11,1359,1360],{},"在WITH_EDITOR宏中的是编辑器属性，用于封装后的UWidget在蓝图编辑器中显示。具体每一个函数的含义已经在注释中了。",[11,1362,1363],{},"将上面的代码编译通过之后，就可以在UMG的编辑器中看到我们添加的控件了：",[11,1365,1366],{},"将CListViewWidget拖动到控件蓝图上，并在HUD中展示此控件蓝图，就能在项目中与其他UMG控件一样使用了：",[11,1368,1369],{},"这样以来Slate的使用就会变得方便很多。",{"title":42,"searchDepth":88,"depth":99,"links":1371},[],"2015-09-11",{"layout":804,"status":805,"published":338,"author":1374,"author_login":1049,"author_email":1050,"author_url":1051,"wordpress_id":1375,"wordpress_url":1376,"date_gmt":1377,"excerpt":1378},{"display_name":1049,"login":1049,"email":1050,"url":1051},1457,"\u002F\u002F?p=1457","2015-09-11 11:11:13 +0000",{"type":8,"value":1379},[1380],[11,1381,1312],{},"\u002F2015-09-11-uwidget-wrap-swidget-to-umg",{"title":1307,"description":1312},"_legacy\u002F2015\u002F2015-09-11-uwidget-wrap-swidget-to-umg",[821,1386,1387],"UMG","Slate","mPzjKCXtbWWXkWThfinkYHexD0dpoqD6gvqKCUaR4v0",{"id":1390,"title":1391,"body":1392,"date":1685,"description":1396,"extension":802,"meta":1686,"navigation":338,"path":1695,"seo":1696,"stem":1697,"tags":1698,"__hash__":1701},"blogs\u002F_legacy\u002F2015\u002F2015-09-10-ue4-taglib-usage-and-plugin.md","UE4中Taglib使用及插件制作",{"type":8,"value":1393,"toc":1677},[1394,1397,1400,1404,1407,1410,1416,1419,1425,1428,1432,1435,1438,1445,1448,1451,1454,1458,1461,1467,1470,1473,1479,1482,1488,1491,1494,1499,1502,1508,1511,1514,1519,1522,1528,1531,1536,1539,1544,1547,1550,1554,1557,1560,1568,1571,1574,1580,1584,1587,1590,1593,1599,1602,1608,1611,1617,1620,1631,1636,1639,1642,1648,1651,1657,1660,1666,1669],[11,1395,1396],{},"UE4使用第三方库时，为了方便跨平台编译、避免冲突和多次复用，将其制作成插件是不错的选择。",[11,1398,1399],{},"当前UE4版本为4.10.1。",[21,1401,1403],{"id":1402},"taglib的使用","Taglib的使用",[11,1405,1406],{},"Taglib是一个用于读取多媒体文件标签的第三方库，之所以会使用到，是因为当前FMod在读取含有非英文的标签会得到错误的结果。",[11,1408,1409],{},"Taglib需要自己使用CMake进行配置和编译。如果只是在Windows下使用的话，最简单的方法是直接引用生成的.lib文件。在项目的.build.cs中，ModuleRules的实现里面添加：",[37,1411,1414],{"className":1412,"code":1413,"language":202},[200],"PublicIncludePaths.Add(\"F:\u002FLibs\u002Ftaglib-master\u002Finclude\u002Ftaglib\");\n\nPublicAdditionalLibraries.Add(\"F:\u002FLibs\u002Ftaglib-master\u002Ftaglib\u002FRelease\u002Ftag.lib\");\n",[44,1415,1413],{"__ignoreMap":42},[11,1417,1418],{},"然后在需要使用的地方使用Taglib即可",[37,1420,1423],{"className":1421,"code":1422,"language":202},[200],"#define TAGLIB_STATIC\n\n#include \"fileref.h\"\n\n#include \"tag.h\"\n\n#include \"tpropertymap.h\"\n\n\u002F\u002F ……\n\n\u002F** TagLib Fetch Tag *\u002F\n\nMusicItem->AlubmName = \"\";\n\nMusicItem->ArtistName = \"\";\n\nTagLib::FileRef TLFile(*MusicFullPath);\n\nif (!TLFile.isNull() && TLFile.tag()){\n\n    OutputDebugStringA(\"TagLib Fetch Reading Begin.\\n\");\n\n    TagLib::Tag *tag = TLFile.tag();\n\n    std::wstring tWsName = tag->title().toWString();\n\n    if (tWsName.length()>0)    MusicItem->SetDisplayName(tWsName.c_str());\n\n    MusicItem->AlubmName = tag->album().toWString().c_str();\n\n    MusicItem->ArtistName = tag->artist().toWString().c_str();\n\n}\n\nelse{\n\n    OutputDebugStringA(\"TagLib Fetch Fails.\\n\");\n\n}\n\n\u002F** TagLib Fetch Tag - End *\u002F\n",[44,1424,1422],{"__ignoreMap":42},[11,1426,1427],{},"但是这样的方法在进行Android之类的平台上的编译时，会遇到很多麻烦。因此将其制作为UE4插件是最好的解决方法。",[21,1429,1431],{"id":1430},"ue4插件制作","UE4插件制作",[11,1433,1434],{},"UE4的插件制作一直都处在WIP的状态，直到4.9版本开始，官方文档才变更为了可以使用的状态。因此，为了避免研究了之后官方大规模的更新，到了现在才开始研究。",[11,1436,1437],{},"UE4插件的制作相对还是比较简单的，在引擎中，官方就已经提供了4个示例插件，以其为基础进行插件开发即可。",[11,1439,1440,1441,610],{},"插件的代码布局和UE4的游戏项目差不多，只有在Public中定义的类对外界才是可见的。详细的插件系统介绍可以参照[",[604,1442,609],{"href":1443,"rel":1444},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FProgramming\u002FPlugins\u002F",[608],[11,1446,1447],{},"插件的描述文件中最重要的地方是Modules中的Type参数，这个参数是决定插件的加载类型的。通常情况下，如果是在实际游戏逻辑中使用的插件的话，这里需要修改为Runtime。",[11,1449,1450],{},"如果是在项目的Plugins目录中添加插件目录进行开发的话，需要重新生成下项目文件",[11,1452,1453],{},"这样才能在vs中看到插件。还有比较容易忘记的是，直接继承自UObject的类，必须定义Blueprintable才可以在编辑器中可见。",[21,1455,1457],{"id":1456},"taglib改造","Taglib改造",[11,1459,1460],{},"将taglib的源代码整个扔到Private目录中去，并在.build.cs中添加包含路径",[37,1462,1465],{"className":1463,"code":1464,"language":202},[200],"            PrivateIncludePaths.AddRange(\n\n                new string[] {\n\n                    \"TagLibs\u002FPrivate\",\"TagLibs\u002FPrivate\u002Ftaglib\", \"TagLibs\u002FPrivate\u002Ftaglib\u002Ftoolkit\", \"TagLibs\u002FPrivate\u002Ftaglib\u002Fasf\", \"TagLibs\u002FPrivate\u002Ftaglib\u002Fmpeg\", \"TagLibs\u002FPrivate\u002Ftaglib\u002Fogg\", \"TagLibs\u002FPrivate\u002Ftaglib\u002Fogg\u002Fflac\", \"TagLibs\u002FPrivate\u002Ftaglib\u002Fflac\", \"TagLibs\u002FPrivate\u002Ftaglib\u002Fmpc\", \"TagLibs\u002FPrivate\u002Ftaglib\u002Fmp4\", \"TagLibs\u002FPrivate\u002Ftaglib\u002Fogg\u002Fvorbis\", \"TagLibs\u002FPrivate\u002Ftaglib\u002Fogg\u002Fspeex\", \"TagLibs\u002FPrivate\u002Ftaglib\u002Fogg\u002Fopus\", \"TagLibs\u002FPrivate\u002Ftaglib\u002Fmpeg\u002Fid3v2\", \"TagLibs\u002FPrivate\u002Ftaglib\u002Fmpeg\u002Fid3v2\u002Fframes\", \"TagLibs\u002FPrivate\u002Ftaglib\u002Fmpeg\u002Fid3v1\", \"TagLibs\u002FPrivate\u002Ftaglib\u002Fape\", \"TagLibs\u002FPrivate\u002Ftaglib\u002Fwavpack\", \"TagLibs\u002FPrivate\u002Ftaglib\u002Ftrueaudio\", \"TagLibs\u002FPrivate\u002Ftaglib\u002Friff\", \"TagLibs\u002FPrivate\u002Ftaglib\u002Friff\u002Faiff\", \"TagLibs\u002FPrivate\u002Ftaglib\u002Friff\u002Fwav\", \"TagLibs\u002FPrivate\u002Ftaglib\u002Fmod\", \"TagLibs\u002FPrivate\u002Ftaglib\u002Fs3m\", \"TagLibs\u002FPrivate\u002Ftaglib\u002Fit\", \"TagLibs\u002FPrivate\u002Ftaglib\u002Fxm\"\n\n                }\n\n                );\n",[44,1466,1464],{"__ignoreMap":42},[11,1468,1469],{},"要添加的比较多，主要是因为Taglib本身使用的就是这样的方法，稍微有点麻烦。",[11,1471,1472],{},"添加完路径之后，首先遇到的错误是",[1474,1475,1476],"blockquote",{},[11,1477,1478],{},"2>EXEC : error : The first include statement in source file ‘F:\\Unreal\\PluginDev\\Plugins\\TagLibs\\Source\\TagLibs\\Private\\taglib\\mpeg\\id3v2\\id3v2header.cpp’ is trying to include the file ‘iostream’ as the precompiled header, but that file could not be located in any of the module’s include search paths.",[11,1480,1481],{},"这个错误的来源乍看之下很奇怪，其实在项目中有时候也会遇到。修正方法是，在cpp文件中首先包含模块头文件。",[37,1483,1486],{"className":1484,"code":1485,"language":202},[200],"#include \"TagLibsPrivatePCH.h\"\n",[44,1487,1485],{"__ignoreMap":42},[11,1489,1490],{},"添加了上面的文件之后，就会遇到Taglib内部大规模的namespace引发的错误，这一部分只能是一个一个的对命名空间进行控制修改了。",[11,1492,1493],{},"命名空间修正之后，会遇到如下错误",[1474,1495,1496],{},[11,1497,1498],{},"error C2872: “DWORD”: 不明确的符号",[11,1500,1501],{},"在UE4内部有对DWORD进行禁止使用的机制，这里需要重新开启。开启的方法是使用UE4内部提供的机制。",[37,1503,1506],{"className":1504,"code":1505,"language":202},[200],"#include \"AllowWindowsPlatformTypes.h\"\n\n#include \u003Cwindows.h>\n\n#include \"HideWindowsPlatformTypes.h\"\n",[44,1507,1505],{"__ignoreMap":42},[11,1509,1510],{},"这样的话就可以使用DWORD等了。",[11,1512,1513],{},"在编译过程中还会遇到",[1474,1515,1516],{},[11,1517,1518],{},"4310:编译器执行的转换将截断数据",[11,1520,1521],{},"这个错误出现点有点多，直接让编译器忽略比较快。",[37,1523,1526],{"className":1524,"code":1525,"language":202},[200],"#pragma warning(disable: 4310)\n",[44,1527,1525],{"__ignoreMap":42},[11,1529,1530],{},"经过上面的几个步骤，基本就可以正常的通过编译了。只是在打包的时候会有这样的错误：",[1474,1532,1533],{},[11,1534,1535],{},"UnrealBuildTool: ERROR: Couldn’t find referenced module ‘PluginName’",[11,1537,1538],{},"或者",[1474,1540,1541],{},[11,1542,1543],{},"Plugin ‘PluginName’ failed to load because module ‘PluginName’ could not be found. Please ensure the plugin is properly installed, otherwise consider disabling the plugin for this project",[11,1545,1546],{},"这时候需要在Development下对代码进行一次重新编译。",[11,1548,1549],{},"这样的话就可以在Windows下正常的使用了。",[21,1551,1553],{"id":1552},"android平台","Android平台",[11,1555,1556],{},"插件在Android平台打包时会遇到很多编译器相关的错误，需要进行一些修正。",[11,1558,1559],{},"首先遇到的错误是",[1474,1561,1562],{},[11,1563,1564,1565],{},"error : delete called on ‘const TagLib::FileRef::FileTypeResolver’ that is abstract but has non-virtual destructor ",[47,1566,1567],{},"-Werror,-Wdelete-non-virtual-dtor",[11,1569,1570],{},"这个错误原本是warning，据说是taglib为了保证其自身的向下兼容性等原因而遗留的。",[11,1572,1573],{},"因此，修正方法是让编译器忽略这个warning。直接在模块头文件上加上",[37,1575,1578],{"className":1576,"code":1577,"language":202},[200],"#if defined(__clang__)\n\n#pragma clang diagnostic ignored \"-Wdocumentation\"\n\n#pragma clang diagnostic ignored \"-Wshadow\"\n\n#pragma clang diagnostic ignored \"-Wdelete-non-virtual-dtor\"\n\n#pragma clang diagnostic ignored \"-Wunused-function\"\n\n#define HAVE_SNPRINTF\n\n#endif\n",[44,1579,1577],{"__ignoreMap":42},[21,1581,1583],{"id":1582},"rtti","RTTI",[11,1585,1586],{},"整个改造过程最麻烦的问题出在了RTTI上，Taglib内部大量的使用了dynamic_cast进行类型检测，而不是使用virtual来进行函数重载。",[11,1588,1589],{},"而UE4和大多数游戏引擎一样，出于效率原因是禁止使用RTTI的，好在UE4提供了可以单独在插件和项目中打开RTTI的方法。",[11,1591,1592],{},"在插件的.build.cs中添加如下所示的代码即可：",[37,1594,1597],{"className":1595,"code":1596,"language":202},[200],"namespace UnrealBuildTool.Rules\n\n{\n\n    public class TagLibs : ModuleRules\n\n    {\n\n        public TagLibs(TargetInfo Target)\n\n        {\n\n            bUseRTTI = true; \u002F\u002F 打开RTTI\n\n            \u002F\u002F ... ...\n",[44,1598,1596],{"__ignoreMap":42},[11,1600,1601],{},"但是，这个开关对Android是没有作用的，我们需要编辑引擎源码进行修正。在AndroidToolChain.cs中，将所有的",[37,1603,1606],{"className":1604,"code":1605,"language":202},[200],"Result += \" -fno-rtti\";\n",[44,1607,1605],{"__ignoreMap":42},[11,1609,1610],{},"改为",[37,1612,1615],{"className":1613,"code":1614,"language":202},[200],"if (!CompileEnvironment.Config.bUseRTTI) Result += \" -fno-rtti\";\n",[44,1616,1614],{"__ignoreMap":42},[11,1618,1619],{},"混合使用RTTI的情况下需要注意的是，根据编译器的不同，将定义了virtual的多态类型在两种不同的编译方式之间混合使用时，其结果是不可预知的。具体而言可能会出现类似下面的错误：",[1474,1621,1622,1625],{},[11,1623,1624],{},"error: undefined reference to ‘typeinfo for UObject’",[11,1626,1627,1628],{},"error: cannot pass object of non-trivial type ‘FString’ through variadic function; call will abort at runtime ",[47,1629,1630],{},"-Wnon-pod-varargs",[11,1632,1633,1634,1124],{},"结论上，就是在插件中要尽量避免使用UE4内部的类。详细的技术上的原因可以参照",[47,1635,932],{},[21,1637,1638],{"id":1638},"插件使用",[11,1640,1641],{},"插件制作完成后，使用起来就比较简单了。如果要在代码中使用TagLibs插件的话，在.build.cs中添加",[37,1643,1646],{"className":1644,"code":1645,"language":202},[200],"PrivateDependencyModuleNames.AddRange(new string[] { \"TagLibs\" });\n",[44,1647,1645],{"__ignoreMap":42},[11,1649,1650],{},"然后在要使用的类，一般是蓝图函数库的文件中添加包含",[37,1652,1655],{"className":1653,"code":1654,"language":202},[200],"#include \"ITagLibs.h\"\n",[44,1656,1654],{"__ignoreMap":42},[11,1658,1659],{},"以及使用",[37,1661,1664],{"className":1662,"code":1663,"language":202},[200],"ITagLibs::Get().GetMusicInfo(args…)\n",[44,1665,1663],{"__ignoreMap":42},[11,1667,1668],{},"即可。",[11,1670,1671,1672,610],{},"其他详情可参见完成的Taglib插件源码，已上传至[",[604,1673,1676],{"href":1674,"rel":1675},"https:\u002F\u002Fgithub.com\u002Fsteinkrausls\u002FUE4-TagLibs",[608],"Github",{"title":42,"searchDepth":88,"depth":99,"links":1678},[1679,1680,1681,1682,1683,1684],{"id":1402,"depth":88,"text":1403},{"id":1430,"depth":88,"text":1431},{"id":1456,"depth":88,"text":1457},{"id":1552,"depth":88,"text":1553},{"id":1582,"depth":88,"text":1583},{"id":1638,"depth":88,"text":1638},"2015-09-10",{"layout":804,"status":805,"published":338,"author":1687,"author_login":1049,"author_email":1050,"author_url":1051,"wordpress_id":1688,"wordpress_url":1689,"date_gmt":1690,"excerpt":1691},{"display_name":1049,"login":1049,"email":1050,"url":1051},1482,"\u002F\u002F?p=1482","2015-09-10 12:21:07 +0000",{"type":8,"value":1692},[1693],[11,1694,1396],{},"\u002F2015-09-10-ue4-taglib-usage-and-plugin",{"title":1391,"description":1396},"_legacy\u002F2015\u002F2015-09-10-ue4-taglib-usage-and-plugin",[821,1699,1700],"Taglib","插件","V3L6gr5PK2TZukj5tZ6xm2xk6Yp5Xa77uDIKBjvTTc8",85,1788763178354]