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:
@@ -344,13 +344,28 @@ void AZMMOPlayerCharacter::FlushInputAxisToServer(const float DeltaSeconds)
|
||||
|
||||
++InputSequence;
|
||||
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(
|
||||
PendingMoveForward,
|
||||
PendingMoveRight,
|
||||
bPendingJumpPressed,
|
||||
bPendingJumpReleased,
|
||||
InputSequence,
|
||||
ClientTimeMs);
|
||||
ClientTimeMs,
|
||||
ViewYawDeg,
|
||||
PosCm,
|
||||
FVector2D(Vel.X, Vel.Y));
|
||||
|
||||
SendAccumulatorSec = 0.0f;
|
||||
TimeSinceLastSendSec = 0.0f;
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
#include "ZMMOPlayerProxy.h"
|
||||
|
||||
#include "Animation/AnimInstance.h"
|
||||
#include "Components/CapsuleComponent.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "Engine/SkeletalMesh.h"
|
||||
#include "Engine/World.h"
|
||||
#include "GameFramework/CharacterMovementComponent.h"
|
||||
#include "UObject/ConstructorHelpers.h"
|
||||
#include "ZMMO.h"
|
||||
|
||||
AZMMOPlayerProxy::AZMMOPlayerProxy(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
@@ -19,8 +25,54 @@ AZMMOPlayerProxy::AZMMOPlayerProxy(const FObjectInitializer& ObjectInitializer)
|
||||
UCharacterMovementComponent* CMC = GetCharacterMovement();
|
||||
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->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;
|
||||
@@ -43,8 +95,31 @@ void AZMMOPlayerProxy::SetZMMOIdentity(const int64 InEntityId, const EZMMOEntity
|
||||
void AZMMOPlayerProxy::ApplyEntitySnapshot(const FZMMOEntitySnapshot& Snapshot)
|
||||
{
|
||||
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;
|
||||
bHasFirstSnapshot = true;
|
||||
RefreshAnimationDrivers();
|
||||
|
||||
@@ -40,12 +40,17 @@
|
||||
"TargetAllowList": [
|
||||
"Editor"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "meshy",
|
||||
"Enabled": false,
|
||||
"SupportedTargetPlatforms": [
|
||||
"Win64",
|
||||
"Mac"
|
||||
]
|
||||
}
|
||||
],
|
||||
"TargetPlatforms": [],
|
||||
"AdditionalRootDirectories": [],
|
||||
"AdditionalPluginDirectories": [
|
||||
"../../Server/Plugins/Unreal"
|
||||
],
|
||||
"EpicSampleNameHash": ""
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user