Nakama official client

I wanted to share with the Defold community that in collaboration with the Defold team we’ve completed the initial release of the official Defold client:

https://github.com/heroiclabs/nakama-defold :tada:

More news will be announced later in the week but we’d love your feedback and support in the development of the code as open-source. :+1:

24 Likes

Cool! Thanks for supporting and sharing!

Hmm getting a 404 error clicking on the full documentation link on github. Otherwise, looks useful. Thanks for sharing. ^ _ ^

1 Like

Hi @novabyte!

Thanks to you (and britzl and whoever else worked on it) for making this happen.

I’ve made some headway in exploring Nakama…

  • I have Nakama and CockroachDB running in Docker
  • I have the example match module in place (I added a print(), and I can see the output in the Docker logs so I know it’s working)
  • I am successfully connecting to the pineapple-pizza-lovers-room (best room, btw) using the examples here

I am completely stumped when it comes to doing anything in relation to creating a match. I have no idea how to communicate with the example match.lua module from my Defold client, and regardless the example module doesn’t come with match creation functionality anyway. I’ve spent all day on this so it’s possible that my mind is just foggy and missing the obvious somewhere.

I know generally it’s something to do with match_create, but since that doesn’t exist in nakama.lua in the Defold client, I am assuming it’s somewhere else. I know the Lua docs are still WIP so I’ve looked at the Cocos2d-x JS docs for now but that doesn’t seem to cover what I’m missing.

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.

I am trying to pick up sufficient tools to create a very simple multiplayer application, p2p messaging should be more than enough to achieve it so I’m not overly concerned about doing authoritative server things at this point. I’m sure it could work either way, I’m just trying to find the most realistic path to achieve it.

Like I said, it’s very possible I’m missing something that’s already covered in the docs. I’d appreciate any pointers on what I should do / read up on. Additionally, if the above makes it sounds like I’m fundamentally misunderstanding some concepts, please let me know as well!

1 Like

They were merged yesterday (https://github.com/heroiclabs/nakama-docs/pull/141) and should be up on the site soon (I hope). The other documentation should be helpful until the Defold docs are available.

There’s another Defold user working with Nakama who have contributed code to the repo (https://github.com/heroiclabs/nakama-defold/pull/7) and he’s been working with matchmaking. Based on his GitHub username and avatar I’m guessing it is forum user @uncle_Night. @uncle_Night do you have some input to share on how to set up matchmaking using Defold and Nakama?

1 Like

Did you read the Matchmaking docs? https://heroiclabs.com/docs/gameplay-matchmaker/

I’m thinking that you need to do something like this:

local client = nakama.create_client(config)
local socket = nakama.create_socket(client)

-- handle matchmaking results
-- https://heroiclabs.com/docs/gameplay-matchmaker/#receive-matchmaker-results
nakama.on_matchmakermatched(socket, function(message)
	pprint(message)
	-- your opponents
	pprint(message.users)

	-- https://heroiclabs.com/docs/gameplay-matchmaker/#join-a-match
	local match_join_message = {
		match_join = {
			token = message.token
		}
	}
	nakama.socket_send(socket, match_join_message);
end)

-- connect and add player to matchmaking pool
nakama.sync(function()
	local ok, err = nakama.socket_connect(socket)

	-- refer to https://heroiclabs.com/docs/gameplay-matchmaker/#properties
	local matchmaker_add_message = {
		matchmaker_add = {
			min_count = 2,
			max_count = 4,
			query = "*",
			string_properties = {
				region = "europe"
			},
			numeric_properties = {
				rank = 8
			}
		}
	}

	-- use the ticket if needed to distinguish between multiple ongoing requets
	-- or to cancel a matchmaking request
	local ticket = nakama.socket_send(socket, matchmaker_add_message)
end)
2 Likes

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