Files
2026-07-28 14:10:17 +02:00

198 lines
4.9 KiB
C++

// Fill out your copyright notice in the Description page of Project Settings.
#include "ScreenFadeComponent.h"
#include "Blueprint/UserWidget.h"
#include "Camera/PlayerCameraManager.h"
#include "GameFramework/PlayerController.h"
namespace
{
// Le voile doit passer devant absolument tout, y compris les widgets qu'on
// ajouterait plus tard sans y penser.
constexpr int32 ZOrderFade = 1000;
}
UScreenFadeComponent::UScreenFadeComponent()
{
// Le composant ne tourne QUE pendant un fondu : on allume le tick dans
// FadeIn/FadeOut et on l'eteint des la cible atteinte. Ticker en permanence
// pour une valeur qui ne bouge qu'une seconde par mort serait du gaspillage.
PrimaryComponentTick.bCanEverTick = true;
PrimaryComponentTick.bStartWithTickEnabled = false;
// Le fondu doit continuer d'avancer quand le monde est fige : c'est le seul
// moyen de partir au noir depuis le menu de pause. Sans ce drapeau, l'ecran
// resterait bloque a mi-chemin et le delegue ne se declencherait jamais.
PrimaryComponentTick.bTickEvenWhenPaused = true;
}
void UScreenFadeComponent::BeginPlay()
{
Super::BeginPlay();
// Cree des maintenant pour que le voile puisse couvrir des la premiere
// frame, avant meme que le reste du HUD existe.
EnsureWidget();
}
APlayerController* UScreenFadeComponent::GetOwningController() const
{
return Cast<APlayerController>(GetOwner());
}
void UScreenFadeComponent::EnsureWidget()
{
if (FadeWidget)
{
return;
}
APlayerController* Controller = GetOwningController();
if (!Controller)
{
UE_LOG(LogTemp, Error, TEXT("UScreenFadeComponent : doit etre pose sur un PlayerController, pas sur %s."),
*GetNameSafe(GetOwner()));
return;
}
// Un widget n'existe que sur la machine du joueur local : inutile et
// incorrect d'en creer un sur un serveur dedie.
if (!Controller->IsLocalController())
{
return;
}
if (!FadeWidgetClass)
{
UE_LOG(LogTemp, Warning, TEXT("UScreenFadeComponent : FadeWidgetClass n'est pas assignee, aucun fondu ne s'affichera."));
return;
}
FadeWidget = CreateWidget<UUserWidget>(Controller, FadeWidgetClass);
if (FadeWidget)
{
FadeWidget->AddToViewport(ZOrderFade);
SetCoverage(1.f - Alpha);
}
}
void UScreenFadeComponent::SetCoverage(float Coverage)
{
if (!FadeWidget)
{
return;
}
const float Clamped = FMath::Clamp(Coverage, 0.f, 1.f);
FadeWidget->SetRenderOpacity(Clamped);
// HitTestInvisible et jamais Visible : un voile qui capture la souris
// bloquerait les boutons du menu meme completement transparent.
FadeWidget->SetVisibility(FMath::IsNearlyZero(Clamped)
? ESlateVisibility::Collapsed
: ESlateVisibility::HitTestInvisible);
}
void UScreenFadeComponent::FadeIn(float Duration)
{
EnsureWidget();
if (APlayerController* Controller = GetOwningController())
{
if (Controller->PlayerCameraManager)
{
Controller->PlayerCameraManager->StartCameraFade(
/*FromAlpha=*/1.f, /*ToAlpha=*/0.f,
Duration, FadeColor,
/*bShouldFadeAudio=*/true);
}
}
CurrentDuration = FMath::Max(Duration, 0.01f);
Alpha = 0.f;
Target = 1.f;
bFading = true;
SetCoverage(1.f);
SetComponentTickEnabled(true);
}
void UScreenFadeComponent::FadeOut(float Duration)
{
EnsureWidget();
if (APlayerController* Controller = GetOwningController())
{
if (Controller->PlayerCameraManager)
{
// bHoldWhenFinished : l'ecran reste noir au lieu de revenir tout seul.
Controller->PlayerCameraManager->StartCameraFade(
/*FromAlpha=*/0.f, /*ToAlpha=*/1.f,
Duration, FadeColor,
/*bShouldFadeAudio=*/true, /*bHoldWhenFinished=*/true);
}
}
CurrentDuration = FMath::Max(Duration, 0.01f);
Target = 0.f;
bFading = true;
SetComponentTickEnabled(true);
}
void UScreenFadeComponent::SnapToBlack()
{
EnsureWidget();
if (APlayerController* Controller = GetOwningController())
{
if (Controller->PlayerCameraManager)
{
Controller->PlayerCameraManager->SetManualCameraFade(1.f, FadeColor, /*bFadeAudio=*/true);
}
}
Alpha = 0.f;
Target = 0.f;
bFading = false;
SetCoverage(1.f);
SetComponentTickEnabled(false);
}
void UScreenFadeComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (!bFading)
{
return;
}
const float Step = DeltaTime / CurrentDuration;
Alpha = FMath::Clamp(Alpha + Step * FMath::Sign(Target - Alpha), 0.f, 1.f);
// Alpha va de 0 (ecran couvert) a 1 (jeu visible) : le voile suit l'inverse.
SetCoverage(1.f - Alpha);
if (FMath::IsNearlyEqual(Alpha, Target))
{
bFading = false;
// On eteint le tick AVANT de prevenir : un abonne qui relance un fondu
// dans son handler doit pouvoir le rallumer.
SetComponentTickEnabled(false);
OnFadeFinished.Broadcast();
}
}
void UScreenFadeComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
if (FadeWidget)
{
FadeWidget->RemoveFromParent();
FadeWidget = nullptr;
}
Super::EndPlay(EndPlayReason);
}