Nakama Godot客户端指南 #

本客户端库指南将展示如何开发名为Sagi-shi(“Imposter”的日语名称)游戏的Nakama特定部分(无完整的游戏逻辑或UI),此游戏受Among Us(外部)启发,从而展示如何在Godot中使用Nakama的核心功能。

Sagi-shi gameplay screen
Sagi-shi gameplay

前提条件 #

开始之前,确保您已经:

安装 #

可通过以下方式下载客户端:

下载客户端压缩包后,将其内容提取到您的Godot项目文件夹。

通过Project -> Project Settings -> Autoload菜单添加Nakama.gd 单例模式(见 addons/com.heroiclabs.nakama/)。

创建客户端对象与服务器交互。

更新 #

Nakama Godot 更改日志中。

异步编程 #

许多Nakama API采用异步、非阻塞操作方式,在Godot SDK中可以作为异步方法使用。

Sagi-shi使用await运算符调用异步方法,不去阻止调用线程,从而提高游戏响应能力和效率。

1
yield(client.authenticate_device_async("<device_id>"), "completed")

阅读官方Godot文档中关于共常式和await的更多内容。

处理异常 #

网络编程需要额外的保护措施,防止出现连接和有效负载方面的问题。

Godot不支持异常处理,所以我们可以在发出异步请求时使用以下is_exception()方法:

1
2
3
4
5
var invalid_session = NakamaSession.new() # An empty session, which will cause an error
var invalid_account = yield(client.get_account_async(invalid_session), "completed")
print(invalid_account) # This will print the exception
if invalid_account.is_exception():
    print("We got an exception.")

对数据进行序列化和反序列化处理 #

通过网络发送和接收数据时,需要对数据进行适当的序列化和反序列化处理。两种最常见的方式是使用JSON和二进制数据。

两种示例都将展示如何对以下Dictionary对象进行序列化和反序列化处理,但也可以用于任何Variant对象。

1
2
3
4
var data = {
  "Key": "Value",
  "AnotherKey": "AnotherValue"
}

JSON #

Godot本地支持使用全局JSON对象对JSON进行序列化和反序列化处理。

1
2
3
4
5
// Serialize
var serialized = JSON.print(data)

// Deserialize
var deserialized = JSON.parse(serialized).result

二进制 #

Godot可以使用全局访问var2bytesbytes2var函数对byte[]阵列进行序列化和反序列化处理。

1
2
3
4
5
// Serialize
var serialized = var2bytes(data)

// Deserialize
var deserialized = bytes2var(serialized)

新手入门 #

新手入门需要使用Nakama客户端和套接字对象开始创建Sagi-shi和您自己的游戏。

Nakama客户端 #

Nakama客户端与Nakama服务器连接,是访问Nakama功能的入口。建议为每个游戏的每个服务器创建一个客户端。

要为Sagi-shi创建一个客户端,请将以下详细连接信息输入您的服务器:

1
2
3
4
5
6
extends Node

var client : NakamaClient

func _ready():
    client = Nakama.create_client("defaultkey", "127.0.0.1", 7350, "http")

配置请求超时时长 #

客户端对Nakama发出的每个请求必须在一定时间内完成,超过这个时间段会被认为超时。您可以通过在客户端设置timeout值来配置该时间段的长度(以秒为单位):

1
client.timeout = 10

Nakama套接字 #

Nakama套接字用于玩法和实时延迟敏感功能,如聊天、派对、比赛和RPC。

从客户端创建套接字:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Make this a node variable or it will disconnect when the function that creates it returns

onready var socket = Nakama.create_socket_from(client)

func _ready():
    var connected : NakamaAsyncResult = yield(socket.connect_async(session), "completed")
    if connected.is_exception():
        print("An error occurred: %s" % connected)
        return
    print("Socket connected.")

身份验证 #

Nakama支持多种身份验证方法,还支持在服务器上创建自定义身份验证

Sagi-shi将通过链接到同一个用户账户的设备和Facebook进行身份验证,以便玩家可以使用不同的设备玩游戏。

Sagi-shi login screen
Login screen and Authentication options

设备身份验证 #

Nakama设备身份验证使用物理设备的唯一标识符轻松验证用户,也可以为没有账户的设备创建账户。

仅使用设备身份验证时,您无需使用登录UI,因为游戏启动时会自动验证玩家身份。

身份验证是从Nakama客户端实例访问Nakama功能的示例。

1
2
3
4
5
6
7
8
9
# Get the System's unique device identifier
var device_id = OS.get_unique_id()

# Authenticate with the Nakama server using Device Authentication
var session : NakamaSession = yield(client.authenticate_device_async(device_id), "completed")
if session.is_exception():
    print("An error occurred: %s" % session)
    return
print("Successfully authenticated: %s" % session)

Facebook身份验证 #

Nakama Facebook身份验证操作简便,您可以有选择性地导入玩家的Facebook好友,并将这些好友添加到玩家的Nakama好友列表。

1
2
3
4
5
6
7
var oauth_token = "<token>"
var import_friends = true
var session : NakamaSession = yield(client.authenticate_facebook_async(oauth_token, import_friends), "completed")
if session.is_exception():
    print("An error occurred: %s" % session)
    return
print("Successfully authenticated: %s" % session)

自定义身份验证 #

Nakama支持自定义身份验证方法,以便与其他身份服务相集成。

示例请见Itch.io自定义身份验证片段。

链接身份验证 #

Nakama允许玩家在进行身份验证后通过链接身份验证 方法前往玩家账户。

链接设备ID身份验证

1
2
3
4
5
6
7
8
var device_id = "<unique_device_id>"

# Link Device Authentication to existing player account.
var linked : NakamaAsyncResult = yield(client.link_custom_async(session, device_id), "completed")
if linked.is_exception():
    print("An error occurred: %s" % linked)
    return
print("Id '%s' linked for user '%s'" % [device_id, session.user_id])

链接Facebook身份验证

1
2
3
4
5
6
7
var oauth_token = "<token>"
var import_friends = true
var session : NakamaSession = yield(client.link_facebook_async(session, oauth_token, import_friends), "completed")
if session.is_exception():
    print("An error occurred: %s" % linked)
    return
print("Facebook authentication linked for user '%s'" % [session.user_id])

会话变量 #

在身份验证时可以存储Nakama 会话变量,只要会话处于活动状态,即可在客户端和服务器上使用该变量。

Sagi-shi使用会话变量执行分析、推荐和奖励计划等。

在进行身份验证时将会话变量作为参数传递即可存储会话变量:

1
2
3
4
5
6
7
8
var vars = {
    "device_os" : OS.get_name,
    "device_model" : OS.get_model_name,
    "invite_user_id" : "<some_user_id>,
    # ...
}

var session : NakamaSession = yield(client.authenticate_device_async("<device_id>", null, true, vars), "completed")

要访问客户端的会话变量,请使用Session对象上的Vars属性:

1
var device_os = session.vars["device_os"];

会话生命周期 #

Nakama 会话在您的服务器配置中设定的时间之后过期。使不活动会话过期是良好的安全做法。

Nakama提供了多种恢复会话的方法,例如Sagi-shi玩家重新启动游戏,或在游戏过程中刷新令牌保持会话处于活动状态。

使用身份验证并刷新会话对象上的令牌可以恢复或刷新会话。

恢复会话而不必重新进行身份验证:

1
2
var auth_token = "restored from save location"
var session = NakamaClient.restore_session(auth_token)

检查会话是否已过期或即将过期,刷新会话使其保持活动状态:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Check whether a session has expired or is close to expiry
if session.expired:
    # Attempt to refresh the existing session.
    session = yield(client.session_refresh_async(session), "completed)
    if session.is_exception():
        # Couldn't refresh the session so reauthenticate.
        session = yield(client.authenticate_device_async(device_id), "completed")
        # Save the new refresh token
        <save_file>.set_value("refresh_token", session.refresh_token)
    }

    # Save the new auth token
    <save_file>.set_value("auth_token", session.auth_token)
}

结束会话 #

退出登录并结束当前会话:

1
yield(client.session_logout_async(session), "completed")

用户账户 #

Nakama 用户账户存储Nakama定义的用户信息和自定义的开发者元数据。

Sagi-shi允许玩家编辑账户,并存储游戏进度和游戏内商品等内容的元数据。

Sagi-shi player profile screen
Player profile

获取用户账户 #

通过经身份验证的会话可以访问Nakama的许多功能,比如获取用户帐户

获取Sagi-shi玩家的完整用户账户,包括基本用户信息 和用户ID:

1
2
3
4
var account = yield(client.get_account_async(session), "completed")
var username = account.user.username
var avatar_url = account.user.avatar_url
var user_id = account.user.id

更新用户账户 #

Nakama为更新服务器存储的资源(如用户账户)提供了简单的方法。

Sagi-shi玩家需要能够更新其公开资料:

1
2
3
4
5
6
7
var new_username = "NotTheImp0ster"
var new_display_name = "Innocent Dave"
var new_avatar_url = "https://example.com/imposter.png"
var new_lang_tag = "en"
var new_location = "Edinburgh"
var new_timezone = "BST"
yield(client.update_account_async(session, new_username, new_display_name, new_avatar_url, new_lang_tag, new_location, new_timezone), "completed")

获取用户 #

除了获取玩家当前经身份验证的用户账户外,Nakama还可以方便地从其他玩家的ID或用户名中获取其他玩家公开资料列表。

结合使用Nakama的其他功能时,Sagi-shi可以通过这种方法显示用户资料:

1
2
var ids = ["userid1", "userid2"]
var users : NakamaAPI.ApiUsers = yield(client.get_users_async(session, ids), "completed")

存储元数据 #

Nakama用户元数据允许开发人员使用公开的用户字段扩展用户账户。

仅可在服务器上更新用户元数据。示例请见更新用户元数据配方。

Sagi-shi将使用元数据存储玩家装备的游戏内商品:

读取元数据 #

定义描述元数据的类,并对JSON元数据进行语法解析:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class_name metadata

export(String) var title
export(String) var hat
export(String) var skin

# Get the updated account object
var account : NakamaAPI.ApiAccount = yield(client.get_account_async(session), "completed")

# Parse the account user metadata.
var metadata = JSON.parse(account.user.metadata)

Print("Title: %s", metadata.title)
Print("Hat: %s", metadata.hat)
Print("Skin: %s", metadata.skin)

钱包 #

Nakama用户钱包可以将多种数字货币存储为字符串/整数的键/值对。

Sagi-shi玩家可以使用游戏内的虚拟货币解锁或购买头衔、皮肤和帽子。

访问钱包 #

对用户账户中的JSON钱包数据进行语法解析:

1
2
3
4
var account : NakamaAPI.ApiAccount = yield(client.get_account_async(session), "completed")
var wallet = JSON.parse(account.wallet)
for currency in wallet
    Print("%s, %s" % [currency, wallet[currency].string(int from)])

更新钱包 #

仅可在服务器上更新钱包。示例请见用户账户虚拟钱包文档。

验证应用内的购买行为 #

Sagi-shi玩家可以通过应用程序内的购买行为购买游戏内的虚拟货币,这些购买行为需要经过服务器授权并且通过合法性验证。

示例请见应用程序内购买行为验证文档。

存储引擎 #

Nakama存储引擎是为您的游戏而设计的,基于文件的分布式、可扩展存储解决方案。

通过存储引擎,您可以更好地控制数据在集合中的访问方式结构

这些集合会被命名,并将JSON数据存储在唯一的键和用户ID下。

默认玩家拥有创建、读取、更新和删除自己的存储对象的全部权限。

Sagi-shi玩家可以解锁或购买存储引擎中存储的许多商品。

Sagi-shi player items screen
Player items

读取存储对象 #

读取存储对象并对JSON数据进行语法解析:

1
2
3
4
5
6
7
var read_object_id = NakamaStorageObjectId.new("unlocks", "hats", session.user_id)

var result : NakamaAPI.ApiStorageObjects = yield(client.read_storage_objects_async(session, read_object_id), "completed")

print("Unlocked hats: ")
for o in result.objects:
    print("%s" % o)

要读取其他玩家的公开存储对象,应该使用其user_id。 玩家仅可读取自己拥有的存储对象或公开的存储对象(PermissionRead值为2)。

写入存储对象 #

Nakama允许开发人员从客户端和服务器写入存储引擎。

在决定写入逻辑的存放位置时,要考虑恶意用户会对您的游戏和财物产生何种不利影响,例如仅允许经过授权后写入数据(即游戏解锁或进度)。

Sagi-shi允许玩家收藏商品,以便在UI界面轻松查看这些商品,并通过客户端安全地写入这些数据。

将存储对象写入存储引擎:

1
2
3
4
5
6
var favorite_hats = ["cowboy", "alien"]
var can_read = 1 # Only the server and owner can read
var can_write = 1 # The server and owner can write

var acks : NakamaAPI.ApiStorageObjectAcks = yield(client.write_storage_objects_async(session, [
    NakamaWriteStorageObject.new("hats", "favorite_hats", can_read, can_write)]), "completed")

您也可以将多个对象传递到write_storage_objects_async方法:

1
2
3
4
var acks : NakamaAPI.ApiStorageObjectAcks = yield(client.write_storage_objects_async(session, [
    NakamaWriteStorageObject.new(...),
    NakamaWriteStorageObject.new(...)
]), "completed")

条件性写入 #

存储引擎条件性写入确保仅当对象在您访问后未改变时才会发生写入操作。

这样可以保护您的数据不被覆盖,例如,Sagi-shi服务器可能在玩家上次访问对象后更新对象。

要执行条件性写入,要用最新的对象版本向写入存储对象添加版本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Assuming we already have a storage object
var favorite_hats = ["cowboy", "alien"]
var can_read = 1 # Only the server and owner can read
var can_write = 1 # The server and owner can write
var version = <version>

var acks : NakamaAPI.ApiStorageObjectAcks = yield(client.write_storage_objects_async(session, [
    NakamaWriteStorageObject.new("hats", "favorite_hats", can_read, can_write, version)]), "completed")
if acks.is_exception():
    print("An error occurred: %s" % acks)
    return

列出存储对象 #

您可以在一个集合中列出玩家可以查看的所有存储对象,而不是通过单独的键发出多次读取请求。

Sagi-shi列出玩家未解锁或已购买的所有头衔、帽子和皮肤:

1
2
3
4
5
6
7
8
var limit = 3
var unlocks_object_list : NakamaAPI.ApiStorageObjectList = yield(client.list_storage_objects_async(session, "titles", "hats", "skins", session.user_id, limit), "completed")
if unlocks_object_list.is_exception():
    print("An error occurred: %s" % unlocks_object_list)
    return
print("Unlocked objects: ")
for o in unlocks_object_list.objects:
    print("%s" % o)

分页结果 #

Nakama列出结果的方法会退回游标,将其传递给Nakama的后续调用,指示在集合中检索对象的起始点。

例如:

  • 如果游标的值为5,您将从第五个对象开始获取结果。
  • 如果游标的值为null,您将从第一个对象开始获取结果。
1
object_list : NakamaAPI.ApiStorageObjectList = yield(client.list_storage_objects_async(session, "<object>", limit, object_list.cursor), "completed")

保护服务器上的存储操作 #

可以在服务器上保护Nakama存储引擎操作,从而保护不应被玩家修改的数据(即 游戏解锁或进度)。参见经授权写入存储引擎配方。

远程过程调用 #

Nakama服务器允许开发人员写入自定义逻辑,并将其作为RPC向客户端公开。

Sagi-shi包含各种需要在服务器上保护的逻辑,比如在装备设备之前检查玩家是否拥有设备。

创建服务器逻辑 #

关于创建远程过程检查玩家在装备设备之前是否拥有设备的示例,请参见经授权处理玩家设备配方。

客户端RPC #

可以从客户端调用Nakama远程过程,并获取可选的JSON负载。

Sagi-shi客户端允许RPC安全地装备帽子:

1
2
3
4
5
6
var payload = {"hat": "cowboy"}
var rpc_id = equip_hat
var response : NakamaAPI.ApiRpc = yield(client.rpc_async(session, rpc_id, JSON.print(payload)), "completed")
if response.is_exception():
    print("An error occurred: %s" % response)
    return

套接字RPC #

当您需要与Nakama实时功能交互时,也可以通过套接字调用Nakama远程过程。这些实时功能需要实时套接字(和相应的会话标识符)。可以在携带相同标识符的套接字上进行RPC。

1
var response : NakamaAPI.ApiRpc = yield(socket.rpc_async("<rpc_id>", "<payload>"), "completed")

好友 #

Nakama好友提供完整的社交图谱系统来管理玩家之间的好友关系。

Sagi-shi允许玩家添加好友,管理好友关系,一起玩游戏。

Sagi-shi Friends screen
Friends screen

添加好友 #

在Nakama中添加好友不会立即添加共同好友。它会向每个用户发送好友请求,需要用户接受请求。

Sagi-shi允许玩家按用户名或用户id添加好友:

1
2
3
4
5
6
7
8
var ids = ["some_user_id", "another_user_id]
var usernames = ["AlwaysTheImposter21", "SneakyBoi"]

# Add friends by username
var result : NakamaAsyncResult = yield(client.add_friends_async(session, usernames), "completed")

# Add friends by user id
var result : NakamaAsyncResult = yield(client.add_friends_async(session, ids), "completed")

好友关系的状态 #

在Nakama中,好友关系可以有以下几种状态

状态
0共同好友
1发出等待接受的好友请求
2收到等待接受的好友请求
3被用户屏蔽

列出好友 #

Nakama允许开发人员按好友关系的状态列出玩家的好友。

Sagi-shi列出最近20位共同好友:

1
2
3
4
5
6
7
8
9
var limit = 20 # Limit is capped at 1000
var friendship_state = 0
var list : NakamaAPI.ApiFriendList = yield(client.list_friends_async(session, limit, friendship_state), "completed")
if list.is_exception():
    print("An error occurred: %s" % list)
    return
for f in list.friends:
    var friend = f as NakamaAPI.ApiFriend
    print("Friends %s [friend.user.id])

接受好友请求 #

在Nakama中接受好友请求时,玩家会添加双向好友关系

Nakama会将两个玩家的好友状态从待接受改为共同好友。

在完整的游戏中,您可以允许玩家单独接受某些请求。

Sagi-shi仅获取并接受收到的所有好友请求:

1
2
3
4
var limit = 1000
var result : NakamaAsyncResult = yield(client.list_friends_async(session, 2, limit, cursor: null)
for f in result.friends:
    yield(client.add_friend_async(session, f.user.id), "completed")

删除好友 #

Sagi-shi玩家可以按用户名或用户id删除好友:

1
2
3
4
5
6
7
8
var ids = ["some_user_id", "another_user_id]
var usernames = ["AlwaysTheImposter21", "SneakyBoi"]

# Delete friends by username
var result : NakamaAsyncResult = yield(client.delete_friends_async(session, usernames), "completed")

# Delete friends by user id
var result : NakamaAsyncResult = yield(client.delete_friends_async(session, ids), "completed")

屏蔽用户 #

Sagi-shi玩家可以按用户名或用户id屏蔽用户:

1
2
3
4
5
6
7
8
var ids = ["some_user_id", "another_user_id]
var usernames = ["AlwaysTheImposter21", "SneakyBoi"]

# Block friends by username
var result : NakamaAsyncResult = yield(client.block_friends_async(session, usernames), "completed")

# Block friends by user id
var result : NakamaAsyncResult = yield(client.block_friends_async(session, ids), "completed")

进一步了解关于屏蔽好友和相关的好友关系状态

被屏蔽的好友也可以作为好友列出,但好友关系状态相应变为(3)。

状态与显示 #

Nakama状态是一种实时状态与显示的服务,允许用户设置显示的在线状态,更新状态消息并关注其他用户的更新。

玩家可以关注其他人但不与之成为好友。

Sagi-shi使用状态消息和显示的在线状态,当好友在线时通知玩家并分享比赛。

Sagi-shi status update screen
Updating player status

关注用户 #

Nakama实时API允许开发人员订阅套接字上的事件并实时接收这些事件,如状态显示变更。

关注用户的方法也会返回至当前的在线用户(即显示的在线状态)及其状态。

Sagi-shi会关注玩家的好友,并当好友在线时通知玩家:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func _ready():
    # Setup the socket and subscribe to the status event
    socket.connect("received_status_presence", self, "_on_status_presence")

func _on_status_presence(p_presence : NakamaRTAPI.StatusPresenceEvent):
    print(p_presence)
    for j in p_presence.joins:
        print("%s is online with status: %s" % [j.user_id, j.status])
    for j in p_presence.leaves:
        print("%s went offline" % [j.user_id])

# Follow mutual friends and get the initial Status of any that are currently online
var friends_result = yield(client.list_friends_async(session, 0), "completed")
var friend_ids = []
for friend in friends_result:
	var f = friend as NakamaAPI.ApiFriend
	if not f or not f.user.online:
		continue
	friend_ids.append(f.user)
var result : NakamaAsyncResult = yield(socket.follow_users_async(friend_ids)

for p in result.presences:
    print("%s is online with status: %s" % [presence.user_id, presence.status])

取消关注用户 #

Sagi-shi玩家可以取消关注其他人:

1
yield(socket.unfollow_users_async("<user_id>"), "completed")

更新玩家状态 #

Sagi-shi玩家可以更改状态并向关注自己的人发布状态:

1
yield(socket.update_status_async("Viewing the Main Menu"), "completed")

群组 #

Nakama群组是指一个公开/私密的群组或家族体系,拥有用户成员资格和权限、元数据和群组聊天功能。

Sagi-shi允许玩家创建和加入群组,可以社交或竞赛。

Sagi-shi groups screen
Groups list screen

创建群组 #

群组可以为公开或私密“开放”。每个人都可以加入公开的群组,但如想加入私密群组,必须要请求加入,得到超级管理员/管理员接受才可。

Sagi-shi玩家可以围绕共同的兴趣创建群组:

1
2
3
4
5
6
var name = "Imposters R Us"
var description = "A group for people who love playing the imposter."
var open = true # public group
var max_size = 100

var group : NakamaAPI.ApiGroup = yield(client.create_group_async(session, name, description, open, max_size), "completed")

更新群组的可见性 #

Nakama允许群组的超级管理员或管理员成员从客户端更新某些属性,如开放可见性:

1
2
var open = false
yield(client.update_group_async(session, "<group_id>", name: null, open), "completed")

更新群组规模 #

其他属性只能在服务器上更改,例如群组成员的最大数量。

示例请见更新群组规模配方和群组服务器功能参考,进一步了解在服务器上更新群组的信息。

Sagi-shi group edit screen
Sagi-shi group edit

列出和过滤群组 #

群组可以像Nakama的其他资源一样列出,也可以使用通配符群组名称过滤

Sagi-shi玩家通过列出和过滤群组搜索加入现有的群组:

1
2
3
4
5
6
7
8
9
var limit = 20
var result : NakamaAPI.ApiGroupList = yield(client.list_groups_async(session, "imposter%", limit), "completed")

for g in result.groups:
    var group = g as NakamaAPI.ApiGroup
    print("Group: name &s, open %s", [group.name, group.open])

$ Get the next page of results
var next_results : NakamaAPI.ApiGroupList = yield(client.list_groups_async(session, name: "imposter%", limit, result.cursor)

删除群组 #

Nakama允许群组的超级管理员删除群组。

开发人员可以完全禁用此功能,请在Guarding API指南中查看如何保护Nakama各种API的示例。

Sagi-shi玩家可以删除自己在其中作为超级管理员的群组:

1
yield(client.delete_group_async(session, "<group_id>"), "completed")

群组元数据 #

与用户账户一样,群组可以拥有公开元数据。

Sagi-shi使用群组元数据存储群组的兴趣、玩家的活跃时间和使用的语言。

仅可在服务器上更新群组元数据。示例请见更新群组元数据配方。

Sagi-shi客户端使用群组元数据负载进行RPC:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var payload = {
    group_id = "<group_id>",
    interests = ["Deception", "Sabotage", "Cute Furry Bunnies"],
    active_times = ["9am-2pm Weekdays", "9am-10pm Weekends"],
    languages = ["English", "German"],
}

var result : NakamaAsyncResult = yield(client.rpc_async(session, "update_group_metadata", JSON.stringify(payload))
if result.is_exception():
    print("An error occurred: %s" % result)
    return
print("Successfully updated group metadata")

群组成员资格状态 #

在Nakama中,成员资格可以有以下几种状态

代码用途
0超级管理员任何群组都必须拥有至少一位超级管理员。超级管理员拥有管理员的所有权限,另外还可以删除群组和升级管理员成员。
1管理员可以有一个或多个管理员。管理员可以更新群组,也可以接受、踢出、升级、降级、封禁或添加成员。
2成员群组常规成员。他们无法接受新用户的加入请求。
3加入请求新用户发送的新的加入请求。这不会被计入群组成员的最大数量。

加入群组 #

如果用户加入公开群组,可立即成为群组成员,但如果尝试加入私密群组,必须等待群组管理员接受请求。

Sagi-shi玩家可以加入群组:

1
yield(client.join_group_async(session, "<group_id>"), "completed")

列出用户的群组 #

Sagi-shi玩家可以列出自己所在的群组:

1
2
3
4
5
6
var user_id = "<user id>"
var result : NakamaAPI.ApiUserGroupList = yield(client.list_user_groups_async(session, user_id), "completed")

for ug in result.user_groups:
    var g = ug.group as NakamaAPI.ApiGroup
    print("Group %s role %s", g.id, ug.state)

列出成员 #

Sagi-shi玩家可以列出群组的成员:

1
2
3
4
5
6
var group_id = "<group id>"
var member_list : NakamaAPI.ApiGroupUserList = yield(client.list_group_users_async(session, group_id), "completed")

for ug in member_list.group_users:
    var u = ug.user as NakamaAPI.ApiUser
    print("User %s role %s" % [u.id, ug.state])

接受加入请求 #

私密群组管理员或超级管理员可以通过将用户重新添加到群组来接受加入请求。

Sagi-shi首先列出处于请求加入状态的用户,然后遍历并将这些用户添加到群组:

1
2
3
4
5
var result : NakamaAPI.ApiGroupUserList = yield(client.list_group_users_async(session, "<group_id>", 3), "completed")

for gu in result.group_users:
    var u = gu.user as NakamaAPI.ApiUser
    yield(client.add_group_users_async(session, "<group_id>", u), "completed"))

升级成员 #

Nakama群组成员可以升级为管理员或超级管理员角色,帮助管理规模不断扩大的群组,或在成员离开时接任。

管理员可以将其他成员升级为管理员,超级管理员可以将其他成员升级为超级管理员。

成员将被提升一级。例如:

  • 成员可以升级为管理员
  • 管理员可以升级为超级管理员
1
yield(client.promote_group_users_async(session, "<group_id>", "<user_id>")

降级成员 #

Sagi-shi群组管理员和超级管理员可以降级成员:

1
yield(client.demote_group_users_async(session, "<group_id>", "<user_id>")

踢出成员 #

Sagi-shi群组管理员和超级管理员可以移除群组成员:

1
yield(client.kick_group_users_async(session, "<group_id>", "<user_id>")

封禁成员 #

当降级用户或踢出用户不够严重时,Sagi-shi群组管理员和超级管理员可以封禁成员:

1
yield(client.ban_group_users_async(session, "<group_id>", "<user_id>")

退出群组 #

Sagi-shi玩家可以退出群组:

1
yield(client.leave_group_async(session, "<group_id>")

聊天 #

Nakama聊天是针对群组、私密/直接消息以及动态聊天室的实时聊天系统。

Sagi-shi在比赛期间使用动态聊天,玩家可以相互误导,讨论谁是内鬼,群组聊天和私密/直接消息。

Sagi-shi chat screen
Sagi-shi Chat

加入动态聊天室 #

Sagi-shi比赛设有非永久聊天室,供玩家交流:

1
2
3
4
5
6
7
var roomname = "<match_id>"
var persistence = false
var hidden = false
var type = NakamaSocket.ChannelType.Room
var channel : NakamaRTAPI.Channel = yield(socket.join_chat_async(roomname, type, persistence, hidden), "completed")

print("Connected to dynamic room channel: '%s'" % [channel.id])

加入群组聊天 #

Sagi-shi群组成员可以在持久的群组聊天频道中跨越游戏会话进行交流:

1
2
3
4
5
6
7
var group_id = "<group_id>"
var persistence = true
var hidden = false
var type = NakamaSocket.ChannelType.Group
var channel : NakamaRTAPI.Channel = yield(socket.join_chat_async(group_id, type, persistence, hidden), "completed")

print("Connected to group channel: '%s'" % [channel.id])

加入直接聊天 #

Sagi-shi玩家也可以在比赛中或比赛后一对一私下交流,可以查看消息历史记录:

1
2
3
4
5
6
7
var user_id = "<user_id>"
var persistence = true
var hidden = false
var type = NakamaSocket.ChannelType.DirectMessage
var channel : NakamaRTAPI.Channel = yield(socket.join_chat_async(user_id, type, persistence, hidden), "completed")

print("Connected to direct message channel: '%s'" % [channel.id])

发送消息 #

在每种聊天频道中,消息的发送都是一样的。消息包含聊天文本和表情,以JSON序列化数据的形式发送:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var channel_id = "<channel_id>"

var message_content = { "message": "I think Red is the imposter!" }

var message_ack : NakamaRTAPI.ChannelMessageAck = yield(socket.write_chat_message_async(channel_id, message_content), "completed")

var emote_content = {
    "emote": "point",
    "emoteTarget": "<red_player_user_id>",
    }

var emote_ack : NakamaRTAPI.ChannelMessageAck = yield(socket.write_chat_message_async(channel_id, emote_content), "completed")

列出消息历史 #

消息列表需要一个参数,指示接收的消息是从最早到最新(向前)还是从最新到最早排列。

Sagi-shi玩家可以列出群组的消息历史:

1
2
3
4
5
6
7
8
var limit = 100
var forward = true
var group_id = "<group_id>"
var result : NakamaAPI.ApiChannelMessageList = yield(client.list_channel_messages_async(session, group_id, limit, forward), "completed")

for m in result.messages:
    var message : NakamaAPI.ApiChannelMessage = m as NakamaAPI.ApiChannelMessage
    print(message.user_id, message.content)

聊天还有可缓存游标,获取最新消息。阅读列表通知文档中关于可缓存游标的更多信息。

更新消息 #

Nakama还支持更新消息。是否使用此功能取决于您,但在类似Sagi-shi的游戏中,它可以增加额外的欺骗元素。

例如玩家发送以下消息:

1
2
3
4
var channel_id = "<channel_id>"
var message_content = {"message": "I think Red is the imposter!" }

var message_ack : NakamaRTAPI.ChannelMessageAck = yield(socket.write_chat_message_async(channel_id, message_content), "completed")

然后他们迅速编辑消息以迷惑他人:

1
2
3
var new_message_content = { "message": "I think BLUE is the imposter!" }

var message_update_ack : NakamaRTAPI.ChannelMessageAck = yield(socket.update_chat_message_async(channel_id, new_message_content), "completed")

比赛 #

Nakama支持服务器授权服务器中继的多人比赛。

在服务器授权比赛中,服务器控制玩法循环,并且必须使所有的客户端与游戏的当前状态保持同步。

在服务器中继比赛中,客户端处于控制地位,服务器仅将信息中继到其他连接的客户端。

在竞争性游戏中,如Sagi-shi,服务器授权比赛可防止客户端以未经授权的方式与您的游戏交互。

在本指南中,为方便起见,采用了服务器中继模式。

创建比赛 #

Sagi-shi玩家可以自行创建比赛并邀请在线好友加入:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
var match : NakamaRTAPI.Match = yield(socket.create_match_async(), "completed")
var friends_list = yield(client.list_friends_async(session, 0, 100)
var online_friends = []
for friend in friends_list:
    var f = friend as NakamaAPI.ApiFriend
	if not f or not f.user.online:
		continue
    online_friends.append(f.user)

for f in online_friends:
    var content = {
        "message": "Hey %s, join me for a match!",
        match_id = match.id,
    }
    var channel = yield(socket.join_chat_async(f.id, NakamaSocket.ChannelType.DirectMessage), "completed")
    var message_ack = yield(socket.write_chat_message_async(channel.id, content), "completed")

使用比赛名称创建比赛

Sagi-shi玩家还可以使用特定的比赛名称创建比赛,以便通过将比赛名称告知好友来邀请好友加入。需要注意的是,使用比赛名称创建比赛时(任意名称,而不是与经授权的比赛处理程序绑定的名称),比赛将始终为中继比赛而非授权比赛。

1
2
var match_name = "NoImpostersAllowed"
var match : NakamaRTAPI.Match = yield(socket.create_match_async(match_name), "completed")

加入比赛 #

如果知道id,Sagi-shi玩家可以尝试加入已有比赛:

1
2
var match_id = "<matchid>"
var match = yield(socket.join_match_async(match_id), "completed")

或者设置实时配对监听器,将自己加入到配对:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func _on_matchmaker_matched(p_matched : NakamaRTAPI.MatchmakerMatched):
  var match : NakamaRTAPI.Match = yield(socket.join_match_async(p_matched), "completed")

var min_players = 2
var max_players = 10
var query = ""

var matchmaking_ticket : NakamaRTAPI.MatchmakerTicket = yield(
  socket.add_matchmaker_async(query, min_players, max_players),
  "completed"
)

按玩家状态加入比赛

Sagi-shi玩家可以在加入新比赛时更新状态:

1
2
3
4
5
6
var status = {
    "status": "Playing a match",
    "matchid": "<match_id>",
    }

yield(socket.update_status_async(status), "completed")

关注玩家的用户可以接收实时状态事件,并尝试加入比赛:

1
2
3
4
5
6
func _on_status_presence(p_presence : NakamaRTAPI.StatusPresenceEvent):
    # Join the first match found in a friend's status
    for j in p_presence.joins:
        var status = JSON.parse(p_presence.status)
        if matchid in status:
            yield(socket.join_match_async(status["matchid"]), "completed")

列出比赛 #

比赛列表需要一些标准来过滤比赛,包括玩家人数、匹配标签和可以进行更复杂的搜索查询的选项。

在大厅状态时可以开始Sagi-shi比赛。比赛会存在于服务器,但只有当加入的玩家人数足够时才会开始比赛。

然后Sagi-shi可以列出正在等待更多玩家的比赛:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var min_players = 2
var max_players = 10
var limit = 10
var authoritative = true
var label = ""
var query = ""
var result : NakamaRTApi.Match = yield(client.list_matches_async(session, min_players, max_players, limit, authoritative, label, query)

for m in result.matches:
    print("%s: %s/10 players", match.match_id, match.size)

找到标签为"an_exact_match_label"的比赛:

1
var label = "an_exact_match_label"

高级:

为了使用更复杂的结构化查询,匹配标签必须为JSON格式。

要查找预期玩家技能级别为>100且可选游戏模式为"sabotage"的比赛:

1
var query = "+label.skill:>100 label.mode:sabotage"

生成玩家 #

比赛对象有一个当前在线用户列表,称为显示在线的用户。

Sagi-shi使用比赛显示的在线状态在客户端上生成玩家:

1
2
3
4
5
6
7
8
var match = yield(socket.join_match_async(match_id), "completed")

var players = {}

for p in match.presences:
    // Spawn a player for this presence and store it in a dictionary by session id.
    var go = <player_node>.new()
    players.add(presence.session_id, go)

Sagi-shi使用接收到的比赛显示的在线状态事件,使生成的玩家在离开和加入比赛时保持最新状态:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func _on_match_presence(p_presence : NakamaRTApi.MatchPresenceEvent):
    # For each player that has joined in this event...
    for p in p_presence.joins:
        # Spawn a player for this presence and store it in a dictionary by session id.
        var go = <player_node>.new()
        players.add(p_presence.session_id, go)
    # For each player that has left in this event...
    for p in p_presence.leaves:
        # Remove the player from the game if they've been spawned
        if presence.session_id in players:
            <player_node>.remove_and_skip()
            players.remove(presence.session_id)

发送比赛状态 #

Nakama拥有实时网络,可以在玩家移动和与游戏世界互动时发送接收比赛状态。

比赛过程中,Sagi-shi的每个客户端都会将比赛状态发送到服务器,从而中继给其他客户端。

匹配状态包含一个操作代码,使接收者了解所接收的数据,以便对数据进行反序列化处理并更新其游戏视图。

Sagi-shi使用的操作代码示例:

  • 1:玩家位置
  • 2:玩家调用投票

发送玩家位置

定义一个类别来代表Sagi-shi玩家的位置状态。

1
2
3
4
5
class_name position_state

var X
var Y
var Z

在玩家的转换中创建一个实例,设置操作代码并发送JSON编码的状态:

1
2
3
4
5
6
7
8
9
var state = {
    X = transform.x,
    Y = transform.y,
    Z = transform.z,
}

var op_code = 1

yield(socket.send_match_state_async(match.id, op_code, JSON.print(state), "completed")

作为静态类操作代码

Sagi-shi有许多联网游戏动作。操作代码使用静态常量类将更容易遵循和维护:

1
2
3
4
5
6
class_name op_codes

const position = 1
const vote = 2

yield(socket.send_match_state_async(match.id, op_codes.position, JSON.print(state), "completed")

接收比赛状态 #

Sagi-shi玩家可以通过订阅比赛状态接收事件,通过其他连接的客户端接收比赛数据:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func _on_match_state(p_state : NakamaRTAPI.MatchData):
    match match_state.op_code:
        op_code.position:
        # Get the updated position data
        var position_state = JSON.parse(match_state.state)
        # Update the game object associated with that player
        var user = match_state.user_presence.session_id
        if user in players:
            # Here we would normally do something like smoothly interpolate to the new position, but for this example let's just set the position directly.
            players[user].transform.Vector3 = vec(position_state.x, position_state.y, position_state.z)
        _:
            print("Unsupported op code.")

配对程序 #

开发人员可以使用比赛列表或Nakama配对程序为玩家寻找匹配,使玩家能够加入实时匹配池,并在匹配到其他符合指定标准的玩家时收到通知。

配对可以帮助玩家找到一起玩游戏的伙伴,但不会创建比赛。这种解耦的设计允许您将配对用于寻找游戏匹配之外的其他目的。例如,如果您想社交,可以使用配对匹配别人进行聊天。

添加配对程序 #

匹配标准可以很简单,即找到2名玩家,也可以更复杂,即找到2-10名对特定游戏模式感兴趣的拥有最低技能水平的玩家。

Sagi-shi允许玩家加入匹配池,使服务器将这些玩家与其他玩家进行匹配:

1
2
3
4
5
6
7
var min_players = 2
var max_players = 10
var query = "+skill:>100 mode:sabotage"
var string_properties = { "mode": "sabotage" }
var numeric_properties = { "skill": 125 }
var matchmaker_ticket : NakamaRTAPI.MatchmakerTicket = yield(
  socket.add_matchmaker_async(query, min_players, max_players, string_properties, numeric_properties)

按照规定的标准成功匹配后,玩家可以加入比赛:

1
2
func _on_matchmaker_matched(p_matched : NakamaRTAPI.MatchmakerMatched):
  var joined_match : NakamaRTAPI.Match = yield(socket.join_match_async(p_matched), "completed")

派对 #

Nakama派对是一个实时系统,允许玩家组成短暂的派对,这些派对在所有玩家断开连接后不会持续存在。

Sagi-shi允许好友组成派对并一起配对。

创建派对 #

创建派对的玩家是派对的领导者。派对玩家的数量有上限,派对既可以为开放式,即自动接受玩家,也可以为封闭式,即等待派对领导者接受玩家发来的加入请求。

Sagi-shi使用封闭式派对,最多可以有四名玩家:

1
2
3
var open = false
var max_players = 4
var party = yield(socket.create_party_async(open, max_players), "completed")

Sagi-shi通过私密/直接消息将派对id分享给好友:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
var friends_list : NakamaAPI.ApiFriendList = yield(client.list_friends_async(session, limit, friendship_state), "completed")
var online_friends = []
for friend in friends_list:
    var f = friend as NakamaAPI.ApiFriend
    if not f or not f.user.online:
        continue
    online_friends.append(f.user)

for f in online_friends:
    var content = {
        "message": "Hey %s, wanna join the party?",
        party_id = party.id,
    }
    var channel = yield(socket.join_chat_async(f.id, NakamaSocket.ChannelType.DirectMessage), "completed")
    var message_ack = yield(socket.write_chat_message_async(channel.id, content), "completed")

加入派对 #

Safi-shi玩家可以通过查看聊天消息中的派对id来加入派对。首先,必须连接 套接字的received_channel_message信号。

1
socket.connect("received_channel_message", self, "_on_received_channel_message")

然后,当接收到这个信号时,可以查看消息内容并加入派对。

1
2
3
4
5
6
func _on_received_channel_message(message):
    var data = JSON.parse(message.content)
    if data.result.party_id:
        var join = yield(socket.join_party_async(data.result.party_id), "completed")
        if join.is_exception():
            print("error joining party)

升级成员 #

Sagi-shi派对成员可以升级为派对领导者:

1
2
3
var new_leader = "<user_id>"
var party_id = "<party_id>"
var leader: NakamaAsyncResult = yield(socket.received_party_leader(party_id, new_leader), "completed)

退出派对 #

Sagi-shi玩家可以退出派对:

1
2
var party_id = "<party_id>"
var party: NakamaAsyncResult = yield(socket.leave_party_async(party_id), "completed")

派对配对 #

加入派对的一个主要好处是,所有玩家都可以同时加入匹配池。

Sagi-shi玩家可以收听配对程序匹配事件,并在找到匹配人时加入比赛:

1
2
func _on_matchmaker_matched(p_matched : NakamaRTAPI.MatchmakerMatched):
  var joined_match : NakamaRTAPI.Match = yield(socket.join_match_async(p_matched), "completed")

派对领导者将开始为派对进行匹配:

1
2
3
4
5
var party_id = "<party_id>"
var min_players = 2
var max_players = 10
var query = ""
var matchmaker_ticket = yield(socket.add_matchmaker_party_async(party_id, query, min_players, max_Players)

排行榜 #

Nakama排行榜为您的游戏引入竞争因素,提高了玩家的参与度和保留率。

Sagi-shi有一个内鬼获胜的周排行榜,玩家每次获胜都会增加得分,同样,也有船员获胜的周排行榜。

Sagi-shi leaderboard screen
Sagi-shi Leaderboard

创建排行榜 #

必须在服务器上创建排行榜,请在排行榜文档中查看有关创建排行榜的详细信息。

提交分数 #

玩家提交分数时,Nakama将会把提交的分数值加到玩家的现有分数中。

除了分数值外,Nakama还有一个子分数,当分数值相同时可使用子分数进行排序。

Sagi-shi玩家可以向排行榜提交带有情境元数据的分数,例如取得分数的地图:

1
2
3
4
var score = 1
var subscore = 0
var metadata = { "map": "space_station" }
var record : NakamaAPI.ApiLeaderboardRecord = yield(client.write_leaderboard_record_async(session, "weekly_imposter_wins", score, subscore, JSON.print(metadata), "completed")

列出最高记录 #

Sagi-shi玩家可以列出排行榜的最高记录:

1
2
3
4
5
6
var limit = 20
var leaderboard_name = "weekly_imposter_wins"
var result : NakamaAPI.ApiLeaderboardRecordList = yield(client.list_leaderboard_records_async(session, leaderboard_name, owner_ids: null, expiry: null, limit, cursor: null), "completed")

for r in result.records:
    print("%s:%s", record.owner_id, record.score)

列出用户周围的记录

Nakama允许开发人员列出玩家周围的排行榜记录。

Sagi-shi向玩家简要介绍其与周围玩家的对抗情况:

1
2
3
4
5
6
var limit = 20
var leaderboard_name = "weekly_imposter_wins"
var result : NakamaAPI.ApiLeaderboardRecordList = yield(client.list_leaderboard_records_async(session, leaderboard_name, session.user_id, expiry: null, limit), "completed")

for r in result.records:
    print("%s:%s", record.owner_id, record.score)

列出一系列用户的记录

Sagi-shi玩家可以通过向所有者id参数提供他们的用户id来获取好友分数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var friends_list : NakamaAPI.ApiFriendList = yield(client.list_friends_async(session, 0, 100, cursor: null), "completed")
var user_ids = []
for friend in friends_list.friends:
    var f = friend as NakamaAPI.ApiFriend
    user_ids.append(f.user.id)

var record_list : NakamaAPI.ApiLeaderboardRecordList = yield(client.list_leaderboard_records_around_owner_async(session, "weekly_imposter_wins", user_ids, expiry: null, 100, cursor: null), "completed")

for record in record_list.records:
    print("%s scored %s", record.username, record.score)

同样也可以通过向所有者id参数提供他们的用户id来获取群组成员的分数:

1
2
3
4
5
6
7
8
var group_id = "<groupid>"
var group_user_list : NakamaAPI.ApiGroupUserList = yield(client.list_group_users_async(session, group_id, 100, cursor: null), "completed")
var user_ids = []
for gu in group_user_list.group_users:
    var u = gu as NakamaAPI.ApiUser
    user_ids.append(u.user.id)

var record_list : NakamaAPI.ApiLeaderboardRecordList = yield(client.list_leaderboard_records_around_owner_async(session, "weekly_imposter_wins", user_ids, expiry: null, 100, cursor: null), "completed")

删除记录 #

Sagi-shi玩家可以删除自己的排行榜记录:

1
yield(client.delete_leaderboard_record_async(session, "<leaderboard_id>"), "completed")

锦标赛 #

Nakama锦标赛是玩家争夺奖品的短暂比赛。

Sagi-shi玩家可以查看、过滤和加入正在进行的锦标赛。

Sagi-shi tournaments screen
Sagi-shi Tournaments

创建锦标赛 #

必须在服务器上创建锦标赛,请在锦标赛文档中查看有关创建锦标赛的详细信息。

Sagi-shi每周都会举行锦标赛,玩家需要投票给最准确的内鬼。本周结束时,排名靠前的玩家将获得游戏货币奖励。

加入锦标赛 #

默认Nakama玩家不必加入锦标赛也可提交分数,但Sagi-shi强制要求加入锦标赛后方可提交分数:

1
yield(client.join_tournament_async(session, "<id>"), "completed")

列出锦标赛 #

Sagi-shi玩家可以根据各种标准列出和筛选锦标赛:

1
2
3
4
5
6
7
8
9
var category_start = 1
var category_end = 2
int start_time = null
int end_time = null
var limit = 100
var result : NakamaAPI.ApiTournamentRecordList = yield(client.list_tournament_records_async(session, category_start, category_end, start_time, end_time, limit, cursor: null), "completed")

for t in result.tournaments:
    print("%s:%s", tournament.id, tournament.title)

出于性能方面的考虑,通过范围而不是单个数字来过滤类别。您可以利用这一点构建您的类别(例如,所有的PvE锦标赛属于1XX范围,所有的PvP锦标赛属于2XX范围)。

列出记录 #

Sagi-shi玩家可以列出锦标赛记录:

1
2
3
4
5
6
var limit = 20
var tournament_name = "weekly_top_detective"
var result : NakamaAPI.ApiTournamentRecordList = yield(client.list_tournament_records_async(session, tournament_name, owner_ids: null, expiry: null, limit, cursor: null), "completed")

for r in result.records:
    print("%s:%s", record.owner_id, record.score)

列出用户周围的记录

与排行榜类似,Sagi-shi玩家可以获取周围其他玩家的记录:

1
2
3
4
5
6
var limit = 20
var tournament_name = "weekly_top_detective"
var result : NakamaAPI.ApiTournamentRecordList = yield(client.list_tournament_records_async(session, tournament_name, session.user_id, expiry: null, limit), "completed")

for r in result.records:
    print("%s:%s", record.owner_id, record.score)

提交分数 #

Sagi-shi玩家可以向锦标赛提交分数、子分数和元数据:

1
2
3
4
5
var score = 1
var subscore = 0
var metadata = JSON.print({
    "map": "space_station" })
var new_record : NakamaAPI.ApiLeaderboardRecord = yield(client.write_tournament_record_async(session, "weekly_top_detective", score, subscore, metadata), "completed")

通知 #

游戏服务器可利用Nakama通知向玩家广播实时消息。

通知可以是持续性(在玩家查看之前一直保留)或暂时性的(仅在玩家当前在线的情况下接收)。

Sagi-shi使用通知将获奖情况告知锦标赛获奖者。

Sagi-shi notification screen
Sagi-shi notifications

接收通知 #

通知必须从服务器发出。

Nakama使用代码区分通知。0 和以下代码是为Nakama内部构件保留的系统代码

Sagi-shi玩家可以订阅收到的通知事件。Sagi-shi使用代码100表示赢得锦标赛:

1
2
3
4
5
6
7
8
cont reward_code = 100

func _on_notification(p_notification : NakamaAPI.ApiNotification):
    match notification.code:
        reward_code:
            print("Congratulations, you won the tournament!\n%s\n%s", notification.subject, notification.content)
        _:
            print("Other notification: %s:%s\n%s", notification.code, notification.subject, notification.content)

列出通知 #

Sagi-shi玩家可以列出离线时收到的通知:

1
2
3
4
5
var limit = 100
var result : NakamaAPI.ApiNotificationList = yield(client.list_notifications_async(session, limit), "completed")

for n in result.notifications:
    print("Notification: %s:{%s\n%s", notification.code, notification.subject, notification.content)

分页及可缓存游标

与其他列出方法一样,可以使用游标或可缓存游标将通知结果分页。

1
2
var result : NakamaAPI.ApiNotificationList = yield(client.list_notifications_async(session, 1), "completed")
var cacheable_cursor = result.cacheable_cursor

玩家下次登录时,可使用可缓存游标列出未读通知。

1
var next_results = yield(client.list_notifications_async(session, limit, cacheable_cursor)

删除通知 #

Sagi-shi玩家可以在阅读通知后将其删除:

1
2
var notification_ids = ["<notification-id>"]
var delete : NakamaAsyncResult = yield(client.delete_notifications_async(session, notification_ids), "completed")