Adding Isometrik
Now we are going to add the Isometrik portion of the code into the game to allow other players to join the game. In
your
main.js
file, add this section of code after the variables you set up and above the javascript files you loaded into
the scene:
window.createMyIsometrik = function (currentLevel) {
window.globalCurrentLevel = currentLevel; // Get the current level and set it to the global level
window.currentFireChannelName = 'realtimephaserFire2';
window.currentChannelName = `realtimephaser${currentLevel}`;
let checkIfJoined = false; // If player has joined the channel
// Setup your Isometrik Keys
window.isometrik = new window.Isometrik({
accountId: 'ADD-YOUR-ISOMETRIK-ACCOUNT_ID-HERE',
projectName: 'ADD-YOUR-ISOMETRIK-PROJECT_NAME-HERE',
keysetName: 'ADD-YOUR-ISOMETRIK-KEY_SET_NAME-HERE',
publishKey: 'ADD-YOUR-ISOMETRIK-PUBKEY-HERE',
subscribeKey: 'ADD-YOUR-ISOMETRIK-SUBKEY-HERE',
uuid: window.UniqueID,
});
// Subscribe to the two Isometrik Channels
window.isometrik.subscribe({
channels: [window.currentChannelName, window.currentFireChannelName],
withPresence: true,
});
// ADD LISTENER HERE
// If person leaves or refreshes the window, run the unsubscribe function
window.addEventListener('beforeunload', () => {
window.globalUnsubscribe();
});
window.isometrik.addListener(window.listener);
};
This function sets up some variables and channel names that Isometrik is going to use for network communication.
We set
window.currentChannelName
to equal whatever current level the user is on. We then setup the Isometrik keys and subscribe to the channels
specified. Then we add a listener that when the browser is unloaded, it sends a beacon that a user has left the channel
so the presence event updates for all other clients. We also have a globalUnsubscribe function that removes the listener
for the client and subscribes them from the channel.
Then we add a listener that when the browser is unloaded, it sends a beacon that a user has left the channel so the
presence event updates for all other clients. We also have a globalUnsubscribe function that removes the listener
for the client and subscribes them from the channel.
Setup Your Isometrik Dashboard
Take a look at your publish and subscribe key. You will need to add your own Publish and Subscribe keys in order for
the game to work. Now if you haven’t already,
create a Isometrik account.
Once you’re in the
Admin Dashboard, name your application whatever you wish, and click the Create New App button. Once you create
the application, click on the application to few the key information. You should see that you have two keys, a Publish
Key, and a Subscribe Key. Click on the demo keyset, and it should load up a page that shows your keys in addition
to Application Add-Ons. In the Application Add-Ons section, turn ON Presence and check Generate Leave on TCP FIN
or RST and Global Here Now. Also turn ON Isometrik Functions. Make sure to have Access Manager turned off or else
the sample code won’t work since you need to include a secret key.
Click on the demo keyset, and it should load up a page that shows your keys in addition to Application Add-Ons. In
the Application Add-Ons section, turn ON Presence and check Generate Leave on TCP FIN or RST and Global Here Now.
Also turn ON Isometrik Functions. Make sure to have Access Manager turned off or else the sample code won’t work
since you need to include a secret key.
The code you have written so far still won’t work since we haven’t added the callback listener that will listen for
all messages sent through the Isometrik Network on your channel while the client is connected.
Let’s add the following code where the comment says ADD LISTENER HERE:
window.listener = {
status() {
// Send fire event to connect to the block
const requestIntMsg = { requestInt: true, currentLevel: window.globalCurrentLevel, uuid: window.UniqueID };
window.isometrik.publish({
message: requestIntMsg,
channel: window.currentFireChannelName,
});
},
message(messageEvent) {
messageEvent.message = JSON.parse(messageEvent.message);
if (messageEvent.message.uuid === window.UniqueID) {
return; // this blocks drawing a new character set by the server for ourselve, to lower latency
}
if (window.globalOtherHeros) { // If player exists
if (messageEvent.channel === window.currentChannelName) { // If the messages channel is equal to your current channel
if (!window.globalOtherHeros.has(messageEvent.message.uuid)) { // If the message isn't equal to your uuid
window.globalGameState._addOtherCharacter(messageEvent.message.uuid); // Add another player to the game that is not yourself
window.sendKeyMessage({}); // Send publish to all clients about user information
const otherplayer = window.globalOtherHeros.get(messageEvent.message.uuid);
otherplayer.position.set(messageEvent.message.position.x, messageEvent.message.position.y); // set the position of each player according to x y
otherplayer.initialRemoteFrame = messageEvent.message.frameCounter;
otherplayer.initialLocalFrame = window.frameCounter;
otherplayer.totalRecvedFrameDelay = 0;
otherplayer.totalRecvedFrames = 0;
}
if (messageEvent.message.position && window.globalOtherHeros.has(messageEvent.message.uuid)) { // If the message contains the position of the player and the player has a uuid that matches with one in the level
window.keyMessages.push(messageEvent);
}
}
}
},
presence(presenceEvent) { // Isometrik on presence message / event
let occupancyCounter;
if (presenceEvent.action === 'join') { // If we recieve a presence event that says a player joined the channel from the Isometrik servers
// checkIfJoined = true;
window.checkFlag();
// text = presenceEvent.totalOccupancy.toString()
if (presenceEvent.uuid !== window.UniqueID) {
window.sendKeyMessage({}); // Send message of players location on screen
}
} else if (presenceEvent.action === 'leave' || presenceEvent.action === 'timeout') {
window.checkFlag();
try {
window.globalGameState._removeOtherCharacter(presenceEvent.uuid); // Remove character on leave events if the individual exists
} catch (err) {
// console.log(err)
}
}
}
};
window.checkFlag = () => {// Function that reruns until response
window.isometrik.hereNow(
{
channels: [window.currentChannelName],
includeUUIDs: true,
includeState: true
},
(status, response) => {
// If I get a valid response from the channel change the text objects to the correct occupancy count
if (typeof (response.channels.realtimephaser0) !== 'undefined') {
textResponse1 = response.channels.realtimephaser0.occupancy.toString();
} else {
textResponse1 = '0';
}
if (typeof (response.channels.realtimephaser1) !== 'undefined') {
textResponse2 = response.channels.realtimephaser1.occupancy.toString();
} else {
textResponse2 = '0';
}
if (typeof (response.channels.realtimephaser2) !== 'undefined') {
textResponse3 = response.channels.realtimephaser2.occupancy.toString();
} else {
textResponse3 = '0';
}
window.text1 = `Level 1 Occupancy: ${textResponse1}`;
window.text2 = `Level 2 Occupancy: ${textResponse2}`;
window.text3 = `Level 3 Occupancy: ${textResponse3}`;
window.textObject1.setText(window.text1);
window.textObject2.setText(window.text2);
window.textObject3.setText(window.text3);
}
);
};
Now let’s go through this code to look at what it’s doing. The listener is listening for events every frame but will
only run on the initial connection status to Isometrik, or when a message is sent on the channel or when a presence
change occurs.
In the
status(status)
callback, it’s sending a fire message to the block to request level information from the KV store.
In the
message(messageEvent)
callback function, we check to see if the message channel name is equal to the current fire channel name.
If it is, call the start loading function to load the game. Then after that if statement, we check to see if the
message channel is equal to the
window.currentChannelName
. If it is equal and it’s not a message from yourself, add another player to the game and set its position in the
correct location based on the message data.
In the
presence(presenceEvent)
callback function, we have a function that runs if someone joins, leaves or timeouts of the channel, or if
window.updateOccupancyCounter
is equal to false. We then run Isometrik’s hereNow API function that checks to see how many people are in
the channel and outputs the current occupancy along with the UUID’s in the channel. We are only checking the amount
of players in the channel when a presence event is called which is optimized compared to calling the function every
frame.
Now right below that function we just wrote but above the load external javascript files code, copy and paste these
last two functions. One will send messages out to all clients connected to the channel. The message will contain
player UUID information, position and frame count. The second function will send a message to the block telling
it the current cache state of the user.
try {
if (window.globalMyHero) {
window.isometrik.publish({
message: {
uuid: window.UniqueID,
keyMessage,
position: window.globalMyHero.body.position,
frameCounter: window.frameCounter
},
channel: window.currentChannelName,
});
}
} catch (err) {
console.log(err);
}
window.fireCoins = () => {
const message = {
uuid: window.UniqueID,
coinCache: window.globalLevelState.coinCache,
currentLevel: window.globalCurrentLevel,
time: window.globalLastTime
};
window.isometrik.publish({
message,
channel: window.currentFireChannelName,
});
};
Uncomment code
Now in order to make the function work, we have to go uncomment some code we left commented out before. Go to the bottom
of
main.js
where the event listener loads the scene. Uncomment the following:
window.createMyIsometrik(0); // Connect to the Isometrik network and run level code 0
window.StartLoading = function () {
game.state.start('loading'); // Run the loading function once you successfully connect to the Isometrik network
};
Now go to
playState.js
uncomment
window.fireCoins();
in the
logCurrentStateCoin()
function. Now go down to the
_handleInput()
function and uncomment:
handleKeyMessages();
...
window.sendKeyMessage({ left: 'down' });
...
window.sendKeyMessage({ left: 'up' });
...
window.sendKeyMessage({ right: 'down' });
...
window.sendKeyMessage({ right: 'up' });
...
window.sendKeyMessage({ up: 'down' });
...
window.sendKeyMessage({ up: 'up' });
...
window.sendKeyMessage({ stopped: 'not moving' });
Then in the
_spawnCharacters()
function uncomment:
Now save your files and refresh your window. If you open up two separate windows of the game, you should be able to
move your character on one window and see the character move on the other window with very low latency. This is all
powered by Isometrik’s realtime Data Stream Network and API.
Handle Messages
Now lets add the
handleKeyMessages()
function to the game so we can start implementing the multiplayer components. This function handles all of
the messages that get received by the client. Essentially what it’s doing is syncing all the clients up to each other
so the movements are accurately displayed on the screen. Copy and paste this code in
playState.js
right below the
logCurrentStateCoin(game, coin)
function:
const earlyMessages = [];
const lateMessages = [];
window.keyMessages.forEach((messageEvent) => {
if (window.globalOtherHeros) { // If player exists
if (messageEvent.channel === window.currentChannelName) { // If the messages channel is equal to your current channel
if (!window.globalOtherHeros.has(messageEvent.message.uuid)) { // If the message isn't equal to your uuid
window.globalGameState._addOtherCharacter(messageEvent.message.uuid); // Add another player to the game that is not yourself
const otherplayer = window.globalOtherHeros.get(messageEvent.message.uuid);
otherplayer.position.set(messageEvent.message.position.x, messageEvent.message.position.y); // set the position of each player according to x y
otherplayer.initialRemoteFrame = messageEvent.message.frameCounter;
otherplayer.initialLocalFrame = window.frameCounter;
window.sendKeyMessage({}); // Send publish to all clients about user information
}
if (messageEvent.message.position && window.globalOtherHeros.has(messageEvent.message.uuid)) { // If the message contains the position of the player and the player has a uuid that matches with one in the level
window.keyMessages.push(messageEvent);
const otherplayer = window.globalOtherHeros.get(messageEvent.message.uuid);
const frameDelta = messageEvent.message.frameCounter - otherplayer.lastKeyFrame;
const initDelta = otherplayer.initialRemoteFrame - otherplayer.initialLocalFrame;
const frameDelay = (messageEvent.message.frameCounter - window.frameCounter) - initDelta + window.syncOtherPlayerFrameDelay;
if (frameDelay > 0) {
if (!messageEvent.hasOwnProperty('frameDelay')) {
messageEvent.frameDelay = frameDelay;
otherplayer.totalRecvedFrameDelay += frameDelay;
otherplayer.totalRecvedFrames++;
}
earlyMessages.push(messageEvent);
return;
} else if (messageEvent.message.keyMessage.stopped === 'not moving') {
otherplayer.body.position.set(messageEvent.message.position.x, messageEvent.message.position.y);
otherplayer.body.velocity.set(0, 0);
otherplayer.goingLeft = false;
otherplayer.goingRight = false;
if (otherplayer.totalRecvedFrames > 0) {
const avgFrameDelay = otherplayer.totalRecvedFrameDelay / otherplayer.totalRecvedFrames;
const floorFrameDelay = Math.floor(avgFrameDelay);
otherplayer.initialRemoteFrame += floorFrameDelay - 7;
}
otherplayer.totalRecvedFrameDelay = 0;
otherplayer.totalRecvedFrames = 0;
} else if (frameDelay < 0) {
otherplayer.totalRecvedFrameDelay += frameDelay;
otherplayer.totalRecvedFrames++;
lateMessages.push(messageEvent);
return;
} else {
//console.log('initDelta', initDelta, 'ontime', frameDelay);
}
otherplayer.lastKeyFrame = messageEvent.message.frameCounter;
if (messageEvent.message.keyMessage.up === 'down') { // If message equals arrow up, make the player jump with the correct UUID
otherplayer.jump();
otherplayer.jumpStart = Date.now();
} else if (messageEvent.message.keyMessage.up === 'up') {
otherplayer.jumpStart = 0;
}
if (messageEvent.message.keyMessage.left === 'down') { // If message equals arrow left, make the player move left with the correct UUID
otherplayer.goingLeft = true;
} else if (messageEvent.message.keyMessage.left === 'up') {
otherplayer.goingLeft = false;
}
if (messageEvent.message.keyMessage.right === 'down') { // If message equals arrow down, make the player move right with the correct UUID
otherplayer.goingRight = true;
} else if (messageEvent.message.keyMessage.right === 'up') {
otherplayer.goingRight = false;
}
}
}
}
});
window.keyMessages.length = 0;
earlyMessages.forEach((em) => {
window.keyMessages.push(em);
});