In-game Chat

Talk to other players directly from the game

Chat communication inside the game is great for engagement and to build a thriving user community. In this guide you're going to learn how to make a custom in-game chat.

Gamedonia calls will only work under a valid Gamedonia session. This means there needs to be a user logged in with Gamedonia. The main exceptions are, for obvious reasons, creating and logging users in.

Server logic

First, you'll have to complete a series of steps on the server side.

Create a collection

Create a new collection on the Dashboard. Name it chatroom.

You must create it in order to:

  • Store chat data, and to
  • Connect to other chat rooms in the game.

Collection Model

  • juid - The user who joins the chat room.
  • ty - The type of entry in the collection { join, message }.
  • t - Timestamp of message set by the server.
  • tj - Timestamp of join set by the server.
  • uid - The user who sent the message.
  • m - Message data sent.
  • k - Key to sort message.

Custom scripts

You will need to create Custom Scripts and Server Hooks in order to manage the delivery of chat messages.

Join a chat room

When a user wants to chat, you should make him join a chat room.

This script handles that. If a join type entity already existed then the timestamp is updated.

// For today's date;
Date.prototype.today = function () { 
    return this.getFullYear() + (((this.getMonth()+1) < 10)?"0":"") + (this.getMonth()+1) + ((this.getDate() < 10)?"0":"") + this.getDate();
}

// For the current time
Date.prototype.timeNow = function () {
     return ((this.getHours() < 10)?"0":"") + this.getHours() + ((this.getMinutes() < 10)?"0":"") + this.getMinutes() + ((this.getSeconds() < 10)?"0":"") + this.getSeconds();
}

// Check parameters
function checkParams() {    
    return true;
}

function joinChatRoom(uid) {
    
    //Gather the limit of ended games
    gamedonia.data.search("chatroom", "{juid:\"" + uid + "\"}", {
        success: function(entities) { 
                if (entities.size() == 0) {
                        var currentdate = new Date();
                        var join = gamedonia.data.newEntity(); 
                        join.juid = uid;  
                        join.ty = "join";
                        join.jt = currentdate.today() + "-" + currentdate.timeNow();
                        
                        gamedonia.data.create("chatroom", join, {
                            success: function(results) {
                                response.success(results);
                            }
                            , 
                            error: function(error) {
                                response.error(error.message);
                            }
                        });
                }else{
                    var currentdate = new Date();
                    var join = entities[0];
                    join.jt = currentdate.today() + "-" + currentdate.timeNow();
                    
                    gamedonia.data.update("chatroom", join.get_id(), join, {
                        success: function(results) {
                            response.success(results);
                        }
                        , 
                        error: function(error) {
                            response.error(error.message);
                        }  
                    });
                    
                }
             },         
        error: function(error) {
            response.error(error.message);
        }
    });
    
}

//Join the chat room
if (checkParams()) {
    joinChatRoom(request.user.id);
} else {
    log.error("error checking paramters");
    response.error("error checking paramters");
}

Leave a chat room

When a user leaves a chat room, you probably want to tell the other users in the chat room about it.

// Check parameters
function checkParams() {    
    return true;
}

function leaveChatRoom(uid) {
    
    //Gather the limit of ended games
    gamedonia.data.search("chatroom", "{juid:\"" + uid + "\"}", {
        success: function(entities) { 
                if (entities.size() > 0) {
                        var join = entities.get(0);
                        
                        gamedonia.data.remove("chatroom", join.get_id(), {
                            success: function(results) {
                                response.success("");
                            }
                            , 
                            error: function(error) {
                                response.error(error.message);
                            }
                        });
                }
             },         
        error: function(error) {
            response.error(error.message);
        }
    });
    
}

//Join the chat room
if (checkParams()) {
    leaveChatRoom(request.user.id);
} else {
    log.error("error checking paramters");
    response.error("error checking paramters");
}

leaveChatRoom(request.user.id);

Server Hooks

You'll need to implement server hooks to be able to notify other users in that chat room that a new message has been sent.

Pre save script

The mission of this script is to add a timestamp to sent messages. This is important to identify chat messages with a reference time, in this case, the server time.

As all the chat messages have a reference time, all messages are sorted.

On the other hand, if we have set the timestamp with the device date and hour, messages could be unsorted, because every device may have different dates and times.

// For today's date;
Date.prototype.today = function () { 
    return this.getFullYear() + (((this.getMonth()+1) < 10)?"0":"") + (this.getMonth()+1) + ((this.getDate() < 10)?"0":"") + this.getDate();
}

// For the current time
Date.prototype.timeNow = function () {
     return ((this.getHours() < 10)?"0":"") + this.getHours() + ((this.getMinutes() < 10)?"0":"") + this.getMinutes() + ((this.getSeconds() < 10)?"0":"") + this.getSeconds();
}

/*
 * Main code block
 */
log.info("chatroom presave");
var chatroom = request.object; 
var uid = request.user.id;
var currentdate = new Date();
var t = currentdate.today() + currentdate.timeNow();
var key = t + "-" + uid;
    
chatroom.k = key;
chatroom.uid = uid;   
chatroom.t = t;

Post save script

This script is in charge of sending push notifications to the rest of users in a chat room when a new message gets to the server. The script retrieves every user in the chat room and sends them a notification with the new message.

function sendNotifications(uid, msg){
    //Gather the chat users
    gamedonia.data.search("chatroom", "{$and: [{ty:\"join\"}, {juid: {$ne:\"" + uid + "\"}}]}", {
        success: function(entities) { 
                for (var i=0; i< entities.size(); i++) {
                 
                    var udest = entities[i].juid;
                    
                    notification = new Object();
                    notification.type = "message";
                    notification.alert = request.user.profile.nickname  + " say: " + msg;
                    notification.badge = 1;
                    //log.info(notification.alert + " to " + udest);
                    gamedonia.push.send(notification, udest);
                    
                }
             },         
        error: function(error) {
            log.error(error.message);
        }
    });
}

/*
 * Main code block
 */
log.info("chatroom postsave");
var chatroom = request.object;
var uid = chatroom.uid;
var type = chatroom.ty;
var msg = chatroom.m;
//log.info("chatroom: uid["+uid+"], ty["+type+"], m["+msg+"]");

if (type == "message") {
    //log.info("send message: " + msg + " from " + uid);
    sendNotifications(uid, msg);
}

Client logic

Follow these five steps to configure your client-side logic before your in-game chat is ready to go.

Enable push notifications

The device needs to be registered in the Apple Push Notification Service (APNS) —for iOS— or in the Google Cloud Messaging (GCM) —for Android. When registered, the notification system will be ready to work.

If you need to enable the Push functionality - whether you’re on iOS or Android - see the Push Notification Guide for a full walkthrough.

To receive push notifications related to game changes on your devices, you must follow these single steps:

Attach a handler in your start() function

//Handle push
GDPushService service = new GDPushService();
service.RegisterEvent += new RegisterEventHandler(OnNotification);
GamedoniaPushNotifications.AddService(service);

Then, create a new OnNotification delegate into the body of your script

void OnNotification(Dictionary<string,object> notification) {
    Hashtable payload = notification["payload"] != null ? (Hashtable) notification["payload"] : new Hashtable();
    string type = payload.ContainsKey("type") ? payload["type"].ToString() : "";
    switch(type) {
        case "message":
            SearchNewMessages();
            break;
        default:
            // Do nothing
            break;
        }
    }
}

First your class must inherit from GamedoniaSDKPushListener. After that you can set the handler of the push event.

//Handle push
GamedoniaSDKPush::setDidReceiveRemoteNotificationCallback(this);

Then, override the didReceiveRemoteNotification method into the body of your class.

void MyGame::didReceiveRemoteNotification(CCDictionary* notification)
{
    const CCString* type = notification->valueForKey("type");

    if (type->compare("message") == 0) {
        searchNewMessages(0.0f);
    } else {
        // Do nothing
    }
}

On iOS is too simple to start receiving push notifications as you only need to activate them in your appDelegate that inherits from UIResponder <UIApplicationDelegate>.

[application registerForRemoteNotificationTypes:UIRemoteNotificationTypeBadge];

Then, implement the didReceiveRemoteNotification method into the body of your class.

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
    NSString *itemType = [[userInfo objectForKey:@"type"] description];

    if ([itemType isEqual: @"message"]) {
        searchNewMessages(nil);
    } else {
        // Do nothing
    }
}

First you need to attach a new event to start receiving push notifications.

GamedoniaPush.instance.addEventListener(GamedoniaPushEvent.REMOTE_NOTIFICATION_RECEIVED, onReceiveRemoteNotification);

Then, implement the onReceiveRemoteNotification method into the body of your class.

protected function onReceiveRemoteNotification(event:GamedoniaPushEvent) : void {
    var payload:Object = event.remoteNotification.payload;
    if (payload != null && payload.hasOwnProperty("type")) {
        var type:String = payload.type;
        if (type == "message") {
            searchNewMessages(null);
        } else {
            // Do nothing
        }
    }
}

First you need to attach a new event to start receiving push notifications.

Runtime:addEventListener( "notification", notificationListener )

Then, implement the notificationListener method into the body of your class.

local function notificationListener( event )

    if event.type == "remote" then
        searchNewMessages(nil)
    end
end

When a new notification arrives, this function checks whether the notification is for a chat message and calls the function SearchNewMessages() implemented below.

Google and Apple do not assure you that all push notifications will be sent to the device. To assure every new chat message is received by other users in the chat room, you need to ask the server for any new chat messages by polling in a given period of time.

The basic idea to achieve this is to have a global variable indicating the last time a poll request was made and at a frequent time check for new messages and update that global variable.

Declare a variable that stores the frequency to check for new messages and another containing the last time a check was made.

private static float PollingFrequency = 10.0f;
private float LastPollingTime = 0.0f;

Then you need to code the polling logic in the Update() function of your script.

void Update() {
    LastPollingTime += Time.deltaTime;
    if (LastPollingTime >= PollingFrecuency) {
        LastPollingTime = 0.0f;
        SearchNewMessages();
    }
}

Declare a variable that stores the frequency to check for new messages.

private: float PollingFrequency = 10.0f;

Then you can declare a callback function to be called with an scheduler.

// Activate the scheduler
this->schedule(schedule_selector(MyClass::searchNewMessages), PollingFrequency);

// Deactivate the scheduler
this->unschedule(schedule_selector(MyClass::searchNewMessages));

Declare a variable that stores the frequency to check for new messages and a repeatable timer.

@property float PollingFrequency = 10.0f;
@property (weak) NSTimer *repeatingTimer;

Then you can declare a callback function to be called with the timer.

// Activate the timer
repeatingTimer = [NSTimer scheduledTimerWithTimeInterval:PollingFrequency
                          target:self selector:@selector(searchNewMessages:)
                          userInfo:nil repeats:YES];

// Deactivate the timer
[self.repeatingTimer invalidate];

Declare a variable that stores the frequency to check for new messages and a repeatable timer.

private var PollingFrequency:Number = 10000; //in milliseconds
private var repeatingTimer:Timer;

Then you can declare a callback function to be called with the timer.

repeatingTimer = new Timer(PollingFrequency, 0);
repeatingTimer.addEventListener(TimerEvent.TIMER, searchNewMessages);
repeatingTimer.start();

Declare a variable that stores the frequency to check for new messages and a repeatable timer.

local PollingFrequency = 10000  -- in milliseconds
local repeatingTimer = {}

Then you can declare a callback function to be called with the timer.

// Activate the timer
repeatingTimer = timer.performWithDelay( PollingFrequency, searchNewMessages, 0)

// Deactivate the timer
timer.pause(repeatingTimer)

Join a chat room

To join a chat room, you will need to run the joinchatroom script and handle the callback to, for example, start the polling counter.

// Create the callback function
void OnJoinChat(bool success, object data) {
    if (success) {
        //Start the polling here.
        //TODO: Show info join chat room.
    } else {
        errorMsg = Gamedonia.getLastError().ToString();
        Debug.Log(errorMsg);
    }
}

//Join a Chat Room
GamedoniaScripts.Run("joinchatroom", new Dictionary<string, object>(){}, OnJoinChat);
// Create the callback function
void MyClass::onChatJoined(bool success, CCDictionary* data) {
    if (success) {
        //Start the polling.
        this->schedule(schedule_selector(MyClass::searchNewMessages), PollingFrequency);

        //TODO: Show info join chat room.
    } else {
        //TODO: Process the error
    }
}

//Join a Chat Room
GamedoniaSDKScript::run("joinchatroom", CCDictionary::create(), this, gamedoniaResponseData_selector(MyClass::onChatJoined));
[[Gamedonia script] run:@"joinchatroom" parameters:[NSDictionary alloc] callback:^(BOOL success, NSDictionary *data) {
    if (success) {
        //Start the polling.
        repeatingTimer = [NSTimer scheduledTimerWithTimeInterval:PollingFrequency
                          target:self selector:@selector(searchNewMessages:)
                          userInfo:nil repeats:YES];

        //TODO: Show info join chat room.
    } else {
        //TODO: Process the error
    }
}];
GamedoniaScripts.run("joinchatroom", {}, function (success:Boolean, data:Object):void {
    if (success) {
        //Start the polling.
        repeatingTimer.start();

        //TODO: Show info join chat room.
    } else {
        //TODO: Process the error
    }
});
-- Create the callback function
local onChatJoined = function (success, data)
    if (success)
        -- Start the polling.
        repeatingTimer = timer.performWithDelay( PollingFrequency, searchNewMessages, 0)

        -- TODO: Show info join chat room.
    else 
        -- TODO: Process the error
    end
end

-- Run the server-script to join or create a match
Gamedonia.Scripts.Run("joinchatroom", {}, onChatJoined)

Send a chat message

When you want to send a chat message, you will only need to make a call to GamedoniaSDK API with the content of the message shown below. Just put this fragment of code inside the button pressed event of a button, or wherever you like.

Dictionary<string,object> parameters = new Dictionary<string, object>(){{"m","YOUR_MESSAGE_HERE"},{"ty","message"}};
GamedoniaData.Create("chatroom", parameters, OnSendMessage);
CCDictionary* parameters = CCDictionary::create();
parameters->setObject("message", "type");
parameters->setObject("YOUR_MESSAGE_HERE", "message");

GamedoniaSDKData::update ("chatroom", parameters, false, this, gamedoniaResponseData_selector(MyClass::onSendMessage));
NSDictionary * parameters = [NSDictionary dictionaryWithObjectsAndKeys:
                           @"message", @"ty",
                           "YOUR_MESSAGE_HERE", @"m",
                        nil];

[[Gamedonia data] update:@"chatroom" 
                  entity:parameters
                  overwrite:false
                  callback:onSendMessage];
var parameters:Object = new Object();
parameters.ty = "message";
parameters.m = "YOUR_MESSAGE_HERE";

GamedoniaData.update("chatroom", parameters, false, onSendMessage);
local parameters = {}
parameters.ty = "message"
parameters.m = "YOUR_MESSAGE_HERE"

Gamedonia.Data.update ("chatroom", parameters, false, onSendMessage)

Get messages

To retrieve just the last chat messages and to not ask the server for the whole conversation every time, you must keep the timestamp of the last synced messages in the class attribute

private ulong LastSyncedMsg = 0;
private: unsigned long LastSyncedMsg = 0;
@property NSULong LastSyncedMsg = 0;
private var LastSyncedMsg:Number = 0;
local LastSyncedMsg = 0

When you need to fetch new chat messages from the server (by a push notification or by polling), just do a search call and set it's respective callback.

void SearchNewMessages()
{
    GamedoniaData.Search("chatroom", "{$and: [{ty:\"message\"}, {t:{$gte:\"" + LastSyncedMsg + "\"}}]}", 0, "{t:1}", 0, OnSearchChat);
}
void OnSearchChat(bool success, IList chats) {
    if (success) {
        if(chats != null) {
            //Get last chat
            IDictionary chat = (IDictionary) chats[chats.Count - 1];

            //Set sync time
            var time = chat["t"] as string;
            if (Convert.ToUInt64(time) > LastSyncedMsg)
                LastSyncedMsg = Convert.ToUInt64(time);
        }

        //TODO: Show chat messages here.
    }else {
        errorMsg = Gamedonia.getLastError().ToString();
        Debug.Log(errorMsg);
    }
}
void MyClass::searchNewMessages(float dt)
{
    GamedoniaSDKData::search ("chatroom",
            "{$and: [{ty:\"message\"}, {t:{$gte:\"" + LastSyncedMsg + "\"}}]}",
            0,
            "{'t':1}",
            0,
            this, gamedoniaResponseData_selector(MyClass::onSearchChat));
}

void MyClass::onSearchChat(bool success, CCArray* chats)
{
    if (success && chats != null)
    {
        //Get last chat
        CCDictionary* chat = dynamic_cast<CCDictionary*>(chats->lastObject());

        //Set sync time
        CCString* time = dynamic_cast<CCString*>(chat->objectForKey("t"));
        if (time->uintValue() > LastSyncedMsg) {
            LastSyncedMsg = time->uintValue();
        }

        //TODO: Show chat messages here.
    }else {
        //No new messages found.
    }
}
- (void)searchNewMessages:(NSTimer*)theTimer {
    [[Gamedonia data] search:@"chatroom"
                      query:@"{$and: [{ty:\"message\"}, {t:{$gte:\"" + LastSyncedMsg + "\"}}]}"
                      limit:0
                      sort:@"{'t':1}"
                      skip:0
                      callback:^(BOOL success, NSArray *chats) {
        if (success) {
            if (chats != NULL) {
                //Get last chat
                NSDictionary* chat = [chats lastObject];

                //Set sync time
                NSNumber *time = [NSNumber numberWithLongLong: [chat[@"t"] longLongValue]];
                if (time.unsignedLongValue > LastSyncedMsg) {
                    LastSyncedMsg = time.unsignedLongValue;
                }
            }

            //TODO: Show chat messages here.
        } else {
            //No new messages found.
        }
    }];
}
private function searchNewMessages(event:TimerEvent) : void
{
    GamedoniaData.search("chatroom", "{$and: [{ty:\"message\"}, {t:{$gte:\"" + LastSyncedMsg + "\"}}]}", 0, "{'t':1}", 0, onSearchChat);
}
private function onSearchChat(success:Boolean, chats:Object) : void {
    if (success) {
        if (data != null) {
            //Get last chat
            var chat:Object = chats[chats.length - 1];
            
            //Set sync time
            var time:Number = Number(chat.t);
            if (time > LastSyncedMsg) {
                LastSyncedMsg = time;
            }
        }

        //TODO: Show chat messages here.
    } else {
        //No new messages found.
    }
}
function myClass:searchNewMessages( event )
    Gamedonia.Data.search ("chatroom", query "{$and: [{ty:\"message\"}, {t:{$gte:\"" + LastSyncedMsg + "\"}}]}", searchCB, limit:0, sort "{'t':1}", skip:0)
end
local searchCB = function (succes, chats)
    if success then
        if chats ~= nil then
            -- Get last chat
            local chat = chats[chats.length - 1]
            
            -- Set sync time
            if chat.timestamp > LastSyncedMsg then 
                LastSyncedMsg = chat.t;
            end
        end
        -- TODO: Show chat messages here.
    else
        -- No new messages found.
    end
end