[{"data":1,"prerenderedAt":1743},["ShallowReactive",2],{"blog-page-20":3,"blog-count":1742},[4,105,426,1338,1631],{"id":5,"title":6,"body":7,"date":80,"description":13,"extension":81,"meta":82,"navigation":85,"path":97,"seo":98,"stem":99,"tags":100,"__hash__":104},"blogs\u002F_legacy\u002F2015\u002F2015-09-11-uwidget-wrap-swidget-to-umg.md","UWidget封装SWidget到UMG",{"type":8,"value":9,"toc":76},"minimark",[10,14,17,20,23,26,29,32,43,46,49,55,58,61,64,67,70,73],[11,12,13],"p",{},"为了使用UMG中的一些高级或便利特性，需要将制作好的Slate控件封装到UWidget中去。",[11,15,16],{},"当前UE4版本4.8.3。",[11,18,19],{},"将Slate封装到UMG中去有很多的好处，因为在代码中对需要重用的控件进行不断的重新布局是一件非常繁琐的事情。同时，UMG也有在3D空间中进行显示这样的高级功能。",[11,21,22],{},"由于UMG本身就是对Slate的封装，所以这个过程可以参照UE4的源代码进行研究。",[11,24,25],{},"这里所做的是尝试对上一回所做的文件目录树进行封装。",[11,27,28],{},"直接上代码",[11,30,31],{},"CListViewWidget.h",[33,34,39],"pre",{"className":35,"code":37,"language":38},[36],"language-text","#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","text",[40,41,37],"code",{"__ignoreMap":42},"",[11,44,45],{},"封装类直接继承自UWidget即可，剩下的就是对UWidget中的函数进行实现。",[11,47,48],{},"CListViewWidget.cpp",[33,50,53],{"className":51,"code":52,"language":38},[36],"#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",[40,54,52],{"__ignoreMap":42},[11,56,57],{},"在所有的函数中，RebuildWidget()是最重要的。",[11,59,60],{},"参看UWidget的源代码可以发现，所有对被封装的SWidget的引用都是通过这个函数获得的，因此在这里面写上SWidget对应的构造生成就可以了。",[11,62,63],{},"OnBindingChanged函数是用于属性代理通知的，这里是简单的封装，没有提供属性代理，因此直接return也是可以的。",[11,65,66],{},"在WITH_EDITOR宏中的是编辑器属性，用于封装后的UWidget在蓝图编辑器中显示。具体每一个函数的含义已经在注释中了。",[11,68,69],{},"将上面的代码编译通过之后，就可以在UMG的编辑器中看到我们添加的控件了：",[11,71,72],{},"将CListViewWidget拖动到控件蓝图上，并在HUD中展示此控件蓝图，就能在项目中与其他UMG控件一样使用了：",[11,74,75],{},"这样以来Slate的使用就会变得方便很多。",{"title":42,"searchDepth":77,"depth":78,"links":79},2,3,[],"2015-09-11","md",{"layout":83,"status":84,"published":85,"author":86,"author_login":87,"author_email":88,"author_url":89,"wordpress_id":90,"wordpress_url":91,"date_gmt":92,"excerpt":93},"post","publish",true,{"display_name":87,"login":87,"email":88,"url":89},"chaoshikari","chaoshikari@gmail.com","\u002F",1457,"\u002F\u002F?p=1457","2015-09-11 11:11:13 +0000",{"type":8,"value":94},[95],[11,96,13],{},"\u002F2015-09-11-uwidget-wrap-swidget-to-umg",{"title":6,"description":13},"_legacy\u002F2015\u002F2015-09-11-uwidget-wrap-swidget-to-umg",[101,102,103],"UE4","UMG","Slate","mPzjKCXtbWWXkWThfinkYHexD0dpoqD6gvqKCUaR4v0",{"id":106,"title":107,"body":108,"date":409,"description":112,"extension":81,"meta":410,"navigation":85,"path":419,"seo":420,"stem":421,"tags":422,"__hash__":425},"blogs\u002F_legacy\u002F2015\u002F2015-09-10-ue4-taglib-usage-and-plugin.md","UE4中Taglib使用及插件制作",{"type":8,"value":109,"toc":401},[110,113,116,121,124,127,133,136,142,145,149,152,155,166,169,172,175,179,182,188,191,194,200,203,209,212,215,220,223,229,232,235,240,243,249,252,257,260,265,268,271,275,278,281,290,293,296,302,306,309,312,315,321,324,330,333,339,342,353,360,363,366,372,375,381,384,390,393],[11,111,112],{},"UE4使用第三方库时，为了方便跨平台编译、避免冲突和多次复用，将其制作成插件是不错的选择。",[11,114,115],{},"当前UE4版本为4.10.1。",[117,118,120],"h2",{"id":119},"taglib的使用","Taglib的使用",[11,122,123],{},"Taglib是一个用于读取多媒体文件标签的第三方库，之所以会使用到，是因为当前FMod在读取含有非英文的标签会得到错误的结果。",[11,125,126],{},"Taglib需要自己使用CMake进行配置和编译。如果只是在Windows下使用的话，最简单的方法是直接引用生成的.lib文件。在项目的.build.cs中，ModuleRules的实现里面添加：",[33,128,131],{"className":129,"code":130,"language":38},[36],"PublicIncludePaths.Add(\"F:\u002FLibs\u002Ftaglib-master\u002Finclude\u002Ftaglib\");\n\nPublicAdditionalLibraries.Add(\"F:\u002FLibs\u002Ftaglib-master\u002Ftaglib\u002FRelease\u002Ftag.lib\");\n",[40,132,130],{"__ignoreMap":42},[11,134,135],{},"然后在需要使用的地方使用Taglib即可",[33,137,140],{"className":138,"code":139,"language":38},[36],"#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",[40,141,139],{"__ignoreMap":42},[11,143,144],{},"但是这样的方法在进行Android之类的平台上的编译时，会遇到很多麻烦。因此将其制作为UE4插件是最好的解决方法。",[117,146,148],{"id":147},"ue4插件制作","UE4插件制作",[11,150,151],{},"UE4的插件制作一直都处在WIP的状态，直到4.9版本开始，官方文档才变更为了可以使用的状态。因此，为了避免研究了之后官方大规模的更新，到了现在才开始研究。",[11,153,154],{},"UE4插件的制作相对还是比较简单的，在引擎中，官方就已经提供了4个示例插件，以其为基础进行插件开发即可。",[11,156,157,158,165],{},"插件的代码布局和UE4的游戏项目差不多，只有在Public中定义的类对外界才是可见的。详细的插件系统介绍可以参照[",[159,160,164],"a",{"href":161,"rel":162},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FProgramming\u002FPlugins\u002F",[163],"nofollow","官方文档","]。",[11,167,168],{},"插件的描述文件中最重要的地方是Modules中的Type参数，这个参数是决定插件的加载类型的。通常情况下，如果是在实际游戏逻辑中使用的插件的话，这里需要修改为Runtime。",[11,170,171],{},"如果是在项目的Plugins目录中添加插件目录进行开发的话，需要重新生成下项目文件",[11,173,174],{},"这样才能在vs中看到插件。还有比较容易忘记的是，直接继承自UObject的类，必须定义Blueprintable才可以在编辑器中可见。",[117,176,178],{"id":177},"taglib改造","Taglib改造",[11,180,181],{},"将taglib的源代码整个扔到Private目录中去，并在.build.cs中添加包含路径",[33,183,186],{"className":184,"code":185,"language":38},[36],"            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",[40,187,185],{"__ignoreMap":42},[11,189,190],{},"要添加的比较多，主要是因为Taglib本身使用的就是这样的方法，稍微有点麻烦。",[11,192,193],{},"添加完路径之后，首先遇到的错误是",[195,196,197],"blockquote",{},[11,198,199],{},"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,201,202],{},"这个错误的来源乍看之下很奇怪，其实在项目中有时候也会遇到。修正方法是，在cpp文件中首先包含模块头文件。",[33,204,207],{"className":205,"code":206,"language":38},[36],"#include \"TagLibsPrivatePCH.h\"\n",[40,208,206],{"__ignoreMap":42},[11,210,211],{},"添加了上面的文件之后，就会遇到Taglib内部大规模的namespace引发的错误，这一部分只能是一个一个的对命名空间进行控制修改了。",[11,213,214],{},"命名空间修正之后，会遇到如下错误",[195,216,217],{},[11,218,219],{},"error C2872: “DWORD”: 不明确的符号",[11,221,222],{},"在UE4内部有对DWORD进行禁止使用的机制，这里需要重新开启。开启的方法是使用UE4内部提供的机制。",[33,224,227],{"className":225,"code":226,"language":38},[36],"#include \"AllowWindowsPlatformTypes.h\"\n\n#include \u003Cwindows.h>\n\n#include \"HideWindowsPlatformTypes.h\"\n",[40,228,226],{"__ignoreMap":42},[11,230,231],{},"这样的话就可以使用DWORD等了。",[11,233,234],{},"在编译过程中还会遇到",[195,236,237],{},[11,238,239],{},"4310:编译器执行的转换将截断数据",[11,241,242],{},"这个错误出现点有点多，直接让编译器忽略比较快。",[33,244,247],{"className":245,"code":246,"language":38},[36],"#pragma warning(disable: 4310)\n",[40,248,246],{"__ignoreMap":42},[11,250,251],{},"经过上面的几个步骤，基本就可以正常的通过编译了。只是在打包的时候会有这样的错误：",[195,253,254],{},[11,255,256],{},"UnrealBuildTool: ERROR: Couldn’t find referenced module ‘PluginName’",[11,258,259],{},"或者",[195,261,262],{},[11,263,264],{},"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,266,267],{},"这时候需要在Development下对代码进行一次重新编译。",[11,269,270],{},"这样的话就可以在Windows下正常的使用了。",[117,272,274],{"id":273},"android平台","Android平台",[11,276,277],{},"插件在Android平台打包时会遇到很多编译器相关的错误，需要进行一些修正。",[11,279,280],{},"首先遇到的错误是",[195,282,283],{},[11,284,285,286],{},"error : delete called on ‘const TagLib::FileRef::FileTypeResolver’ that is abstract but has non-virtual destructor ",[287,288,289],"span",{},"-Werror,-Wdelete-non-virtual-dtor",[11,291,292],{},"这个错误原本是warning，据说是taglib为了保证其自身的向下兼容性等原因而遗留的。",[11,294,295],{},"因此，修正方法是让编译器忽略这个warning。直接在模块头文件上加上",[33,297,300],{"className":298,"code":299,"language":38},[36],"#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",[40,301,299],{"__ignoreMap":42},[117,303,305],{"id":304},"rtti","RTTI",[11,307,308],{},"整个改造过程最麻烦的问题出在了RTTI上，Taglib内部大量的使用了dynamic_cast进行类型检测，而不是使用virtual来进行函数重载。",[11,310,311],{},"而UE4和大多数游戏引擎一样，出于效率原因是禁止使用RTTI的，好在UE4提供了可以单独在插件和项目中打开RTTI的方法。",[11,313,314],{},"在插件的.build.cs中添加如下所示的代码即可：",[33,316,319],{"className":317,"code":318,"language":38},[36],"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",[40,320,318],{"__ignoreMap":42},[11,322,323],{},"但是，这个开关对Android是没有作用的，我们需要编辑引擎源码进行修正。在AndroidToolChain.cs中，将所有的",[33,325,328],{"className":326,"code":327,"language":38},[36],"Result += \" -fno-rtti\";\n",[40,329,327],{"__ignoreMap":42},[11,331,332],{},"改为",[33,334,337],{"className":335,"code":336,"language":38},[36],"if (!CompileEnvironment.Config.bUseRTTI) Result += \" -fno-rtti\";\n",[40,338,336],{"__ignoreMap":42},[11,340,341],{},"混合使用RTTI的情况下需要注意的是，根据编译器的不同，将定义了virtual的多态类型在两种不同的编译方式之间混合使用时，其结果是不可预知的。具体而言可能会出现类似下面的错误：",[195,343,344,347],{},[11,345,346],{},"error: undefined reference to ‘typeinfo for UObject’",[11,348,349,350],{},"error: cannot pass object of non-trivial type ‘FString’ through variadic function; call will abort at runtime ",[287,351,352],{},"-Wnon-pod-varargs",[11,354,355,356,359],{},"结论上，就是在插件中要尽量避免使用UE4内部的类。详细的技术上的原因可以参照",[287,357,358],{},"这里","。",[117,361,362],{"id":362},"插件使用",[11,364,365],{},"插件制作完成后，使用起来就比较简单了。如果要在代码中使用TagLibs插件的话，在.build.cs中添加",[33,367,370],{"className":368,"code":369,"language":38},[36],"PrivateDependencyModuleNames.AddRange(new string[] { \"TagLibs\" });\n",[40,371,369],{"__ignoreMap":42},[11,373,374],{},"然后在要使用的类，一般是蓝图函数库的文件中添加包含",[33,376,379],{"className":377,"code":378,"language":38},[36],"#include \"ITagLibs.h\"\n",[40,380,378],{"__ignoreMap":42},[11,382,383],{},"以及使用",[33,385,388],{"className":386,"code":387,"language":38},[36],"ITagLibs::Get().GetMusicInfo(args…)\n",[40,389,387],{"__ignoreMap":42},[11,391,392],{},"即可。",[11,394,395,396,165],{},"其他详情可参见完成的Taglib插件源码，已上传至[",[159,397,400],{"href":398,"rel":399},"https:\u002F\u002Fgithub.com\u002Fsteinkrausls\u002FUE4-TagLibs",[163],"Github",{"title":42,"searchDepth":77,"depth":78,"links":402},[403,404,405,406,407,408],{"id":119,"depth":77,"text":120},{"id":147,"depth":77,"text":148},{"id":177,"depth":77,"text":178},{"id":273,"depth":77,"text":274},{"id":304,"depth":77,"text":305},{"id":362,"depth":77,"text":362},"2015-09-10",{"layout":83,"status":84,"published":85,"author":411,"author_login":87,"author_email":88,"author_url":89,"wordpress_id":412,"wordpress_url":413,"date_gmt":414,"excerpt":415},{"display_name":87,"login":87,"email":88,"url":89},1482,"\u002F\u002F?p=1482","2015-09-10 12:21:07 +0000",{"type":8,"value":416},[417],[11,418,112],{},"\u002F2015-09-10-ue4-taglib-usage-and-plugin",{"title":107,"description":112},"_legacy\u002F2015\u002F2015-09-10-ue4-taglib-usage-and-plugin",[101,423,424],"Taglib","插件","V3L6gr5PK2TZukj5tZ6xm2xk6Yp5Xa77uDIKBjvTTc8",{"id":427,"title":428,"body":429,"date":1320,"description":433,"extension":81,"meta":1321,"navigation":85,"path":1333,"seo":1334,"stem":1335,"tags":1336,"__hash__":1337},"blogs\u002F_legacy\u002F2015\u002F2015-08-19-slate-file-browser-tree-view-simple.md","Slate文件目录树基础实现",{"type":8,"value":430,"toc":1315},[431,434,436,439,448,451,454,457,460,463,469,472,476,479,482,485,491,494,500,503,506,509,512,515,518,657,660,1308,1311],[11,432,433],{},"目前UMG中并没有提供TreeView类，要使用TreeView必须使用Slate。",[11,435,16],{},[11,437,438],{},"TreeView在UE4编辑器中被广泛的使用，但是出于某些不可知的原因无法在UMG中进行直接使用。鉴于制作复杂度较高的界面需要使用到Slate，对Slate进行专门的研究是有必要的。",[11,440,441,442,447],{},"本文参照Unreal官方社区文档",[159,443,446],{"href":444,"rel":445},"https:\u002F\u002Fwiki.unrealengine.com\u002FSlate,_Tree_View_Widget,_Ex:_In-Editor_File_Structure_Explorer",[163],"Slate, Tree View Widget","完成。",[11,449,450],{},"请注意：原始文档中并没有给出具体的文件浏览器实现，而是提供了作为其基础的目录树使用方式。",[117,452,453],{"id":453},"目录树数据结构",[11,455,456],{},"要实现目录树并不需要太复杂的数据结构，实现基本的功能即可。",[11,458,459],{},"TreeView中本身需要的数据是数组型的，这里定义的数据结构是节点使用的。",[11,461,462],{},"DDFileTreeItem.h",[33,464,467],{"className":465,"code":466,"language":38},[36],"#pragma once\n\ntypedef TSharedPtr\u003C class FDDFileTreeItem > FDDFileTreeItemPtr;\n\n\u002F**\n* \u003C目录树节点\n*\u002F\n\nclass FDDFileTreeItem\n{\npublic:\n\n\u002F** @return \u003C返回父节点 *\u002F\nconst FDDFileTreeItemPtr GetParentCategory() const\n{\n  return ParentDir.Pin();\n}\n\n\u002F** @return \u003C返回节点的路径【只读】 *\u002F\nconst FString& GetDirectoryPath() const\n{\n  return DirectoryPath;\n}\n\n\u002F** @return \u003C返回显示名称【只读】 *\u002F\nconst FString& GetDisplayName() const\n{\n  return DisplayName;\n}\n\n\u002F** @return \u003C返回子节点【只读】 *\u002F\nconst TArray\u003C FDDFileTreeItemPtr >& GetSubDirectories() const\n{\n  return SubDirectories;\n}\n\n\u002F** @return \u003C返回可写入的子节点引用 *\u002F\nTArray\u003C FDDFileTreeItemPtr >& AccessSubDirectories()\n{\n  return SubDirectories;\n}\n\n\u002F** \u003C为当前节点添加子目录 *\u002F\nvoid AddSubDirectory(const FDDFileTreeItemPtr NewSubDir)\n{\n  SubDirectories.Add(NewSubDir);\n}\n\npublic:\n\u002F** \u003C构造函数 *\u002F\n\nFDDFileTreeItem(const FDDFileTreeItemPtr IN_ParentDir, const FString& IN_DirectoryPath, const FString& IN_DisplayName)\n: ParentDir(IN_ParentDir)\n, DirectoryPath(IN_DirectoryPath)\n, DisplayName(IN_DisplayName)\n{\n\n}\n\nprivate:\n\n\u002F** \u003C父节点引用; *\u002F\nTWeakPtr\u003C FDDFileTreeItem > ParentDir;\n\n\u002F** \u003C完整路径 *\u002F\nFString DirectoryPath;\n\n\u002F** \u003C显示名称 *\u002F\nFString DisplayName;\n\n\u002F** \u003C子节点数组 *\u002F\nTArray\u003C FDDFileTreeItemPtr > SubDirectories;\n\n};\n",[40,468,466],{"__ignoreMap":42},[11,470,471],{},"节点的数据结构较为简单，直接在头文件中对函数进行了实现。",[117,473,475],{"id":474},"目录树slate控件","目录树Slate控件",[11,477,478],{},"由于原始文档中提供的代码中引用的Engine是不必要的，故而在这里直接替换成了较为通用的HUD引用，这样就不会导致代码有项目相关性了。",[11,480,481],{},"这里先上代码，其他的一些说明放在代码后面。",[11,483,484],{},"SDDFileTree.h",[33,486,489],{"className":487,"code":488,"language":38},[36],"#pragma once\n\n\u002F* \u003C节点数据结构 *\u002F\n\n#include \"DDFileTreeItem.h\"\n\ntypedef STreeView\u003C FDDFileTreeItemPtr > SDDFileTreeView;\n\n\u002F**\n* \u003C目录树Slate\n*\u002F\n\nclass SDDFileTree : public SCompoundWidget\n{\n\npublic:\n\nSLATE_BEGIN_ARGS(SDDFileTree){}\n\nSLATE_ARGUMENT(TWeakObjectPtr\u003Cclass AHUD>, OwnerHUD)\n\nSLATE_END_ARGS()\n\npublic:\n\nTWeakObjectPtr\u003CAHUD> OwnerHUD;\n\n\u002F** \u003C刷新目录 *\u002F\n\n\u002F\u002Fbool DoRefresh;\n\npublic:\n\nvoid Construct(const FArguments& InArgs);\n\n\u002F** Destructor *\u002F\n~SDDFileTree();\n\n\u002F** @return \u003C返回当前被选中的目录 *\u002F\nFDDFileTreeItemPtr GetSelectedDirectory() const;\n\n\u002F** \u003C选择目录 *\u002F\nvoid SelectDirectory(const FDDFileTreeItemPtr& CategoryToSelect);\n\n\u002F** @return \u003C返回节点是否展开 *\u002F\nbool IsItemExpanded(const FDDFileTreeItemPtr Item) const;\n\nprivate:\n\n\u002F** \u003C生成单个节点元素 *\u002F\nTSharedRef\u003CITableRow> DDFileTree_OnGenerateRow(FDDFileTreeItemPtr Item, const TSharedRef\u003CSTableViewBase>& OwnerTable);\n\n\u002F** \u003C获得子节点 *\u002F\nvoid DDFileTree_OnGetChildren(FDDFileTreeItemPtr Item, TArray\u003C FDDFileTreeItemPtr >& OutChildren);\n\n\u002F** \u003C当选中项发生变化时 *\u002F\nvoid DDFileTree_OnSelectionChanged(FDDFileTreeItemPtr Item, ESelectInfo::Type SelectInfo);\n\n\u002F** \u003C构建目录树数据 *\u002F\nvoid RebuildFileTree();\n\n\u002F** \u003C重写Tick方便以后实现目录刷新 *\u002F\nvirtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override;\n\nprivate:\n\n\u002F** \u003CTreeView控件 *\u002F\nTSharedPtr\u003C SDDFileTreeView > DDFileTreeView;\n\n\u002F** \u003C目录树的数据 *\u002F\nTArray\u003C FDDFileTreeItemPtr > Directories;\n\n};\n",[40,490,488],{"__ignoreMap":42},[11,492,493],{},"SDDFileTree.cpp",[33,495,498],{"className":496,"code":497,"language":38},[36],"#include \"Test_mp.h\"\n#include \"SDDFileTree.h\"\n#include \"DDFileTreeItem.h\"\n\nvoid SDDFileTree::Construct(const FArguments& InArgs)\n{\n  OwnerHUD = InArgs._OwnerHUD;\n  RebuildFileTree(); \u002F* \u003C构建目录树数据 *\u002F\n\n  \u002F\u002FBuild the tree view of the above core data\n  DDFileTreeView =\n    SNew(SDDFileTreeView)\n    .SelectionMode(ESelectionMode::Single) \u002F\u002F 只允许选中一个项目\n    .ClearSelectionOnClick(false) \u002F\u002F 不允许不选中内容\n    .TreeItemsSource(&Directories)\n    .OnGenerateRow(this, &SDDFileTree::DDFileTree_OnGenerateRow)\n    .OnGetChildren(this, &SDDFileTree::DDFileTree_OnGetChildren)\n    .OnSelectionChanged(this, &SDDFileTree::DDFileTree_OnSelectionChanged)\n;\n\n    \u002F*\n    \u002F\u002F Expand the root  by default\n    for( auto RootDirIt( Directories.CreateConstIterator() ); RootDirIt; ++RootDirIt )\n    {\n      const auto& Dir = *RootDirIt;\n      DDFileTreeView->SetItemExpansion( Dir, true );\n    }\n\n    \u002F\u002F Select the first item by default\n    if( Directories.Num() > 0 )\n    {\n      DDFileTreeView->SetSelection( Directories[ 0 ] );\n    }\n\n    *\u002F\n\n    ChildSlot.AttachWidget(DDFileTreeView.ToSharedRef());\n}\n\nSDDFileTree::~SDDFileTree()\n{\n\n}\n\nvoid SDDFileTree::RebuildFileTree()\n{\n  Directories.Empty();\n  \u002F\u002F~~~~~~~~~~~~~~~~~~~\n  \u002F\u002FRoot Level\n  TSharedRef\u003CFDDFileTreeItem> RootDir = MakeShareable(new FDDFileTreeItem(NULL, TEXT(\"RootDir\"), FString(\"RootDir\")));\n  Directories.Add(RootDir);\n  TSharedRef\u003CFDDFileTreeItem> RootDir2 = MakeShareable(new FDDFileTreeItem(NULL, TEXT(\"RootDir2\"), FString(\"RootDir2\")));\n  Directories.Add(RootDir2);\n  \u002F\u002F~~~~~~~~~~~~~~~~~~~\n\n  \u002F\u002FRoot Category\n  FDDFileTreeItemPtr ParentCategory = RootDir;\n\n  \u002F\u002FAdd\n  FDDFileTreeItemPtr EachSubDir = MakeShareable(new FDDFileTreeItem(ParentCategory, \"Joy\", \"Joy\"));\n  RootDir->AddSubDirectory(EachSubDir);\n\n  \u002F\u002FAdd\n\n  EachSubDir = MakeShareable(new FDDFileTreeItem(ParentCategory, \"Song\", \"Song\"));\n  RootDir->AddSubDirectory(EachSubDir);\n\n  \u002F\u002FAdd\n\n  FDDFileTreeItemPtr SongDir = MakeShareable(new FDDFileTreeItem(ParentCategory, \"Dance\", \"Dance\"));\n  EachSubDir->AddSubDirectory(SongDir);\n\n  \u002F\u002FAdd\n\n  SongDir = MakeShareable(new FDDFileTreeItem(ParentCategory, \"Rainbows\", \"Rainbows\"));\n  EachSubDir->AddSubDirectory(SongDir);\n  \u002F\u002FAdd\n  EachSubDir = MakeShareable(new FDDFileTreeItem(ParentCategory, \"Butterflies\", \"Butterflies\"));\n  RootDir->AddSubDirectory(EachSubDir);\n\n  \u002F\u002FRefresh\n\n  if (DDFileTreeView.IsValid())\n  {\n    DDFileTreeView->RequestTreeRefresh();\n  }\n\n}\n\nTSharedRef\u003CITableRow> SDDFileTree::DDFileTree_OnGenerateRow(FDDFileTreeItemPtr Item, const TSharedRef\u003CSTableViewBase>& OwnerTable)\n{\n  if (!Item.IsValid())\n  {\n    return SNew(STableRow\u003C FDDFileTreeItemPtr >, OwnerTable)\n      [\n        SNew(STextBlock)\n          .Text(NSLOCTEXT(\"Your Namespace\", \"twns\", \"THIS WAS NULL SOMEHOW\"))\n      ];\n\n  }\n\n  return SNew(STableRow\u003C FDDFileTreeItemPtr >, OwnerTable)\n    [\n      SNew(STextBlock)\n        .Text(FText::FromString(Item->GetDisplayName()))\n        .Font(FSlateFontInfo(FPaths::EngineContentDir() \u002F TEXT(\"Slate\u002FFonts\u002FRoboto-Bold.ttf\"), 12))\n        .ColorAndOpacity(FLinearColor(1, 0, 1, 1))\n        .ShadowColorAndOpacity(FLinearColor::Black)\n        .ShadowOffset(FIntPoint(-2, 2))\n    ];\n}\n\nvoid SDDFileTree::DDFileTree_OnGetChildren(FDDFileTreeItemPtr Item, TArray\u003C FDDFileTreeItemPtr >& OutChildren)\n{\n  const auto& SubCategories = Item->GetSubDirectories();\n  OutChildren.Append(SubCategories);\n}\n\n\u002F\u002FKey function for interaction with user!\nvoid SDDFileTree::DDFileTree_OnSelectionChanged(FDDFileTreeItemPtr Item, ESelectInfo::Type SelectInfo)\n{\n  \u002F\u002FSelection Changed!\n  UE_LOG(LogTemp, Warning, TEXT(\"Item Selected: %s\"), *Item->GetDisplayName());\n}\n\nFDDFileTreeItemPtr SDDFileTree::GetSelectedDirectory() const\n{\n  if (DDFileTreeView.IsValid())\n  {\n     auto SelectedItems = DDFileTreeView->GetSelectedItems();\n     if (SelectedItems.Num() > 0)\n     {\n       const auto& SelectedCategoryItem = SelectedItems[0];\n       return SelectedCategoryItem;\n     }\n\n  }\n  return NULL;\n}\n\nvoid SDDFileTree::SelectDirectory(const FDDFileTreeItemPtr& CategoryToSelect)\n{\n  if (ensure(DDFileTreeView.IsValid()))\n  {\n      DDFileTreeView->SetSelection(CategoryToSelect);\n  }\n}\n\n\u002F\u002Fis the tree item expanded to show children?\nbool SDDFileTree::IsItemExpanded(const FDDFileTreeItemPtr Item) const\n{\n  return DDFileTreeView->IsItemExpanded(Item);\n}\n\nvoid SDDFileTree::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)\n{\n  \u002F\u002F Call parent implementation\n  SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);\n  \u002F\u002Fcan do things here every tick\n}\n",[40,499,497],{"__ignoreMap":42},[11,501,502],{},"代码的结构还是比较清晰的，这里需要注意的是，针对TreeView的事件是在控件本身生成的时候注册的。",[11,504,505],{},"其中，OnGenerateRow事件是STreeView和SListView的必须事件。当控件需要进行元素显示时，会对这个函数进行调用，获得需要显示的元素。",[11,507,508],{},"另外，代码中的OnSelectionChanged函数缺乏必要的安全检测，在使用中需要留意。",[11,510,511],{},"用于显示的数据是在Construct中通过调用RebuildFileTree()函数生成的。",[117,513,514],{"id":514},"最终结果",[11,516,517],{},"完成了上面的步骤之后，直接在HUD中对控件进行展示即可。",[33,519,523],{"className":520,"code":521,"language":522,"meta":42,"style":42},"language-cpp shiki shiki-themes github-light-high-contrast github-dark monokai","SAssignNew(DDFileTree, SDDFileTree).OwnerHUD(this);\n\nif (GEngine->IsValidLowLevel())\n{\n  GEngine->GameViewport->AddViewportWidgetContent(SNew(SWeakWidget).PossiblyNullContent(DDFileTree.ToSharedRef()));\n}\n\nif (DDFileTree.IsValid())\n{\n  DDFileTree->SetVisibility(EVisibility::Visible);\n}\n","cpp",[40,524,525,550,555,570,576,605,611,616,629,634,652],{"__ignoreMap":42},[287,526,529,533,537,540,543,547],{"class":527,"line":528},"line",1,[287,530,532],{"class":531},"scAT4","SAssignNew",[287,534,536],{"class":535},"s0idv","(DDFileTree, SDDFileTree).",[287,538,539],{"class":531},"OwnerHUD",[287,541,542],{"class":535},"(",[287,544,546],{"class":545},"s4Wjf","this",[287,548,549],{"class":535},");\n",[287,551,552],{"class":527,"line":77},[287,553,554],{"emptyLinePlaceholder":85},"\n",[287,556,557,561,564,567],{"class":527,"line":78},[287,558,560],{"class":559},"sLXdl","if",[287,562,563],{"class":535}," (GEngine->",[287,565,566],{"class":531},"IsValidLowLevel",[287,568,569],{"class":535},"())\n",[287,571,573],{"class":527,"line":572},4,[287,574,575],{"class":535},"{\n",[287,577,579,582,585,587,590,593,596,599,602],{"class":527,"line":578},5,[287,580,581],{"class":535},"  GEngine->GameViewport->",[287,583,584],{"class":531},"AddViewportWidgetContent",[287,586,542],{"class":535},[287,588,589],{"class":531},"SNew",[287,591,592],{"class":535},"(SWeakWidget).",[287,594,595],{"class":531},"PossiblyNullContent",[287,597,598],{"class":535},"(DDFileTree.",[287,600,601],{"class":531},"ToSharedRef",[287,603,604],{"class":535},"()));\n",[287,606,608],{"class":527,"line":607},6,[287,609,610],{"class":535},"}\n",[287,612,614],{"class":527,"line":613},7,[287,615,554],{"emptyLinePlaceholder":85},[287,617,619,621,624,627],{"class":527,"line":618},8,[287,620,560],{"class":559},[287,622,623],{"class":535}," (DDFileTree.",[287,625,626],{"class":531},"IsValid",[287,628,569],{"class":535},[287,630,632],{"class":527,"line":631},9,[287,633,575],{"class":535},[287,635,637,640,643,645,649],{"class":527,"line":636},10,[287,638,639],{"class":535},"  DDFileTree->",[287,641,642],{"class":531},"SetVisibility",[287,644,542],{"class":535},[287,646,648],{"class":647},"sfUV7","EVisibility",[287,650,651],{"class":535},"::Visible);\n",[287,653,655],{"class":527,"line":654},11,[287,656,610],{"class":535},[11,658,659],{},"启动预览，由于没有进行布局，可以在最上方看到目录树的展示。",[11,661,662,663,1307],{},"![U10",[287,664,667,878],{"className":665},[666],"katex",[287,668,671],{"className":669},[670],"katex-mathml",[672,673,675],"math",{"xmlns":674},"http:\u002F\u002Fwww.w3.org\u002F1998\u002FMath\u002FMathML",[676,677,678,873],"semantics",{},[679,680,681,690,693,697,700,703,706,714,717,720,725,728,731,734,736,739,741,743,746,749,751,754,757,760,763,766,769,771,773,775,778,780,783,785,787,790,793,795,798,800,803,805,807,814,816,818,820,822,824,830,832,835,837,839,845,848,850,853,856,859,861,863,866,869,871],"mrow",{},[682,683,684,686],"msub",{},[679,685],{},[687,688,689],"mi",{},"I",[687,691,692],{},"A",[694,695,696],"mn",{},"57",[687,698,699],{},"U",[687,701,702],{},"R",[687,704,705],{},"M",[682,707,708,711],{},[687,709,710],{},"V",[687,712,713],{},"P",[687,715,716],{},"O",[694,718,719],{},"0",[721,722,724],"mo",{"stretchy":723},"false","]",[694,726,727],{},"6",[721,729,730],{"stretchy":723},"[",[687,732,733],{},"J",[687,735,699],{},[687,737,738],{},"C",[721,740,724],{"stretchy":723},[721,742,542],{"stretchy":723},[687,744,89],{"mathvariant":745},"normal",[687,747,748],{},"w",[687,750,11],{},[721,752,753],{},"−",[687,755,756],{},"c",[687,758,759],{},"o",[687,761,762],{},"n",[687,764,765],{},"t",[687,767,768],{},"e",[687,770,762],{},[687,772,765],{},[687,774,89],{"mathvariant":745},[687,776,777],{},"u",[687,779,11],{},[687,781,782],{},"l",[687,784,759],{},[687,786,159],{},[687,788,789],{},"d",[687,791,792],{},"s",[687,794,89],{"mathvariant":745},[694,796,797],{},"2017",[687,799,89],{"mathvariant":745},[694,801,802],{},"03",[687,804,89],{"mathvariant":745},[687,806,699],{},[682,808,809,812],{},[694,810,811],{},"10",[687,813,689],{},[687,815,692],{},[694,817,696],{},[687,819,699],{},[687,821,702],{},[687,823,705],{},[682,825,826,828],{},[687,827,710],{},[687,829,713],{},[687,831,716],{},[694,833,834],{},"06",[687,836,733],{},[687,838,699],{},[682,840,841,843],{},[687,842,738],{},[687,844,765],{},[687,846,847],{},"h",[687,849,777],{},[687,851,852],{},"m",[687,854,855],{},"b",[687,857,858],{"mathvariant":745},".",[687,860,11],{},[687,862,762],{},[687,864,865],{},"g",[687,867,868],{"mathvariant":745},"\"",[687,870,699],{},[694,872,811],{},[874,875,877],"annotation",{"encoding":876},"application\u002Fx-tex","_IA57URMV_PO0]6[JUC](\u002Fwp-content\u002Fuploads\u002F2017\u002F03\u002FU10_IA57URMV_PO06JUC_thumb.png \"U10",[287,879,883,1066],{"className":880,"ariaHidden":882},[881],"katex-html","true",[287,884,887,892,950,953,956,960,964,967,1010,1014,1017,1021,1024,1028,1032,1035,1039,1042,1045,1048,1052,1055,1059,1063],{"className":885},[886],"base",[287,888],{"className":889,"style":891},[890],"strut","height:1em;vertical-align:-0.25em;",[287,893,896,898],{"className":894},[895],"mord",[287,897],{},[287,899,902],{"className":900},[901],"msupsub",[287,903,907,941],{"className":904},[905,906],"vlist-t","vlist-t2",[287,908,911,936],{"className":909},[910],"vlist-r",[287,912,916],{"className":913,"style":915},[914],"vlist","height:0.3283em;",[287,917,919,924],{"style":918},"top:-2.55em;margin-right:0.05em;",[287,920],{"className":921,"style":923},[922],"pstrut","height:2.7em;",[287,925,931],{"className":926},[927,928,929,930],"sizing","reset-size6","size3","mtight",[287,932,689],{"className":933,"style":935},[895,934,930],"mathnormal","margin-right:0.0785em;",[287,937,940],{"className":938},[939],"vlist-s","​",[287,942,944],{"className":943},[910],[287,945,948],{"className":946,"style":947},[914],"height:0.15em;",[287,949],{},[287,951,692],{"className":952},[895,934],[287,954,696],{"className":955},[895],[287,957,699],{"className":958,"style":959},[895,934],"margin-right:0.109em;",[287,961,702],{"className":962,"style":963},[895,934],"margin-right:0.0077em;",[287,965,705],{"className":966,"style":959},[895,934],[287,968,970,974],{"className":969},[895],[287,971,710],{"className":972,"style":973},[895,934],"margin-right:0.2222em;",[287,975,977],{"className":976},[901],[287,978,980,1002],{"className":979},[905,906],[287,981,983,999],{"className":982},[910],[287,984,986],{"className":985,"style":915},[914],[287,987,989,992],{"style":988},"top:-2.55em;margin-left:-0.2222em;margin-right:0.05em;",[287,990],{"className":991,"style":923},[922],[287,993,995],{"className":994},[927,928,929,930],[287,996,713],{"className":997,"style":998},[895,934,930],"margin-right:0.1389em;",[287,1000,940],{"className":1001},[939],[287,1003,1005],{"className":1004},[910],[287,1006,1008],{"className":1007,"style":947},[914],[287,1009],{},[287,1011,716],{"className":1012,"style":1013},[895,934],"margin-right:0.0278em;",[287,1015,719],{"className":1016},[895],[287,1018,724],{"className":1019},[1020],"mclose",[287,1022,727],{"className":1023},[895],[287,1025,730],{"className":1026},[1027],"mopen",[287,1029,733],{"className":1030,"style":1031},[895,934],"margin-right:0.0962em;",[287,1033,699],{"className":1034,"style":959},[895,934],[287,1036,738],{"className":1037,"style":1038},[895,934],"margin-right:0.0715em;",[287,1040,724],{"className":1041},[1020],[287,1043,542],{"className":1044},[1027],[287,1046,89],{"className":1047},[895],[287,1049,748],{"className":1050,"style":1051},[895,934],"margin-right:0.0269em;",[287,1053,11],{"className":1054},[895,934],[287,1056],{"className":1057,"style":973},[1058],"mspace",[287,1060,753],{"className":1061},[1062],"mbin",[287,1064],{"className":1065,"style":973},[1058],[287,1067,1069,1072,1076,1079,1082,1085,1088,1091,1094,1097,1102,1105,1108,1111,1114,1118,1121,1125,1166,1169,1172,1175,1178,1181,1221,1224,1227,1230,1233,1275,1278,1281,1285,1288,1291,1294,1298,1301,1304],{"className":1068},[886],[287,1070],{"className":1071,"style":891},[890],[287,1073,1075],{"className":1074},[895,934],"co",[287,1077,762],{"className":1078},[895,934],[287,1080,765],{"className":1081},[895,934],[287,1083,768],{"className":1084},[895,934],[287,1086,762],{"className":1087},[895,934],[287,1089,765],{"className":1090},[895,934],[287,1092,89],{"className":1093},[895],[287,1095,777],{"className":1096},[895,934],[287,1098,1101],{"className":1099,"style":1100},[895,934],"margin-right:0.0197em;","pl",[287,1103,759],{"className":1104},[895,934],[287,1106,159],{"className":1107},[895,934],[287,1109,789],{"className":1110},[895,934],[287,1112,792],{"className":1113},[895,934],[287,1115,1117],{"className":1116},[895],"\u002F2017\u002F03\u002F",[287,1119,699],{"className":1120,"style":959},[895,934],[287,1122,1124],{"className":1123},[895],"1",[287,1126,1128,1131],{"className":1127},[895],[287,1129,719],{"className":1130},[895],[287,1132,1134],{"className":1133},[901],[287,1135,1137,1158],{"className":1136},[905,906],[287,1138,1140,1155],{"className":1139},[910],[287,1141,1143],{"className":1142,"style":915},[914],[287,1144,1146,1149],{"style":1145},"top:-2.55em;margin-left:0em;margin-right:0.05em;",[287,1147],{"className":1148,"style":923},[922],[287,1150,1152],{"className":1151},[927,928,929,930],[287,1153,689],{"className":1154,"style":935},[895,934,930],[287,1156,940],{"className":1157},[939],[287,1159,1161],{"className":1160},[910],[287,1162,1164],{"className":1163,"style":947},[914],[287,1165],{},[287,1167,692],{"className":1168},[895,934],[287,1170,696],{"className":1171},[895],[287,1173,699],{"className":1174,"style":959},[895,934],[287,1176,702],{"className":1177,"style":963},[895,934],[287,1179,705],{"className":1180,"style":959},[895,934],[287,1182,1184,1187],{"className":1183},[895],[287,1185,710],{"className":1186,"style":973},[895,934],[287,1188,1190],{"className":1189},[901],[287,1191,1193,1213],{"className":1192},[905,906],[287,1194,1196,1210],{"className":1195},[910],[287,1197,1199],{"className":1198,"style":915},[914],[287,1200,1201,1204],{"style":988},[287,1202],{"className":1203,"style":923},[922],[287,1205,1207],{"className":1206},[927,928,929,930],[287,1208,713],{"className":1209,"style":998},[895,934,930],[287,1211,940],{"className":1212},[939],[287,1214,1216],{"className":1215},[910],[287,1217,1219],{"className":1218,"style":947},[914],[287,1220],{},[287,1222,716],{"className":1223,"style":1013},[895,934],[287,1225,834],{"className":1226},[895],[287,1228,733],{"className":1229,"style":1031},[895,934],[287,1231,699],{"className":1232,"style":959},[895,934],[287,1234,1236,1239],{"className":1235},[895],[287,1237,738],{"className":1238,"style":1038},[895,934],[287,1240,1242],{"className":1241},[901],[287,1243,1245,1267],{"className":1244},[905,906],[287,1246,1248,1264],{"className":1247},[910],[287,1249,1252],{"className":1250,"style":1251},[914],"height:0.2806em;",[287,1253,1255,1258],{"style":1254},"top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;",[287,1256],{"className":1257,"style":923},[922],[287,1259,1261],{"className":1260},[927,928,929,930],[287,1262,765],{"className":1263},[895,934,930],[287,1265,940],{"className":1266},[939],[287,1268,1270],{"className":1269},[910],[287,1271,1273],{"className":1272,"style":947},[914],[287,1274],{},[287,1276,847],{"className":1277},[895,934],[287,1279,777],{"className":1280},[895,934],[287,1282,1284],{"className":1283},[895,934],"mb",[287,1286,858],{"className":1287},[895],[287,1289,11],{"className":1290},[895,934],[287,1292,762],{"className":1293},[895,934],[287,1295,865],{"className":1296,"style":1297},[895,934],"margin-right:0.0359em;",[287,1299,868],{"className":1300},[895],[287,1302,699],{"className":1303,"style":959},[895,934],[287,1305,811],{"className":1306},[895],"_IA57URMV_PO0]6[JUC\")",[11,1309,1310],{},"至此，目录树的基本功能已经实现。",[1312,1313,1314],"style",{},"html pre.shiki code .scAT4, html code.shiki .scAT4{--shiki-default:#622CBC;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .s0idv, html code.shiki .s0idv{--shiki-default:#0E1116;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .s4Wjf, html code.shiki .s4Wjf{--shiki-default:#023B95;--shiki-dark:#79B8FF;--shiki-sepia:#FD971F}html pre.shiki code .sLXdl, html code.shiki .sLXdl{--shiki-default:#A0111F;--shiki-dark:#F97583;--shiki-sepia:#F92672}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 .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":42,"searchDepth":77,"depth":78,"links":1316},[1317,1318,1319],{"id":453,"depth":77,"text":453},{"id":474,"depth":77,"text":475},{"id":514,"depth":77,"text":514},"2015-08-19",{"layout":83,"status":84,"published":85,"author":1322,"author_login":1324,"author_email":1325,"wordpress_id":1326,"wordpress_url":1327,"date_gmt":1328,"excerpt":1329},{"display_name":1323,"login":1324,"email":1325,"url":42},"风铃","flinkor","flinkor@foxmail.com",1542,"\u002F\u002F?p=1542","2015-08-19 06:52:43 +0000",{"type":8,"value":1330},[1331],[11,1332,433],{},"\u002F2015-08-19-slate-file-browser-tree-view-simple",{"title":428,"description":433},"_legacy\u002F2015\u002F2015-08-19-slate-file-browser-tree-view-simple",[101,103],"AHchideoQOxz25BWavPLnfqJsUoUsDtNrYhTtEf97xw",{"id":1339,"title":1340,"body":1341,"date":1320,"description":1345,"extension":81,"meta":1616,"navigation":85,"path":1625,"seo":1626,"stem":1627,"tags":1628,"__hash__":1630},"blogs\u002F_legacy\u002F2015\u002F2015-08-19-ue4-cel-look.md","UE4赛璐珞风格",{"type":8,"value":1342,"toc":1605},[1343,1346,1349,1362,1365,1368,1374,1377,1380,1399,1402,1563,1567,1570,1573,1576,1579,1583,1588,1591,1595,1598,1602],[11,1344,1345],{},"UE4默认的渲染风格是写实的，以照片级为目标。而如果要做赛璐珞风格的话，就需要稍微做一些改动。",[11,1347,1348],{},"当前UE4版本4.10.0。",[11,1350,1351,1352,1356,1357,1361],{},"3D的赛璐珞风格实现是有一些现成的经验的，最近有些番组也是用的3D做的。不过作为对图形学方面的知识是外行的人而言，要自己实现还是有很多困难的。好在，已经有人将赛璐珞风格在UE4中实现了。原文的话在[",[159,1353,358],{"href":1354,"rel":1355},"https:\u002F\u002Fforums.unrealengine.com\u002Fshowthread.php?88581-%E3%82%A2%E3%83%B3%E3%83%AA%E3%82%A2%E3%83%AB%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%B34%E3%81%A7%E3%83%8E%E3%83%B3%E3%83%95%E3%82%A9%E3%83%88%E3%83%AA%E3%82%A2%E3%83%AB%E6%8F%8F%E7%94%BB%E3%81%97%E3%82%88%E3%81%86%EF%BC%81",[163],"]，还有对应的PPT，里面有对相应的原理的说明。PPT的下载地址在原文中有提供，点击[",[159,1358,358],{"href":1359,"rel":1360},"https:\u002F\u002Fwww.slideshare.net\u002FEpicGamesJapan\u002F4-unreal-fest-2015-yokohama-54129845",[163],"]可以直达。",[117,1363,1364],{"id":1364},"最终效果",[11,1366,1367],{},"对参数进行少许的调整之后，最终的效果是这样的：",[11,1369,1370],{},[1371,1372],"img",{"alt":42,"src":1373},"\u002Fwp-content\u002Fuploads\u002F2017\u002F03\u002FphpiU9u6l.1453546682-1.png",[11,1375,1376],{},"在没有做精细调整的情况下，这样的效果基本上算是让人满意了。使用上也非常的方便，如果有在项目中使用的话，一定要记得在原文中感谢作者哦~下面对在使用过程中可能会使用到的一些东西进行总结，其中几乎所有内容都是直接翻译的，如有错误欢迎指正。",[117,1378,1379],{"id":1379},"使用步骤",[1381,1382,1383,1387,1390,1393,1396],"ol",{},[1384,1385,1386],"li",{},"由PP_NPR生成新的材质实例（也可以使用生成的PPI_NPR），将PostProcessVolume的Blendables中添加刚刚生成的材质实例即可",[1384,1388,1389],{},"控制渲染效果的参数位于NPR_ParamCollection中",[1384,1391,1392],{},"控制皮肤的亮度（在SkeletalMesh编辑器中将头发等的投影关闭）",[1384,1394,1395],{},"从M_CharacterBase创建材质实例并应用于角色",[1384,1397,1398],{},"将DirectionalLight替换为BP_DirectionalLightForCharacter(与上面的材质合同工作)",[117,1400,1401],{"id":1401},"参数相关",[1403,1404,1405,1421],"table",{},[1406,1407,1408],"thead",{},[1409,1410,1411,1415,1418],"tr",{},[1412,1413,1414],"th",{},"名称",[1412,1416,1417],{},"默认值",[1412,1419,1420],{},"说明",[1422,1423,1424,1435,1446,1457,1468,1479,1489,1499,1510,1520,1530,1541,1552],"tbody",{},[1409,1425,1426,1430,1432],{},[1427,1428,1429],"td",{},"DebugWipe",[1427,1431,719],{},[1427,1433,1434],{},"屏幕分割，对效果进行调试对比用",[1409,1436,1437,1440,1443],{},[1427,1438,1439],{},"LightIntensityBias",[1427,1441,1442],{},"0.3",[1427,1444,1445],{},"场景较为暗时可以对暗的部分进行",[1409,1447,1448,1451,1454],{},[1427,1449,1450],{},"LightIntensityStepping",[1427,1452,1453],{},"2",[1427,1455,1456],{},"Cel涂色步进，小于1会出现不可预知的表现",[1409,1458,1459,1462,1465],{},[1427,1460,1461],{},"FresnelFunc_ContrastDark",[1427,1463,1464],{},"0.1",[1427,1466,1467],{},"用于侧面判定的Fresel的暗面控制参数",[1409,1469,1470,1473,1476],{},[1427,1471,1472],{},"FresnelFunc_ContrastBright",[1427,1474,1475],{},"1.5",[1427,1477,1478],{},"用于侧面判定的Fresel的亮面控制参数",[1409,1480,1481,1484,1486],{},[1427,1482,1483],{},"EdgeLineThickness",[1427,1485,1475],{},[1427,1487,1488],{},"轮廓线的粗细",[1409,1490,1491,1494,1496],{},[1427,1492,1493],{},"CreaseLineThickness",[1427,1495,1475],{},[1427,1497,1498],{},"棱线的粗细（有时设定为负值，在法线方向上取样时会有好的效果）",[1409,1500,1501,1504,1507],{},[1427,1502,1503],{},"FlankLine",[1427,1505,1506],{},"0.2",[1427,1508,1509],{},"侧面线颜色叠加系数（0为全黑，0.5为原本的颜色的0.5）",[1409,1511,1512,1515,1517],{},[1427,1513,1514],{},"EdgeLine",[1427,1516,1506],{},[1427,1518,1519],{},"轮廓线颜色叠加系数（0为全黑，0.5为原本的颜色的0.5）",[1409,1521,1522,1525,1527],{},[1427,1523,1524],{},"CreaseLine",[1427,1526,1506],{},[1427,1528,1529],{},"棱线颜色叠加系数（0为全黑，0.5为原本的颜色的0.5）",[1409,1531,1532,1535,1538],{},[1427,1533,1534],{},"LineColor",[1427,1536,1537],{},"(0.8, 0.084908, 0.045, 0)",[1427,1539,1540],{},"以上三种线的颜色，Alpha值代表叠加颜色的程度，可以超限以达到发光效果",[1409,1542,1543,1546,1549],{},[1427,1544,1545],{},"CharacterLightVector",[1427,1547,1548],{},"(0, 0, -1, 0)",[1427,1550,1551],{},"角色表情等会用到的光照向量（建议直接复制DirectionalLight的向量）",[1409,1553,1554,1557,1560],{},[1427,1555,1556],{},"CharacterLightColor",[1427,1558,1559],{},"(1, 1, 1, 0)",[1427,1561,1562],{},"（当前未使用：角色用光照颜色）",[117,1564,1566],{"id":1565},"m_characterbase相关","M_CharacterBase相关",[11,1568,1569],{},"一般情况下只要利用Postprocess Materia就可以达到效果了，当角色由于阴影的影响过强导致着色浑浊现象出现时，建议使用M_CharaterBase作为对象材质的基础生成材质实例，并在该材质实例中进行属性的调整。",[11,1571,1572],{},"如果材质的贴图中本来就有阴影等信息的话，可以在材质实例宏将DiffuseBase调整为0.01使得BaseColor接近于0，将DiffuseEmissive设定为1.0以将阴影效果降到最低。",[11,1574,1575],{},"对于追求与现实系相近的中间型的着色效果的情况，可以将材质实例中的Shading Mode更改为Subsurface，这样以来就可以通过对SubsurfaceColor的调整来对颜色效果进行微调。",[11,1577,1578],{},"在有发光部件的情况下，勾选UseEmissiveTexture，并将发光部件的贴图彷如EmissiveTexture中，同时设定EmmissiveScale为很大的值就可以达到发光的效果了。",[117,1580,1582],{"id":1581},"faq","FAQ",[1584,1585,1587],"h3",{"id":1586},"q-想要关闭掉角色的反光效果","Q. 想要关闭掉角色的反光效果",[11,1589,1590],{},"A. 在PostProcess的内部，可以某种程度的削弱反光效果，但是想要完全取消掉的话很困难，因为会增加额外的运算负担。对原有的材质的Roughness进行上调以及下调Metalic的值可以达到类似与消除反光的效果。这样的话就可以对模型的效果进行个别控制，一些闪闪发光的材质也能保留原来的效果。",[1584,1592,1594],{"id":1593},"q-将漫反射的贴图变得平滑不是更能实现动画风格吗","Q. 将漫反射的贴图变得平滑不是更能实现动画风格吗？",[11,1596,1597],{},"A. 虽然对相近的像素进行采样并平均的话能够达到平滑的效果，但是以贴图为采样目标的话运算负担就会过重，推荐直接对原有的材质进行修改。这样也可以实现对不同材质的控制和调整。",[1584,1599,1601],{"id":1600},"q-角色激烈运动时会导致线条杂乱","Q. 角色激烈运动时会导致线条杂乱",[11,1603,1604],{},"A. 这是Temporal AA的影响导致的。在PostProcessVolume-Settings-Misc中有AA Method的属性，将其设定为FXAA可以有效的防止这种情况（静画的情况下Temporal AA的画面质量更好一些）。另外，如果是面向在线游戏的画质的话，通过将Screen Percentage设定为200与Temporar AA进行组合，或者将AA关闭（设定为FXAA亦可）以2×2倍的分辨率渲染等方法将渲染目标缩小的方法也是可以的。",{"title":42,"searchDepth":77,"depth":78,"links":1606},[1607,1608,1609,1610,1611],{"id":1364,"depth":77,"text":1364},{"id":1379,"depth":77,"text":1379},{"id":1401,"depth":77,"text":1401},{"id":1565,"depth":77,"text":1566},{"id":1581,"depth":77,"text":1582,"children":1612},[1613,1614,1615],{"id":1586,"depth":78,"text":1587},{"id":1593,"depth":78,"text":1594},{"id":1600,"depth":78,"text":1601},{"layout":83,"status":84,"published":85,"author":1617,"author_login":87,"author_email":88,"author_url":89,"wordpress_id":1618,"wordpress_url":1619,"date_gmt":1620,"excerpt":1621},{"display_name":87,"login":87,"email":88,"url":89},1495,"\u002F\u002F?p=1495","2015-08-19 01:03:52 +0000",{"type":8,"value":1622},[1623],[11,1624,1345],{},"\u002F2015-08-19-ue4-cel-look",{"title":1340,"description":1345},"_legacy\u002F2015\u002F2015-08-19-ue4-cel-look",[101,1629],"Materia","-YGfnoTBCuFUJHxFFE3zFTNTplzJ2UdvV1LeiYnQOn4",{"id":1632,"title":1633,"body":1634,"date":1320,"description":1638,"extension":81,"meta":1726,"navigation":85,"path":1735,"seo":1736,"stem":1737,"tags":1738,"__hash__":1741},"blogs\u002F_legacy\u002F2015\u002F2015-08-19-ue4-fmod-usage-in-cpp.md","UE4中C++模式FMod的使用",{"type":8,"value":1635,"toc":1721},[1636,1639,1642,1645,1648,1651,1654,1660,1663,1669,1672,1675,1679,1682,1688,1691,1694,1697,1703,1706,1712,1715],[11,1637,1638],{},"FMod在为UE4提供音效便利的同时，有提供底层的API来方便更加详尽的需求。",[11,1640,1641],{},"当前UE4版本4.9.2；FMod版本1.07.00。",[11,1643,1644],{},"目前版本下对FMod的使用和之前进行UE4与FMod音乐可视化验证的时候并没有什么不同，这里主要是总结下使用中遇到的问题。",[117,1646,1647],{"id":1647},"编码问题",[11,1649,1650],{},"编码问题主要出在中文路径的处理上，UE4内部使用的是UTF-16编码。",[11,1652,1653],{},"因此，将通过Save&Load之类保存起来的路径提供给FMod使用的时候，需要对字符编码进行转换。",[33,1655,1658],{"className":1656,"code":1657,"language":38},[36],"\u002F\u002F 传入参数：const FString& FileFullPath\n\nstd::stringstream dts;\ndts \u003C\u003C TCHAR_TO_UTF8(*FileFullPath);\nresult = system->createStream(dts.str().c_str(), FMOD_LOOP_NORMAL | FMOD_2D, 0, &sound);\n",[40,1659,1657],{"__ignoreMap":42},[11,1661,1662],{},"关于字符编码，在使用Tag系统的时候还是会遇到。",[33,1664,1667],{"className":1665,"code":1666,"language":38},[36],"int NumTags;\nresult = sound_to_analyse->getNumTags(&NumTags, NULL);\nif (result != FMOD_RESULT::FMOD_OK) break;\nif (NumTags \u003C= 0) break;\nFMOD_TAG MusicTag;\nfor (int mTag = 0; mTag \u003C NumTags; ++mTag){ sound_to_analyse->getTag(NULL, mTag, &MusicTag);\nstd::stringstream ts;\nts \u003C\u003C \"Tag:\";\nts \u003C\u003C MusicTag.name;\nts \u003C\u003C \" Type:\";\nts \u003C\u003C MusicTag.type;\nts \u003C\u003C \" Data:\";\nts \u003C\u003C std::string((char*)MusicTag.data);\nts \u003C\u003C \" DataLength:\";\nts \u003C\u003C MusicTag.datalen;\nts \u003C\u003C \"\\n\"; OutputDebugStringA(ts.str().c_str()); } result = sound_to_analyse->getTag(\"TITLE\", 0, &MusicTag);\n",[40,1668,1666],{"__ignoreMap":42},[11,1670,1671],{},"这里目前似乎是有BUG存在的，通过getTag对音乐的标签进行读取时，如果标签是非英文字符的话，读取出来的字符编码无法被正常的解析。并不是读写方式的问题，通过断点直接查看内存的话会看到其中的编码不符合文档中所描述的编码格式。",[11,1673,1674],{},"因此建议音乐的预览图片使用FMod来读取APIC的tag，而其他的tag使用别的库进行读取，例如使用TagLib。",[117,1676,1678],{"id":1677},"apic图片读取","APIC图片读取",[11,1680,1681],{},"音乐文件的预览图片信息一般存储于APIC标签中，要将读取出的图片数据展示到界面上。可以借助ImageWrapper和SImage来实现。",[33,1683,1686],{"className":1684,"code":1685,"language":38},[36],"\u002F\u002F 变量定义\nTSharedPtr ImageTitle;\nIImageWrapperPtr ImageWrapper;\nUTexture2D* mImageHolder;\nFSlateDynamicImageBrush* mSlateDyn;\n\u002F\u002F 实际读取\nresult = sound_to_analyse->getTag(\"APIC\", 0, &MusicTag);\nif (result != FMOD_RESULT::FMOD_OK) break;\nTArray RawFloatImgData;\nRawFloatImgData.AddUninitialized(MusicTag.datalen);\nFMemory::Memcpy(RawFloatImgData.GetData(), MusicTag.data, MusicTag.datalen*sizeof(uint8));\nEImageFormat::Type MusicFormat = EImageFormat::PNG;\nchar tc = RawFloatImgData[1];\nif (tc == 'i'){\ntc = RawFloatImgData[7];\nswitch (tc)\n{\ncase 'j':\ncase 'J':\nMusicFormat = EImageFormat::JPEG;\nbreak;\ncase 'b':\ncase 'B':\nMusicFormat = EImageFormat::BMP;\nbreak;\ndefault:\nbreak;\n}\n}\nIImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked(FName(\"ImageWrapper\"));\nImageWrapper = ImageWrapperModule.CreateImageWrapper(MusicFormat);\nif (!ImageWrapper.IsValid()) break;\nif (!ImageWrapper->SetCompressed(RawFloatImgData.GetData(), RawFloatImgData.Num())) break;\nOutputDebugStringA(\"APIC Read Success\\n\");\nconst TArray* UncompressedBGRA = NULL;\nif (!ImageWrapper->GetRaw(ERGBFormat::BGRA, 8, UncompressedBGRA)) break;\nOutputDebugStringA(\"APIC UTexture2D OK\\n\");\nmImageHolder = UTexture2D::CreateTransient(ImageWrapper->GetWidth(), ImageWrapper->GetHeight(), PF_B8G8R8A8);\nvoid* TextureData = mImageHolder->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE);\nFMemory::Memcpy(TextureData, UncompressedBGRA->GetData(), UncompressedBGRA->Num());\nmImageHolder->PlatformData->Mips[0].BulkData.Unlock();\nmImageHolder->UpdateResource();\nmSlateDyn = new FSlateDynamicImageBrush(mImageHolder, FVector2D(ImageWrapper->GetWidth(), ImageWrapper->GetHeight()), FName(\"tabActiveImage\"));\nImageTitle->SetImage(mSlateDyn);\n",[40,1687,1685],{"__ignoreMap":42},[11,1689,1690],{},"由于部分的变量的初始化没有复制过来，代码仅作交流用。",[117,1692,1693],{"id":1693},"播放回调",[11,1695,1696],{},"如果有需要对音乐播放的状态进行追踪，例如在音乐播放完成后进入积分画面的话。使用FMod提供的回调系统是一个不错的解决方案。",[33,1698,1701],{"className":1699,"code":1700,"language":38},[36],"channel->setMode(FMOD_LOOP_OFF);\nif (result != FMOD_RESULT::FMOD_OK) break;\nresult = channel->setCallback(FModHolders::EndCallBack);\nif (result != FMOD_RESULT::FMOD_OK) break;\n",[40,1702,1700],{"__ignoreMap":42},[11,1704,1705],{},"由于设置回调时并没有指定回调类型，故而在回调函数内部对事件进行过滤",[33,1707,1710],{"className":1708,"code":1709,"language":38},[36],"FMOD_RESULT FModHolders::EndCallBack(FMOD_CHANNELCONTROL *chanControl, FMOD_CHANNELCONTROL_TYPE controlType, FMOD_CHANNELCONTROL_CALLBACK_TYPE callbackType, void *commandData1, void *commandData2)\n{\nif (controlType == FMOD_CHANNELCONTROL_TYPE::FMOD_CHANNELCONTROL_CHANNEL){\nif (callbackType == FMOD_CHANNELCONTROL_CALLBACK_TYPE::FMOD_CHANNELCONTROL_CALLBACK_END){\n\u002F\u002F 处理代码\n",[40,1711,1709],{"__ignoreMap":42},[11,1713,1714],{},"这里需要注意的是，回调函数本身的声明必须要是Static的。",[33,1716,1719],{"className":1717,"code":1718,"language":38},[36],"static FMOD_RESULT F_CALLBACK EndCallBack(FMOD_CHANNELCONTROL *chanControl, FMOD_CHANNELCONTROL_TYPE controlType, FMOD_CHANNELCONTROL_CALLBACK_TYPE callbackType, void *commandData1, void *commandData2);\n",[40,1720,1718],{"__ignoreMap":42},{"title":42,"searchDepth":77,"depth":78,"links":1722},[1723,1724,1725],{"id":1647,"depth":77,"text":1647},{"id":1677,"depth":77,"text":1678},{"id":1693,"depth":77,"text":1693},{"layout":83,"status":84,"published":85,"author":1727,"author_login":87,"author_email":88,"author_url":89,"wordpress_id":1728,"wordpress_url":1729,"date_gmt":1730,"excerpt":1731},{"display_name":87,"login":87,"email":88,"url":89},1516,"\u002F\u002F?p=1516","2015-08-18 23:48:29 +0000",{"type":8,"value":1732},[1733],[11,1734,1638],{},"\u002F2015-08-19-ue4-fmod-usage-in-cpp",{"title":1633,"description":1638},"_legacy\u002F2015\u002F2015-08-19-ue4-fmod-usage-in-cpp",[1739,101,1740],"c++","FMod","x4VnHG8oYgpU4XCrWiG9_Wzrb-tAVEXhAGEkpJn3jNI",222,1788763183887]