Nakama는 소셜과 실시간 게임을 위한 확장이 가능한 서버입니다. 유저인증, 소셜기능, 데이터 저장, 실시간 패킷 통신이 가능합니다. 실시간 게임과 앱을 위해 필수적인 서비스를 제공합니다. 자체 서버에 설치해서 사용할 수도 있고, Heroic Cloud 서비스를 이용할 수도 있습니다. 계정인증, 채팅, 친구, 길드, 리더보드, 데이터 저장, 매치메이킹, 실시간 멀티플레이 기능을 제공합니다.
<host>:7351
localhost:7351
모두 .NET 예제 입니다.
const string scheme = "http";
const string host = "<my_host>";
const int port = 7350;
const string serverKey = "defaultkey";
var client = new Client(scheme, host, port, serverKey);
var email = "<user_email>";
var password = "<password>";
var sessionTask = client.AuthenticateEmailAsync(email, password);
var session = sessionTask.Result;
Console.WriteLine($"UserId: {session.UserId}, UserName: {session.Username}");
Console.WriteLine($"AuthToken: {session.AuthToken}");
var socket = Socket.From(client);
socket.Connected += () =>
{
System.Console.WriteLine("Socket connected.");
};
socket.Closed += () =>
{
System.Console.WriteLine("Socket closed.");
};
socket.ReceivedError += e => System.Console.WriteLine(e);
var connectTask = socket.ConnectAsync(session);
connectTask.Wait();
1대1 채팅과 그룹챗이 가능합니다. 상대가 온라인이면 바로 전달되고, 메세지 히스토리가 DB에 저장되기 때문에, 나중에 접속하면 메세지를 받을 수 있습니다. 채널이 있어서 어떤 유저가 메세지를 받아야 할 지 알수있습니다. 유저는 동시에 여러개의 채널에 참여할 수 있습니다.
아래와 같이 3종류의 채널이 있습니다.
socket.ReceivedChannelMessage += message =>
{
Console.WriteLine("Received: {0}", message);
Console.WriteLine("Message has channel id: {0}", message.ChannelId);
Console.WriteLine("Message content: {0}", message.Content);
};
// 단톡방
var roomname = "MarvelMovieFans";
var persistence = true;
var hidden = false;
var channel = await socket.JoinChatAsync(roomname, ChannelType.Room, persistence, hidden);
Console.WriteLine("Now connected to channel id: '{0}'", channel.Id);
// 그룹챗(길드챗)
var groupId = "<group id>";
var persistence = true;
var hidden = false;
var channel = await socket.JoinChatAsync(groupId, ChannelType.Group, persistence, hidden);
Console.WriteLine("You can now send messages to channel id: '{0}'", channel.Id);
// 귓속말
var userId = "<user id>";
var persistence = true;
var hidden = false;
var channel = await socket.JoinChatAsync(userId, ChannelType.DirectMessage, persistence, hidden);
Console.WriteLine("You can now send messages to channel id: '{0}'", channel.Id);
var roomUsers = new List<IUserPresence>(10);
socket.ReceivedChannelPresence += presenceEvent =>
{
foreach (var presence in presenceEvent.Leaves)
{
roomUsers.Remove(presence);
}
roomUsers.AddRange(presenceEvent.Joins);
Console.WriteLine("Room users: [{0}]", string.Join(",\n ", roomUsers));
};
var roomName = "PizzaFans";
var persistence = true;
var hidden = false;
var channel = await socket.JoinChatAsync(roomName, ChannelType.Room, persistence, hidden);
roomUsers.AddRange(channel.Presences);
var channelId = "<channel id>";
var content = new Dictionary<string, string> {{"msg", "hello, world"}, {"time", "11112222333"}}.ToJson();
var sendAck = await socket.WriteChatMessageAsync(channelId, content);
var channelId = "<channel id>";
await socket.LeaveChatAsync(channelId);
var channelId = "<channel id>";
var result = await client.ListChannelMessagesAsync(session, channelId, 10, true);
foreach (var m in result.Messages)
{
Console.WriteLine("Message id '{0}' content '{1}'", m.MessageId, m.Content);
}
var ids = new[] {"user-id1", "user-id2"};
var usernames = new[] {"username1"};
await client.AddFriendsAsync(session, ids, usernames);
var result = await client.ListFriendsAsync(session);
foreach (var f in result.Friends)
{
System.Console.WriteLine("Friend '{0}' state '{1}'", f.User.Username, f.State);
}
var ids = new[] {"user-id1", "user-id2"};
var usernames = new[] {"username1"};
await client.DeleteFriendsAsync(session, ids, usernames);
var ids = new[] {"user-id1", "user-id2"};
var usernames = new[] {"username1"};
await client.BlockFriendsAsync(session, ids, usernames);
실시간 멀티플레이어 엔진은 방을 만들고 그 안에서 패킷를 주고 받는 작업을 쉽게 해줍니다. 유저는 방을 만들고 입장하고 퇴장할 수 있습니다. 방에서 클라이언트가 보내는 메세지는 다른 참여자들에게 전달됩니다. 방은 메모리에 저장되지만 계속 유지 될 수도 있습니다.
var match = await socket.CreateMatchAsync();
Console.WriteLine("New match with id '{0}'.", match.Id);
var matchId = "<matchid>";
var match = await socket.JoinMatchAsync(matchId);
foreach (var presence in match.Presences)
{
Console.WriteLine("User id '{0}' name '{1}'.", presence.UserId, presence.Username);
}
var connectedOpponents = new List<IUserPresence>(2);
socket.ReceivedMatchPresence += presenceEvent =>
{
foreach (var presence in presenceEvent.Leaves)
{
connectedOpponents.Remove(presence);
}
connectedOpponents.AddRange(presenceEvent.Joins);
// Remove yourself from connected opponents.
connectedOpponents.Remove(self);
Console.WriteLine("Connected opponents: [{0}]", string.Join(",\n ", connectedOpponents));
};
// using Nakama.TinyJson;
// send json
var matchId = "<matchid>";
var opCode = 1;
var newState = new Dictionary<string, string> {{"msg", "hello, world!"} }.ToJson();
socket.SendMatchStateAsync(matchId, opCode, newState);
// send binary data
var customOpCode = 101;
var sendBytes = BuildBytes(...);
socket.SendMatchStateAsync(matchId, customOpCode, sendBytes);
// Use whatever decoder for your message contents.
var enc = System.Text.Encoding.UTF8;
socket.ReceivedMatchState += newState =>
{
var content = enc.GetString(newState.State);
switch (newState.OpCode)
{
case customOpCode:
Console.WriteLine("A custom opcode.");
var bytes = newState.State;
// parse bytes
break;
default:
Console.WriteLine("User '{0}'' sent '{1}'", newState.UserPresence.Username, content);
}
};
var matchId = "<matchid>";
await socket.LeaveMatchAsync(matchId);
Nakama