通过WiFi Direct创建Web多人游戏

简介

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库的设备之间的交流。

  1. 首先,你必须通过调用tizen.wifidirect.init(initCF);函数来初始化WiFi Direct。 它所做的事就是使用WiFi Direct管理器来创建一些WiFi Direct对象。 我们必须将回调函数作为第个参数传给init方法,使其能够处理错误。 这里要提到的一个重要的事情,就是这个WiFi Direct库是异步的。 这意味着,它不会阻止程序的运行,因此你应该一直用回调函数来交替地执行每个动作。
  2. 既然WiFi Direct已经初始化了,我们就要激活它。 我们通过调用tizen.wifidirect.activate(activateCF)函数来这么做。 又一次,我们必须处理来自服务端的请求,这是通过回调函数作为激活方法的第一个参数来做的。 当激活过程完后,已激活事件就会产生,因此我们必须要监听它,通过 tizen.wifidirect.addEventListener('activated', activatedCF);来监听。
  3. 下面要做的事情则根据我们是客户端还是服务端来定。 如果我们是服务端,我们必须要创建组;如果我们是客户端,我们则可以扫描已有的组并连接其中的一个。
    1. 要创建组的话,我们需要调tizen.wifidirect.createGroup(createGroupCF);函数,并监听groupCreated事件,即tizen.wifidirect.addEventListener('groupCreated', groupCreatedCF);。
    2. 要扫描组,我们就使用tizen.wifidirect.scan(scanCF);函监听scanCompleted 事件:tizen.wifidirect.addEventListener('scanCompleted', scanCompletedCF);。 当扫描完成后会生成此事件。 现有组的列表将被作为回调函数的第一个参数。
  4. 现在,作为在客户端,我们通过调用tizen.wifidirect.connect(deviceInfo,connectCF);函数连接到该组。 我们可以听已连接事件,该事件在连接被建立的时候会通知出来:tizen.wifidirect.addEventListener('connected', connectedCF);。
  5. 在发送消息之前还有一件事要做。 本地WiFi Direct API 只支持创建虚拟网络:创建和扫描组,连接到组,等等。 然而,它不提供对设备之间的通信的任何API,因此我们必须自己来管理。 在我们的库中,我们只使用用标准的网络套接字。 下一个函数tizen.wifidirect.initSocket(initSocketCF);为你初始化这些套接字,并将API导出给你使用。
  6. 现在,我们可以开始设备之间的通信。 我们可以把消息发送到组内的所有设备
    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应用程序的启动界面


两个玩家正在玩游戏

“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上面进行本地编程的事情。

文件附件: