Skip to content

Instantly share code, notes, and snippets.

@alfredbaudisch
Last active April 23, 2024 15:46
Show Gist options
  • Save alfredbaudisch/7978f1913e76640e2dc25b770ffa6ae1 to your computer and use it in GitHub Desktop.
Save alfredbaudisch/7978f1913e76640e2dc25b770ffa6ae1 to your computer and use it in GitHub Desktop.
Unreal Engine OcclusionAwarePlayerController to make actors/meshes transparent when they block the Camera. This is a simple see-through solution that does not require any changes to your scene or actors. To learn more details and instructions on how to use, check my post: https://alfredbaudisch.com/blog/gamedev/unreal-engine-ue/unreal-engine-act…
/**
* !! NOTICE !!
* Instructions: https://alfredbaudisch.com/blog/gamedev/unreal-engine-ue/unreal-engine-actors-transparent-block-camera-occlusion-see-through/
*/
// OcclusionAwarePlayerController.h
// By Alfred Reinold Baudisch (https://github.com/alfredbaudisch)
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "OcclusionAwarePlayerController.generated.h"
USTRUCT(BlueprintType)
struct FCameraOccludedActor
{
GENERATED_USTRUCT_BODY()
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
const AActor* Actor;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
UStaticMeshComponent* StaticMesh;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
TArray<UMaterialInterface*> Materials;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
bool IsOccluded;
};
/**
*
*/
UCLASS()
class YOURGAME_API AOcclusionAwarePlayerController : public APlayerController
{
GENERATED_BODY()
public:
AOcclusionAwarePlayerController();
protected:
// Called when the game starts
virtual void BeginPlay() override;
/** How much of the Pawn capsule Radius and Height
* should be used for the Line Trace before considering an Actor occluded?
* Values too low may make the camera clip through walls.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Camera Occlusion|Occlusion",
meta=(ClampMin="0.1", ClampMax="10.0") )
float CapsulePercentageForTrace;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Camera Occlusion|Materials")
UMaterialInterface* FadeMaterial;
UPROPERTY(BlueprintReadWrite, Category="Camera Occlusion|Components")
class USpringArmComponent* ActiveSpringArm;
UPROPERTY(BlueprintReadWrite, Category="Camera Occlusion|Components")
class UCameraComponent* ActiveCamera;
UPROPERTY(BlueprintReadWrite, Category="Camera Occlusion|Components")
class UCapsuleComponent* ActiveCapsuleComponent;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Camera Occlusion")
bool IsOcclusionEnabled;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Camera Occlusion|Occlusion")
bool DebugLineTraces;
private:
TMap<const AActor*, FCameraOccludedActor> OccludedActors;
bool HideOccludedActor(const AActor* Actor);
bool OnHideOccludedActor(const FCameraOccludedActor& OccludedActor) const;
void ShowOccludedActor(FCameraOccludedActor& OccludedActor);
bool OnShowOccludedActor(const FCameraOccludedActor& OccludedActor) const;
void ForceShowOccludedActors();
__forceinline bool ShouldCheckCameraOcclusion() const
{
return IsOcclusionEnabled && FadeMaterial && ActiveCamera && ActiveCapsuleComponent;
}
public:
UFUNCTION(BlueprintCallable)
void SyncOccludedActors();
};
/**
* !! NOTICE !!
* Instructions: https://alfredbaudisch.com/blog/gamedev/unreal-engine-ue/unreal-engine-actors-transparent-block-camera-occlusion-see-through/
*/
// OcclusionAwarePlayerController.cpp
// By Alfred Reinold Baudisch (https://github.com/alfredbaudisch)
#include "OcclusionAwarePlayerController.h"
#include "Kismet/GameplayStatics.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Containers/Set.h"
AOcclusionAwarePlayerController::AOcclusionAwarePlayerController()
{
CapsulePercentageForTrace = 1.0f;
DebugLineTraces = true;
IsOcclusionEnabled = true;
}
void AOcclusionAwarePlayerController::BeginPlay()
{
Super::BeginPlay();
if (IsValid(GetPawn()))
{
ActiveSpringArm = Cast<
USpringArmComponent>(GetPawn()->GetComponentByClass(USpringArmComponent::StaticClass()));
ActiveCamera = Cast<UCameraComponent>(GetPawn()->GetComponentByClass(UCameraComponent::StaticClass()));
ActiveCapsuleComponent = Cast<UCapsuleComponent>(
GetPawn()->GetComponentByClass(UCapsuleComponent::StaticClass()));
}
}
void AOcclusionAwarePlayerController::SyncOccludedActors()
{
if (!ShouldCheckCameraOcclusion()) return;
// Camera is currently colliding, show all current occluded actors
// and do not perform further occlusion
if (ActiveSpringArm->bDoCollisionTest)
{
ForceShowOccludedActors();
return;
}
FVector Start = ActiveCamera->GetComponentLocation();
FVector End = GetPawn()->GetActorLocation();
TArray<TEnumAsByte<EObjectTypeQuery>> CollisionObjectTypes;
CollisionObjectTypes.Add(UEngineTypes::ConvertToObjectType(ECC_WorldStatic));
TArray<AActor*> ActorsToIgnore; // TODO: Add configuration to ignore actor types
TArray<FHitResult> OutHits;
auto ShouldDebug = DebugLineTraces ? EDrawDebugTrace::ForDuration : EDrawDebugTrace::None;
bool bGotHits = UKismetSystemLibrary::CapsuleTraceMultiForObjects(
GetWorld(), Start, End, ActiveCapsuleComponent->GetScaledCapsuleRadius() * CapsulePercentageForTrace,
ActiveCapsuleComponent->GetScaledCapsuleHalfHeight() * CapsulePercentageForTrace, CollisionObjectTypes, true,
ActorsToIgnore,
ShouldDebug,
OutHits, true);
if (bGotHits)
{
// The list of actors hit by the line trace, that means that they are occluded from view
TSet<const AActor*> ActorsJustOccluded;
// Hide actors that are occluded by the camera
for (FHitResult Hit : OutHits)
{
const AActor* HitActor = Cast<AActor>(Hit.GetActor());
HideOccludedActor(HitActor);
ActorsJustOccluded.Add(HitActor);
}
// Show actors that are currently hidden but that are not occluded by the camera anymore
for (auto& Elem : OccludedActors)
{
if (!ActorsJustOccluded.Contains(Elem.Value.Actor) && Elem.Value.IsOccluded)
{
ShowOccludedActor(Elem.Value);
if (DebugLineTraces)
{
UE_LOG(LogTemp, Warning,
TEXT("Actor %s was occluded, but it's not occluded anymore with the new hits."), *Elem.Value.Actor->GetName());
}
}
}
}
else
{
ForceShowOccludedActors();
}
}
bool AOcclusionAwarePlayerController::HideOccludedActor(const AActor* Actor)
{
FCameraOccludedActor* ExistingOccludedActor = OccludedActors.Find(Actor);
if (ExistingOccludedActor && ExistingOccludedActor->IsOccluded)
{
if (DebugLineTraces) UE_LOG(LogTemp, Warning, TEXT("Actor %s was already occluded. Ignoring."),
*Actor->GetName());
return false;
}
if (ExistingOccludedActor && IsValid(ExistingOccludedActor->Actor))
{
ExistingOccludedActor->IsOccluded = true;
OnHideOccludedActor(*ExistingOccludedActor);
if (DebugLineTraces) UE_LOG(LogTemp, Warning, TEXT("Actor %s exists, but was not occluded. Occluding it now."), *Actor->GetName());
}
else
{
UStaticMeshComponent* StaticMesh = Cast<UStaticMeshComponent>(
Actor->GetComponentByClass(UStaticMeshComponent::StaticClass()));
FCameraOccludedActor OccludedActor;
OccludedActor.Actor = Actor;
OccludedActor.StaticMesh = StaticMesh;
OccludedActor.Materials = StaticMesh->GetMaterials();
OccludedActor.IsOccluded = true;
OccludedActors.Add(Actor, OccludedActor);
OnHideOccludedActor(OccludedActor);
if (DebugLineTraces) UE_LOG(LogTemp, Warning, TEXT("Actor %s does not exist, creating and occluding it now."), *Actor->GetName());
}
return true;
}
void AOcclusionAwarePlayerController::ForceShowOccludedActors()
{
for (auto& Elem : OccludedActors)
{
if (Elem.Value.IsOccluded)
{
ShowOccludedActor(Elem.Value);
if (DebugLineTraces) UE_LOG(LogTemp, Warning, TEXT("Actor %s was occluded, force to show again."), *Elem.Value.Actor->GetName());
}
}
}
void AOcclusionAwarePlayerController::ShowOccludedActor(FCameraOccludedActor& OccludedActor)
{
if (!IsValid(OccludedActor.Actor))
{
OccludedActors.Remove(OccludedActor.Actor);
}
OccludedActor.IsOccluded = false;
OnShowOccludedActor(OccludedActor);
}
bool AOcclusionAwarePlayerController::OnShowOccludedActor(const FCameraOccludedActor& OccludedActor) const
{
for (int matIdx = 0; matIdx < OccludedActor.Materials.Num(); ++matIdx)
{
OccludedActor.StaticMesh->SetMaterial(matIdx, OccludedActor.Materials[matIdx]);
}
return true;
}
bool AOcclusionAwarePlayerController::OnHideOccludedActor(const FCameraOccludedActor& OccludedActor) const
{
for (int i = 0; i < OccludedActor.StaticMesh->GetNumMaterials(); ++i)
{
OccludedActor.StaticMesh->SetMaterial(i, FadeMaterial);
}
return true;
}
@cinvisch
Copy link

cinvisch commented Apr 23, 2024

Hi @alfredbaudisch, thanks for this great tutorial! I managed to make it work just fine although I'm fairly new to C++.

However, Unreal always crashes when the line trace hits some actors in my map. I tried to find out why some actors become transparent without any problem, and some others make the software crash, but I had no luck so far and I'm puzzled.

In the crash report there is a reference to line 57 "ActorsJustOccluded.Add(HitActor);" and 100 "OccludedActor.Materials = StaticMesh->GetMaterials();" in the .cpp.

For line 100, the debugger sends "Unhandled exception thrown: read access violation. StaticMesh was nullptr"

At first I thought it had something to do with the material, but I tried the material of one actor that made the game crash on another actor, and it worked just fine.

Do you have any idea where this might come from?

I'm looking forward to reading from you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment