diff --git a/.vsconfig b/.vsconfig index 3b919ea..b3c233d 100644 --- a/.vsconfig +++ b/.vsconfig @@ -1,12 +1,10 @@ { "version": "1.0", "components": [ - "Component.Unreal.Debugger", - "Component.Unreal.Ide", "Microsoft.Net.Component.4.6.2.TargetingPack", + "Microsoft.VisualStudio.Component.Unreal.Workspace", "Microsoft.VisualStudio.Component.VC.14.38.17.8.ATL", "Microsoft.VisualStudio.Component.VC.14.38.17.8.x86.x64", - "Microsoft.VisualStudio.Component.VC.Llvm.Clang", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", "Microsoft.VisualStudio.Component.Windows11SDK.22621", "Microsoft.VisualStudio.Workload.CoreEditor", diff --git a/BladeSmith.uproject b/BladeSmith.uproject index 89230e3..511e59d 100644 --- a/BladeSmith.uproject +++ b/BladeSmith.uproject @@ -1,6 +1,6 @@ { "FileVersion": 3, - "EngineAssociation": "{DE41D031-4C3F-817F-365A-5AAD9A1CE2AD}", + "EngineAssociation": "5.5", "Category": "", "Description": "", "Modules": [ diff --git a/Content/FirstPerson/Blueprints/BP_IronBlock.uasset b/Content/FirstPerson/Blueprints/BP_IronBlock.uasset index 77aad8d..3536ae0 100644 Binary files a/Content/FirstPerson/Blueprints/BP_IronBlock.uasset and b/Content/FirstPerson/Blueprints/BP_IronBlock.uasset differ diff --git a/Content/FirstPerson/Blueprints/BP_PickUp_Rifle.uasset b/Content/FirstPerson/Blueprints/BP_PickUp_Rifle.uasset index aba823a..cd5e82d 100644 Binary files a/Content/FirstPerson/Blueprints/BP_PickUp_Rifle.uasset and b/Content/FirstPerson/Blueprints/BP_PickUp_Rifle.uasset differ diff --git a/Content/StarterContent/Architecture/Floor_400x400.uasset b/Content/StarterContent/Architecture/Floor_400x400.uasset index e3b449a..568b3e2 100644 Binary files a/Content/StarterContent/Architecture/Floor_400x400.uasset and b/Content/StarterContent/Architecture/Floor_400x400.uasset differ diff --git a/Content/StarterContent/HDRI/HDRI_Epic_Courtyard_Daylight.uasset b/Content/StarterContent/HDRI/HDRI_Epic_Courtyard_Daylight.uasset index 9b1f1be..bc93f51 100644 Binary files a/Content/StarterContent/HDRI/HDRI_Epic_Courtyard_Daylight.uasset and b/Content/StarterContent/HDRI/HDRI_Epic_Courtyard_Daylight.uasset differ diff --git a/Content/__ExternalActors__/FirstPerson/Maps/FirstPersonMap/5/SI/6K90CL2ZSQWKILI9NQQLEA.uasset b/Content/__ExternalActors__/FirstPerson/Maps/FirstPersonMap/5/SI/6K90CL2ZSQWKILI9NQQLEA.uasset index 6eab154..a42e84f 100644 Binary files a/Content/__ExternalActors__/FirstPerson/Maps/FirstPersonMap/5/SI/6K90CL2ZSQWKILI9NQQLEA.uasset and b/Content/__ExternalActors__/FirstPerson/Maps/FirstPersonMap/5/SI/6K90CL2ZSQWKILI9NQQLEA.uasset differ diff --git a/Source/BladeSmith/IronBlock.cpp b/Source/BladeSmith/IronBlock.cpp index 65e44e0..e9505d9 100644 --- a/Source/BladeSmith/IronBlock.cpp +++ b/Source/BladeSmith/IronBlock.cpp @@ -3,611 +3,436 @@ #include "IronBlock.h" -#include "FDeformComputeShader.h" +#include "FDeformComputeShader.h" // 确保这个头文件存在且包含了 FDeformationCSDispatchParams #include "StaticMeshResources.h" #include "TextureResource.h" #include "Engine/TextureRenderTarget2D.h" -#include "Engine/Texture.h" #include "Engine/StaticMesh.h" #include "Engine/Texture2D.h" #include "Materials/MaterialInstanceDynamic.h" #include "PhysicsEngine/BodySetup.h" +#include "RHICommandList.h" +#include "RenderGraphBuilder.h" // 包含 FRDGBuilder +#include "Engine/Texture.h" +#include "GenericPlatform/GenericPlatformProcess.h" + + // Sets default values -AIronBlock::AIronBlock(): StaticMeshComp(nullptr), PositionTexture0(nullptr), PositionTexture1(nullptr), - PositionRenderTarget(nullptr), - DynMaterial(nullptr), - CurrentImpactPoint(), - CurrentImpactNormal() +AIronBlock::AIronBlock() : StaticMeshComp(nullptr), +PositionRenderTarget0(nullptr), PositionRenderTarget1(nullptr), +DynMaterial(nullptr), +CurrentImpactPoint(), +CurrentImpactNormal() { - // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. - PrimaryActorTick.bCanEverTick = true; + // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. + PrimaryActorTick.bCanEverTick = true; + + StaticMeshComp = CreateDefaultSubobject(TEXT("StaticMeshComponent")); + RootComponent = StaticMeshComp; + // 设置默认网格(立方体) + UStaticMesh* DefaultMesh = LoadObject(nullptr, TEXT("/Engine/BasicShapes/Cube")); + if (DefaultMesh) + { + StaticMeshComp->SetStaticMesh(DefaultMesh); + } } // Called when the game starts or when spawned void AIronBlock::BeginPlay() { Super::BeginPlay(); - //CalculateLocalBounds(); - InitializePositionTexture(); -} - -UTexture2D* AIronBlock::CreatePositionTexture(const FString& TexName, int32 Size, const TArray& InitialData) -{ - // 创建纹理对象 - UTexture2D* NewTexture = NewObject( - this, - FName(*TexName), - RF_Transient - ); - - // 设置基本属性 - //NewTexture->Filter = TF_Nearest; - NewTexture->SRGB = false; - NewTexture->bNoTiling = true; - NewTexture->AddressX = TA_Clamp; - NewTexture->AddressY = TA_Clamp; - //NewTexture->CompressionSettings = TC_HDR; - //NewTexture->MipGenSettings = TMGS_NoMipmaps; - //NewTexture->NeverStream = true; - //NewTexture->LODGroup = TEXTUREGROUP_RenderTarget; // 关键:标记为渲染目标组 - - // 设置纹理尺寸和格式 - // NewTexture->SetPlatformData(new FTexturePlatformData()); - // NewTexture->GetPlatformData()->SizeX = Size; - // NewTexture->GetPlatformData()->SizeY = Size; - // // 使用兼容格式 - // EPixelFormat SafeFormat = PF_A32B32G32R32F; - // NewTexture->GetPlatformData()->PixelFormat = SafeFormat; - - NewTexture->SetPlatformData(new FTexturePlatformData()); - NewTexture->GetPlatformData()->SizeX = Size; - NewTexture->GetPlatformData()->SizeY = Size; - NewTexture->GetPlatformData()->PixelFormat = PF_R32_FLOAT; - - // 添加初始Mip - FTexture2DMipMap* Mip = new FTexture2DMipMap(); - //NewTexture->GetPlatformData()->Mips.Reset(); - Mip->SizeX = Size; - Mip->SizeY = Size; - const int32 DataSize = Size * Size * sizeof(FLinearColor); - Mip->BulkData.Lock(LOCK_READ_WRITE); - Mip->BulkData.Realloc(DataSize); - Mip->BulkData.Unlock(); - NewTexture->GetPlatformData()->Mips.Add(Mip); - // // 如果有初始数据,填充纹理 - // if (InitialData.Num() >= Size * Size) - // { - // const int32 DataSize = Size * Size * sizeof(FLinearColor); - // Mip->BulkData.Lock(LOCK_READ_WRITE); - // FMemory::Memcpy(Mip->BulkData.Realloc(DataSize), InitialData.GetData(), DataSize); - // Mip->BulkData.Unlock(); - // } - // else - // { - // UE_LOG(LogTemp, Warning, TEXT("Initial data size mismatch for texture %s"), *TexName); - // } - - // 标记需要更新资源 - NewTexture->UpdateResource(); - //ETextureDimension Dimension = NewTexture->GetResource()->GetTextureRHI()->GetDesc().Dimension; - return NewTexture; -} - -void AIronBlock::OnEndFrameInitializeTextures() -{ - // 确保纹理资源已创建 - if (PositionTexture0 && PositionTexture0->GetResource() && - PositionTexture1 && PositionTexture1->GetResource() - /*&& PositionRenderTarget && PositionRenderTarget->GetResource()*/) - { - // 等待资源提交完成 - FRenderCommandFence Fence; - Fence.BeginFence(); - Fence.Wait(); - // 初始化纹理对 - InitializeTexturePair(0); - InitializeTexturePair(1); - //CreateSafeUAV(PositionRenderTarget); - // 移除委托 - FCoreDelegates::OnEndFrame.Remove(TextureInitHandle); - TextureInitHandle.Reset(); - - UE_LOG(LogTemp, Log, TEXT("Position textures initialized")); - } + InitializePositionTexture(); } void AIronBlock::EndPlay(const EEndPlayReason::Type EndPlayReason) { - // 清理资源 - if (TextureInitHandle.IsValid()) - { - FCoreDelegates::OnEndFrame.Remove(TextureInitHandle); - TextureInitHandle.Reset(); - } - - // 释放 RHI 资源 - ENQUEUE_RENDER_COMMAND(ReleaseResources)( - [this](FRHICommandListImmediate& RHICmdList) - { - for (int i = 0; i < 2; i++) - { - TexturePairs->ReleaseRHI(); - } - }); - - Super::EndPlay(EndPlayReason); + // RenderTargets 会被 UPROPERTY 自动 GC,无需手动释放 RHI 资源 + + Super::EndPlay(EndPlayReason); } -// void CreateTexture() -// { -// const int32 RequestedMipIdx = 0; -// const FTexture2DMipMap* RequestedMip = GetPlatformMip(RequestedMipIdx); -// -// // create texture with ResourceMem data when available -// const FRHITextureCreateDesc Desc = -// FRHITextureCreateDesc::Create2D(TEXT("FTexture2DResource"), RequestedMip->SizeX, RequestedMip->SizeY, PixelFormat) -// .SetNumMips(1) -// .SetFlags(CreationFlags) -// .SetExtData(PlatformData->GetExtData()) -// .SetBulkData(ResourceMem) -// .SetClassName(Texture2DResourceName) -// .SetOwnerName(GetOwnerName()); -// -// TextureRHI = RHICreateTexture(Desc); -// -// // if( ResourceMem && !State.bReadyForStreaming) // To investigate! -// if (ResourceMem) -// { -// // when using resource memory the RHI texture has already been initialized with data and won't need to have mips copied -// check(State.NumRequestedLODs == ResourceMem->GetNumMips()); -// check(RequestedMip->SizeX == ResourceMem->GetSizeX() && RequestedMip->SizeY == ResourceMem->GetSizeY()); -// for( int32 MipIndex=0; MipIndex < MipData.Num(); MipIndex++ ) -// { -// MipData[MipIndex] = nullptr; -// MipDataSize[MipIndex] = 0; -// } -// } -// else -// { -// // Read the resident mip-levels into the RHI texture. -// for (int32 RHIMipIdx = 0; RHIMipIdx < State.NumRequestedLODs; ++RHIMipIdx) -// { -// const int32 ResourceMipIdx = RHIMipIdx + RequestedMipIdx; -// if (MipData[ResourceMipIdx]) -// { -// uint32 DestPitch = -1; -// uint64 Size = ~0ULL; -// void* TheMipData = RHILockTexture2D(TextureRHI, RHIMipIdx, RLM_WriteOnly, DestPitch, false /* bLockWithinMiptail */, true /* bFlushRHIThread */, &Size); -// //check(Size != ~0ULL); -// -// GetData( ResourceMipIdx, TheMipData, DestPitch, Size ); -// RHIUnlockTexture2D(TextureRHI, RHIMipIdx, false ); -// } -// } -// } -// } - - -void AIronBlock::InitializeTexturePair(int32 Index) +// 安全的渲染目标获取 +FTextureRHIRef AIronBlock::GetRenderTargetRHI(UTextureRenderTarget2D* RT) { - if (Index < 0 || Index > 1) return; - - UTexture2D* Texture = (Index == 0) ? PositionTexture0 : PositionTexture1; - if (!Texture || !Texture->GetResource()) return; - - // 获取纹理引用 - FTextureRHIRef RHITexture = Texture->GetResource()->TextureRHI; - if (!RHITexture.IsValid()) return; - TexturePairs[Index].RHITexture = RHITexture; - // 在渲染线程创建 UAV - ENQUEUE_RENDER_COMMAND(CreateTextureUAV)( - [this, Index](FRHICommandListImmediate& RHICmdList) - { - // 创建UAV描述 - FRHITextureDesc Desc = TexturePairs[Index].RHITexture->GetDesc(); - ensure(Desc.Flags & TexCreate_UAV); // 必须包含UAV标志 - ensure(Desc.Format == PF_R32_FLOAT); // 匹配格式 - - // FRHIViewDesc::FUnorderedAccessView UAVDesc(FRHIViewDesc::FTextureUAV() - // .SetTexture2D(TextureRHI) - // .SetFormat(Desc.Format) - // ); - //FUnorderedAccessViewRHIRef UAV = RHICreateUnorderedAccessView(TextureRHI, UAVDesc); - - FRHIViewDesc::FTextureUAV::FInitializer ViewDesc = FRHIViewDesc::CreateTextureUAV(); - ViewDesc.SetFormat(Desc.Format); - ViewDesc.SetDimensionFromTexture(TexturePairs[Index].RHITexture); - // ViewDesc.SetDimension(ETextureDimension::Texture2D); - // ViewDesc.SetPlane(ERHITexturePlane::Primary); - // ViewDesc.SetMipLevel(0); - // ViewDesc.SetArrayRange(0,1); - // 使用RDG安全创建 - // FRDGBuilder GraphBuilder(RHICmdList); - // FRDGTextureRef RDGTexture = GraphBuilder.RegisterExternalTexture(CreateRenderTarget(TexturePairs[Index].RHITexture, TEXT("PositionTexture"))); - // FRDGTextureUAVDesc UAVDesc(RDGTexture); - - // TexturePairs[Index].UAV = GraphBuilder.CreateUAV(UAVDesc); - - // 立即执行 - // GraphBuilder.Execute(); - - //ViewDesc.SetDimensionFromTexture(TexturePairs[Index].RHITexture); - TexturePairs[Index].UAV = RHICmdList.CreateUnorderedAccessView(TexturePairs[Index].RHITexture,ViewDesc); - - UE_LOG(LogTemp, Log, TEXT("Created UAV for texture %d"), Index); - } - ); + if (!RT || !RT->GetResource()) return nullptr; + return RT->GetResource()->TextureRHI; } void AIronBlock::GetStaticMeshVertexPositions(TArray& OutPositions) { - OutPositions.Empty(); - - if (!StaticMeshComp || !StaticMeshComp->GetStaticMesh()) - return; - - // 获取静态网格资源 - UStaticMesh* StaticMesh = StaticMeshComp->GetStaticMesh(); - if (StaticMesh->GetRenderData() && StaticMesh->GetRenderData()->LODResources.Num() > 0) - { - // 获取LOD0的顶点数据 - FStaticMeshLODResources& LOD = StaticMesh->GetRenderData()->LODResources[0]; - const FPositionVertexBuffer& VertexBuffer = LOD.VertexBuffers.PositionVertexBuffer; - - const int32 VertexCount = VertexBuffer.GetNumVertices(); - OutPositions.SetNum(VertexCount); - - for (int32 i = 0; i < VertexCount; i++) - { - OutPositions[i] = VertexBuffer.VertexPosition(i); - } - } + OutPositions.Empty(); + + if (!StaticMeshComp || !StaticMeshComp->GetStaticMesh()) + return; + + // 获取静态网格资源 + UStaticMesh* StaticMesh = StaticMeshComp->GetStaticMesh(); + // 确保有有效的 RenderData 和 LOD + if (StaticMesh->GetRenderData() && StaticMesh->GetRenderData()->LODResources.Num() > 0) + { + // 获取LOD0的顶点数据 + FStaticMeshLODResources& LOD = StaticMesh->GetRenderData()->LODResources[0]; + const FPositionVertexBuffer& VertexBuffer = LOD.VertexBuffers.PositionVertexBuffer; + + const int32 VertexCount = VertexBuffer.GetNumVertices(); + OutPositions.SetNum(VertexCount); + + for (int32 i = 0; i < VertexCount; i++) + { + OutPositions[i] = VertexBuffer.VertexPosition(i); + } + } } +/** + * 核心修复:创建一个配置好用于变形计算的 RenderTarget + */ +UTextureRenderTarget2D* AIronBlock::CreateDeformRT(const FString& RTName, int32 Size) +{ + UTextureRenderTarget2D* RenderTarget = NewObject(this, FName(*RTName)); + if (RenderTarget) + { + // **关键修复 1:** 设置为32位浮点数格式,以匹配 FLinearColor 和 CS 的 PF_A32B32G32R32F + RenderTarget->RenderTargetFormat = RTF_RGBA32f; + + // **关键修复 2:** 明确告诉引擎我们需要为这个纹理创建UAV + RenderTarget->bCanCreateUAV = true; + + RenderTarget->bNeedsTwoCopies = true; // 告知引擎这个纹理会被用于Ping-Pong,优化内存 + RenderTarget->SizeX = Size; + RenderTarget->SizeY = Size; + RenderTarget->bAutoGenerateMips = false; + RenderTarget->AddressX = TA_Clamp; + RenderTarget->AddressY = TA_Clamp; + + // 立即创建RHI资源 + RenderTarget->UpdateResource(); + } + return RenderTarget; +} + + void AIronBlock::InitializePositionTexture() { - // 创建静态网格组件 - if (!StaticMeshComp) - { - StaticMeshComp = CreateDefaultSubobject(TEXT("StaticMeshComponent")); - RootComponent = StaticMeshComp; - - // 设置默认网格(立方体) - UStaticMesh* DefaultMesh = LoadObject(nullptr, TEXT("/Engine/BasicShapes/Cube")); - if (DefaultMesh) - { - StaticMeshComp->SetStaticMesh(DefaultMesh); - } - } - - // 计算本地边界 - CalculateLocalBounds(); - - // 获取顶点位置 - TArray VertexPositions; - GetStaticMeshVertexPositions(VertexPositions); - - // 创建位置纹理 - const int32 TextureSize = 256; // 纹理尺寸 - TArray InitialData; - InitialData.SetNum(TextureSize * TextureSize); - - // 初始化纹理数据 - for (int32 Y = 0; Y < TextureSize; ++Y) - { - for (int32 X = 0; X < TextureSize; ++X) - { - int32 Index = Y * TextureSize + X; - FVector2D UV(FVector2D(X, Y) / (TextureSize - 1)); - - // 将UV坐标转换为本地空间位置 - FVector LocalPos( - FMath::Lerp(LocalBounds.Min.X, LocalBounds.Max.X, UV.X), - FMath::Lerp(LocalBounds.Min.Y, LocalBounds.Max.Y, UV.Y), - LocalBounds.Min.Z - ); - - InitialData[Index] = FLinearColor(LocalPos.X, LocalPos.Y, LocalPos.Z, 1.0f); - } - } - - // 创建纹理对 - PositionTexture0 = CreatePositionTexture(TEXT("PositionTexture0"), TextureSize, InitialData); - PositionTexture1 = CreatePositionTexture(TEXT("PositionTexture1"), TextureSize, InitialData); - - // 创建渲染目标 - PositionRenderTarget = NewObject(this); - PositionRenderTarget->SizeX = TextureSize; - PositionRenderTarget->SizeY = TextureSize; - PositionRenderTarget->RenderTargetFormat = RTF_RGBA16f; - PositionRenderTarget->bAutoGenerateMips = false; - PositionRenderTarget->UpdateResource(); - - // 设置变形材质 - SetupDeformationMaterial(); - - // 初始化纹理对 - InitializeTexturePair(0); - InitializeTexturePair(1); - - // 延迟初始化纹理资源 - TextureInitHandle = FDelegateHandle(); - GetWorld()->GetTimerManager().SetTimerForNextTick([this]() - { - OnEndFrameInitializeTextures(); - }); -} + CalculateLocalBounds(); -// 创建UAV的安全方法 -FUnorderedAccessViewRHIRef AIronBlock::CreateSafeUAV(UTextureRenderTarget2D* RenderTarget) -{ - if (!RenderTarget || !RenderTarget->GetResource()) - return nullptr; - - FTextureRHIRef TextureRHI = RenderTarget->GetResource()->GetTexture2DRHI(); - if (!TextureRHI.IsValid()) - return nullptr; - - // 创建UAV描述 - FUnorderedAccessViewRHIRef UAV; - ENQUEUE_RENDER_COMMAND(CreateUAVCommand)( - [TextureRHI, &UAV](FRHICommandListImmediate& RHICmdList) - { - FRHIViewDesc::FTextureUAV::FInitializer ViewDesc = FRHIViewDesc::CreateTextureUAV(); - ViewDesc.SetMipLevel(0); - ViewDesc.SetPlane(ERHITexturePlane::Primary); - ViewDesc.SetFormat(PF_A32B32G32R32F); - - UAV = RHICmdList.CreateUnorderedAccessView(TextureRHI, ViewDesc); - } - ); - - // 等待渲染命令完成 - FlushRenderingCommands(); - - return UAV; + // 获取顶点位置 (用于计算初始UV->WorldPos) + TArray VertexPositions; + GetStaticMeshVertexPositions(VertexPositions); + + // 创建位置纹理 + const int32 TextureSize = 256; // 纹理尺寸 + TArray InitialData; + InitialData.SetNum(TextureSize * TextureSize); + + // 初始化纹理数据 (将UV映射到本地包围盒的XY平面) + for (int32 Y = 0; Y < TextureSize; ++Y) + { + for (int32 X = 0; X < TextureSize; ++X) + { + int32 Index = Y * TextureSize + X; + FVector2D UV(FVector2D(X, Y) / (TextureSize - 1.0f)); // 使用 255.0 或 (Size-1) + + // 将UV坐标转换为本地空间位置 (XY平面) + FVector LocalPos( + FMath::Lerp(LocalBounds.Min.X, LocalBounds.Max.X, UV.X), + FMath::Lerp(LocalBounds.Min.Y, LocalBounds.Max.Y, UV.Y), + LocalBounds.Min.Z // 假设初始是一个平面 + ); + + InitialData[Index] = FLinearColor(LocalPos.X, LocalPos.Y, LocalPos.Z, 1.0f); + } + } + + // --- 关键修复: 创建两个 RenderTarget --- + PositionRenderTarget0 = CreateDeformRT(TEXT("PositionRT0"), TextureSize); + PositionRenderTarget1 = CreateDeformRT(TEXT("PositionRT1"), TextureSize); + + // --- 将初始数据写入GPU --- + // 我们需要将CPU端的 InitialData 写入到GPU纹理中 + // 最简单的方法是创建一个临时的UTexture2D,用BulkData上传,然后拷贝到RT + + UTexture2D* TempTexture = UTexture2D::CreateTransient(TextureSize, TextureSize, PF_A32B32G32R32F); // 必须匹配格式 + if (TempTexture && PositionRenderTarget0 && PositionRenderTarget1) + { + // 锁定 Mip 并拷贝数据 + FTexture2DMipMap& Mip = TempTexture->GetPlatformData()->Mips[0]; + void* MipData = Mip.BulkData.Lock(LOCK_READ_WRITE); + FMemory::Memcpy(MipData, InitialData.GetData(), InitialData.Num() * sizeof(FLinearColor)); + Mip.BulkData.Unlock(); + TempTexture->UpdateResource(); // 提交到GPU + + // 获取RHI资源 + FTextureRHIRef DestRT0_RHI = GetRenderTargetRHI(PositionRenderTarget0); + FTextureRHIRef DestRT1_RHI = GetRenderTargetRHI(PositionRenderTarget1); + FTextureRHIRef SrcTex_RHI = TempTexture->GetResource() ? TempTexture->GetResource()->TextureRHI : nullptr; + + if (DestRT0_RHI && DestRT1_RHI && SrcTex_RHI) + { + // 在渲染线程上执行拷贝 + ENQUEUE_RENDER_COMMAND(InitializeDeformRTs)( + [DestRT0_RHI, DestRT1_RHI, SrcTex_RHI](FRHICommandListImmediate& RHICmdList) + { + // 将初始数据拷贝到两个RT + RHICmdList.CopyTexture(SrcTex_RHI, DestRT0_RHI, {}); + RHICmdList.CopyTexture(SrcTex_RHI, DestRT1_RHI, {}); + }); + + // 确保拷贝完成 + FlushRenderingCommands(); + } + } + + // 设置变形材质 + SetupDeformationMaterial(); + UE_LOG(LogTemp, Log, TEXT("Position textures initialized")); } void AIronBlock::CalculateLocalBounds() { - if (!StaticMeshComp) - { - StaticMeshComp = GetComponentByClass(); - } - if (StaticMeshComp && StaticMeshComp->GetStaticMesh()) - { - // 获取本地空间包围盒 - LocalBounds = StaticMeshComp->GetStaticMesh()->GetBoundingBox(); - } - else - { - // 智能默认值 - float Extent = 50.0f; - LocalBounds = FBox( - FVector(-Extent, -Extent, 0), - FVector(Extent, Extent, Extent * 0.2f) - ); - } - - // 确保包围盒有效 - if (LocalBounds.GetVolume() <= 0) - { - LocalBounds = FBox(FVector(-50, -50, 0), FVector(50, 50, 10)); - } + if (!StaticMeshComp) + { + StaticMeshComp = GetComponentByClass(); + } + if (StaticMeshComp && StaticMeshComp->GetStaticMesh()) + { + // 获取本地空间包围盒 + LocalBounds = StaticMeshComp->GetStaticMesh()->GetBoundingBox(); + } + else + { + // 智能默认值 + float Extent = 50.0f; + LocalBounds = FBox( + FVector(-Extent, -Extent, 0), + FVector(Extent, Extent, Extent * 0.2f) + ); + } + + // 确保包围盒有效 + if (LocalBounds.GetVolume() <= 0) + { + LocalBounds = FBox(FVector(-50, -50, 0), FVector(50, 50, 10)); + } } void AIronBlock::SetupDeformationMaterial() { - // 创建动态材质实例 - if (StaticMeshComp) - { - // 加载基础材质 - UMaterial* BaseMaterial = LoadObject(nullptr, TEXT("/Engine/BasicShapes/BasicShapeMaterial")); - if (BaseMaterial) - { - DynMaterial = UMaterialInstanceDynamic::Create(BaseMaterial, this); - StaticMeshComp->SetMaterial(0, DynMaterial); - - // 设置初始参数 - if (DynMaterial) - { - DynMaterial->SetTextureParameterValue(TEXT("PositionTexture"), PositionTexture0); - DynMaterial->SetVectorParameterValue(TEXT("LocalBoundsMin"), FLinearColor(LocalBounds.Min)); - DynMaterial->SetVectorParameterValue(TEXT("LocalBoundsMax"), FLinearColor(LocalBounds.Max)); - DynMaterial->SetScalarParameterValue(TEXT("DeformationStrength"), 1.0f); - } - } - else - { - UE_LOG(LogTemp, Warning, TEXT("Failed to load base material for iron block")); - } - } + // 创建动态材质实例 + if (StaticMeshComp) + { + // 加载基础材质 (请确保你的材质支持读取 PositionTexture) + UMaterial* BaseMaterial = LoadObject(nullptr, TEXT("/Game/YourMaterialFolder/M_DeformableIron")); // 示例路径 + if (!BaseMaterial) + { + BaseMaterial = LoadObject(nullptr, TEXT("/Engine/BasicShapes/BasicShapeMaterial")); + } + + if (BaseMaterial) + { + DynMaterial = UMaterialInstanceDynamic::Create(BaseMaterial, this); + StaticMeshComp->SetMaterial(0, DynMaterial); + + // 设置初始参数 + if (DynMaterial) + { + // 初始时读取 RT0 + DynMaterial->SetTextureParameterValue(TEXT("PositionTexture"), PositionRenderTarget0); + DynMaterial->SetVectorParameterValue(TEXT("LocalBoundsMin"), FLinearColor(LocalBounds.Min)); + DynMaterial->SetVectorParameterValue(TEXT("LocalBoundsMax"), FLinearColor(LocalBounds.Max)); + DynMaterial->SetScalarParameterValue(TEXT("DeformationStrength"), 1.0f); + } + } + else + { + UE_LOG(LogTemp, Warning, TEXT("Failed to load base material for iron block")); + } + } } void AIronBlock::OnHammerHit(const FHitResult& HitResult) { - // 获取锤子冲击力 - float ImpactForce = 0.0f; - if (HitResult.GetComponent() && HitResult.GetComponent()->GetPhysicsLinearVelocity().IsNearlyZero()) - { - ImpactForce = HitResult.GetComponent()->GetPhysicsLinearVelocity().Size(); - } - - // 默认冲击力 - if (ImpactForce < 10.0f) ImpactForce = 500.0f; - - // 转换到本地空间 - FVector ImpactPoint = HitResult.ImpactPoint; - FVector ImpactNormal = HitResult.ImpactNormal; - FVector LocalImpactPoint = GetTransform().InverseTransformPosition(ImpactPoint); - FVector LocalImpactNormal = GetTransform().InverseTransformVector(ImpactNormal); - - UpdateComputeShaderParams(LocalImpactPoint, LocalImpactNormal, ImpactForce); - - ExecuteDeformationCS(); + // 获取锤子冲击力 + float ImpactForce = 0.0f; + // 检查 HitResult.GetComponent() 是否有效 + if (HitResult.GetComponent() && !HitResult.GetComponent()->GetPhysicsLinearVelocity().IsNearlyZero()) + { + ImpactForce = HitResult.GetComponent()->GetPhysicsLinearVelocity().Size(); + } + + // 默认冲击力 + if (ImpactForce < 10.0f) ImpactForce = 500.0f; + + // 转换到本地空间 + FVector ImpactPoint = HitResult.ImpactPoint; + FVector ImpactNormal = HitResult.ImpactNormal; + FVector LocalImpactPoint = GetTransform().InverseTransformPosition(ImpactPoint); + FVector LocalImpactNormal = GetTransform().InverseTransformVector(ImpactNormal); + + UpdateComputeShaderParams(LocalImpactPoint, LocalImpactNormal, ImpactForce); + + ExecuteDeformationCS(); } -// 安全的纹理资源获取 -FTextureRHIRef AIronBlock::GetTextureRHI(UTexture2D* Texture) -{ - return Texture && Texture->GetResource() ? Texture->GetResource()->TextureRHI : nullptr; -} -// 安全的渲染目标获取 -FTextureRHIRef AIronBlock::GetRenderTargetRHI(UTextureRenderTarget2D* RT) -{ - if (!RT || !RT->GetResource()) return nullptr; - return RT->GetResource()->TextureRHI; -} void AIronBlock::UpdateComputeShaderParams(FVector LocalImpactPoint, FVector LocalImpactNormal, float ImpactForce) { - // 更新碰撞参数 - CurrentImpactPoint = LocalImpactPoint; - CurrentImpactNormal = LocalImpactNormal.GetSafeNormal(); - CurrentImpactForce = FMath::Clamp(ImpactForce, 0.0f, 5000.0f); - - // 根据冲击力计算动态变形半径 - CurrentDeformationRadius = FMath::Lerp(5.0f, 30.0f, CurrentImpactForce / 2000.0f); - - // 根据材质类型调整塑性系数 - UMaterialInterface* Material = StaticMeshComp->GetMaterial(0); - if(Material && Material->GetName().Contains("Steel")) - { - CurrentPlasticity = 0.4f; // 钢材塑性较低 - } - else - { - CurrentPlasticity = 0.8f; // 默认高塑性 - } - - // 切换纹理缓冲 - CurrentTextureIndex = 1 - CurrentTextureIndex; + // 更新碰撞参数 + CurrentImpactPoint = LocalImpactPoint; + CurrentImpactNormal = LocalImpactNormal.GetSafeNormal(); + CurrentImpactForce = FMath::Clamp(ImpactForce, 0.0f, 5000.0f); + + // 根据冲击力计算动态变形半径 + CurrentDeformationRadius = FMath::Lerp(5.0f, 30.0f, CurrentImpactForce / 2000.0f); + + // 根据材质类型调整塑性系数 (示例) + UMaterialInterface* Material = StaticMeshComp->GetMaterial(0); + if (Material && Material->GetName().Contains("Steel")) + { + CurrentPlasticity = 0.4f; // 钢材塑性较低 + } + else + { + CurrentPlasticity = 0.8f; // 默认高塑性 + } + + // 切换纹理缓冲索引 + CurrentTextureIndex = 1 - CurrentTextureIndex; } void AIronBlock::ExecuteDeformationCS() { - // 确保纹理资源有效 - if (!PositionTexture0 || !PositionTexture0->GetResource() || - !PositionTexture1 || !PositionTexture1->GetResource()) - { - UE_LOG(LogTemp, Error, TEXT("Position textures not initialized!")); - return; - } - - // 获取当前纹理资源 - UTexture2D* CurrentTex = (CurrentTextureIndex == 0) ? PositionTexture0 : PositionTexture1; - UTexture2D* PrevTex = (CurrentTextureIndex == 0) ? PositionTexture1 : PositionTexture0; - - // 获取纹理RHI资源 - FTextureRHIRef InputRHI = GetTextureRHI(PrevTex); - FTextureRHIRef OutputRHI = GetRenderTargetRHI(PositionRenderTarget); + // 确保纹理资源有效 + if (!PositionRenderTarget0 || !PositionRenderTarget0->GetResource() || + !PositionRenderTarget1 || !PositionRenderTarget1->GetResource()) + { + UE_LOG(LogTemp, Error, TEXT("Position render targets not initialized!")); + return; + } - if (!InputRHI || !OutputRHI) - { - UE_LOG(LogTemp, Error, TEXT("Invalid texture RHI resources!")); - return; - } - - // 计算纹理UV坐标 - const int32 TexSize = PositionRenderTarget->SizeX; - FVector2D ImpactUV( - (CurrentImpactPoint.X - LocalBounds.Min.X) / LocalBounds.GetSize().X, - (CurrentImpactPoint.Y - LocalBounds.Min.Y) / LocalBounds.GetSize().Y - ); - - // 计算更新区域 (只更新受影响部分) - const float RadiusRatio = CurrentDeformationRadius / LocalBounds.GetExtent().GetMax(); - FIntRect UpdateRegion; - UpdateRegion.Min.X = FMath::Clamp((ImpactUV.X - RadiusRatio) * TexSize, 0, TexSize); - UpdateRegion.Min.Y = FMath::Clamp((ImpactUV.Y - RadiusRatio) * TexSize, 0, TexSize); - UpdateRegion.Max.X = FMath::Min((ImpactUV.X + RadiusRatio) * TexSize, (float)TexSize); - UpdateRegion.Max.Y = FMath::Min((ImpactUV.Y + RadiusRatio) * TexSize, (float)TexSize); - - // 准备参数 - FDeformationCSDispatchParams Params; - Params.ImpactPoint = CurrentImpactPoint; - Params.ImpactNormal = CurrentImpactNormal; - Params.ImpactForce = CurrentImpactForce; - Params.DeformationRadius = CurrentDeformationRadius; - Params.Plasticity = CurrentPlasticity; - Params.InputTexture = InputRHI; - - // 创建UAV用于输出 - FUnorderedAccessViewRHIRef OutputUAV; - ENQUEUE_RENDER_COMMAND(CreateOutputUAV)( - [OutputRHI, &OutputUAV](FRHICommandListImmediate& RHICmdList) - { - FRHIViewDesc::FTextureUAV::FInitializer ViewDesc = FRHIViewDesc::CreateTextureUAV(); - ViewDesc.SetMipLevel(0); - ViewDesc.SetPlane(ERHITexturePlane::Primary); - ViewDesc.SetFormat(PF_A32B32G32R32F); - - OutputUAV = RHICmdList.CreateUnorderedAccessView(OutputRHI, ViewDesc); - } - ); - FlushRenderingCommands(); - Params.OutputTexture = OutputUAV; - - Params.UpdateRegion = UpdateRegion; - Params.TextureSize = FIntVector(TexSize, TexSize, 1); - - // 在渲染线程中执行Compute Shader - ENQUEUE_RENDER_COMMAND(ExecuteDeformationCS)( - [Params](FRHICommandListImmediate& RHICmdList) - { - FDeformationCS::Dispatch(RHICmdList, Params); - }); - - // 更新材质参数 - if (DynMaterial) - { - DynMaterial->SetTextureParameterValue(TEXT("PositionTexture"), CurrentTex); - DynMaterial->SetVectorParameterValue(TEXT("LocalBoundsMin"), FLinearColor(LocalBounds.Min)); - DynMaterial->SetVectorParameterValue(TEXT("LocalBoundsMax"), FLinearColor(LocalBounds.Max)); - } + // --- 关键修复: 决定哪个是读取RT,哪个是写入RT --- + UTextureRenderTarget2D* ReadTarget = (CurrentTextureIndex == 0) ? PositionRenderTarget0 : PositionRenderTarget1; + UTextureRenderTarget2D* WriteTarget = (CurrentTextureIndex == 1) ? PositionRenderTarget0 : PositionRenderTarget1; // Flipped + + // --- 关键修复: 直接获取 RT 的 RHI 和 UAV --- + FTextureRHIRef InputRHI = GetRenderTargetRHI(ReadTarget); + // 从 WriteTarget 获取 UAV。因为我们创建时设置了 bCanCreateUAV=true,所以这个UAV是有效的。 + FUnorderedAccessViewRHIRef OutputUAV = nullptr; + if (WriteTarget && WriteTarget->GetResource()) + { + FTextureResource* RTResource = WriteTarget->GetResource(); + FTextureRenderTargetResource* WriteRTResource = static_cast(RTResource); + if (WriteRTResource) + { + OutputUAV = WriteRTResource->GetRenderTargetUAV(); + } + } + + if (!InputRHI || !OutputUAV) + { + UE_LOG(LogTemp, Error, TEXT("Invalid texture RHI resources or UAV!")); + return; + } + + // 计算纹理UV坐标 + const int32 TexSize = ReadTarget->SizeX; + FVector2D ImpactUV( + (CurrentImpactPoint.X - LocalBounds.Min.X) / LocalBounds.GetSize().X, + (CurrentImpactPoint.Y - LocalBounds.Min.Y) / LocalBounds.GetSize().Y + ); + + // 计算更新区域 (只更新受影响部分) + const float RadiusRatio = CurrentDeformationRadius / LocalBounds.GetExtent().GetMax(); + FIntRect UpdateRegion; + UpdateRegion.Min.X = FMath::Clamp(FMath::FloorToInt((ImpactUV.X - RadiusRatio) * TexSize), 0, TexSize - 1); + UpdateRegion.Min.Y = FMath::Clamp(FMath::FloorToInt((ImpactUV.Y - RadiusRatio) * TexSize), 0, TexSize - 1); + UpdateRegion.Max.X = FMath::Clamp(FMath::CeilToInt((ImpactUV.X + RadiusRatio) * TexSize), 1, TexSize); + UpdateRegion.Max.Y = FMath::Clamp(FMath::CeilToInt((ImpactUV.Y + RadiusRatio) * TexSize), 1, TexSize); + + // 确保 Min < Max + if (UpdateRegion.Min.X >= UpdateRegion.Max.X) UpdateRegion.Max.X = UpdateRegion.Min.X + 1; + if (UpdateRegion.Min.Y >= UpdateRegion.Max.Y) UpdateRegion.Max.Y = UpdateRegion.Min.Y + 1; + + // 准备参数 + FDeformationCSDispatchParams Params; + Params.ImpactPoint = CurrentImpactPoint; // 确保类型匹配 (FVector -> FVector3f) + Params.ImpactNormal = CurrentImpactNormal; + Params.ImpactForce = CurrentImpactForce; + Params.DeformationRadius = CurrentDeformationRadius; + Params.Plasticity = CurrentPlasticity; + Params.InputTexture = InputRHI; + Params.OutputTexture = OutputUAV; // **直接使用RT的UAV** + Params.UpdateRegion = UpdateRegion; + Params.TextureSize = FIntVector(TexSize, TexSize, 1); + + // 在渲染线程中执行Compute Shader + ENQUEUE_RENDER_COMMAND(ExecuteDeformationCS)( + [Params](FRHICommandListImmediate& RHICmdList) + { + // 在这里调用你的 Compute Shader 调度逻辑 + FDeformationCS::Dispatch(RHICmdList, Params); + }); + + // 等待渲染命令完成,以便在CPU上读取 (如果需要立即更新碰撞体) + // 注意:这是一个同步点,会影响性能。 + // FlushRenderingCommands(); + // UpdateCollisionAsync(UpdateRegion, WriteTarget); // 异步更新碰撞体 + + // --- 关键修复: 更新材质参数,使其下一帧读取我们刚刚写入的纹理 --- + if (DynMaterial) + { + DynMaterial->SetTextureParameterValue(TEXT("PositionTexture"), WriteTarget); + DynMaterial->SetVectorParameterValue(TEXT("LocalBoundsMin"), FLinearColor(LocalBounds.Min)); + DynMaterial->SetVectorParameterValue(TEXT("LocalBoundsMax"), FLinearColor(LocalBounds.Max)); + } } -// 异步更新物理碰撞体 -void AIronBlock::UpdateCollisionAsync(const FIntRect& UpdateRegion) + +// 异步更新物理碰撞体 (示例,未优化) +void AIronBlock::UpdateCollisionAsync(const FIntRect& UpdateRegion, UTextureRenderTarget2D* FromTarget) { - if (!PositionRenderTarget || !PositionRenderTarget->GetResource()) - { - return; - } - // 从GPU读取关键区域数据 - TArray SampledData; - FReadSurfaceDataFlags Flags; - Flags.SetLinearToGamma(false); - - PositionRenderTarget->GameThread_GetRenderTargetResource()->ReadPixels( - SampledData, - Flags, - FIntRect(UpdateRegion.Min.X, UpdateRegion.Min.Y, - UpdateRegion.Max.X, UpdateRegion.Max.Y) - ); - - // 创建临时碰撞体 - TArray CollisionVerts; - for(FColor& Pixel : SampledData) - { - FVector Pos( - Pixel.R * 255.0f / 10.0f, // 根据实际范围调整 - Pixel.G * 255.0f / 10.0f, - Pixel.B * 255.0f / 10.0f - ); - CollisionVerts.Add(Pos); - } - - // 更新简单碰撞体 - UBodySetup* BodySetup = StaticMeshComp->GetBodySetup(); - BodySetup->AggGeom.ConvexElems[0].VertexData = CollisionVerts; - BodySetup->CreatePhysicsMeshes(); + // 注意:ReadPixels 是一项非常慢的操作 (GPU->CPU 同步) + // 更好的方法是使用另一个Compute Shader将顶点数据简化并读回到一个小的StructuredBuffer中 + + if (!FromTarget || !FromTarget->GetResource()) + { + return; + } + // 从GPU读取关键区域数据 + TArray SampledData; // 使用 FLinearColor + FReadSurfaceDataFlags Flags; + Flags.SetLinearToGamma(false); + + FRenderTarget* RTResource = FromTarget->GameThread_GetRenderTargetResource(); + if (!RTResource) return; + + RTResource->ReadLinearColorPixels( + SampledData, + Flags, + UpdateRegion // FIntRect + ); + + // 创建临时碰撞体 + TArray CollisionVerts; + for (const FLinearColor& Pixel : SampledData) + { + // 像素数据已经是 FLinearColor (即 FVector) + CollisionVerts.Add(FVector(Pixel.R, Pixel.G, Pixel.B)); + } + + if (CollisionVerts.Num() == 0) return; + + // 更新简单碰撞体 + UBodySetup* BodySetup = StaticMeshComp->GetBodySetup(); + if (BodySetup && BodySetup->AggGeom.ConvexElems.Num() > 0) + { + BodySetup->AggGeom.ConvexElems[0].VertexData = CollisionVerts; + BodySetup->CreatePhysicsMeshes(); // 重新创建物理网格体 + } } // Called every frame void AIronBlock::Tick(float DeltaTime) { Super::Tick(DeltaTime); - -} - +} \ No newline at end of file diff --git a/Source/BladeSmith/IronBlock.h b/Source/BladeSmith/IronBlock.h index 3ddabcc..8231fbb 100644 --- a/Source/BladeSmith/IronBlock.h +++ b/Source/BladeSmith/IronBlock.h @@ -4,90 +4,92 @@ #include "CoreMinimal.h" #include "GameFramework/Actor.h" +// FDeformComputeShader.h 假定包含了 FDeformationCSDispatchParams 结构 +#include "FDeformComputeShader.h" + #include "IronBlock.generated.h" +class UTextureRenderTarget2D; +class UMaterialInstanceDynamic; +class UStaticMeshComponent; + UCLASS() class BLADESMITH_API AIronBlock : public AActor { GENERATED_BODY() - UPROPERTY(EditAnywhere) - UStaticMeshComponent* StaticMeshComp; - - UPROPERTY() - UTexture2D* PositionTexture0; + UPROPERTY(EditAnywhere) + UStaticMeshComponent* StaticMeshComp; + /** 用于Ping-Pong缓冲的渲染目标 A */ UPROPERTY() - UTexture2D* PositionTexture1; - - UPROPERTY() - UTextureRenderTarget2D* PositionRenderTarget; - - UPROPERTY() - UMaterialInstanceDynamic* DynMaterial; -public: + UTextureRenderTarget2D* PositionRenderTarget0; + + /** 用于Ping-Pong缓冲的渲染目标 B */ + UPROPERTY() + UTextureRenderTarget2D* PositionRenderTarget1; + + /** 应用于网格的动态材质实例 */ + UPROPERTY() + UMaterialInstanceDynamic* DynMaterial; + +public: // Sets default values for this actor's properties AIronBlock(); UFUNCTION(BlueprintCallable) - void OnHammerHit(const FHitResult& HitResult); + void OnHammerHit(const FHitResult& HitResult); protected: // Called when the game starts or when spawned virtual void BeginPlay() override; virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; - void InitializeTexturePair(int32 Index); + + /** 初始化两个渲染目标和它们的初始数据 */ + void InitializePositionTexture(); + + /** 创建一个配置好用于变形计算的 RenderTarget */ + UTextureRenderTarget2D* CreateDeformRT(const FString& RTName, int32 Size); + + /** 获取静态网格体的顶点位置 (用于计算初始纹理) */ void GetStaticMeshVertexPositions(TArray& OutPositions); - UTexture2D* CreatePositionTexture(const FString& TexName, int32 Size, const TArray& InitialData); - void InitializePositionTexture(); - FUnorderedAccessViewRHIRef CreateSafeUAV(UTextureRenderTarget2D* RenderTarget); - void OnEndFrameInitializeTextures(); - void SetupDeformationMaterial(); - FTextureRHIRef GetTextureRHI(UTexture2D* Texture); + + /** 设置动态材质实例和参数 */ + void SetupDeformationMaterial(); + + /** 安全地获取 RenderTarget 的 RHI 资源 */ FTextureRHIRef GetRenderTargetRHI(UTextureRenderTarget2D* RT); + private: + /** 更新 Compute Shader 所需的参数 */ void UpdateComputeShaderParams(FVector LocalImpactPoint, FVector LocalImpactNormal, float ImpactForce); + + /** 在渲染线程上执行 Compute Shader */ void ExecuteDeformationCS(); - void UpdateCollisionAsync(const FIntRect& UpdateRegion); + + /** (可选) 异步更新物理碰撞体 */ + void UpdateCollisionAsync(const FIntRect& UpdateRegion, UTextureRenderTarget2D* FromTarget); + + /** 计算网格体的本地包围盒 */ void CalculateLocalBounds(); - // 双缓冲纹理对结构体 - struct FDeformationTexturePair - { - // 纹理资源引用 - FTextureRHIRef RHITexture; - - // UAV 资源引用 - FUnorderedAccessViewRHIRef UAV; - //FRDGTextureUAVRef UAV; - // 安全释放资源 - void ReleaseRHI() - { - UAV.SafeRelease(); - //UAV->GetRHI()->Release(); - RHITexture.SafeRelease(); - } - }; - - // 双缓冲纹理对数组 [0] = 第一对, [1] = 第二对 - FDeformationTexturePair TexturePairs[2]; - - // 当前使用的纹理对索引 - int32 CurrentPairIndex = 0; + // 网格体的本地包围盒 FBox LocalBounds; - // 纹理资源初始化委托 - FDelegateHandle TextureInitHandle; - // 当前碰撞参数 + + // --- 碰撞参数 --- FVector CurrentImpactPoint; FVector CurrentImpactNormal; float CurrentImpactForce = 0.0f; float CurrentDeformationRadius = 10.0f; float CurrentPlasticity = 0.7f; - - // 双缓冲纹理索引 + + /** * 双缓冲纹理索引 + * 0: 读 RT0, 写 RT1 + * 1: 读 RT1, 写 RT0 + */ int32 CurrentTextureIndex = 0; -public: + +public: // Called every frame virtual void Tick(float DeltaTime) override; - -}; +}; \ No newline at end of file