RealtimeStyleTransferRuntime/Source/LyraGame/Feedback/NumberPops/LyraNumberPopComponent_Mesh...

328 lines
13 KiB
C++
Raw Normal View History

2022-05-23 18:41:30 +00:00
// Copyright Epic Games, Inc. All Rights Reserved.
#include "LyraNumberPopComponent_MeshText.h"
#include "TimerManager.h"
#include "Components/StaticMeshComponent.h"
#include "Engine/CollisionProfile.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "Camera/PlayerCameraManager.h"
#include "LyraDamagePopStyle.h"
ULyraNumberPopComponent_MeshText::ULyraNumberPopComponent_MeshText(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
ComponentLifespan = 1.f;
SignDigitParameterName = FName(TEXT("+Or-"));
ColorParameterName = FName(TEXT("Color"));
AnimationLifespanParameterName = FName(TEXT("Animation Lifespan"));
IsCriticalHitParameterName = FName(TEXT("isCriticalHit?"));
MoveToCameraParameterName = FName(TEXT("MoveToCamera"));
PositionParameterNames = { TEXT("0a"), TEXT("1a"), TEXT("2a"), TEXT("3a"), TEXT("4a"), TEXT("5a"), TEXT("6a"), TEXT("7a"), TEXT("8a") };
ScaleRotationAngleParameterNames = { TEXT("0b"), TEXT("1b"), TEXT("2b"), TEXT("3b"), TEXT("4b"), TEXT("5b"), TEXT("6b"), TEXT("7b"), TEXT("8b") };
DurationParameterNames = { TEXT("0c"), TEXT("1c"), TEXT("2c"), TEXT("3c"), TEXT("4c"), TEXT("5c"), TEXT("6c"), TEXT("7c"), TEXT("8c") };
SpacingPercentageForOnes = 0.8f;
DistanceFromCameraBeforeDoublingSize = 1024.f;
CriticalHitSizeMultiplier = 1.7f;
FontXSize = 10.920001f;
FontYSize = 21.0f;
NumberOfNumberRotations = 1.f;
}
void ULyraNumberPopComponent_MeshText::AddNumberPop(const FLyraNumberPopRequest& NewRequest)
{
// Drop requests for remote players on the floor
// (this prevents multiple pops from showing up for the host of a listen server)
if (APlayerController* PC = GetController<APlayerController>())
{
if (!PC->IsLocalController())
{
return;
}
}
FTempNumberPopInfo PreparedNumberInfo;
// Prepare the DamageNumberArray with the digits from the damage.
{
int32 LocalDamage = NewRequest.NumberToDisplay;
PreparedNumberInfo.DamageNumberArray.Empty();
if (LocalDamage == 0)
{
// We want to just show a zero
PreparedNumberInfo.DamageNumberArray.Insert(0, 0);
}
else
{
// Parse the base10 number into an array
while (LocalDamage > 0)
{
PreparedNumberInfo.DamageNumberArray.Insert(LocalDamage % 10, 0);
LocalDamage /= 10;
}
}
// Insert a zero to reserve space for + or -. Used by the blueprint
PreparedNumberInfo.DamageNumberArray.Insert(0, 0);
}
// Grab a component from the pool for this number or create one
{
UStaticMesh* MeshToUse = DetermineStaticMesh(NewRequest);
if (MeshToUse == nullptr)
{
return;
}
FPooledNumberPopComponentList& ComponentPool = PooledComponentMap.FindOrAdd(MeshToUse);
UStaticMeshComponent* ComponentToUse = nullptr;
if (ComponentPool.Components.Num() > 0)
{
ComponentToUse = ComponentPool.Components.Pop();
}
else
{
ComponentToUse = NewObject<UStaticMeshComponent>(GetOwner());
ComponentToUse->SetupAttachment(nullptr);
ComponentToUse->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
ComponentToUse->SetStaticMesh(MeshToUse);
// Used to allow post-processes to opt out of affecting the number pop digits
ComponentToUse->SetRenderCustomDepth(true);
ComponentToUse->SetCustomDepthStencilValue(123);
// The digits travel a great distance from their original bounds due to
// world position offset (WPO) animation in the material, so expand bounds
ComponentToUse->SetBoundsScale(2000.0f);
// We'll be overriding values like the desired color and digits to use, so we need MIDs
for (int32 MatIdx = 0; MatIdx < ComponentToUse->GetNumMaterials(); ++MatIdx)
{
ComponentToUse->CreateDynamicMaterialInstance(MatIdx);
}
}
// Register
check(ComponentToUse);
ComponentToUse->RegisterComponent();
// Add to the "live" list
UWorld* LocalWorld = GetWorld();
check(LocalWorld);
LiveComponents.Emplace(ComponentToUse, &ComponentPool, LocalWorld->GetTimeSeconds() + ComponentLifespan);
// Assign struct pointers
PreparedNumberInfo.StaticMeshComponent = ComponentToUse;
for (int32 MatIdx = 0; MatIdx < ComponentToUse->GetNumMaterials(); ++MatIdx)
{
UMaterialInstanceDynamic* NewMID = Cast<UMaterialInstanceDynamic>(ComponentToUse->GetMaterial(MatIdx));
PreparedNumberInfo.MeshMIDs.Add(NewMID);
}
// Start the timer if it wasn't already running
if (!LocalWorld->GetTimerManager().IsTimerActive(ReleaseTimerHandle))
{
LocalWorld->GetTimerManager().SetTimer(ReleaseTimerHandle, this, &ThisClass::ReleaseNextComponents, ComponentLifespan);
}
}
// Determine the position
FTransform CameraTransform;
FVector NumberLocation(NewRequest.WorldLocation);
if (APlayerController* PC = GetController<APlayerController>())
{
if (APlayerCameraManager* PlayerCameraManager = PC->PlayerCameraManager)
{
CameraTransform = FTransform(PlayerCameraManager->GetCameraRotation(), PlayerCameraManager->GetCameraLocation());
FVector LocationOffset(ForceInitToZero);
const float RandomMagnitude = 5.0f; //@TODO: Make this style driven
LocationOffset += FMath::RandPointInBox(FBox(FVector(-RandomMagnitude), FVector(RandomMagnitude)));
NumberLocation += LocationOffset;
}
}
PreparedNumberInfo.StaticMeshComponent->SetWorldTransform(FTransform(CameraTransform.GetRotation(), NumberLocation));
// Now apply the material parameters to make the digits, etc...
SetMaterialParameters(NewRequest, PreparedNumberInfo, CameraTransform, NumberLocation);
}
void ULyraNumberPopComponent_MeshText::ReleaseNextComponents()
{
UWorld* LocalWorld = GetWorld();
check(LocalWorld);
const float CurrentTime = LocalWorld->GetTimeSeconds();
int32 NumReleased = 0;
for (const FLiveNumberPopEntry& LiveComp : LiveComponents)
{
if (CurrentTime >= LiveComp.ReleaseTime)
{
NumReleased++;
if (ensure(LiveComp.Component))
{
LiveComp.Component->UnregisterComponent();
if (ensure(LiveComp.Pool))
{
// Return this component to the pool
LiveComp.Pool->Components.Push(LiveComp.Component);
}
else
{
// No pool. Just remove it.
LiveComp.Component->SetFlags(RF_Transient);
LiveComp.Component->Rename(nullptr, GetTransientPackage(), RF_NoFlags);
}
}
}
else
{
// These are in chronological order so none of the other elements will be deleted
break;
}
}
// Actually remove it from the live components array
LiveComponents.RemoveAt(0, NumReleased);
// If we still have live components animating, set the timer to remove the next one
if (LiveComponents.Num() > 0)
{
const float TimeUntilNextRelease = LiveComponents[0].ReleaseTime - CurrentTime;
LocalWorld->GetTimerManager().SetTimer(ReleaseTimerHandle, this, &ThisClass::ReleaseNextComponents, TimeUntilNextRelease);
}
}
FLinearColor ULyraNumberPopComponent_MeshText::DetermineColor(const FLyraNumberPopRequest& Request) const
{
for (ULyraDamagePopStyle* Style : Styles)
{
if ((Style != nullptr) && Style->bOverrideColor)
{
if (Style->MatchPattern.Matches(Request.TargetTags))
{
return Request.bIsCriticalDamage ? Style->CriticalColor : Style->Color;
}
}
}
return FLinearColor::White;
}
UStaticMesh* ULyraNumberPopComponent_MeshText::DetermineStaticMesh(const FLyraNumberPopRequest& Request) const
{
for (ULyraDamagePopStyle* Style : Styles)
{
if ((Style != nullptr) && Style->bOverrideMesh)
{
if (Style->MatchPattern.Matches(Request.TargetTags))
{
return Style->TextMesh;
}
}
}
return nullptr;
}
void ULyraNumberPopComponent_MeshText::SetMaterialParameters(const FLyraNumberPopRequest& Request, FTempNumberPopInfo& NewDamageNumberInfo, const FTransform& CameraTransform, const FVector& NumberLocation)
{
UWorld* World = GetWorld();
if (World && GEngine)
{
const float RealGameTime = World->GetRealTimeSeconds();
// Whether we should show a sign as the first digit, and if so which one
// (if bIsSignNegative is true, we show minus, false is plus)
const bool bShouldShowSign = false;
const bool bIsSignNegative = true;
for (UMaterialInstanceDynamic* MeshMID : NewDamageNumberInfo.MeshMIDs)
{
MeshMID->SetScalarParameterValue(SignDigitParameterName, bIsSignNegative ? 0.5f : 0.0f);
MeshMID->SetVectorParameterValue(ColorParameterName, DetermineColor(Request));
// IF the damage number has more digits than we support
// THEN force the damage number to the highest number we can support
const int32 MaxSupportedDigits = FMath::Min(FMath::Min(PositionParameterNames.Num(), ScaleRotationAngleParameterNames.Num()), DurationParameterNames.Num());
if (!ensure(NewDamageNumberInfo.DamageNumberArray.Num() <= MaxSupportedDigits))
{
NewDamageNumberInfo.DamageNumberArray.SetNum(MaxSupportedDigits);
// Set all number digits to 9 so we show the largest number we can
// Skip digit 0 because that digit is for the +/- sign
for (int32 DigitIndex = 1; DigitIndex < NewDamageNumberInfo.DamageNumberArray.Num(); ++DigitIndex)
{
NewDamageNumberInfo.DamageNumberArray[DigitIndex] = 9;
}
}
MeshMID->SetScalarParameterValue(AnimationLifespanParameterName, ComponentLifespan);
MeshMID->SetScalarParameterValue(IsCriticalHitParameterName, Request.bIsCriticalDamage ? 1.f : 0.f);
const int32 DamageNumberArrayLength = NewDamageNumberInfo.DamageNumberArray.Num();
float OffsetAccumulatedValue = (DamageNumberArrayLength * -1.f) + (bShouldShowSign ? 0.f : -1.f);
const int32 LastIndex = (DamageNumberArrayLength >= 4) ? DamageNumberArrayLength : 4;
for (int32 NumberIndex = 0; NumberIndex < LastIndex; ++NumberIndex)
{
const float NumberYOffset = ((NumberIndex / FMath::Max(1, DamageNumberArrayLength - 1)) - 0.5f) * 2.f;
const FVector NumberOffset = FVector(0.f, NumberYOffset, 0.f);
const FVector CameraSpaceDirection = CameraTransform.TransformVectorNoScale(NumberOffset);
const float SpacingForNumber = ((NumberIndex < DamageNumberArrayLength) && ((NewDamageNumberInfo.DamageNumberArray[NumberIndex] == 1) || ((NumberIndex > 0) && (NewDamageNumberInfo.DamageNumberArray[NumberIndex - 1] == 1)))) ? SpacingPercentageForOnes : 1.f;
OffsetAccumulatedValue += SpacingForNumber;
FLinearColor RGBAPositionParameter(CameraSpaceDirection);
RGBAPositionParameter.A = OffsetAccumulatedValue;
const FName PositionParameterName = PositionParameterNames[NumberIndex];
MeshMID->SetVectorParameterValue(PositionParameterName, RGBAPositionParameter);
const float DistanceFromCameraToNumber = (CameraTransform.GetLocation() - NumberLocation).Size();
const float DistanceSpriteScale = DistanceFromCameraBeforeDoublingSize == 0.f ? 1.f : FMath::Clamp(DistanceFromCameraToNumber / DistanceFromCameraBeforeDoublingSize, 1.f, 1000000000.f);
const float ScaleToZeroMultiplier = (NumberIndex < DamageNumberArrayLength) && (((NumberIndex == 0) && bShouldShowSign) || (NumberIndex != 0)) ? 1.f : 0.f;
const float HitSizeMultiplier = Request.bIsCriticalDamage ? CriticalHitSizeMultiplier : 1.f;
const float FontSizeMultiplier = HitSizeMultiplier * DistanceSpriteScale * ScaleToZeroMultiplier;
FLinearColor RGBAScaleRotationParameter;
RGBAScaleRotationParameter.R = FontXSize * FontSizeMultiplier;
RGBAScaleRotationParameter.G = FontYSize * FontSizeMultiplier;
RGBAScaleRotationParameter.B = NewDamageNumberInfo.DamageNumberArray[FMath::Min(DamageNumberArrayLength - 1, NumberIndex)];
RGBAScaleRotationParameter.A = FMath::Sign(CameraSpaceDirection.X) * NumberOfNumberRotations;
const FName ScaleRotationAngleParameterName = ScaleRotationAngleParameterNames[NumberIndex];
MeshMID->SetVectorParameterValue(ScaleRotationAngleParameterName, RGBAScaleRotationParameter);
FLinearColor RGBADurationParameter;
RGBADurationParameter.R = RealGameTime + ComponentLifespan;
RGBADurationParameter.G = FMath::FRand();
const FName DurationParameterName = DurationParameterNames[NumberIndex];
MeshMID->SetVectorParameterValue(DurationParameterName, RGBADurationParameter);
}
// Non-gameplay cameras while spectating have more cinematic values of aperture as default.
// This makes damage numbers very blurry as they are brought close to the camera, and away from the point of focus.
// Disable the shifting of numbers towards the camera here, if in a cinematic spectator camera.
//@TODO: Determine whether or not we are spectating
const bool bIsSpectating = false;
MeshMID->SetScalarParameterValue(MoveToCameraParameterName, bIsSpectating ? 0.0f : 1.0f);
}
}
}