📜 ⬆️ ⬇️

FadeObjects - Hide objects between camera and character

image

Once, it took to write a module to hide objects between the camera and the character, or between several characters for the RTS game. I want to share for those who started their journey in the Unreal Engine. This tutorial, if you can call it that, will be using C ++, but in the attached project on github there will be an option on Blueprint, the functionality of both is identical.

Video example


And so, let's go. We divide our task into several smaller ones:
')
  1. Get the objects between the camera and the character.
  2. Change the material of these objects to the desired.
  3. Change the material back to the one that was, if the object does not interfere with the review of our character.

We will need 2 timers, one adds objects to the array to work with them, and the second to change the object itself, in this case I change the material from normal to slightly transparent. This material can be replaced by any suitable for you.

SFadeObjectsComponent.h

FTimerHandle timerHandle_ObjectComputeTimer; FTimerHandle timerHandle_AddObjectsTimer; 

As soon as the object is in the array, for further work we need to memorize some of its properties, for example, what material it had before we changed it, because we will have to change it back. Also, in our case, we hide, and when necessary we return the original state of the object gradually, so we need to remember its current state.

To do this, we will create a structure:
 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; } }; 


We also need some of the settings available from Blueprint for the flexible operation of our component. Such as the type of collision to identify objects, the size of the capsule (the beam itself) from character to camera, the larger the size, the more objects around the character will be captured.

 // 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; 

The distance at which objects will be hidden.

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

And of course, the character class itself or other actors in the scene.

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

We will not parse all the variables used, you can independently get acquainted with the source code.

Let's turn to implementation. In BeginPlay we will start our timers. Instead of timers, you can, of course, use EventTick, but it’s better not to do this, the operation itself for changing materials if a large number of objects is quite expensive for a CPU.

SFadeObjectsComponent.cpp

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

The function of adding an object to an array. Here I would like to note that she adds not only the actor in the scene, but also its components and SkeletalMesh, if necessary.
 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(); } 


The function for working with objects that changes the material from the original to the required and back.
 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); } } } } 


There is nothing special to tell here, some pieces of code, and so with comments. The video at the beginning shows the result. I also want to add only the settings with which the component is initialized.

 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); 

Perhaps someone will be useful. Or someone will tell their opinion in the comments.

Link to source

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


All Articles