Nakama official client

Sorry, guys, I’m late to the party :sweat_smile:

@britzl is correct, as always :slight_smile: just some notes from me:

  • minimal working matchmaker message for 1x1 multiplayer would look like
            matchmaker_add = { 
                min_count = 2,
                max_count = 2,
                query = "*",
            }

Once you just test it’s working, add the filters to your query or extra properties.

  • define your listener within nk.sync() wrapper: in practice, you’d want to connect the socket first and only then add the listenter
nakama.sync(function()
    local ok, err = nakama.socket_connect(socket)

    nakama.on_matchmakermatched(socket, function(message)
    -- omitted code
    end)

    local matchmaker_add_message = {
    -- omitted code
    }
    local ticket = nakama.socket_send(socket, matchmaker_add_message)
end)
  • keep the ticket and cancel the matchmaking request as soon as the players join the match;
  • in a match, use op_code field to distinguish the events you send between the players. You can map those to event types/names (if your multiplayer is event-based) or state updates (if you’re sharing the state), and refer to them in the code. Don’t neglect this field (it’s easy because docs don’t advertise this much), it’s a simple but powerful way to keep your messages short while sharing event information between the clients;
  • once you feel like your matchmaking logic needs some adjustments and you might need an authoritative server, don’t hesitate much, it’s quite easy to write as well and it’s in Lua, too.

Also, just had a look at Defold client guide, it looks exactly the way I needed it when I just started, so you’ll be riding Nakama in no time! :smile:
Good luck and share your impressions!

8 Likes

Thank you both for your help!

@britzl - I had been looking at the matchmaking documentation, but something had made me get stuck on “well I can’t matchmake until I have a match, right?” Now that I look at your code + the documentation, I understand that you have to register users with the matchmaker and it’s when two or more registered users fit the correct criteria that a match is created.

I think if I step back a bit, it boils down to my learning style. I have always struggled processing/retaining written information, and I learn much better by doing. That’s why I find examples much more useful than explanations. I’m saying this because I don’t want to come across as lazy, I have been all over the documentation - it just often doesn’t click with me, especially when I’m learning something very new.

@uncle_Night - thank you very much for your additional notes! I think they will be really helpful. I am also impressed that you got so far without specific docs. :grin:

2 Likes

It’s just I’m the guy who learns from docs and reading the code and breaking it down until realization hits :smiley:
Nakama docs are quite well-structured (at least those that are there), btw, they all follow the same pattern of “explain what, explain why, show examples”. And that is good because they give you an idea about the building blocks and how it works before they let you code stuff, so by the time you write your own lines (or shamelessly copy-paste the example) you at least know what you’re doing it for :smile: There are quirks though but by now Defold client is stable enough to just take-and-use.

1 Like

I’m hoping to find the time to build a full example of how to use the Nakama Defold client. The Godot client has an example project (game) if I’m not mistaken.

Also ping @novabyte as there is some good discussion here about different learning styles and perhaps ways in which HeroicLabs can cater to all of these?

3 Likes

Hi all :wave:

Big thanks to @britzl and @uncle_Night for the code examples and help with getting started with Nakama.

some good discussion here about different learning styles and perhaps ways in which HeroicLabs can cater to all of these?

Definitely. I’ll add a few notes and appreciate all the feedback on improvements we can make to the docs. We’ve often found it hard to keep pace on the documentation as we add features to meet the requirements of different game teams. We’ve brought on some engineering help with this so expect to see big improvements :slight_smile:

I’m looking at the Function reference docs as well and I see match create mentioned there, but the Lua require makes me think it’s to do with the modules folder (i.e. server side?) and not with the client side.

Yep the function reference has all the code you have available in the server framework to write custom logic that will run on the server. This is useful for implementing metagame features like: inventory, collectables, daily rewards, player stats, and really anything else specific to the game in the server.

I had been looking at the matchmaking documentation, but something had made me get stuck on “well I can’t matchmake until I have a match, right?”

As you mentioned in Nakama you must find players, find a match, or create a match for it to exist on the server. I think in your case you expected the opposite to be true where a match must exist before you can matchmake. I’ve made a note and we can make this clearer in our docs.

p2p messaging should be more than enough to achieve it so I’m not overly concerned about doing authoritative server things at this point

For client authoritative multiplayer you create the multiplayer match with a socket call or you can use the matchmaker to match player who’re waiting to play together or you can write a small RPC function on the server and call it to create or find a match with match listings. These 3 options are described a bit loosely in the docs:

  1. https://heroiclabs.com/docs/gameplay-multiplayer-realtime/#create-a-match
  2. https://heroiclabs.com/docs/gameplay-matchmaker/
  3. It doesn’t look like we have a code example on how to find or create a match with a simple RPC function yet :confused:

Which of the above you use to create a multiplayer match depends on the type of multiplayer game you’re working on. You can also combine the options above so if you had a player challenge mode it could use option (1) but in finding other players to play you could use option (2) or (3).

I’m hoping to find the time to build a full example of how to use the Nakama Defold client. The Godot client has an example project (game) if I’m not mistaken.

Yep. There’s a small sample project that actually uses Lua for the server-side logic and demonstrates the basics of a multiplayer match handler in Nakama. You can take a peek at the code here:

https://github.com/heroiclabs/nakama-godot-demo/tree/master/nakama/modules


I am successfully connecting to the pineapple-pizza-lovers-room (best room, btw)

Haha. What can I say. Pizza and pineapple is :100: the winner. :smiley:

4 Likes

I have made a lot of progress, thanks to everyone for the help.

use op_code field to distinguish the events you send between the players

@uncle_Night thanks for this advice. Sounds like op_code is kind of similar to message_id in Defold.

On to my current position.

I am now able to use the matchmaker to, well, matchmake! I get the callback successfully and am then able to actually join/create the match. My issue now is I am unable to get the format of the data right for a match data message.

I know my socket is right, I know I have the right match id and that I am using op_code correctly. I know this because I can send a match data message with no data:

My code (with no data)

local match_data = {
	match_data_send = {
		match_id = match.match.match_id,
		op_code = 1337,
		data = {}
	}
}

pprint(match_data)
print("sending match data...")
nakama.socket_send(socket, match_data, function(message)
	print("data send callback")
	pprint(message)
end)

Callback successful :+1:

image

If I try to send a regular Lua table:

data = {some_var = "some_value"}

My on_disconnect callback is triggered and I see the following in the Docker log for Nakama (Received malformed payload):

{"level":"warn","ts":"2020-07-24T08:26:47.251Z","msg":"Received malformed payload","uid":"8a99c291-36b9-4121-994f-4d6a0c94764c","sid":"723c7437-daca-47a4-b295-2c331c28be75","data":"eyJtYXRjaF9kYXRhX3NlbmQiOnsibWF0Y2hfaWQiOiJlNWE2Njg1ZS0wN2JlLTRjMWMtODM4ZC0zODZlMTEzZTg4NWUuIiwib3BfY29kZSI6MTMzNywiZGF0YSI6eyJzb21lX3ZhciI6InNvbWVfdmFsdWUifX0sImNpZCI6IjMifQ=="}

{"level":"info","ts":"2020-07-24T08:26:47.251Z","msg":"Closed client connection","uid":"8a99c291-36b9-4121-994f-4d6a0c94764c","sid":"723c7437-daca-47a4-b295-2c331c28be75"}

I know from the docs that there is a specific format required, like JSON. So I try to encode a table of values using CJSON:

data = cjson.encode({move = {dir = "left", steps = 4}})

If I just print the encode itself I get:

{"move":{"dir":"left", "steps":4}}

This exactly matches the documentation. But if I pprint the table I am passing into the socket_send, the data table is wrapped in quotation marks to make it a string. I assume this is the reason for the payload being malformed:

"{"move":{"dir":"left", "steps":4}}"

Am I on the right path? What am I missing? I just can’t figure out how to format the data properly. :thinking:

1 Like

I specifically emphasized this in Defold client readme:

Nakama supports any binary content in data attribute of a match message. Regardless of your data type, the server only accepts base64-encoded data , so make sure you don’t post plain-text data or even JSON, or Nakama server will claim the data malformed and disconnect your client (set server logging to debug to detect these events).

There are utility functions and the example in README.md, just check it out! :wink:

– Edit: accidentally logged in and posted from another mail :sweat_smile:

2 Likes

@uncle_Night Please open an issue on the Defold client. I think we should improve this behaviour. It’s not required to know with Nakama that the message content needs to be base64 encoded. This is an implementation detail when the socket is in text rather than binary mode and handled internally by our other client sdks.

The implementation details for why its designed that way is that we optimize for Protobuf over binary packets but have a fallback mode which is JSON-encoded protobuf messages when the text socket mode is used. The protobuf messages use bytes type in which cannot be represented in JavaScript as JSON so its base64 encoded by the protobuf toolchain.

Hope that helps.

3 Likes

:man_facepalming: Sorry that I missed that part of the documentation, @uncle_Night . Thank you for pointing it out.

For the benefit of anyone that may have the same question in the future, I have now set it up as:

local data = md.encode({move = {dir = "left", steps = 4}})

Remember that you need to require the matchdata utility to use md.encode:

local md = require "nakama.util.matchdata"

My on_matchdata callback:

nakama.on_matchdata(socket, function(message)
     print("match data received")
     pprint(md.decode(message.match_data.data))
end)

image

:partying_face: :partying_face: :partying_face:

1 Like

Remember op_code I was talking about? With a map you can shorten the message to this:

local OP_CODES = {
    move_left = 11,
    move_right = 12,
}

local net_msg = {
    match_data_send = {
        match_id = match.match.match_id,
        op_code = OP_CODES.move_left,
        data = md.encode({steps = 4}),
    }
}

See? Way shorter and more efficient (you’ll have to use ifs anyway later, regardless of using OP_CODES or not - so why make it more complicated?).

1 Like

Yes, I think I understand. In my situation I will have a lot of yes/no votes broadcast.

So the “bad” way would be:

local OP_CODES = {
    vote = 11
}

local net_msg = {
    match_data_send = {
        match_id = match.match.match_id,
        op_code = OP_CODES.vote,
        data = md.encode({result = "yes"}),
    }
}

A more efficient way would be:

local OP_CODES = {
    vote_yes = 11,
    vote_no = 12,
}

local net_msg = {
    match_data_send = {
        match_id = match.match.match_id,
        op_code = OP_CODES.vote_yes,
        data = {},
    }
}

I agree. We should look into this.

EDIT: https://github.com/heroiclabs/nakama-defold/issues/12

2 Likes

Hi there.
Everything work very good, but I have always got Disconnected when try to call socket_send on build HTML5.
DEBUG:SCRIPT: connect true nil
DEBUG:SCRIPT: Disconnected
I’m using example.script for my project because example prj cannot build HTML5.

Does some one can help on this case?
Thanks guys. :partying_face:

1 Like

Ok, HTML5 should work as well. Would you mind creating a ticket in the extension repo on GitHub? Please include steps to reproduce etc.

1 Like

I couldn’t find Lua reference to create a Match (not a matchmaker) , i used the javascript api as refence .
To Create Match in js you have to use

var response = await socket.createMatch();
console.log("Created match with ID:", response.match.match_id);

You can share the match id with other players to join .
i want to do the same thing using Defold and Lua .
if i am not mistaken , the same should be in Lua :

local match_create_message = {
        match_create = {} -- empty , no arguments		
}
		
local match_result = nakama.socket_send(socket, match_create_message,callback)

This caused the server to disconnect with :

{"level":"warn","ts":"2021-01-04T11:23:32.727Z","msg":"Received malformed payload","uid":"51fc50d1-da4e-4f4c-b74a-ea60e75bad37","sid":"71335a58-9d9d-4d72-8cd4-3942df576269","data":"eyJjaWQiOiIxIiwibWF0Y2hfY3JlYXRlIjpbXX0="}

after decoding the data

{"cid":"1","match_create":[]}

Please have a look here: Nakama official client

I have checked the Official defold client , i couldn’t find a way through making a custom Match(not a match maker) , similar to Realtime Multiplayer - Nakama server (heroiclabs.com)

The error seems to be this

{"cid":"1","match_create":[]}

It should be

{"cid":"1","match_create":{}}

Edit.
I have tested and this is the problem.

To fix it, you to have to patch nakama.engine.defold.lua
You properly have to copy manually the nakama modules to your project.

Change function M.socket_send to be

function M.socket_send(socket, message, callback)
	assert(socket and socket.ws, "You must provide a socket")
	assert(message, "You must provide a message to send")
	socket.cid = socket.cid + 1
	message.cid = tostring(socket.cid)

	socket.requests[message.cid] = callback
	local data = json.encode(message)
	local clean_data = string.gsub(data, "%[%]", "{}")
	pprint("Todo remove", clean_data)
	socket.ws:send(clean_data) 
end

Please create a ticket in the Nakama repo and I’ll make sure to fix it! It should be solved in the Nakama client.