feat(client): cliente autoritativo da pose + proxy visivel (ADR 0040/0041)

ZMMOPlayerCharacter: SendInputAxis agora envia viewYawDeg
(GetController()->GetControlRotation().Yaw, fallback para
GetActorRotation().Yaw) + posicao autoritativa (GetActorLocation)
+ velocidade XY (GetVelocity) — bate com o C_INPUT_AXIS de 41
bytes do servidor (ADR 0041).

ZMMOPlayerProxy:
- MOVE_Walking + RotationRate=0 + bRunPhysicsWithNoController=false
  para o AnimBP do Quinn transitar entre Idle/Run sem o CMC
  integrar fisica.
- Defaults visuais SKM_Quinn_Simple + ABP_Unarmed no construtor
  para o proxy ser visivel em PIE sem precisar de BP filha.
- Line trace local de fallback (ECC_Visibility, 5m down) ajusta Z
  quando o snapshot Z vier estranho. Armadilha conhecida: Landscape
  do UE5 esta em ECC_WorldStatic — quando o trace falhar,
  AdjustedPos.Z fica igual ao snapshot. Fix do canal fica no backlog
  (bug do proxy sumir ao encostar parede).

ZMMO.uproject: indentacao tabs + plugin "meshy" registrado disabled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-08 18:20:49 -03:00
parent 3d4d0de40b
commit 1744383f6f
3 changed files with 148 additions and 53 deletions

View File

@@ -344,13 +344,28 @@ void AZMMOPlayerCharacter::FlushInputAxisToServer(const float DeltaSeconds)
++InputSequence; ++InputSequence;
const int32 ClientTimeMs = static_cast<int32>(FPlatformTime::Seconds() * 1000.0); const int32 ClientTimeMs = static_cast<int32>(FPlatformTime::Seconds() * 1000.0);
// ADR 0040: yaw da camara/controller para que o servidor consiga rotar o
// input (referencial-camara) para velocidade mundial. Fallback para o yaw
// do actor se nao houver controller (e.g. tela de loading).
const float ViewYawDeg = (GetController()
? static_cast<float>(GetController()->GetControlRotation().Yaw)
: static_cast<float>(GetActorRotation().Yaw));
// ADR 0041: cliente autoritativo da posicao XYZ + velocidade XY. O CMC ja
// integrou o input local antes deste flush, portanto `GetActorLocation` e
// `GetVelocity` reflectem o estado real que o servidor deve replicar para
// os outros clientes (sujeito ao clamp anti-cheat).
const FVector PosCm = GetActorLocation();
const FVector Vel = GetVelocity();
ZeusNetwork->SendInputAxis( ZeusNetwork->SendInputAxis(
PendingMoveForward, PendingMoveForward,
PendingMoveRight, PendingMoveRight,
bPendingJumpPressed, bPendingJumpPressed,
bPendingJumpReleased, bPendingJumpReleased,
InputSequence, InputSequence,
ClientTimeMs); ClientTimeMs,
ViewYawDeg,
PosCm,
FVector2D(Vel.X, Vel.Y));
SendAccumulatorSec = 0.0f; SendAccumulatorSec = 0.0f;
TimeSinceLastSendSec = 0.0f; TimeSinceLastSendSec = 0.0f;

View File

@@ -1,7 +1,13 @@
#include "ZMMOPlayerProxy.h" #include "ZMMOPlayerProxy.h"
#include "Animation/AnimInstance.h"
#include "Components/CapsuleComponent.h" #include "Components/CapsuleComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Engine/SkeletalMesh.h"
#include "Engine/World.h"
#include "GameFramework/CharacterMovementComponent.h" #include "GameFramework/CharacterMovementComponent.h"
#include "UObject/ConstructorHelpers.h"
#include "ZMMO.h"
AZMMOPlayerProxy::AZMMOPlayerProxy(const FObjectInitializer& ObjectInitializer) AZMMOPlayerProxy::AZMMOPlayerProxy(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer) : Super(ObjectInitializer)
@@ -19,8 +25,54 @@ AZMMOPlayerProxy::AZMMOPlayerProxy(const FObjectInitializer& ObjectInitializer)
UCharacterMovementComponent* CMC = GetCharacterMovement(); UCharacterMovementComponent* CMC = GetCharacterMovement();
if (CMC) if (CMC)
{ {
// MOVE_Walking (em vez de MOVE_None) e necessario para o AnimBP do Quinn
// transitar entre Idle/Run — a state machine consulta `IsFalling()` /
// `MovementMode` no CMC mesmo quando este nao integra fisica. Mantemos
// `bOrientRotationToMovement=false` e `RotationRate=0` porque a
// orientacao e ditada pelo snapshot do servidor; `bRunPhysicsWithNoController`
// evita que o CMC tente integrar movimento quando nao ha PlayerController.
// Espelha [F:/ZeusV1/ZeusClient ZeusRemoteCharacter:34-38].
CMC->bOrientRotationToMovement = false; CMC->bOrientRotationToMovement = false;
CMC->SetMovementMode(MOVE_None); CMC->RotationRate = FRotator::ZeroRotator;
CMC->bRunPhysicsWithNoController = false;
CMC->MaxWalkSpeed = 600.0f;
CMC->SetMovementMode(MOVE_Walking);
}
// Defaults visuais (mesh + AnimBP) em paridade com `AZMMOPlayerCharacter`.
// O `UZMMOWorldSubsystem` instancia `AZMMOPlayerProxy::StaticClass()`
// directamente — sem este bloco o `MeshComponent` ficaria sem
// `SkeletalMesh`/`AnimInstance` e o proxy seria invisivel em PIE. Uma BP
// filha continua opcional (basta atribuir em `RemoteEntityClasses[Player]`).
if (USkeletalMeshComponent* MeshComponent = GetMesh())
{
static ConstructorHelpers::FObjectFinder<USkeletalMesh> QuinnMesh(
TEXT("/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple.SKM_Quinn_Simple"));
static ConstructorHelpers::FClassFinder<UAnimInstance> QuinnAnimBp(
TEXT("/Game/Characters/Mannequins/Anims/Unarmed/ABP_Unarmed"));
if (QuinnMesh.Succeeded())
{
MeshComponent->SetSkeletalMesh(QuinnMesh.Object);
MeshComponent->SetRelativeLocation(FVector(0.0f, 0.0f, -90.0f));
MeshComponent->SetRelativeRotation(FRotator(0.0f, -90.0f, 0.0f));
}
else
{
UE_LOG(LogZMMO, Warning,
TEXT("AZMMOPlayerProxy: SKM_Quinn_Simple nao encontrado em /Game/Characters/Mannequins/Meshes."));
}
if (QuinnAnimBp.Succeeded())
{
MeshComponent->SetAnimationMode(EAnimationMode::AnimationBlueprint);
MeshComponent->SetAnimInstanceClass(QuinnAnimBp.Class);
}
else
{
UE_LOG(LogZMMO, Warning,
TEXT("AZMMOPlayerProxy: ABP_Unarmed nao encontrado em /Game/Characters/Mannequins/Anims/Unarmed."));
}
} }
bUseControllerRotationPitch = false; bUseControllerRotationPitch = false;
@@ -43,8 +95,31 @@ void AZMMOPlayerProxy::SetZMMOIdentity(const int64 InEntityId, const EZMMOEntity
void AZMMOPlayerProxy::ApplyEntitySnapshot(const FZMMOEntitySnapshot& Snapshot) void AZMMOPlayerProxy::ApplyEntitySnapshot(const FZMMOEntitySnapshot& Snapshot)
{ {
const FRotator NewRotation(0.0f, Snapshot.YawDeg, 0.0f); const FRotator NewRotation(0.0f, Snapshot.YawDeg, 0.0f);
SetActorLocationAndRotation(Snapshot.PositionCm, NewRotation);
LastSnapshotPosition = Snapshot.PositionCm; // V0: o servidor envia Z=spawnZ fixo (ADR 0039). ADR 0041 passou a Z a
// vir do cliente do dono (`GetActorLocation().Z`), pelo que o snapshot
// ja contem a Z autoritativa. Mantemos o line trace local como fallback
// gracioso para os casos em que o snapshot Z fica estranho (terreno
// receiver com buracos, drift entre clientes, packet loss). Sem hit, o
// AdjustedPos.Z fica igual a Snapshot.PositionCm.Z.
FVector AdjustedPos = Snapshot.PositionCm;
if (UWorld* World = GetWorld())
{
const FVector TraceStart = AdjustedPos + FVector(0.0f, 0.0f, 1000.0f);
const FVector TraceEnd = AdjustedPos - FVector(0.0f, 0.0f, 4000.0f);
FHitResult Hit;
FCollisionQueryParams Params(TEXT("ZMMOProxyGround"), false, this);
if (World->LineTraceSingleByChannel(Hit, TraceStart, TraceEnd, ECC_Visibility, Params))
{
const float HalfHeight = GetCapsuleComponent()
? GetCapsuleComponent()->GetScaledCapsuleHalfHeight()
: 96.0f;
AdjustedPos.Z = Hit.Location.Z + HalfHeight;
}
}
SetActorLocationAndRotation(AdjustedPos, NewRotation);
LastSnapshotPosition = AdjustedPos;
VisualVelocity = Snapshot.VelocityCmS; VisualVelocity = Snapshot.VelocityCmS;
bHasFirstSnapshot = true; bHasFirstSnapshot = true;
RefreshAnimationDrivers(); RefreshAnimationDrivers();

View File

@@ -1,51 +1,56 @@
{ {
"FileVersion": 3, "FileVersion": 3,
"EngineAssociation": "5.7", "EngineAssociation": "5.7",
"Category": "", "Category": "",
"Description": "", "Description": "",
"Modules": [ "Modules": [
{ {
"Name": "ZMMO", "Name": "ZMMO",
"Type": "Runtime", "Type": "Runtime",
"LoadingPhase": "Default" "LoadingPhase": "Default"
} }
], ],
"Plugins": [ "Plugins": [
{ {
"Name": "ModelingToolsEditorMode", "Name": "ModelingToolsEditorMode",
"Enabled": true "Enabled": true
}, },
{ {
"Name": "StateTree", "Name": "StateTree",
"Enabled": true "Enabled": true
}, },
{ {
"Name": "GameplayStateTree", "Name": "GameplayStateTree",
"Enabled": true "Enabled": true
}, },
{ {
"Name": "VisualStudioTools", "Name": "VisualStudioTools",
"Enabled": true, "Enabled": true,
"SupportedTargetPlatforms": [ "SupportedTargetPlatforms": [
"Win64" "Win64"
] ]
}, },
{ {
"Name": "ZeusNetwork", "Name": "ZeusNetwork",
"Enabled": true "Enabled": true
}, },
{ {
"Name": "ZeusMapTools", "Name": "ZeusMapTools",
"Enabled": true, "Enabled": true,
"TargetAllowList": [ "TargetAllowList": [
"Editor" "Editor"
] ]
} },
], {
"TargetPlatforms": [], "Name": "meshy",
"AdditionalRootDirectories": [], "Enabled": false,
"AdditionalPluginDirectories": [ "SupportedTargetPlatforms": [
"../../Server/Plugins/Unreal" "Win64",
], "Mac"
"EpicSampleNameHash": "" ]
}
],
"AdditionalPluginDirectories": [
"../../Server/Plugins/Unreal"
]
} }