# 코드 샘플

**URL:** https://heroiclabs.com/docs/kr/nakama/server-framework/lua-runtime/code-samples/
**Summary:** Lua 런타임에 대한 개발 예시.
**Keywords:** nakama lua development, sql, rpc, registerrpc, setup
**Categories:** server framework, setup

---


# 코드 샘플

이 페이지는 Lua 런타임을 사용하여 프로젝트를 개발할 때 템플릿으로 사용할 수 있는 기능에 대한 공통적인 예시를 제공합니다.

## 대결 핸들러

대결 핸들러는 [권한 보유 멀티플레이어](../../../concepts/multiplayer/authoritative/) 대결에 대한 게임 입력과 운영을 처리하기 위한 서버 측 함수를 모두 표시합니다. [대결 핸들러 API](../function-reference/match-handler/) 및 [대결 런타임 API](../function-reference/match-runtime/) 참조 페이지에서 대결 핸들러 기능을 살펴봅니다.

이것은 탁구 대결 핸들러에 대한 예시입니다. 서버에 수신된 메시지는 메시지를 발송한 사람한테 다시 전달됩니다.

```lua
local nk = require("nakama")

local M = {}

function M.match_init(context, setupstate)
  local gamestate = {
    presences = {}
  }

  local tickrate = 1 -- per sec
  local label = ""

  return gamestate, tickrate, label
end

function M.match_join_attempt(context, dispatcher, tick, state, presence, metadata)
  local acceptuser = true
  return state, acceptuser
end

function M.match_join(context, dispatcher, tick, state, presences)
  for _, presence in ipairs(presences) do
    state.presences[presence.session_id] = presence
  end

  return state
end

function M.match_leave(context, dispatcher, tick, state, presences)
  for _, presence in ipairs(presences) do
    state.presences[presence.session_id] = nil
  end

  return state
end

function M.match_loop(context, dispatcher, tick, state, messages)
  for _, p in pairs(state.presences) do
    nk.logger_info(string.format("Presence %s named %s", p.user_id, p.username))
  end

  for _, m in ipairs(messages) do
    nk.logger_info(string.format("Received %s from %s", m.data, m.sender.username))
    local decoded = nk.json_decode(m.data)

    for k, v in pairs(decoded) do
      nk.logger_info(string.format("Key %s contains value %s", k, v))
    end

    -- PONG message back to sender
    dispatcher.broadcast_message(1, m.data, { m.sender })
  end

  return state
end

function M.match_terminate(context, dispatcher, tick, state, grace_seconds)
  local message = "Server shutting down in " .. grace_seconds .. " seconds"
  dispatcher.broadcast_message(2, message)

  return nil
end

local function match_signal(context, dispatcher, tick, state, data)
  return state, "signal received: " .. data
end

return M
```

## 컨텍스트

```lua
-- Getting the calling user ID from the context
local user_id = context.user_id

-- Getting the environment variables from the context
local env = context.env
local secret_key = env["SECRET_KEY"]
if not secret_key then
  -- Did not find the environment variable
end
```

## 데이터베이스 핸들러

이 예시는 클라이언트에서 사용할 수 없는 시스템 ID를 생성하고 사용자 지정 SQL 쿼리는 이 ID를 `users` 테이블로 입력합니다.

```lua
nk.run_once(function(context)
  local system_id = context.env["SYSTEM_ID"]

  nk.sql_exec([[
INSERT INTO users (id, username)
VALUES ($1, $2)
ON CONFLICT (id) DO NOTHING
  ]], { system_id, "system_id" })
end)
```

## RPC

아래의 예시는 `custom_rpc_func_id` 식별자를 사용하여 함수를 등록합니다. 클라이언트 코드 내에서 ID를 사용하여 RPC 메시지를 전송하고, 서버에서 함수를 실행하여 결과를 반환합니다.

```lua
local nk = require("nakama")

local function custom_rpc_func(context, payload)
  nk.logger_info(string.format("Payload: %q", payload))

  -- "payload" is bytes sent by the client we'll JSON decode it.
  local json = nk.json_decode(payload)

  return nk.json_encode(json)
end

nk.register_rpc(custom_rpc_func, "custom_rpc_func_id")
```

## 사전 후크

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

```lua
local nk = require("nakama")

local function limit_friends(context, payload)
  local user = nk.users_get_id({context.user_id})[1]
  -- Let's assume we've stored a user's level in their metadata.
  if user.metadata.level < 10 then
      error("Must reach level 10 before you can add friends.")
  end
  return payload -- important!
end

nk.register_req_before(limit_friends, "AddFriends")
```

## 사후 후크

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

```lua
local nk = require("nakama")

local function add_reward(context, outgoing_payload, incoming_payload)
  local value = {
    user_ids = {incoming_payload.user_id}
  }

  local object = {
    collection = "rewards",
    key = "reward",
    user_id = context.user_id,
    value = value
  }

  nk.storage_write({ object })
end

nk.register_req_after(add_reward, "AddFriends")
```

## 예시 모듈

예시로 [Pokéapi](http://pokeapi.co/)를 사용하여 "pokeapi.lua"라고 하는 모듈을 만들어보겠습니다.

```lua
local nk = require("nakama")

local M = {}

local API_BASE_URL = "https://pokeapi.co/api/v2"

function M.lookup_pokemon(name)
  local url = string.format("%s/pokemon/%s", API_BASE_URL, name)
  local method = "GET"
  local headers = {
    ["Content-Type"] = "application/json",
    ["Accept"] = "application/json"
  }

  local success, code, _, body = pcall(nk.http_request, url, method, headers, nil)

	if (not success) then
    nk.logger_error(string.format("Failed request %q", code))
    error(code)
  elseif (code >= 400) then
    nk.logger_error(string.format("Failed request %q %q", code, body))
    error(body)
  else
    return nk.json_decode(body)
  end
end

return M

-- We can import the code up to this point into another module we'll call "pokemon.lua" which will register an RPC call.
local nk = require("nakama")
local pokeapi = require("pokeapi")

local function get_pokemon(_, payload)
  -- We'll assume payload was sent as JSON and decode it.
  local json = nk.json_decode(payload)
  local success, result = pcall(pokeapi.lookup_pokemon, json.PokemonName)

	if (not success) then
    error("Unable to lookup pokemon.")
  else
    local pokemon = {
      name = result.name,
      height = result.height,
      weight = result.weight,
      image = result.sprites.front_default
    }

    return nk.json_encode(pokemon)
  end
end

nk.register_rpc(get_pokemon, "get_pokemon")
```

이제 클라이언트에서 Pokémon에 대한 RPC 호출을 생성할 수 있습니다:

{{< code type="client" >}}
```bash
curl "http://127.0.0.1:7350/v2/rpc/get_pokemon" \
  -H 'authorization: Bearer <session token>'
  -d '"{"PokemonName": "dragonite"}"'
```
{{< / code >}}270

{{< code type="client" >}}
```javascript
const payload = { "PokemonName": "dragonite"};
const rpcid = "get_pokemon";
const pokemonInfo = await client.rpc(session, rpcid, payload);
console.log("Retrieved pokemon info: %o", pokemonInfo);
```
{{< / code >}}270

{{< code type="client" >}}
```csharp
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);
```
{{< / code >}}270

{{< code type="client" >}}
```cpp
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);
```
{{< / code >}}270

{{< code type="client" >}}
```java
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());
```
{{< / code >}}270

{{< code type="client" framework="godot3" >}}
```gdscript
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)])
```
{{< / code >}}270


{{< code type="client" >}}
```shell
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 >}}270

{{< missing type="client" lang="lua" framework="defold" />}}
