《UE5_C++多人TPS完整教程》学习笔记62 ——《P63 多人游戏中的开火特效(Fire Effects in Multiplayer)》


由于笔者时间和精力有限,在本文发布后将停更一段时间。


本文为B站系列教学视频 《UE5_C++多人TPS完整教程》 ------ 《P63 多人游戏中的开火特效(Fire Effects in Multiplayer)》 的学习笔记,该系列教学视频为计算机工程师、程序员、游戏开发者、作家(Engineer, Programmer, Game Developer, Author)Stephen Ulibarri 发布在 Udemy 上的课程 《Unreal Engine 5 C++ Multiplayer Shooter》 的中文字幕翻译版,UP主(也是译者)为 游戏引擎能吃么


文章目录

  • [P63 多人游戏中的开火特效(Fire Effects in Multiplayer)](#P63 多人游戏中的开火特效(Fire Effects in Multiplayer))
  • [63.1 创建服务器 RPC 函数](#63.1 创建服务器 RPC 函数)
  • [63.2 创建多播 RPC 函数](#63.2 创建多播 RPC 函数)
  • [63.3 Summary](#63.3 Summary)

P63 多人游戏中的开火特效(Fire Effects in Multiplayer)

本节课我们将在多人游戏中实现武器开火特效动画的网络同步,这涉及创建在客户端上调用在服务器上执行的服务器 RPC 函数以及在服务器上调用并在所有客户端上执行的多播 RPC 函数。


63.1 创建服务器 RPC 函数

  1. 回顾虚幻引擎官方文档《虚幻引擎中的 RPC》中关于 RPC 函数的介绍,可以知道在服务器上调用的 RPC 函数,它只在服务器上执行;在客户端上调用的 RPC 函数,也会在服务器上执行。因此,我们首先要使用服务器 RPC 函数通知服务器我们正在尝试使用武器开火。

  2. 在 "CombatComponent.h" 中声明可靠的服务器 RPC 函数 "ServerFire()",可靠 RPC 应被谨慎、有节制地使用,这里由于武器开火在枪战中非常重要,因此需要使用可靠 RPC 函数保证网络同步。接着,在 "CombatComponent.cpp" 完成 "ServerFire()" 实施函数的定义,根据人物角色的瞄准状态,播放开火蒙太奇片段以及已装备武器的开火特效动画;修改 "ireButtonPressed()" 函数的定义,如果开火键被按下则调用 "ServerFire()"。

    cpp 复制代码
    /*** CombatComponent.h ***/
    
    ...
    
    UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
    class BLASTER_API UCombatComponent : public UActorComponent
    {
        GENERATED_BODY()
    
        ...
    
    protected:
    
        ...
    
        UFUNCTION()
        void OnRep_EquippedWeapon();				// 设置人物角色朝向的 Repnotify 函数
    
        void FireButtonPressed(bool bPressed);		// 根据人物角色是否在开火,设置 bIsFireButtonPressed
    
        /* P63 多人游戏中的开火特效(Fire Effects in Multiplayer)*/
        UFUNCTION(Server, Reliable)
        void ServerFire();
        /* P63 多人游戏中的开火特效(Fire Effects in Multiplayer)*/
    
        ...
    
    };
    cpp 复制代码
    /*** CombatComponent.cpp ***/
    
    ...
    
    // 根据人物角色是否在开火,设置 bIsFireButtonPressed
    void UCombatComponent::FireButtonPressed(bool bPressed)
    {
        bIsFireButtonPressed = bPressed;			// 设置 bIsFireButtonPressed
    
        // if (EquippedWeapon == nullptr) return;	// 若没有装备武器,退出
        //
        // if (Character && bIsFireButtonPressed)	// 如果人物角色在开火
        // {
        // 	Character->PlayFireMontage(bAiming);	// 根据 bAiming 的值播放开火蒙太奇片段
        // 	
        // 	EquippedWeapon->Fire();					// 已装备的武器播放开火特效动画
        // }
        
        /* P63 多人游戏中的开火特效(Fire Effects in Multiplayer)*/
        if (bIsFireButtonPressed)					// 如果开火键按下
        {
            ServerFire();							// 调用武器开火 RPC 实施函数
        }
        /* P63 多人游戏中的开火特效(Fire Effects in Multiplayer)*/
    }
    
    /* P63 多人游戏中的开火特效(Fire Effects in Multiplayer)*/
    // 武器开火 RPC 实施函数。根据人物角色的瞄准状态,播放开火蒙太奇片段以及已装备武器的开火特效动画
    void UCombatComponent::ServerFire_Implementation()
    {
        if (EquippedWeapon == nullptr) return;		// 若没有装备武器,退出
    
        if (Character)		                        
        {
            Character->PlayFireMontage(bAiming);	// 根据 bAiming 的值播放开火蒙太奇片段
            
            EquippedWeapon->Fire();					// 已装备的武器播放开火特效动画
        }
    }
    /* P63 多人游戏中的开火特效(Fire Effects in Multiplayer)*/
    
    ...
  3. 编译后进行测试,如果我们操控其中一个客户端上的人物角色拾取武器进行开火,可以在服务器上看到武器开火特效动画,但在这个客户端和另一个客户端上却看不到,这也验证了在服务器上调用的 RPC 函数只在服务器上执行。如果我们操控服务器上的人物角色拾取武器进行开火,客户端上还是看不到武器开火特效动画。


63.2 创建多播 RPC 函数

  1. 为了解决上述问题,我们可能会想到对 "bFireButtonPressed" 进行变量复制,因为这是最简单的方法,如果我们在服务器上设置 "bFireButtonPressed" 为真,那么它在客户端上也将会被设置为真。

  2. 但是,我们并不能那么做,因为在后续课程中我们将引入自动化武器,当我们按住开火键时,自动化武器可以持续开火,"bFireButtonPressed" 的布尔值为真并保持一段时间;换句话说,当我们多次开火时,"bFireButtonPressed" 的布尔值可能不会改变,而变量复制的工作方式是仅在可复制变量在更改时才会进行复制,即 "bFireButtonPressed" 的布尔值必须从真变到假或从假变到真时才能进行变量复制,这时候我们就需要另一种方式告诉客户端正在发生的事情,那就是使用 多播 RPC 函数(Multicast RPC)。

  3. 从虚幻引擎官方文档可以知道,如果我们在服务器上调用多播 RPC 函数,它将在服务器和所有客户端上执行;而如果我们在客户端上调用,它只会在调用它的客户端上执行,显得有些无用(Kind of worthless)。因此,多播 RPC 函数更多用于在服务器上调用,向所有客户端广播,告诉它们执行一系列特定的指令(Execute a particular set of instructions)。

    要求和注意事项

    您必须满足一些要求才能充分发挥 RPC 的作用:

    • 它们必须从 Actor 上调用。
    • Actor 必须被复制。
    • 如果 RPC 是从服务器调用并在客户端上执行,则只有实际拥有这个 Actor 的客户端才会执行函数。
    • 如果 RPC 是从客户端调用并在服务器上执行,客户端就必须拥有调用 RPC 的 Actor。

    多播 RPC 则是个例外:

    • 如果它们是从服务器调用,服务器将在本地和所有已连接的客户端上执行它们。
    • 如果它们是从客户端调用,则只在本地而非服务器上执行。

    现在,我们有了一个简单的多播事件限制机制:在特定 Actor 的网络更新期内,多播函数将不会复制两次以上。按长期计划,我们会对此进行改善,同时更好的支持跨通道流量管理与限制。
    从服务器调用的 RPC

    Actor 所有权 未复制 NetMulticast Server Client
    Client-owned actor 在服务器上运行 在服务器和所有客户端上运行 在服务器上运行 在 actor 的所属客户端上运行
    Server-owned actor 在服务器上运行 在服务器和所有客户端上运行 在服务器上运行 在服务器上运行
    Unowned actor 在服务器上运行 在服务器和所有客户端上运行 在服务器上运行 在服务器上运行

    从客户端调用的 RPC

    Actor 所有权 未复制 NetMulticast Server Client
    Owned by invoking client 在执行调用的客户端上运行 在执行调用的客户端上运行 在服务器上运行 在执行调用的客户端上运行
    Owned by a different client 在执行调用的客户端上运行 在执行调用的客户端上运行 丢弃 在执行调用的客户端上运行
    Server-owned actor 在执行调用的客户端上运行 在执行调用的客户端上运行 丢弃 在执行调用的客户端上运行
    Unowned actor 在执行调用的客户端上运行 在执行调用的客户端上运行 丢弃 在执行调用的客户端上运行

    ------ 虚幻引擎官方文档《虚幻引擎中的 RPC》

  4. 在 "CombatComponent.h" 中使用带有 "NetMulticast" 和 "Reliable" 关键字的 "UFUNCTION" 宏声明可靠多播 RPC 函数 "MulticastFire()"。接着,在 "CombatComponent.cpp" 完成 "MulticastFire()" 实施函数的定义,根据人物角色的瞄准状态,播放开火蒙太奇片段以及已装备武器的开火特效动画;修改 "ServerFire()" 实施函数的定义,确保当它被调用时调用 "MulticastFire()" 进行多播。

    cpp 复制代码
    /*** CombatComponent.h ***/
    
    ...
    
    UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
    class BLASTER_API UCombatComponent : public UActorComponent
    {
        GENERATED_BODY()
    
        ...
    
    protected:
    
        ...
    
        UFUNCTION()
        void OnRep_EquippedWeapon();				// 设置人物角色朝向的 Repnotify 函数
    
        void FireButtonPressed(bool bPressed);		// 根据人物角色是否在开火,设置 bIsFireButtonPressed
    
        /* P63 多人游戏中的开火特效(Fire Effects in Multiplayer)*/
        UFUNCTION(Server, Reliable)
        void ServerFire();
    
        UFUNCTION(NetMulticast, Reliable)
        void MulticastFire();
        /* P63 多人游戏中的开火特效(Fire Effects in Multiplayer)*/
    
        ...
    
    };
    cpp 复制代码
    /*** CombatComponent.cpp ***/
    
    ...
    
    // 根据人物角色是否在开火,设置 bIsFireButtonPressed
    void UCombatComponent::FireButtonPressed(bool bPressed)
    {
        bIsFireButtonPressed = bPressed;			// 设置 bIsFireButtonPressed
    
        // if (EquippedWeapon == nullptr) return;	// 若没有装备武器,退出
        //
        // if (Character && bIsFireButtonPressed)	// 如果人物角色在开火
        // {
        // 	Character->PlayFireMontage(bAiming);	// 根据 bAiming 的值播放开火蒙太奇片段
        // 	
        // 	EquippedWeapon->Fire();					// 已装备的武器播放开火特效动画
        // }
        
        /* P63 多人游戏中的开火特效(Fire Effects in Multiplayer)*/
        if (bIsFireButtonPressed)					// 如果开火键按下
        {
            ServerFire();							// 调用武器开火 RPC 实施函数
        }
        /* P63 多人游戏中的开火特效(Fire Effects in Multiplayer)*/
    }
    
    
    
    /* P63 多人游戏中的开火特效(Fire Effects in Multiplayer)*/
    // 武器开火 RPC 实施函数,根据人物角色的瞄准状态,播放开火蒙太奇片段以及已装备武器的开火特效动画
    void UCombatComponent::ServerFire_Implementation()
    {
        // if (EquippedWeapon == nullptr) return;		// 若没有装备武器,退出
        //
        // if (Character)		// 如果人物角色在开火
        // {
        // 	Character->PlayFireMontage(bAiming);	// 根据 bAiming 的值播放开火蒙太奇片段
        // 	
        // 	EquippedWeapon->Fire();					// 已装备的武器播放开火特效动画
        // }
    
        MulticastFire();
    }
    
    void UCombatComponent::MulticastFire_Implementation()
    {
        if (EquippedWeapon == nullptr) return;		// 若没有装备武器,退出
    
        if (Character)		// 如果人物角色在开火
        {
            Character->PlayFireMontage(bAiming);	// 根据 bAiming 的值播放开火蒙太奇片段
            
            EquippedWeapon->Fire();					// 已装备的武器播放开火特效动画
        }
    }
    /* P63 多人游戏中的开火特效(Fire Effects in Multiplayer)*/
    
    ...
  5. 编译后进行测试,可以观察到无论是操控服务器还是客户端的人物角色进行开火,武器开火特效动画能够在所有机器上正确播放。

  6. 综上,我们知道多播 RPC 函数是如此容易实现,但是并不意味着我们可以一直使用它,因为我们每次调用 RPC 函数时都要通过网络发送接收数据,对于多人游戏来说,发送接收的数据当然越少越好,因此多播 RPC 函数仅适用于多人游戏中重要性极高的部分。


63.3 Summary

本节课我们成功实现了多人游戏中武器开火特效的网络同步功能。通过创建服务器 RPC 函数 "ServerFire()" 和多播 RPC 函数 "MulticastFire()",我们构建了一个完整的开火事件传播机制:当客户端按下开火键时,调用 "ServerFire()" 函数在服务器上执行,服务器随后通过 "MulticastFire()" 函数向所有客户端广播开火指令,确保每个客户端都能正确播放人物角色开火蒙太奇和武器开火特效动画。这种设计保证了无论玩家控制的是服务器还是客户端,所有玩家都能看到彼此的武器开火特效动画,同时我们也注意到多播 RPC 函数会向所有客户端发送数据,因此应谨慎使用,仅用于关键且必要的游戏事件。


相关推荐
liu****5 小时前
基于websocket的多用户网页五子棋(九)
服务器·网络·数据库·c++·websocket·网络协议·个人开发
liu****5 小时前
基于websocket的多用户网页五子棋(八)
服务器·前端·javascript·数据库·c++·websocket·个人开发
ajassi20005 小时前
开源 C++ QT QML 开发(十二)通讯--TCP客户端
c++·qt·开源
进击的圆儿5 小时前
【学习笔记05】C++11新特性学习总结(下)
c++·笔记·学习
Jayden_Ruan5 小时前
C++十进制转二进制
数据结构·c++·算法
小何好运暴富开心幸福6 小时前
C++之日期类的实现
开发语言·c++·git·bash
老赵的博客7 小时前
c++ 是静态编译语言
开发语言·c++
lixinnnn.8 小时前
贪心:火烧赤壁
数据结构·c++·算法
Predestination王瀞潞8 小时前
类的多态(Num020)
开发语言·c++