_哔哩哔哩_bilibili

这边是Blaster 笔记 自己整理的有点杂乱 是本人 unity转ue的一个重要项目,

采用前视频后源码 去了解这个项目。

中间有其他资料与AI辅助。

插件编写(待)

这边我先开盖即用插件

Play设置

编译模式

模式 用途描述 编译特点 文件/性能特点
DebugGame 开发过程中进行调试。在游戏中设置断点、查看变量值以及代码调试。 以调试模式编译,包含调试符号(debug symbols)。 可执行文件较大且运行速度较慢。
Development 开发过程中进行内部测试和验证。相比DebugGame模式会进行更多优化,同时保留调试能力。 启用优化选项以提高运行性能,保留部分调试信息。 运行性能更高,但仍便于问题排查。
DebugGame Editor 在Unreal Editor中调试游戏功能(与DebugGame模式类似,但专用于编辑器环境)。 编译包含调试符号,便于在编辑器中调试。 可执行文件较大,运行效率较低。
Development Editor 在Unreal Editor中进行开发测试。优化编辑器运行性能,同时保留调试能力(与Development模式类似)。 启用优化选项提高编辑器性能,保留部分调试信息。 编辑器运行更流畅,同时支持问题排查。
Shipping 发布给玩家的正式版本构建。 全面优化编译,移除所有调试符号和调试信息。 可执行文件最小化,运行性能最高(无调试开销)。

网络模式(ENetMode)

更新插件代码

1
2
// 原弃用代码:bIsFocusable = true;
SetIsFocusable(true); // 使用UE5官方Setter方法[1,3](@ref)

基础3C

项目建立

  • 编译项目
    • 装入自定义网络插件
    • 装入Online SubSystem Steam
    • 设置ini:

在DefaultEngine.ini写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[/Script/Engine.GameEngine]
+NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="OnlineSubsystemSteam.SteamNetDriver",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver")

[OnlineSubsystem]
DefaultPlatformService=Steam

[OnlineSubsystemSteam]
bEnabled=true
SteamDevAppId=480

; If using Sessions
; bInitServerOnClient=true

[/Script/OnlineSubsystemSteam.SteamNetDriver]
NetConnectionClassName="OnlineSubsystemSteam.SteamNetConnection"

在DefaultGame.ini写入

1
2
[/Script/Engine.GameSession]
MaxPlayers=4

删除二进制文件夹

  • 创建关卡

    • 创建StartUpMap,LobbyMap
    • 在Project Setting - Map&Modes - 里面更换默认的Map
    • 在StartUpMap关卡蓝图里面调用Create WBP Menu widget -> Menu Setup (设置Lobby路径)
  • Bulid

    • 设置bulid的map : 在Project Setting - 搜索 maps to 设置俩个关卡
    • 平台-打包-windows-放入新建的build文件夹

建立网络连接

资源

Maximo

重定向(待定)重定向教程

3C

Character

新建一个cpp类 注意

1
#include "FCharacter.h"

头文件path要对,新建立的时候默认h会错误

每次更新都 ====在编辑器或游戏窗口中按下 **Ctrl + Alt + F11**,强制终止当前 Live Coding 进程,释放资源后重新编译

C++类创建蓝图 并且设置Mesh和pos

Camera

FCharacter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
	
FCharacter.h
UPROPERTY(VisibleAnyWhere, Category = Camera)
class USpringArmComponent* CameraBoom;

UPROPERTY(VisibleAnyWhere, Category = Camera)
class UCameraComponent* FollowCamera;


FCharacter.cpp
AFCharacter::AFCharacter()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;


CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);

// 设置弹簧臂长度(摄像机与角色的默认距离)
CameraBoom->TargetArmLength = 500.f;

// 启用角色旋转控制弹簧臂 当玩家控制器旋转时,弹簧臂同步旋转(适用于第三人称视角)
CameraBoom->bUsePawnControlRotation = true;


FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);

// 设置摄像机的视角控制
FollowCamera->bUsePawnControlRotation = false; // 禁用摄像机的控制器旋转(通常用于第一人称视角)

}

Control

project setting - Engine Input (类似unity Input Manager)

设置人物BP_FCharacter - Pawn - 自动控制玩家 - 选择玩家0

FCharacter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// Called to bind functionality to input
void AFCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);

PlayerInputComponent->BindAxis("MoveForward", this, &AFCharacter::MoveForward);
PlayerInputComponent->BindAxis("MoveRight", this, &AFCharacter::MoveRight);
PlayerInputComponent->BindAxis("Turn", this, &AFCharacter::Turn);
PlayerInputComponent->BindAxis("LookUp", this, &AFCharacter::LookUp);

}

void AFCharacter::MoveForward(float Value)
{
if (Controller && Value != 0.0f)
{
// 获取前向向量并应用移动

// 完整控制器旋转(包含俯仰角)
// const FRotator Rotation = Controller->GetControlRotation();
// 仅Yaw方向旋转(锁定水平移动)
const FRotator Rotation = FRotator(0.f, Controller->GetControlRotation().Yaw, 0.f);
const FVector Direction = FRotationMatrix(Rotation).GetScaledAxis(EAxis::X);
AddMovementInput(Direction, Value);
}
}

void AFCharacter::MoveRight(float Value)
{
if (Controller && Value != 0.0f)
{
// 获取右向向量并应用移动
// 完整控制器旋转(包含俯仰角)
// const FRotator Rotation = Controller->GetControlRotation();
// 仅Yaw方向旋转(锁定水平移动)
const FRotator Rotation = FRotator(0.f, Controller->GetControlRotation().Yaw, 0.f);
const FVector Direction = FRotationMatrix(Rotation).GetScaledAxis(EAxis::Y);
AddMovementInput(Direction, Value);
}
}

void AFCharacter::Turn(float Value)
{
// 处理水平旋转输入
AddControllerYawInput(Value);
}


void AFCharacter::LookUp(float Value)
{
// 处理垂直旋转输入
AddControllerPitchInput(Value);
}

Anim

FAnimInstance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void UFAnimInstance::NativeInitializeAnimation()
{
// 初始化动画实例时调用
Super::NativeInitializeAnimation();
//尝试获取角色实例
FCharacter = Cast<AFCharacter>(TryGetPawnOwner());
}


void UFAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
// 每帧更新动画实例时调用
Super::NativeUpdateAnimation(DeltaSeconds);

if(FCharacter == nullptr)
{
// 如果FCharacter为空,尝试获取角色实例
FCharacter = Cast<AFCharacter>(TryGetPawnOwner());
}

if (FCharacter == nullptr)
{
// 如果仍然为空,直接返回
return;
}

FVector Velocity = FCharacter->GetVelocity();

// 计算角色的速度
// 忽略垂直分量,通常用于地面移动速度计算
Velocity.Z = 0; // 忽略垂直速度分量
Speed = Velocity.Size(); // 计算速度大小

bIsInAir= FCharacter->GetMovementComponent()->IsFalling(); // 检查角色是否在空中
bIsAccelerating = FCharacter->GetCharacterMovement()->GetCurrentAcceleration().Size() > 0; // 检查角色是否正在加速

}
  • 动画蓝图创建

    • 类设置 - 父类设置为 F Anim Instance
  • 创建状态机

    • 创建状态
    • 创建连线
    • 创建连线条件
  • 创建混合空间(混合空间1D和混合空间的区别混合空间

    • 放入 Idle Walk Run 三个动画
    • X轴设置为Speed
    • 放入动画状态机 中 并且输入设置为CPP文件中的Speed

修改动画手感与摄像机小优化

自由摄像机视角

  • FCharacter.cpp
1
2
3
4
5
6
//动画后的优化
AFCharacter::AFCharacter(){
bUseControllerRotationYaw = false; // 禁止控制器偏航旋转
GetCharacterMovement()->bOrientRotationToMovement = true; // 角色朝向移动方向
}

  • BP_FCharacter - 使用控制器旋转Yaw - false
  • CharMoveComp - 将旋转朝向运动 - true

下落bug

  • 下落动画Loop

无缝切换

(99+ 封私信 / 80 条消息) 关卡系统四、无缝切换 - 知乎

对于地图切换(也即关卡切换),UE还提供了无缝切换(Seamless Travel)和非无缝切换(Non-Seamless Travel),无缝切换使用异步加载关卡资源,是非阻塞式切换,而非无缝切换即为前面介绍的同步加载关卡资源,是阻塞式切换(传送门),在网络联机游戏中,无缝切换不会导致网络断开,而非无缝会导致网络断开后重连,UE推荐在网络联机游戏中使用无缝切换,感兴趣可以看看官方文档(有点晦涩难懂T_T,因此需要深入研究一番)。

GameMode

  • 创建FLobbyGameMode

  • 设置Pawn为FCharacter

  • 创建过度关卡

  • 在Project Setting 里面设置过度关卡

  • 在GameMode中设置无缝切换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void AFLobbyGameMode::PostLogin(APlayerController* NewPlayer)
{
// 调用父类实现,确保基础登录流程完成
Super::PostLogin(NewPlayer);

// 获取当前已登录玩家数量(游戏状态中的玩家数组人数)
int32 NumofPlayer = GameState.Get()->PlayerArray.Num();

// 当有2名玩家登录时,开始游戏
if(NumofPlayer == 2)
{
UWorld* World = GetWorld();
if(World)
{
bUseSeamlessTravel = true; // 启用无缝切换地图功能
// 无缝切换到战斗地图(使用监听服务端模式)
World->ServerTravel(FString("/Game/AFMaps/FBattleMap?listen"));
}
}
}

HUD

  • 创建HUD Class
  • 创建WBP继承与HUD Class
  • 在代码中绑定WBP
  • 在FCharacter中设置WBP类以及展示Canvas
  • 在FCharacter蓝图中反射调用UpdateText
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void UFPlayerHUDWidget::SetDisplayText(const  FString& TextToDisplay)
{
if (DisplayText)
{
DisplayText->SetText(FText::FromString(TextToDisplay));
}
}
void UFPlayerHUDWidget::UpdateHUD(APawn* InPawn)
{
ENetRole RemoteRole = InPawn->GetRemoteRole();
FString Role;
switch (RemoteRole)
{
case ENetRole::ROLE_Authority:
Role = FString("Authority");
break;
case ENetRole::ROLE_AutonomousProxy:
Role = FString("Autonomous Proxy");
break;
case ENetRole::ROLE_SimulatedProxy:
Role = FString("Simulated Proxy");
break;
case ENetRole::ROLE_None:
Role = FString("None");
break;
}
FString RemoteRoleString = FString::Printf(TEXT("Remote Role: %s"), *Role);
SetDisplayText(RemoteRoleString);
}

void UFPlayerHUDWidget::NativeDestruct()
{
RemoveFromParent();
Super::NativeDestruct();
}

GetRemoteRole

主要API

11.Camera

CreateDefaultSubobject 是 Unreal Engine(UE)中用于创建组件或子对象的核心函数,主要在 C++ 中实现 Actor 或组件的初始化。

1
T* CreateDefaultSubobject<TReturnType>(FName SubobjectName, bool bIsRequired = true, bool bIsTransient = false);
  • **TReturnType**:需创建的组件类型(如 USpringArmComponent)。
  • SubobjectName:组件唯一标识(同一 Actor 内不可重复)
  • **bIsTransient**:若为 true,组件不会被序列化(适用于临时对象)

SetupAttachment 是用于建立组件层级关系的核心方法,主要作用是将一个组件(子组件)附加到另一个组件(父组件)上,形成父子依赖关系。

1
void USceneComponent::SetupAttachment(USceneComponent* Parent, FName SocketName = NAME_None);

反射 - UPROPERTY

Weapon

这边开始以阅读源码方式去处理代码部分,可能涉及到下面课程的部分,但主要还是看引擎操作流程。

1-Weapon Class

  • 创建CPP FWeaponBase

  • 编写代码

    • 代码内容为AFWeaponBase 的创建组件与网格碰撞(详见主要API 碰撞)的设置
    • 创建组件如下的1.2,1.5-1.10
    • 网格碰撞如下的3.1-3.4
  • 创建BP

    • BP继承与FWeaponBase
    • 设置Mesh

碰撞

UE4 物理碰撞(C++)_setcollisionresponsetochannel-CSDN博客

SetCollisionResponseToAllChannels(ECollisionResponse Response) 设置组件对所有通道的统一响应 Response: IgnoreOverlapBlock 快速全局设置(如禁用所有碰撞)
SetCollisionResponseToChannel(ECollisionChannel Channel, ECollisionResponse Response) 设置组件对单个通道的响应 Channel: 目标通道(如ECC_WorldStaticResponse: 响应类型 精细化控制(如角色忽略子弹通道)
SetCollisionEnabled(ECollisionEnabled::Type Type) 启用/禁用碰撞检测 Type: NoCollisionQueryOnly(仅检测)、PhysicsOnly(仅物理)、QueryAndPhysics 动态开关碰撞检测(如死亡后禁用)
通道名称 枚举值 主要用途 典型应用场景
WorldStatic ECC_WorldStatic 静态环境物体(不可移动) 墙壁、地面、建筑物等场景静态元素的碰撞阻挡 。
WorldDynamic ECC_WorldDynamic 动态物体(可移动或受物理影响) 可移动平台、可破坏物、开关门等动态交互对象 。
Pawn ECC_Pawn 玩家或AI控制的角色 角色移动碰撞、AI视线检测、玩家与NPC的交互 。
PhysicsBody ECC_PhysicsBody 受物理模拟影响的物体 滚石、箱子、布娃娃等物理驱动对象的碰撞检测 。
Visibility ECC_Visibility 可见性检测(非物理阻挡) 光线追踪、玩家视线判断、渲染剔除优化 。
Camera ECC_Camera 摄像机碰撞检测 防止摄像机穿墙、第三人称视角的镜头避障 。
Destructible ECC_Destructible 可破坏物体 栅栏、玻璃、爆炸物等可破坏对象的碰撞响应 。
Vehicle ECC_Vehicle 载具类对象 汽车、坦克、飞行器等载具的物理碰撞与交互 。
Gameplay ECC_GameTraceChannelN 预留的自定义游戏逻辑通道(N=1~8 武器检测(Weapon)、拾取物(Pickup)、陷阱触发等专属交互 。

2-Pickup Widget

  • 创建WBP Pickup

  • 加入Text

  • 编写代码 - 添加子物体

    • 创建WBP引用
  • BP_Weapon中

    • 设置Pickup Widget
      • Space-Screen
      • Widget Class - WBP Pickup
      • 所需大小绘制-true
  • 编写代码

    • OnSphereOverlap 当有物体进入拾取区域时调用

    • 必须是U函数

    1
    void AFWeaponBase::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) 
    • 绑定事件委托
1
2
AreaSphere->OnComponentBeginOverlap.AddDynamic(this, &AFWeaponBase::OnSphereOverlap);  // 3.5 绑定重叠开始事件
AreaSphere->OnComponentEndOverlap.AddDynamic(this, &AFWeaponBase::OnSphereEndOverlap); // 3.6 绑定重叠结束事件

委托

[UE4]委托代理:单播委托,多播委托,动态单播委托,动态多播委托,事件_ue 多播委托-CSDN博客

特性 单播委托 多播委托 动态单播委托 动态多播委托
绑定数量 仅绑定 1个 函数 可绑定 多个 函数 仅绑定 1个 函数 可绑定 多个 函数
执行方式 .Execute() .Broadcast() .Execute() .Broadcast()
蓝图支持 ❌ 不支持蓝图绑定 ❌ 不支持蓝图绑定 ✅ 支持蓝图绑定(需UFUNCTION ✅ 支持蓝图绑定(需UFUNCTION
线程安全 非线程安全 非线程安全 非线程安全 非线程安全
声明宏 DECLARE_DELEGATE[_Xxx] DECLARE_MULTICAST_DELEGATE[_Xxx] DECLARE_DYNAMIC_DELEGATE[_Xxx] DECLARE_DYNAMIC_MULTICAST_DELEGATE[_Xxx]
典型用例 一对一回调(如任务完成通知) 事件广播(如伤害事件通知多个系统) 蓝图与C++交互的单次回调 蓝图可订阅的事件系统(如UI事件)

3- Variable Replication

  • 复制变量设置

    • 在Character设置新变量为复制变量(详见主要API 复制变量)
    • 重写 virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override;
    • 这边TArray(详见主要API 数据容器)
    • DOREPLIFETIME
  • 内联宏 FORCEINLINE(详见主要API 内联宏)

  • DOREPLIFETIME_CONDITION(AFCharacter,OverlappingWeapon,COND_OwnerOnly)

  • 复制时回调函数

1
2
3
4
5
UPROPERTY(ReplicatedUsing = OnRep_OverlappingWeapon)
class AFWeaponBase* OverlappingWeapon;

UFUNCTION()
void OnRep_OverlappingWeapon();
  • SetOverlappingWeapon
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void AFCharacter::SetOverlappingWeapon(AFWeaponBase* Weapon)
{
if (OverlappingWeapon)
{
OverlappingWeapon->ShowPickupWidget(false);
}
OverlappingWeapon = Weapon;
if (IsLocallyControlled())
{
if (OverlappingWeapon)
{
OverlappingWeapon->ShowPickupWidget(true);
}
}
}

这边使用了IsLocallyControlled()

退出碰撞函数

1
2
UFUNCTION()
void OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
  • 设置Rep回调函数多一个变量

    1
    2
    UFUNCTION()
    void OnRep_OverlappingWeapon(AFWeaponBase* lastWeapon);

    注:这边lastWeapon 会有值(代表着被销毁的前一帧),但是OverlappingWeapon 可能为空,

网络复制(复制变量三部曲)

在虚幻引擎(Unreal Engine)中,UPROPERTY(Replicated) 是一个核心宏,用于声明一个网络复制的属性。其作用是标记该变量(属性)的值需要从服务器(Server)自动同步到所有相关的客户端(Client),以实现多人游戏中状态的一致性。

1. 声明复制属性

**ReplicatedUsing**:指定同步后的回调函数为 OnRep_OverlappingWeapon,即属性值在客户端更新后自动触发此函数

1
2
UPROPERTY(Replicated)
float Health; // 生命值需从服务器同步到客户端

2. 重写虚函数

重写 GetLifetimeReplicatedProps 函数 – 在.h声明虚函数

1
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

3. 使用DOREPLIFETIME宏

  • DOREPLIFETIME` 宏是复制注册的关键。
  • 头文件要求:必须包含 #include "Net/UnrealNetwork.h"
  • 详见DOREPLIFETIME宏解析

该宏确保 OverlappingWeapon(角色当前重叠的武器对象)仅在特定条件下从服务器同步到客户端。通过 COND_OwnerOnly 条件,仅同步给控制该角色的客户端,其他客户端(如队友或敌人)不会收到此数据

在属性所属的类中重写此函数,注册需复制的变量:

1
2
3
4
5
6
#include "Net/UnrealNetwork.h"  // 必须包含此头文件

void AMyActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const {
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AMyActor, Health); // 注册Health为复制属性
}

4. 启用Actor复制

(不包含三部曲中,如果要复制Actor的话用这个代替第一步)

在Actor的构造函数中设置:

1
2
3
AMyActor::AMyActor() {
SetReplicates(true); // 开启Actor的网络复制能力
}

数据容器(代替STL)

UE C++基础 | 常用数据容器 | TArray、TMap、TSet_emplace tmap-CSDN博客

DOREPLIFETIME宏解析

在虚幻引擎(Unreal Engine)中,DOREPLIFETIME(AFCharacter, OverlappingWeapon) 是一个核心宏,用于注册需要网络同步的属性,确保服务器上 AFCharacter 类中 OverlappingWeapon 变量的值能自动同步到所有客户端。以下是详细解析:


🔧 一、功能与作用

  1. 网络同步机制
    • 当服务器修改 OverlappingWeapon(如角色拾取武器)时,该宏会触发引擎自动将新值广播给所有客户端。
    • 客户端收到数据后更新本地副本,无需手动处理同步逻辑,确保多人游戏中状态一致。
  2. 使用场景
    • 适用于需跨客户端同步的 Actor 引用(如角色当前重叠的武器对象)。
    • 典型案例:角色靠近武器时,客户端需实时显示武器可拾取提示。

⚙️ 二、底层实现原理

  1. 依赖函数
    该宏需在 AFCharacter 类的 GetLifetimeReplicatedProps 函数中调用:

    1
    2
    3
    4
    void AFCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const {
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    DOREPLIFETIME(AFCharacter, OverlappingWeapon); // 注册同步属性
    }

    -

  2. 属性声明
    OverlappingWeapon 需用 UPROPERTY(Replicated) 标记为可复制属性:

    1
    2
    UPROPERTY(Replicated)
    class AWeapon* OverlappingWeapon; // 声明为可复制引用
  3. Actor复制启用
    AFCharacter 构造函数中需设置:

    1
    2
    3
    AFCharacter::AFCharacter() {
    SetReplicates(true); // 启用Actor的网络复制能力
    }

📊 三、同步流程与数据流

步骤 服务器 客户端
属性修改 调用 SetOverlappingWeapon(NewWeapon) -
检测变化 引擎自动检测 OverlappingWeapon 变更 -
广播更新 发送新值给所有客户端 接收更新数据
应用更新 - 自动更新本地 OverlappingWeapon

⚠️ 注意:客户端不能直接修改 Replicated 属性,否则会与服务器值冲突。


🛠️ 四、高级用法

  1. 条件复制
    通过 DOREPLIFETIME_CONDITION 限制同步范围,优化带宽:

    1
    DOREPLIFETIME_CONDITION(AFCharacter, OverlappingWeapon, COND_OwnerOnly);
    条件类型 作用
    COND_InitialOnly 仅首次同步时复制(如初始装备)
    COND_OwnerOnly 仅同步给控制该角色的客户端
  2. 复制通知(RepNotify)
    使用 ReplicatedUsing 在属性同步后触发客户端逻辑:

    1
    2
    3
    4
    5
    6
    7
    UPROPERTY(ReplicatedUsing = OnRep_OverlappingWeapon)
    AWeapon* OverlappingWeapon;

    UFUNCTION()
    void OnRep_OverlappingWeapon() {
    if (OverlappingWeapon) ShowPickupWidget(true); // 显示拾取UI
    }
    • 客户端属性更新后自动调用 OnRep_OverlappingWeapon

⚠️ 五、常见问题与注意事项

  1. 权限验证
    修改复制属性的代码需在服务器执行:

    1
    2
    3
    4
    5
    void AFCharacter::SetNewWeapon(AWeapon* Weapon) {
    if (HasAuthority()) { // 仅服务器可修改
    OverlappingWeapon = Weapon;
    }
    }
  2. 适用对象限制

    • 仅继承自 AActor 的类支持属性复制(如 AFCharacter)。
    • Actor 类(如 UObject)需通过 RPC 或序列化同步。
  3. 性能优化

    • 避免高频同步属性(如每帧更新的位置),优先使用内置组件(如 CharacterMovementComponent)。
    • 对低频属性(如装备状态、交互对象)使用复制更高效。

💎 总结

DOREPLIFETIME(AFCharacter, OverlappingWeapon) 是虚幻引擎网络同步的核心机制,通过声明-注册模式实现属性自动同步。结合 ReplicatedUsing 可扩展客户端响应逻辑,而条件复制能进一步优化带宽。在多人游戏中,合理使用该宏可显著提升状态同步的效率和可靠性。

IsLocallyControlled

在Unreal Engine的多人游戏开发中,类似 IsLocallyControlled() 的函数主要用于处理网络角色控制权、执行权限和同步逻辑的判断。以下是关键函数及其作用分类说明,结合了Unreal Engine的网络同步机制设计:


🔧 一、角色控制权与执行端判断

  1. IsLocallyControlled()

    • 功能:判断当前角色是否由本地玩家控制(客户端视角)。
    • 典型场景:客户端特效播放、本地UI更新(如武器拾取提示)。
  2. HasAuthority()

    • 功能:判断当前逻辑是否在服务端(Authority)执行。
    • 场景:关键状态修改(如生命值扣除)、生成网络同步对象(如武器掉落),确保仅服务端修改全局状态。
  3. IsNetMode(ENetMode Mode)

    • 功能:检测当前网络模式(如客户端、服务端、独立运行)。

    • 常用模式:

      • NM_Client:当前为客户端。
      • NM_DedicatedServer:专用服务器。
      • NM_ListenServer:监听服务器(同时是主机客户端)。

📡 二、网络执行函数变体

  1. GetWorld()->IsServer() / GetWorld()->IsClient()

    • 功能:直接判断当前运行环境是服务端或客户端,与 HasAuthority() 类似但更直观。
  2. RunOnServer (RPC函数限定符)

    • 功能:通过标记 UFUNCTION(Server, Reliable),强制函数逻辑在服务端执行。
    • 场景:客户端发起攻击请求时,需服务端验证并广播同步。
  3. Client / NetMulticast RPC

    • 功能:从服务端向客户端广播事件:

      • Client:仅发送给控制该角色的客户端。
  • NetMulticast:发送给所有客户端。
  • 场景:播放全局特效(如爆炸)、更新多人UI。

⚙️ 三、属性同步与响应函数

  1. OnRep_[PropertyName]()
    • 功能:属性标记为 Replicated 后,当属性在客户端同步时自动触发的回调函数。
    • 场景:客户端根据同步后的生命值更新血条UI。
  2. GetNetMode()
    • 功能:返回当前网络模式枚举值(比 IsNetMode 更灵活),用于分支逻辑处理。

📊 关键函数对比表
函数/方法 判断目标 典型应用场景
IsLocallyControlled() 角色是否本地控制 本地UI显示、输入响应
HasAuthority() 是否在服务端执行 关键状态修改、生成同步对象
IsNetMode(NM_Client) 当前是否为客户端环境 环境适配逻辑(如禁用客户端物理)
OnRep_Health() 客户端属性同步完成 更新本地UI或播放特效
UFUNCTION(Server) 强制函数在服务端运行 客户端发起的动作请求(如拾取武器)
UFUNCTION(NetMulticast) 服务端向所有客户端广播 全局事件(如游戏开始通知)

💡 四、组合使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void AMyCharacter::TryPickupWeapon(AWeapon* Weapon) {
// 客户端发起请求
if (IsLocallyControlled()) {
Server_PickupWeapon(Weapon); // 调用Server RPC
}
}

UFUNCTION(Server, Reliable)
void Server_PickupWeapon(AWeapon* Weapon) {
// 服务端验证并执行
if (HasAuthority() && Weapon) {
Weapon->AttachToCharacter(this);
Multicast_PlayPickupFX(); // 广播特效
}
}

UFUNCTION(NetMulticast, Unreliable)
void Multicast_PlayPickupFX() {
// 所有客户端播放特效
SpawnPickupParticles();
}

此代码体现了以下设计原则:

  1. 本地控制判断(IsLocallyControlled)触发客户端请求 →
  2. 服务端权限验证(HasAuthority)执行逻辑 →
  3. 广播事件(NetMulticast)同步到所有客户端。

⚠️ 注意事项

  1. 避免在客户端修改同步属性:所有需网络同步的属性修改必须通过服务端执行,否则会被引擎覆盖。
  2. RPC的可靠性选择:关键事件(如角色死亡)用 Reliable RPC;高频非关键事件(如位置微调)用 Unreliable RPC
  3. 网络优化:通过 NetUpdateFrequency 控制属性同步频率,平衡带宽与实时性需求。

这些函数共同构成了Unreal Engine多人游戏开发的底层框架,理解其差异和适用场景是保证网络同步一致性与性能的关键。

4 装备武器

  • 创建一个ActorComponent的子类:FShootingComponent

    • 在FCharactor中添加 FShootingComponent 的 U变量
    • FShootingComponent 加入静态变量并设置 SetIsReplicated –true
  • input 设置Equip

    • FCharactor设置UInputComponent按键回调
  • 设置FShootingComponent友元给 FCharactor

    • 友元类 可以全访问
  • 重载PostInitializeComponents

1
virtual void PostInitializeComponents() override;
  • 添加武器插槽

    • 在SK(骨骼)中hand_r右键添加武器插槽
    • 点击预览模型
    • 可以在Anim中查看预览模型位置以调整插槽位置
  • 写SetWeapon

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    USkeletalMeshComponent* mesh = Character->GetMesh(); // 获取角色的网格组件
    const USkeletalMeshSocket* hand = mesh-> GetSocketByName(FName("RightHandSocket")); // 将武器附加到角色的右手插槽
    if (hand)
    {
    hand->AttachActor(CurWeapon, mesh); // 将武器附加到角色的右手插槽
    }
    else
    {
    UE_LOG(LogTemp, Warning, TEXT("RightHandSocket not found!"));
    }
    CurWeapon ->SetOwner(Character); // 设置武器的拥有者为角色
  • 在FCharactor中判断 HasAuthority() 并调用SetWeapon

5 RPC

  • Sever函数中装备武器:
1
2
UFUNCTION(Server, Reliable)
void ServerEquipButtonPressed();
  • EquipButtonPressed 区分双端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
EquipButtonPressed 

if (shootingComponent)
{
shootingComponent-> SetWeapon(OverlappingWeapon);
if (HasAuthority())
{
//server端装备武器
shootingComponent-> SetWeapon(OverlappingWeapon);
}
else
{
//客户端请求装备武器
ServerEquipButtonPressed();
}

}
  • EquippedWeapon->SetOwner(Character);

    • 在这边Owner也是复制变量,OnRep_Owner为回调函数

      1
      2
      UPROPERTY(ReplicatedUsing=OnRep_Owner) 
      TObjectPtr<AActor> Owner;
  • 把 AFWeapon 的State 变成复制变量 回调函数为 OnRep_WeaponState (如复制变量三部曲)

若 EWS_Equipped 则

1
2
3
// 如果武器状态为已装备,隐藏拾取UI
ShowPickupWidget(false);
AreaSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision); // 禁用碰撞
  • 那么在UFShootingComponent 中会调用 SetState 从而实现 回调 OnRep_WeaponState

RPC流程

在虚幻引擎(UE)中,RPC(Remote Procedure Call,远程过程调用) 是实现网络同步的核心机制,允许客户端与服务器之间跨网络调用函数。以下是完整的RPC制作流程及关键要点,结合C++和蓝图实现方式:


🛠️ 一、RPC类型与选择

RPC分为三类,需根据场景选择:

  1. Server RPC
    • 作用:客户端调用 → 服务器执行(如请求跳跃、交互验证)。
    • 标记UFUNCTION(Server, Reliable/Unreliable)
    • 命名规范:函数名前缀 Server_(如 Server_Jump())。
  2. Client RPC
    • 作用:服务器调用 → 特定客户端执行(如播放动画、更新UI)。
    • 标记UFUNCTION(Client, Reliable/Unreliable)
    • 命名规范:函数名前缀 Client_(如 Client_PlayMontage())。
  3. Multicast RPC
    • 作用:服务器调用 → 所有客户端执行(如爆炸特效、全局事件)。
    • 标记UFUNCTION(NetMulticast, Reliable/Unreliable)
    • 命名规范:函数名前缀 Multicast_(如 Multicast_Explode())。
RPC类型 调用端 执行端 典型场景
Server 客户端 服务器 动作请求、状态变更
Client 服务器 特定客户端 动画播放、UI更新
Multicast 服务器 所有客户端 全局特效、环境事件

可靠性选择

  • Reliable:保证必达(关键操作如跳跃、伤害)。
  • Unreliable:允许丢包(高频更新如位置同步)。

📝 二、RPC声明与定义

C++实现

  1. 头文件声明

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // MyCharacter.h
    UCLASS()
    class AMyCharacter : public ACharacter {
    GENERATED_BODY()
    public:
    // Server RPC示例
    UFUNCTION(Server, Reliable, WithValidation)
    void Server_Jump();
    virtual bool Server_Jump_Validate(); // 参数验证(防作弊)
    virtual void Server_Jump_Implementation();

    // Client RPC示例
    UFUNCTION(Client, Reliable)
    void Client_PlayMontage(UAnimMontage* Montage);
    virtual void Client_PlayMontage_Implementation(UAnimMontage* Montage);
    };
  2. 源文件实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // MyCharacter.cpp
    void AMyCharacter::Server_Jump_Implementation() {
    if (CanJump()) Jump(); // 服务器逻辑
    Multicast_Jump(); // 广播到所有客户端
    }
    bool AMyCharacter::Server_Jump_Validate() { return true; } // 验证逻辑

    void AMyCharacter::Client_PlayMontage_Implementation(UAnimMontage* Montage) {
    if (IsLocallyControlled()) PlayAnimMontage(Montage); // 仅本地客户端执行
    }

蓝图实现

  1. 创建自定义事件:
    • 在蓝图编辑器中右键 → Add EventCustom Event
    • 设置事件名称(如 PlayMontageEvent)。
  2. 标记为RPC:
    • 在事件详情面板 → Replicates → 选择 Server/Client/Multicast

🔐 三、权限控制与调用条件

  • 调用者权限:

    • Server RPC:仅客户端可调用,且Actor需被客户端控制(如 IsLocallyControlled())。
    • Client/Multicast RPC:仅服务器可调用。
  • 执行端检查:

    • 在RPC实现中,使用

      1
      GetLocalRole()

      1
      HasAuthority()

      区分执行环境:

      1
      2
      3
      4
      void AMyCharacter::Client_PlayMontage_Implementation(UAnimMontage* Montage) {
      if (GetLocalRole() == ROLE_SimulatedProxy) return; // 非主控端跳过
      PlayAnimMontage(Montage);
      }
  • Actor复制要求:

    • RPC调用的Actor必须开启复制(bReplicates = true)。

⚙️ 四、参数处理与序列化

  • 支持类型:基础类型(intfloatFString)、结构体、UObject指针(需网络同步)。
  • 对象参数限制:
    • 传递的 UObject*(如 UAnimMontage*)必须在所有客户端加载,否则需用 SoftObjectPath 异步加载。
  • 结构体序列化:
    • 若结构体包含动态数据,需重写 NetSerialize() 函数优化流量。

📡 五、RPC调用流程

  1. 触发调用

    • 客户端触发Server RPC

      (如输入事件):

      1
      2
      3
      void AMyCharacter::OnJumpInput() {
      if (IsLocallyControlled()) Server_Jump();
      }
    • 服务器触发Client RPC(如播放动画):

      1
      2
      3
      void AMyCharacter::TriggerMontageOnClient() {
      if (HasAuthority()) Client_PlayMontage(JumpMontage);
      }
  2. 网络传输

    • 可靠RPC立即发送,不可靠RPC随属性同步发送。
    • 数据顺序:可靠RPC → 属性复制 → 不可靠RPC(同一Actor通道内有序)。

🧪 六、调试与测试

  1. 多玩家测试:
    • 编辑器开启多个PIE窗口:1个为服务器,其余为客户端。
  2. 网络模拟:
    • 控制台命令 net.PktLoss=20 模拟丢包,验证RPC可靠性。
  3. 日志追踪:
    • 使用 UE_LOG(LogNet, Log, TEXT("RPC Called")) 输出调用记录。

⚠️ 七、常见问题与优化

  1. 时序问题:
    • 避免在Actor初始化前调用RPC(如 BeginPlay 中),优先在 PostInitializeComponents() 后调用。
  2. 流量优化:
    • 高频更新(如位置同步)用 不可靠RPC + 属性复制 组合。
  3. 安全性:
    • Server RPC必须添加 WithValidation 验证参数(如检测坐标是否合法)。

💎 总结流程

  1. 设计RPC类型 → 2. 声明函数并标记UFUNCTION → 3. 实现逻辑与验证 → 4. 权限检查 → 5. 参数传递优化 → 6. 网络测试与调试
    通过合理使用RPC,可高效实现多人游戏的实时交互,同时确保安全性与流畅性。

6 动画

  • 在FAnim中声明一个变量 : 武器是否装配(bool)

  • 把FShootingComponent组件中的武器提升成复制变量(如复制变量三部曲)

  • 在新建一个子FSM – 在Blend Poses by bool 节点上 – 武器是否装配(bool) 来当 Condition

    • 这个子FSM代表的是已装配FSM - Equipped
    • 之前上一章的FSM是未装配FSM - Unequipped
  • Equipped 创建idle State

7 下蹲

继承与Character Croush 有 bIsCroushed 的bool 这个还是一个复制变量

在AFAnim中设置 this-> bIsCroushed = bIsCroushed 然后在FSM设置Condition

8 瞄准

tmp:动画 Play Idle_Rifle_Ironsights Play Idle_Rifle_Hip idle状态中

Croush 中也一样

  • bAnim (bool)定义

  • 在FCharactor(取Component的),FAnim ,shootingComponent

  • bAnim 提升成复制变量

    • 这边只能从S->C 才同步
  • 使用RPC

    • 这边才能C-> C and S
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void UFShootingComponent::SetAiming(bool b)
{
bAnim = b; // 设置动画状态

if (!Character->HasAuthority())
{
// 如果不是服务器端,调用服务器函数来设置动画状态
ServerSetAiming(b);
}

}

void UFShootingComponent::ServerSetAiming_Implementation(bool b)
{
bAnim = b; // 设置动画状态
}

这边HasAuthority其实不必要

在C调用Server RPC 是S执行,后调整bAnim数值

在S调用Server RPC 是S执行,里面的逻辑不会同步到All C,但这边 bAnim为复制变量可以同步All C

源码阅读笔记

BlasterCharacter

input(完结)

Input == SetupPlayerInputComponent

前后左右:

用欧拉角判断Vector

欧拉角:

pitch():俯仰,将物体绕X轴旋转(localRotationX)

yaw():航向,将物体绕Y轴旋转(localRotationY)

roll():横滚,将物体绕Z轴旋转(localRotationZ)

Jump( ):

蹲伏

CrouchButtonPressed ( )

用UE自带的Character重写

装备–EquipButtonPressed( )

真正逻辑在Combat中

瞄准

AimButtonPressed( )

AimButtonReleased( )

开火

FireButtonPressed( )
FireButtonReleased( )

换弹

ReloadButtonPressed( )

投掷

GrenadeButtonPressed( )

装备

EquipButtonPressed( )

HUD (完结)

  • OnRep_Health

    • PlayHitReactMontage( ) ==>详见动画 TODO
    • UpdateHUDHealth ==> BlasterPlayerController->SetHUDHealth ==> BlasterHUD
  • OnRep_Shield 盾

    • PlayHitReactMontage( ) ==>详见动画 TODO
    • UpdateHUDShield ==> BlasterPlayerController->SetHUDShield ==> BlasterHUD
  • UpdateHUDGrenade ==> BlasterPlayerController->SetHUDGrenades==> BlasterHUD

  • UpdateHUDAmmo弹药
    • BlasterPlayerController->SetHUDCarriedAmmo SetHUDWeaponAmmo ==> BlasterHUD

动画调用

PlayFireMontage( )

PlayReloadMontage( )

PlayElimMontage( )

PlayThrowGrenadeMontage ( )

PlaySwapMontage( )

PlayHitReactMontage( )

提供给Anim的接口

GetCombatState

IsLocallyReloading

IsAiming

CalculateSpeed

CalculateAO_Pitch

AimOffset

Charactor 生命周期

  • Elim( )死亡

    • DropOrDestroyWeapons( ) 丢弃武器

      • DropOrDestroyWeapon( )
        • Weapon Actor::Destroy( ) or Dropped( ) 结束
    • MulticastElim( ) –TODO 多播淘汰效果

      • StartDissolve UpdateDissolveMaterial
      • ElimTimerFinished
    • ServerLeaveGame 服务器离开游戏

  • Destroyed (Actor)

    • ElimBotComponent ==>DestroyComponent (SceneCompent)

    • Combat 销毁武器

init

  • GetLifetimeReplicatedProps (ACharacter) 注册属性复制

  • SpawnDefaultWeapon 获得默认武器

    • Combat->EquipWeapon(StartingWEeapon);

  • PostInitializeComponents (ACharacter) 初始化组件

    • Combat
    • Buff
    • LagCompensation
  • OnPlayerStateInitialized 初始化PlayerState
    • SetSpawnPoint
    • SetTeamColor
  • ReceiveDamage TODO 造成伤害

Tick

RotateInPlace

HideCameraIfCharacterClose

PollInit

prop and rep

GetTeam

SetTeamColor

SetOverlappingWeapon

OnRep_OverlappingWeapon

Flag

SetHoldingTheFlag

IsHoldingTheFlag

UCombatComponent

  • 瞄准 SetAiming
    • RPC
      • 服务器权威 瞄准移动速度降低
    • 瞄准移动速度降低 设置原生Charactor MaxWalkSpeed
    • 狙击枪开镜 UI
    • 预测(网络延迟)
      • SetAiming 记录本地玩家的瞄准按钮状态
      • OnRep_Aiming

预测

在虚幻引擎的多人游戏开发中,OnRep_Aiming(复制回调函数)与SetAiming(本地预测函数)结合使用,能够有效缓解网络延迟带来的瞄准状态同步问题。其核心原理在于客户端预测 + 服务器权威验证 + 延迟补偿修正的协同机制,具体分析如下:


一、网络延迟问题的本质

网络延迟导致客户端操作(如按下瞄准键)与服务器状态同步之间存在时间差。若仅依赖服务器同步(如通过RPC或变量复制),玩家会感受到明显的操作延迟(如按下右键后角色需等待数百毫秒才举枪)。这种延迟会严重破坏实时游戏的体验。


二、SetAiming的作用:本地预测与快速响应

  1. 即时状态更新

    当玩家按下瞄准键时,SetAiming立即更新本地客户端的 bAiming状态(如 bAiming = true),并调整角色移动速度、显示狙击镜UI等视觉效果。这实现了操作的“零延迟”响应。

  2. 发起服务器同步

    通过 ServerSetAimingRPC 将操作请求发送给服务器,要求服务器权威验证并更新状态。

  3. 预测执行

    客户端在等待服务器确认期间,假定操作会被服务器接受,提前执行相关逻辑(如移动速度变化)。这种预测机制是减少感知延迟的核心。


三、OnRep_Aiming的作用:延迟补偿与状态修正

  1. 响应服务器同步

    OnRep_Aiming是虚幻引擎的复制回调函数,当服务器通过变量复制将 bAiming的新值同步到客户端时,该函数自动触发。

  2. 覆盖复制的延迟状态

    若网络延迟较高,服务器同步的 bAiming值可能过时(例如玩家已松开右键,但服务器仍同步“瞄准中”状态)。此时 OnRep_Aiming会用本地记录的 bAimButtonPressed(实时记录按键状态)覆盖复制的值,确保客户端状态与玩家实时操作一致。

    1
    2
    3
    4
    5
    void UCombatComponent::OnRep_Aiming() {
    if (Character && Character->IsLocallyControlled()) {
    bAiming = bAimButtonPressed; // 用本地按键状态修正复制值
    }
    }
  3. 避免状态回滚抖动

    若未使用此机制,客户端会在收到旧状态时错误地回退到过时状态(如突然收起狙击镜再重新举起),导致画面抖动。OnRep_Aiming的修正逻辑避免了这一问题。


四、协作机制:解决延迟的核心逻辑

  1. 预测-验证-修正流程

    • 客户端预测SetAiming立即执行本地操作并通知服务器。
    • 服务器验证:服务器通过 ServerSetAiming更新权威状态并复制到所有客户端。
    • 客户端修正OnRep_Aiming在收到复制值时,用本地实时状态覆盖延迟的复制值。
  2. 权限隔离

    • 仅对本地控制角色(IsLocallyControlled())启用修正逻辑,远程角色直接使用复制值,避免逻辑冲突。
  3. 降低感知延迟

    玩家始终看到与自身操作一致的状态(即使网络延迟存在),而服务器仍保持状态权威性,防止作弊。


五、对比传统方案的优势

方案 响应速度 状态一致性 抗延迟能力
纯RPC同步 低(需等待RPC往返)
纯变量复制 低(依赖复制周期) 中(可能过时)
SetAiming+OnRep (本地预测) (延迟补偿)

六、适用场景与局限性

  • 适用场景:需快速响应的操作(如瞄准、射击、跳跃)。
  • 局限性
    • 需严格区分本地/远程角色逻辑。
    • 高丢包率下预测可能频繁出错(需结合重传机制)。
    • 复杂状态(如物理模拟)需更精细的预测回滚算法。

总结

OnRep_AimingSetAiming的协作本质是客户端预测执行 + 服务器权威验证 + 复制延迟补偿

  1. SetAiming实现本地操作的即时反馈(预测);

  2. OnRep_Aiming在服务器状态同步后修正网络延迟导致的误差(补偿)。

    这一机制在保持服务器权威性的前提下,最大限度消除了玩家感知到的操作延迟,是实时多人游戏中网络同步设计的核心模式。

TODO

开火 FireButtonPressed

开火流程(以 UCombatComponent::Fire() 为核心)如下:

  1. 按下射击按钮
    玩家按下射击按钮,调用 FireButtonPressed(true),进而调用 Fire()

  2. 条件检查
    Fire() 内部通过 CanFire() 检查是否满足开火条件(如弹药、冷却、状态等)。

  3. 锁定射击
    满足条件后,设置 bCanFire = false,防止连发。

  4. 分类型射击
    根据当前武器类型(抛射物、射线、霰弹枪)分别调用 FireProjectileWeapon()FireHitScanWeapon()FireShotgun()

  5. 本地预测
    如果是客户端(非服务器),先本地执行 LocalFire()ShotgunLocalFire(),立即播放动画和武器效果,实现射击预测。

  6. 服务器同步
    客户端通过 RPC(如 ServerFire()ServerShotgunFire())通知服务器进行权威判定。

  7. 多播同步
    服务器通过 MulticastFire()MulticastShotgunFire() 广播所有客户端,确保所有玩家看到一致的射击效果。

  8. 冷却计时
    调用 StartFireTimer() 启动射击冷却,冷却结束后允许再次射击。

整个流程保证了射击的即时反馈(本地预测)和网络同步(服务器权威+多播)。

换弹Reload

换弹(Reload)流程如下:

  1. 触发换弹
    玩家点击换弹按钮,调用 Reload()

  2. 条件检查
    Reload() 检查携带弹药、武器状态、是否满弹、是否正在本地换弹等,只有全部满足才继续。

  3. 本地与服务器同步

    • 客户端调用 ServerReload(),请求服务器同步换弹。
    • 客户端本地执行 HandleReload(),播放换弹动画,并设置 bLocallyReloading = true
  4. 服务器处理

    • ServerReload_Implementation() 设置 CombatState = ECS_Reloading
    • 非本地控制角色时,服务器也调用 HandleReload() 播放动画。
  5. 动画结束/换弹完成

    • 动画结束后,调用 FinishReloading()
    • 服务器端:更新弹药数据,CombatState 设为 ECS_Unoccuiped
    • 客户端:重置 bLocallyReloading,如射击按钮仍按下则自动开火。
  6. 弹药同步

    • UpdateAmmoValues() 更新弹匣和携带弹药,并同步到 HUD。

整个流程保证了本地即时反馈、服务器权威同步和弹药数据一致性。

投掷 ThrowGrenade

投掷手雷(ThrowGrenade)流程如下:

  1. 触发投掷
    玩家点击投掷手雷按钮,调用 ThrowGrenade()

  2. 条件检查
    检查手雷数量、战斗状态和武器有效性,只有全部满足才继续。

  3. 本地动画与显示

    • 设置 CombatState = ECS_ThrowingGrenade
    • 本地控制角色播放投掷动画,切换武器到左手,显示手雷模型。
  4. 服务器同步

    • 非服务器端调用 ServerThrowGrenade(),请求服务器同步投掷。
    • 服务器端减少手雷数量,更新HUD,播放动画和显示。
  5. 动画结束/投掷完成

    • 动画结束后,调用 ThrowGrenadeFinished(),恢复战斗状态,武器回到右手。
  6. 手雷发射

    • 调用 LaunchGrenade(),本地显示手雷消失,并通过 ServerLaunchGrenade() 在服务器生成手雷抛射物。

整个流程保证了本地即时反馈、服务器权威同步和手雷数量一致性。

ULagCompensationComponent

ULagCompensationComponent 主要用于延迟补偿(Lag Compensation),确保网络射击命中判定的公平性。各函数作用如下:

  • 构造函数/BeginPlay/TickComponent
    初始化组件,Tick 时保存每帧的碰撞盒快照(FramePackage)。

  • InterpBetweenFrames
    在两帧之间插值,重建某一时刻角色的碰撞盒状态。

  • ConfirmHit/ProjectileConfirmHit/ShotgunConfirmHit
    用于命中判定:

    • ConfirmHit:命中检测(如射线武器),优先检测头部,再检测身体。
    • ProjectileConfirmHit:用于投射物武器,预测弹道后检测碰撞。
    • ShotgunConfirmHit:霰弹枪多目标多弹丸命中检测,分别统计头部和身体命中数。
  • CacheBoxPositions/MoveBoxes/ResetHitBoxes/EnableCharacterMeshCollision

    • CacheBoxPositions:缓存角色当前所有碰撞盒的位置、旋转、大小。
    • MoveBoxes:将角色碰撞盒移动到指定快照状态。
    • ResetHitBoxes:恢复碰撞盒到原始状态并关闭碰撞。
    • EnableCharacterMeshCollision:设置角色网格体的碰撞开关。
  • ShowFramePackage
    用于调试,显示某一帧的所有碰撞盒。

  • ServerSideRewind/ProjectileServerSideRewind/ShotgunServerSideRewind
    服务器端回溯到指定时间点,进行命中判定。

  • GetFrameToCheck
    根据时间戳,从历史帧中获取或插值出需要判定的帧。

  • ServerScoreRequest_Implementation/ProjectileServerScoreRequest_Implementation/ShotgunServerScoreRequest_Implementation
    服务器端处理命中结果,应用伤害。

  • SaveFramePackageServer/SaveFramePackage
    保存当前帧的碰撞盒快照到历史记录中。

Weapon

Weapon.cpp

1
2
3
WeaponMesh->SetCustomDepthStencilValue(CUSTOM_DEPTH_BLUE);
WeaponMesh->MarkRenderStateDirty(); // 强制更新渲染状态
EnableCustomDepth(true); // 启用轮廓效果

Bloom效果

霰弹枪与步枪区别

在你的代码中,霰弹枪(Shotgun)和步枪(如步枪/步枪类武器,AssaultRifle)主要区别体现在以下几个方面:

  1. 武器类型区分
    通过 EWeaponTypeEFireType 枚举区分,霰弹枪通常是 EWT_Shotgun,步枪是 EWT_AssaultRifle,它们的 FireType 也不同:
  • 步枪:EFireType::EFT_HitScan(射线检测,单发命中)
  • 霰弹枪:EFireType::EFT_Shotgun(一次发射多颗弹丸,散射)
  1. 开火逻辑
  • 步枪调用 FireHitScanWeapon(),只处理一个命中点,单发或连发。
  • 霰弹枪调用 FireShotgun(),会生成多个命中点(弹丸散射),并用 Shotgun->ShotgunTraceEndWithScatter() 计算所有弹丸的落点,Shotgun->FireShotgun() 处理多弹丸伤害。
  1. 换弹逻辑
  • 步枪换弹一次性填满弹匣,调用 UpdateAmmoValues()
  • 霰弹枪采用“逐发装填”,每次装填一发,调用 UpdateShotgunAmmoValues(),并在弹匣满或无子弹时跳转到装填动画结束。
  1. 射击动画与同步
  • 步枪射击只需同步一次命中点。
  • 霰弹枪射击需要同步所有弹丸的命中点数组(TArray<FVector_NetQuantize>)。
  1. CanFire 特殊处理
    霰弹枪在装填过程中允许中断射击(即装填时也能开火),步枪则不允许。

总结

  • 步枪是单发/连发、单点命中,射线检测,换弹一次性填满。
  • 霰弹枪是多弹丸散射、逐发装填,射击和同步都处理多个命中点,装填时可中断射击。

这些差异体现在武器类型、开火方式、换弹方式和网络同步等核心逻辑上。

投掷武器特点