feat: adicionar projeto ZClientMMO (Source, Content, Config, Plugins)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
15
Source/ZClientMMO.Target.cs
Normal file
15
Source/ZClientMMO.Target.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
using UnrealBuildTool;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class ZClientMMOTarget : TargetRules
|
||||
{
|
||||
public ZClientMMOTarget(TargetInfo Target) : base(Target)
|
||||
{
|
||||
Type = TargetType.Game;
|
||||
DefaultBuildSettings = BuildSettingsVersion.V6;
|
||||
IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_7;
|
||||
ExtraModuleNames.Add("ZClientMMO");
|
||||
}
|
||||
}
|
||||
278
Source/ZClientMMO/Game/Player/PlayerCharacter.cpp
Normal file
278
Source/ZClientMMO/Game/Player/PlayerCharacter.cpp
Normal file
@@ -0,0 +1,278 @@
|
||||
#include "PlayerCharacter.h"
|
||||
|
||||
#include "EnhancedInputComponent.h"
|
||||
#include "GameFramework/CharacterMovementComponent.h"
|
||||
#include "ZeusNetworkSubsystem.h"
|
||||
#include "ZeusInputBridgeComponent.h"
|
||||
#include "ZeusProxyMovementComponent.h"
|
||||
#include "ZClientMMO.h"
|
||||
#include "GameFramework/SpringArmComponent.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "Animation/AnimInstance.h"
|
||||
#include "Misc/App.h"
|
||||
#include "UObject/UnrealType.h"
|
||||
|
||||
APlayerCharacter::APlayerCharacter(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer.SetDefaultSubobjectClass<UZeusProxyMovementComponent>(ACharacter::CharacterMovementComponentName))
|
||||
{
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
|
||||
InputBridge = CreateDefaultSubobject<UZeusInputBridgeComponent>(TEXT("ZeusInputBridge"));
|
||||
}
|
||||
|
||||
void APlayerCharacter::EnsureExpectedAnimBinding(const bool bForceReinit)
|
||||
{
|
||||
USkeletalMeshComponent* MeshComponent = GetMesh();
|
||||
if (MeshComponent == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
static const TCHAR* AnimClassPath =
|
||||
TEXT("/Script/Engine.AnimBlueprintGeneratedClass'/Game/Characters/Mannequins/Anims/Unarmed/ABP_Unarmed.ABP_Unarmed_C'");
|
||||
UClass* ExpectedAnimClass = StaticLoadClass(UAnimInstance::StaticClass(), nullptr, AnimClassPath);
|
||||
if (ExpectedAnimClass == nullptr)
|
||||
{
|
||||
UE_LOG(LogZClientMMO, Error, TEXT("Anim binding failed: could not load AnimClass path=%s"), AnimClassPath);
|
||||
return;
|
||||
}
|
||||
|
||||
const bool bNeedsFix =
|
||||
MeshComponent->GetAnimationMode() != EAnimationMode::AnimationBlueprint ||
|
||||
MeshComponent->GetAnimClass() != ExpectedAnimClass ||
|
||||
MeshComponent->GetAnimInstance() == nullptr;
|
||||
if (!bNeedsFix && !bForceReinit)
|
||||
{
|
||||
RegisterMeshTickAfterInputBridge();
|
||||
return;
|
||||
}
|
||||
|
||||
MeshComponent->SetAnimationMode(EAnimationMode::AnimationBlueprint);
|
||||
MeshComponent->SetAnimInstanceClass(ExpectedAnimClass);
|
||||
MeshComponent->InitAnim(true);
|
||||
if (IsLocallyControlled() && !bAnimTickPrerequisiteAdded)
|
||||
{
|
||||
AddTickPrerequisiteComponent(MeshComponent);
|
||||
bAnimTickPrerequisiteAdded = true;
|
||||
}
|
||||
UE_LOG(LogZClientMMO, Log, TEXT("Anim binding ok: mesh=%s mode=%d animClass=%s animInstance=%s"),
|
||||
*GetNameSafe(MeshComponent->GetSkeletalMeshAsset()),
|
||||
static_cast<int32>(MeshComponent->GetAnimationMode()),
|
||||
*GetNameSafe(MeshComponent->GetAnimClass()),
|
||||
*GetNameSafe(MeshComponent->GetAnimInstance()));
|
||||
|
||||
RegisterMeshTickAfterInputBridge();
|
||||
}
|
||||
|
||||
void APlayerCharacter::RegisterMeshTickAfterInputBridge()
|
||||
{
|
||||
if (!IsLocallyControlled() || bMeshWaitsForBridgeTickPrerequisite)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (InputBridge == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
USkeletalMeshComponent* MeshComponent = GetMesh();
|
||||
if (MeshComponent == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
MeshComponent->AddTickPrerequisiteComponent(InputBridge);
|
||||
bMeshWaitsForBridgeTickPrerequisite = true;
|
||||
UE_LOG(LogZClientMMO, Log, TEXT("Anim tick order: skeletal mesh waits for ZeusInputBridge (CMC refresh before AnimBP)."));
|
||||
}
|
||||
|
||||
void APlayerCharacter::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
EnsureExpectedAnimBinding(true);
|
||||
|
||||
const UZeusNetworkSubsystem* Zeus = UZeusNetworkSubsystem::GetZeusNetworkSubsystem(GetWorld());
|
||||
const bool bIsConnectedToZeus = Zeus != nullptr && Zeus->IsConnected();
|
||||
|
||||
// Se estiver ligado ao servidor Zeus, mantemos caminho autoritativo por snapshot.
|
||||
// Se nao estiver ligado, habilitamos fallback local para permitir locomocao offline.
|
||||
if (UCharacterMovementComponent* CMC = GetCharacterMovement())
|
||||
{
|
||||
// Online: mantemos CMC ativo para o AnimBP ler Velocity/MovementMode vindos do snapshot.
|
||||
CMC->SetMovementMode(MOVE_Walking);
|
||||
}
|
||||
if (USpringArmComponent* LocalCameraBoom = GetCameraBoom())
|
||||
{
|
||||
LocalCameraBoom->bEnableCameraLag = true;
|
||||
LocalCameraBoom->CameraLagSpeed = bIsConnectedToZeus ? 14.0f : 18.0f;
|
||||
LocalCameraBoom->CameraLagMaxDistance = bIsConnectedToZeus ? 70.0f : 40.0f;
|
||||
}
|
||||
if (IsLocallyControlled() && !bAnimTickPrerequisiteAdded)
|
||||
{
|
||||
if (USkeletalMeshComponent* MeshComponent = GetMesh())
|
||||
{
|
||||
AddTickPrerequisiteComponent(MeshComponent);
|
||||
bAnimTickPrerequisiteAdded = true;
|
||||
}
|
||||
}
|
||||
RegisterMeshTickAfterInputBridge();
|
||||
}
|
||||
|
||||
void APlayerCharacter::Tick(const float DeltaSeconds)
|
||||
{
|
||||
Super::Tick(DeltaSeconds);
|
||||
|
||||
if (!IsLocallyControlled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyShouldMoveToAnimInstanceAfterMesh();
|
||||
|
||||
const double NowSeconds = FApp::GetCurrentTime();
|
||||
if ((NowSeconds - LastAnimLifecycleAuditTimeSeconds) >= 1.0)
|
||||
{
|
||||
LastAnimLifecycleAuditTimeSeconds = NowSeconds;
|
||||
EnsureExpectedAnimBinding(false);
|
||||
}
|
||||
}
|
||||
|
||||
void APlayerCharacter::ApplyShouldMoveToAnimInstanceAfterMesh()
|
||||
{
|
||||
USkeletalMeshComponent* MeshComponent = GetMesh();
|
||||
if (MeshComponent == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
UAnimInstance* AnimInstance = MeshComponent->GetAnimInstance();
|
||||
if (AnimInstance == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const UZeusNetworkSubsystem* Zeus = UZeusNetworkSubsystem::GetZeusNetworkSubsystem(GetWorld());
|
||||
const bool bIsConnectedToZeus = Zeus != nullptr && Zeus->IsConnected();
|
||||
bool bIsFalling = false;
|
||||
if (bIsConnectedToZeus && InputBridge != nullptr)
|
||||
{
|
||||
FVector AuthoritativeVel = FVector::ZeroVector;
|
||||
FVector AuthoritativeAccel = FVector::ZeroVector;
|
||||
EMovementMode AuthoritativeMode = MOVE_Walking;
|
||||
if (InputBridge->TryGetAuthoritativeKinematics(AuthoritativeVel, AuthoritativeAccel, AuthoritativeMode))
|
||||
{
|
||||
bIsFalling = (AuthoritativeMode == MOVE_Falling);
|
||||
}
|
||||
else if (const UCharacterMovementComponent* CMC = GetCharacterMovement())
|
||||
{
|
||||
bIsFalling = CMC->IsFalling();
|
||||
}
|
||||
}
|
||||
else if (const UCharacterMovementComponent* CMC = GetCharacterMovement())
|
||||
{
|
||||
bIsFalling = CMC->IsFalling();
|
||||
}
|
||||
|
||||
const FVector VelForAnim = GetVelocity();
|
||||
const float Speed2D = FVector2D(VelForAnim.X, VelForAnim.Y).Size();
|
||||
static constexpr float ShouldMoveSpeedThresholdCmS = 15.0f;
|
||||
const bool bShouldMove = (Speed2D > ShouldMoveSpeedThresholdCmS) && !bIsFalling;
|
||||
|
||||
FBoolProperty* BoolProp = CastField<FBoolProperty>(AnimInstance->GetClass()->FindPropertyByName(FName(TEXT("ShouldMove"))));
|
||||
if (BoolProp == nullptr)
|
||||
{
|
||||
BoolProp = CastField<FBoolProperty>(AnimInstance->GetClass()->FindPropertyByName(FName(TEXT("bShouldMove"))));
|
||||
}
|
||||
if (BoolProp == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
bool& bStored = *BoolProp->ContainerPtrToValuePtr<bool>(AnimInstance);
|
||||
bStored = bShouldMove;
|
||||
}
|
||||
|
||||
void APlayerCharacter::DoMove(const float Right, const float Forward)
|
||||
{
|
||||
if (InputBridge != nullptr)
|
||||
{
|
||||
InputBridge->SetMovementInput(Forward, Right);
|
||||
}
|
||||
|
||||
const UZeusNetworkSubsystem* Zeus = UZeusNetworkSubsystem::GetZeusNetworkSubsystem(GetWorld());
|
||||
const bool bIsConnectedToZeus = Zeus != nullptr && Zeus->IsConnected();
|
||||
if (bIsConnectedToZeus)
|
||||
{
|
||||
// Online: input vai para o servidor; movimento local fica desativado.
|
||||
return;
|
||||
}
|
||||
|
||||
// Offline: usa o CharacterMovement normal do template.
|
||||
if (UCharacterMovementComponent* CMC = GetCharacterMovement())
|
||||
{
|
||||
if (CMC->MovementMode == MOVE_None)
|
||||
{
|
||||
CMC->SetMovementMode(MOVE_Walking);
|
||||
}
|
||||
}
|
||||
Super::DoMove(Right, Forward);
|
||||
}
|
||||
|
||||
void APlayerCharacter::DoJumpStart()
|
||||
{
|
||||
if (InputBridge != nullptr)
|
||||
{
|
||||
InputBridge->RegisterJumpPressed();
|
||||
}
|
||||
|
||||
const UZeusNetworkSubsystem* Zeus = UZeusNetworkSubsystem::GetZeusNetworkSubsystem(GetWorld());
|
||||
const bool bIsConnectedToZeus = Zeus != nullptr && Zeus->IsConnected();
|
||||
if (!bIsConnectedToZeus)
|
||||
{
|
||||
Super::DoJumpStart();
|
||||
}
|
||||
}
|
||||
|
||||
void APlayerCharacter::DoJumpEnd()
|
||||
{
|
||||
if (InputBridge != nullptr)
|
||||
{
|
||||
InputBridge->RegisterJumpReleased();
|
||||
}
|
||||
|
||||
const UZeusNetworkSubsystem* Zeus = UZeusNetworkSubsystem::GetZeusNetworkSubsystem(GetWorld());
|
||||
const bool bIsConnectedToZeus = Zeus != nullptr && Zeus->IsConnected();
|
||||
if (!bIsConnectedToZeus)
|
||||
{
|
||||
Super::DoJumpEnd();
|
||||
}
|
||||
}
|
||||
|
||||
FVector APlayerCharacter::GetVelocity() const
|
||||
{
|
||||
const UZeusNetworkSubsystem* Zeus = UZeusNetworkSubsystem::GetZeusNetworkSubsystem(GetWorld());
|
||||
const bool bIsConnectedToZeus = Zeus != nullptr && Zeus->IsConnected();
|
||||
if (bIsConnectedToZeus && InputBridge != nullptr)
|
||||
{
|
||||
FVector AuthoritativeVelocity = FVector::ZeroVector;
|
||||
FVector AuthoritativeAcceleration = FVector::ZeroVector;
|
||||
EMovementMode AuthoritativeMode = MOVE_Walking;
|
||||
if (InputBridge->TryGetAuthoritativeKinematics(
|
||||
AuthoritativeVelocity,
|
||||
AuthoritativeAcceleration,
|
||||
AuthoritativeMode))
|
||||
{
|
||||
return AuthoritativeVelocity;
|
||||
}
|
||||
}
|
||||
return Super::GetVelocity();
|
||||
}
|
||||
|
||||
void APlayerCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
|
||||
{
|
||||
Super::SetupPlayerInputComponent(PlayerInputComponent);
|
||||
// O template liga o JumpAction a ACharacter::Jump/StopJumping. Aqui
|
||||
// adicionamos handlers para tambem registar press/release no InputBridge.
|
||||
if (UEnhancedInputComponent* EIC = Cast<UEnhancedInputComponent>(PlayerInputComponent))
|
||||
{
|
||||
EIC->BindAction(JumpAction, ETriggerEvent::Started, this, &APlayerCharacter::DoJumpStart);
|
||||
EIC->BindAction(JumpAction, ETriggerEvent::Completed, this, &APlayerCharacter::DoJumpEnd);
|
||||
}
|
||||
}
|
||||
51
Source/ZClientMMO/Game/Player/PlayerCharacter.h
Normal file
51
Source/ZClientMMO/Game/Player/PlayerCharacter.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "ZClientMMOCharacter.h"
|
||||
#include "PlayerCharacter.generated.h"
|
||||
|
||||
class UZeusInputBridgeComponent;
|
||||
|
||||
/**
|
||||
* APlayerCharacter — pawn local controlavel pelo cliente Zeus.
|
||||
*
|
||||
* Evolui o template `AZClientMMOCharacter` (camera + spring arm + skeletal mesh)
|
||||
* adicionando um `UZeusInputBridgeComponent` que envia C_INPUT_AXIS para o
|
||||
* servidor e aplica os snapshots S_PLAYER_STATE.
|
||||
*
|
||||
* O servidor e' autoritativo: o `CharacterMovementComponent` local fica
|
||||
* desactivado em pawns possessivos para evitar dupla simulacao. Movimento
|
||||
* visual vem dos snapshots (futuras Partes 7+ adicionam predicao real).
|
||||
*/
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class ZCLIENTMMO_API APlayerCharacter : public AZClientMMOCharacter
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
APlayerCharacter(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Zeus|Player")
|
||||
UZeusInputBridgeComponent* InputBridge;
|
||||
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
virtual void Tick(float DeltaSeconds) override;
|
||||
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
|
||||
|
||||
public:
|
||||
virtual void DoMove(float Right, float Forward) override;
|
||||
virtual void DoJumpStart() override;
|
||||
virtual void DoJumpEnd() override;
|
||||
virtual FVector GetVelocity() const override;
|
||||
|
||||
private:
|
||||
double LastAnimLifecycleAuditTimeSeconds = 0.0;
|
||||
bool bAnimTickPrerequisiteAdded = false;
|
||||
bool bMeshWaitsForBridgeTickPrerequisite = false;
|
||||
void EnsureExpectedAnimBinding(bool bForceReinit);
|
||||
/** Garante tick do bridge antes do skeletal mesh para o AnimBP ler CMC ja refrescado pelo snapshot. */
|
||||
void RegisterMeshTickAfterInputBridge();
|
||||
/** ABP_Unarmed exige aceleracao != 0 para ShouldMove; online isso falha. Sobrescreve apos o tick do mesh. */
|
||||
void ApplyShouldMoveToAnimInstanceAfterMesh();
|
||||
};
|
||||
344
Source/ZClientMMO/Game/Player/ZeusInputBridgeComponent.cpp
Normal file
344
Source/ZClientMMO/Game/Player/ZeusInputBridgeComponent.cpp
Normal file
@@ -0,0 +1,344 @@
|
||||
#include "ZeusInputBridgeComponent.h"
|
||||
|
||||
#include "PlayerCharacter.h"
|
||||
#include "ZeusProxyMovementComponent.h"
|
||||
#include "ZeusNetworkSubsystem.h"
|
||||
#include "GameFramework/Controller.h"
|
||||
#include "GameFramework/CharacterMovementComponent.h"
|
||||
|
||||
UZeusInputBridgeComponent::UZeusInputBridgeComponent()
|
||||
{
|
||||
PrimaryComponentTick.bCanEverTick = true;
|
||||
}
|
||||
|
||||
void UZeusInputBridgeComponent::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
if (UZeusNetworkSubsystem* Zeus = GetZeus())
|
||||
{
|
||||
Zeus->OnPlayerSpawned.AddDynamic(this, &UZeusInputBridgeComponent::HandlePlayerSpawned);
|
||||
Zeus->OnPlayerStateUpdate.AddDynamic(this, &UZeusInputBridgeComponent::HandlePlayerStateUpdate);
|
||||
Zeus->OnPlayerDespawned.AddDynamic(this, &UZeusInputBridgeComponent::HandlePlayerDespawned);
|
||||
|
||||
int32 CachedEntityId = 0;
|
||||
FVector CachedPos = FVector::ZeroVector;
|
||||
float CachedYaw = 0.0f;
|
||||
int64 CachedSpawnServerTimeMs = 0;
|
||||
if (Zeus->TryGetLastLocalSpawn(CachedEntityId, CachedPos, CachedYaw, CachedSpawnServerTimeMs))
|
||||
{
|
||||
HandlePlayerSpawned(CachedEntityId, true, CachedPos, CachedYaw, CachedSpawnServerTimeMs);
|
||||
|
||||
int32 CachedInputSeq = 0;
|
||||
FVector CachedVel = FVector::ZeroVector;
|
||||
bool bCachedGrounded = false;
|
||||
int64 CachedStateServerTimeMs = 0;
|
||||
if (Zeus->TryGetLastPlayerState(
|
||||
CachedEntityId,
|
||||
CachedInputSeq,
|
||||
CachedPos,
|
||||
CachedVel,
|
||||
bCachedGrounded,
|
||||
CachedStateServerTimeMs))
|
||||
{
|
||||
HandlePlayerStateUpdate(
|
||||
CachedEntityId,
|
||||
CachedInputSeq,
|
||||
CachedPos,
|
||||
CachedVel,
|
||||
bCachedGrounded,
|
||||
CachedStateServerTimeMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UZeusInputBridgeComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
if (UZeusNetworkSubsystem* Zeus = GetZeus())
|
||||
{
|
||||
Zeus->OnPlayerSpawned.RemoveDynamic(this, &UZeusInputBridgeComponent::HandlePlayerSpawned);
|
||||
Zeus->OnPlayerStateUpdate.RemoveDynamic(this, &UZeusInputBridgeComponent::HandlePlayerStateUpdate);
|
||||
Zeus->OnPlayerDespawned.RemoveDynamic(this, &UZeusInputBridgeComponent::HandlePlayerDespawned);
|
||||
}
|
||||
Super::EndPlay(EndPlayReason);
|
||||
}
|
||||
|
||||
UZeusNetworkSubsystem* UZeusInputBridgeComponent::GetZeus() const
|
||||
{
|
||||
return UZeusNetworkSubsystem::GetZeusNetworkSubsystem(GetWorld());
|
||||
}
|
||||
|
||||
APlayerCharacter* UZeusInputBridgeComponent::GetOwnerCharacter() const
|
||||
{
|
||||
return Cast<APlayerCharacter>(GetOwner());
|
||||
}
|
||||
|
||||
void UZeusInputBridgeComponent::RefreshAuthoritativeAnimationDrivers(APlayerCharacter* Owner) const
|
||||
{
|
||||
if (Owner == nullptr || !bHasSnapshotKinematics)
|
||||
{
|
||||
return;
|
||||
}
|
||||
UCharacterMovementComponent* CMC = Owner->GetCharacterMovement();
|
||||
if (CMC == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
CMC->Velocity = LastSnapshotVelocityCmS;
|
||||
if (CMC->MovementMode != LastSnapshotMovementMode)
|
||||
{
|
||||
CMC->SetMovementMode(LastSnapshotMovementMode);
|
||||
}
|
||||
if (UZeusProxyMovementComponent* ProxyMovement = Cast<UZeusProxyMovementComponent>(CMC))
|
||||
{
|
||||
ProxyMovement->SetZeusExternalAcceleration(LastDerivedAccelerationCmS2);
|
||||
}
|
||||
}
|
||||
|
||||
bool UZeusInputBridgeComponent::TryGetAuthoritativeKinematics(
|
||||
FVector& OutVelocityCmS,
|
||||
FVector& OutDerivedAccelerationCmS2,
|
||||
EMovementMode& OutMovementMode) const
|
||||
{
|
||||
if (!bHasSnapshotKinematics)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OutVelocityCmS = LastSnapshotVelocityCmS;
|
||||
OutDerivedAccelerationCmS2 = LastDerivedAccelerationCmS2;
|
||||
OutMovementMode = LastSnapshotMovementMode;
|
||||
return true;
|
||||
}
|
||||
|
||||
void UZeusInputBridgeComponent::SetMovementInput(const float Forward, const float Right)
|
||||
{
|
||||
CurrentForward = FMath::Clamp(Forward, -1.0f, 1.0f);
|
||||
CurrentRight = FMath::Clamp(Right, -1.0f, 1.0f);
|
||||
}
|
||||
|
||||
void UZeusInputBridgeComponent::RegisterJumpPressed()
|
||||
{
|
||||
bJumpPressedThisFrame = true;
|
||||
}
|
||||
|
||||
void UZeusInputBridgeComponent::RegisterJumpReleased()
|
||||
{
|
||||
bJumpReleasedThisFrame = true;
|
||||
}
|
||||
|
||||
void UZeusInputBridgeComponent::TickComponent(
|
||||
const float DeltaTime,
|
||||
const ELevelTick TickType,
|
||||
FActorComponentTickFunction* ThisTickFunction)
|
||||
{
|
||||
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||
|
||||
UZeusNetworkSubsystem* Zeus = GetZeus();
|
||||
if (Zeus == nullptr || !Zeus->IsConnected())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (APlayerCharacter* OwnerCharacter = GetOwnerCharacter())
|
||||
{
|
||||
// Proxy-style (V1): reescreve drivers por tick entre snapshots.
|
||||
RefreshAuthoritativeAnimationDrivers(OwnerCharacter);
|
||||
}
|
||||
|
||||
SendAccumulator += DeltaTime;
|
||||
if (SendAccumulator < SendIntervalSeconds && !bJumpPressedThisFrame && !bJumpReleasedThisFrame)
|
||||
{
|
||||
return;
|
||||
}
|
||||
SendAccumulator = 0.0f;
|
||||
|
||||
const APlayerCharacter* Owner = GetOwnerCharacter();
|
||||
float ForwardToSend = CurrentForward;
|
||||
float RightToSend = CurrentRight;
|
||||
|
||||
// Se temos controlador, projetar o input no plano horizontal usando o yaw do controlador,
|
||||
// para que o "forward" do servidor corresponda ao que o cliente ve em camera.
|
||||
if (Owner != nullptr)
|
||||
{
|
||||
if (const AController* Ctrl = Owner->GetController())
|
||||
{
|
||||
const FRotator Rot = Ctrl->GetControlRotation();
|
||||
const float YawRad = FMath::DegreesToRadians(Rot.Yaw);
|
||||
const float Cy = FMath::Cos(YawRad);
|
||||
const float Sy = FMath::Sin(YawRad);
|
||||
// X = forward, Y = right (Zeus convention). UE usa X=forward,Y=right tambem.
|
||||
const float OutF = ForwardToSend * Cy - RightToSend * Sy;
|
||||
const float OutR = ForwardToSend * Sy + RightToSend * Cy;
|
||||
ForwardToSend = OutF;
|
||||
RightToSend = OutR;
|
||||
const float LenSq = ForwardToSend * ForwardToSend + RightToSend * RightToSend;
|
||||
if (LenSq > 1.0f)
|
||||
{
|
||||
const float InvLen = FMath::InvSqrt(LenSq);
|
||||
ForwardToSend *= InvLen;
|
||||
RightToSend *= InvLen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int32 ClientTimeMs = static_cast<int32>(
|
||||
FMath::FloorToInt32(static_cast<float>(FPlatformTime::Seconds() * 1000.0)));
|
||||
|
||||
++InputSeq;
|
||||
Zeus->SendInputAxis(
|
||||
ForwardToSend,
|
||||
RightToSend,
|
||||
bJumpPressedThisFrame,
|
||||
bJumpReleasedThisFrame,
|
||||
InputSeq,
|
||||
ClientTimeMs);
|
||||
|
||||
bJumpPressedThisFrame = false;
|
||||
bJumpReleasedThisFrame = false;
|
||||
}
|
||||
|
||||
void UZeusInputBridgeComponent::HandlePlayerSpawned(
|
||||
const int32 EntityId,
|
||||
const bool bIsLocal,
|
||||
const FVector PosCm,
|
||||
const float YawDeg,
|
||||
const int64 ServerTimeMs)
|
||||
{
|
||||
(void)ServerTimeMs;
|
||||
if (!bIsLocal)
|
||||
{
|
||||
return;
|
||||
}
|
||||
APlayerCharacter* Owner = GetOwnerCharacter();
|
||||
if (Owner == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
LocalEntityId = EntityId;
|
||||
bSpawnedLocal = true;
|
||||
LastAppliedServerInputSeq = -1;
|
||||
bHasLastServerPos = false;
|
||||
LastServerPosCm = PosCm;
|
||||
bHasSnapshotKinematics = false;
|
||||
LastSnapshotVelocityCmS = FVector::ZeroVector;
|
||||
LastDerivedAccelerationCmS2 = FVector::ZeroVector;
|
||||
LastSnapshotMovementMode = MOVE_Walking;
|
||||
LastSnapshotServerTimeMs = ServerTimeMs;
|
||||
Owner->SetActorLocation(PosCm, /*bSweep*/ false);
|
||||
Owner->SetActorRotation(FRotator(0.0f, YawDeg, 0.0f));
|
||||
CurrentForward = 0.0f;
|
||||
CurrentRight = 0.0f;
|
||||
bJumpPressedThisFrame = false;
|
||||
bJumpReleasedThisFrame = false;
|
||||
}
|
||||
|
||||
void UZeusInputBridgeComponent::HandlePlayerStateUpdate(
|
||||
const int32 EntityId,
|
||||
const int32 InInputSeq,
|
||||
const FVector PosCm,
|
||||
const FVector VelCmS,
|
||||
const bool bGrounded,
|
||||
const int64 ServerTimeMs)
|
||||
{
|
||||
if (!bSpawnedLocal || EntityId != LocalEntityId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
APlayerCharacter* Owner = GetOwnerCharacter();
|
||||
if (Owner == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (InInputSeq > 0 && LastAppliedServerInputSeq >= InInputSeq)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (InInputSeq > 0)
|
||||
{
|
||||
LastAppliedServerInputSeq = InInputSeq;
|
||||
}
|
||||
const EMovementMode DesiredMode = bGrounded ? MOVE_Walking : MOVE_Falling;
|
||||
const FVector PrevVelocity = LastSnapshotVelocityCmS;
|
||||
const int64 PrevServerTimeMs = LastSnapshotServerTimeMs;
|
||||
const double DeltaSnapshotSec = (bHasSnapshotKinematics && PrevServerTimeMs > 0)
|
||||
? static_cast<double>(ServerTimeMs - PrevServerTimeMs) / 1000.0
|
||||
: 0.0;
|
||||
FVector DerivedAccel = FVector::ZeroVector;
|
||||
if (DeltaSnapshotSec > KINDA_SMALL_NUMBER)
|
||||
{
|
||||
DerivedAccel = (VelCmS - PrevVelocity) / static_cast<float>(DeltaSnapshotSec);
|
||||
DerivedAccel = DerivedAccel.GetClampedToMaxSize(8000.0f);
|
||||
}
|
||||
const float AccelSmoothingAlpha = 0.35f;
|
||||
LastDerivedAccelerationCmS2 = bHasSnapshotKinematics
|
||||
? FMath::Lerp(LastDerivedAccelerationCmS2, DerivedAccel, AccelSmoothingAlpha)
|
||||
: DerivedAccel;
|
||||
const FVector2D Vel2D(VelCmS.X, VelCmS.Y);
|
||||
const float Speed2D = Vel2D.Size();
|
||||
const FVector2D Accel2D(LastDerivedAccelerationCmS2.X, LastDerivedAccelerationCmS2.Y);
|
||||
const float AccelMag2D = Accel2D.Size();
|
||||
const bool bApplyVisualAccelFallback =
|
||||
DesiredMode == MOVE_Walking &&
|
||||
Speed2D >= AnimationMinSpeedForAccelFallbackCmS &&
|
||||
AccelMag2D < AnimationMinAccelFallbackCmS2;
|
||||
if (bApplyVisualAccelFallback)
|
||||
{
|
||||
const FVector2D VelDir = Vel2D.GetSafeNormal();
|
||||
const FVector2D FallbackAccel2D = VelDir * AnimationMinAccelFallbackCmS2;
|
||||
LastDerivedAccelerationCmS2.X = FallbackAccel2D.X;
|
||||
LastDerivedAccelerationCmS2.Y = FallbackAccel2D.Y;
|
||||
LastDerivedAccelerationCmS2.Z = 0.0f;
|
||||
}
|
||||
LastSnapshotVelocityCmS = VelCmS;
|
||||
LastSnapshotMovementMode = DesiredMode;
|
||||
LastSnapshotServerTimeMs = ServerTimeMs;
|
||||
bHasSnapshotKinematics = true;
|
||||
bHasLastServerPos = true;
|
||||
LastServerPosCm = PosCm;
|
||||
RefreshAuthoritativeAnimationDrivers(Owner);
|
||||
const FVector Cur = Owner->GetActorLocation();
|
||||
const FVector Delta = PosCm - Cur;
|
||||
const FVector2D DeltaXY(Delta.X, Delta.Y);
|
||||
const float DeltaZAbs = FMath::Abs(Delta.Z);
|
||||
if (DeltaXY.Size() < SnapshotMinHorizontalCorrectionCm && DeltaZAbs < SnapshotMinVerticalCorrectionCm)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const float DistSq = Delta.SizeSquared();
|
||||
const bool bUseTeleport = DistSq > TeleportThresholdCm * TeleportThresholdCm;
|
||||
if (bUseTeleport)
|
||||
{
|
||||
Owner->SetActorLocation(PosCm, /*bSweep*/ false);
|
||||
}
|
||||
else
|
||||
{
|
||||
const FVector Lerped = FMath::Lerp(Cur, PosCm, FMath::Clamp(SnapshotLerpAlpha, 0.0f, 1.0f));
|
||||
Owner->SetActorLocation(Lerped, /*bSweep*/ false);
|
||||
}
|
||||
}
|
||||
|
||||
void UZeusInputBridgeComponent::HandlePlayerDespawned(const int32 EntityId)
|
||||
{
|
||||
if (EntityId == LocalEntityId)
|
||||
{
|
||||
if (APlayerCharacter* Owner = GetOwnerCharacter())
|
||||
{
|
||||
if (UCharacterMovementComponent* CMC = Owner->GetCharacterMovement())
|
||||
{
|
||||
CMC->Velocity = FVector::ZeroVector;
|
||||
CMC->SetMovementMode(MOVE_Walking);
|
||||
}
|
||||
}
|
||||
bSpawnedLocal = false;
|
||||
LocalEntityId = 0;
|
||||
LastAppliedServerInputSeq = -1;
|
||||
bHasLastServerPos = false;
|
||||
LastServerPosCm = FVector::ZeroVector;
|
||||
bHasSnapshotKinematics = false;
|
||||
LastSnapshotVelocityCmS = FVector::ZeroVector;
|
||||
LastDerivedAccelerationCmS2 = FVector::ZeroVector;
|
||||
LastSnapshotMovementMode = MOVE_Walking;
|
||||
LastSnapshotServerTimeMs = 0;
|
||||
}
|
||||
}
|
||||
106
Source/ZClientMMO/Game/Player/ZeusInputBridgeComponent.h
Normal file
106
Source/ZClientMMO/Game/Player/ZeusInputBridgeComponent.h
Normal file
@@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "GameFramework/CharacterMovementComponent.h"
|
||||
#include "ZeusInputBridgeComponent.generated.h"
|
||||
|
||||
class UZeusNetworkSubsystem;
|
||||
class APlayerCharacter;
|
||||
|
||||
/**
|
||||
* UZeusInputBridgeComponent — liga `APlayerCharacter` ao `UZeusNetworkSubsystem`.
|
||||
*
|
||||
* Responsabilidades:
|
||||
* - Tick: ler axis correntes (via `SetMovementInput`) e flags de jump;
|
||||
* chamar `Zeus->SendInputAxis(...)` no intervalo configurado.
|
||||
* - Subscrever `OnPlayerSpawned` (apenas se `bIsLocal=true`): aplica
|
||||
* `SetActorLocation(PosCm)` ao owner e zera input acumulado.
|
||||
* - Subscrever `OnPlayerStateUpdate`: aplica posicao do snapshot. Nesta fase
|
||||
* sem predicao — `SetActorLocation(NewPos)` directo (sweep=false). Se a
|
||||
* distancia for grande (>50cm) faz teleport, senao lerp suave.
|
||||
* - Subscrever `OnPlayerDespawned`: limpa estado local de snapshot/input.
|
||||
*/
|
||||
UCLASS(ClassGroup = (Zeus), meta = (BlueprintSpawnableComponent))
|
||||
class ZCLIENTMMO_API UZeusInputBridgeComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UZeusInputBridgeComponent();
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|Input")
|
||||
float SendIntervalSeconds = 1.0f / 30.0f;
|
||||
|
||||
/** Distancia (cm) acima da qual fazemos teleport em vez de lerp. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|Input")
|
||||
float TeleportThresholdCm = 120.0f;
|
||||
|
||||
/** Factor de lerp [0..1] aplicado por snapshot (default 0.4). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|Input")
|
||||
float SnapshotLerpAlpha = 0.6f;
|
||||
|
||||
/** Ignora micro-correcao horizontal para reduzir jitter visual. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|Input")
|
||||
float SnapshotMinHorizontalCorrectionCm = 3.0f;
|
||||
|
||||
/** Ignora micro-correcao vertical para reduzir jitter visual. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|Input")
|
||||
float SnapshotMinVerticalCorrectionCm = 6.0f;
|
||||
|
||||
/** Velocidade horizontal minima para ativar fallback de aceleracao visual. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|Animation")
|
||||
float AnimationMinSpeedForAccelFallbackCmS = 120.0f;
|
||||
|
||||
/** Aceleracao horizontal minima usada para manter locomotion blend estavel. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|Animation")
|
||||
float AnimationMinAccelFallbackCmS2 = 300.0f;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Zeus|Input")
|
||||
void SetMovementInput(float Forward, float Right);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Zeus|Input")
|
||||
void RegisterJumpPressed();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Zeus|Input")
|
||||
void RegisterJumpReleased();
|
||||
|
||||
bool TryGetAuthoritativeKinematics(FVector& OutVelocityCmS, FVector& OutDerivedAccelerationCmS2, EMovementMode& OutMovementMode) const;
|
||||
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
virtual void EndPlay(EEndPlayReason::Type EndPlayReason) override;
|
||||
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
|
||||
|
||||
private:
|
||||
void RefreshAuthoritativeAnimationDrivers(APlayerCharacter* Owner) const;
|
||||
|
||||
UFUNCTION()
|
||||
void HandlePlayerSpawned(int32 EntityId, bool bIsLocal, FVector PosCm, float YawDeg, int64 ServerTimeMs);
|
||||
|
||||
UFUNCTION()
|
||||
void HandlePlayerStateUpdate(int32 EntityId, int32 InputSeq, FVector PosCm, FVector VelCmS, bool bGrounded, int64 ServerTimeMs);
|
||||
|
||||
UFUNCTION()
|
||||
void HandlePlayerDespawned(int32 EntityId);
|
||||
|
||||
UZeusNetworkSubsystem* GetZeus() const;
|
||||
APlayerCharacter* GetOwnerCharacter() const;
|
||||
|
||||
float CurrentForward = 0.0f;
|
||||
float CurrentRight = 0.0f;
|
||||
bool bJumpPressedThisFrame = false;
|
||||
bool bJumpReleasedThisFrame = false;
|
||||
float SendAccumulator = 0.0f;
|
||||
int32 InputSeq = 0;
|
||||
int32 LocalEntityId = 0;
|
||||
bool bSpawnedLocal = false;
|
||||
int32 LastAppliedServerInputSeq = -1;
|
||||
bool bHasLastServerPos = false;
|
||||
FVector LastServerPosCm = FVector::ZeroVector;
|
||||
bool bHasSnapshotKinematics = false;
|
||||
FVector LastSnapshotVelocityCmS = FVector::ZeroVector;
|
||||
FVector LastDerivedAccelerationCmS2 = FVector::ZeroVector;
|
||||
EMovementMode LastSnapshotMovementMode = MOVE_Walking;
|
||||
int64 LastSnapshotServerTimeMs = 0;
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
#include "ZeusProxyMovementComponent.h"
|
||||
25
Source/ZClientMMO/Game/Player/ZeusProxyMovementComponent.h
Normal file
25
Source/ZClientMMO/Game/Player/ZeusProxyMovementComponent.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/CharacterMovementComponent.h"
|
||||
|
||||
#include "ZeusProxyMovementComponent.generated.h"
|
||||
|
||||
/**
|
||||
* Componente de movimento para proxies Zeus.
|
||||
*
|
||||
* Expõe escrita de aceleração externa para manter o AnimBP em locomotion
|
||||
* quando o movimento vem de snapshot autoritativo (sem NetDriver da UE).
|
||||
*/
|
||||
UCLASS(ClassGroup = (Zeus), meta = (BlueprintSpawnableComponent))
|
||||
class ZCLIENTMMO_API UZeusProxyMovementComponent : public UCharacterMovementComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Define aceleração externa consumida pelo CMC/AnimBP. */
|
||||
void SetZeusExternalAcceleration(const FVector& InAccelerationCmS2)
|
||||
{
|
||||
Acceleration = InAccelerationCmS2;
|
||||
}
|
||||
};
|
||||
54
Source/ZClientMMO/ZClientMMO.Build.cs
Normal file
54
Source/ZClientMMO/ZClientMMO.Build.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class ZClientMMO : ModuleRules
|
||||
{
|
||||
public ZClientMMO(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(new string[] {
|
||||
"Core",
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"InputCore",
|
||||
"EnhancedInput",
|
||||
"AIModule",
|
||||
"StateTreeModule",
|
||||
"GameplayStateTreeModule",
|
||||
"UMG",
|
||||
"Slate"
|
||||
});
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(new string[] {
|
||||
"ZeusNetwork"
|
||||
});
|
||||
|
||||
PublicIncludePaths.AddRange(new string[] {
|
||||
"ZClientMMO",
|
||||
"ZClientMMO/Game/Player",
|
||||
"ZClientMMO/Variant_Platforming",
|
||||
"ZClientMMO/Variant_Platforming/Animation",
|
||||
"ZClientMMO/Variant_Combat",
|
||||
"ZClientMMO/Variant_Combat/AI",
|
||||
"ZClientMMO/Variant_Combat/Animation",
|
||||
"ZClientMMO/Variant_Combat/Gameplay",
|
||||
"ZClientMMO/Variant_Combat/Interfaces",
|
||||
"ZClientMMO/Variant_Combat/UI",
|
||||
"ZClientMMO/Variant_SideScrolling",
|
||||
"ZClientMMO/Variant_SideScrolling/AI",
|
||||
"ZClientMMO/Variant_SideScrolling/Gameplay",
|
||||
"ZClientMMO/Variant_SideScrolling/Interfaces",
|
||||
"ZClientMMO/Variant_SideScrolling/UI"
|
||||
});
|
||||
|
||||
// Uncomment if you are using Slate UI
|
||||
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
|
||||
|
||||
// Uncomment if you are using online features
|
||||
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
|
||||
|
||||
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
|
||||
}
|
||||
}
|
||||
8
Source/ZClientMMO/ZClientMMO.cpp
Normal file
8
Source/ZClientMMO/ZClientMMO.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "ZClientMMO.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, ZClientMMO, "ZClientMMO" );
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogZClientMMO)
|
||||
8
Source/ZClientMMO/ZClientMMO.h
Normal file
8
Source/ZClientMMO/ZClientMMO.h
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
/** Main log category used across the project */
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogZClientMMO, Log, All);
|
||||
197
Source/ZClientMMO/ZClientMMOCharacter.cpp
Normal file
197
Source/ZClientMMO/ZClientMMOCharacter.cpp
Normal file
@@ -0,0 +1,197 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "ZClientMMOCharacter.h"
|
||||
|
||||
#include "Animation/AnimInstance.h"
|
||||
#include "Camera/CameraComponent.h"
|
||||
#include "Components/CapsuleComponent.h"
|
||||
#include "EnhancedInputComponent.h"
|
||||
#include "Engine/SkeletalMesh.h"
|
||||
#include "GameFramework/CharacterMovementComponent.h"
|
||||
#include "GameFramework/Controller.h"
|
||||
#include "GameFramework/SpringArmComponent.h"
|
||||
#include "InputAction.h"
|
||||
#include "InputActionValue.h"
|
||||
#include "UObject/ConstructorHelpers.h"
|
||||
#include "ZClientMMO.h"
|
||||
|
||||
AZClientMMOCharacter::AZClientMMOCharacter(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
GetCapsuleComponent()->InitCapsuleSize(42.0f, 96.0f);
|
||||
|
||||
bUseControllerRotationPitch = false;
|
||||
bUseControllerRotationYaw = false;
|
||||
bUseControllerRotationRoll = false;
|
||||
|
||||
UCharacterMovementComponent* MovementComponent = GetCharacterMovement();
|
||||
MovementComponent->bOrientRotationToMovement = true;
|
||||
MovementComponent->RotationRate = FRotator(0.0f, 500.0f, 0.0f);
|
||||
MovementComponent->JumpZVelocity = 500.0f;
|
||||
MovementComponent->AirControl = 0.35f;
|
||||
MovementComponent->MaxWalkSpeed = 500.0f;
|
||||
MovementComponent->MinAnalogWalkSpeed = 20.0f;
|
||||
MovementComponent->BrakingDecelerationWalking = 2000.0f;
|
||||
MovementComponent->BrakingDecelerationFalling = 1500.0f;
|
||||
|
||||
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
|
||||
CameraBoom->SetupAttachment(RootComponent);
|
||||
CameraBoom->TargetArmLength = 400.0f;
|
||||
CameraBoom->bUsePawnControlRotation = true;
|
||||
|
||||
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
|
||||
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
|
||||
FollowCamera->bUsePawnControlRotation = false;
|
||||
|
||||
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(LogZClientMMO, Warning, TEXT("Default mesh SKM_Quinn_Simple not found for AZClientMMOCharacter."));
|
||||
}
|
||||
|
||||
if (QuinnAnimBp.Succeeded())
|
||||
{
|
||||
MeshComponent->SetAnimationMode(EAnimationMode::AnimationBlueprint);
|
||||
MeshComponent->SetAnimInstanceClass(QuinnAnimBp.Class);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogZClientMMO, Warning, TEXT("Default anim blueprint ABP_Unarmed not found for AZClientMMOCharacter."));
|
||||
}
|
||||
}
|
||||
|
||||
static ConstructorHelpers::FObjectFinder<UInputAction> MoveActionAsset(
|
||||
TEXT("/Game/Input/Actions/IA_Move.IA_Move"));
|
||||
static ConstructorHelpers::FObjectFinder<UInputAction> LookActionAsset(
|
||||
TEXT("/Game/Input/Actions/IA_Look.IA_Look"));
|
||||
static ConstructorHelpers::FObjectFinder<UInputAction> MouseLookActionAsset(
|
||||
TEXT("/Game/Input/Actions/IA_MouseLook.IA_MouseLook"));
|
||||
static ConstructorHelpers::FObjectFinder<UInputAction> JumpActionAsset(
|
||||
TEXT("/Game/Input/Actions/IA_Jump.IA_Jump"));
|
||||
|
||||
if (MoveActionAsset.Succeeded())
|
||||
{
|
||||
MoveAction = MoveActionAsset.Object;
|
||||
}
|
||||
if (LookActionAsset.Succeeded())
|
||||
{
|
||||
LookAction = LookActionAsset.Object;
|
||||
}
|
||||
if (MouseLookActionAsset.Succeeded())
|
||||
{
|
||||
MouseLookAction = MouseLookActionAsset.Object;
|
||||
}
|
||||
if (JumpActionAsset.Succeeded())
|
||||
{
|
||||
JumpAction = JumpActionAsset.Object;
|
||||
}
|
||||
}
|
||||
|
||||
void AZClientMMOCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
|
||||
{
|
||||
if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent))
|
||||
{
|
||||
if (JumpAction != nullptr)
|
||||
{
|
||||
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &ACharacter::Jump);
|
||||
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);
|
||||
}
|
||||
if (MoveAction != nullptr)
|
||||
{
|
||||
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AZClientMMOCharacter::Move);
|
||||
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Completed, this, &AZClientMMOCharacter::MoveCompleted);
|
||||
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Canceled, this, &AZClientMMOCharacter::MoveCompleted);
|
||||
}
|
||||
if (MouseLookAction != nullptr)
|
||||
{
|
||||
EnhancedInputComponent->BindAction(MouseLookAction, ETriggerEvent::Triggered, this, &AZClientMMOCharacter::Look);
|
||||
}
|
||||
if (LookAction != nullptr)
|
||||
{
|
||||
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AZClientMMOCharacter::Look);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogZClientMMO, Error, TEXT("'%s' failed to find an Enhanced Input component."), *GetNameSafe(this));
|
||||
}
|
||||
}
|
||||
|
||||
void AZClientMMOCharacter::Move(const FInputActionValue& Value)
|
||||
{
|
||||
// input is a Vector2D
|
||||
FVector2D MovementVector = Value.Get<FVector2D>();
|
||||
|
||||
// route the input
|
||||
DoMove(MovementVector.X, MovementVector.Y);
|
||||
}
|
||||
|
||||
void AZClientMMOCharacter::MoveCompleted(const FInputActionValue& Value)
|
||||
{
|
||||
(void)Value;
|
||||
// Garante release explícito para evitar "input preso" no pipeline autoritativo.
|
||||
DoMove(0.0f, 0.0f);
|
||||
}
|
||||
|
||||
void AZClientMMOCharacter::Look(const FInputActionValue& Value)
|
||||
{
|
||||
// input is a Vector2D
|
||||
FVector2D LookAxisVector = Value.Get<FVector2D>();
|
||||
|
||||
// route the input
|
||||
DoLook(LookAxisVector.X, LookAxisVector.Y);
|
||||
}
|
||||
|
||||
void AZClientMMOCharacter::DoMove(float Right, float Forward)
|
||||
{
|
||||
if (GetController() != nullptr)
|
||||
{
|
||||
// find out which way is forward
|
||||
const FRotator Rotation = GetController()->GetControlRotation();
|
||||
const FRotator YawRotation(0, Rotation.Yaw, 0);
|
||||
|
||||
// get forward vector
|
||||
const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
|
||||
|
||||
// get right vector
|
||||
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
|
||||
|
||||
// add movement
|
||||
AddMovementInput(ForwardDirection, Forward);
|
||||
AddMovementInput(RightDirection, Right);
|
||||
}
|
||||
}
|
||||
|
||||
void AZClientMMOCharacter::DoLook(float Yaw, float Pitch)
|
||||
{
|
||||
if (GetController() != nullptr)
|
||||
{
|
||||
// add yaw and pitch input to controller
|
||||
AddControllerYawInput(Yaw);
|
||||
AddControllerPitchInput(Pitch);
|
||||
}
|
||||
}
|
||||
|
||||
void AZClientMMOCharacter::DoJumpStart()
|
||||
{
|
||||
// signal the character to jump
|
||||
Jump();
|
||||
}
|
||||
|
||||
void AZClientMMOCharacter::DoJumpEnd()
|
||||
{
|
||||
// signal the character to stop jumping
|
||||
StopJumping();
|
||||
}
|
||||
94
Source/ZClientMMO/ZClientMMOCharacter.h
Normal file
94
Source/ZClientMMO/ZClientMMOCharacter.h
Normal file
@@ -0,0 +1,94 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "Logging/LogMacros.h"
|
||||
#include "ZClientMMOCharacter.generated.h"
|
||||
|
||||
class USpringArmComponent;
|
||||
class UCameraComponent;
|
||||
class UInputAction;
|
||||
struct FInputActionValue;
|
||||
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogTemplateCharacter, Log, All);
|
||||
|
||||
/** Character base do template third-person usado pelo cliente. */
|
||||
UCLASS(Abstract)
|
||||
class AZClientMMOCharacter : public ACharacter
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** Camera boom positioning the camera behind the character */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
|
||||
USpringArmComponent* CameraBoom;
|
||||
|
||||
/** Follow camera */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
|
||||
UCameraComponent* FollowCamera;
|
||||
|
||||
protected:
|
||||
|
||||
/** Jump Input Action */
|
||||
UPROPERTY(EditAnywhere, Category="Input")
|
||||
UInputAction* JumpAction;
|
||||
|
||||
/** Move Input Action */
|
||||
UPROPERTY(EditAnywhere, Category="Input")
|
||||
UInputAction* MoveAction;
|
||||
|
||||
/** Look Input Action */
|
||||
UPROPERTY(EditAnywhere, Category="Input")
|
||||
UInputAction* LookAction;
|
||||
|
||||
/** Mouse Look Input Action */
|
||||
UPROPERTY(EditAnywhere, Category="Input")
|
||||
UInputAction* MouseLookAction;
|
||||
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
AZClientMMOCharacter(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
|
||||
|
||||
protected:
|
||||
|
||||
/** Initialize input action bindings */
|
||||
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
|
||||
|
||||
protected:
|
||||
|
||||
/** Called for movement input */
|
||||
void Move(const FInputActionValue& Value);
|
||||
void MoveCompleted(const FInputActionValue& Value);
|
||||
|
||||
/** Called for looking input */
|
||||
void Look(const FInputActionValue& Value);
|
||||
|
||||
public:
|
||||
|
||||
/** Handles move inputs from either controls or UI interfaces */
|
||||
UFUNCTION(BlueprintCallable, Category="Input")
|
||||
virtual void DoMove(float Right, float Forward);
|
||||
|
||||
/** Handles look inputs from either controls or UI interfaces */
|
||||
UFUNCTION(BlueprintCallable, Category="Input")
|
||||
virtual void DoLook(float Yaw, float Pitch);
|
||||
|
||||
/** Handles jump pressed inputs from either controls or UI interfaces */
|
||||
UFUNCTION(BlueprintCallable, Category="Input")
|
||||
virtual void DoJumpStart();
|
||||
|
||||
/** Handles jump pressed inputs from either controls or UI interfaces */
|
||||
UFUNCTION(BlueprintCallable, Category="Input")
|
||||
virtual void DoJumpEnd();
|
||||
|
||||
public:
|
||||
|
||||
/** Returns CameraBoom subobject **/
|
||||
FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
|
||||
|
||||
/** Returns FollowCamera subobject **/
|
||||
FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }
|
||||
};
|
||||
|
||||
169
Source/ZClientMMO/ZClientMMOGameInstance.cpp
Normal file
169
Source/ZClientMMO/ZClientMMOGameInstance.cpp
Normal file
@@ -0,0 +1,169 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "ZClientMMOGameInstance.h"
|
||||
#include "ZeusNetworkSubsystem.h"
|
||||
#include "ZClientMMO.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
|
||||
UZClientMMOGameInstance::UZClientMMOGameInstance()
|
||||
{
|
||||
ZeusServerHost = TEXT("127.0.0.1");
|
||||
}
|
||||
|
||||
void UZClientMMOGameInstance::Init()
|
||||
{
|
||||
Super::Init();
|
||||
|
||||
if (!bZeusAutoConnectOnInit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UZeusNetworkSubsystem* Zeus = GetSubsystem<UZeusNetworkSubsystem>();
|
||||
if (!Zeus)
|
||||
{
|
||||
UE_LOG(LogZClientMMO, Error, TEXT("ZeusNetworkSubsystem não encontrado na Game Instance. O plugin ZeusNetwork está activo?"));
|
||||
return;
|
||||
}
|
||||
|
||||
BindZeusDelegates(Zeus);
|
||||
FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject(this, &UZClientMMOGameInstance::HandlePostLoadMap);
|
||||
|
||||
if (bZeusUseDeveloperSettingsEndpoint)
|
||||
{
|
||||
Zeus->ConnectToDefaultZeusServer();
|
||||
}
|
||||
else
|
||||
{
|
||||
Zeus->ConnectToZeusServer(ZeusServerHost, ZeusServerPort);
|
||||
}
|
||||
}
|
||||
|
||||
void UZClientMMOGameInstance::Shutdown()
|
||||
{
|
||||
if (UZeusNetworkSubsystem* Zeus = GetSubsystem<UZeusNetworkSubsystem>())
|
||||
{
|
||||
UnbindZeusDelegates(Zeus);
|
||||
Zeus->DisconnectFromZeusServer();
|
||||
}
|
||||
FCoreUObjectDelegates::PostLoadMapWithWorld.RemoveAll(this);
|
||||
|
||||
Super::Shutdown();
|
||||
}
|
||||
|
||||
void UZClientMMOGameInstance::BindZeusDelegates(UZeusNetworkSubsystem* Zeus)
|
||||
{
|
||||
if (!Zeus)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Zeus->OnConnected.AddDynamic(this, &UZClientMMOGameInstance::HandleZeusConnected);
|
||||
Zeus->OnConnectionFailed.AddDynamic(this, &UZClientMMOGameInstance::HandleZeusConnectionFailed);
|
||||
Zeus->OnDisconnected.AddDynamic(this, &UZClientMMOGameInstance::HandleZeusDisconnected);
|
||||
Zeus->OnPingUpdated.AddDynamic(this, &UZClientMMOGameInstance::HandleZeusPingUpdated);
|
||||
Zeus->OnNetworkLog.AddDynamic(this, &UZClientMMOGameInstance::HandleZeusNetworkLog);
|
||||
Zeus->OnServerTravelRequested.AddDynamic(this, &UZClientMMOGameInstance::HandleZeusServerTravelRequested);
|
||||
}
|
||||
|
||||
void UZClientMMOGameInstance::UnbindZeusDelegates(UZeusNetworkSubsystem* Zeus)
|
||||
{
|
||||
if (!Zeus)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Zeus->OnConnected.RemoveDynamic(this, &UZClientMMOGameInstance::HandleZeusConnected);
|
||||
Zeus->OnConnectionFailed.RemoveDynamic(this, &UZClientMMOGameInstance::HandleZeusConnectionFailed);
|
||||
Zeus->OnDisconnected.RemoveDynamic(this, &UZClientMMOGameInstance::HandleZeusDisconnected);
|
||||
Zeus->OnPingUpdated.RemoveDynamic(this, &UZClientMMOGameInstance::HandleZeusPingUpdated);
|
||||
Zeus->OnNetworkLog.RemoveDynamic(this, &UZClientMMOGameInstance::HandleZeusNetworkLog);
|
||||
Zeus->OnServerTravelRequested.RemoveDynamic(this, &UZClientMMOGameInstance::HandleZeusServerTravelRequested);
|
||||
}
|
||||
|
||||
void UZClientMMOGameInstance::HandleZeusConnected()
|
||||
{
|
||||
UE_LOG(LogZClientMMO, Log, TEXT("[Zeus] Ligado ao servidor."));
|
||||
}
|
||||
|
||||
void UZClientMMOGameInstance::HandleZeusConnectionFailed(FString Reason)
|
||||
{
|
||||
UE_LOG(LogZClientMMO, Warning, TEXT("[Zeus] Falha de ligação: %s"), *Reason);
|
||||
}
|
||||
|
||||
void UZClientMMOGameInstance::HandleZeusDisconnected()
|
||||
{
|
||||
bTravelInFlight = false;
|
||||
LastTravelTarget.Empty();
|
||||
UE_LOG(LogZClientMMO, Log, TEXT("[Zeus] Desligado."));
|
||||
}
|
||||
|
||||
void UZClientMMOGameInstance::HandleZeusPingUpdated(float RttMs)
|
||||
{
|
||||
UE_LOG(LogZClientMMO, Verbose, TEXT("[Zeus] RTT %.2f ms"), RttMs);
|
||||
}
|
||||
|
||||
void UZClientMMOGameInstance::HandleZeusNetworkLog(FString Message)
|
||||
{
|
||||
UE_LOG(LogZClientMMO, Log, TEXT("[Zeus] %s"), *Message);
|
||||
}
|
||||
|
||||
void UZClientMMOGameInstance::HandleZeusServerTravelRequested(const FString& MapName, const FString& MapPath)
|
||||
{
|
||||
const FString Target = MapPath.IsEmpty() ? MapName : MapPath;
|
||||
if (Target.IsEmpty())
|
||||
{
|
||||
UE_LOG(LogZClientMMO, Warning, TEXT("[Zeus] S_TRAVEL_TO_MAP recebido sem destino (mapName e mapPath vazios). A ignorar."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (bTravelInFlight && LastTravelTarget.Equals(Target, ESearchCase::IgnoreCase))
|
||||
{
|
||||
UE_LOG(LogZClientMMO, Verbose, TEXT("[Zeus] Travel repetido para %s ignorado (em progresso)."), *Target);
|
||||
return;
|
||||
}
|
||||
|
||||
UWorld* CurrentWorld = GetWorld();
|
||||
if (CurrentWorld != nullptr)
|
||||
{
|
||||
FString CurrentName = CurrentWorld->GetMapName();
|
||||
CurrentName.RemoveFromStart(TEXT("UEDPIE_0_"));
|
||||
if (!CurrentName.IsEmpty() && (Target.EndsWith(CurrentName) || Target.EndsWith(TEXT("/") + CurrentName)))
|
||||
{
|
||||
bTravelInFlight = false;
|
||||
LastTravelTarget = Target;
|
||||
UE_LOG(LogZClientMMO, Verbose, TEXT("[Zeus] Ja estamos no mapa %s; OpenLevel ignorado."), *CurrentName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bTravelInFlight = true;
|
||||
LastTravelTarget = Target;
|
||||
UE_LOG(LogZClientMMO, Log, TEXT("[Zeus] OpenLevel solicitado pelo servidor: name=%s path=%s"), *MapName, *MapPath);
|
||||
UGameplayStatics::OpenLevel(this, FName(*Target));
|
||||
}
|
||||
|
||||
void UZClientMMOGameInstance::HandlePostLoadMap(UWorld* LoadedWorld)
|
||||
{
|
||||
if (LoadedWorld == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FString LoadedMapName = LoadedWorld->GetMapName();
|
||||
LoadedMapName.RemoveFromStart(TEXT("UEDPIE_0_"));
|
||||
|
||||
if (bTravelInFlight)
|
||||
{
|
||||
if (!LastTravelTarget.IsEmpty() &&
|
||||
(LastTravelTarget.EndsWith(LoadedMapName) || LastTravelTarget.EndsWith(TEXT("/") + LoadedMapName)))
|
||||
{
|
||||
bTravelInFlight = false;
|
||||
UE_LOG(LogZClientMMO, Log, TEXT("[Zeus] Travel concluido para mapa %s."), *LoadedMapName);
|
||||
return;
|
||||
}
|
||||
|
||||
UE_LOG(LogZClientMMO, Verbose, TEXT("[Zeus] PostLoadMap=%s, aguardando target=%s"), *LoadedMapName, *LastTravelTarget);
|
||||
}
|
||||
}
|
||||
65
Source/ZClientMMO/ZClientMMOGameInstance.h
Normal file
65
Source/ZClientMMO/ZClientMMOGameInstance.h
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "ZClientMMOGameInstance.generated.h"
|
||||
|
||||
/** GameInstance do cliente com bootstrap de conexao Zeus e travel solicitado pelo servidor. */
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class UZClientMMOGameInstance : public UGameInstance
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UZClientMMOGameInstance();
|
||||
|
||||
/** Chamado pelo motor após criar subsistemas da Game Instance (via Super::Init). A lógica Zeus corre aqui, não em ReceiveInit. */
|
||||
virtual void Init() override;
|
||||
|
||||
/** Remove delegados e pede desligamento limpo ao subsistema Zeus. */
|
||||
virtual void Shutdown() override;
|
||||
|
||||
protected:
|
||||
/** Se verdadeiro, chama ConnectToDefaultZeusServer() (Developer Settings ZeusNetwork) em vez de Host/Port abaixo. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Zeus|Connection")
|
||||
bool bZeusUseDeveloperSettingsEndpoint = false;
|
||||
|
||||
/** Host UDP do servidor Zeus (ignorado se bZeusUseDeveloperSettingsEndpoint). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Zeus|Connection")
|
||||
FString ZeusServerHost;
|
||||
|
||||
/** Porta UDP (ignorada se bZeusUseDeveloperSettingsEndpoint). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Zeus|Connection")
|
||||
int32 ZeusServerPort = 27777;
|
||||
|
||||
/** Ligar e registar delegados automaticamente no fim de Init. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Zeus|Connection")
|
||||
bool bZeusAutoConnectOnInit = true;
|
||||
|
||||
private:
|
||||
void BindZeusDelegates(class UZeusNetworkSubsystem* Zeus);
|
||||
void UnbindZeusDelegates(class UZeusNetworkSubsystem* Zeus);
|
||||
void HandlePostLoadMap(class UWorld* LoadedWorld);
|
||||
bool bTravelInFlight = false;
|
||||
FString LastTravelTarget;
|
||||
|
||||
UFUNCTION()
|
||||
void HandleZeusConnected();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleZeusConnectionFailed(FString Reason);
|
||||
|
||||
UFUNCTION()
|
||||
void HandleZeusDisconnected();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleZeusPingUpdated(float RttMs);
|
||||
|
||||
UFUNCTION()
|
||||
void HandleZeusNetworkLog(FString Message);
|
||||
|
||||
UFUNCTION()
|
||||
void HandleZeusServerTravelRequested(const FString& MapName, const FString& MapPath);
|
||||
};
|
||||
13
Source/ZClientMMO/ZClientMMOGameMode.cpp
Normal file
13
Source/ZClientMMO/ZClientMMOGameMode.cpp
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "ZClientMMOGameMode.h"
|
||||
|
||||
#include "Game/Player/PlayerCharacter.h"
|
||||
#include "ZClientMMOPlayerController.h"
|
||||
|
||||
AZClientMMOGameMode::AZClientMMOGameMode()
|
||||
{
|
||||
// Mantemos o pawn especializado (APlayerCharacter) e controller padrão do cliente.
|
||||
DefaultPawnClass = APlayerCharacter::StaticClass();
|
||||
PlayerControllerClass = AZClientMMOPlayerController::StaticClass();
|
||||
}
|
||||
18
Source/ZClientMMO/ZClientMMOGameMode.h
Normal file
18
Source/ZClientMMO/ZClientMMOGameMode.h
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/GameModeBase.h"
|
||||
#include "ZClientMMOGameMode.generated.h"
|
||||
|
||||
/** GameMode base do cliente MMO. */
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class AZClientMMOGameMode : public AGameModeBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Constructor */
|
||||
AZClientMMOGameMode();
|
||||
};
|
||||
83
Source/ZClientMMO/ZClientMMOPlayerController.cpp
Normal file
83
Source/ZClientMMO/ZClientMMOPlayerController.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "ZClientMMOPlayerController.h"
|
||||
|
||||
#include "EnhancedInputSubsystems.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "Engine/LocalPlayer.h"
|
||||
#include "InputMappingContext.h"
|
||||
#include "UObject/ConstructorHelpers.h"
|
||||
#include "ZClientMMO.h"
|
||||
#include "Widgets/Input/SVirtualJoystick.h"
|
||||
|
||||
AZClientMMOPlayerController::AZClientMMOPlayerController()
|
||||
{
|
||||
static ConstructorHelpers::FObjectFinder<UInputMappingContext> DefaultImc(
|
||||
TEXT("/Game/Input/IMC_Default.IMC_Default"));
|
||||
static ConstructorHelpers::FObjectFinder<UInputMappingContext> MouseLookImc(
|
||||
TEXT("/Game/Input/IMC_MouseLook.IMC_MouseLook"));
|
||||
|
||||
if (DefaultImc.Succeeded())
|
||||
{
|
||||
DefaultMappingContexts.Add(DefaultImc.Object);
|
||||
}
|
||||
if (MouseLookImc.Succeeded())
|
||||
{
|
||||
MobileExcludedMappingContexts.Add(MouseLookImc.Object);
|
||||
}
|
||||
}
|
||||
|
||||
void AZClientMMOPlayerController::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
// only spawn touch controls on local player controllers
|
||||
if (ShouldUseTouchControls() && IsLocalPlayerController())
|
||||
{
|
||||
// spawn the mobile controls widget
|
||||
MobileControlsWidget = CreateWidget<UUserWidget>(this, MobileControlsWidgetClass);
|
||||
|
||||
if (MobileControlsWidget)
|
||||
{
|
||||
// add the controls to the player screen
|
||||
MobileControlsWidget->AddToPlayerScreen(0);
|
||||
|
||||
} else {
|
||||
|
||||
UE_LOG(LogZClientMMO, Error, TEXT("Could not spawn mobile controls widget."));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void AZClientMMOPlayerController::SetupInputComponent()
|
||||
{
|
||||
Super::SetupInputComponent();
|
||||
|
||||
if (IsLocalPlayerController())
|
||||
{
|
||||
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer()))
|
||||
{
|
||||
for (UInputMappingContext* CurrentContext : DefaultMappingContexts)
|
||||
{
|
||||
Subsystem->AddMappingContext(CurrentContext, 0);
|
||||
}
|
||||
|
||||
// only add these IMCs if we're not using mobile touch input
|
||||
if (!ShouldUseTouchControls())
|
||||
{
|
||||
for (UInputMappingContext* CurrentContext : MobileExcludedMappingContexts)
|
||||
{
|
||||
Subsystem->AddMappingContext(CurrentContext, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AZClientMMOPlayerController::ShouldUseTouchControls() const
|
||||
{
|
||||
// are we on a mobile platform? Should we force touch?
|
||||
return SVirtualJoystick::ShouldDisplayTouchInterface() || bForceTouchControls;
|
||||
}
|
||||
51
Source/ZClientMMO/ZClientMMOPlayerController.h
Normal file
51
Source/ZClientMMO/ZClientMMOPlayerController.h
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "ZClientMMOPlayerController.generated.h"
|
||||
|
||||
class UInputMappingContext;
|
||||
class UUserWidget;
|
||||
|
||||
/** PlayerController do cliente com setup de IMCs do template. */
|
||||
UCLASS()
|
||||
class AZClientMMOPlayerController : public APlayerController
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
AZClientMMOPlayerController();
|
||||
|
||||
protected:
|
||||
/** Input Mapping Contexts base. */
|
||||
UPROPERTY(EditAnywhere, Category = "Input|Input Mappings")
|
||||
TArray<UInputMappingContext*> DefaultMappingContexts;
|
||||
|
||||
/** Input Mapping Contexts excluidos quando touch controls estao ativos. */
|
||||
UPROPERTY(EditAnywhere, Category = "Input|Input Mappings")
|
||||
TArray<UInputMappingContext*> MobileExcludedMappingContexts;
|
||||
|
||||
/** Mobile controls widget to spawn */
|
||||
UPROPERTY(EditAnywhere, Category="Input|Touch Controls")
|
||||
TSubclassOf<UUserWidget> MobileControlsWidgetClass;
|
||||
|
||||
/** Pointer to the mobile controls widget */
|
||||
UPROPERTY()
|
||||
TObjectPtr<UUserWidget> MobileControlsWidget;
|
||||
|
||||
/** If true, the player will use UMG touch controls even if not playing on mobile platforms */
|
||||
UPROPERTY(EditAnywhere, Config, Category = "Input|Touch Controls")
|
||||
bool bForceTouchControls = false;
|
||||
|
||||
/** Gameplay initialization */
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
/** Input mapping context setup */
|
||||
virtual void SetupInputComponent() override;
|
||||
|
||||
/** Returns true if the player should use UMG touch controls */
|
||||
bool ShouldUseTouchControls() const;
|
||||
|
||||
};
|
||||
15
Source/ZClientMMOEditor.Target.cs
Normal file
15
Source/ZClientMMOEditor.Target.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
using UnrealBuildTool;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class ZClientMMOEditorTarget : TargetRules
|
||||
{
|
||||
public ZClientMMOEditorTarget(TargetInfo Target) : base(Target)
|
||||
{
|
||||
Type = TargetType.Editor;
|
||||
DefaultBuildSettings = BuildSettingsVersion.V6;
|
||||
IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_7;
|
||||
ExtraModuleNames.Add("ZClientMMO");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user