Bluelua 支持在 lua 中重写 UE4 网络事件

最近抽空把 Bluelua 计划的最后一部分功能内容做完了,就是在 lua 中重载 UE4 中的网络事件,这样就可以直接在 lua 中重写网络相关的逻辑了。还有就是将之前重载纯蓝图函数和事件几个崩溃修复了。网络相关的重写示例在 BlueluaDemoNetTest 文件夹中

UE4 中网络事件分两种,一种是 C++ 中的网络事件,就是在 UFUNCTION 中带上 Server/NetMulticast/Client 关键字,另一种是在蓝图中,创建一个 Custom Event,然后在这个事件的复制属性中选择 Run On Server/Multicast/Run on owning Client,如图

BPNetEvent.png

这两种是互相独立的,也就是 C++ 中的 Server/NetMulticast/Client 函数是无法在蓝图中进行重写,所以如果有这样的需求就需要在 C++ 的 Server/NetMulticast/Client 函数中去调用其它 BlueprintNativeEvent/BlueprintImplementable 函数,将这个事件抛到蓝图中,略显麻烦。Bluelua 中就不用这么麻烦了,现在可以直接在 lua 中分别重写这两类网络事件

重写 C++ 网络事件

首先在 ANetCharacter 的 C++ 类中定义三个函数,一个可复制属性和属性的修改通知函数

UFUNCTION(Unreliable, Server, WithValidation)
void TestNativeServerFunction();

UFUNCTION(Unreliable, NetMulticast)
void TestNativeNetMulticastFunction();

UFUNCTION(Unreliable, Client)
void TestNativeClientFunction();

UPROPERTY(ReplicatedUsing=OnRep_Counter)
int32 Counter;

UFUNCTION(BlueprintNativeEvent)
void OnRep_Counter();

实现这几个函数

void ANetCharacter::TestNativeClientFunction_Implementation()
{
    UE_LOG(LogTemp, Display, TEXT("%sTestNativeClientFunction get called"), *GetPrefix(this));
}

void ANetCharacter::TestNativeNetMulticastFunction_Implementation()
{
    UE_LOG(LogTemp, Display, TEXT("%sTestNativeNetMulticastFunction get called"), *GetPrefix(this));
}

void ANetCharacter::TestNativeServerFunction_Implementation()
{
    UE_LOG(LogTemp, Display, TEXT("%sTestNativeServerFunction get called"), *GetPrefix(this));

    TestNativeNetMulticastFunction(); // will run on local and remote
    TestNativeClientFunction(); // will run on remote
}

bool ANetCharacter::TestNativeServerFunction_Validate()
{
    return true;
}

void ANetCharacter::OnRep_Counter_Implementation()
{
    UE_LOG(LogTemp, Display, TEXT("%sNative OnRep_Counter: %d"), *GetPrefix(this), Counter);
}

// 在服务器的 Tick 中每隔 1 秒递增 Counter
void ANetCharacter::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    if (Role == ROLE_Authority)
    {
        static float UpdateTime = 0;
        UpdateTime += DeltaTime;

        if (UpdateTime > 1.f)
        {
            ++Counter;
            UpdateTime = 0.f;
        }
    }
}

在 ANetCharacter 的子类 lua 中绑定一个 P 键输入事件,按下 P 键后从客户端调用 TestNativeServerFunction/TestNativeNetMulticastFunction/TestNativeClientFunction 几个函数进行测试

function m:OnSetupPlayerInputComponent()
    local BlueluaLibrary = LoadClass('BlueluaLibrary')

    local EInputEvent = {
        IE_Pressed = 0,
        IE_Released = 1,
        IE_Repeat = 2,
        IE_DoubleClick = 3,
        IE_Axis = 4,
        IE_MAX = 5,
    }

    -- Press P key to start ent test
    BlueluaLibrary:BindKeyAction(Super, { Key = { KeyName = 'P' } }, EInputEvent.IE_Pressed, true, false, CreateFunctionDelegate(Super, self, self.OnKeyPressed))
end

function m:OnKeyPressed()
    -- test net event in c++
    Super:TestNativeClientFunction() -- will run on current client
    Super:TestNativeNetMulticastFunction() -- will run on current client
    Super:TestNativeServerFunction() -- will run on remote server
end

在测试前需要在编辑器中勾选 Run Dedicated Server,如图

RunDedicatedServer.png

在没有重写的情况下,调用的是 C++ 中的实现,输出的 log 为

LogTemp: Display: Client 1: Native OnRep_Counter: 1
LogTemp: Display: Client 1: Native OnRep_Counter: 2
LogTemp: Display: Client 1: Native OnRep_Counter: 3
LogTemp: Display: Client 1: Native OnRep_Counter: 4
LogTemp: Display: Client 1: Native OnRep_Counter: 5
LogTemp: Display: Client 1: Native OnRep_Counter: 6
LogTemp: Display: Client 1: Native OnRep_Counter: 7
LogTemp: Display: Client 1: TestNativeClientFunction get called
LogTemp: Display: Client 1: TestNativeNetMulticastFunction get called
LogTemp: Display: Server: TestNativeServerFunction get called
LogTemp: Display: Server: TestNativeNetMulticastFunction get called
LogTemp: Display: Client 1: TestNativeClientFunction get called
LogTemp: Display: Client 1: TestNativeNetMulticastFunction get called
LogTemp: Display: Client 1: Native OnRep_Counter: 8
LogTemp: Display: Client 1: Native OnRep_Counter: 9
LogTemp: Display: Client 1: Native OnRep_Counter: 10

从 log 中可以看出,当客户端调用 Client/NetMulticast 函数时,是在本地执行的,当调用 Server 函数时,会在服务器执行。服务器上调用 NetMulticast 函数会在服务器本地和客户端上执行,调用 Client 函数会在对应的主控(Autonomous)客户端上执行

现在在 lua 中重写这些函数的实现,方法就是直接声明一个同名的函数,如下

-- override Server replicated event in c++
function m:TestNativeClientFunction()
    print('TestNativeClientFunction get called')
end

-- override NetMulticast replicated event in c++
function m:TestNativeNetMulticastFunction()
    print('TestNativeNetMulticastFunction get called')
end

-- override Client replicated event in c++
function m:TestNativeServerFunction()
    print('TestNativeServerFunction get called')
    Super:TestNativeNetMulticastFunction() -- will run on local server and remote client
    Super:TestNativeClientFunction() -- will run on remote client
end

-- override property replicated event in c++
function m:OnRep_Counter()
    print('OnRep_Counter:', Super.Counter)
end

按 P 键进行测试,得到得 log 输出为

LogBluelua: Display: Client 1: Lua log: OnRep_Counter:   1
LogBluelua: Display: Client 1: Lua log: OnRep_Counter:   2
LogBluelua: Display: Client 1: Lua log: OnRep_Counter:   3
LogBluelua: Display: Client 1: Lua log: OnRep_Counter:   4
LogBluelua: Display: Client 1: Lua log: OnRep_Counter:   5
LogBluelua: Display: Client 1: Lua log: TestNativeClientFunction get called
LogBluelua: Display: Client 1: Lua log: TestNativeNetMulticastFunction get called
LogBluelua: Display: Server: Lua log: TestNativeServerFunction get called
LogBluelua: Display: Server: Lua log: TestNativeNetMulticastFunction get called
LogBluelua: Display: Client 1: Lua log: TestNativeClientFunction get called
LogBluelua: Display: Client 1: Lua log: TestNativeNetMulticastFunction get called
LogBluelua: Display: Client 1: Lua log: OnRep_Counter:   6
LogBluelua: Display: Client 1: Lua log: OnRep_Counter:   7
LogBluelua: Display: Client 1: Lua log: OnRep_Counter:   8
LogBluelua: Display: Client 1: Lua log: OnRep_Counter:   9
LogBluelua: Display: Client 1: Lua log: OnRep_Counter:   10

从 log 中可以看出,所有的网络事件都正确调到 lua 中重写的实现中了,并且执行一致

重写蓝图网络事件

同样在 NetCharacter 蓝图中创建三个 Custom Event,并分别选择 Run On Server/Multicast/Run on owning Client。创建一个 BPCounter 属性,选择 RepNotify,之后蓝图中会自动创建一个函数 OnRep_BPCounter,在这些函数中分别打印一句 log,如图

BPCustomEvent.png

OnRep_BPCounter.png

同样在 lua 的按键事件中调用这三个网络事件

function m:OnKeyPressed()
    -- test net event in c++
    --Super:TestNativeClientFunction() -- will run on current client
    --Super:TestNativeNetMulticastFunction() -- will run on current client
    --Super:TestNativeServerFunction() -- will run on remote server

    -- test net event in blueprint
    Super:TestBPClientFunction() -- will run on current client
    Super:TestBPNetMulticastFunction() -- will run on current client
    Super:TestBPServerFunction() -- will run on remote server
end

在 lua 没有重载的情况下的 log 输出为

LogBlueprintUserMessages: [NetCharacter_C_0] Server: BP OnRep_BPCounter: 1
LogBlueprintUserMessages: [NetCharacter_C_0] Client 1: BP OnRep_BPCounter: 1
LogBlueprintUserMessages: [NetCharacter_C_0] Server: BP OnRep_BPCounter: 2
LogBlueprintUserMessages: [NetCharacter_C_0] Client 1: BP OnRep_BPCounter: 2
LogBlueprintUserMessages: [NetCharacter_C_0] Server: BP OnRep_BPCounter: 3
LogBlueprintUserMessages: [NetCharacter_C_0] Client 1: BP OnRep_BPCounter: 3
LogBlueprintUserMessages: [NetCharacter_C_0] Server: BP OnRep_BPCounter: 4
LogBlueprintUserMessages: [NetCharacter_C_0] Client 1: BP OnRep_BPCounter: 4
LogBlueprintUserMessages: [NetCharacter_C_0] Server: BP OnRep_BPCounter: 5
LogBlueprintUserMessages: [NetCharacter_C_0] Client 1: BP OnRep_BPCounter: 5
LogBlueprintUserMessages: [NetCharacter_C_0] Client 1: TestBPClientFunction get called
LogBlueprintUserMessages: [NetCharacter_C_0] Client 1: TestBPNetMulticastFunction get called
LogBlueprintUserMessages: [NetCharacter_C_0] Server: TestBPServerFunction get called
LogBlueprintUserMessages: [NetCharacter_C_0] Server: TestBPNetMulticastFunction get called
LogBlueprintUserMessages: [NetCharacter_C_0] Client 1: TestBPClientFunction get called
LogBlueprintUserMessages: [NetCharacter_C_0] Client 1: TestBPNetMulticastFunction get called
LogBlueprintUserMessages: [NetCharacter_C_0] Server: BP OnRep_BPCounter: 6
LogBlueprintUserMessages: [NetCharacter_C_0] Client 1: BP OnRep_BPCounter: 6

从 log 中可以看出,蓝图的 Run On Server/Multicast/Run on owning Client 事件和 C++ 的 Server/NetMulticast/Client 函数的执行规则是一致的,唯一的区别是可复制属性的 RepNotify 和 ReplicatedUsing,RepNotify 会在服务器本地也调用 OnRep_BPCounter 函数,而 ReplicatedUsing 不会,这一点需要注意

现在在 lua 中重写这些事件,同样只要声明一个同名的函数就行了,如下

-- override Server replicated event in blueprint
function m:TestBPClientFunction()
    print('TestBPClientFunction get called')
end

-- override NetMulticast replicated event in blueprint
function m:TestBPNetMulticastFunction()
    print('TestBPNetMulticastFunction get called')
end

-- override Client replicated event in blueprint
function m:TestBPServerFunction()
    print('TestBPServerFunction get called')
    Super:TestBPNetMulticastFunction() -- will run on local server and remote client
    Super:TestBPClientFunction() -- will run on remote client
end

-- override property replicated event in blueprint
function m:OnRep_BPCounter()
    print('OnRep_BPCounter:', Super.BPCounter)
end

重新按 P 键进行测试,得到得 log 输出为

LogBluelua: Display: Server: Lua log: OnRep_BPCounter:   1
LogBluelua: Display: Client 1: Lua log: OnRep_BPCounter: 1
LogBluelua: Display: Server: Lua log: OnRep_BPCounter:   2
LogBluelua: Display: Client 1: Lua log: OnRep_BPCounter: 2
LogBluelua: Display: Server: Lua log: OnRep_BPCounter:   3
LogBluelua: Display: Client 1: Lua log: OnRep_BPCounter: 3
LogBluelua: Display: Server: Lua log: OnRep_BPCounter:   4
LogBluelua: Display: Client 1: Lua log: OnRep_BPCounter: 4
LogBluelua: Display: Server: Lua log: OnRep_BPCounter:   5
LogBluelua: Display: Client 1: Lua log: OnRep_BPCounter: 5
LogBluelua: Display: Client 1: Lua log: TestBPClientFunction get called
LogBluelua: Display: Client 1: Lua log: TestBPNetMulticastFunction get called
LogBluelua: Display: Server: Lua log: TestBPServerFunction get called
LogBluelua: Display: Server: Lua log: TestBPNetMulticastFunction get called
LogBluelua: Display: Client 1: Lua log: TestBPClientFunction get called
LogBluelua: Display: Client 1: Lua log: TestBPNetMulticastFunction get called

可以看出,蓝图的网络事件也调到了 lua 中了,并且执行规则一致