通过WiFi Direct创建Web多人游戏
PUBLISHED
简介
Tizen平台还不支持WiFi Direct Web API,然而这很快就要实现了。 目前有一些方法来使用WiFi Direct创建web应用程序。 其中一个解决方案,我将在本文中描述。 我们已经为web环境创建了WiFi Direct库,就是使用合成的应用程序去提供Web API,开发者们可以使用这些API并调用一些本地函数。 我已经创建了多人游戏TypeRace来使用这个库,创建的过程将会在本文中进行详细地介绍。
WiFi Direct 库
库的详细描述可以在下面的链接中找到: https://developer.tizen.org/documentation/articles/wi-fi-direct-and-sockets-tizen-web-applications。 我们已经描述了WiFi Direct库是如何构建的,库的某些方面是如何工作,以及如何在你的应用程序里面使用它。 这里,我们将只集中在库的使用上面。
库是建立在两个关键部分:
- HybridWifiDirectService服务应用
- 两个JavaScript文件:tizen.hybrid.js和tizen.wifidirect.js
第一个就是你必须导入到IDE的服务应用程序。 然后,当创建web应用程序的时候,你要看其属性和设置与HybridWifiDirectService服务应用程序的相关性。 它所做的,仅仅是创建这些应用程序之间的相互连接。 这两个应用程序都将被包含在一个包 - 混合包里面。 在下面的图片中,你可以看到TypeRace应用程序的属性窗口。
通过设置TypeRace和HybridWiFiDirectService应用程序之前的关系,来制作合成包。
下一步你要做的事,就是将两个JavaScript文件包含到你的web工程里面。 现在你可以准备开始了。 现在,我将描述一个最简单的创建过程,就是在使用WiFi Direct Web库的设备之间的交流。
- 首先,你必须通过调用tizen.wifidirect.init(initCF);函数来初始化WiFi Direct。 它所做的事就是使用WiFi Direct管理器来创建一些WiFi Direct对象。 我们必须将回调函数作为第一个参数传给init方法,使其能够处理错误。 这里要提到的一个重要的事情,就是这个WiFi Direct库是异步的。 这意味着,它不会阻止程序的运行,因此你应该一直用回调函数来交替地执行每个动作。
- 既然WiFi Direct已经初始化了,我们就要激活它。 我们通过调用tizen.wifidirect.activate(activateCF)函数来这么做。 又一次,我们必须处理来自服务端的请求,这是通过将回调函数作为激活方法的第一个参数来做的。 当激活过程完成后,已激活事件就会产生,因此我们必须要监听它,通过 tizen.wifidirect.addEventListener('activated', activatedCF);来监听。
- 下面要做的事情则根据我们是客户端还是服务端来定。 如果我们是服务端,我们必须要创建组;如果我们是客户端,我们则可以扫描已有的组并连接其中的一个。
- 要创建组的话,我们需要调用tizen.wifidirect.createGroup(createGroupCF);函数,并监听groupCreated事件,即tizen.wifidirect.addEventListener('groupCreated', groupCreatedCF);。
- 要扫描组,我们就使用tizen.wifidirect.scan(scanCF);函数并监听scanCompleted 事件:tizen.wifidirect.addEventListener('scanCompleted', scanCompletedCF);。 当扫描完成后会生成此事件。 现有组的列表将被作为回调函数的第一个参数。
- 现在,作为在客户端,我们通过调用tizen.wifidirect.connect(deviceInfo,connectCF);函数连接到该组。 我们可以监听已连接事件,该事件在连接被建立的时候会通知出来:tizen.wifidirect.addEventListener('connected', connectedCF);。
- 在发送消息之前还有一件事要做。 本地WiFi Direct API 只支持创建虚拟网络:创建和扫描组,连接到组,等等。 然而,它不提供对设备之间的通信的任何API,因此我们必须自己来管理。 在我们的库中,我们只使用用标准的网络套接字。 下一个函数tizen.wifidirect.initSocket(initSocketCF);为你初始化这些套接字,并将API导出给你使用。
- 现在,我们可以开始设备之间的通信。 我们可以把消息发送到组内的所有设备
tizen.wifidirect.sendBroadcast('Hello World!', sendBroadcastCF);
或到特定的一个提供的IP地址
tizen.wifidirect.sendMessage('192.168.0.10', 'Hello World!', sendBroadcastCF);
我们还必须监听从其他设备接收的消息
tizen.wifidirect.addEventListener('messageReceived', messageReceivedCF);
这将是几乎所有我们需要做的,开始使用WiFi Direct创建应用程序。 当然,它是应用程序的基础。 在现实世界中的应用程序,你需要使用更多的功能和设计应用程序的逻辑。
创建“TypeRace”多人游戏
在本节中,我们将介绍这个创建过程,就是使用WiFi Direct Web 库来创建"TypeRace"多人游戏的过程。 在下面图片你可以看到应用程序的外观。
“TypeRace”游戏是关于输入文字的游戏。 你和第二个玩家在输入六个单词上面竞争(从数据库中随机抽取)。 正如你在上面的图片中看到,我们在屏幕上有两个玩家的文字区域,以及当出现一些输入错误时文字区域会变红。 您可以实时看到你的对手正在他/她的文本字段中的打字。
创建用户界面
游戏的UI只由很少的屏幕组成,这些画面由屏幕类的DIV元素组成。 当从一个屏幕换到另一个的时候,我们只是简单地隐藏一个并显示另一个。 我们避免使用复杂的过渡,以使得事情变得更简单,并将注意力都集中放在实现WiFi Direct的逻辑上面。
<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>
我们还定义了DIV元素的加载指示器,当显示面积覆盖整个屏幕且为半透明的时候,不允许用户点击任何UI元素。 每当进行较长时间的操作比如搜索现有组的时候,加载层就会显示出来。
在文本字段中写的字都低于或高于该文本字段(根据播放器来看)。 它们被画在画布元素上面,这样可以禁止用户复制文字并将其粘贴到文字区域里面。 我们还可以做一些字母旋转,缩放和分散,以使得OCR读者更难阅读,但这不是本文的主题。
<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>
我们已经为整个游戏的功能定义了游戏对象。 它有ui属性可以用来管理UI。 这有以下几种方法:
- openScreen() - 通过给定的名字打开界面
- closeScreen() - 关闭当前界面
- show() - 显示给定DOM元素
- hide() - 隐藏给定DOM元素
- enable() - 启用给定DOM元素
- disable() - 禁止给定DOM元素
这个ui对象也保存指向所有游戏的DOM元素的引用,而这些元素都将要在游戏中使用。 那些函数的实现简单直接,所以我没有把代码放在本文里面。 如果你要查看那些 函数的代码,请看main.js文件。
在游戏中的命名空间中,我们还定义printWord()函数,它打印出给定的单词到给定玩家的画布上。 您可以添加一些额外的功能到这个函数里面,使得对于OCR读者来说更难于阅读。
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(); }
游戏逻辑
游戏从创建组开始,由另一个玩家连接。 然后,我们点击开始游戏按钮,倒计时开始。 在点击开始游戏按钮后,从单词表中取出六个单词并发给另一个玩家。 在同一时刻倒计时开始,倒计时结束后,玩家们就可以开始打字了。
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(); }
正如你在start()函数最后可以看到,我们调用this.update()方法来引用UI. 每当UI需要刷新的时候我们就呼叫这个函数。 我们不会在每秒钟循环更新60次,因为也没有必要这样做。 相反地,我们只在某些数据有改变的时候才更新UI。 更新函数很复杂,要处理所有的游戏状态。 如果要理解它,你需要从头到尾仔细地研究它。
我们已经定义了一些函数来处理玩家的数据:
- setPlayerData(播放器,键,值) - 设定值的键,对于给定的玩家
- getPlayerData(播放器,键) - 获取给定键的数值,对于给定的玩家
- resetPlayersData(hard) - 当新游戏开始时将玩家数据置位
用户数据对象包含这些信息,比如正在打的字的信息(typeIndex 和 typeWord), 玩家打完所有的六个单词的信息(finished),用户点击开始游戏按钮的信息(started),对手已经连接的信息(connected),以及其中的玩家赢得比赛的信息(winner)。 我们可以使用前面提到的函数,来设置和获取这些信息。
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 } };
还有一件值得一提的事就是使用WiFi Direct。 我们几乎为了WiFi Direct库的每一个方法都定义了函数和适合的事件监听器。 这些函数要做的事情,只不过是调用给定的WiFi Direct函数,并等待从服务端的回复。 当从服务器返回响应后,会设置给定数据,更新UI或者运行一些的逻辑。
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));
总结
为了更好地理解代码,我建议你去研究TypeRace游戏的整个代码。 我希望这篇短文能更好地帮助你理解,如何来创建属于你自己的游戏。 创建多人游戏并不是一个简单的事情,然而通过使用Web的WiFi Direct库就变得简单多了,因为你不用去处理内存分配,指针和其他关于在Tizen上面进行本地编程的事情。