FadeObjects - Blendet Objekte zwischen Kamera und Zeichen aus

Bild

Einmal war es notwendig, ein Modul zu schreiben, um Objekte zwischen der Kamera und dem Charakter oder zwischen mehreren Charakteren für das RTS-Spiel zu verbergen. Ich möchte mit denen teilen, die ihre Reise in der Unreal Engine begonnen haben. In diesem Tutorial wird, wenn Sie es so nennen können, C ++ verwendet, aber im angefügten Projekt auf github gibt es eine Option für Blueprint, die Funktionalität von beiden ist identisch.

Videobeispiel


Und los geht's. Wir teilen unsere Aufgabe in mehrere kleinere auf:

  1. Holen Sie sich die Objekte zwischen der Kamera und dem Charakter.
  2. Ändern Sie das Material dieser Objekte in das gewünschte.
  3. Ändern Sie das Material wieder in das, das war, wenn das Objekt die Überprüfung unseres Charakters nicht beeinträchtigt.

Wir benötigen 2 Timer, einer fügt dem Array Objekte hinzu, um mit ihnen zu arbeiten, und der zweite, um das Objekt selbst zu ändern. In diesem Fall ändere ich das Material von normal auf leicht transparent. Dieses Material kann von jedem für Sie geeigneten ersetzt werden.

SFadeObjectsComponent.h

FTimerHandle timerHandle_ObjectComputeTimer;
FTimerHandle timerHandle_AddObjectsTimer;

Sobald sich das Objekt im Array befindet, müssen wir uns für die weitere Arbeit einige Eigenschaften merken, z. B., welches Material vor der Änderung vorhanden war, da wir es wieder ändern müssen. In unserem Fall verstecken wir uns auch, und wenn nötig, kehren wir den ursprünglichen Zustand des Objekts allmählich zurück, sodass wir uns an seinen aktuellen Zustand erinnern müssen.

Dazu erstellen wir eine Struktur:
USTRUCT()
structFFadeObjStruct
{
	GENERATED_USTRUCT_BODY()
	UPROPERTY()
	UPrimitiveComponent* primitiveComp;
	UPROPERTY()
	TArray<UMaterialInterface*> baseMatInterface;
	UPROPERTY()
	TArray<UMaterialInstanceDynamic*> fadeMID;
	UPROPERTY()
	float fadeCurrent;
	UPROPERTY()
	bool bToHide;
	voidNewElement(UPrimitiveComponent* newComponent, TArray<UMaterialInterface*> newBaseMat, <UMaterialInstanceDynamic*> newMID, float currentFade, bool bHide){
    	primitiveComp = newComponent;
    	baseMatInterface = newBaseMat;
    	fadeMID = newMID;
    	fadeCurrent = currentFade;
    	bToHide = bHide;
	}
	voidSetHideOnly(bool hide){
    	bToHide = hide;
	}
	voidSetFadeAndHide(float newFade, bool newHide){
    	fadeCurrent = newFade;
    	bToHide = newHide;
	}
	//For DestroyvoidDestroy(){
    	primitiveComp = nullptr;
	}
	//Constructor
	FFadeObjStruct()
	{
    	primitiveComp = nullptr;
    	fadeCurrent = 0;
    	bToHide = true;
	}
};


Wir benötigen auch einige der von Blueprint verfügbaren Einstellungen für den flexiblen Betrieb unserer Komponente. B. die Art der Kollision zum Identifizieren von Objekten, die Größe der Kapsel (der Strahl selbst) von Charakter zu Kamera, je größer die Größe, desto mehr Objekte werden um den Charakter herum erfasst.

// Check trace block by this
	UPROPERTY(EditAnywhere, Category = "Fade Objects")
	TArray<TEnumAsByte<ECollisionChannel>> objectTypes;
 // Trace object size
	UPROPERTY(EditAnywhere, Category = "Fade Objects")
	float capsuleHalfHeight;
	// Trace object size
	UPROPERTY(EditAnywhere, Category = "Fade Objects")
	float capsuleRadius;

Die Entfernung, in der Objekte ausgeblendet werden.

UPROPERTY(EditAnywhere, Category = "Fade Objects")
float workDistance;

Und natürlich die Charakterklasse selbst oder andere Darsteller in der Szene.

UPROPERTY(EditAnywhere, Category = "Fade Objects")
UClass* playerClass;

Wir werden nicht alle verwendeten Variablen parsen, Sie können sich unabhängig mit dem Quellcode vertraut machen.

Kommen wir zur Umsetzung. In BeginPlay werden wir unsere Timer starten. Anstelle von Timern können Sie natürlich auch EventTick verwenden. Es ist jedoch besser, dies nicht zu tun, da der Vorgang zum Wechseln von Materialien bei einer großen Anzahl von Objekten für die CPU recht teuer ist.

SFadeObjectsComponent.cpp

GetWorld()->GetTimerManager().SetTimer(timerHandle_AddObjectsTimer, this, &USFadeObjectsComponent::AddObjectsToHide, addObjectInterval, true); 
GetWorld()->GetTimerManager().SetTimer(timerHandle_ObjectComputeTimer, this, &USFadeObjectsComponent::FadeObjWorker, calcFadeInterval, true);                             

Die Funktion zum Hinzufügen eines Objekts zu einem Array. Ich möchte hier darauf hinweisen, dass sie nicht nur den Schauspieler in der Szene hinzufügt, sondern auch dessen Komponenten und gegebenenfalls SkeletalMesh.
void USFadeObjectsComponent::AddObjectsToHide()
{
	UGameplayStatics::GetAllActorsOfClass(this, playerClass, characterArray);
	for (AActor* currentActor : characterArray)
	{
		const FVector traceStart = GEngine->GetFirstLocalPlayerController(GetWorld())->PlayerCameraManager->GetCameraLocation();
		const FVector traceEnd = currentActor->GetActorLocation();
		const FRotator traceRot = currentActor->GetActorRotation();
		FVector traceLentgh = traceStart - traceEnd;
		const FQuat acQuat = currentActor->GetActorQuat();
		if (traceLentgh.Size() < workDistance)
		{
			FCollisionQueryParams traceParams(TEXT("FadeObjectsTrace"), true, GetOwner());
			traceParams.AddIgnoredActors(actorsIgnore);
			traceParams.bTraceAsyncScene = true;
			traceParams.bReturnPhysicalMaterial = false;
			// Not tracing complex uses the rough collision instead making tiny objects easier to select.
			traceParams.bTraceComplex = false;
			TArray<FHitResult> hitArray;
			TArray<TEnumAsByte<EObjectTypeQuery>> traceObjectTypes;
			// Convert ECollisionChannel to ObjectTypefor (int i = 0; i < objectTypes.Num(); ++i)
			{
				traceObjectTypes.Add(UEngineTypes::ConvertToObjectType(objectTypes[i].GetValue()));
			}
			// Check distance between camera and player for new object to fade, and add this in array
			GetWorld()->SweepMultiByObjectType(hitArray, traceStart, traceEnd, acQuat, traceObjectTypes,
				FCollisionShape::MakeCapsule(capsuleRadius, capsuleHalfHeight), traceParams);
			for (int hA = 0; hA < hitArray.Num(); ++hA)
			{
				if (hitArray[hA].bBlockingHit && IsValid(hitArray[hA].GetComponent()) && !fadeObjectsHit.Contains(hitArray[hA].GetComponent()))
				{
					fadeObjectsHit.AddUnique(hitArray[hA].GetComponent());
				}
			}
		}
	}
	// Make fade array after complete GetAllActorsOfClass loopfor (int fO = 0; fO < fadeObjectsHit.Num(); ++fO)
	{
		// If not contains this component in fadeObjectsTempif (!fadeObjectsTemp.Contains(fadeObjectsHit[fO]))
		{
			TArray<UMaterialInterface*> lBaseMaterials;
			TArray<UMaterialInstanceDynamic*> lMidMaterials;
			lBaseMaterials.Empty();
			lMidMaterials.Empty();
			fadeObjectsTemp.AddUnique(fadeObjectsHit[fO]);
			// For loop all materials ID in objectfor (int nM = 0; nM < fadeObjectsHit[fO]->GetNumMaterials(); ++nM)
			{
				lMidMaterials.Add(UMaterialInstanceDynamic::Create(fadeMaterial, fadeObjectsHit[fO]));
				lBaseMaterials.Add(fadeObjectsHit[fO]->GetMaterial(nM));
				// Set new material on object
				fadeObjectsHit[fO]->SetMaterial(nM, lMidMaterials.Last());
			}
			// Create new fade object in array of objects to fade
			FFadeObjStruct newObject;
			newObject.NewElement(fadeObjectsHit[fO], lBaseMaterials, lMidMaterials, immediatelyFade, true);
			// Add object to array
			fadeObjects.Add(newObject);
			// Set collision on Primitive Component
			fadeObjectsHit[fO]->SetCollisionResponseToChannel(ECC_Camera, ECR_Ignore);
		}
	}
	// Set hide to visible true if containsfor (int fOT = 0; fOT < fadeObjectsTemp.Num(); ++fOT)
	{
		if (!fadeObjectsHit.Contains(fadeObjectsTemp[fOT]))
		{
			fadeObjects[fOT].SetHideOnly(false);
		}
	}
	// Clear array
	fadeObjectsHit.Empty();
}


Die Funktion zum Arbeiten mit Objekten, die das Material vom Original in das gewünschte und zurück ändert.
void USFadeObjectsComponent::FadeObjWorker()
{
	if (fadeObjects.Num() > 0)
	{
    	// For loop all fade objectsfor (int i = 0; i < fadeObjects.Num(); ++i)
    	{
        	// Index of iterationint fnID = i;
        	float adaptiveFade;
        	if (fnID == fadeObjects.Num())
        	{
            	adaptiveFade = nearObjectFade;
        	}
        	else
        	{
            	adaptiveFade = farObjectFade;
        	}
        	// For loop fadeMID arrayfor (int t = 0; t < fadeObjects[i].fadeMID.Num(); ++t)
        	{
            	float targetF;
            	constfloat currentF = fadeObjects[i].fadeCurrent;
            	if (fadeObjects[i].bToHide)
            	{
                	targetF = adaptiveFade;
            	}
            	else
            	{
                	targetF = 1.0f;
            	}
            	constfloat newFade = FMath::FInterpConstantTo(currentF, targetF, GetWorld()->GetDeltaSeconds(), fadeRate);
            	fadeObjects[i].fadeMID[t]->SetScalarParameterValue("Fade", newFade);
            	currentFade = newFade;
            	fadeObjects[i].SetFadeAndHide(newFade, fadeObjects[i].bToHide);
        	}
        	// remove index in arrayif (currentFade == 1.0f)
        	{
            	for (int bmi = 0; bmi < fadeObjects[fnID].baseMatInterface.Num(); ++bmi)
            	{
                	fadeObjects[fnID].primitiveComp->SetMaterial(bmi, fadeObjects[fnID].baseMatInterface[bmi]);
            	}
            	fadeObjects[fnID].primitiveComp->SetCollisionResponseToChannel(ECC_Camera, ECR_Block);
            	fadeObjects.RemoveAt(fnID);
            	fadeObjectsTemp.RemoveAt(fnID);
        	}
    	}
	}
}


Hier gibt es nichts Besonderes zu sagen, einige Code-Teile und so auch Kommentare. Das Video am Anfang zeigt das Ergebnis. Ich möchte auch nur die Einstellungen hinzufügen, mit denen die Komponente initialisiert wird.

PrimaryComponentTick.bCanEverTick = false;
bEnable = true;
addObjectInterval = 0.1f;
calcFadeInterval = 0.05f;
fadeRate = 10.0f;
capsuleHalfHeight = 88.0f;
capsuleRadius = 34.0f;
workDistance = 5000.0f;
nearCameraRadius = 300.0f;
nearObjectFade = 0.3;
farObjectFade = 0.1;
immediatelyFade = 0.5f;
// Add first collision type
objectTypes.Add(ECC_WorldStatic);

Vielleicht wird jemand nützlich sein. Oder jemand wird seine Meinung in den Kommentaren äußern.

Quelllink

Jetzt auch beliebt: