FadeObjects - Objekte zwischen Kamera und Charakter ausblenden

Bild

Einmal musste ein Modul geschrieben werden, um Objekte zwischen der Kamera und dem Charakter oder zwischen mehreren Charakteren für ein RTS-Spiel zu verbergen. Ich möchte für diejenigen teilen, die ihre Reise in Unreal Engine begonnen haben. Dieses Tutorial, wenn Sie es so nennen können, wird C ++ verwenden, aber im angehängten Projekt auf Github gibt es eine Option auf Blueprint, die Funktionalität von beiden ist identisch.

Videobeispiel


Und so, lass uns gehen. Wir teilen unsere Aufgabe in mehrere kleine auf:

  1. Holen Sie sich 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 den ursprünglichen Zustand, wenn das Objekt die Überprüfung unseres Charakters nicht beeinträchtigt.

Wir benötigen zwei Timer, einen zum Hinzufügen von Objekten zum Array, um mit ihnen zu arbeiten, und den zweiten zum Ändern des Objekts selbst. In diesem Fall ändere ich das Material von normal auf leicht transparent. Sie können dieses Material durch ein für Sie geeignetes ersetzen.

SFadeObjectsComponent.h

FTimerHandle timerHandle_ObjectComputeTimer; FTimerHandle timerHandle_AddObjectsTimer; 

Sobald sich das Objekt im Array befindet, müssen wir uns für weitere Arbeiten einige seiner Eigenschaften merken, z. B. welches Material es hatte, bevor wir es geändert haben, da wir es zurück ändern müssen. In unserem Fall verstecken wir uns und geben bei Bedarf schrittweise den Ausgangszustand des Objekts zurück, sodass wir uns an seinen aktuellen Zustand erinnern müssen.

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


Für den flexiblen Betrieb unserer Komponente benötigen wir auch einige von Blueprint verfügbare Einstellungen. Wie die Art der Kollision zum Identifizieren von Objekten, die Größe der Kapsel (der Strahl selbst) vom Charakter zur Kamera, je größer die Größe, desto mehr Objekte um den Charakter herum werden 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 Akteure in der Szene.

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

Wir werden nicht alle verwendeten Variablen analysieren, Sie können sich unabhängig mit den Quellen vertraut machen.

Fahren wir mit der Implementierung fort. Führen Sie in BeginPlay unsere Timer aus. Anstelle von Timern können Sie natürlich EventTick verwenden, aber es ist besser, dies nicht zu tun. Der Vorgang selbst zum Ändern von Materialien, wenn eine große 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. An dieser Stelle möchte ich darauf hinweisen, dass nicht nur der Schauspieler selbst in die Szene aufgenommen wird, sondern gegebenenfalls auch seine Komponenten und 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 ObjectType for (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 loop for (int fO = 0; fO < fadeObjectsHit.Num(); ++fO) { // If not contains this component in fadeObjectsTemp if (!fadeObjectsTemp.Contains(fadeObjectsHit[fO])) { TArray<UMaterialInterface*> lBaseMaterials; TArray<UMaterialInstanceDynamic*> lMidMaterials; lBaseMaterials.Empty(); lMidMaterials.Empty(); fadeObjectsTemp.AddUnique(fadeObjectsHit[fO]); // For loop all materials ID in object for (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 contains for (int fOT = 0; fOT < fadeObjectsTemp.Num(); ++fOT) { if (!fadeObjectsHit.Contains(fadeObjectsTemp[fOT])) { fadeObjects[fOT].SetHideOnly(false); } } // Clear array fadeObjectsHit.Empty(); } 


Eine Funktion zum Arbeiten mit Objekten, die das Material vom Original zum gewünschten und umgekehrt ändert.
 void USFadeObjectsComponent::FadeObjWorker() { if (fadeObjects.Num() > 0) { // For loop all fade objects for (int i = 0; i < fadeObjects.Num(); ++i) { // Index of iteration int fnID = i; float adaptiveFade; if (fnID == fadeObjects.Num()) { adaptiveFade = nearObjectFade; } else { adaptiveFade = farObjectFade; } // For loop fadeMID array for (int t = 0; t < fadeObjects[i].fadeMID.Num(); ++t) { float targetF; const float currentF = fadeObjects[i].fadeCurrent; if (fadeObjects[i].bToHide) { targetF = adaptiveFade; } else { targetF = 1.0f; } const float 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 array if (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 erzählen, einige Codeteile und so weiter mit Kommentaren. Das Video am Anfang zeigt das Ergebnis. Ich möchte 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 ist jemand nützlich. Oder jemand wird seine Meinung in den Kommentaren sagen.

Link zur Quelle

Source: https://habr.com/ru/post/de425389/


All Articles