코드 샘플 #

이 페이지는 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를 생성하고 사용자 지정 SQL 쿼리는 이 ID를 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);

사전 후크 #

아래의 코드 예시는 현재 사용자의 프로필을 가져와서 "{level: 12}"(으)로 인코딩된 JSON으로 추정되는 메타데이터를 확인합니다. 사용자의 수준이 낮은 경우, 오류가 발생하여 친구 추가 메시지가 서버에서 전달되지 않도록 합니다:

 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);

사후 후크 #

아래의 예시 코드는 사용자가 친구를 추가할 때 사용자의 저장소에 레코드를 작성합니다. 함수에서 반환되는 모든 데이터는 삭제됩니다.

 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