效果图:
第一步,创建C++ Basic Code
第二步,定义键盘和鼠标输入的映射
第三步,修改 Rendering 中的 Custom Depth - Stencil Pass
第四步,找到GlobalPostProcessVolume [如果没有的话自行拖放一个PostProcessVolume组件]
将 unbound 勾选上
再修改 Blendables 为 PPI_OutlineColored
完整代码如下:
MyPlayer.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "GameFramework/Character.h"
#include "MyPlayer.generated.h"
UCLASS()
class OUTLINECPLUSPLUS_API AMyPlayer : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AMyPlayer();
void MoveForward(float val);
void MoveRight(float val);
void LookYaw(float val);
void LookPitch(float val);
void Use();
class AInteractableActor* FindFocusedActor();
void HandleHighlight();
// Called when the game starts or when spawned
virtual void BeginPlay() override;
// Called every frame
virtual void Tick( float DeltaSeconds ) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
private:
UPROPERTY(EditDefaultsOnly)
float InteractionDistance = 300.f; // 交互的范围
class AInteractableActor* FocusedActor;
// 用于 LineTraceSingleByChannel
FCollisionQueryParams TraceParams;
};
MyPlayer.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "InteractableActor.h"
#include "MyPlayer.h"
// Sets default values
AMyPlayer::AMyPlayer()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
TraceParams = FCollisionQueryParams(FName(TEXT("TraceParams")), false, this);
TraceParams.bTraceComplex = false;
TraceParams.bTraceAsyncScene = false;
TraceParams.bReturnPhysicalMaterial = false;
}
// Called when the game starts or when spawned
void AMyPlayer::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AMyPlayer::Tick( float DeltaTime )
{
Super::Tick( DeltaTime );
if (Controller && Controller->IsLocalController())
{
HandleHighlight();
}
}
// Called to bind functionality to input
void AMyPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
InputComponent->BindAxis("MoveForward", this, &AMyPlayer::MoveForward);
InputComponent->BindAxis("MoveRight", this, &AMyPlayer::MoveRight);
InputComponent->BindAxis("LookYaw", this, &AMyPlayer::LookYaw);
InputComponent->BindAxis("LookPitch", this, &AMyPlayer::LookPitch);
InputComponent->BindAction("Use", IE_Pressed, this, &AMyPlayer::Use);
}
// 前后移动
void AMyPlayer::MoveForward(float val)
{
FRotator Rotation(0, GetActorRotation().Yaw, 0); // Roll, Yaw, Pitch
FVector forward = FRotationMatrix(Rotation).GetScaledAxis(EAxis::X);
AddMovementInput(forward, val);
}
// 左右移动
void AMyPlayer::MoveRight(float val)
{
FRotator Rotation(0, GetActorRotation().Yaw, 0); // Roll, Yaw, Pitch
FVector right = FRotationMatrix(Rotation).GetScaledAxis(EAxis::Y);
AddMovementInput(right, val);
}
// 左右转向
void AMyPlayer::LookYaw(float val)
{
AddControllerYawInput(val);
}
// 上下转向
void AMyPlayer::LookPitch(float val)
{
// 注意方向相反
AddControllerPitchInput(val);
}
// 按 E 键与激活对象进行交互
void AMyPlayer::Use()
{
AInteractableActor* Interactable = FindFocusedActor();
if (Interactable)
{
// OnInteract_Implementation
Interactable->OnInteract(this);
}
}
AInteractableActor* AMyPlayer::FindFocusedActor()
{
if (!Controller)
{
return nullptr;
}
FVector Location;
FRotator Rotation;
FHitResult Hit(ForceInit);
Controller->GetPlayerViewPoint(Location, Rotation);
FVector Start = Location;
FVector End = Start + (Rotation.Vector() * InteractionDistance);
// 通过 “射线拾取” 选定对象
GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECC_Camera, TraceParams);
if (Hit.bBlockingHit) // 击中
{
// 获取当前被击中的对象的引用
AInteractableActor* MyCastActor = Cast<AInteractableActor>(Hit.GetActor());
if (MyCastActor)
{
return MyCastActor;
}
}
return nullptr;
}
void AMyPlayer::HandleHighlight()
{
AInteractableActor* NewHighlight = FindFocusedActor();
if (NewHighlight)
{
// 如果当前描边和新激活的对象不是同一个
if (FocusedActor != NewHighlight)
{
if (FocusedActor)
{
// 当前描边对象取消描边
FocusedActor->OnEndFocus();
}
// 描边新激活对象
NewHighlight->OnBeginFocus();
FocusedActor = NewHighlight;
}
}
else
{
if (FocusedActor)
{
// 取消描边
FocusedActor->OnEndFocus();
FocusedActor = nullptr;
}
}
}
InteractableActor.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "GameFramework/Actor.h"
#include "OutlineCPlusPlus.h"
#include "InteractableActor.generated.h"
UCLASS()
class OUTLINECPLUSPLUS_API AInteractableActor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AInteractableActor();
// Called when the game starts or when spawned
virtual void BeginPlay() override;
// Called every frame
virtual void Tick( float DeltaSeconds ) override;
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = Interaction)
void OnInteract(AActor* Caller) ;
virtual void OnInteract_Implementation(AActor* Caller);
void OnBeginFocus();
void OnEndFocus();
private:
UPROPERTY(EditDefaultsOnly)
uint32 bCanInteract : 1;
TArray<UMeshComponent*> Meshes;
UPROPERTY(EditDefaultsOnly)
EStencilColor Color = EStencilColor::SC_Green;
};
InteractableActor.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyPlayer.h"
#include "InteractableActor.h"
// Sets default values
AInteractableActor::AInteractableActor()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
}
// Called when the game starts or when spawned
void AInteractableActor::BeginPlay()
{
Super::BeginPlay();
for (UActorComponent* Mesh : GetComponentsByClass(UMeshComponent::StaticClass()))
{
UMeshComponent* thisMesh = Cast<UMeshComponent>(Mesh);
if (thisMesh)
{
Meshes.Push(thisMesh);
}
}
}
// Called every frame
void AInteractableActor::Tick( float DeltaTime )
{
Super::Tick( DeltaTime );
}
void AInteractableActor::OnInteract_Implementation(AActor* Caller)
{
AMyPlayer* Player = Cast<AMyPlayer>(Caller);
if (Player)
{
GEngine->AddOnScreenDebugMessage(-1,
5.f,
FColor::Red,
FString::Printf(TEXT("Now deleting the interactable actor! "))
);
// 销毁自己
Destroy();
}
}
void AInteractableActor::OnBeginFocus()
{
if (bCanInteract)
{
for (UMeshComponent* Mesh : Meshes)
{
Mesh->SetRenderCustomDepth(true);
Mesh->SetCustomDepthStencilValue((uint8)Color);
}
}
}
void AInteractableActor::OnEndFocus()
{
if (bCanInteract)
{
for (UMeshComponent* Mesh : Meshes)
{
Mesh->SetRenderCustomDepth(false);
}
}
}
颜色 的 Enum
UENUM(BlueprintType)
enum class EStencilColor : uint8
{
SC_Green = 250 UMETA(DisplayName = "Green"),
SC_Blue = 251 UMETA(DisplayName = "Blue"),
SC_Red = 252 UMETA(DisplayName = "Red"),
SC_White = 253 UMETA(DisplayName = "White")
};