[{"data":1,"prerenderedAt":1624},["ShallowReactive",2],{"page-UE4-12":3,"page-count-UE4":1623},[4,937,1233,1344,1471],{"id":5,"title":6,"body":7,"date":914,"description":13,"extension":915,"meta":916,"navigation":145,"path":930,"seo":931,"stem":932,"tags":933,"__hash__":936},"blogs\u002F_legacy\u002F2015\u002F2015-08-19-slate-file-browser-tree-view-simple.md","Slate文件目录树基础实现",{"type":8,"value":9,"toc":909},"minimark",[10,14,17,20,31,34,38,41,44,47,58,61,65,68,71,74,80,83,89,92,95,98,101,104,107,250,253,902,905],[11,12,13],"p",{},"目前UMG中并没有提供TreeView类，要使用TreeView必须使用Slate。",[11,15,16],{},"当前UE4版本4.8.3。",[11,18,19],{},"TreeView在UE4编辑器中被广泛的使用，但是出于某些不可知的原因无法在UMG中进行直接使用。鉴于制作复杂度较高的界面需要使用到Slate，对Slate进行专门的研究是有必要的。",[11,21,22,23,30],{},"本文参照Unreal官方社区文档",[24,25,29],"a",{"href":26,"rel":27},"https:\u002F\u002Fwiki.unrealengine.com\u002FSlate,_Tree_View_Widget,_Ex:_In-Editor_File_Structure_Explorer",[28],"nofollow","Slate, Tree View Widget","完成。",[11,32,33],{},"请注意：原始文档中并没有给出具体的文件浏览器实现，而是提供了作为其基础的目录树使用方式。",[35,36,37],"h2",{"id":37},"目录树数据结构",[11,39,40],{},"要实现目录树并不需要太复杂的数据结构，实现基本的功能即可。",[11,42,43],{},"TreeView中本身需要的数据是数组型的，这里定义的数据结构是节点使用的。",[11,45,46],{},"DDFileTreeItem.h",[48,49,54],"pre",{"className":50,"code":52,"language":53},[51],"language-text","#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","text",[55,56,52],"code",{"__ignoreMap":57},"",[11,59,60],{},"节点的数据结构较为简单，直接在头文件中对函数进行了实现。",[35,62,64],{"id":63},"目录树slate控件","目录树Slate控件",[11,66,67],{},"由于原始文档中提供的代码中引用的Engine是不必要的，故而在这里直接替换成了较为通用的HUD引用，这样就不会导致代码有项目相关性了。",[11,69,70],{},"这里先上代码，其他的一些说明放在代码后面。",[11,72,73],{},"SDDFileTree.h",[48,75,78],{"className":76,"code":77,"language":53},[51],"#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",[55,79,77],{"__ignoreMap":57},[11,81,82],{},"SDDFileTree.cpp",[48,84,87],{"className":85,"code":86,"language":53},[51],"#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",[55,88,86],{"__ignoreMap":57},[11,90,91],{},"代码的结构还是比较清晰的，这里需要注意的是，针对TreeView的事件是在控件本身生成的时候注册的。",[11,93,94],{},"其中，OnGenerateRow事件是STreeView和SListView的必须事件。当控件需要进行元素显示时，会对这个函数进行调用，获得需要显示的元素。",[11,96,97],{},"另外，代码中的OnSelectionChanged函数缺乏必要的安全检测，在使用中需要留意。",[11,99,100],{},"用于显示的数据是在Construct中通过调用RebuildFileTree()函数生成的。",[35,102,103],{"id":103},"最终结果",[11,105,106],{},"完成了上面的步骤之后，直接在HUD中对控件进行展示即可。",[48,108,112],{"className":109,"code":110,"language":111,"meta":57,"style":57},"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",[55,113,114,140,147,163,169,198,204,209,222,227,245],{"__ignoreMap":57},[115,116,119,123,127,130,133,137],"span",{"class":117,"line":118},"line",1,[115,120,122],{"class":121},"scAT4","SAssignNew",[115,124,126],{"class":125},"s0idv","(DDFileTree, SDDFileTree).",[115,128,129],{"class":121},"OwnerHUD",[115,131,132],{"class":125},"(",[115,134,136],{"class":135},"s4Wjf","this",[115,138,139],{"class":125},");\n",[115,141,143],{"class":117,"line":142},2,[115,144,146],{"emptyLinePlaceholder":145},true,"\n",[115,148,150,154,157,160],{"class":117,"line":149},3,[115,151,153],{"class":152},"sLXdl","if",[115,155,156],{"class":125}," (GEngine->",[115,158,159],{"class":121},"IsValidLowLevel",[115,161,162],{"class":125},"())\n",[115,164,166],{"class":117,"line":165},4,[115,167,168],{"class":125},"{\n",[115,170,172,175,178,180,183,186,189,192,195],{"class":117,"line":171},5,[115,173,174],{"class":125},"  GEngine->GameViewport->",[115,176,177],{"class":121},"AddViewportWidgetContent",[115,179,132],{"class":125},[115,181,182],{"class":121},"SNew",[115,184,185],{"class":125},"(SWeakWidget).",[115,187,188],{"class":121},"PossiblyNullContent",[115,190,191],{"class":125},"(DDFileTree.",[115,193,194],{"class":121},"ToSharedRef",[115,196,197],{"class":125},"()));\n",[115,199,201],{"class":117,"line":200},6,[115,202,203],{"class":125},"}\n",[115,205,207],{"class":117,"line":206},7,[115,208,146],{"emptyLinePlaceholder":145},[115,210,212,214,217,220],{"class":117,"line":211},8,[115,213,153],{"class":152},[115,215,216],{"class":125}," (DDFileTree.",[115,218,219],{"class":121},"IsValid",[115,221,162],{"class":125},[115,223,225],{"class":117,"line":224},9,[115,226,168],{"class":125},[115,228,230,233,236,238,242],{"class":117,"line":229},10,[115,231,232],{"class":125},"  DDFileTree->",[115,234,235],{"class":121},"SetVisibility",[115,237,132],{"class":125},[115,239,241],{"class":240},"sfUV7","EVisibility",[115,243,244],{"class":125},"::Visible);\n",[115,246,248],{"class":117,"line":247},11,[115,249,203],{"class":125},[11,251,252],{},"启动预览，由于没有进行布局，可以在最上方看到目录树的展示。",[11,254,255,256,901],{},"![U10",[115,257,260,472],{"className":258},[259],"katex",[115,261,264],{"className":262},[263],"katex-mathml",[265,266,268],"math",{"xmlns":267},"http:\u002F\u002Fwww.w3.org\u002F1998\u002FMath\u002FMathML",[269,270,271,467],"semantics",{},[272,273,274,283,286,290,293,296,299,307,310,313,318,321,324,327,329,332,334,336,340,343,345,348,351,354,357,360,363,365,367,369,372,374,377,379,381,384,387,389,392,394,397,399,401,408,410,412,414,416,418,424,426,429,431,433,439,442,444,447,450,453,455,457,460,463,465],"mrow",{},[275,276,277,279],"msub",{},[272,278],{},[280,281,282],"mi",{},"I",[280,284,285],{},"A",[287,288,289],"mn",{},"57",[280,291,292],{},"U",[280,294,295],{},"R",[280,297,298],{},"M",[275,300,301,304],{},[280,302,303],{},"V",[280,305,306],{},"P",[280,308,309],{},"O",[287,311,312],{},"0",[314,315,317],"mo",{"stretchy":316},"false","]",[287,319,320],{},"6",[314,322,323],{"stretchy":316},"[",[280,325,326],{},"J",[280,328,292],{},[280,330,331],{},"C",[314,333,317],{"stretchy":316},[314,335,132],{"stretchy":316},[280,337,339],{"mathvariant":338},"normal","\u002F",[280,341,342],{},"w",[280,344,11],{},[314,346,347],{},"−",[280,349,350],{},"c",[280,352,353],{},"o",[280,355,356],{},"n",[280,358,359],{},"t",[280,361,362],{},"e",[280,364,356],{},[280,366,359],{},[280,368,339],{"mathvariant":338},[280,370,371],{},"u",[280,373,11],{},[280,375,376],{},"l",[280,378,353],{},[280,380,24],{},[280,382,383],{},"d",[280,385,386],{},"s",[280,388,339],{"mathvariant":338},[287,390,391],{},"2017",[280,393,339],{"mathvariant":338},[287,395,396],{},"03",[280,398,339],{"mathvariant":338},[280,400,292],{},[275,402,403,406],{},[287,404,405],{},"10",[280,407,282],{},[280,409,285],{},[287,411,289],{},[280,413,292],{},[280,415,295],{},[280,417,298],{},[275,419,420,422],{},[280,421,303],{},[280,423,306],{},[280,425,309],{},[287,427,428],{},"06",[280,430,326],{},[280,432,292],{},[275,434,435,437],{},[280,436,331],{},[280,438,359],{},[280,440,441],{},"h",[280,443,371],{},[280,445,446],{},"m",[280,448,449],{},"b",[280,451,452],{"mathvariant":338},".",[280,454,11],{},[280,456,356],{},[280,458,459],{},"g",[280,461,462],{"mathvariant":338},"\"",[280,464,292],{},[287,466,405],{},[468,469,471],"annotation",{"encoding":470},"application\u002Fx-tex","_IA57URMV_PO0]6[JUC](\u002Fwp-content\u002Fuploads\u002F2017\u002F03\u002FU10_IA57URMV_PO06JUC_thumb.png \"U10",[115,473,477,660],{"className":474,"ariaHidden":476},[475],"katex-html","true",[115,478,481,486,544,547,550,554,558,561,604,608,611,615,618,622,626,629,633,636,639,642,646,649,653,657],{"className":479},[480],"base",[115,482],{"className":483,"style":485},[484],"strut","height:1em;vertical-align:-0.25em;",[115,487,490,492],{"className":488},[489],"mord",[115,491],{},[115,493,496],{"className":494},[495],"msupsub",[115,497,501,535],{"className":498},[499,500],"vlist-t","vlist-t2",[115,502,505,530],{"className":503},[504],"vlist-r",[115,506,510],{"className":507,"style":509},[508],"vlist","height:0.3283em;",[115,511,513,518],{"style":512},"top:-2.55em;margin-right:0.05em;",[115,514],{"className":515,"style":517},[516],"pstrut","height:2.7em;",[115,519,525],{"className":520},[521,522,523,524],"sizing","reset-size6","size3","mtight",[115,526,282],{"className":527,"style":529},[489,528,524],"mathnormal","margin-right:0.0785em;",[115,531,534],{"className":532},[533],"vlist-s","​",[115,536,538],{"className":537},[504],[115,539,542],{"className":540,"style":541},[508],"height:0.15em;",[115,543],{},[115,545,285],{"className":546},[489,528],[115,548,289],{"className":549},[489],[115,551,292],{"className":552,"style":553},[489,528],"margin-right:0.109em;",[115,555,295],{"className":556,"style":557},[489,528],"margin-right:0.0077em;",[115,559,298],{"className":560,"style":553},[489,528],[115,562,564,568],{"className":563},[489],[115,565,303],{"className":566,"style":567},[489,528],"margin-right:0.2222em;",[115,569,571],{"className":570},[495],[115,572,574,596],{"className":573},[499,500],[115,575,577,593],{"className":576},[504],[115,578,580],{"className":579,"style":509},[508],[115,581,583,586],{"style":582},"top:-2.55em;margin-left:-0.2222em;margin-right:0.05em;",[115,584],{"className":585,"style":517},[516],[115,587,589],{"className":588},[521,522,523,524],[115,590,306],{"className":591,"style":592},[489,528,524],"margin-right:0.1389em;",[115,594,534],{"className":595},[533],[115,597,599],{"className":598},[504],[115,600,602],{"className":601,"style":541},[508],[115,603],{},[115,605,309],{"className":606,"style":607},[489,528],"margin-right:0.0278em;",[115,609,312],{"className":610},[489],[115,612,317],{"className":613},[614],"mclose",[115,616,320],{"className":617},[489],[115,619,323],{"className":620},[621],"mopen",[115,623,326],{"className":624,"style":625},[489,528],"margin-right:0.0962em;",[115,627,292],{"className":628,"style":553},[489,528],[115,630,331],{"className":631,"style":632},[489,528],"margin-right:0.0715em;",[115,634,317],{"className":635},[614],[115,637,132],{"className":638},[621],[115,640,339],{"className":641},[489],[115,643,342],{"className":644,"style":645},[489,528],"margin-right:0.0269em;",[115,647,11],{"className":648},[489,528],[115,650],{"className":651,"style":567},[652],"mspace",[115,654,347],{"className":655},[656],"mbin",[115,658],{"className":659,"style":567},[652],[115,661,663,666,670,673,676,679,682,685,688,691,696,699,702,705,708,712,715,719,760,763,766,769,772,775,815,818,821,824,827,869,872,875,879,882,885,888,892,895,898],{"className":662},[480],[115,664],{"className":665,"style":485},[484],[115,667,669],{"className":668},[489,528],"co",[115,671,356],{"className":672},[489,528],[115,674,359],{"className":675},[489,528],[115,677,362],{"className":678},[489,528],[115,680,356],{"className":681},[489,528],[115,683,359],{"className":684},[489,528],[115,686,339],{"className":687},[489],[115,689,371],{"className":690},[489,528],[115,692,695],{"className":693,"style":694},[489,528],"margin-right:0.0197em;","pl",[115,697,353],{"className":698},[489,528],[115,700,24],{"className":701},[489,528],[115,703,383],{"className":704},[489,528],[115,706,386],{"className":707},[489,528],[115,709,711],{"className":710},[489],"\u002F2017\u002F03\u002F",[115,713,292],{"className":714,"style":553},[489,528],[115,716,718],{"className":717},[489],"1",[115,720,722,725],{"className":721},[489],[115,723,312],{"className":724},[489],[115,726,728],{"className":727},[495],[115,729,731,752],{"className":730},[499,500],[115,732,734,749],{"className":733},[504],[115,735,737],{"className":736,"style":509},[508],[115,738,740,743],{"style":739},"top:-2.55em;margin-left:0em;margin-right:0.05em;",[115,741],{"className":742,"style":517},[516],[115,744,746],{"className":745},[521,522,523,524],[115,747,282],{"className":748,"style":529},[489,528,524],[115,750,534],{"className":751},[533],[115,753,755],{"className":754},[504],[115,756,758],{"className":757,"style":541},[508],[115,759],{},[115,761,285],{"className":762},[489,528],[115,764,289],{"className":765},[489],[115,767,292],{"className":768,"style":553},[489,528],[115,770,295],{"className":771,"style":557},[489,528],[115,773,298],{"className":774,"style":553},[489,528],[115,776,778,781],{"className":777},[489],[115,779,303],{"className":780,"style":567},[489,528],[115,782,784],{"className":783},[495],[115,785,787,807],{"className":786},[499,500],[115,788,790,804],{"className":789},[504],[115,791,793],{"className":792,"style":509},[508],[115,794,795,798],{"style":582},[115,796],{"className":797,"style":517},[516],[115,799,801],{"className":800},[521,522,523,524],[115,802,306],{"className":803,"style":592},[489,528,524],[115,805,534],{"className":806},[533],[115,808,810],{"className":809},[504],[115,811,813],{"className":812,"style":541},[508],[115,814],{},[115,816,309],{"className":817,"style":607},[489,528],[115,819,428],{"className":820},[489],[115,822,326],{"className":823,"style":625},[489,528],[115,825,292],{"className":826,"style":553},[489,528],[115,828,830,833],{"className":829},[489],[115,831,331],{"className":832,"style":632},[489,528],[115,834,836],{"className":835},[495],[115,837,839,861],{"className":838},[499,500],[115,840,842,858],{"className":841},[504],[115,843,846],{"className":844,"style":845},[508],"height:0.2806em;",[115,847,849,852],{"style":848},"top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;",[115,850],{"className":851,"style":517},[516],[115,853,855],{"className":854},[521,522,523,524],[115,856,359],{"className":857},[489,528,524],[115,859,534],{"className":860},[533],[115,862,864],{"className":863},[504],[115,865,867],{"className":866,"style":541},[508],[115,868],{},[115,870,441],{"className":871},[489,528],[115,873,371],{"className":874},[489,528],[115,876,878],{"className":877},[489,528],"mb",[115,880,452],{"className":881},[489],[115,883,11],{"className":884},[489,528],[115,886,356],{"className":887},[489,528],[115,889,459],{"className":890,"style":891},[489,528],"margin-right:0.0359em;",[115,893,462],{"className":894},[489],[115,896,292],{"className":897,"style":553},[489,528],[115,899,405],{"className":900},[489],"_IA57URMV_PO0]6[JUC\")",[11,903,904],{},"至此，目录树的基本功能已经实现。",[906,907,908],"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":57,"searchDepth":142,"depth":149,"links":910},[911,912,913],{"id":37,"depth":142,"text":37},{"id":63,"depth":142,"text":64},{"id":103,"depth":142,"text":103},"2015-08-19","md",{"layout":917,"status":918,"published":145,"author":919,"author_login":921,"author_email":922,"wordpress_id":923,"wordpress_url":924,"date_gmt":925,"excerpt":926},"post","publish",{"display_name":920,"login":921,"email":922,"url":57},"风铃","flinkor","flinkor@foxmail.com",1542,"\u002F\u002F?p=1542","2015-08-19 06:52:43 +0000",{"type":8,"value":927},[928],[11,929,13],{},"\u002F2015-08-19-slate-file-browser-tree-view-simple",{"title":6,"description":13},"_legacy\u002F2015\u002F2015-08-19-slate-file-browser-tree-view-simple",[934,935],"UE4","Slate","AHchideoQOxz25BWavPLnfqJsUoUsDtNrYhTtEf97xw",{"id":938,"title":939,"body":940,"date":914,"description":944,"extension":915,"meta":1216,"navigation":145,"path":1227,"seo":1228,"stem":1229,"tags":1230,"__hash__":1232},"blogs\u002F_legacy\u002F2015\u002F2015-08-19-ue4-cel-look.md","UE4赛璐珞风格",{"type":8,"value":941,"toc":1205},[942,945,948,962,965,968,974,977,980,999,1002,1163,1167,1170,1173,1176,1179,1183,1188,1191,1195,1198,1202],[11,943,944],{},"UE4默认的渲染风格是写实的，以照片级为目标。而如果要做赛璐珞风格的话，就需要稍微做一些改动。",[11,946,947],{},"当前UE4版本4.10.0。",[11,949,950,951,956,957,961],{},"3D的赛璐珞风格实现是有一些现成的经验的，最近有些番组也是用的3D做的。不过作为对图形学方面的知识是外行的人而言，要自己实现还是有很多困难的。好在，已经有人将赛璐珞风格在UE4中实现了。原文的话在[",[24,952,955],{"href":953,"rel":954},"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",[28],"这里","]，还有对应的PPT，里面有对相应的原理的说明。PPT的下载地址在原文中有提供，点击[",[24,958,955],{"href":959,"rel":960},"https:\u002F\u002Fwww.slideshare.net\u002FEpicGamesJapan\u002F4-unreal-fest-2015-yokohama-54129845",[28],"]可以直达。",[35,963,964],{"id":964},"最终效果",[11,966,967],{},"对参数进行少许的调整之后，最终的效果是这样的：",[11,969,970],{},[971,972],"img",{"alt":57,"src":973},"\u002Fwp-content\u002Fuploads\u002F2017\u002F03\u002FphpiU9u6l.1453546682-1.png",[11,975,976],{},"在没有做精细调整的情况下，这样的效果基本上算是让人满意了。使用上也非常的方便，如果有在项目中使用的话，一定要记得在原文中感谢作者哦~下面对在使用过程中可能会使用到的一些东西进行总结，其中几乎所有内容都是直接翻译的，如有错误欢迎指正。",[35,978,979],{"id":979},"使用步骤",[981,982,983,987,990,993,996],"ol",{},[984,985,986],"li",{},"由PP_NPR生成新的材质实例（也可以使用生成的PPI_NPR），将PostProcessVolume的Blendables中添加刚刚生成的材质实例即可",[984,988,989],{},"控制渲染效果的参数位于NPR_ParamCollection中",[984,991,992],{},"控制皮肤的亮度（在SkeletalMesh编辑器中将头发等的投影关闭）",[984,994,995],{},"从M_CharacterBase创建材质实例并应用于角色",[984,997,998],{},"将DirectionalLight替换为BP_DirectionalLightForCharacter(与上面的材质合同工作)",[35,1000,1001],{"id":1001},"参数相关",[1003,1004,1005,1021],"table",{},[1006,1007,1008],"thead",{},[1009,1010,1011,1015,1018],"tr",{},[1012,1013,1014],"th",{},"名称",[1012,1016,1017],{},"默认值",[1012,1019,1020],{},"说明",[1022,1023,1024,1035,1046,1057,1068,1079,1089,1099,1110,1120,1130,1141,1152],"tbody",{},[1009,1025,1026,1030,1032],{},[1027,1028,1029],"td",{},"DebugWipe",[1027,1031,312],{},[1027,1033,1034],{},"屏幕分割，对效果进行调试对比用",[1009,1036,1037,1040,1043],{},[1027,1038,1039],{},"LightIntensityBias",[1027,1041,1042],{},"0.3",[1027,1044,1045],{},"场景较为暗时可以对暗的部分进行",[1009,1047,1048,1051,1054],{},[1027,1049,1050],{},"LightIntensityStepping",[1027,1052,1053],{},"2",[1027,1055,1056],{},"Cel涂色步进，小于1会出现不可预知的表现",[1009,1058,1059,1062,1065],{},[1027,1060,1061],{},"FresnelFunc_ContrastDark",[1027,1063,1064],{},"0.1",[1027,1066,1067],{},"用于侧面判定的Fresel的暗面控制参数",[1009,1069,1070,1073,1076],{},[1027,1071,1072],{},"FresnelFunc_ContrastBright",[1027,1074,1075],{},"1.5",[1027,1077,1078],{},"用于侧面判定的Fresel的亮面控制参数",[1009,1080,1081,1084,1086],{},[1027,1082,1083],{},"EdgeLineThickness",[1027,1085,1075],{},[1027,1087,1088],{},"轮廓线的粗细",[1009,1090,1091,1094,1096],{},[1027,1092,1093],{},"CreaseLineThickness",[1027,1095,1075],{},[1027,1097,1098],{},"棱线的粗细（有时设定为负值，在法线方向上取样时会有好的效果）",[1009,1100,1101,1104,1107],{},[1027,1102,1103],{},"FlankLine",[1027,1105,1106],{},"0.2",[1027,1108,1109],{},"侧面线颜色叠加系数（0为全黑，0.5为原本的颜色的0.5）",[1009,1111,1112,1115,1117],{},[1027,1113,1114],{},"EdgeLine",[1027,1116,1106],{},[1027,1118,1119],{},"轮廓线颜色叠加系数（0为全黑，0.5为原本的颜色的0.5）",[1009,1121,1122,1125,1127],{},[1027,1123,1124],{},"CreaseLine",[1027,1126,1106],{},[1027,1128,1129],{},"棱线颜色叠加系数（0为全黑，0.5为原本的颜色的0.5）",[1009,1131,1132,1135,1138],{},[1027,1133,1134],{},"LineColor",[1027,1136,1137],{},"(0.8, 0.084908, 0.045, 0)",[1027,1139,1140],{},"以上三种线的颜色，Alpha值代表叠加颜色的程度，可以超限以达到发光效果",[1009,1142,1143,1146,1149],{},[1027,1144,1145],{},"CharacterLightVector",[1027,1147,1148],{},"(0, 0, -1, 0)",[1027,1150,1151],{},"角色表情等会用到的光照向量（建议直接复制DirectionalLight的向量）",[1009,1153,1154,1157,1160],{},[1027,1155,1156],{},"CharacterLightColor",[1027,1158,1159],{},"(1, 1, 1, 0)",[1027,1161,1162],{},"（当前未使用：角色用光照颜色）",[35,1164,1166],{"id":1165},"m_characterbase相关","M_CharacterBase相关",[11,1168,1169],{},"一般情况下只要利用Postprocess Materia就可以达到效果了，当角色由于阴影的影响过强导致着色浑浊现象出现时，建议使用M_CharaterBase作为对象材质的基础生成材质实例，并在该材质实例中进行属性的调整。",[11,1171,1172],{},"如果材质的贴图中本来就有阴影等信息的话，可以在材质实例宏将DiffuseBase调整为0.01使得BaseColor接近于0，将DiffuseEmissive设定为1.0以将阴影效果降到最低。",[11,1174,1175],{},"对于追求与现实系相近的中间型的着色效果的情况，可以将材质实例中的Shading Mode更改为Subsurface，这样以来就可以通过对SubsurfaceColor的调整来对颜色效果进行微调。",[11,1177,1178],{},"在有发光部件的情况下，勾选UseEmissiveTexture，并将发光部件的贴图彷如EmissiveTexture中，同时设定EmmissiveScale为很大的值就可以达到发光的效果了。",[35,1180,1182],{"id":1181},"faq","FAQ",[1184,1185,1187],"h3",{"id":1186},"q-想要关闭掉角色的反光效果","Q. 想要关闭掉角色的反光效果",[11,1189,1190],{},"A. 在PostProcess的内部，可以某种程度的削弱反光效果，但是想要完全取消掉的话很困难，因为会增加额外的运算负担。对原有的材质的Roughness进行上调以及下调Metalic的值可以达到类似与消除反光的效果。这样的话就可以对模型的效果进行个别控制，一些闪闪发光的材质也能保留原来的效果。",[1184,1192,1194],{"id":1193},"q-将漫反射的贴图变得平滑不是更能实现动画风格吗","Q. 将漫反射的贴图变得平滑不是更能实现动画风格吗？",[11,1196,1197],{},"A. 虽然对相近的像素进行采样并平均的话能够达到平滑的效果，但是以贴图为采样目标的话运算负担就会过重，推荐直接对原有的材质进行修改。这样也可以实现对不同材质的控制和调整。",[1184,1199,1201],{"id":1200},"q-角色激烈运动时会导致线条杂乱","Q. 角色激烈运动时会导致线条杂乱",[11,1203,1204],{},"A. 这是Temporal AA的影响导致的。在PostProcessVolume-Settings-Misc中有AA Method的属性，将其设定为FXAA可以有效的防止这种情况（静画的情况下Temporal AA的画面质量更好一些）。另外，如果是面向在线游戏的画质的话，通过将Screen Percentage设定为200与Temporar AA进行组合，或者将AA关闭（设定为FXAA亦可）以2×2倍的分辨率渲染等方法将渲染目标缩小的方法也是可以的。",{"title":57,"searchDepth":142,"depth":149,"links":1206},[1207,1208,1209,1210,1211],{"id":964,"depth":142,"text":964},{"id":979,"depth":142,"text":979},{"id":1001,"depth":142,"text":1001},{"id":1165,"depth":142,"text":1166},{"id":1181,"depth":142,"text":1182,"children":1212},[1213,1214,1215],{"id":1186,"depth":149,"text":1187},{"id":1193,"depth":149,"text":1194},{"id":1200,"depth":149,"text":1201},{"layout":917,"status":918,"published":145,"author":1217,"author_login":1218,"author_email":1219,"author_url":339,"wordpress_id":1220,"wordpress_url":1221,"date_gmt":1222,"excerpt":1223},{"display_name":1218,"login":1218,"email":1219,"url":339},"chaoshikari","chaoshikari@gmail.com",1495,"\u002F\u002F?p=1495","2015-08-19 01:03:52 +0000",{"type":8,"value":1224},[1225],[11,1226,944],{},"\u002F2015-08-19-ue4-cel-look",{"title":939,"description":944},"_legacy\u002F2015\u002F2015-08-19-ue4-cel-look",[934,1231],"Materia","-YGfnoTBCuFUJHxFFE3zFTNTplzJ2UdvV1LeiYnQOn4",{"id":1234,"title":1235,"body":1236,"date":914,"description":1240,"extension":915,"meta":1328,"navigation":145,"path":1337,"seo":1338,"stem":1339,"tags":1340,"__hash__":1343},"blogs\u002F_legacy\u002F2015\u002F2015-08-19-ue4-fmod-usage-in-cpp.md","UE4中C++模式FMod的使用",{"type":8,"value":1237,"toc":1323},[1238,1241,1244,1247,1250,1253,1256,1262,1265,1271,1274,1277,1281,1284,1290,1293,1296,1299,1305,1308,1314,1317],[11,1239,1240],{},"FMod在为UE4提供音效便利的同时，有提供底层的API来方便更加详尽的需求。",[11,1242,1243],{},"当前UE4版本4.9.2；FMod版本1.07.00。",[11,1245,1246],{},"目前版本下对FMod的使用和之前进行UE4与FMod音乐可视化验证的时候并没有什么不同，这里主要是总结下使用中遇到的问题。",[35,1248,1249],{"id":1249},"编码问题",[11,1251,1252],{},"编码问题主要出在中文路径的处理上，UE4内部使用的是UTF-16编码。",[11,1254,1255],{},"因此，将通过Save&Load之类保存起来的路径提供给FMod使用的时候，需要对字符编码进行转换。",[48,1257,1260],{"className":1258,"code":1259,"language":53},[51],"\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",[55,1261,1259],{"__ignoreMap":57},[11,1263,1264],{},"关于字符编码，在使用Tag系统的时候还是会遇到。",[48,1266,1269],{"className":1267,"code":1268,"language":53},[51],"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",[55,1270,1268],{"__ignoreMap":57},[11,1272,1273],{},"这里目前似乎是有BUG存在的，通过getTag对音乐的标签进行读取时，如果标签是非英文字符的话，读取出来的字符编码无法被正常的解析。并不是读写方式的问题，通过断点直接查看内存的话会看到其中的编码不符合文档中所描述的编码格式。",[11,1275,1276],{},"因此建议音乐的预览图片使用FMod来读取APIC的tag，而其他的tag使用别的库进行读取，例如使用TagLib。",[35,1278,1280],{"id":1279},"apic图片读取","APIC图片读取",[11,1282,1283],{},"音乐文件的预览图片信息一般存储于APIC标签中，要将读取出的图片数据展示到界面上。可以借助ImageWrapper和SImage来实现。",[48,1285,1288],{"className":1286,"code":1287,"language":53},[51],"\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",[55,1289,1287],{"__ignoreMap":57},[11,1291,1292],{},"由于部分的变量的初始化没有复制过来，代码仅作交流用。",[35,1294,1295],{"id":1295},"播放回调",[11,1297,1298],{},"如果有需要对音乐播放的状态进行追踪，例如在音乐播放完成后进入积分画面的话。使用FMod提供的回调系统是一个不错的解决方案。",[48,1300,1303],{"className":1301,"code":1302,"language":53},[51],"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",[55,1304,1302],{"__ignoreMap":57},[11,1306,1307],{},"由于设置回调时并没有指定回调类型，故而在回调函数内部对事件进行过滤",[48,1309,1312],{"className":1310,"code":1311,"language":53},[51],"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",[55,1313,1311],{"__ignoreMap":57},[11,1315,1316],{},"这里需要注意的是，回调函数本身的声明必须要是Static的。",[48,1318,1321],{"className":1319,"code":1320,"language":53},[51],"static FMOD_RESULT F_CALLBACK EndCallBack(FMOD_CHANNELCONTROL *chanControl, FMOD_CHANNELCONTROL_TYPE controlType, FMOD_CHANNELCONTROL_CALLBACK_TYPE callbackType, void *commandData1, void *commandData2);\n",[55,1322,1320],{"__ignoreMap":57},{"title":57,"searchDepth":142,"depth":149,"links":1324},[1325,1326,1327],{"id":1249,"depth":142,"text":1249},{"id":1279,"depth":142,"text":1280},{"id":1295,"depth":142,"text":1295},{"layout":917,"status":918,"published":145,"author":1329,"author_login":1218,"author_email":1219,"author_url":339,"wordpress_id":1330,"wordpress_url":1331,"date_gmt":1332,"excerpt":1333},{"display_name":1218,"login":1218,"email":1219,"url":339},1516,"\u002F\u002F?p=1516","2015-08-18 23:48:29 +0000",{"type":8,"value":1334},[1335],[11,1336,1240],{},"\u002F2015-08-19-ue4-fmod-usage-in-cpp",{"title":1235,"description":1240},"_legacy\u002F2015\u002F2015-08-19-ue4-fmod-usage-in-cpp",[1341,934,1342],"c++","FMod","x4VnHG8oYgpU4XCrWiG9_Wzrb-tAVEXhAGEkpJn3jNI",{"id":1345,"title":1346,"body":1347,"date":1456,"description":1351,"extension":915,"meta":1457,"navigation":145,"path":1466,"seo":1467,"stem":1468,"tags":1469,"__hash__":1470},"blogs\u002F_legacy\u002F2015\u002F2015-08-18-hello-slate.md","Hello Slate",{"type":8,"value":1348,"toc":1450},[1349,1352,1354,1357,1365,1368,1371,1377,1380,1384,1387,1396,1399,1405,1408,1414,1417,1421,1424,1427,1430,1436,1439,1445,1447],[11,1350,1351],{},"UE4中通常的游戏内逻辑使用UMG就可以了，当需要一些独特的功能时就会需要用到Slate。",[11,1353,16],{},[11,1355,1356],{},"Slate是UE4的用户界面系统，UE4编辑器的大部分界面都是由Slate构建的。同时，在编辑器中使用的UMG也是在Slate的基础上封装的。",[11,1358,1359,1360,30],{},"本文参照官方社区文档",[24,1361,1364],{"href":1362,"rel":1363},"https:\u002F\u002Fwiki.unrealengine.com\u002FSlate,_Hello",[28],"Slate, Hello",[35,1366,1367],{"id":1367},"准备工作",[11,1369,1370],{},"要使用Slate，第一步是将其API开放到项目。在项目对应的Build.cs中将下面的代码的注释去掉即可",[48,1372,1375],{"className":1373,"code":1374,"language":53},[51],"\u002F\u002F Uncomment if you are using Slate UI\n\nPrivateDependencyModuleNames.AddRange(new string[] { \"Slate\", \"SlateCore\" });\n",[55,1376,1374],{"__ignoreMap":57},[11,1378,1379],{},"根据UE4版本的不同如果没有这行的直接加上就好了。",[35,1381,1383],{"id":1382},"slatewidget","SlateWidget",[11,1385,1386],{},"创建用于显示文字的Slate控件。",[11,1388,1389,1390,1395],{},"Slate控件的一些特殊的宏和界面定义方式的详情可以参考官方的",[24,1391,1394],{"href":1392,"rel":1393},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FProgramming\u002FSlate\u002FOverview\u002Findex.html",[28],"Slate概述","。",[11,1397,1398],{},"StandardSlateWidget.h",[48,1400,1403],{"className":1401,"code":1402,"language":53},[51],"#pragma once\n#include \"Test_mp.h\" \u002F* \u003C项目头文件 *\u002F\n\nclass SStandardSlateWidget: public SCompoundWidget\n{\nSLATE_BEGIN_ARGS(SStandardSlateWidget){}\n\n\u002F*\u003C参照下面的OwnerHUD的声明 *\u002F\nSLATE_ARGUMENT(TWeakObjectPtr\u003Cclass AHUD>,OwnerHUD)\n\n\nSLATE_END_ARGS()\n\npublic:\n\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\n\u002F\u002F\u002F\u002F\u002F\u003C每一个控件都必须要有这个函数\n\u002F\u002F\u002F\u002F\u002F\u003C构建控件及其子控件\nvoid Construct(const FArguments& InArgs);\nprivate:\n\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\n\u002F\u002F\u002F\u002F\u002F\u003C指向控件的持有者Hud\n\u002F\u002F\u002F\u002F\u002F\u003C使用弱引用持有HUD的指针，因为HUD是使用强引用来持有Widget的。\n\u002F\u002F\u002F\u002F\u002F\u003C如果双方都为强引用的话将会导致解构时形成循环引用并引发内存泄露\nTWeakObjectPtr\u003Cclass AHUD> OwnerHUD;\n\n};\n",[55,1404,1402],{"__ignoreMap":57},[11,1406,1407],{},"StandardSlateWidget.cpp",[48,1409,1412],{"className":1410,"code":1411,"language":53},[51],"#include \"Test_mp.h\"\n#include \"Plugin\u002FStandardSlateWidget.h\"\n \nvoid SStandardSlateWidget::Construct(const FArguments& InArgs)\n{\n    OwnerHUD = InArgs._OwnerHUD;\n \n    ChildSlot\n        .VAlign(VAlign_Fill)\n        .HAlign(HAlign_Fill)\n        [\n            SNew(SOverlay)\n            + SOverlay::Slot()\n            .VAlign(VAlign_Top)\n            .HAlign(HAlign_Center)\n             [\n                 SNew(STextBlock)\n                 .ShadowColorAndOpacity(FLinearColor::Black)\n                 .ColorAndOpacity(FLinearColor::Red)\n                 .ShadowOffset(FIntPoint(-5, 5))\n                 .Font(FSlateFontInfo(\"Veranda\", 24))\n                 .Text(FText::FromString(\"Hello, Slate!\"))\n             ]\n        ];\n \n}\n",[55,1413,1411],{"__ignoreMap":57},[11,1415,1416],{},"在这里使用OwnerHUD并不是必须的，主要的作用是作为参数传递的示范。",[35,1418,1420],{"id":1419},"hud","HUD",[11,1422,1423],{},"通过自定义一个HUD用于Slate控件的展示。添加HUD代码没有什么特殊的地方。",[11,1425,1426],{},"添加好之后直接在BeginPlay中将Widget输出到屏幕即可。",[11,1428,1429],{},"StandardHud.h",[48,1431,1434],{"className":1432,"code":1433,"language":53},[51],"#pragma once\n#include \"GameFramework\u002FHUD.h\"\n#include \"StandardHUD.generated.h\"\n \nclass SStandardSlateWidget;\n \nUCLASS()\nclass AStandardHUD : public AHUD\n{\n    GENERATED_BODY()\n \npublic:\n    AStandardHUD();\n    TSharedPtr\u003CSStandardSlateWidget> myUIWidget;\n \n    void BeginPlay();\n};\n\n",[55,1435,1433],{"__ignoreMap":57},[11,1437,1438],{},"StandardHud.cpp",[48,1440,1443],{"className":1441,"code":1442,"language":53},[51],"#pragma once\n \u002F\u002F Fill out your copyright notice in the Description page of Project Settings.   \n #include \"Test_mp.h\" \n #include \"Plugin\u002FStandardSlateWidget.h\" \n #include \"Plugin\u002FStandardHUD.h\"   \n \nAStandardHUD::AStandardHUD() \n{   \n\n}\n\nvoid AStandardHUD::BeginPlay() \n{     \n    SAssignNew(myUIWidget, SStandardSlateWidget).OwnerHUD(this);       \n    if (GEngine->IsValidLowLevel())     \n    {         \n        GEngine->GameViewport->AddViewportWidgetContent(SNew(SWeakWidget).PossiblyNullContent(myUIWidget.ToSharedRef()));     \n    }       \n    if (myUIWidget.IsValid())     \n    {         \n        myUIWidget->SetVisibility(EVisibility::Visible);     \n    } \n}\n\n",[55,1444,1442],{"__ignoreMap":57},[35,1446,103],{"id":103},[11,1448,1449],{},"原始的教程中还有自定义GameMode的部分，在这里就不执行了。直接在编辑器中将HUD指定为刚刚定义的StandardHud即可。\n指定完成后，点击运行即可在屏幕上方看到“Hello, Slate!”的文字输出了。",{"title":57,"searchDepth":142,"depth":149,"links":1451},[1452,1453,1454,1455],{"id":1367,"depth":142,"text":1367},{"id":1382,"depth":142,"text":1383},{"id":1419,"depth":142,"text":1420},{"id":103,"depth":142,"text":103},"2015-08-18",{"layout":917,"status":918,"published":145,"author":1458,"author_login":1218,"author_email":1219,"author_url":339,"wordpress_id":1459,"wordpress_url":1460,"date_gmt":1461,"excerpt":1462},{"display_name":1218,"login":1218,"email":1219,"url":339},1472,"\u002F\u002F?p=1472","2015-08-18 11:56:34 +0000",{"type":8,"value":1463},[1464],[11,1465,1351],{},"\u002F2015-08-18-hello-slate",{"title":1346,"description":1351},"_legacy\u002F2015\u002F2015-08-18-hello-slate",[934,935],"nMQL2MFSDju9LkcCXygpyjn248xCbtlgOGdlwP5Xquk",{"id":1472,"title":1473,"body":1474,"date":1456,"description":1478,"extension":915,"meta":1608,"navigation":145,"path":1617,"seo":1618,"stem":1619,"tags":1620,"__hash__":1622},"blogs\u002F_legacy\u002F2015\u002F2015-08-18-slate-icons.md","Slate中图标的使用",{"type":8,"value":1475,"toc":1603},[1476,1479,1481,1484,1487,1490,1496,1499,1505,1508,1514,1517,1520,1526,1530,1533,1536,1539,1542,1548,1551,1557,1560,1568,1571,1574,1577,1580,1583,1586,1594,1597,1600],[11,1477,1478],{},"在Slate控件中，图标的使用除了直接引用图片外，还是有一些其他便利的方法的。",[11,1480,16],{},[11,1482,1483],{},"目前官方的版本已经到4.9.0了，但是由于FMod并没有更新对应版本的插件，项目无法迁移，故而依然使用4.8.3版本。",[35,1485,1486],{"id":1486},"自带图标",[11,1488,1489],{},"UE4自带的图标在对SWidget进行封装时比较容易遇到。UWidget在编辑器中的显示图标使用的就是自带的图标系统。通常为对如下的函数进行覆盖实现：",[48,1491,1494],{"className":1492,"code":1493,"language":53},[51],"const FSlateBrush* UCListViewWidget::GetEditorIcon() \n{     \n  return FUMGStyle::Get().GetBrush(\"Widget.TextBlock\"); \n}\n",[55,1495,1493],{"__ignoreMap":57},[11,1497,1498],{},"这些图标都是在引擎内部预定义的，通过搜索就能看到这些图标的定义。",[48,1500,1503],{"className":1501,"code":1502,"language":53},[51],"\u002F\u002F UMGStyle.cpp  \n\nStyle->Set(\"Widget.NativeWidgetHost\", new IMAGE_BRUSH(TEXT(\"NativeWidgetHost\"), Icon16x16));\n",[55,1504,1502],{"__ignoreMap":57},[11,1506,1507],{},"其中IMAGE_BRUSH是同一个文件中定义的宏",[48,1509,1512],{"className":1510,"code":1511,"language":53},[51],"#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( Style->RootToContentDir( RelativePath, TEXT(\".png\") ), __VA_ARGS__ )\n",[55,1513,1511],{"__ignoreMap":57},[11,1515,1516],{},"ICON16x16也同样是宏定义，再次就不赘述了。",[11,1518,1519],{},"自带图标也可以直接在控件中进行使用",[48,1521,1524],{"className":1522,"code":1523,"language":53},[51],"const FSlateBrush *m_Icon = FUMGStyle::Get().GetBrush(\"Palette.Icon\");\n \nreturn SNew(STableRow\u003C FFileListItemPtr >, OwnerTable)\n[\nSNew(SHorizontalBox)\n    + SHorizontalBox::Slot()\n    .HAlign(HAlign_Left)\n    .AutoWidth()\n    [\n        SNew(SImage)\n        .Image(m_Icon)\n    ]\n    + SHorizontalBox::Slot()\n    .HAlign(HAlign_Left)\n    [\n        SNew(STextBlock)\n        .Text(FText::FromString(Item->GetDisplayName()))\n        .Font(FSlateFontInfo(FPaths::EngineContentDir() \u002F TEXT(\"Slate\u002FFonts\u002FRoboto-Bold.ttf\"), 12))\n    ]\n];\n",[55,1525,1523],{"__ignoreMap":57},[35,1527,1529],{"id":1528},"icon-font","Icon Font",[11,1531,1532],{},"Icon Font添加图标确实非常的方便，UE4编辑器中也有使用到Font Awesome作为图标显示。",[11,1534,1535],{},"由于Font Awesome比较有名，项目中使用的也是它。",[11,1537,1538],{},"C++中的使用",[11,1540,1541],{},"要在C++中使用Font Awesome，首先要对UFont对象进行生成。",[48,1543,1546],{"className":1544,"code":1545,"language":53},[51],"FontAwesome = Cast\u003CUFont>(StaticLoadObject(UFont::StaticClass(), NULL, TEXT(\"Font'\u002FGame\u002FResource\u002FFont\u002Ffontawesome-webfont.fontawesome-webfont'\")));\n",[55,1547,1545],{"__ignoreMap":57},[11,1549,1550],{},"其中Font部分为直接对导入的字体进行引用复制所得。接下来只要使用STextBlock来使用相应的图标就可以了",[48,1552,1555],{"className":1553,"code":1554,"language":53},[51],"return SNew(STableRow\u003C FFileListItemPtr >, OwnerTable)\n    [\n        SNew(SHorizontalBox)\n        + SHorizontalBox::Slot()\n        .HAlign(HAlign_Left)\n        .AutoWidth()\n        .Padding(FMargin(0.0f, 3.0f, 1.0f, 0.0f))\n        [\n            SNew(STextBlock)\n            .Font(FSlateFontInfo(FontAwesome, 12))\n            .Text(FText::FromString(FString(Item->IsDirectory ? TEXT(\"\\xf07b\" \u002F*fa-folder*\u002F) : TEXT(\"\\xf15b\" \u002F*fa-file*\u002F))))\n        ]\n        + SHorizontalBox::Slot()\n        .HAlign(HAlign_Left)\n        .Padding(FMargin(3.0f, 3.0f, 0.0f, 0.0f))\n        [\n            SNew(STextBlock)\n            .Text(FText::FromString(Item->GetDisplayName()))\n            .Font(FSlateFontInfo(FPaths::EngineContentDir() \u002F TEXT(\"Slate\u002FFonts\u002FRoboto-Bold.ttf\"), 12))\n        ]\n \n    ];\n",[55,1556,1554],{"__ignoreMap":57},[11,1558,1559],{},"如果要在蓝图中进行使用的话，需要首先将字体的使用制定为对应的Icon Font。",[11,1561,1562,1563,1567],{},"通常Icon Font在提供的时候都会有每一个图标对应的Unicode编码，Font Awesome的编码集在[",[24,1564,955],{"href":1565,"rel":1566},"https:\u002F\u002Ffortawesome.github.io\u002FFont-Awesome\u002Fcheatsheet\u002F",[28],"]。",[11,1569,1570],{},"Font Awesome的编码集页面是可以直接复制对应的字符的，但是也有只提供了编码集的情况出现。",[11,1572,1573],{},"只要有编码集就可以对Icon进行使用了，要输入对应的Unicode码有很多种方式，这里提供两种。",[11,1575,1576],{},"Win+R输入并运行charmap，在其中选择对应的图标并复制其Unicode码。",[11,1578,1579],{},"在Word中输入对应的Unicode码，选中这些值按Alt+X就可以实现转换。",[11,1581,1582],{},"由于UE4编辑器中使用的字体并非对应的Icon Font，所以会以乱码显示，不过并不影响实际使用。",[35,1584,1585],{"id":1585},"排版",[11,1587,1588,1589,1395],{},"使用到图标的时候有时需要对布局进行控制，布局的具体操作可以参看官方的",[24,1590,1593],{"href":1591,"rel":1592},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FProgramming\u002FSlate\u002FWidgets\u002Findex.html",[28],"Slate控件文档",[11,1595,1596],{},"其中需要注意的是，Padding布局使用的是数据结构FMargin。作用参看FMargin本身的构造函数定义即可，只是Padding参数本身对FMargin进行了封装，光看官方的例子会有一定的迷惑性。",[11,1598,1599],{},"对于FMargin，当参数为4个时分别对应Left, Top, Right, Bottom；为2个时对应Horizontal和Vertical；为1个时则对应所有方向。",[11,1601,1602],{},"另外要注意的是在列表中AutoWidth必须手动指定，并不像文档中所描述的一样是默认启动的。",{"title":57,"searchDepth":142,"depth":149,"links":1604},[1605,1606,1607],{"id":1486,"depth":142,"text":1486},{"id":1528,"depth":142,"text":1529},{"id":1585,"depth":142,"text":1585},{"layout":917,"status":918,"published":145,"author":1609,"author_login":1218,"author_email":1219,"author_url":339,"wordpress_id":1610,"wordpress_url":1611,"date_gmt":1612,"excerpt":1613},{"display_name":1218,"login":1218,"email":1219,"url":339},1524,"\u002F\u002F?p=1524","2015-08-18 04:53:15 +0000",{"type":8,"value":1614},[1615],[11,1616,1478],{},"\u002F2015-08-18-slate-icons",{"title":1473,"description":1478},"_legacy\u002F2015\u002F2015-08-18-slate-icons",[934,935,1621],"Icon","A8mfQicx3BCg5ItNQMw-s9Z2yH3w34f1SwIYUghvJeE",85,1788763178370]