效果图:
第一步,创建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") };