事件 #

事件是将数据发送到游戏服务器并创建要在服务器后台处理的数据的强大方式。您可以创建和接收可转发给第三方服务(如分析、广告、应用内购买等)的数据。

这对游戏工作室和开发人员来说有很多好处:

  • 它减少了客户端SDK的数量,从而缩减了游戏客户端;这对于提高FTUE和下载游戏的玩家数量非常重要;
  • 游戏客户端使用的各种第三方服务的网络流量更少;
  • 它大幅减少了集成时间和开发团队所花费的主动维护成本。

通过使用客户端或服务器端函数发出的事件来实现游戏分析或LiveOps是一个很好的用例。这些事件将在Nakama服务器后台处理。玩家的游戏体验受到最小的干扰,开发人员可以根据分析反馈优化和改进游戏。

在内部,该功能通过以下方式实现:高性能的循环缓冲器,用于存储所收到的事件,以及消费者(事件处理程序)工作池,用于确保事件处理程序无法跟上的情况下,所收到的大量事件不会使服务器超载。

生成事件 #

事件可以由游戏客户端发送至服务器,也可以在服务器上创建。一个事件有一个名称和一个属性映射,属性映射用更多信息装饰事件。

发送事件 #

使用事件API发送到服务器。

Client
1
curl -vvv -H "Authorization: Bearer $SESSION"  http://127.0.0.1:7350/v2/event -d '{"name": "my_event", "properties": {"my_key": "my_value"}}'
Client
1
yield(client.event_async(session, "my_event", {"my_key": "my_value"}), "completed")
Client
1
2
3
4
local external = true
local name = "my_event"
local properties = { my_key = "my_value" }
local result = nakama.event(client, external, name, properties)

Code snippet for this language .NET/Unity has not been found. Please choose another language to show equivalent examples.
Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language JavaScript/Cocos2d-js has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Java/Android has not been found. Please choose another language to show equivalent examples.
Code snippet for this language REST has not been found. Please choose another language to show equivalent examples.

创建事件 #

使用服务器端模块在您的Go代码中生成事件。

Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// import "github.com/heroiclabs/nakama-common/api"
// import "github.com/heroiclabs/nakama-common/runtime"

// ctx context.Context, nk runtime.NakamaModule
evt := &api.Event{
    Name:       "event_name"
    Properties: map[string]string{
       "my_key": "my_value",
    },
    External:   true,
}
if err := nk.Event(ctx, evt); err != nil {
    // Handle error.
}
Server
1
2
3
4
5
6
7
8
9
local nk = require("nakama")

local properties = {
  my_key = "my_value"
}
local timestamp = nk.time
local external = false

nk.event("event_name", properties, timestamp, external)
Server
1
2
3
4
5
let properties = {
  my_key: 'my_value'
};

nk.event('event_name', properties);

Code snippet for this language TypeScript has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.

如果需要从服务器的其他功能发送事件,还可以利用服务器运行中的钩子后。例如,当更新用户账户时,您可能需要发送要处理的事件。

Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func afterUpdateAccount(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, in *api.UpdateAccountRequest) error {
    evt := &api.Event{
        Name:       "account_updated",
        Properties: map[string]string{},
        External:   false, // used to denote if the event was generated from the client
    }
    return nk.Event(context.Background(), evt)
}

func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, initializer runtime.Initializer) error {
    if err := initializer.RegisterAfterUpdateAccount(afterUpdateAccount); err != nil {
        return err
    }
    return nil
}
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
local nk = require("nakama")

local function after_update_account(ctx, payload)
  local properties = {
    my_key = "my_value"
  }
  local timestamp = nk.time
  local external = false  -- used to denote if the event was generated from the client
  return nk.event("event_name", properties, timestamp, external)
end

nk.register_req_after(after_update_account, "UpdateAccount")
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function InitModule(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, initializer: nkruntime.Initializer) {
  let afterUpdateAccount: nkruntime.AfterHookFunction<void, nkruntime.UserUpdateAccount> = function(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, data: void, payload: nkruntime.UserUpdateAccount) {
    let properties = {
      my_key: 'my_value'
    };

    nk.event('event_name', properties);
  }
  initializer.registerAfterUpdateAccount(afterUpdateAccount);
}

内置事件 #

服务器将产生内置事件,可以专门针对__SessionStart__和__SessionEnd__操作处理这些事件。当服务器启动新的套接字会话和结束会话时,会发生这些特殊事件。有关如何处理这些事件的信息见下文。

处理事件 #

可以使用在服务器启动时注册到运行时初始值设定项的函数来处理事件。事件的外部字段将被标记为true客户端生成的字段。

目前尚不支持通过Lua/TypeScript运行时函数处理事件。
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func processEvent(ctx context.Context, logger runtime.Logger, evt *api.Event) {
    switch evt.GetName() {
    case "account_updated":
        logger.Debug("process evt: %+v", evt)
        // Send event to an analytics service.
    default:
       logger.Error("unrecognised evt: %+v", evt)
    }
}

// initializer runtime.Initializer
if err := initializer.RegisterEvent(processEvent); err != nil {
    return err
}

Code snippet for this language TypeScript has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.

内置事件 #

内部生成的事件具有自己的注册功能。在服务器上打开新的套接字连接时,会创建SessionStart。

Server
1
2
3
4
5
6
7
8
func eventSessionStart(ctx context.Context, logger runtime.Logger, evt *api.Event) {
    logger.Debug("process event session start: %+v", evt)
}

// initializer runtime.Initializer
if err := initializer.RegisterEventSessionStart(eventSessionStart); err != nil {
    return err
}

Code snippet for this language TypeScript has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.

会话的套接字连接被关闭时,会创建SessionEnd事件。套接字可能因多种原因而关闭,但可以观察到它的反应。

Server
1
2
3
4
5
6
7
8
func eventSessionEnd(ctx context.Context, logger runtime.Logger, evt *api.Event) {
   logger.Debug("process event session end: %+v", evt)
}

// initializer runtime.Initializer
if err := initializer.RegisterEventSessionEnd(eventSessionEnd); err != nil {
   return err
}

Code snippet for this language TypeScript has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.

高级设置 #

为保护服务器的性能,事件处理子系统的设计限制分配给处理事件的资源。您可以使用这些配置设置调整分配给此子系统的资源。

配置键描述
runtime.event_queue_sizeint事件队列缓冲的大小。默认值为65536。
runtime.event_queue_workersint用于并发处理事件的工作线程的数量。默认值为8。

要查看其他配置设置,请查看这些文档。

示例 #

这是完整的插件示例,它处理SessionStart、SessionEnd以及用户账户更新时产生的事件。

Server
 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
package main

import (
    "context"
    "database/sql"
    "github.com/heroiclabs/nakama-common/api"
    "github.com/heroiclabs/nakama-common/runtime"
)

func afterUpdateAccount(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, in *api.UpdateAccountRequest) error {
    evt := &api.Event{
        Name:       "account_updated",
        Properties: map[string]string{},
        External:   false,
    }
    return nk.Event(context.Background(), evt)
}

func processEvent(ctx context.Context, logger runtime.Logger, evt *api.Event) {
    switch evt.GetName() {
    case "account_updated":
        logger.Debug("process evt: %+v", evt)
        // Send event to an analytics service.
    default:
        logger.Error("unrecognised evt: %+v", evt)
    }
}

func eventSessionEnd(ctx context.Context, logger runtime.Logger, evt *api.Event) {
   logger.Debug("process event session end: %+v", evt)
}

func eventSessionStart(ctx context.Context, logger runtime.Logger, evt *api.Event) {
    logger.Debug("process event session start: %+v", evt)
}

//noinspection GoUnusedExportedFunction
func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, initializer runtime.Initializer) error {
    if err := initializer.RegisterAfterUpdateAccount(afterUpdateAccount); err != nil {
       return err
    }
    if err := initializer.RegisterEvent(processEvent); err != nil {
        return err
    }
    if err := initializer.RegisterEventSessionEnd(eventSessionEnd); err != nil {
       return err
    }
    if err := initializer.RegisterEventSessionStart(eventSessionStart); err != nil {
       return err
    }
    logger.Info("Server loaded.")
    return nil
}

Code snippet for this language TypeScript has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.