Creating Web Multiplayer Game with WiFi Direct
PUBLISHED
Introduction
Tizen Platform doesn’t support WiFi Direct Web API yet, however its implementation should appear soon. For now there are few ways of creating web application using WiFi Direct. One of those solutions I’m going to describe in this article. We’ve created WiFi Direct library for web environment that uses hybrid application to provide Web API that developer can use and invoke some native functions. Using this library I’ve created multiplayer game called TypeRace, which process of creation is going to be described in details in this article.
WiFi Direct library
Detailed description of the library can be found under this link https://developer.tizen.org/documentation/articles/wi-fi-direct-and-sockets-tizen-web-applications. We’ve described how WiFi Direct library is structured, how given aspects of library works and how to use it in your application. Here, we’re going to focus only on library usage.
Library is built of two key components:
- HybridWifiDirectService service application
- Two JavaScript files: tizen.hybrid.js and tizen.wifidirect.js
The first one is just a service application that you have to import into you IDE. Later, when creating web application you have to go to its Properties and set correlation with HybridWifiDirectService service application. What it does, is just creating connection between those applications. Both applications are going to be included in one package – hybrid package. In the picture below you can see Properties window of the TypeRace application.
Setting correlation between TypeRace and HybridWiFiDirectService applications to make hybrid package.
Next thing you have to do, is including two JavaScript files to you web project. Now you are ready to start. Now I’m going to describe process of creating simplest possible communication between devices using WiFi Direct Web Library.
- At first you have to initialize WiFi Direct by calling tizen.wifidirect.init(initCF); function. What it does is just creating some WiFi Direct objects using WiFi Direct Manager. We have to pass callback function as a first parameter of the init method to be able to handle errors. One important thing to mention here, is that this WiFi Direct library is asynchronous. It means that, it doesn’t block execution of the program, so you should always use callbacks to execute one action after another.
- Having WiFi Direct initialized, we have to activate it. We do it by calling tizen.wifidirect.activate(activateCF); function. And one more time, we have to handle response from the service by passing callback function as a first parameter of activate method. When activation process is finished, the activated event is generated, so we have to listen to it by using tizen.wifidirect.addEventListener('activated', activatedCF); function.
- Next thing we have to do depends on whether we’re client or server. If we’re server, we have to create group and if we’re client we can scan for existing groups and connect to one of them.
- To create group we invoke tizen.wifidirect.createGroup(createGroupCF); function and listen to groupCreated event tizen.wifidirect.addEventListener('groupCreated', groupCreatedCF);.
- To scan for groups we use tizen.wifidirect.scan(scanCF); function and listen to scanCompleted event: tizen.wifidirect.addEventListener('scanCompleted', scanCompletedCF);. This event is generated when scan is completed. The list of existing groups will be passed as a first parameter of callback function.
- Now, being the client, we have to connect to the group by calling tizen.wifidirect.connect(deviceInfo, connectCF); function. We can listen to connected event to be notified when connection has been established: tizen.wifidirect.addEventListener('connected', connectedCF);.
- One more thing to do before sending messages. Native WiFi Direct API only provides support for creating virtual network: creating and scanning for groups, connecting to groups etc. However it doesn’t provide any API for communication between devices so we have to manage this ourselves. In our library we just used standard network sockets. Next function tizen.wifidirect.initSocket(initSocketCF); is initializing those sockets for you and export API for you to use.
- Now, we can start communicating between devices. We can send message to all devices within the group
tizen.wifidirect.sendBroadcast('Hello World!', sendBroadcastCF);
or to particular one by providing IP address
tizen.wifidirect.sendMessage('192.168.0.10', 'Hello World!', sendBroadcastCF);
We also have to listen to messages received from other devices
tizen.wifidirect.addEventListener('messageReceived', messageReceivedCF);
That would be pretty much all we have to do to start creating application using WiFi Direct. Of course it’s the base of application. In real world application you would need to use more functions and design application’s logic.
Creating “TypeRace” multiplayer game
In this section, we’re going to describe the process of creating “TypeRace multiplayer game using WiFi Direct Web Library. In the pictures below you can see how application looks like.
Start screen of the TypeRace application
“TypeRace” game is about typing words. You compete with the second player in typing six words (randomly drawn from the database). As you can see in the picture above, we have text fields of both players on the screen and when some typo is made then text field turns red. You can see in real time the word that your opponent is typing in his/her text field.
Creating UI
The game’s UI is build of few screen made of DIV elements with the screen class. When going from one screen to another we just simply hide one and show the other. We avoided using complex transitions to make things easier and focus mostly on implementing WiFi Direct logic.
<div class="screen center-content" id="menu-screen" hidden="hidden"> <input type="button" id="create-game" value="Create Game"> <input type="button" id="join-game" value="Join Game"> </div>
We also defined DIV element with the loading indicator that when shown covers entire screen with semitransparent layer that disallows user to click any UI element. Loading layer is being shown whenever longer action takes place like for example seeking existing groups.
The words to write in the text field are presented below or above the text field (depending on the player). They’re drawn on the canvas element to disallow user copying text and just pasting it in the text field. We could also do some letters rotation, scaling and scattering to make it more difficult to read by OCR readers but it’s not the subject of this article.
<div id="you"> <div class="row"> <canvas width="720" height="150"></canvas> </div> <div class="row"> <input type="text" disabled="disabled"> </div> <div class="progress"> <div class="bar" style="width: 0%;"></div> </div> </div>
We’ve defined game object that holds all game’s functionality. It has ui attribute that deals with UI management. It has few methods:
- openScreen() – opens screen by given name
- closeScreen() – closes current screen
- show() – shows given DOM element
- hide() – hides given DOM element
- enable() – enables given DOM element
- disable() – disables given DOM element
The ui object also stores references to all game’s DOM elements that are going to be used in the game. Implementation of those functions is quite straight forward, so I haven’t put the code in the article. If you want to examine the code of those functions, please go to the main.js file.
In the game namespace we also defined printWord() function which prints given word on the canvas of given player. You can add some extra functionality to this function to a make text more difficult to ready by OCR readers.
printWord: function (text, player) { text = text || 'WINNER!'; if (player !== 'you' && player !== 'opp') return; var context = this.ui[player].context; var font = { color: '#ffffff', weight: 'bold', size: 80, family: 'Arial' }; context.save(); context.font = font.weight + ' ' + font.size + 'px ' + font.family; context.clearRect(0, 0, 720, 150); context.shadowBlur = 4; context.shadowColor = '#000000'; context.shadowOffsetY = 2; context.fillStyle = font.color; context.fillText(text, (720 - context.measureText(text).width) / 2, (150 / 2) + (font.size / 2.5)); context.restore(); }
Game logic
Game starts from creating group and connecting to it by another player. Then, we click Start Game button and countdown starts. After clicking Start Game button six words are drawn from the list of words and sent to another player. In the same time countdown starts and after it’s finished players can start typing words.
this.ui.startGame.addEventListener('click', function (e) { this.start(); }.bind(this)); /* … */ start: function () { this.resetPlayersData(); if (this.isServer) { this.drawWords(); this.sendWords(); } this.data.you.started = true; this.winner = null; this.sendPlayerData('started'); if (this.checkIfReadyToStart()) { this.countDown(); } this.update(); }
As you can see at the end of the start() function we call this.update() method which refreshes the UI. We call this function whenever UI needs refreshing. We don’t update UI in loop 60 times per second because it doesn’t need to. Instead, we update UI only when some data has changed. Update function is quite complex and deals with all the game states. To understand it you need to investigate it carefully from top to bottom.
We’ve defined some function to deal with players’ data:
- setPlayerData(player, key, value) – sets value for key, for given player
- getPlayerData(player, key) – gets value under given key, for given player
- resetPlayersData(hard) – resets players data, when new game is started
Users’ data object contains such information like currently being typed word (typeIndex and typedWord), information if player finished typing all the six words (finished), information if user clicked Start Game button (started), information if opponent is connected (connected) and information if given player won the game (winner). We can set and get those information using previously mentioned functions.
this.data = { you: { wordIndex: 0, typedWord: '', finished: false, started: false, connected: false, winner: false }, opp: { wordIndex: 0, typedWord: '', finished: false, started: false, connected: false, winner: false } };
One more thing worth mentioning is just using WiFi Direct. We defined functions for almost every method of WiFi Direct library and proper event listeners. What those function do is just calling given WiFi Direct function and waiting for the response from the server. When response from the server comes back is set given data and update UI or execute some logic.
tizen.wifidirect.addEventListener('messageReceived', this.onDataReceived.bind(this));
onDataReceived: function (error, data) { if (error) { this.onError(error); return; } data = JSON.parse(data.message); switch (data.command) { case 'words': this.wordsIndexes = data.data; this.update(); break; case 'wordIndex': this.setPlayerData('opp', data.command, data.data); // Update word's image. this.printWord(this.getPlayerData('opp', 'currWord'), 'opp'); this.update(); break; case 'typedWord': this.setPlayerData('opp', data.command, data.data); this.update(); break; case 'finished': this.setPlayerData('opp', data.command, data.data); this.finish(false); break; case 'started': this.setPlayerData('opp', data.command, data.data); if (this.checkIfReadyToStart()) { this.countDown(); } break; } }
this.ui.you.textField.addEventListener('keyup', function (e) { var typedWord, wordIndex, currWord; if (this.data.you.started) { typedWord = e.target.value; this.setPlayerData('you', 'typedWord', typedWord); /* ... */ this.sendPlayerData('typedWord'); this.update(); } }.bind(this));
Summary
For better understanding of the code I encourage you to investigate the whole code of TypeRace game. I hope that this short description helped you better understand how to deal with creating your own games. Creating multiplayer games is not easy task to do, however with WiFi Direct library for Web it’s a lot simpler because you don’t have to deal with memory allocation, pointers and other things that are related to native programming on Tizen.