代码示例 #

本页面提供了一些可用功能的常见示例,这些功能可在使用 TypeScript 运行库开发项目时用作模板。

比赛处理程序 #

比赛处理程序代表所有服务器端函数,用于处理权威多人游戏比赛的游戏输入和操作。请参阅比赛处理程序 API比赛运行时 API参考页面,了解比赛处理程序功能。

这是乒乓比赛处理程序示例。服务器接收到的消息将被广播回发送消息的对等方。

 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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
const matchInit = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, params: {[key: string]: string}): {state: nkruntime.MatchState, tickRate: number, label: string} {
  logger.debug('Lobby match created');

  const presences: {[userId: string]: nkruntime.Presence} = {};

  return {
    state: { presences },
    tickRate: 1,
    label: ''
  };
};

const matchJoinAttempt = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presence: nkruntime.Presence, metadata: {[key: string]: any }) : {state: nkruntime.MatchState, accept: boolean, rejectMessage?: string | undefined } | null {
  logger.debug('%q attempted to join Lobby match', ctx.userId);

  return {
    state,
    accept: true
  };
}

const matchJoin = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presences: nkruntime.Presence[]) : { state: nkruntime.MatchState } | null {
  presences.forEach(function (presence) {
    state.presences[presence.userId] = presence;
    logger.debug('%q joined Lobby match', presence.userId);
  });

  return {
    state
  };
}

const matchLeave = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presences: nkruntime.Presence[]) : { state: nkruntime.MatchState } | null {
  presences.forEach(function (presence) {
    delete (state.presences[presence.userId]);
    logger.debug('%q left Lobby match', presence.userId);
  });

  return {
    state
  };
}

const matchLoop = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, messages: nkruntime.MatchMessage[]) : { state: nkruntime.MatchState} | null {
  logger.debug('Lobby match loop executed');

  Object.keys(state.presences).forEach(function (key) {
    const presence = state.presences[key];
    logger.info('Presence %v name $v', presence.userId, presence.username);
  });

  messages.forEach(function (message) {
    logger.info('Received %v from %v', message.data, message.sender.userId);
    dispatcher.broadcastMessage(1, message.data, [message.sender], null);
  });

  return {
    state
  };
}

const matchTerminate = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, graceSeconds: number) : { state: nkruntime.MatchState} | null {
  logger.debug('Lobby match terminated');

  const message = `Server shutting down in ${graceSeconds} seconds.`;
  dispatcher.broadcastMessage(2, message, null, null);

  return {
    state
  };
}

const matchSignal = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, data: string) : { state: nkruntime.MatchState, data?: string } | null {
  logger.debug('Lobby match signal received: ' + data);

  return {
    state,
    data: "Lobby match signal received: " + data
  };
}

上下文 #

此示例演示如何提取调用用户的 ID 和作为环境变量存储的密钥:

1
2
3
4
5
6
7
8
9
// Getting the calling user ID from the context
const userId = ctx.userId;

// Getting the environment variables from the context
const env = ctx.env;
const secretKey = env["SECRET_KEY"];
if !secretKey {
  // Did not find the environment variable
}

数据库处理程序 #

此示例创建系统 ID(不能从客户端使用的 ID),自定义 SQL 查询将其插入 users 表中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let InitModule: nkruntime.InitModule =
        function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, initializer: nkruntime.Initializer) {
    let systemId: string = ctx.env["SYSTEM_ID"]

    nk.sqlExec(`
INSERT INTO users (id, username)
VALUES ($1, $2)
ON CONFLICT (id) DO NOTHING
    `, { systemId, "system_id" })

    logger.Info('system id: %s', systemId)
}

RPC #

以下示例注册带有标识符 custom_rpc_func_id 的函数。然后可在客户端代码中使用此 ID 发送 RPC 消息,在服务器上执行函数并返回结果。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let customFuncRpc: nkruntime.RpcFunction =
function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string) {
	logger.info('payload: %q', payload);

	// "payload" is bytes sent by the client we'll JSON decode it.
	let json = JSON.parse(payload);

	return JSON.stringify(json);
}

// Register as an after hook for the appropriate feature, this call should be in InitModule.
initializer.registerRpc("custom_rpc_func_id", customFuncRpc);

Before 挂钩 #

以下代码示例获取当前用户的配置文件,检查元数据。通常假定此元数据采用 JSON 编码,其中有 "{level: 12}"。如果用户级别太低,则会引发一个错误,以阻止在服务器中向前传递“好友添加”消息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let userAddFriendLevelCheck: nkruntime.BeforeHookFunction<AddFriendsRequest> =
function(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, data: nkruntime.AddFriendsRequest): nkruntime.AddFriendsRequest {
	let userId = ctx.userId;

	let users: nkruntime.User[];
	try {
			users = nk.usersGetId([ userId ]);
	} catch (error) {
			logger.error('Failed to get user: %s', error.message);
			throw error;
	}

	// Let's assume we've stored a user's level in their metadata.
	if (users[0].metadata.level < 10) {
			throw Error('Must reach level 10 before you can add friends.');
	}

	// important!
	return data;
};

// Register as an after hook for the appropriate feature, this call should be in InitModule.
initializer.registerBeforeAddFriends(userAddFriendLevelCheck);

After 挂钩 #

下面的示例代码在用户添加好友时将记录写入用户的存储。函数返回的任何数据都将被丢弃。

 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
// The AddFriends function does not return a payload, hence why the outPayload argument is null.
let afterAddFriends: nkruntime.AfterHookFunction<null, AddFriendsRequest> =
function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, outPayload: null, inPayload: nkruntime.AddFriendsRequest) {
	let userId = ctx.userId;
	if (!userId) {
			throw Error('Missing user ID.');
	}

	let userIds = inPayload.ids;
	let storageObj: nkruntime.StorageWriteRequest = {
			collection: 'rewards',
			key: 'reward',
			userId: userId,
			value: { userIds },
	};

	try {
			nk.storageWrite([ storageObj ]);
	} catch (error) {
			logger.error('Error writing storage object: %s', error.message);
			throw error;
	};

	return null; // Can be omitted, will return `undefined` implicitly
};

// Register as an after hook for the appropriate feature, this call should be in InitModule.
initializer.registerAfterAddFriends(afterAddFriends);

示例模块 #

我们以 Pokéapi 作为一个有趣的示例,并建立一个实用模块。

 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
const apiBaseUrl = 'https://pokeapi.co/api/v2';

let InitModule: nkruntime.InitModule =
function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, initializer: nkruntime.Initializer) {
	initializer.registerRpc('get_pokemon', getPokemon);
};

function lookupPokemon(nk: nkruntime.Nakama, name: string) {
	let url = apiBaseUrl + '/pokemon/' + name;
	let headers = { 'Accept': 'application/json' };
	let response = nk.httpRequest(url, 'get', headers);

	return JSON.parse(response.body);
}

let getPokemon: nkruntime.RpcFunction =
function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string) {
	// We'll assume payload was sent as JSON and decode it.
	let json = JSON.parse(payload);
	let pokemon;

	try {
		pokemon = lookupPokemon(nk, json['PokemonName']);
	} catch (error) {
		logger.error('An error occurred looking up pokemon: %s', error.message);
		throw error;
	}

	let result = {
		name: pokemon.name,
		height: pokemon.height,
		weight: pokemon.weight,
		image: pokemon.sprites.front_default,
	}

	return JSON.stringify(result);
}

现在,您可以从客户端对 Pokémon 进行 RPC 调用:

Client
1
2
3
curl "http://127.0.0.1:7350/v2/rpc/get_pokemon" \
  -H 'authorization: Bearer <session token>'
  -d '"{"PokemonName": "dragonite"}"'
Client
1
2
3
4
const payload = { "PokemonName": "dragonite"};
const rpcid = "get_pokemon";
const pokemonInfo = await client.rpc(session, rpcid, payload);
console.log("Retrieved pokemon info: %o", pokemonInfo);
Client
1
2
3
4
var payload = JsonWriter.ToJson(new { PokemonName = "dragonite" });
var rpcid = "get_pokemon";
var pokemonInfo = await client.RpcAsync(session, rpcid, payload);
System.Console.WriteLine("Retrieved pokemon info: {0}", pokemonInfo);
Client
1
2
3
4
5
6
7
8
auto successCallback = [](const NRpc& rpc)
{
  	std::cout << "Retrieved pokemon info: " << rpc.payload << std::endl;
};

string payload = "{ "PokemonName": "dragonite" }";
string rpcid = "get_pokemon";
client->rpc(session, rpcid, payload, successCallback);
Client
1
2
3
4
5
6
Map<String, String> payloadData = new HashMap<>();
payloadData.put("PokemonName", "dragonite");
String payload = new Gson().toJson(payloadData, payloadData.getClass());
String rpcid = "get_pokemon";
Rpc pokemonInfo = client.rpc(session, rpcid, payload);
System.out.format("Retrieved pokemon info: %s", pokemonInfo.getPayload());
Client
1
2
3
4
5
6
7
var payload = {"PokemonName": "dragonite"}
var rpc_id = "get_pokemon"
var pokemon_info : NakamaAPI.ApiRpc = yield(client.rpc_async(session, rpc_id, JSON.print(payload)), "completed")
if pokemon_info.is_exception():
	print("An error occurred: %s" % pokemon_info)
	return
print("Retrieved pokemon info: %s" % [parse_json(pokemon_info.payload)])
Client
1
2
3
4
5
6
7
8
POST /v2/rpc/get_pokemon
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "PokemonName": "dragonite"
}
Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.

Related Pages