├── .gitignore ├── Config ├── DefaultLGUI_ParticleSystem.ini └── FilterPlugin.ini ├── LGUI_ParticleSystem.uplugin ├── LICENSE ├── README.md ├── README_en.md └── Source └── LGUI_ParticleSystem ├── LGUI_ParticleSystem.Build.cs ├── Private ├── LGUIWorldParticleSystemComponent.cpp ├── LGUI_ParticleSystemModule.cpp ├── UIParticleSystem.cpp └── UIParticleSystemRendererItem.cpp └── Public ├── LGUIWorldParticleSystemComponent.h ├── LGUI_ParticleSystemModule.h ├── SLGUIParticleSystemUpdateAgentWidget.h ├── UIParticleSystem.h └── UIParticleSystemRendererItem.h /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore UE4 files and VS / XCode solutions and projects 2 | 3 | *.sln 4 | *.vcxproj 5 | *.xcodeproj 6 | *.vc.db 7 | Binaries/ 8 | Build/ 9 | DerivedDataCache/ 10 | Intermediate/ 11 | Saved/ 12 | 13 | ## Ignore Visual Studio temporary files, build results, and 14 | ## files generated by popular Visual Studio add-ons. 15 | 16 | # User-specific files 17 | *.suo 18 | *.user 19 | *.userosscache 20 | *.sln.docstates 21 | 22 | # User-specific files (MonoDevelop/Xamarin Studio) 23 | *.userprefs 24 | 25 | # Build results 26 | [Dd]ebug/ 27 | [Dd]ebugPublic/ 28 | [Rr]elease/ 29 | [Rr]eleases/ 30 | x64/ 31 | x86/ 32 | build/ 33 | bld/ 34 | [Bb]in/ 35 | [Oo]bj/ 36 | 37 | # Visual Studo 2015 cache/options directory 38 | .vs/ 39 | 40 | # MSTest test Results 41 | [Tt]est[Rr]esult*/ 42 | [Bb]uild[Ll]og.* 43 | 44 | # NUNIT 45 | *.VisualState.xml 46 | TestResult.xml 47 | 48 | # Build Results of an ATL Project 49 | [Dd]ebugPS/ 50 | [Rr]eleasePS/ 51 | dlldata.c 52 | 53 | *_i.c 54 | *_p.c 55 | *_i.h 56 | *.ilk 57 | *.meta 58 | *.obj 59 | *.pch 60 | *.pdb 61 | *.pgc 62 | *.pgd 63 | *.rsp 64 | *.sbr 65 | *.tlb 66 | *.tli 67 | *.tlh 68 | *.tmp 69 | *.tmp_proj 70 | *.log 71 | *.vspscc 72 | *.vssscc 73 | .builds 74 | *.pidb 75 | *.svclog 76 | *.scc 77 | 78 | # Chutzpah Test files 79 | _Chutzpah* 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | 94 | # TFS 2012 Local Workspace 95 | $tf/ 96 | 97 | # Guidance Automation Toolkit 98 | *.gpState 99 | 100 | # ReSharper is a .NET coding add-in 101 | _ReSharper*/ 102 | *.[Rr]e[Ss]harper 103 | *.DotSettings.user 104 | 105 | # JustCode is a .NET coding addin-in 106 | .JustCode 107 | 108 | # TeamCity is a build add-in 109 | _TeamCity* 110 | 111 | # DotCover is a Code Coverage Tool 112 | *.dotCover 113 | 114 | # NCrunch 115 | _NCrunch_* 116 | .*crunch*.local.xml 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | *.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | 158 | # Windows Azure Build Output 159 | csx/ 160 | *.build.csdef 161 | 162 | # Windows Store app package directory 163 | AppPackages/ 164 | 165 | # Others 166 | *.[Cc]ache 167 | ClientBin/ 168 | [Ss]tyle[Cc]op.* 169 | ~$* 170 | *~ 171 | *.dbmdl 172 | *.dbproj.schemaview 173 | *.pfx 174 | *.publishsettings 175 | node_modules/ 176 | bower_components/ 177 | 178 | # RIA/Silverlight projects 179 | Generated_Code/ 180 | 181 | # Backup & report files from converting an old project file 182 | # to a newer Visual Studio version. Backup files are not needed, 183 | # because we have git ;-) 184 | _UpgradeReport_Files/ 185 | Backup*/ 186 | UpgradeLog*.XML 187 | UpgradeLog*.htm 188 | 189 | # SQL Server files 190 | *.mdf 191 | *.ldf 192 | 193 | # Business Intelligence projects 194 | *.rdl.data 195 | *.bim.layout 196 | *.bim_*.settings 197 | 198 | # Microsoft Fakes 199 | FakesAssemblies/ 200 | 201 | # Node.js Tools for Visual Studio 202 | .ntvs_analysis.dat 203 | 204 | # Visual Studio 6 build log 205 | *.plg 206 | 207 | # Visual Studio 6 workspace options file 208 | *.opt -------------------------------------------------------------------------------- /Config/DefaultLGUI_ParticleSystem.ini: -------------------------------------------------------------------------------- 1 | [CoreRedirects] 2 | +PropertyRedirects=(OldName="UIParticleSystem.NormalMaterialMap",NewName="UIParticleSystem.ReplaceMaterialMap") 3 | -------------------------------------------------------------------------------- /Config/FilterPlugin.ini: -------------------------------------------------------------------------------- 1 | [FilterPlugin] 2 | ; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and 3 | ; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. 4 | ; 5 | ; Examples: 6 | ; /README.txt 7 | ; /Extras/... 8 | ; /Binaries/ThirdParty/*.dll 9 | /Config/DefaultLGUI_ParticleSystem.ini -------------------------------------------------------------------------------- /LGUI_ParticleSystem.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.2", 5 | "FriendlyName": "Niagara ParticleSystem renderer for LGUI", 6 | "Description": "Use Niagara ParticleSystem in LGUI, supports sprite and ribbon data type.", 7 | "Category": "UI", 8 | "CreatedBy": "LexLiu", 9 | "CreatedByURL": "", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "CanContainContent": true, 14 | "Installed": true, 15 | "Modules": [ 16 | { 17 | "Name": "LGUI_ParticleSystem", 18 | "Type": "Runtime", 19 | "LoadingPhase": "Default" 20 | } 21 | ], 22 | "Plugins": [ 23 | { 24 | "Name": "LGUI", 25 | "Enabled": true, 26 | "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/content/92c868e93e1d4dd596f87b200102f0cf" 27 | }, 28 | { 29 | "Name": "Niagara", 30 | "Enabled": true 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 liufei2008 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [English](./README_en.md) 2 | #Niagara ParticleSystem renderer for LGUI UE4 3 | --- 4 | **这是LGUI插件的一个扩展插件,需要LGUI 2.14.0 和 UE4.26 及以上版本** 5 | 此插件可以让Niagara粒子系统渲染到LGUI上。 6 | 效果演示:[bilibili](https://www.bilibili.com/video/bv1Pf4y1576u) [youtube](https://youtu.be/mrb-JNf44Us) 7 | -------------------------------------------------------------------------------- /README_en.md: -------------------------------------------------------------------------------- 1 | [English](./README_en.md) 2 | #Niagara ParticleSystem renderer for LGUI UE4 3 | --- 4 | **This is a LGUI's extension plugin, need LGUI version 2.14.0 and UE4.26 upward.** 5 | This plugin allows us to render Niagara ParticleSystem to LGUI. 6 | Check video: [bilibili](https://www.bilibili.com/video/bv1Pf4y1576u) [youtube](https://youtu.be/mrb-JNf44Us) 7 | -------------------------------------------------------------------------------- /Source/LGUI_ParticleSystem/LGUI_ParticleSystem.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2021-present LexLiu. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class LGUI_ParticleSystem : ModuleRules 6 | { 7 | public LGUI_ParticleSystem(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | 12 | { 13 | PublicIncludePaths.AddRange( 14 | new string[] 15 | { 16 | // ... add public include paths required here ... 17 | } 18 | ); 19 | 20 | 21 | PrivateIncludePaths.AddRange( 22 | new string[] 23 | { 24 | // ... add other private include paths required here ... 25 | } 26 | ); 27 | 28 | 29 | PublicDependencyModuleNames.AddRange( 30 | new string[] 31 | { 32 | "Core", 33 | "UMG", 34 | // ... add other public dependencies that you statically link with here ... 35 | } 36 | ); 37 | 38 | 39 | PrivateDependencyModuleNames.AddRange( 40 | new string[] 41 | { 42 | "CoreUObject", 43 | "Engine", 44 | "Slate", 45 | "SlateCore", 46 | "LGUI", 47 | "Niagara", 48 | // "RenderCore", 49 | // ... add private dependencies that you statically link with here ... 50 | } 51 | ); 52 | 53 | 54 | DynamicallyLoadedModuleNames.AddRange( 55 | new string[] 56 | { 57 | // ... add any modules that your module loads dynamically here ... 58 | } 59 | ); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Source/LGUI_ParticleSystem/Private/LGUIWorldParticleSystemComponent.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021-present LexLiu. All Rights Reserved. 2 | 3 | #include "LGUIWorldParticleSystemComponent.h" 4 | #include "NiagaraRibbonRendererProperties.h" 5 | #include "NiagaraSpriteRendererProperties.h" 6 | #include "NiagaraRenderer.h" 7 | #include "Core/LGUIMesh/LGUIMeshComponent.h" 8 | #include "Core/LGUIIndexBuffer.h" 9 | 10 | //PRAGMA_DISABLE_OPTIMIZATION 11 | 12 | ALGUIWorldParticleSystemActor::ALGUIWorldParticleSystemActor() 13 | { 14 | PrimaryActorTick.bCanEverTick = false; 15 | 16 | SpawnCollisionHandlingMethod = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; 17 | } 18 | ULGUIWorldParticleSystemComponent* ALGUIWorldParticleSystemActor::Emit(UNiagaraSystem* NiagaraSystemTemplate, bool AutoActivate) 19 | { 20 | // Find old component 21 | TArray OldComponents; 22 | GetComponents(OldComponents); 23 | 24 | // And destroy it 25 | for (ULGUIWorldParticleSystemComponent* Component : OldComponents) 26 | Component->DestroyComponent(); 27 | 28 | 29 | auto ParticleComponent = NewObject(this); 30 | 31 | ParticleComponent->SetAutoActivate(AutoActivate); 32 | ParticleComponent->SetAsset(NiagaraSystemTemplate); 33 | ParticleComponent->SetHiddenInGame(true); 34 | ParticleComponent->RegisterComponent(); 35 | ParticleComponent->SetAutoDestroy(false); 36 | //ParticleComponent->SetForceSolo(true); 37 | this->Niagara = ParticleComponent; 38 | this->RootComponent = ParticleComponent; 39 | return ParticleComponent; 40 | } 41 | 42 | void ULGUIWorldParticleSystemComponent::GetRenderEntries(TArray& Renderers) 43 | { 44 | if (!GetSystemInstance()) 45 | return; 46 | 47 | Renderers.Reset(); 48 | auto& Emitters = GetSystemInstance()->GetEmitters(); 49 | for (TSharedRef EmitterInst : Emitters) 50 | { 51 | if (auto Emitter = EmitterInst->GetCachedEmitter()) 52 | { 53 | if (Emitter->SimTarget == ENiagaraSimTarget::CPUSim) 54 | { 55 | auto& Properties = Emitter->GetRenderers(); 56 | 57 | for (UNiagaraRendererProperties* Property : Properties) 58 | { 59 | if (Property->GetIsEnabled() && Property->IsSimTargetSupported(Emitter->SimTarget)) 60 | { 61 | if (UNiagaraSpriteRendererProperties* SpriteRenderer = Cast(Property)) 62 | { 63 | FLGUINiagaraRendererEntry NewEntry(Property, EmitterInst, Emitter, SpriteRenderer->Material); 64 | Renderers.Add(NewEntry); 65 | } 66 | else if (UNiagaraRibbonRendererProperties* RibbonRenderer = Cast(Property)) 67 | { 68 | FLGUINiagaraRendererEntry NewEntry(Property, EmitterInst, Emitter, RibbonRenderer->Material); 69 | Renderers.Add(NewEntry); 70 | } 71 | } 72 | } 73 | } 74 | } 75 | } 76 | 77 | Algo::Sort(Renderers, [](FLGUINiagaraRendererEntry& FirstElement, FLGUINiagaraRendererEntry& SecondElement) {return FirstElement.RendererProperties->SortOrderHint < SecondElement.RendererProperties->SortOrderHint; }); 78 | } 79 | 80 | void ULGUIWorldParticleSystemComponent::SetTransformationForUIRendering(MyVector2 Location, MyVector2 Scale, float Angle) 81 | { 82 | const FVector NewLocation(Location.X, 0, Location.Y); 83 | const FVector NewScale(Scale.X, 1, Scale.Y); 84 | const FRotator NewRotation(0.f, 0.f, FMath::RadiansToDegrees(Angle)); 85 | 86 | SetRelativeTransform(FTransform(NewRotation, NewLocation, NewScale)); 87 | } 88 | 89 | void ULGUIWorldParticleSystemComponent::RenderUI(FLGUIMeshSection* UIMeshSection, FLGUINiagaraRendererEntry RendererEntry, float ScaleFactor, MyVector2 LocationOffset, float Alpha01, const int ParticleCountIncreaseAndDecrease) 90 | { 91 | if (!GetSystemInstance()) 92 | return; 93 | 94 | if (UNiagaraSpriteRendererProperties* SpriteRenderer = Cast(RendererEntry.RendererProperties)) 95 | { 96 | AddSpriteRendererData(UIMeshSection, RendererEntry.EmitterInstance, SpriteRenderer, ScaleFactor, LocationOffset, Alpha01, ParticleCountIncreaseAndDecrease); 97 | } 98 | else if (UNiagaraRibbonRendererProperties* RibbonRenderer = Cast(RendererEntry.RendererProperties)) 99 | { 100 | AddRibbonRendererData(UIMeshSection, RendererEntry.EmitterInstance, RibbonRenderer, ScaleFactor, LocationOffset, Alpha01, ParticleCountIncreaseAndDecrease); 101 | } 102 | } 103 | 104 | FORCEINLINE MyVector2 FastRotate(const MyVector2 Vector, float Sin, float Cos) 105 | { 106 | return MyVector2(Cos * Vector.X - Sin * Vector.Y, 107 | Sin * Vector.X + Cos * Vector.Y); 108 | } 109 | FORCEINLINE MyVector3 MakePositionVector(const MyVector2& InVector2D) 110 | { 111 | return MyVector3(0, InVector2D.X, InVector2D.Y); 112 | } 113 | 114 | void ULGUIWorldParticleSystemComponent::AddSpriteRendererData(FLGUIMeshSection* UIMeshSection 115 | , TSharedRef EmitterInst 116 | , UNiagaraSpriteRendererProperties* SpriteRenderer 117 | , float ScaleFactor, MyVector2 LocationOffset, float Alpha01 118 | , const int ParticleCountIncreaseAndDecrease 119 | ) 120 | { 121 | FVector ComponentLocation = this->GetRelativeLocation(); 122 | FVector ComponentScale = this->GetRelativeScale3D(); 123 | FRotator ComponentRotation = this->GetRelativeRotation(); 124 | float ComponentPitchRadians = FMath::DegreesToRadians(ComponentRotation.Pitch); 125 | 126 | FNiagaraDataSet& DataSet = EmitterInst->GetData(); 127 | FNiagaraDataBuffer& ParticleData = DataSet.GetCurrentDataChecked(); 128 | const int32 ParticleCount = ParticleData.GetNumInstances(); 129 | 130 | int VertexCount = ParticleCount * 4; 131 | int IndexCount = ParticleCount * 6; 132 | auto& VertexData = UIMeshSection->vertices; 133 | auto& IndexData = UIMeshSection->triangles; 134 | 135 | const int VertexCountIncreaseAndDecrease = ParticleCountIncreaseAndDecrease * 4; 136 | const int IndexCountIncreaseAndDecrease = ParticleCountIncreaseAndDecrease * 6; 137 | 138 | int NewTotalVertexCount = ((VertexCount / VertexCountIncreaseAndDecrease) + (VertexCount % VertexCountIncreaseAndDecrease > 0 ? 1 : 0)) * VertexCountIncreaseAndDecrease; 139 | VertexData.SetNumZeroed(NewTotalVertexCount); 140 | 141 | int NewTotalIndexCount = ((IndexCount / IndexCountIncreaseAndDecrease) + (IndexCount % IndexCountIncreaseAndDecrease > 0 ? 1 : 0)) * IndexCountIncreaseAndDecrease; 142 | IndexData.SetNumZeroed(NewTotalIndexCount); 143 | if (IndexData.Num() > IndexCount)//set not required triangle index to zero 144 | { 145 | FMemory::Memzero(((uint8*)IndexData.GetData()) + IndexCount * sizeof(FLGUIIndexType), (IndexData.Num() - IndexCount) * sizeof(FLGUIIndexType)); 146 | } 147 | 148 | if (ParticleCount < 1) 149 | return; 150 | 151 | bool LocalSpace = EmitterInst->GetCachedEmitter()->bLocalSpace; 152 | 153 | //const float FakeDepthScaler = 1 / WidgetProperties->FakeDepthScaleDistance; 154 | 155 | auto SubImageSize = (MyVector2)SpriteRenderer->SubImageSize; 156 | auto SubImageDelta = MyVector2::UnitVector / SubImageSize; 157 | 158 | #if ENGINE_MAJOR_VERSION >= 5 159 | const auto PositionData = FNiagaraDataSetAccessor::CreateReader(DataSet, SpriteRenderer->PositionBinding.GetDataSetBindableVariable().GetName()); 160 | #else 161 | const auto PositionData = FNiagaraDataSetAccessor::CreateReader(DataSet, SpriteRenderer->PositionBinding.GetDataSetBindableVariable().GetName()); 162 | #endif 163 | const auto ColorData = FNiagaraDataSetAccessor::CreateReader(DataSet, SpriteRenderer->ColorBinding.GetDataSetBindableVariable().GetName()); 164 | const auto VelocityData = FNiagaraDataSetAccessor::CreateReader(DataSet, SpriteRenderer->VelocityBinding.GetDataSetBindableVariable().GetName()); 165 | const auto SizeData = FNiagaraDataSetAccessor::CreateReader(DataSet, SpriteRenderer->SpriteSizeBinding.GetDataSetBindableVariable().GetName()); 166 | const auto RotationData = FNiagaraDataSetAccessor::CreateReader(DataSet, SpriteRenderer->SpriteRotationBinding.GetDataSetBindableVariable().GetName()); 167 | const auto SubImageData = FNiagaraDataSetAccessor::CreateReader(DataSet, SpriteRenderer->SubImageIndexBinding.GetDataSetBindableVariable().GetName()); 168 | const auto DynamicMaterialData = FNiagaraDataSetAccessor::CreateReader(DataSet, SpriteRenderer->DynamicMaterialBinding.GetDataSetBindableVariable().GetName()); 169 | 170 | auto GetParticlePosition2D = [&PositionData](int32 Index) 171 | { 172 | const auto Position3D = PositionData.GetSafe(Index, MyVector3::ZeroVector); 173 | return MyVector2(Position3D.X, Position3D.Z); 174 | }; 175 | 176 | auto GetParticleDepth = [&PositionData](int32 Index) 177 | { 178 | return PositionData.GetSafe(Index, MyVector3::ZeroVector).Y; 179 | }; 180 | 181 | auto GetParticleColor = [&ColorData](int32 Index) 182 | { 183 | return ColorData.GetSafe(Index, FLinearColor::White); 184 | }; 185 | 186 | auto GetParticleVelocity2D = [&VelocityData](int32 Index) 187 | { 188 | const auto Velocity3D = VelocityData.GetSafe(Index, MyVector3::ZeroVector); 189 | return MyVector2(Velocity3D.X, -Velocity3D.Z); 190 | }; 191 | 192 | auto GetParticleSize = [&SizeData](int32 Index) 193 | { 194 | return SizeData.GetSafe(Index, MyVector2::ZeroVector); 195 | }; 196 | 197 | auto GetParticleRotation = [&RotationData](int32 Index) 198 | { 199 | return RotationData.GetSafe(Index, 0.f); 200 | }; 201 | 202 | auto GetParticleSubImage = [&SubImageData](int32 Index) 203 | { 204 | return SubImageData.GetSafe(Index, 0.f); 205 | }; 206 | 207 | auto GetDynamicMaterialData = [&DynamicMaterialData](int32 Index) 208 | { 209 | return DynamicMaterialData.GetSafe(Index, MyVector4(0.f, 0.f, 0.f, 0.f)); 210 | }; 211 | 212 | for (int ParticleIndex = 0; ParticleIndex < ParticleCount; ++ParticleIndex) 213 | { 214 | auto ParticlePosition = GetParticlePosition2D(ParticleIndex) * ScaleFactor; 215 | auto ParticleSize = GetParticleSize(ParticleIndex) * ScaleFactor; 216 | 217 | if (LocalSpace) 218 | { 219 | ParticlePosition *= MyVector2(ComponentScale.X, ComponentScale.Z); 220 | ParticlePosition = ParticlePosition.GetRotated(-ComponentRotation.Pitch); 221 | ParticlePosition += LocationOffset; 222 | ParticlePosition += MyVector2(ComponentLocation.X, ComponentLocation.Z) * ScaleFactor; 223 | 224 | ParticleSize *= MyVector2(ComponentScale.X, ComponentScale.Z); 225 | } 226 | else 227 | { 228 | ParticlePosition += LocationOffset; 229 | } 230 | 231 | 232 | //if (WidgetProperties->FakeDepthScale) 233 | //{ 234 | // const float ParticleDepth = (-GetParticleDepth(ParticleIndex) + WidgetProperties->FakeDepthScaleDistance) * FakeDepthScaler; 235 | // ParticleSize *= ParticleDepth; 236 | //} 237 | 238 | 239 | const MyVector2 ParticleHalfSize = ParticleSize * 0.5; 240 | 241 | 242 | FColor ParticleColor = GetParticleColor(ParticleIndex).ToFColor(false); 243 | ParticleColor.A = ParticleColor.A * Alpha01; 244 | 245 | 246 | float ParticleRotationSin = 0, ParticleRotationCos = 0; 247 | 248 | if (SpriteRenderer->Alignment == ENiagaraSpriteAlignment::VelocityAligned) 249 | { 250 | const MyVector2 ParticleVelocity = GetParticleVelocity2D(ParticleIndex); 251 | 252 | ParticleRotationCos = MyVector2::DotProduct(ParticleVelocity.GetSafeNormal(), MyVector2(0.f, 1.f)); 253 | const float SinSign = FMath::Sign(MyVector2::DotProduct(ParticleVelocity, MyVector2(1.f, 0.f))); 254 | 255 | if (LocalSpace) 256 | { 257 | const float ParticleRotation = FMath::Acos(ParticleRotationCos * SinSign) - ComponentPitchRadians; 258 | FMath::SinCos(&ParticleRotationSin, &ParticleRotationCos, ParticleRotation); 259 | } 260 | else 261 | { 262 | ParticleRotationSin = FMath::Sqrt(1 - ParticleRotationCos * ParticleRotationCos) * SinSign; 263 | } 264 | } 265 | else 266 | { 267 | float ParticleRotation = GetParticleRotation(ParticleIndex); 268 | 269 | if (LocalSpace) 270 | ParticleRotation -= ComponentRotation.Pitch; 271 | 272 | FMath::SinCos(&ParticleRotationSin, &ParticleRotationCos, FMath::DegreesToRadians(ParticleRotation)); 273 | } 274 | 275 | MyVector2 TextureCoordinates[4]; 276 | 277 | if (SubImageSize != MyVector2(1.f, 1.f)) 278 | { 279 | const float ParticleSubImage = GetParticleSubImage(ParticleIndex); 280 | const int Row = (int)FMath::Floor(ParticleSubImage / SubImageSize.X) % (int)SubImageSize.Y; 281 | const int Column = (int)(ParticleSubImage) % (int)(SubImageSize.X); 282 | 283 | const float LeftUV = SubImageDelta.X * Column; 284 | const float Right = SubImageDelta.X * (Column + 1); 285 | const float TopUV = SubImageDelta.Y * Row; 286 | const float BottomUV = SubImageDelta.Y * (Row + 1); 287 | 288 | TextureCoordinates[0] = MyVector2(LeftUV, TopUV); 289 | TextureCoordinates[1] = MyVector2(Right, TopUV); 290 | TextureCoordinates[2] = MyVector2(LeftUV, BottomUV); 291 | TextureCoordinates[3] = MyVector2(Right, BottomUV); 292 | } 293 | else 294 | { 295 | TextureCoordinates[0] = MyVector2(0.f, 0.f); 296 | TextureCoordinates[1] = MyVector2(1.f, 0.f); 297 | TextureCoordinates[2] = MyVector2(0.f, 1.f); 298 | TextureCoordinates[3] = MyVector2(1.f, 1.f); 299 | } 300 | 301 | 302 | const auto MaterialData = GetDynamicMaterialData(ParticleIndex); 303 | 304 | MyVector2 PositionArray[4]; 305 | PositionArray[0] = FastRotate(MyVector2(-ParticleHalfSize.X, -ParticleHalfSize.Y), ParticleRotationSin, ParticleRotationCos); 306 | PositionArray[1] = FastRotate(MyVector2(ParticleHalfSize.X, -ParticleHalfSize.Y), ParticleRotationSin, ParticleRotationCos); 307 | PositionArray[2] = -PositionArray[1]; 308 | PositionArray[3] = -PositionArray[0]; 309 | 310 | const int VertexIndex = ParticleIndex * 4; 311 | const int indexIndex = ParticleIndex * 6; 312 | 313 | 314 | for (int i = 0; i < 4; ++i) 315 | { 316 | VertexData[VertexIndex + i].Position = MakePositionVector(PositionArray[i] + ParticlePosition); 317 | VertexData[VertexIndex + i].Color = ParticleColor; 318 | VertexData[VertexIndex + i].TextureCoordinate[0] = TextureCoordinates[i]; 319 | VertexData[VertexIndex + i].TextureCoordinate[1].X = MaterialData.X; 320 | VertexData[VertexIndex + i].TextureCoordinate[1].Y = MaterialData.Y; 321 | VertexData[VertexIndex + i].TextureCoordinate[2].X = MaterialData.Z; 322 | VertexData[VertexIndex + i].TextureCoordinate[2].Y = MaterialData.W; 323 | } 324 | 325 | 326 | IndexData[indexIndex] = VertexIndex; 327 | IndexData[indexIndex + 1] = VertexIndex + 1; 328 | IndexData[indexIndex + 2] = VertexIndex + 2; 329 | 330 | IndexData[indexIndex + 3] = VertexIndex + 2; 331 | IndexData[indexIndex + 4] = VertexIndex + 1; 332 | IndexData[indexIndex + 5] = VertexIndex + 3; 333 | } 334 | } 335 | 336 | void ULGUIWorldParticleSystemComponent::AddRibbonRendererData(FLGUIMeshSection* UIMeshSection 337 | , TSharedRef EmitterInst 338 | , UNiagaraRibbonRendererProperties* RibbonRenderer 339 | , float ScaleFactor, MyVector2 LocationOffset, float Alpha01 340 | , const int ParticleCountIncreaseAndDecrease 341 | ) 342 | { 343 | FVector ComponentLocation = GetRelativeLocation(); 344 | FVector ComponentScale = GetRelativeScale3D(); 345 | FRotator ComponentRotation = GetRelativeRotation(); 346 | 347 | FNiagaraDataSet& DataSet = EmitterInst->GetData(); 348 | FNiagaraDataBuffer& ParticleData = DataSet.GetCurrentDataChecked(); 349 | const int32 ParticleCount = ParticleData.GetNumInstances(); 350 | 351 | auto& VertexData = UIMeshSection->vertices; 352 | auto& IndexData = UIMeshSection->triangles; 353 | VertexData.Reset(); 354 | IndexData.Reset(); 355 | 356 | if (ParticleCount < 2) 357 | return; 358 | 359 | const auto SortKeyReader = RibbonRenderer->SortKeyDataSetAccessor.GetReader(DataSet); 360 | 361 | const auto PositionData = RibbonRenderer->PositionDataSetAccessor.GetReader(DataSet); 362 | const auto ColorData = FNiagaraDataSetAccessor::CreateReader(DataSet, RibbonRenderer->ColorBinding.GetDataSetBindableVariable().GetName()); 363 | const auto RibbonWidthData = RibbonRenderer->SizeDataSetAccessor.GetReader(DataSet); 364 | 365 | const auto RibbonFullIDData = RibbonRenderer->RibbonFullIDDataSetAccessor.GetReader(DataSet); 366 | 367 | auto GetParticlePosition2D = [&PositionData](int32 Index) 368 | { 369 | const auto Position3D = PositionData.GetSafe(Index, MyVector3::ZeroVector); 370 | return MyVector2(Position3D.X, Position3D.Z); 371 | }; 372 | 373 | auto GetParticleColor = [&ColorData](int32 Index) 374 | { 375 | return ColorData.GetSafe(Index, FLinearColor::White); 376 | }; 377 | 378 | auto GetParticleWidth = [&RibbonWidthData](int32 Index) 379 | { 380 | return RibbonWidthData.GetSafe(Index, 0.f); 381 | }; 382 | 383 | auto AngleLargerThanPi = [](const MyVector2& A, const MyVector2& B) 384 | { 385 | float temp = A.X * B.Y - B.X * A.Y; 386 | return temp < 0; 387 | }; 388 | 389 | auto GenerateLinePoint = [AngleLargerThanPi](const MyVector2& InCurrentPoint, const MyVector2& InPrevPoint, const MyVector2& InNextPoint 390 | , float InLineLeftWidth, float InLineRightWidth 391 | , MyVector2& OutPosA, MyVector2& OutPosB 392 | ) 393 | { 394 | MyVector2 normalizedV1 = (InPrevPoint - InCurrentPoint).GetSafeNormal(); 395 | MyVector2 normalizedV2 = (InNextPoint - InCurrentPoint).GetSafeNormal(); 396 | if (normalizedV1 == -normalizedV2) 397 | { 398 | auto itemNormal = MyVector2(normalizedV2.Y, -normalizedV2.X); 399 | OutPosA = InCurrentPoint + InLineLeftWidth * itemNormal; 400 | OutPosB = InCurrentPoint - InLineRightWidth * itemNormal; 401 | } 402 | else 403 | { 404 | auto itemNormal = normalizedV1 + normalizedV2; 405 | itemNormal.Normalize(); 406 | if (itemNormal.X == 0 && itemNormal.Y == 0)//wrong normal 407 | { 408 | itemNormal = MyVector2(normalizedV2.Y, -normalizedV2.X); 409 | } 410 | float prevDotN = MyVector2::DotProduct(normalizedV1, itemNormal); 411 | float angle = FMath::Acos(prevDotN); 412 | float sin = FMath::Sin(angle); 413 | itemNormal = AngleLargerThanPi(normalizedV1, normalizedV2) ? -itemNormal : itemNormal; 414 | OutPosA = InCurrentPoint + InLineLeftWidth / sin * itemNormal; 415 | OutPosB = InCurrentPoint - InLineRightWidth / sin * itemNormal; 416 | } 417 | }; 418 | 419 | const bool LocalSpace = EmitterInst->GetCachedEmitter()->bLocalSpace; 420 | const bool FullIDs = RibbonFullIDData.IsValid(); 421 | const bool MultiRibbons = FullIDs; 422 | 423 | const int VertexCountIncreaseAndDecrease = ParticleCountIncreaseAndDecrease * 2; 424 | const int IndexCountIncreaseAndDecrease = ParticleCountIncreaseAndDecrease * 6; 425 | 426 | auto AddRibbonVerts = [&](TArray& RibbonIndices, int32& InOutVertexCount, int32& InOutIndexCount) 427 | { 428 | const int32 numParticlesInRibbon = RibbonIndices.Num(); 429 | if (numParticlesInRibbon < 3) 430 | return; 431 | 432 | int VertexCount = (numParticlesInRibbon - 1) * 2; 433 | int IndexCount = (numParticlesInRibbon - 2) * 6; 434 | 435 | auto CurrentVertexIndex = InOutVertexCount; 436 | auto CurrentIndexIndex = InOutIndexCount; 437 | InOutVertexCount += VertexCount; 438 | InOutIndexCount += IndexCount; 439 | 440 | int NewTotalVertexCount = ((InOutVertexCount / VertexCountIncreaseAndDecrease) + (InOutVertexCount % VertexCountIncreaseAndDecrease > 0 ? 1 : 0)) * VertexCountIncreaseAndDecrease; 441 | VertexData.SetNumZeroed(NewTotalVertexCount); 442 | 443 | int NewTotalIndexCount = ((InOutIndexCount / IndexCountIncreaseAndDecrease) + (InOutIndexCount % IndexCountIncreaseAndDecrease > 0 ? 1 : 0)) * IndexCountIncreaseAndDecrease; 444 | IndexData.SetNumZeroed(NewTotalIndexCount); 445 | 446 | const int32 StartDataIndex = RibbonIndices[0]; 447 | 448 | float TotalDistance = 0.0f; 449 | 450 | MyVector2 LastPosition = GetParticlePosition2D(StartDataIndex); 451 | MyVector2 CurrentPosition = MyVector2::ZeroVector; 452 | float CurrentWidth = 0.f; 453 | MyVector2 LastToCurrentVector = MyVector2::ZeroVector; 454 | float LastToCurrentSize = 0.f; 455 | float LastU0 = 0.f; 456 | float LastU1 = 0.f; 457 | 458 | MyVector2 LastParticleUIPosition = LastPosition * ScaleFactor; 459 | 460 | if (LocalSpace) 461 | { 462 | LastParticleUIPosition *= MyVector2(ComponentScale.X, ComponentScale.Z); 463 | LastParticleUIPosition = LastParticleUIPosition.GetRotated(-ComponentRotation.Pitch); 464 | LastParticleUIPosition += LocationOffset; 465 | 466 | LastParticleUIPosition += MyVector2(ComponentLocation.X, ComponentLocation.Z) * ScaleFactor; 467 | } 468 | else 469 | { 470 | LastParticleUIPosition += LocationOffset; 471 | } 472 | 473 | int32 CurrentIndex = 1; 474 | int32 CurrentDataIndex = RibbonIndices[CurrentIndex]; 475 | 476 | CurrentPosition = GetParticlePosition2D(CurrentDataIndex); 477 | LastToCurrentVector = CurrentPosition - LastPosition; 478 | LastToCurrentSize = LastToCurrentVector.Size(); 479 | 480 | // Normalize LastToCurrVec 481 | LastToCurrentVector *= 1.f / LastToCurrentSize; 482 | 483 | 484 | FColor InitialColor = GetParticleColor(StartDataIndex).ToFColor(false); 485 | InitialColor.A = InitialColor.A * Alpha01; 486 | const float InitialWidth = GetParticleWidth(StartDataIndex) * ScaleFactor; 487 | 488 | MyVector2 InitialPositionArray[2]; 489 | InitialPositionArray[0] = LastToCurrentVector.GetRotated(90.f) * InitialWidth * 0.5f; 490 | InitialPositionArray[1] = -InitialPositionArray[0]; 491 | 492 | for (int i = 0; i < 2; ++i) 493 | { 494 | VertexData[CurrentVertexIndex + i].Position = MakePositionVector(InitialPositionArray[i] + LastParticleUIPosition); 495 | VertexData[CurrentVertexIndex + i].Color = InitialColor; 496 | VertexData[CurrentVertexIndex + i].TextureCoordinate[0] = MyVector2(i, 0); 497 | } 498 | 499 | CurrentVertexIndex += 2; 500 | 501 | int32 NextIndex = CurrentIndex + 1; 502 | 503 | while (NextIndex < numParticlesInRibbon) 504 | { 505 | const int32 NextDataIndex = RibbonIndices[NextIndex]; 506 | const MyVector2 NextPosition = GetParticlePosition2D(NextDataIndex); 507 | MyVector2 CurrentToNextVector = NextPosition - CurrentPosition; 508 | const float CurrentToNextSize = CurrentToNextVector.Size(); 509 | CurrentWidth = GetParticleWidth(CurrentDataIndex) * ScaleFactor; 510 | FColor CurrentColor = GetParticleColor(CurrentDataIndex).ToFColor(false); 511 | CurrentColor.A = CurrentColor.A * Alpha01; 512 | 513 | // Normalize CurrToNextVec 514 | CurrentToNextVector *= 1.f / CurrentToNextSize; 515 | 516 | const MyVector2 CurrentTangent = (LastToCurrentVector + CurrentToNextVector).GetSafeNormal(); 517 | 518 | TotalDistance += LastToCurrentSize; 519 | 520 | MyVector2 CurrentPositionArray[2]; 521 | CurrentPositionArray[0] = CurrentTangent.GetRotated(90.f) * CurrentWidth * 0.5f; 522 | CurrentPositionArray[1] = -CurrentPositionArray[0]; 523 | 524 | MyVector2 CurrentParticleUIPosition = CurrentPosition * ScaleFactor; 525 | 526 | if (LocalSpace) 527 | { 528 | CurrentParticleUIPosition *= MyVector2(ComponentScale.X, ComponentScale.Z); 529 | CurrentParticleUIPosition = CurrentParticleUIPosition.GetRotated(-ComponentRotation.Pitch); 530 | CurrentParticleUIPosition += LocationOffset; 531 | CurrentParticleUIPosition += MyVector2(ComponentLocation.X, ComponentLocation.Z) * ScaleFactor; 532 | } 533 | else 534 | { 535 | CurrentParticleUIPosition += LocationOffset; 536 | } 537 | 538 | float CurrentU0 = 0.f; 539 | 540 | if (RibbonRenderer->UV0Settings.DistributionMode == ENiagaraRibbonUVDistributionMode::TiledOverRibbonLength) 541 | { 542 | CurrentU0 = LastU0 + LastToCurrentSize / RibbonRenderer->UV0Settings.TilingLength; 543 | } 544 | else 545 | { 546 | CurrentU0 = (float)CurrentIndex / (float)numParticlesInRibbon; 547 | } 548 | 549 | float CurrentU1 = 0.f; 550 | 551 | if (RibbonRenderer->UV1Settings.DistributionMode == ENiagaraRibbonUVDistributionMode::TiledOverRibbonLength) 552 | { 553 | CurrentU1 = LastU1 + LastToCurrentSize / RibbonRenderer->UV1Settings.TilingLength; 554 | } 555 | else 556 | { 557 | CurrentU1 = (float)CurrentIndex / (float)numParticlesInRibbon; 558 | } 559 | 560 | MyVector2 TextureCoordinates0[2]; 561 | TextureCoordinates0[0] = MyVector2(CurrentU0, 1.f); 562 | TextureCoordinates0[1] = MyVector2(CurrentU0, 0.f); 563 | 564 | MyVector2 TextureCoordinates1[2]; 565 | TextureCoordinates1[0] = MyVector2(CurrentU1, 1.f); 566 | TextureCoordinates1[1] = MyVector2(CurrentU1, 0.f); 567 | 568 | for (int i = 0; i < 2; ++i) 569 | { 570 | VertexData[CurrentVertexIndex + i].Position = MakePositionVector(CurrentPositionArray[i] + CurrentParticleUIPosition); 571 | VertexData[CurrentVertexIndex + i].Color = CurrentColor; 572 | VertexData[CurrentVertexIndex + i].TextureCoordinate[0] = TextureCoordinates0[i]; 573 | VertexData[CurrentVertexIndex + i].TextureCoordinate[1] = TextureCoordinates1[i]; 574 | } 575 | 576 | IndexData[CurrentIndexIndex] = CurrentVertexIndex - 2; 577 | IndexData[CurrentIndexIndex + 1] = CurrentVertexIndex - 1; 578 | IndexData[CurrentIndexIndex + 2] = CurrentVertexIndex; 579 | 580 | IndexData[CurrentIndexIndex + 3] = CurrentVertexIndex; 581 | IndexData[CurrentIndexIndex + 4] = CurrentVertexIndex - 1; 582 | IndexData[CurrentIndexIndex + 5] = CurrentVertexIndex + 1; 583 | 584 | 585 | CurrentVertexIndex += 2; 586 | CurrentIndexIndex += 6; 587 | 588 | CurrentIndex = NextIndex; 589 | CurrentDataIndex = NextDataIndex; 590 | LastPosition = CurrentPosition; 591 | LastParticleUIPosition = CurrentParticleUIPosition; 592 | CurrentPosition = NextPosition; 593 | LastToCurrentVector = CurrentToNextVector; 594 | LastToCurrentSize = CurrentToNextSize; 595 | LastU0 = CurrentU0; 596 | 597 | ++NextIndex; 598 | } 599 | }; 600 | 601 | if (!MultiRibbons) 602 | { 603 | TArray SortedIndices; 604 | for (int32 i = 0; i < ParticleCount; ++i) 605 | { 606 | SortedIndices.Add(i); 607 | } 608 | 609 | SortedIndices.Sort([&SortKeyReader](const int32& A, const int32& B) { return (SortKeyReader[A] < SortKeyReader[B]); }); 610 | 611 | int VertexCount = 0, IndexCount = 0; 612 | AddRibbonVerts(SortedIndices, VertexCount, IndexCount); 613 | if (IndexData.Num() > IndexCount) 614 | { 615 | FMemory::Memzero((void*)(IndexData.GetData() + IndexCount), (IndexData.Num() - IndexCount) * sizeof(FLGUIIndexType)); 616 | } 617 | } 618 | else 619 | { 620 | if (FullIDs) 621 | { 622 | TMap> MultiRibbonSortedIndices; 623 | 624 | for (int32 i = 0; i < ParticleCount; ++i) 625 | { 626 | TArray& Indices = MultiRibbonSortedIndices.FindOrAdd(RibbonFullIDData[i]); 627 | Indices.Add(i); 628 | } 629 | 630 | // Sort the ribbons by ID so that the draw order stays consistent. 631 | MultiRibbonSortedIndices.KeySort(TLess()); 632 | 633 | int VertexCount = 0, IndexCount = 0; 634 | for (TPair>& Pair : MultiRibbonSortedIndices) 635 | { 636 | TArray& SortedIndices = Pair.Value; 637 | SortedIndices.Sort([&SortKeyReader](const int32& A, const int32& B) { return (SortKeyReader[A] < SortKeyReader[B]); }); 638 | AddRibbonVerts(SortedIndices, VertexCount, IndexCount); 639 | } 640 | if (IndexData.Num() > IndexCount) 641 | { 642 | FMemory::Memzero(((uint8*)IndexData.GetData()) + IndexCount * sizeof(FLGUIIndexType), (IndexData.Num() - IndexCount) * sizeof(FLGUIIndexType)); 643 | } 644 | } 645 | } 646 | } 647 | //PRAGMA_ENABLE_OPTIMIZATION -------------------------------------------------------------------------------- /Source/LGUI_ParticleSystem/Private/LGUI_ParticleSystemModule.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021-present LexLiu. All Rights Reserved. 2 | 3 | #include "LGUI_ParticleSystemModule.h" 4 | 5 | #define LOCTEXT_NAMESPACE "UIParticleSystemModule" 6 | 7 | void FLGUI_ParticleSystemModule::StartupModule() 8 | { 9 | // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module 10 | } 11 | 12 | void FLGUI_ParticleSystemModule::ShutdownModule() 13 | { 14 | // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, 15 | // we call this function before unloading the module. 16 | } 17 | 18 | #undef LOCTEXT_NAMESPACE 19 | 20 | IMPLEMENT_MODULE(FLGUI_ParticleSystemModule, LGUI_ParticleSystem) -------------------------------------------------------------------------------- /Source/LGUI_ParticleSystem/Private/UIParticleSystem.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021-present LexLiu. All Rights Reserved. 2 | 3 | #include "UIParticleSystem.h" 4 | #include "Particles/ParticleSpriteEmitter.h" 5 | #include "CoreMinimal.h" 6 | #include "LGUIWorldParticleSystemComponent.h" 7 | #include "UIParticleSystemRendererItem.h" 8 | #include "Core/LGUIMesh/LGUIMeshComponent.h" 9 | #include "SLGUIParticleSystemUpdateAgentWidget.h" 10 | 11 | #define LOCTEXT_NAMESPACE "UIParticleSystem" 12 | 13 | #if ENGINE_MAJOR_VERSION >= 5 14 | typedef FVector3f MyVector3; 15 | typedef FVector2f MyVector2; 16 | typedef FVector4f MyVector4; 17 | #else 18 | typedef FVector MyVector3; 19 | typedef FVector2D MyVector2; 20 | typedef FVector4 MyVector4; 21 | #endif 22 | 23 | UUIParticleSystem::UUIParticleSystem(const FObjectInitializer& ObjectInitializer):Super(ObjectInitializer) 24 | { 25 | PrimaryComponentTick.bCanEverTick = false; 26 | PrimaryComponentTick.bStartWithTickEnabled = false; 27 | } 28 | 29 | void UUIParticleSystem::BeginPlay() 30 | { 31 | Super::BeginPlay(); 32 | 33 | if (!UpdateAgentWidget.IsValid()) 34 | { 35 | UpdateAgentWidget = SNew(SLGUIParticleSystemUpdateAgentWidget); 36 | GEngine->GameViewport->AddViewportWidgetContent(UpdateAgentWidget.ToSharedRef()); 37 | UpdateAgentWidget->OnPaintCallbackDelegate.BindUObject(this, &UUIParticleSystem::OnPaintUpdate); 38 | } 39 | if (IsValid(ParticleSystem)) 40 | { 41 | auto WorldParticleSystemActor = this->GetWorld()->SpawnActor(); 42 | #if WITH_EDITOR 43 | WorldParticleSystemActor->SetActorLabel(FString(TEXT("LGUI_PS_")) + this->GetOwner()->GetActorLabel()); 44 | #endif 45 | ParticleSystemInstance = WorldParticleSystemActor->Emit(ParticleSystem, bAutoActivateParticleSystem); 46 | 47 | if (bAutoActivateParticleSystem) 48 | { 49 | SetRenderEntries(); 50 | } 51 | } 52 | } 53 | 54 | void UUIParticleSystem::SetRenderEntries() 55 | { 56 | if (!RenderEntriesValid) 57 | { 58 | UWorld* World = this->GetWorld(); 59 | if (World) 60 | { 61 | ParticleSystemInstance->GetRenderEntries(RenderEntries); 62 | RenderEntriesValid = true; 63 | for (int i = 0; i < RenderEntries.Num(); i++) 64 | { 65 | auto ParticleSytemRendererItemActor = World->SpawnActor(); 66 | #if WITH_EDITOR 67 | ParticleSytemRendererItemActor->SetActorLabel(FString::Printf(TEXT("%s_%d"), *this->GetOwner()->GetActorLabel(), i)); 68 | #endif 69 | auto RendererItem = ParticleSytemRendererItemActor->GetUIParticleSystemRendererItem(); 70 | RendererItem->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); 71 | RendererItem->SetWidth(0); 72 | RendererItem->SetHeight(0); 73 | UMaterialInterface* Mat = RenderEntries[i].Material; 74 | if (auto FoundMatPtr = ReplaceMaterialMap.Find(Mat)) 75 | { 76 | Mat = *FoundMatPtr; 77 | } 78 | RendererItem->SetMaterial(Mat); 79 | RendererItem->Manager = this; 80 | UIParticleSystemRenderers.Add(RendererItem); 81 | } 82 | } 83 | } 84 | } 85 | void UUIParticleSystem::ActivateParticleSystem(bool Reset) 86 | { 87 | if (ParticleSystemInstance.IsValid()) 88 | { 89 | ParticleSystemInstance->Activate(Reset); 90 | SetRenderEntries(); 91 | } 92 | } 93 | 94 | void UUIParticleSystem::DeactivateParticleSystem() 95 | { 96 | if (ParticleSystemInstance.IsValid()) 97 | ParticleSystemInstance->Deactivate(); 98 | } 99 | 100 | void UUIParticleSystem::SetReplaceMaterialMap(const TMap& value) 101 | { 102 | ReplaceMaterialMap = value; 103 | for (int i = 0; i < UIParticleSystemRenderers.Num(); i++) 104 | { 105 | auto RendererItem = UIParticleSystemRenderers[i]; 106 | UMaterialInterface* Mat = RenderEntries[i].Material; 107 | if (auto FoundMatPtr = ReplaceMaterialMap.Find(Mat)) 108 | { 109 | Mat = *FoundMatPtr; 110 | } 111 | RendererItem->SetMaterial(Mat); 112 | UIParticleSystemRenderers.Add(RendererItem); 113 | } 114 | } 115 | 116 | DECLARE_CYCLE_STAT(TEXT("UIParticleSystem RenderToUI"), STAT_UIParticleSystem, STATGROUP_LGUI); 117 | 118 | void UUIParticleSystem::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) 119 | { 120 | Super::TickComponent(DeltaTime, TickType, ThisTickFunction); 121 | } 122 | void UUIParticleSystem::EndPlay(const EEndPlayReason::Type EndPlayReason) 123 | { 124 | Super::EndPlay(EndPlayReason); 125 | if (ParticleSystemInstance.IsValid()) 126 | { 127 | auto WorldParticleActor = ParticleSystemInstance->GetOwner(); 128 | if (IsValid(WorldParticleActor)) 129 | { 130 | WorldParticleActor->Destroy(); 131 | } 132 | ParticleSystemInstance.Reset(); 133 | } 134 | RenderEntries.Empty(); 135 | for (auto item : UIParticleSystemRenderers) 136 | { 137 | if (IsValid(item)) 138 | { 139 | auto itemActor = item->GetOwner(); 140 | if (IsValid(itemActor)) 141 | { 142 | itemActor->Destroy(); 143 | } 144 | } 145 | } 146 | UIParticleSystemRenderers.Empty(); 147 | if (UpdateAgentWidget.IsValid()) 148 | { 149 | if (IsValid(GEngine) && IsValid(GEngine->GameViewport)) 150 | { 151 | GEngine->GameViewport->RemoveViewportWidgetContent(UpdateAgentWidget.ToSharedRef()); 152 | } 153 | } 154 | } 155 | 156 | void UUIParticleSystem::OnPaintUpdate() 157 | { 158 | if (ParticleSystemInstance.IsValid()) 159 | { 160 | if (GetIsUIActiveInHierarchy()) 161 | { 162 | SCOPE_CYCLE_COUNTER(STAT_UIParticleSystem); 163 | 164 | //auto layoutScale = this->GetRootCanvas()->GetCanvasScale(); 165 | auto layoutScale = 1.0f; 166 | //auto locationOffset = MyVector2(-rootUIItem->GetWidth() * 0.5f, -rootUIItem->GetHeight() * 0.5f); 167 | auto locationOffset = MyVector2::ZeroVector; 168 | 169 | //update transform 170 | auto rootUIItem = this->GetRenderCanvas()->GetUIItem(); 171 | auto rootSpaceLocation = rootUIItem->GetComponentTransform().InverseTransformPosition(this->GetComponentLocation()); 172 | auto rootSpaceLocation2D = MyVector2(rootSpaceLocation.Y, rootSpaceLocation.Z); 173 | auto scale3D = this->GetRelativeScale3D(); 174 | auto scale2D = MyVector2(scale3D.Y, scale3D.Z); 175 | ParticleSystemInstance->SetTransformationForUIRendering(rootSpaceLocation2D, scale2D, this->GetRelativeRotation().Roll); 176 | const int ParticleCountIncreaseAndDecrease = 50;//only recreate RenderResource when particle count increase or decrease N count, good for performance 177 | for (int i = 0; i < RenderEntries.Num(); i++) 178 | { 179 | auto UIMeshSection = UIParticleSystemRenderers[i]->GetMeshSection(); 180 | auto UIMesh = UIParticleSystemRenderers[i]->GetUIMesh(); 181 | if (UIMeshSection.IsValid()) 182 | { 183 | auto MeshSectionPtr = UIMeshSection.Pin(); 184 | ParticleSystemInstance->RenderUI(MeshSectionPtr.Get(), RenderEntries[i], layoutScale, locationOffset, bUseAlpha ? UIParticleSystemRenderers[i]->GetFinalAlpha01() : 1.0f, ParticleCountIncreaseAndDecrease); 185 | if (MeshSectionPtr->prevVertexCount == MeshSectionPtr->vertices.Num() && MeshSectionPtr->prevIndexCount == MeshSectionPtr->triangles.Num()) 186 | { 187 | if (MeshSectionPtr->prevVertexCount > 0 && MeshSectionPtr->prevIndexCount > 0) 188 | { 189 | UIMesh->UpdateMeshSectionData(MeshSectionPtr, true, 1); 190 | } 191 | } 192 | else 193 | { 194 | MeshSectionPtr->prevVertexCount = MeshSectionPtr->vertices.Num(); 195 | MeshSectionPtr->prevIndexCount = MeshSectionPtr->triangles.Num(); 196 | UIMesh->CreateMeshSectionData(MeshSectionPtr); 197 | } 198 | } 199 | } 200 | } 201 | } 202 | } 203 | 204 | #if WITH_EDITOR 205 | void UUIParticleSystem::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) 206 | { 207 | Super::PostEditChangeProperty(PropertyChangedEvent); 208 | } 209 | #endif 210 | void UUIParticleSystem::SetUseAlpha(bool value) 211 | { 212 | if (bUseAlpha != value) 213 | { 214 | bUseAlpha = value; 215 | } 216 | } 217 | void UUIParticleSystem::SetParticleSystemTemplate(UNiagaraSystem* value) 218 | { 219 | if (ParticleSystem != value) 220 | { 221 | ParticleSystem = value; 222 | if (ParticleSystemInstance.IsValid()) 223 | { 224 | ParticleSystemInstance->SetAsset(ParticleSystem); 225 | ParticleSystemInstance->ResetSystem(); 226 | } 227 | } 228 | } 229 | 230 | 231 | AUIParticleSystemActor::AUIParticleSystemActor() 232 | { 233 | PrimaryActorTick.bCanEverTick = false; 234 | 235 | UIParticleSystem = CreateDefaultSubobject(TEXT("UIParticleSystem")); 236 | RootComponent = UIParticleSystem; 237 | } 238 | 239 | #undef LOCTEXT_NAMESPACE 240 | -------------------------------------------------------------------------------- /Source/LGUI_ParticleSystem/Private/UIParticleSystemRendererItem.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021-present LexLiu. All Rights Reserved. 2 | 3 | #include "UIParticleSystemRendererItem.h" 4 | #include "CoreMinimal.h" 5 | #include "Core/LGUIMesh/LGUIMeshComponent.h" 6 | #include "Materials/MaterialInstanceDynamic.h" 7 | #include "UIParticleSystem.h" 8 | #include "Core/UIDrawcall.h" 9 | 10 | 11 | #define LOCTEXT_NAMESPACE "UIParticleSystemRendererItem" 12 | 13 | UUIParticleSystemRendererItem::UUIParticleSystemRendererItem(const FObjectInitializer& ObjectInitializer):Super(ObjectInitializer) 14 | { 15 | PrimaryComponentTick.bCanEverTick = false; 16 | PrimaryComponentTick.bStartWithTickEnabled = false; 17 | } 18 | 19 | void UUIParticleSystemRendererItem::BeginPlay() 20 | { 21 | Super::BeginPlay(); 22 | } 23 | void UUIParticleSystemRendererItem::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) 24 | { 25 | Super::TickComponent(DeltaTime, TickType, ThisTickFunction); 26 | } 27 | 28 | 29 | #if WITH_EDITOR 30 | void UUIParticleSystemRendererItem::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) 31 | { 32 | Super::PostEditChangeProperty(PropertyChangedEvent); 33 | } 34 | #endif 35 | 36 | void UUIParticleSystemRendererItem::SetMaterial(UMaterialInterface* InMaterial) 37 | { 38 | if (Material != InMaterial) 39 | { 40 | Material = InMaterial; 41 | if (RenderCanvas.IsValid()) 42 | { 43 | if (drawcall.IsValid()) 44 | { 45 | drawcall->bMaterialChanged = true; 46 | } 47 | MarkCanvasUpdate(true, false, false); 48 | } 49 | } 50 | } 51 | 52 | void UUIParticleSystemRendererItem::OnMeshDataReady() 53 | { 54 | Super::OnMeshDataReady(); 55 | if (drawcall->DrawcallMeshSection.IsValid() && Material.IsValid()) 56 | { 57 | drawcall->DrawcallMesh->SetMeshSectionMaterial(drawcall->DrawcallMeshSection.Pin(), Material.Get()); 58 | } 59 | } 60 | 61 | bool UUIParticleSystemRendererItem::HaveValidData()const 62 | { 63 | return true;//because this UIParticleSystemRendererItem is created by UIParticleSystem, and UIParticleSystem will always check if have valid data 64 | } 65 | 66 | UMaterialInterface* UUIParticleSystemRendererItem::GetMaterial()const 67 | { 68 | return Material.Get(); 69 | } 70 | 71 | AUIParticleSystemRendererItemActor::AUIParticleSystemRendererItemActor() 72 | { 73 | PrimaryActorTick.bCanEverTick = false; 74 | 75 | UIParticleSystemRendererItem = CreateDefaultSubobject(TEXT("UIParticleSystemRendererItem")); 76 | RootComponent = UIParticleSystemRendererItem; 77 | } 78 | 79 | #undef LOCTEXT_NAMESPACE 80 | -------------------------------------------------------------------------------- /Source/LGUI_ParticleSystem/Public/LGUIWorldParticleSystemComponent.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021-present LexLiu. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "NiagaraComponent.h" 7 | #include "LGUIWorldParticleSystemComponent.generated.h" 8 | 9 | #if ENGINE_MAJOR_VERSION >= 5 10 | typedef FVector3f MyVector3; 11 | typedef FVector2f MyVector2; 12 | typedef FVector4f MyVector4; 13 | #else 14 | typedef FVector MyVector3; 15 | typedef FVector2D MyVector2; 16 | typedef FVector4 MyVector4; 17 | #endif 18 | 19 | struct FLGUIMeshSection; 20 | class FNiagaraEmitterInstance; 21 | class UNiagaraSpriteRendererProperties; 22 | class UNiagaraRibbonRendererProperties; 23 | 24 | struct FLGUINiagaraRendererEntry 25 | { 26 | FLGUINiagaraRendererEntry(UNiagaraRendererProperties* PropertiesIn, TSharedRef EmitterInstIn, UNiagaraEmitter* EmitterIn, UMaterialInterface* MaterialIn) 27 | : RendererProperties(PropertiesIn), EmitterInstance(EmitterInstIn), Emitter(EmitterIn), Material(MaterialIn) {} 28 | 29 | UNiagaraRendererProperties* RendererProperties; 30 | TSharedRef EmitterInstance; 31 | UNiagaraEmitter* Emitter; 32 | UMaterialInterface* Material; 33 | }; 34 | 35 | UCLASS() 36 | class LGUI_PARTICLESYSTEM_API ULGUIWorldParticleSystemComponent : public UNiagaraComponent 37 | { 38 | GENERATED_BODY() 39 | public: 40 | void GetRenderEntries(TArray& Renderers); 41 | 42 | void SetTransformationForUIRendering(MyVector2 Location, MyVector2 Scale, float Angle); 43 | 44 | void RenderUI(FLGUIMeshSection* UIMeshSection, FLGUINiagaraRendererEntry RendererEntry, float ScaleFactor, MyVector2 LocationOffset, float Alpha01, const int ParticleCountIncreaseAndDecrease); 45 | private: 46 | void AddSpriteRendererData(FLGUIMeshSection* UIMeshSection 47 | , TSharedRef EmitterInst 48 | , UNiagaraSpriteRendererProperties* SpriteRenderer 49 | , float ScaleFactor, MyVector2 LocationOffset, float Alpha01 50 | , const int ParticleCountIncreaseAndDecrease 51 | ); 52 | void AddRibbonRendererData(FLGUIMeshSection* UIMeshSection 53 | , TSharedRef EmitterInst 54 | , UNiagaraRibbonRendererProperties* RibbonRenderer 55 | , float ScaleFactor, MyVector2 LocationOffset, float Alpha01 56 | , const int ParticleCountIncreaseAndDecrease 57 | ); 58 | }; 59 | 60 | UCLASS(ClassGroup = LGUI, NotPlaceable) 61 | class LGUI_PARTICLESYSTEM_API ALGUIWorldParticleSystemActor : public AActor 62 | { 63 | GENERATED_BODY() 64 | 65 | public: 66 | ALGUIWorldParticleSystemActor(); 67 | 68 | ULGUIWorldParticleSystemComponent* Emit(UNiagaraSystem* NiagaraSystemTemplate, bool AutoActivate); 69 | UPROPERTY(VisibleAnywhere, Transient) 70 | ULGUIWorldParticleSystemComponent* Niagara; 71 | }; 72 | -------------------------------------------------------------------------------- /Source/LGUI_ParticleSystem/Public/LGUI_ParticleSystemModule.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021-present LexLiu. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Runtime/Core/Public/Modules/ModuleManager.h" 7 | 8 | class FLGUI_ParticleSystemModule : public IModuleInterface 9 | { 10 | public: 11 | 12 | /** IModuleInterface implementation */ 13 | virtual void StartupModule() override; 14 | virtual void ShutdownModule() override; 15 | }; -------------------------------------------------------------------------------- /Source/LGUI_ParticleSystem/Public/SLGUIParticleSystemUpdateAgentWidget.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021-present LexLiu. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "Widgets/SLeafWidget.h" 6 | 7 | /** 8 | * 9 | */ 10 | class LGUI_PARTICLESYSTEM_API SLGUIParticleSystemUpdateAgentWidget : public SLeafWidget 11 | { 12 | public: 13 | SLATE_BEGIN_ARGS(SLGUIParticleSystemUpdateAgentWidget) 14 | { 15 | } 16 | SLATE_END_ARGS() 17 | 18 | void Construct(const FArguments& Args) {}; 19 | 20 | virtual FVector2D ComputeDesiredSize(float LayoutScaleMultiplier)const override { return FVector2D::ZeroVector; } 21 | virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override 22 | { 23 | OnPaintCallbackDelegate.ExecuteIfBound(); 24 | return LayerId; 25 | } 26 | virtual void OnArrangeChildren(const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren) const override {} 27 | 28 | FSimpleDelegate OnPaintCallbackDelegate; 29 | }; 30 | -------------------------------------------------------------------------------- /Source/LGUI_ParticleSystem/Public/UIParticleSystem.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021-present LexLiu. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Core/ActorComponent/UIItem.h" 7 | #include "Core/Actor/UIBaseActor.h" 8 | #include "UIParticleSystem.generated.h" 9 | 10 | class UNiagaraSystem; 11 | 12 | UCLASS(ClassGroup = (LGUI), NotBlueprintable, meta = (BlueprintSpawnableComponent)) 13 | class LGUI_PARTICLESYSTEM_API UUIParticleSystem : public UUIItem 14 | { 15 | GENERATED_BODY() 16 | 17 | public: 18 | UUIParticleSystem(const FObjectInitializer& ObjectInitializer); 19 | 20 | virtual void BeginPlay()override; 21 | virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)override; 22 | virtual void EndPlay(const EEndPlayReason::Type EndPlayReason)override; 23 | private: 24 | TWeakObjectPtr ParticleSystemInstance = nullptr; 25 | void SetRenderEntries(); 26 | #if WITH_EDITOR 27 | virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; 28 | #endif 29 | void OnPaintUpdate(); 30 | 31 | TArray RenderEntries; 32 | bool RenderEntriesValid = false; 33 | UPROPERTY(Transient) 34 | TArray UIParticleSystemRenderers; 35 | TSharedPtr UpdateAgentWidget = nullptr; 36 | 37 | UPROPERTY(EditAnywhere, Category = "LGUI") 38 | UNiagaraSystem* ParticleSystem; 39 | /** Auto activate particle system when create it in begin play. */ 40 | UPROPERTY(EditAnywhere, Category = "LGUI", DisplayName = "Auto Activate") 41 | bool bAutoActivateParticleSystem = true; 42 | /** Particle color relate to this UI element's alpha. */ 43 | UPROPERTY(EditAnywhere, Category = "LGUI") 44 | bool bUseAlpha = true; 45 | /** Remap material for LGUI to render, if not assigned then use default material in particle system. */ 46 | UPROPERTY(EditAnywhere, Category = "LGUI") 47 | TMap ReplaceMaterialMap; 48 | public: 49 | UFUNCTION(BlueprintCallable, Category = "LGUI") 50 | class ULGUIWorldParticleSystemComponent* GetParticleSystemInstance()const { return ParticleSystemInstance.Get(); } 51 | UFUNCTION(BlueprintCallable, Category = "LGUI") 52 | UNiagaraSystem* GetParticleSystemTemplate()const { return ParticleSystem; } 53 | UFUNCTION(BlueprintCallable, Category = "LGUI") 54 | bool GetUseAlpha()const { return bUseAlpha; } 55 | UFUNCTION(BlueprintCallable, Category = "LGUI") 56 | const TMap& GetReplaceMaterialMap()const { return ReplaceMaterialMap; } 57 | 58 | UFUNCTION(BlueprintCallable, Category = "LGUI") 59 | void SetUseAlpha(bool value); 60 | UFUNCTION(BlueprintCallable, Category = "LGUI") 61 | void SetParticleSystemTemplate(UNiagaraSystem* value); 62 | UFUNCTION(BlueprintCallable, Category = "LGUI") 63 | void ActivateParticleSystem(bool Reset); 64 | UFUNCTION(BlueprintCallable, Category = "LGUI") 65 | void DeactivateParticleSystem(); 66 | UFUNCTION(BlueprintCallable, Category = "LGUI") 67 | void SetReplaceMaterialMap(const TMap& value); 68 | }; 69 | 70 | 71 | UCLASS(ClassGroup = LGUI) 72 | class LGUI_PARTICLESYSTEM_API AUIParticleSystemActor : public AUIBaseActor 73 | { 74 | GENERATED_BODY() 75 | 76 | public: 77 | AUIParticleSystemActor(); 78 | 79 | FORCEINLINE virtual UUIItem* GetUIItem()const override { return UIParticleSystem; } 80 | FORCEINLINE UUIParticleSystem* GetUIParticleSystem()const { return UIParticleSystem; } 81 | private: 82 | UPROPERTY(Category = "LGUI", VisibleAnywhere, BlueprintReadOnly, Transient, meta = (AllowPrivateAccess = "true")) 83 | class UUIParticleSystem* UIParticleSystem; 84 | 85 | }; 86 | -------------------------------------------------------------------------------- /Source/LGUI_ParticleSystem/Public/UIParticleSystemRendererItem.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021-present LexLiu. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Core/ActorComponent/UIDirectMeshRenderable.h" 7 | #include "Core/Actor/UIBaseActor.h" 8 | #include "Core/ActorComponent/LGUICanvas.h" 9 | #include "UIParticleSystemRendererItem.generated.h" 10 | 11 | class ULGUIWorldParticleSystemComponent; 12 | 13 | UCLASS() 14 | class LGUI_PARTICLESYSTEM_API UUIParticleSystemRendererItem : public UUIDirectMeshRenderable 15 | { 16 | GENERATED_BODY() 17 | 18 | public: 19 | UUIParticleSystemRendererItem(const FObjectInitializer& ObjectInitializer); 20 | 21 | virtual void BeginPlay()override; 22 | virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)override; 23 | 24 | #if WITH_EDITOR 25 | virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; 26 | #endif 27 | TWeakObjectPtr Manager = nullptr; 28 | virtual void SetMaterial(UMaterialInterface* InMaterial); 29 | protected: 30 | virtual void OnMeshDataReady()override; 31 | virtual bool HaveValidData()const override; 32 | virtual UMaterialInterface* GetMaterial()const override; 33 | 34 | TWeakObjectPtr Material = nullptr; 35 | }; 36 | 37 | UCLASS(ClassGroup = LGUI, Transient, NotPlaceable) 38 | class LGUI_PARTICLESYSTEM_API AUIParticleSystemRendererItemActor : public AUIBaseActor 39 | { 40 | GENERATED_BODY() 41 | 42 | public: 43 | AUIParticleSystemRendererItemActor(); 44 | 45 | FORCEINLINE virtual UUIItem* GetUIItem()const override { return UIParticleSystemRendererItem; } 46 | FORCEINLINE UUIParticleSystemRendererItem* GetUIParticleSystemRendererItem()const { return UIParticleSystemRendererItem; } 47 | private: 48 | UPROPERTY(Category = "LGUI", VisibleAnywhere, BlueprintReadOnly, Transient, meta = (AllowPrivateAccess = "true")) 49 | class UUIParticleSystemRendererItem* UIParticleSystemRendererItem; 50 | 51 | }; 52 | --------------------------------------------------------------------------------