// Fill out your copyright notice in the Description page of Project Settings. #include "PlayerNameplateComponent.h" #include "Engine/Engine.h" #include "Engine/World.h" #include "GameFramework/Pawn.h" #include "GameFramework/PlayerController.h" #include "GameFramework/PlayerState.h" #include "PlayerNameplateWidget.h" UPlayerNameplateComponent::UPlayerNameplateComponent() { // Espace ecran : l'etiquette fait toujours face a la camera, par // construction, et n'est jamais occultee par le decor. SetWidgetSpace(EWidgetSpace::Screen); // Le widget se dimensionne sur son contenu : un pseudo court n'a pas a // trainer une boite de la largeur du plus long. SetDrawAtDesiredSize(true); // Une etiquette ne se vise pas et n'arrete pas un trace : sans ca, elle // bloquerait le canal Visibility et masquerait l'interaction du joueur // qu'elle surmonte. SetCollisionEnabled(ECollisionEnabled::NoCollision); SetGenerateOverlapEvents(false); // Au-dessus de la tete. Reglable dans le Blueprint, la capsule et la taille // du personnage pouvant changer. SetRelativeLocation(FVector(0.f, 0.f, 100.f)); // Cachee au depart : la premiere passe decidera. Sans ca, une etiquette // sans nom clignote pendant la frame qui precede l'arrivee du PlayerState. SetVisibility(false); PrimaryComponentTick.bCanEverTick = true; } void UPlayerNameplateComponent::BeginPlay() { Super::BeginPlay(); // Un serveur dedie ne rend rien : lui faire calculer des distances pour des // etiquettes que personne ne verra serait du gaspillage pur. Sans effet sur // notre serveur d'ecoute, qui n'est pas dans ce mode. if (GetNetMode() == NM_DedicatedServer) { SetComponentTickEnabled(false); return; } const APawn* OwnerPawn = Cast(GetOwner()); // Son propre pseudo : on eteint definitivement, tick compris. Ce n'est pas // un cas a reevaluer -- un pawn ne change pas de proprietaire en cours de // vie, et apres un respawn c'est un NOUVEL acteur qui repassera par ici. if (bHideOnOwningPlayer && OwnerPawn && OwnerPawn->IsLocallyControlled()) { SetVisibility(false); SetComponentTickEnabled(false); } } APlayerState* UPlayerNameplateComponent::GetOwnerPlayerState() const { const APawn* OwnerPawn = Cast(GetOwner()); return OwnerPawn ? OwnerPawn->GetPlayerState() : nullptr; } void UPlayerNameplateComponent::RefreshName() { // Le PlayerState arrive APRES le pawn chez un client : la premiere passe // trouve donc un nom vide, et il faut continuer a regarder. const APlayerState* State = GetOwnerPlayerState(); if (!State) { return; } const FString CurrentName = State->GetPlayerName(); if (CurrentName.IsEmpty() || CurrentName == LastShownName) { return; } if (UPlayerNameplateWidget* Nameplate = Cast(GetUserWidgetObject())) { Nameplate->SetPlayerName(FText::FromString(CurrentName)); LastShownName = CurrentName; } } void UPlayerNameplateComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); // Le point de vue du joueur LOCAL, et non celui du pawn porteur : c'est de // notre ecran qu'on parle. GetFirstLocalPlayerController et non // GetFirstPlayerController -- en PIE a deux fenetres, le second rendrait le // controller de l'autre instance et l'etiquette clignoterait. const APlayerController* Viewer = GEngine ? GEngine->GetFirstLocalPlayerController(GetWorld()) : nullptr; if (!Viewer || !Viewer->PlayerCameraManager) { SetVisibility(false); return; } const float Distance = FVector::Dist( Viewer->PlayerCameraManager->GetCameraLocation(), GetComponentLocation()); // Au-dela de la portee on eteint le composant lui-meme, pas seulement son // opacite : un widget transparent continue d'etre projete et dessine. if (Distance > MaxDrawDistance) { SetVisibility(false); return; } RefreshName(); // Sans nom a montrer, rien a montrer du tout. if (LastShownName.IsEmpty()) { SetVisibility(false); return; } UPlayerNameplateWidget* Nameplate = Cast(GetUserWidgetObject()); if (!Nameplate) { SetVisibility(false); return; } // Echelle en 1/d : c'est la loi de la perspective, et c'est ce que l'oeil // attend. A ReferenceDistance l'etiquette vaut exactement ce qui est dessine // dans le WBP ; deux fois plus pres elle double, deux fois plus loin elle // est de moitie -- borne des deux cotes. const float SafeDistance = FMath::Max(Distance, 1.f); const float Scale = FMath::Clamp(ReferenceDistance / SafeDistance, MinScale, MaxScale); Nameplate->SetRenderScale(FVector2D(Scale, Scale)); // Fondu sur la derniere bande avant la portee maximale. FadeLength est // borne a MaxDrawDistance : un fondu plus long que la portee elle-meme // donnerait une etiquette jamais totalement opaque. const float EffectiveFade = FMath::Clamp(FadeLength, 0.f, MaxDrawDistance); float Opacity = 1.f; if (EffectiveFade > KINDA_SMALL_NUMBER) { const float FadeStart = MaxDrawDistance - EffectiveFade; Opacity = FMath::Clamp(1.f - (Distance - FadeStart) / EffectiveFade, 0.f, 1.f); } Nameplate->SetRenderOpacity(Opacity); SetVisibility(true); }