1.服务器、客户端里面角色和控制器之间的关系:
在客户端中,只有一个本地玩家控制器;但在服务器端(不管是监听服务器还是专用服务器),都有其他玩家的控制器。
所以,我们先判断,屏幕上的角色,是不是由本地玩家控制器控制的:
Source/Crunch/Public/Character/CCharacter.h
//判断是否有本地玩家控制
bool IsLocallyControlledByPlayer();
Source/Crunch/Private/Character/CCharacter.cpp
bool ACCharacter::IsLocallyControlledByPlayer()
{
return GetController() != nullptr && GetController()->IsLocalPlayerController();
}
思考:
如果,我将上面的IsLocalPlayerController()换成IsLocalController()会如何呢?
在Unreal Engine中,IsLocalPlayerController()
和IsLocalController()
的区别主要体现在以下方面:
-
所属类与功能定位
IsLocalPlayerController()
是APlayerController
类的成员函数,专门用于判断当前PlayerController是否为本地控制的玩家控制器IsLocalController()
是AController
基类的成员函数,适用于所有控制器类型(包括PlayerController和AIController)的本地化判断
-
应用场景差异
IsLocalPlayerController()
通常用于:- 分屏游戏中区分不同本地玩家的控制器实例
- 确保输入事件、UI交互等操作仅对本地玩家生效
IsLocalController()
更通用,可用于:- AI控制的本地模拟(如离线训练模式)
- 网络游戏中判断任意控制器的本地归属
-
返回值逻辑
IsLocalPlayerController()
需同时满足:- 控制器是PlayerController类型
- 控制器由本地机器控制
IsLocalController()
仅判断控制器是否由本地机器控制,不关心具体类型
-
典型使用示例
// 仅PlayerController专用判断 if (PlayerController->IsLocalPlayerController()) { // 处理本地玩家专属逻辑 } // 通用控制器判断 if (AnyController->IsLocalController()) { // 处理本地控制的AI或玩家逻辑 }
对于需要精确区分玩家控制的场景,推荐使用IsLocalPlayerController()
;若需兼容AI控制器的本地化判断,则应选择IsLocalController()
。
void ACCharacter::ConfigureOverHeadStateWidget()
{
if (!OverHeadWidgetComponent)
{
UE_LOG(LogTemp, Error, TEXT("OverHeadWidgetComponent is NULL"));
return;
}
//如果是本地玩家控制的角色,不显示状态条
if (IsLocallyControlledByPlayer())
{
OverHeadWidgetComponent->SetHiddenInGame(true);
return;
}
//得到小部件:头顶属性条
UOverHeadStateGauge* OverHeadStateGauge = Cast<UOverHeadStateGauge>(
OverHeadWidgetComponent->GetUserWidgetObject());
if (OverHeadStateGauge)
{
//调用能力系统组件,并配置它
OverHeadStateGauge ->ConfigWithASC(GetAbilitySystemComponent());
OverHeadWidgetComponent->SetHiddenInGame(false);
}
}
效果
拖入AI角色:
发现,AI状态条不更新!
2.检查C++代码,发现,我们只是在玩家控制器调用的ServerSideInit(),而AI角色没有执行过!
void ACPlayerController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
CPlayerCharacter = Cast<ACPlayerCharacter>(InPawn);
if (CPlayerCharacter)
{
CPlayerCharacter->ServerSideInit();
}
}
void ACCharacter::ServerSideInit()
{
CAbilitySystemComponent->InitAbilityActorInfo(this, this); //应用能力系统组件前都要初始化
CAbilitySystemComponent->ApplyInitialEffects(); //效果初始化
}
所以,要在角色类里,重写服务器调用函数:
// 只有服务器调用的方法
virtual void PossessedBy(AController* NewController) override;
void ACCharacter::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
// AI控制的角色
if (NewController && !NewController->IsPlayerController())
{
ServerSideInit();
}
}
源码:
Source/Crunch/Public/Character/CCharacter.h:
// Copyright@ChenChao
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "AbilitySystemInterface.h"
#include "CCharacter.generated.h"
class UWidgetComponent;
class UCAbilitySystemComponent;
class UCAttributeSet;
UCLASS(Abstract)
class CRUNCH_API ACCharacter : public ACharacter, public IAbilitySystemInterface
{
GENERATED_BODY()
public:
ACCharacter();
//服务器端初始化
void ServerSideInit();
//客户端初始化
void ClientSideInit();
//判断是否有本地玩家控制
bool IsLocallyControlledByPlayer();
// 只有服务器调用的方法
virtual void PossessedBy(AController* NewController) override;
/********************************************************************************/
/* GameplayAbilitySystem */
/********************************************************************************/
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override;
protected:
virtual void BeginPlay() override;
private:
UPROPERTY(VisibleDefaultsOnly, Category="GamePlay Ability")
TObjectPtr<UCAbilitySystemComponent> CAbilitySystemComponent;
UPROPERTY(VisibleDefaultsOnly, Category="GamePlay Ability")
TObjectPtr<UCAttributeSet> CAttributeSet;
/********************************************************************************/
/* UI */
/********************************************************************************/
UPROPERTY(VisibleDefaultsOnly, Category="UI")
TObjectPtr<UWidgetComponent> OverHeadWidgetComponent; //小部件组件
//控制头顶状态部件
void ConfigureOverHeadStateWidget();
};
Source/Crunch/Private/Character/CCharacter.cpp:
// Copyright@ChenChao
#include "Character/CCharacter.h"
#include "Components/WidgetComponent.h"
#include "GAS/CAbilitySystemComponent.h"
#include "GAS/CAttributeSet.h"
#include "UI/Widget/OverHeadStateGauge.h"
// Sets default values
ACCharacter::ACCharacter()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = false;
// 网格体无碰撞
GetMesh()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
//添加组件
CAbilitySystemComponent = CreateDefaultSubobject<UCAbilitySystemComponent>(TEXT("CAbilitySystemComponent"));
CAttributeSet = CreateDefaultSubobject<UCAttributeSet>(TEXT("CAttributeSet"));
OverHeadWidgetComponent = CreateDefaultSubobject<UWidgetComponent>(TEXT("OverHeadWidgetComponent"));
OverHeadWidgetComponent->SetupAttachment(GetRootComponent());
}
void ACCharacter::ServerSideInit()
{
CAbilitySystemComponent->InitAbilityActorInfo(this, this); //应用能力系统组件前都要初始化
CAbilitySystemComponent->ApplyInitialEffects(); //效果初始化
}
void ACCharacter::ClientSideInit()
{
CAbilitySystemComponent->InitAbilityActorInfo(this, this); //应用能力系统组件前都要初始化
}
bool ACCharacter::IsLocallyControlledByPlayer()
{
return GetController() && GetController()->IsLocalPlayerController();
}
void ACCharacter::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
// AI控制的角色
if (NewController && !NewController->IsPlayerController())
{
ServerSideInit();
}
}
UAbilitySystemComponent* ACCharacter::GetAbilitySystemComponent() const
{
return CAbilitySystemComponent;
}
void ACCharacter::BeginPlay()
{
Super::BeginPlay();
ConfigureOverHeadStateWidget();
}
void ACCharacter::ConfigureOverHeadStateWidget()
{
if (!OverHeadWidgetComponent)
{
UE_LOG(LogTemp, Error, TEXT("OverHeadWidgetComponent is NULL"));
return;
}
//如果是本地玩家控制的角色,不显示状态条
if (IsLocallyControlledByPlayer())
{
OverHeadWidgetComponent->SetHiddenInGame(true);
return;
}
//得到小部件:头顶属性条
UOverHeadStateGauge* OverHeadStateGauge = Cast<UOverHeadStateGauge>(
OverHeadWidgetComponent->GetUserWidgetObject());
if (OverHeadStateGauge)
{
//调用能力系统组件,并配置它
OverHeadStateGauge ->ConfigWithASC(GetAbilitySystemComponent());
OverHeadWidgetComponent->SetHiddenInGame(false);
}
}
效果:
PS:其实,我们习惯在角色类里的PossessedBy方法里写服务器代码,在控制器类的AcknowledgePossession方法里写客户端方法:
修改如下:
Source/Crunch/Public/Character/CCharacter.h:
// Copyright@ChenChao
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "AbilitySystemInterface.h"
#include "CCharacter.generated.h"
class UWidgetComponent;
class UCAbilitySystemComponent;
class UCAttributeSet;
UCLASS(Abstract)
class CRUNCH_API ACCharacter : public ACharacter, public IAbilitySystemInterface
{
GENERATED_BODY()
public:
ACCharacter();
void ServerSideInit(); //服务器端初始化
void ClientSideInit(); //客户端初始化
//判断是否有本地玩家控制
bool IsLocallyControlledByPlayer() const;
// 只有服务器调用的方法
virtual void PossessedBy(AController* NewController) override;
protected:
virtual void BeginPlay() override;
/********************************************************************************/
/* GameplayAbilitySystem */
/********************************************************************************/
public:
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override;
private:
UPROPERTY(VisibleDefaultsOnly, Category="GamePlay Ability")
TObjectPtr<UCAbilitySystemComponent> CAbilitySystemComponent;
UPROPERTY(VisibleDefaultsOnly, Category="GamePlay Ability")
TObjectPtr<UCAttributeSet> CAttributeSet;
/********************************************************************************/
/* UI */
/********************************************************************************/
private:
//控制头顶状态部件
void ConfigureOverHeadStateWidget();
//检测状态条可见性
void UpdateHeadGaugeVisibility();
private:
UPROPERTY(VisibleDefaultsOnly, Category="UI")
TObjectPtr<UWidgetComponent> OverHeadWidgetComponent; //小部件组件
//计时器频率
UPROPERTY(EditDefaultsOnly, Category="UI")
float CheakUpdateRate = 0.2f;
//状态条可见距离
UPROPERTY(EditDefaultsOnly, Category="UI")
float CheakUpdateDistance = 300.f;
// 距离检测计时器句柄
FTimerHandle VisibilityCheckTimerHandle;
};
Source/Crunch/Private/Character/CCharacter.cpp:
// Copyright@ChenChao
#include "Character/CCharacter.h"
#include "Components/WidgetComponent.h"
#include "GAS/CAbilitySystemComponent.h"
#include "GAS/CAttributeSet.h"
#include "Kismet/GameplayStatics.h"
#include "UI/Widget/OverHeadStateGauge.h"
// Sets default values
ACCharacter::ACCharacter()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = false;
// 网格体无碰撞
GetMesh()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
//添加组件
CAbilitySystemComponent = CreateDefaultSubobject<UCAbilitySystemComponent>(TEXT("CAbilitySystemComponent"));
CAttributeSet = CreateDefaultSubobject<UCAttributeSet>(TEXT("CAttributeSet"));
OverHeadWidgetComponent = CreateDefaultSubobject<UWidgetComponent>(TEXT("OverHeadWidgetComponent"));
OverHeadWidgetComponent->SetupAttachment(GetRootComponent());
}
void ACCharacter::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
ServerSideInit();
/*
// AI控制的角色
if (NewController && !NewController->IsPlayerController())
{
ServerSideInit();
}
*/
}
void ACCharacter::ServerSideInit()
{
CAbilitySystemComponent->InitAbilityActorInfo(this, this); //应用能力系统组件前都要初始化
CAbilitySystemComponent->ApplyInitialEffects(); //效果初始化
}
void ACCharacter::ClientSideInit()
{
CAbilitySystemComponent->InitAbilityActorInfo(this, this); //应用能力系统组件前都要初始化
}
UAbilitySystemComponent* ACCharacter::GetAbilitySystemComponent() const
{
return CAbilitySystemComponent;
}
void ACCharacter::ConfigureOverHeadStateWidget()
{
if (!OverHeadWidgetComponent)
{
UE_LOG(LogTemp, Error, TEXT("OverHeadWidgetComponent is NULL"));
return;
}
//如果是本地玩家控制的角色,不显示状态条
if (IsLocallyControlledByPlayer())
{
OverHeadWidgetComponent->SetHiddenInGame(true);
return;
}
//得到小部件:头顶属性条
UOverHeadStateGauge* OverHeadStateGauge = Cast<UOverHeadStateGauge>(OverHeadWidgetComponent->GetUserWidgetObject());
if (OverHeadStateGauge)
{
//调用能力系统组件,并配置它
OverHeadStateGauge ->ConfigWithASC(GetAbilitySystemComponent());
OverHeadWidgetComponent->SetHiddenInGame(false);
GetWorldTimerManager().ClearTimer(VisibilityCheckTimerHandle);
GetWorldTimerManager().SetTimer(VisibilityCheckTimerHandle, this, &ACCharacter::UpdateHeadGaugeVisibility, CheakUpdateRate, true);
}
}
bool ACCharacter::IsLocallyControlledByPlayer() const
{
return GetController() && GetController()->IsLocalPlayerController();
}
void ACCharacter::UpdateHeadGaugeVisibility()
{
//测算距离
APawn* PlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
if (!PlayerPawn) return;
float Distance = FVector::DistSquared(GetActorLocation(), PlayerPawn->GetActorLocation());
//隐藏状态条
OverHeadWidgetComponent->SetHiddenInGame(Distance > CheakUpdateDistance*CheakUpdateDistance);
}
void ACCharacter::BeginPlay()
{
Super::BeginPlay();
ConfigureOverHeadStateWidget();
}
Source/Crunch/Public/Player/CPlayerController.h:
// Copyright@ChenChao
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "CPlayerController.generated.h"
class ACPlayerCharacter;
class UGameplayWidget;
/**
*
*/
UCLASS()
class CRUNCH_API ACPlayerController : public APlayerController
{
GENERATED_BODY()
public:
/*
// 只有服务器调用的方法
virtual void OnPossess(APawn* InPawn) override;
*/
// 只有客户端(或者监听服务器)调用的方法
virtual void AcknowledgePossession(class APawn* P) override;
private:
UPROPERTY()
TObjectPtr<ACPlayerCharacter> CPlayerCharacter;
/*************************************************************************************************/
/* UI */
/*************************************************************************************************/
private:
void SpawnGameplayWidget();
UPROPERTY()
TObjectPtr<UGameplayWidget> GameplayWidget;
UPROPERTY(EditDefaultsOnly, Category="UI")
TSubclassOf<UGameplayWidget> GameplayWidgetClass;
};
Source/Crunch/Private/Player/CPlayerController.cpp:
// Copyright@ChenChao
#include "Player/CPlayerController.h"
#include "Blueprint/UserWidget.h"
#include "Player/CPlayerCharacter.h"
#include "UI/Widget/GameplayWidget.h"
/*
void ACPlayerController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
CPlayerCharacter = Cast<ACPlayerCharacter>(InPawn);
if (CPlayerCharacter)
{
CPlayerCharacter->ServerSideInit();
}
}
*/
void ACPlayerController::AcknowledgePossession(class APawn* P)
{
Super::AcknowledgePossession(P);
CPlayerCharacter = Cast<ACPlayerCharacter>(P);
if (CPlayerCharacter)
{
CPlayerCharacter->ClientSideInit();
SpawnGameplayWidget();
}
}
void ACPlayerController::SpawnGameplayWidget()
{
if (!IsLocalPlayerController()) return;
//创建游戏玩法小部件
GameplayWidget = CreateWidget<UGameplayWidget>(this, GameplayWidgetClass);
if (GameplayWidget)
{
GameplayWidget->AddToViewport(); //添加到视口
}
}
效果:
出现BUG:
当用监听服务器运行游戏时,别的玩家也会隐藏状态条:
实验:
(1) ROLE_SimulatedProxy - 模拟代理,表示该对象在本地是远程对象的模拟副本
if ( GetLocalRole() == ROLE_SimulatedProxy)
{
OverHeadWidgetComponent->SetHiddenInGame(true);
return;
}
效果:
客户端模式:其他的客户端的角色和AI控制的角色都隐藏了
监听服务器模式:其他客户端的角色隐藏了,AI没隐藏
(2) ROLE_AutonomousProxy - 自主代理,表示该对象在本地有自主控制权(如玩家控制的角色)
if ( GetLocalRole() == ROLE_AutonomousProxy )
{
OverHeadWidgetComponent->SetHiddenInGame(true);
return;
}
效果:
客户端:自己控制的角色隐藏了
服务器:别的客户端控制的角色隐藏了
(3) ROLE_Authority - 权威角色,表示该对象在网络中具有最高控制权(通常是服务器)
if ( GetLocalRole() == ROLE_Authority)
{
OverHeadWidgetComponent->SetHiddenInGame(true);
return;
}
效果:
客户端:没有隐藏的
服务端:全部隐藏