Gameplay重要类及重要功能使用方法(二)
Actor的碰撞检测事件
- 做个火的碰撞检测实验,创建一个Actor添加上火的粒子,开启碰撞事件,当玩家进入到碰撞范围就打印名字,离开范围也打印
- 新建一个Actor作为实验目标
- 声明场景组件作为根组件使用,碰撞胶囊组件,粒子组件,碰撞处理函数作为碰撞事件的绑定函数使用
- 触发器中提供了两个与一个绑定宏用来判断组件是否被重叠,这两个函数是要参与UE反射系统的,我们需要使用
UFUNCTION
进行标明,UFUNCTION
里面可以什么参数不要,我们称这个操作为多播委托OnComponentBeginOverlap
:事件,当某些内容开始与此组件重叠时调用,例如玩家进入触发器。OnComponentEndOverlap
:事件,当组件停止重叠时调用
AddDynamic
宏:这个宏需要传入一个绑定函数,绑定函数参数格式如下UE源码,因为UE是把参数与参数类型都当做参数了,所以我们需要把逗号删除 。
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "FireItem.generated.h"
UCLASS()
class GAMEPLAYCODEPARSING_API AFireItem : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AFireItem();
//场景组件作为根组件使用
UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "Components")
class USceneComponent* SceneComponent;
//碰撞胶囊组件
UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "Components")
class USphereComponent* SphereCollisionComponent;
//粒子组件
UPROPERTY(EditAnywhere,BlueprintReadWrite,Category="Components")
class UParticleSystemComponent* ParticleComponent;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
//碰撞处理函数
UFUNCTION()
void BeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
UFUNCTION()
void EndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
};
- 做一下限定只要在独立服务器的时候才会执行碰撞事件
// Fill out your copyright notice in the Description page of Project Settings.
#include "FireItem.h"
#include "Components/SphereComponent.h"
#include "Particles/ParticleSystemComponent.h"
#include "Utils/BFL_LogManager.h"
DEFINE_LOG_CATEGORY_STATIC(MyLog_Collision, Log, All);
// Sets default values
AFireItem::AFireItem()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
//让屏幕组件成为根组件
SceneComponent = CreateDefaultSubobject<USceneComponent>(TEXT("SceneComponent"));
SceneComponent->SetupAttachment(GetRootComponent());
SphereCollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereCollisionComponent"));
SphereCollisionComponent->SetupAttachment(SceneComponent);
SphereCollisionComponent->SetSphereRadius(150.);
ParticleComponent = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("ParticleComponent"));
ParticleComponent->SetupAttachment(SceneComponent);
}
// Called when the game starts or when spawned
void AFireItem::BeginPlay()
{
Super::BeginPlay();
//做限定,碰撞事件只在服务器上运行
if (GetNetMode() == ENetMode::NM_DedicatedServer)
{
SphereCollisionComponent->OnComponentBeginOverlap.AddDynamic(this, &AFireItem::BeginOverlap);
SphereCollisionComponent->OnComponentEndOverlap.AddDynamic(this, &AFireItem::EndOverlap);
}
}
// Called every frame
void AFireItem::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void AFireItem::BeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if (OtherActor)
{
UE_LOG(MyLog_Collision, Log, TEXT("__StartPlayerMode__%s"), *UBFL_LogManager::GetNetModeStr(this));
}
}
void AFireItem::EndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
if (OtherActor)
{
UE_LOG(MyLog_Collision, Log, TEXT("__EndPlayerMode__%s"), *UBFL_LogManager::GetNetModeStr(this));
}
}
- 因为做了限定所以要在独立服务器模式下运行,用客户端模式也可以,因为后台会自动帮我们启动一个独立服务器。运行结果:
Actor的属性同步
玩家属性注册同步
- 玩家扣血事件是在服务器中进行的,然后服务器要把扣血的数据传递到客户端,客户端只是用接收服务器的消息
- 新建一个
PlayerState
类,这个类是专门用来存放玩家属性
//注册同步属性
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
TArray<FLifetimeProperty>& OutLifetimeProps
参数是一个引用,用来存储那些需要被网络复制的属性信息
//属性同步,单独的服务器把要改变的属性传给客户端
UPROPERTY(Replicated)
float MaxHP =100;
UPROPERTY(Replicated):
这个宏标记了MaxHP变量需要在网络中进行复制。
这意味着每当服务器上的MaxHP值发生变化时,该变化会自动广播给所有连接的客户端,
确保所有客户端上的MaxHP值与服务器保持一致。这里还直接初始化MaxHP为100。
//属性通知,即传递了数据还可以写个函数执行行为
UPROPERTY(ReplicatedUsing = "OnRep_ChangeCurHP")
float CurHP = MaxHP;
UPROPERTY(ReplicatedUsing = "OnRep_ChangeCurHP"):
这个宏不仅指示了CurHP属性需要在网络间复制,而且还指定了一个名为OnRep_ChangeCurHP的函数来处理属性复制之后的动作。
这意味着每当服务器上的CurHP值发生改变并同步到客户端时,还会自动调用OnRep_ChangeCurHP函数。
这个函数允许开发者在属性更新后执行额外的逻辑或行为,比如播放动画、触发UI更新等。
- 注册同步属性
- 进行同步
玩家扣血逻辑
- 在玩家类中新建两个函数用于扣血与走出火的碰撞圈结束扣血,思路就是使用定时器进行扣血,当出了碰撞圈就关闭计数器
- 然后在火的Actor类中碰撞事件上调用该函数
- 最后将PlayerState注册到GameMode
- 运行结果
AInfo与APlayerState简介
- 探索APlayerState源码类
PlayerState
是为服务器上的每个玩家创建的(或在独立游戏中)PlayerStates
被复制到所有客户端,并包含有关玩家的网络游戏相关信息,如玩家名,分数等。
APlayerState
是继承自AInfo
AInfo
是所有保存信息保存类的根类,没有任何碰撞移动类型的代码Info
是Actor
的基类,它并不意味着在世界中具有物理表示,主要使用于“管理器”类型的类,该类保存有关世界的设置数据,但出于复制目的可能需要是Actor。Info
类中基本是没什么代码的吗,它本身也是个抽象类WITH_EDITOR
的意思是在编译器编译的时候才有用,否则就没用,打包的话这些代码都无效了
Info
派生的类:这些的共性只做为数据的管理类,有没有被实体化不重要。
- PlayerState类中比较重要的函数
CopyProperties
:复制需要保存在非活动PlayerState中的属性
- CopyProperties:用与切换关卡的时候进行数据交换
- 因为切换关卡会创建一个新的
PlayerState
- 一个关卡对应一个
GameMode
,PlayerState
是注册在GameMode中
的,当关卡切换了PlayerState
也会有新的,为了切换关卡数据不丢失所以就存在这个函数了
- 因为切换关卡会创建一个新的
Actor的网络同步
- 做一个小功能当人物在火焰上扣血时生成一个
Actor方块
- 在角色类中添加一个
生成Actor的函数
与承接生成Actor的模版类
- 生成Actor函数逻辑
- 在开始掉血的时候就调用这个函数
- 然后在虚幻引擎中新建一个蓝图,将模型添加上,打开复制到远程机
- 在角色蓝图中添加生成的Actor
- 运行结果
- 在运行过程中是可以选择进入到别的世界的
RPC函数_Client_服务器调用客户端执行
- RPC函数
UFUNCTION(Reliable,Client)
:服务器调用,仅在自己客户端执行。<RunOnOwningClient>UFUNCTION(Reliable,Server)
:客户端调用,服务器执行。<血量控制>UFUNCTION(Reliable,NetMulticast)
:服务器调用,服务器和所有客户端执行
- 制作一个功能来讲解Client服务器调用客户端执行函数
- 实现在客户端的UI面板上显示之前生成的Actor的有多少个
- 在角色类中新建一个客户端RPC函数与一个计数的变量
- 写入逻辑
- 在角色信息UI里面添加一个文本UI组件用于显示,记得将这些组件的作用域标为公有
- 绑定好这个新建的这个UI组件
- 新建两个玩家进行测试
- 运行结果,验证
UFUNCTION(Reliable,Client)
:服务器调用,仅在自己客户端执行
RPC函数_Server_客户端调用服务器执行
- 制作一个功能来讲解Server客户端调用服务器执行函数
- 功能:按H键瞬间满血
- 新建一个服务器RPC函数与封装这个回血函数的普通函数
- 逻辑
- 然后绑定按键H执行恢复血量函数
- 然后在虚幻引擎中添加操作绑定到映射中
- 运行结果
RPC函数_NetMulticast_服务器调用全部执行
- 服务器与各客户端关系图
- 多播是在服务器上调用,所有服务器和客户端都会执行一遍
- 我们使用多播验证一下关系图结论
- 声明一个多播RPC函数
- 抒写打印流程信息逻辑
- 在服务器中调用
- 将定时器改为10一次执行,开始就执行一次
- 用批处理脚本打开两个客户端与一个服务器验证关系图
- 我们发现客户端1在火上所以打印了三次NetMulticast三次,但是客户端2只打印了一次,客户端之间不会拥有对方的HUD与控制器,但是他们在服务器端都会有一个自己的控制器
Actor的网络身份
- 先说结论,当我们使用客户端1进入到火焰中时,服务器和客户端都有有控制器,但是这个控制器在客户端1是自治的身份,在服务器上是权威身份,在其他客户端就是模拟代理的身份
- 验证
- 在打印日志类里面新建三个打印获取网络身份的函数
- 打印网络身份函数
- 运行结果:使用批处理脚本开一个服务器与两个客户端,让客户端1踩上火焰
- 可以看见服务器上客户端1的控制器是权威一个与自治一个
- 而客户端1自身控制器是自治的,然后客户端1的远程端就是服务器也就是权威的
- 客户端2是模拟的,客户端2的远程端是服务器也是权威的