Object & Script

MultiPlayer Object

This is a GM2 persistent object that handles the multiplayer communications and events. You are free to review the code, but no changes to this object should be made. While in beta if you have suggestions to us on ideas to improve the code or performance, please post in the F99 forum and we will take under advisement.

Also the MultiPlayer object expects that it is the first thing in a room to get initilized, so please make sure it is at the top of the call stack.

MultiPlayerCallbacks

This is a script file where you can put intermediate code specific to your game. It is important that the initial stock functions remain in place even if unused. We are open to suggestions for additional events to add to this script to enhance the basic MultiPlayer capabilities.


Architecture

API & WebSockets

The F99 MultiPlayer object uses two forms of communication with the F99 server.

The first is the API layer. This is an https call made using ssl and GM2's built in http_get function. It is for initial connection to receive a session token and when changing subscriptions (rooms). It is done this way to protect sensitive information like the F99 account key. This data is high value/low velocity data.

The second is WebSockets which handles the high volume postional and event data. It is done using an complex internal buffer and queue built in GM2 and the network_send_packet GM2 function. This data is low value/high velocity data.

Publish/Subscribe

The F99 MultiPlayer GM2 object communicates using a pub/sub architecture. There are several key concepts in a Pub/Sub service:

  • Message: The data that moves through the object. Returned data uses GM2's ds_map_find_value to find name value pairs in messages.
  • Room: A named entity that represents a feed of messages. Players in the same room are automatically subscribed to each others messages. A player may only be in one room at a time. Rooms are persistent as long as there is at least one player in the room.
  • Producer: Creates messages and sends (publishes) them to the room.
  • Consumer: Receives messages in a specified room. NOTE: Players do not receive their own messages back from the subscription. So, at a minimum a second player must also be in the room for any callbacks to fire.
  • CallBack: This is the push notification of a new message published within a room. The callback function fires after the message had been processed by the MultiPlayer object so that you can write additional code to handle the data and events.

Variables

Design Time Variables

MultiPlayer.MasterKey: This is the key received from your F99 account. It identifies your account authorization.

MultiPlayer.TalkToPlayers: This is a boolean variable that you can set at design time or run time to activate or deactivate audio chat between all players currently in a room together.

MultiPlayer.MuteMyPlayerAudioChat: This is a boolean variable that turns on/off the mic for the local player while leaving the audio feed open from the other players.

MultiPlayer.MuteOtherPlayerAudioChat: This is a boolean variable that turns on/off the sound feed from other players but leave the current players mic open.

MultiPlayer.AudioChatSensitivity: This is an integer that sets the low end volume level threshold to trigger sending audio. The higher the number the less background noise may be sent, but the players voice may become choppy if it is not loud enough to trigger the threshold.


Runtime Variables

MultiPlayer.PlayerID: A read-only variable they stores the current local players ID on the server. Note that this ID is unique to the game session, it does not persist accross game start/stops.

MultiPlayer.RoomID: A read-only variable that contains the session ID of the current room.

MultiPlayer.PrivateKey: A read-only ID that contains a human readable key so users can share room IDs to easily join the same room.

MultiPlayer.CurrentPlayerIDs: A read-only array of the other players IDs in the room with the current player. Please see the code example for how to use this array and the x and y arrays.

MultiPlayer.CurrentPlayerXs: A read-only array of the other players X position.

MultiPlayer.CurrentPlayerYs: A read-only array of the other players Y position.


Functions

MultiPlayer.SendStruct(Outload)

This function allows you to send data to the other players int he room. It is infinatly flexible, you can create whatever structure you need, and you can send the results to different callback functions for easier organization.

The only required value is the CallBackID. The system currently works with IDs 100 through 104. You can further enhance the callbacks by creating your own strucure, including how ever many name value pairs you need. On strategy if five callback function is not enough is to create your own sub-structure. Simply create a new variable as a flag, and assign it values to create unlimited more granular different message types.

var Outload = "{ 'CallBackID':'100', 'dir':'"+string(inst.dir)+"', 'spd':'"+string(inst.spd)+"', 'x':'"+string(x)+"', 'y':'"+string(y)+"' }";

A couple rules. The data goes across the wire as a string. The SEND structure is json like, but GM2 and JSON have an argument concernign single and double quotes. So, the SEND data needs to use single quotes (so it can all be in a nice string), which the server re-strings as double quotes in the return so that the callback can reference structures using native GM2 diction.

MultiPlayer.MultiPlayerPush(MultiPlayer.PlayerID,x,y)

You can place this line of code into the step event of the main player object, and its location will be sent to all the other games.

MultiPlayer.ChangeRooms(NewRoomPrivateKey)

If this function is given a valid private key, the player will change from the current roomto the provided room. 


Events

CallBack

This function is called after an asynchronous network events fires, where data is being pushed to the consumer from a subscription. A GM2 struct is passed into the callback, the result of the MultiPlayer.SendStruct() sent from another player in the same room.

PlayerJoinedRoom(NewPlayerID,NewX,NewY)

This function is called after an asynchronous network events fires, where there is a new PlayerID in the room.

PlayerLeftRoom(OldPlayerID)

This function is called after an asynchronous network events fires, where a player that was in the room has left (there session on the server has been terminated).


Code Examples

Main 'Hero' Object

Place in the STEP Event of the main player object. This will broadcast the player position to any other players in the same room.

MultiPlayer.MultiPlayerPush(MultiPlayer.PlayerID,x,y);

Other Players Objects

There are different strategies for handling this. In our tests we did the following:

In the create event of the 'OtherPlayer' object we assigned the variable MyPlayerID to zero to denote a created object but not yet attached to another player instance ID.

MyPlayerID=0;

Then in the MultiPlayerCallbacks.PlayerJoinedRoom function we create the OtherPlayer object and assign it the new player ID from the server.

function PlayerJoinedRoom(NewPlayerID,NewX,NewY){
    var inst = instance_create_layer(NewX, NewY, "Instances", OtherPlayer);
    inst.MyPlayerID=NewPlayerID;
}

In the OtherPlayer STEP event we put code to destroy the object if the MyPlayerID = -1. This way we can simply set that value from the player left room callback, and the instance is gone.

if MyPlayerID==-1 then instance_destroy(id); 

The MultiPlayerCallbacks.PlayerLeftRoom gets the leaving player ID from the server so we can find the OtherPlayer object and set the value

function PlayerLeftRoom(OldPlayerID){
    with OtherPlayer {
        //tell it to destroy itself
        if MyPlayerID==OldPlayerID then MyPlayerID=-1;
    }
}

 Firing Bullets (or other custom game events other players need to know about)

 In our demo on our Hero character we check to see if the right stick is moved, if it is we fire a bullet in the direction of the stick.

//right stick

var right_h_axis = gamepad_axis_value(0, gp_axisrh);
var right_v_axis = gamepad_axis_value(0, gp_axisrv);

if abs(right_h_axis)>.1 or abs(right_v_axis)>.1 {
FireBullet(right_h_axis,right_v_axis);
}

In the FireBullet function we fore the bullet then PUSH the event to the other players

FireBullet = function (right_h_axis,right_v_axis) {
repeat(1)
{
offCenter=random(.2)-.1;
var inst = instance_create_layer(x, y, "Instances", bullet);
inst.dir=point_direction(0, 0, right_h_axis+offCenter, right_v_axis+offCenter);
inst.spd = 10+random(3);

// below, CallBackID = 100 - this is the callback function that will fire on a message from another player
var Outload = "{ 'CallBackID':'100', 'dir':'"+string(inst.dir)+"', 'spd':'"+string(inst.spd)+"', 'x':'"+string(x)+"', 'y':'"+string(y)+"' }";
MultiPlayer.SendStruct(Outload);
}
}

We tell the MultiPlayer Object for fire CallBack100 when this event gets pushed to them. In this example we get the values from the structure we sent, and create our bullet in the other players games.

function CallBack100(ReturnStructure){
//used this callback for firing a bullet
var dir = ds_map_find_value(ReturnStructure, "dir");
var spd = ds_map_find_value(ReturnStructure, "spd");
var xx = ds_map_find_value(ReturnStructure, "x");
var yy = ds_map_find_value(ReturnStructure, "y");

var inst = instance_create_layer(xx, yy, "Instances", bullet);
inst.dir = dir;
inst.spd = spd;
}