In my last post, I had talked about game play and matchmaking in AppWarp Gaming Engine. In this post, I am going to take you through the communication protocols that can be used in your multiplayer game.
I’ll describe this using the same catapult wars game from my previous post. There are two operations that a user can perform in catapult wars – dragging the catapult using the finger to vary its strength and velocity or launching the missile on release.
It is critical to send the release time (final) strength and velocity of the missile to the opponent so it can update the game state. We can also send the information about the user dragging the finger as he aims the missile to enhance the experience but it’s not critical. Also the user is expected to continuously drag the catapult before its release which means a high frequency of updates.
This will serve as a basis in picking the protocol used in the game in the case of catapult wars.
We know UDP communication is fast but offers no delivery guarantees – so we can use it for sending the drag data in our case.
Due to the protocol reliability overheads, TCP tends to be slower than UDP. However, its in-order and reliable characteristics are desirable when critical data is to be sent. So in our case we will send the shot strength and velocity over TCP.
Using UDP in AppWarp
We need to initialize UDP before we can send updates using it. Since some networks might block UDP, we need to wait for a callback from the server to determine its status. UDP can only be initialized if the client is successfully connected with the server. In catapult wars, I initialized UDP immediately after connecting successfully with the server.
public void onConnectDone(ConnectEvent eventObj)
{
switch (eventObj.getResult())
{
case WarpResponseResultCode.SUCCESS:
GlobalContext.IsConnectedToAppWarp = true;
// Successfully connected to the server. Lets go ahead and init the udp.
//Init udp is essentional if we are using UDP communication in our Game
WarpClient.GetInstance().initUDP();
if (mOnConnectDoneCallback != null)
{
//Request Was From Main Screen
Deployment.Current.Dispatcher.BeginInvoke(new ConnectionCallback(mOnConnectDoneCallback));
}
else if ((mOnConnectDoneCallback == null) && mShowResultCallback != null)
{
//Request Was From Join Screen
Deployment.Current.Dispatcher.BeginInvoke(new ShowResultCallback(mShowResultCallback), "connected");
}
break;
}
}
We will get the result in the onInitUDPDone callback.
public void onInitUDPDone(byte ResultCode)
{
if (ResultCode == WarpResponseResultCode.SUCCESS)
{
GlobalContext.IsUDPEnableOnNetwork = true;
}
else
{
GlobalContext.IsUDPEnableOnNetwork = false;
}
}
Since UDP communication is unreliable and might deliver packets out of order, game developers might want to add some extra validation e.g. In Catapult wars, I have added two parameters in the UDP update message – fireNumber and packetNumber.
fireNumber is also included in room-properties. After each shot by any of the users I increment it by one.
GlobalContext.tableProperties.Add("fireNumber", 1);
While building the UDP Packet:
public static byte[] buildDragingMessageBytes(int pnumber, String X, String Y)
{
GlobalContext.fireNumber = Convert.ToInt32(GlobalContext.tableProperties["fireNumber"]);
JObject moveObj = newJObject();
moveObj.Add("sender", GlobalContext.localUsername);
moveObj.Add("fireNumber", GlobalContext.fireNumber.ToString());
moveObj.Add("packetNumber", pnumber.ToString());
moveObj.Add("Type", "DRAGGING");
moveObj.Add("X", X);
moveObj.Add("Y", Y);
return System.Text.Encoding.UTF8.GetBytes(moveObj.ToString());
}
At recipient, I compare the “fireNumber” with game-room property “fireNumber”. If it’s not same then the recipient should ignore the packet as it is for an older shot.
“packetNumber” indicates the dragging data of the current shot (yet to be fired). So it should be greater than the last successfully received dragging data UDP packet.
if (msg.Type.Equals("DRAGGING"))
{
GlobalContext.fireNumber = Convert.ToInt32(GlobalContext.tableProperties["fireNumber"]);
int fireNumber = Convert.ToInt32(dragJsonObj["fireNumber"].ToString());
int udpPacketNumber = Convert.ToInt32(dragJsonObj["packetNumber"].ToString());
if ((fireNumber == GlobalContext.fireNumber) && (udpPacketNumber > GlobalContext.prevUDPPacketNumber))
{
GlobalContext.prevUDPPacketNumber = udpPacketNumber;
msg.X = dragJsonObj["X"].ToString();
msg.Y = dragJsonObj["Y"].ToString();
}
else
{//igonre the udp packet
msg.Type = "NONE";
}
}
As described before, it’s fine to ignore some UDP packets as it doesn’t affect the game play.
Using TCP update in AppWarp for sending critical drag complete data
TCP packets arrive reliably and in-order so game developers don’t have to do any additional book-keeping.
When the user releases the catapult, I update the room property and send the shot strength and velocity using TCP update API of AppWarp.
public void HandleInput(GestureSample gestureSample)
{
if (gestureSample.GestureType == GestureType.DragComplete)
{
GlobalContext.fireNumber = Convert.ToInt32(GlobalContext.tableProperties["fireNumber"]);
GlobalContext.fireNumber++;
Dictionary fireProperties = new Dictionary();
fireProperties.Add("fireNumber", GlobalContext.fireNumber);
WarpClient.GetInstance().UpdateRoomProperties(GlobalContext.GameRoomId, fireProperties, null);
WarpClient.GetInstance().SendUpdatePeers(MoveMessage.buildSHOTMessageBytes(Catapult.ShotVelocity.ToString(), Catapult.ShotAngle.ToString()));
//Here i am resetting this counter because host user has sent his final shot via TCP so in next
//step remote user will send his data,before receive remote user UDP packets we need to reset this counter
GlobalContext.prevUDPPacketNumber = 0;
}
}
public static byte[] buildSHOTMessageBytes(String ShotVelocity, String ShotAngle)
{
JObject moveObj = new JObject();
moveObj.Add("sender", GlobalContext.localUsername);
moveObj.Add("Type", "SHOT");
moveObj.Add("ShotVelocity", ShotVelocity);
moveObj.Add("ShotAngle", ShotAngle);
return System.Text.Encoding.UTF8.GetBytes(moveObj.ToString());
}
At the recipient I parse the data accordingly.
if (msg.Type.Equals("SHOT"))
{
msg.ShotVelocity = dragJsonObj["ShotVelocity"].ToString();
msg.ShotAngle = dragJsonObj["ShotAngle"].ToString();
}
Complete source code of the game can be downloaded or viewed from our Git repo. If you have any questions or need any further assistance, please feel free to write us at support@shephertz.com.
Leave A Reply