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++ 类中定义三个函数,一个可复制属性和属性的修改通知函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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();

实现这几个函数

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
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 几个函数进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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.p![RunDedicatedServer.png](https://i.loli.net/2019/05/30/5cef88118028475844.png)ng

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 中重写这些函数的实现,方法就是直接声明一个同名的函数,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- 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 输出为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 的按键事件中调用这三个网络事件

1
2
3
4
5
6
7
8
9
10
11
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 输出为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 中重写这些事件,同样只要声明一个同名的函数就行了,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- 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 输出为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 中了,并且执行规则一致