Compare commits
2 Commits
368f27a417
...
e8e128457a
| Author | SHA1 | Date | |
|---|---|---|---|
| e8e128457a | |||
| 5970db0512 |
+24
-5
@@ -685,12 +685,31 @@ couleurs sur des MID). `DA_CharacterParts` couvre Body A ; **Body B reste à rem
|
|||||||
`ABP_PlayerBody_B` et sans barbes ni moustaches — le pack n'en livre pas, et une liste vide se
|
`ABP_PlayerBody_B` et sans barbes ni moustaches — le pack n'en livre pas, et une liste vide se
|
||||||
comporte déjà comme `NoPart`.
|
comporte déjà comme `NoPart`.
|
||||||
|
|
||||||
Restent les lots 3 et 4 :
|
**Le lot 3 est fait** : l'apparence vit sur `ASurvivalPlayerState` en `ReplicatedUsing` et se
|
||||||
|
persiste dans `USurvivalUserSettings`. Elle survit donc au respawn — le pawn est un nouvel acteur,
|
||||||
|
le PlayerState non — et au relancement du jeu.
|
||||||
|
|
||||||
- **Lot 3** — réplication par `ASurvivalPlayerState` en `ReplicatedUsing`, persistance dans
|
- **`SubmitCharacterAppearance()` sur `AFpsPlayerController` est le point d'entrée UNIQUE.** Il
|
||||||
`USurvivalUserSettings`
|
persiste *et* annonce au serveur, jamais l'un sans l'autre : persister sans annoncer laisserait
|
||||||
- **Lot 4** — l'écran : `ACharacterPreviewActor` dans `MenuScene`, caméra à cadrages interpolés,
|
le joueur seul à se voir changé, annoncer sans persister le ferait repartir au visage par défaut
|
||||||
UI à gauche
|
au lancement suivant. C'est par là que passent les commandes console, et par là que passera
|
||||||
|
l'écran du lot 4.
|
||||||
|
- **La course pawn / PlayerState se résout des deux côtés.** Chez un client ils arrivent par deux
|
||||||
|
chemins de réplication indépendants et rien ne dit lequel gagne : `OnRep_CharacterAppearance`
|
||||||
|
monte le personnage si le pawn est déjà là, `AFpsPlayer::OnRep_PlayerState` va chercher
|
||||||
|
l'apparence si c'est le pawn qui est arrivé le premier. Les deux appellent la même fonction
|
||||||
|
idempotente. Chez l'hôte, aucun `OnRep` ne se déclenche — c'est le `BeginPlay` du pawn qui monte,
|
||||||
|
et `SetCharacterAppearance` qui applique en local juste après avoir écrit la propriété.
|
||||||
|
- **Rien n'est validé côté PlayerState, et c'est délibéré** : borner un index demande le catalogue,
|
||||||
|
que seul `UCharacterAppearanceComponent` connaît — or il passe déjà tout ce qu'il monte par
|
||||||
|
`Catalog->Sanitize()`, sur chaque machine. Dupliquer la validation demanderait de donner le
|
||||||
|
catalogue au PlayerState pour un résultat identique.
|
||||||
|
- Piège de test : **en PIE à deux joueurs, les deux fenêtres lisent le même `.ini`**, donc les deux
|
||||||
|
personnages démarrent identiques. Il faut changer l'un des deux à la console pour vérifier que la
|
||||||
|
réplication marche.
|
||||||
|
|
||||||
|
Reste le **lot 4** — l'écran : `ACharacterPreviewActor` dans `MenuScene`, caméra à cadrages
|
||||||
|
interpolés, UI à gauche. Et le remplissage de **Body B** dans `DA_CharacterParts`.
|
||||||
|
|
||||||
#### Trois pièges du lot 2, aucun visible depuis le code
|
#### Trois pièges du lot 2, aucun visible depuis le code
|
||||||
|
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ void UCharacterAppearanceComponent::RebuildMeshes(const FCharacterBodySet& BodyS
|
|||||||
|
|
||||||
if (BodySet.BodyMeshes.Num() > 0 && BodySet.BodyMeshes[0])
|
if (BodySet.BodyMeshes.Num() > 0 && BodySet.BodyMeshes[0])
|
||||||
{
|
{
|
||||||
TargetMesh->SetSkeletalMeshAsset(BodySet.BodyMeshes[0]);
|
SetPartMesh(TargetMesh, BodySet.BodyMeshes[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BodySet.AnimClass)
|
if (BodySet.AnimClass)
|
||||||
@@ -267,7 +267,7 @@ void UCharacterAppearanceComponent::RebuildMeshes(const FCharacterBodySet& BodyS
|
|||||||
|
|
||||||
if (USkeletalMeshComponent* Segment = ExtraBodyComponents[i])
|
if (USkeletalMeshComponent* Segment = ExtraBodyComponents[i])
|
||||||
{
|
{
|
||||||
Segment->SetSkeletalMeshAsset(BodySet.BodyMeshes[i + 1]);
|
SetPartMesh(Segment, BodySet.BodyMeshes[i + 1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,7 +278,7 @@ void UCharacterAppearanceComponent::RebuildMeshes(const FCharacterBodySet& BodyS
|
|||||||
{
|
{
|
||||||
if (USkeletalMeshComponent* Segment = ExtraBodyComponents[i])
|
if (USkeletalMeshComponent* Segment = ExtraBodyComponents[i])
|
||||||
{
|
{
|
||||||
Segment->SetSkeletalMeshAsset(nullptr);
|
SetPartMesh(Segment, nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,7 +305,7 @@ void UCharacterAppearanceComponent::RebuildMeshes(const FCharacterBodySet& BodyS
|
|||||||
|
|
||||||
if (USkeletalMeshComponent* PartComponent = EnsurePartComponent(Category))
|
if (USkeletalMeshComponent* PartComponent = EnsurePartComponent(Category))
|
||||||
{
|
{
|
||||||
PartComponent->SetSkeletalMeshAsset(Mesh);
|
SetPartMesh(PartComponent, Mesh);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -432,6 +432,26 @@ void UCharacterAppearanceComponent::ApplyColors(const FCharacterBodySet& BodySet
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UCharacterAppearanceComponent::SetPartMesh(USkeletalMeshComponent* MeshComponent, USkeletalMesh* NewMesh)
|
||||||
|
{
|
||||||
|
if (!MeshComponent || MeshComponent->GetSkeletalMeshAsset() == NewMesh)
|
||||||
|
{
|
||||||
|
// Rien a faire, et surtout : on ne jette pas les MID en place pour
|
||||||
|
// rien. ApplyColors les reutilise, c'est ce qui rend le changement de
|
||||||
|
// couleur seul aussi bon marche.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// LE point du montage a ne pas oublier : les materiaux surcharges d'un
|
||||||
|
// composant vivent dans OverrideMaterials, un tableau qui lui appartient et
|
||||||
|
// que changer de mesh ne touche PAS. Sans ce vidage, passer du corps A au
|
||||||
|
// corps B garde le MID derive de MI_BodyA_a sur l'element 0 -- on obtient
|
||||||
|
// une silhouette feminine peinte avec la peau et les abdominaux du modele
|
||||||
|
// masculin. Le symptome trompe, parce que le mesh, lui, a bien change.
|
||||||
|
MeshComponent->EmptyOverrideMaterials();
|
||||||
|
MeshComponent->SetSkeletalMeshAsset(NewMesh);
|
||||||
|
}
|
||||||
|
|
||||||
void UCharacterAppearanceComponent::TintAllElements(USkeletalMeshComponent* MeshComponent, FName Parameter, const FLinearColor& Color)
|
void UCharacterAppearanceComponent::TintAllElements(USkeletalMeshComponent* MeshComponent, FName Parameter, const FLinearColor& Color)
|
||||||
{
|
{
|
||||||
if (!MeshComponent || Parameter.IsNone() || !MeshComponent->GetSkeletalMeshAsset())
|
if (!MeshComponent || Parameter.IsNone() || !MeshComponent->GetSkeletalMeshAsset())
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#include "InputActionValue.h"
|
#include "InputActionValue.h"
|
||||||
#include "CharacterAppearanceComponent.h"
|
#include "CharacterAppearanceComponent.h"
|
||||||
#include "CraftingComponent.h"
|
#include "CraftingComponent.h"
|
||||||
|
#include "FpsPlayerController.h"
|
||||||
#include "HeadBobComponent.h"
|
#include "HeadBobComponent.h"
|
||||||
#include "InteractionComponent.h"
|
#include "InteractionComponent.h"
|
||||||
#include "InventoryComponent.h"
|
#include "InventoryComponent.h"
|
||||||
@@ -24,6 +25,7 @@
|
|||||||
#include "Net/UnrealNetwork.h"
|
#include "Net/UnrealNetwork.h"
|
||||||
#include "PickupItem.h"
|
#include "PickupItem.h"
|
||||||
#include "SurvivalGameInstance.h"
|
#include "SurvivalGameInstance.h"
|
||||||
|
#include "SurvivalPlayerState.h"
|
||||||
#include "SurvivalStatsComponent.h"
|
#include "SurvivalStatsComponent.h"
|
||||||
#include "SurvivalUserSettings.h"
|
#include "SurvivalUserSettings.h"
|
||||||
|
|
||||||
@@ -134,6 +136,12 @@ void AFpsPlayer::BeginPlay()
|
|||||||
|
|
||||||
SurvivalStats->OnDied.AddDynamic(this, &AFpsPlayer::HandleDeath);
|
SurvivalStats->OnDied.AddDynamic(this, &AFpsPlayer::HandleDeath);
|
||||||
|
|
||||||
|
// Chez l'hote et en solo, le PlayerState est deja la a cet instant :
|
||||||
|
// OnRep_PlayerState ne s'y declenchant jamais, c'est ce seul appel qui monte
|
||||||
|
// le personnage. Chez un client il rend souvent la main sans rien faire,
|
||||||
|
// l'OnRep prenant le relais -- les deux chemins sont idempotents.
|
||||||
|
RefreshAppearanceFromPlayerState();
|
||||||
|
|
||||||
// Sans cet abonnement, un objet fabrique alors que l'inventaire est plein
|
// Sans cet abonnement, un objet fabrique alors que l'inventaire est plein
|
||||||
// serait purement et simplement perdu.
|
// serait purement et simplement perdu.
|
||||||
CraftingComponent->OnCraftOverflow.AddDynamic(this, &AFpsPlayer::HandleCraftOverflow);
|
CraftingComponent->OnCraftOverflow.AddDynamic(this, &AFpsPlayer::HandleCraftOverflow);
|
||||||
@@ -362,6 +370,28 @@ void AFpsPlayer::AddCrouchTilt(float Direction)
|
|||||||
CrouchTiltSpring.AddImpulse(Direction * UE_EULERS_NUMBER * Omega);
|
CrouchTiltSpring.AddImpulse(Direction * UE_EULERS_NUMBER * Omega);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AFpsPlayer::OnRep_PlayerState()
|
||||||
|
{
|
||||||
|
Super::OnRep_PlayerState();
|
||||||
|
|
||||||
|
RefreshAppearanceFromPlayerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AFpsPlayer::RefreshAppearanceFromPlayerState()
|
||||||
|
{
|
||||||
|
if (!AppearanceComponent)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Absent n'est pas une erreur : c'est le cas normal quand le pawn gagne la
|
||||||
|
// course. L'apparence viendra par OnRep_CharacterAppearance.
|
||||||
|
if (const ASurvivalPlayerState* State = GetPlayerState<ASurvivalPlayerState>())
|
||||||
|
{
|
||||||
|
AppearanceComponent->ApplyAppearance(State->GetCharacterAppearance());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
// Commandes console d'apparence -- de simples delegations. Voir le commentaire
|
// Commandes console d'apparence -- de simples delegations. Voir le commentaire
|
||||||
// de FpsPlayer.h : les UFUNCTION(Exec) d'un composant ne sont jamais routees,
|
// de FpsPlayer.h : les UFUNCTION(Exec) d'un composant ne sont jamais routees,
|
||||||
@@ -373,6 +403,7 @@ void AFpsPlayer::EmberPart(const FString& Part, int32 Index)
|
|||||||
if (AppearanceComponent)
|
if (AppearanceComponent)
|
||||||
{
|
{
|
||||||
AppearanceComponent->ApplyPartByName(Part, Index);
|
AppearanceComponent->ApplyPartByName(Part, Index);
|
||||||
|
SubmitCurrentAppearance();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,6 +412,19 @@ void AFpsPlayer::EmberColor(const FString& Part, int32 ColorIndex)
|
|||||||
if (AppearanceComponent)
|
if (AppearanceComponent)
|
||||||
{
|
{
|
||||||
AppearanceComponent->ApplyColorByName(Part, ColorIndex);
|
AppearanceComponent->ApplyColorByName(Part, ColorIndex);
|
||||||
|
SubmitCurrentAppearance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AFpsPlayer::SubmitCurrentAppearance()
|
||||||
|
{
|
||||||
|
// Applique en local D'ABORD (c'est deja fait par l'appelant), soumis
|
||||||
|
// ensuite : le contraire ferait attendre l'aller-retour reseau pour voir
|
||||||
|
// son propre changement, ce qui donnerait un reglage qui repond en retard.
|
||||||
|
// C'est le meme ordre que Server_SetSprinting.
|
||||||
|
if (AFpsPlayerController* FpsController = Cast<AFpsPlayerController>(GetController()))
|
||||||
|
{
|
||||||
|
FpsController->SubmitCharacterAppearance(AppearanceComponent->GetAppearance());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,9 @@
|
|||||||
#include "FpsPlayer.h"
|
#include "FpsPlayer.h"
|
||||||
#include "GameFramework/GameModeBase.h"
|
#include "GameFramework/GameModeBase.h"
|
||||||
#include "SurvivalGameInstance.h"
|
#include "SurvivalGameInstance.h"
|
||||||
|
#include "SurvivalPlayerState.h"
|
||||||
#include "SurvivalStatsComponent.h"
|
#include "SurvivalStatsComponent.h"
|
||||||
|
#include "SurvivalUserSettings.h"
|
||||||
#include "HotbarWidget.h"
|
#include "HotbarWidget.h"
|
||||||
#include "InputActionValue.h"
|
#include "InputActionValue.h"
|
||||||
#include "InputCoreTypes.h"
|
#include "InputCoreTypes.h"
|
||||||
@@ -111,6 +113,12 @@ void AFpsPlayerController::BeginPlay()
|
|||||||
Server_SetPlayerNickname(Nickname);
|
Server_SetPlayerNickname(Nickname);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dans la foulee du pseudo : les deux repondent au meme constat, le
|
||||||
|
// serveur ne peut connaitre ni l'un ni l'autre. Ils partent donc
|
||||||
|
// ensemble, au seul moment ou l'on est certain que le PlayerState local
|
||||||
|
// existe.
|
||||||
|
AnnounceCharacterAppearance();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Le delegue se declenche a la fin de CHAQUE fondu : c'est
|
// Le delegue se declenche a la fin de CHAQUE fondu : c'est
|
||||||
@@ -1094,6 +1102,47 @@ void AFpsPlayerController::Server_SetPlayerNickname_Implementation(const FString
|
|||||||
UE_LOG(LogTemp, Log, TEXT("AFpsPlayerController : pseudo [%s]."), *State->GetPlayerName());
|
UE_LOG(LogTemp, Log, TEXT("AFpsPlayerController : pseudo [%s]."), *State->GetPlayerName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AFpsPlayerController::SubmitCharacterAppearance(const FCharacterAppearance& InAppearance)
|
||||||
|
{
|
||||||
|
if (USurvivalUserSettings* Settings = USurvivalUserSettings::Get())
|
||||||
|
{
|
||||||
|
Settings->SetCharacterAppearance(InAppearance);
|
||||||
|
|
||||||
|
// Ecrit tout de suite, contrairement aux reglages de l'ecran d'options
|
||||||
|
// qui attendent sa fermeture : un changement d'apparence n'a pas
|
||||||
|
// d'ecran a fermer quand il vient de la console, et le perdre a la
|
||||||
|
// sortie du jeu serait le plus sur moyen de croire que rien ne marche.
|
||||||
|
Settings->SaveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Meme raccourci que pour le pseudo : l'hote est deja l'autorite, passer
|
||||||
|
// par la RPC marcherait mais ferait un aller-retour pour rien.
|
||||||
|
if (HasAuthority())
|
||||||
|
{
|
||||||
|
Server_SetCharacterAppearance_Implementation(InAppearance);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Server_SetCharacterAppearance(InAppearance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AFpsPlayerController::AnnounceCharacterAppearance()
|
||||||
|
{
|
||||||
|
if (const USurvivalUserSettings* Settings = USurvivalUserSettings::Get())
|
||||||
|
{
|
||||||
|
SubmitCharacterAppearance(Settings->GetCharacterAppearance());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AFpsPlayerController::Server_SetCharacterAppearance_Implementation(const FCharacterAppearance& InAppearance)
|
||||||
|
{
|
||||||
|
if (ASurvivalPlayerState* State = GetPlayerState<ASurvivalPlayerState>())
|
||||||
|
{
|
||||||
|
State->SetCharacterAppearance(InAppearance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AFpsPlayerController::ShowInviteFriends()
|
void AFpsPlayerController::ShowInviteFriends()
|
||||||
{
|
{
|
||||||
if (USessionSubsystem* Sessions = GetGameInstance() ? GetGameInstance()->GetSubsystem<USessionSubsystem>() : nullptr)
|
if (USessionSubsystem* Sessions = GetGameInstance() ? GetGameInstance()->GetSubsystem<USessionSubsystem>() : nullptr)
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
|
||||||
|
#include "SurvivalPlayerState.h"
|
||||||
|
|
||||||
|
#include "CharacterAppearanceComponent.h"
|
||||||
|
#include "FpsPlayer.h"
|
||||||
|
#include "Net/UnrealNetwork.h"
|
||||||
|
|
||||||
|
void ASurvivalPlayerState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||||
|
{
|
||||||
|
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||||
|
|
||||||
|
// Aucune condition : COND_OwnerOnly serait exactement le contraire de ce
|
||||||
|
// qu'on veut. L'interet d'une apparence, c'est que les trois autres la
|
||||||
|
// voient ; celle du proprietaire est la seule dont il pourrait se passer,
|
||||||
|
// puisqu'il ne voit pas son propre corps en vue premiere personne.
|
||||||
|
DOREPLIFETIME(ASurvivalPlayerState, CharacterAppearance);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ASurvivalPlayerState::SetCharacterAppearance(const FCharacterAppearance& InAppearance)
|
||||||
|
{
|
||||||
|
if (!HasAuthority())
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("ASurvivalPlayerState : SetCharacterAppearance appelee sans autorite, ignoree."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// L'apparence n'est PAS validee ici, et c'est deliberé : borner un index
|
||||||
|
// demande le catalogue, que seul UCharacterAppearanceComponent connait. Or
|
||||||
|
// celui-ci passe deja tout ce qu'il monte par Catalog->Sanitize(), donc un
|
||||||
|
// index aberrant venu du reseau est corrige au montage, sur chaque machine.
|
||||||
|
// Dupliquer la validation ici demanderait de donner le catalogue au
|
||||||
|
// PlayerState pour un resultat identique -- et cette coop entre amis n'a de
|
||||||
|
// toute facon aucune anti-triche a faire respecter.
|
||||||
|
CharacterAppearance = InAppearance;
|
||||||
|
|
||||||
|
// Un OnRep ne se declenche jamais sur la machine qui ecrit la propriete.
|
||||||
|
// Chez l'hote, qui est serveur ET client local, sans cette ligne son propre
|
||||||
|
// personnage resterait au visage par defaut pendant que les autres le
|
||||||
|
// verraient correctement.
|
||||||
|
ApplyAppearanceToPawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ASurvivalPlayerState::OnRep_CharacterAppearance()
|
||||||
|
{
|
||||||
|
ApplyAppearanceToPawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ASurvivalPlayerState::ApplyAppearanceToPawn()
|
||||||
|
{
|
||||||
|
// Le pawn peut manquer, et ce n'est pas une erreur : chez un client, le
|
||||||
|
// PlayerState et le pawn arrivent par deux chemins de replication
|
||||||
|
// independants, dans un ordre qu'on ne controle pas. Quand c'est le
|
||||||
|
// PlayerState qui gagne, on ne fait rien -- c'est le BeginPlay du pawn qui
|
||||||
|
// viendra chercher l'apparence a son tour.
|
||||||
|
if (AFpsPlayer* Player = Cast<AFpsPlayer>(GetPawn()))
|
||||||
|
{
|
||||||
|
if (UCharacterAppearanceComponent* Appearance = Player->GetAppearanceComponent())
|
||||||
|
{
|
||||||
|
Appearance->ApplyAppearance(CharacterAppearance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ASurvivalPlayerState::CopyProperties(APlayerState* PlayerState)
|
||||||
|
{
|
||||||
|
Super::CopyProperties(PlayerState);
|
||||||
|
|
||||||
|
if (ASurvivalPlayerState* SurvivalState = Cast<ASurvivalPlayerState>(PlayerState))
|
||||||
|
{
|
||||||
|
SurvivalState->CharacterAppearance = CharacterAppearance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -344,3 +344,8 @@ void USurvivalUserSettings::SetPlayInBackground(bool bValue)
|
|||||||
{
|
{
|
||||||
bPlayInBackground = bValue;
|
bPlayInBackground = bValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void USurvivalUserSettings::SetCharacterAppearance(const FCharacterAppearance& InAppearance)
|
||||||
|
{
|
||||||
|
CharacterAppearance = InAppearance;
|
||||||
|
}
|
||||||
|
|||||||
@@ -133,6 +133,15 @@ private:
|
|||||||
void ApplyColors(const FCharacterBodySet& BodySet);
|
void ApplyColors(const FCharacterBodySet& BodySet);
|
||||||
|
|
||||||
/** Pose un parametre vectoriel sur TOUS les elements d'un mesh. */
|
/** Pose un parametre vectoriel sur TOUS les elements d'un mesh. */
|
||||||
|
/**
|
||||||
|
* Assigne un mesh a un composant en vidant d'abord ses materiaux surcharges.
|
||||||
|
*
|
||||||
|
* Indispensable : OverrideMaterials appartient au COMPOSANT, pas au mesh, et
|
||||||
|
* survit donc a un changement d'asset. Sans ce vidage, le corps B garderait
|
||||||
|
* la peau du corps A.
|
||||||
|
*/
|
||||||
|
static void SetPartMesh(USkeletalMeshComponent* MeshComponent, USkeletalMesh* NewMesh);
|
||||||
|
|
||||||
static void TintAllElements(USkeletalMeshComponent* MeshComponent, FName Parameter, const FLinearColor& Color);
|
static void TintAllElements(USkeletalMeshComponent* MeshComponent, FName Parameter, const FLinearColor& Color);
|
||||||
|
|
||||||
/** Le mesh porteur, leader de tous les autres. */
|
/** Le mesh porteur, leader de tous les autres. */
|
||||||
|
|||||||
@@ -154,6 +154,16 @@ public:
|
|||||||
UFUNCTION(Exec, Category = "Apparence")
|
UFUNCTION(Exec, Category = "Apparence")
|
||||||
void EmberAppearanceDump();
|
void EmberAppearanceDump();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* Persiste l'apparence du composant et l'annonce au serveur, via le
|
||||||
|
* controller. Appelee apres chaque commande console, pour que le
|
||||||
|
* changement se voie chez les autres et survive au relancement.
|
||||||
|
*/
|
||||||
|
void SubmitCurrentAppearance();
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
/** Sert a l'UI pour afficher la vraie touche mappee, plutot qu'un "E" ecrit en dur. */
|
/** Sert a l'UI pour afficher la vraie touche mappee, plutot qu'un "E" ecrit en dur. */
|
||||||
UFUNCTION(BlueprintPure, Category = "Input")
|
UFUNCTION(BlueprintPure, Category = "Input")
|
||||||
const UInputAction* GetInteractAction() const { return InteractAction; }
|
const UInputAction* GetInteractAction() const { return InteractAction; }
|
||||||
@@ -230,10 +240,25 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual void CalcCamera(float DeltaTime, FMinimalViewInfo& OutResult) override;
|
virtual void CalcCamera(float DeltaTime, FMinimalViewInfo& OutResult) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* L'autre cote de la course avec le PlayerState.
|
||||||
|
*
|
||||||
|
* Chez un client, le pawn et le PlayerState arrivent par deux chemins de
|
||||||
|
* replication independants : tantot l'apparence est la avant le corps --
|
||||||
|
* ASurvivalPlayerState::OnRep_CharacterAppearance la pose alors lui-meme --
|
||||||
|
* tantot c'est l'inverse, et c'est ici qu'on va la chercher. Ni l'un ni
|
||||||
|
* l'autre ne peut supposer avoir gagne, donc les deux appellent la meme
|
||||||
|
* fonction idempotente.
|
||||||
|
*/
|
||||||
|
virtual void OnRep_PlayerState() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Called when the game starts or when spawned
|
// Called when the game starts or when spawned
|
||||||
virtual void BeginPlay() override;
|
virtual void BeginPlay() override;
|
||||||
|
|
||||||
|
/** Lit l'apparence sur le PlayerState et la monte, s'il est deja arrive. */
|
||||||
|
void RefreshAppearanceFromPlayerState();
|
||||||
|
|
||||||
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||||
|
|
||||||
// Appelés par le CharacterMovement quand la capsule change de taille (accroupi)
|
// Appelés par le CharacterMovement quand la capsule change de taille (accroupi)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
// ESlateVisibility n'est que declare en avant par le moteur : il faut le type
|
// ESlateVisibility n'est que declare en avant par le moteur : il faut le type
|
||||||
// complet pour en stocker une valeur par defaut. Meme raison pour
|
// complet pour en stocker une valeur par defaut. Meme raison pour
|
||||||
// ECraftingStation, stocke en membre.
|
// ECraftingStation, stocke en membre.
|
||||||
|
#include "CharacterAppearanceTypes.h"
|
||||||
#include "Components/SlateWrapperTypes.h"
|
#include "Components/SlateWrapperTypes.h"
|
||||||
#include "CraftingRecipeDataAsset.h"
|
#include "CraftingRecipeDataAsset.h"
|
||||||
#include "GameFramework/PlayerController.h"
|
#include "GameFramework/PlayerController.h"
|
||||||
@@ -115,6 +116,19 @@ public:
|
|||||||
UFUNCTION(BlueprintCallable, Category = "Coop")
|
UFUNCTION(BlueprintCallable, Category = "Coop")
|
||||||
void ShowInviteFriends();
|
void ShowInviteFriends();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Le point d'entree unique d'un changement d'apparence : persiste dans les
|
||||||
|
* reglages du joueur ET l'annonce au serveur.
|
||||||
|
*
|
||||||
|
* Les deux ensemble et jamais l'un sans l'autre -- persister sans annoncer
|
||||||
|
* laisserait le joueur seul a se voir change, annoncer sans persister le
|
||||||
|
* ferait repartir au visage par defaut au prochain lancement. C'est par ici
|
||||||
|
* que passent les commandes console, et par ici que passera l'ecran de
|
||||||
|
* customisation.
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Apparence")
|
||||||
|
void SubmitCharacterAppearance(const FCharacterAppearance& InAppearance);
|
||||||
|
|
||||||
/** Faux quand Steam est absent ou qu'aucune session n'a pu etre creee. */
|
/** Faux quand Steam est absent ou qu'aucune session n'a pu etre creee. */
|
||||||
UFUNCTION(BlueprintPure, Category = "Coop")
|
UFUNCTION(BlueprintPure, Category = "Coop")
|
||||||
bool CanInviteFriends() const;
|
bool CanInviteFriends() const;
|
||||||
@@ -572,6 +586,20 @@ private:
|
|||||||
UFUNCTION(Server, Reliable)
|
UFUNCTION(Server, Reliable)
|
||||||
void Server_SetPlayerNickname(const FString& Nickname);
|
void Server_SetPlayerNickname(const FString& Nickname);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Meme chemin que le pseudo, et pour la meme raison : le serveur ne peut
|
||||||
|
* pas deviner l'apparence choisie par un client, elle vit dans le fichier
|
||||||
|
* de reglages de SA machine. C'est donc lui qui l'annonce.
|
||||||
|
*
|
||||||
|
* Treize octets envoyes une fois a la connexion -- l'apparence ne change
|
||||||
|
* pas en cours de partie, l'ecran de customisation vit dans le menu.
|
||||||
|
*/
|
||||||
|
UFUNCTION(Server, Reliable)
|
||||||
|
void Server_SetCharacterAppearance(const FCharacterAppearance& InAppearance);
|
||||||
|
|
||||||
|
/** Lit l'apparence persistee et l'envoie au serveur. Appelee au BeginPlay. */
|
||||||
|
void AnnounceCharacterAppearance();
|
||||||
|
|
||||||
FTimerHandle RespawnTimerHandle;
|
FTimerHandle RespawnTimerHandle;
|
||||||
|
|
||||||
/** Le fondu d'arrivée attend le pawn. */
|
/** Le fondu d'arrivée attend le pawn. */
|
||||||
|
|||||||
@@ -4,20 +4,70 @@
|
|||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "GameFramework/PlayerState.h"
|
#include "GameFramework/PlayerState.h"
|
||||||
|
#include "CharacterAppearanceTypes.h"
|
||||||
#include "SurvivalPlayerState.generated.h"
|
#include "SurvivalPlayerState.generated.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ce que les AUTRES joueurs savent de toi.
|
* Ce que les AUTRES joueurs savent de toi : le pseudo, porte par APlayerState
|
||||||
*
|
* lui-meme, et l'apparence du personnage.
|
||||||
* Vide pour l'instant. Y mettra : pseudo, couleur, temps de survie, nombre de
|
|
||||||
* morts -- tout ce qu'un coequipier doit pouvoir lire sur ton compte.
|
|
||||||
*
|
*
|
||||||
* La regle de partage avec le pawn est simple : le PlayerState survit a la mort
|
* La regle de partage avec le pawn est simple : le PlayerState survit a la mort
|
||||||
* du pawn, le pawn non. L'inventaire et les jauges restent donc sur le pawn,
|
* du pawn, le pawn non. L'inventaire et les jauges restent donc sur le pawn,
|
||||||
* puisque la regle du jeu veut qu'on perde tout au sol en mourant.
|
* puisque la regle du jeu veut qu'on perde tout au sol en mourant -- alors que
|
||||||
|
* reapparaitre avec le visage de quelqu'un d'autre n'aurait aucun sens.
|
||||||
|
*
|
||||||
|
* C'est exactement pour cette raison que l'apparence est ici et non sur
|
||||||
|
* AFpsPlayer : elle appartient au JOUEUR, pas au corps.
|
||||||
*/
|
*/
|
||||||
UCLASS()
|
UCLASS()
|
||||||
class SURVIVAL_PROJET_API ASurvivalPlayerState : public APlayerState
|
class SURVIVAL_PROJET_API ASurvivalPlayerState : public APlayerState
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintPure, Category = "Apparence")
|
||||||
|
const FCharacterAppearance& GetCharacterAppearance() const { return CharacterAppearance; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A n'appeler que sur le SERVEUR : c'est lui qui fait autorite sur ce que
|
||||||
|
* les autres voient.
|
||||||
|
*
|
||||||
|
* Applique aussi en local dans la foulee, parce qu'un OnRep ne se declenche
|
||||||
|
* jamais sur la machine qui ecrit. Sans ce second appel, l'hote serait le
|
||||||
|
* seul a ne pas voir sa propre apparence -- le genre de bug qui ne se
|
||||||
|
* remarque qu'en testant a deux, et du mauvais cote.
|
||||||
|
*/
|
||||||
|
void SetCharacterAppearance(const FCharacterAppearance& InAppearance);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pousse l'apparence sur le pawn possede, s'il existe deja.
|
||||||
|
*
|
||||||
|
* Idempotente et appelee des DEUX cotes de la course : par l'OnRep quand
|
||||||
|
* l'apparence arrive avant le pawn, et par le pawn lui-meme a son BeginPlay
|
||||||
|
* quand c'est l'inverse. On ne sait pas lequel des deux gagne -- chez un
|
||||||
|
* client le pawn et le PlayerState arrivent par deux chemins de replication
|
||||||
|
* independants -- donc les deux doivent savoir se debrouiller seuls.
|
||||||
|
*/
|
||||||
|
void ApplyAppearanceToPawn();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Repliquee a TOUT LE MONDE et pas au seul proprietaire : c'est justement
|
||||||
|
* ce que les autres doivent voir. Treize octets par joueur, envoyes une
|
||||||
|
* fois -- l'apparence ne change pas en cours de partie.
|
||||||
|
*/
|
||||||
|
UPROPERTY(ReplicatedUsing = OnRep_CharacterAppearance)
|
||||||
|
FCharacterAppearance CharacterAppearance;
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void OnRep_CharacterAppearance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Le moteur engendre un NOUVEAU PlayerState a chaque voyage de map, et
|
||||||
|
* recopie l'ancien par ici. Sans cette surcharge, changer de carte remettrait
|
||||||
|
* tout le monde au visage par defaut.
|
||||||
|
*/
|
||||||
|
virtual void CopyProperties(APlayerState* PlayerState) override;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "GameFramework/GameUserSettings.h"
|
#include "GameFramework/GameUserSettings.h"
|
||||||
|
#include "CharacterAppearanceTypes.h"
|
||||||
#include "SurvivalUserSettings.generated.h"
|
#include "SurvivalUserSettings.generated.h"
|
||||||
|
|
||||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnSurvivalSettingsApplied);
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnSurvivalSettingsApplied);
|
||||||
@@ -286,7 +287,39 @@ public:
|
|||||||
UFUNCTION(BlueprintCallable, Category = "Reglages|Audio")
|
UFUNCTION(BlueprintCallable, Category = "Reglages|Audio")
|
||||||
void SetPlayInBackground(bool bValue);
|
void SetPlayInBackground(bool bValue);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
// Apparence du personnage
|
||||||
|
//
|
||||||
|
// Ici et pas dans une sauvegarde de partie : l'apparence appartient au
|
||||||
|
// JOUEUR et pas a une partie donnee, elle doit le suivre d'une session a
|
||||||
|
// l'autre. C'est aussi ce qui donne un visage a qui arrive par invitation
|
||||||
|
// Steam sans passer par l'ecran de customisation.
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintPure, Category = "Reglages|Apparence")
|
||||||
|
const FCharacterAppearance& GetCharacterAppearance() const { return CharacterAppearance; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* N'ecrit rien sur le disque : la sauvegarde a lieu a la fermeture de
|
||||||
|
* l'ecran, comme pour tous les autres reglages. Ne diffuse pas non plus
|
||||||
|
* OnSurvivalSettingsApplied -- le pawn ne relit pas son apparence dans les
|
||||||
|
* reglages, il la tient du PlayerState.
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Reglages|Apparence")
|
||||||
|
void SetCharacterAppearance(const FCharacterAppearance& InAppearance);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/**
|
||||||
|
* Serialisee telle quelle dans le .ini, sous la forme
|
||||||
|
* `CharacterAppearance=(BodyType=BodyA,HeadIndex=0,...)`.
|
||||||
|
*
|
||||||
|
* C'est le dividende de la struct d'index : une apparence faite de
|
||||||
|
* TSoftObjectPtr aurait donne une douzaine de chemins d'assets a ecrire, et
|
||||||
|
* une seule renommee d'asset aurait suffi a la casser silencieusement.
|
||||||
|
*/
|
||||||
|
UPROPERTY(config)
|
||||||
|
FCharacterAppearance CharacterAppearance;
|
||||||
|
|
||||||
/** Echelle lisible (5 a 100), convertie a la lecture. Jamais brute. */
|
/** Echelle lisible (5 a 100), convertie a la lecture. Jamais brute. */
|
||||||
UPROPERTY(config)
|
UPROPERTY(config)
|
||||||
float LookSensitivity;
|
float LookSensitivity;
|
||||||
|
|||||||
Reference in New Issue
Block a user