Skip to content

Instantly share code, notes, and snippets.

@alfredbaudisch
Last active July 10, 2023 20:56
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Embed
What would you like to do?
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 DACHSHUNDSPA_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.Actor);
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;
}
@StoreBoughtRocketGames
Copy link

It looks like this doesn't work with Unreal engine 5.1. I'm getting an error 'Actor' is not a member of 'FHitResult' on line 68 of the .cpp file. If it doesn't work for UE5, could you add a note to the blog post? Really disappointed I couldn't use it. Looked like a really simple solution too. But, I'm not versed in c++ with UE at all.

2>[2/6] Compile OcclusionAwarePlayerController.cpp
2>C:\Repositories\BadArtJam\ProjectCode\BadArtJam\Source\BadArtJam\OcclusionAwarePlayerController.cpp(68): error C2039: 'Actor': is not a member of 'FHitResult'
2>C:\Program Files\Epic Games\UE_5.1\Engine\Intermediate\Build\Win64\UnrealEditor\Inc\Engine\UHT\GameplayStatics.generated.h(55): note: see declaration of 'FHitResult'
2>[3/6] Compile OcclusionAwarePlayerController.gen.cpp
2>[4/6] Link UnrealEditor-BadArtJam.lib cancelled
2>[5/6] Link UnrealEditor-BadArtJam.dll cancelled
2>[6/6] WriteMetadata BadArtJamEditor.target cancelled

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