码云笔记前端博客
Home > 前端技术 > HTML5游戏之Websocket俄罗斯方块基础版(一)

HTML5游戏之Websocket俄罗斯方块基础版(一)

2019-01-14 分类:前端技术 作者:码云 阅读(4550)

本文共计11815个字,阅读时间预计30分钟,干货满满,记得点赞加收藏哦

俄罗斯方块游戏,对于我们这个年龄段的小伙伴来说都是童年的美好回忆了,这可是当年的经典啊!呵呵,我不是来暴露年龄的。当然现在发展快了,各种小游戏层出不穷,很多年轻的孩子已经对俄罗斯方块不感兴趣了,甚至都没接触过,之所以把它作为我们今天实现的案例,希望对大家在脑海里有一个初步的认知,毕竟玩过的都知道它的具体样子,方便大家理解,没玩过的也没有关系,慢慢跟着我一步一步学,满足你的要求滴。本篇俄罗斯方块游戏是基于HTML5的Websocket实现的,主要带领大家了解要实现俄罗斯方块的基础知识WebSocket,以及socket.io,为后续实现俄罗斯打下基础。本demo案例共分为三章来学完,第二章请移步到《HTML5游戏之Websocket俄罗斯方块进阶版(二)》观看。

  简单介绍一下这个游戏,首先它是双人一起来玩的,玩家可以通过上下左右空格键操作我的游戏区域的俄罗斯方块(图一),对方玩家游戏区域界面同样也有这样的操作(图二),那么对方玩家游戏区域的操作会被实时的同步到我的游戏区域界面的对方的游戏区域(图一),这样我在玩俄罗斯方块的时候就可以实时了解到对方玩家的游戏情况,从而进行一个比赛。

  俄罗斯方块介绍图一:

俄罗斯方块介绍图一

  俄罗斯方块介绍图二:

俄罗斯方块介绍图二

  为了完成这个案例,我们需要小伙伴们具备以下知识:

  1、一些HTML和CSS的基础知识;

  2、javascript基础知识;

  3、用过node.js,因为我们的server是用node搭建的。

  希望大家先把node.js下载下来安装在自己的电脑上,然后运行一个helloworld的例子,由于篇幅内容比较长,所以为了大家学习更轻松,我将本课程分为三篇文章来学习,分别是基础班,单机版,升级版。在本篇我会给大家介绍websocket基础知识、以及websocket的相关例子。

Websocket初体验

  websocket是伴随HTML5出现的一门新技术,它和传统的http是不同的,我们知道传统的http请求是由浏览器发起,服务端接收到请求后返回一个数据,那么,这样一次来回之后请求就断了。但是websocket不一样,同样是由浏览器发出一个请求,但这个请求呢是websocket请求,当服务器接收到一个websocket请求的时候,它会在服务器和浏览器之间建立一个web端和socket链接,这个socket链接就允许浏览器和服务器相互发送消息,如果浏览器端和服务器端都不断开的话,这个socket链接是不会放开的,本质就是TCP的链接。我们看一下具体代码

HTML代码:

1
2
3
4
<h1>Echo test</h1>
<input id="sendTxt" type="text"/>
<button id="sendBtn">发送</button>
<div id="recv"></div>

JS代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script>
    var websocket = new WebSocket("ws://echo.websocket.org/");
    websocket.onopen = function () {
        console.log('wbsocket open');
        document.getElementById("recv").innerHTML = "Connected";
    }
    websocket.onclose = function () {
        console.log('websocket close');
    }
    websocket.onmessage = function (e) {
        console.log(e.data);
        document.getElementById("recv").innerHTML = e.data;
    }
    document.getElementById("sendBtn").onclick = function () {
        var txt = document.getElementById("sendTxt").value;
        websocket.send(txt);
    }
</script>

  注意newWebSocket的大小写,ws为websocket的协议,不是http。

  看一下效果:

Websocket初体验

  上图我们看到websocket已经成功连接,在input框中随便输入写东西,数据也能被成功的接收到,到这儿,相信大家明白案例是什么意思了,当我们在输入框写一些东西,数据发送到那儿了呢,它其实就是发送给了"ws://echo.websocket.org/"这个地址,这个地址的背后就是websocket的server,server是将你发送过去的数据原分不动的返回给你,在图上可以看出接收到的就是返回到的数据。

  接着我们打开控制台(F12或者右键“审查元素”),选择network,勾选上filter,然后我们选择WS,刷新一下页面就看到websocket发出了请求。

websocket发出了请求

  我们看一下Headers里面,Connection它是Upgrade,Upgrade字段是websocket。在RequestHeaders里面呢,Connection也是Upgrade,Upgrade字段也是websocket。这些都是websocket独有的特征。

websocket独有的特征
websocket独有的特征

  我们在切换到Frames里面,我们可以看到websocket发送和接收到的数据,假如我在input框输入数据,从frames中我们看到,绿色条代表我发送出去的数据,白色条代表我接收到的数据。

websocket发送和接收到的数据

  以上内容就是websocket在浏览器的一些基础知识。在这个例子中我们服务器用到的ws地址是"ws://echo.websocket.org/"显然是别人的server,接下来我们用node来搭建一个自己的WebSocket的server服务器。

搭建自己的websocket server

  在实现websocket server前,我们需要一个模块叫nodejs-websocket,在GitHub上找到nodejs-websocket项目地址,在终端工具上通过命令npm install nodejs-websocket在我们当前的项目中安装该模块。

node-websocket模块命令安装

  安装成功之后,我们还需要一段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
var ws = require("nodejs-websocket")

// Scream server example: "hi" -> "HI!!!"
var server = ws.createServer(function (conn) {
    console.log("New connection")
    conn.on("text", function (str) {
        console.log("Received "+str)
        conn.sendText(str.toUpperCase()+"!!!")
    })
    conn.on("close", function (code, reason) {
        console.log("Connection closed")
    })
}).listen(8001)

  简单看一下代码,首先将node-websocket模块引进来,然后调用createServer,里面有一个参数是回调函数,这个server的listen端口是8001,当客户端发送链接过来的时候,coon参数就代表这个链接,text当客户端有消息发过来的时候就被放到str里面,然后通过toUpperCase方法将str变成大写的,然后在链接close关闭的时候打印出一段话。

  我们通过命令node wsServer.js运行起来,然后修改"ws://echo.websocket.org/"地址为"ws://localhost:8001/",我们在页面上测试一下,我在input框输入hi,可以看到打印出来的是“HI!!!”

运行nodeserve.js结果

  然后在终端它也打印出了相应的日志

终端它也打印出了相应的日志

  但是这个程序有个小小的毛病,比如说我现在把运行窗口关闭后,在终端显示server挂了,告诉我们有一个未处理的error事件,如何解决呢?

关闭窗口server报错

  我们在GitHub上的node-websocket官网上查找一下看有没有相关方法,果不其然。

解决服务器挂掉的方法

  我们打开我们的js,在close后边加一个error处理方法,然后我们将端口号拿出来定义一下,传入到listen,在每次启动时打印一句话,代码修改如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var ws = require("nodejs-websocket")

var PORT = 3000

// Scream server example: "hi" -> "HI!!!"
var server = ws.createServer(function (conn) {
    console.log("New connection")
    conn.on("text", function (str) {
        console.log("Received "+str)
        conn.sendText(str.toUpperCase()+"!!!")
    })
    conn.on("close", function (code, reason) {
        console.log("Connection closed")
    })
    coon.on("error", function (err) {
        console.log("handle err");
        console.log(err)
    })
}).listen(PORT)

console.log("websocket server listening on port " + PORT)

这样每次启动server的时候都会打印我们设置好的话

打印输出server信息

  这样我们通过对代码的修改,无论输入,还是关闭窗口,都不会出现出现错误,error也被我们打印出来了。

server运行结果

实现简单聊天功能

HTML代码部分:

1
2
3
<h1>Chat Room</h1>
<input id="sendTxt" type="text"/>
<button id="sendBtn">发送</button>

JS代码部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<script>
    var websocket = new WebSocket("ws://localhost:3000/");
    function showMessage(str) {
        var div = document.createElement('div');
        div.innerHTML = str;
        document.body.appendChild(div);
    }
    websocket.onopen = function () {
        console.log('wbsocket open');
        document.getElementById("sendBtn").onclick = function () {
            var txt = document.getElementById("sendTxt").value;
            if(txt) {
                websocket.send(txt);
            }
        }
    }
    websocket.onclose = function () {
        console.log('websocket close');
    }
    websocket.onmessage = function (e) {
        console.log(e.data);
        showMessage(e.data);
    }
</script>

  当我们点击发送按钮的时候,它会把我们文本框内的内容发送给server,当我们接收到数据的时候,会把这个数据变成一个div添加到我们的body里面。

  接下来我们对server进行改造,首先,我们不可能有一个客户端来连接,我们需要给每个客户端分开一个名字,所以我定义了一个客户端的计数器clientCount,然后在每一次连接的时候自增,然后我们给conn附一个变量,所以当第一个用户连上之后就是user1,第二个就是user2,以此类推。进来之后给每一个客户端发送一个消息通知他们这个客户端进来了,这里我们定义一个broadcast函数

  如何实现这个函数呢?我们只要取到所有server下面所有连接,然后遍历每个连接跟每一个连接都去调用sendText方法就可以了。

  那么怎么样取到server下的所有链接呢?在server有一个connections这么个东西,在这个变量里面就保存了所有连接,在调用forEach,每一个链接就叫connection,然后每一个连接去sendText,这样我们就实现了广播的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var ws = require("nodejs-websocket")

var PORT = 3000;

var clientCount = 0;

// Scream server example: "hi" -> "HI!!!"
var server = ws.createServer(function (conn) {
    console.log("New connection")
    clientCount ++;
    conn.nickname = 'user' + clientCount;
    broadcast(conn.nickname + ' comes in')
    conn.on("text", function (str) {
        console.log("Received "+str)
        broadcast(str)
    })
    conn.on("close", function (code, reason) {
        console.log("Connection closed")
        broadcast(conn.nickname + ' left')
    })
    conn.on("error", function (err) {
        console.log("handle err");
        console.log(err)
    })
}).listen(PORT)

console.log("websocket server listening on port " + PORT);

function broadcast(str) {
    server.connections.forEach(function(connection){
        connection.sendText(str)
    })
}
实现简单聊天功能

  虽然功能实现了,但是还不是很完美,我们在基础上再去优化一下。现在这个e.data知识消息本身,但是我们想想一个用户进来是一个进入的消息,一个用户离开是一个离开的消息,所以作为消息除了本身之外还有一个消息属性,即消息应该是作为一个对象来传的,而在我们的onmessage里的e.data只能接收字符串,那么我们怎么样把一个对象传递过来呢?

  我们在server中第一次获取消息的地方定义一个mes对象,然后我们设置mes对象的type属性为“enter”表示进入的消息,它的数据就是我们发送的这个字符串“conn.nickname+'comesin'”,broadcast发什么,我们可以把对象利用JSON.stringify方法给它格式化一下变成字符串,同样的道理,在这个text中给我们也进行这样的改造,type就是message,data就是我们的字符串str。然后我们再对close进行改造,type为leave,data为“conn.nickname+'left'”这样我们的所有server就改造完毕了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
var ws = require("nodejs-websocket")

var PORT = 3000;

var clientCount = 0;

// Scream server example: "hi" -> "HI!!!"
var server = ws.createServer(function (conn) {
    console.log("New connection")
    clientCount ++;
    conn.nickname = 'user' + clientCount;
    var mes = {}
    mes.type = "enter"
    mes.data = conn.nickname + ' comes in'
    broadcast(JSON.stringify(mes))
    conn.on("text", function (str) {
        console.log("Received "+str)
        var mes = {}
        mes.type = "message"
        mes.data = str
        broadcast(JSON.stringify(mes))
    })
    conn.on("close", function (code, reason) {
        console.log("Connection closed")
        var mes = {}
        mes.type = "leave"
        mes.data = conn.nickname + ' left'
        broadcast(JSON.stringify(mes))
    })
    conn.on("error", function (err) {
        console.log("handle err");
        console.log(err)
    })
}).listen(PORT)

console.log("websocket server listening on port " + PORT);

function broadcast(str) {
    server.connections.forEach(function(connection){
        connection.sendText(str)
    })
}

  index页面内也需要改造,主要是对onmessage我们接收到消息的时候,e.data已经是被格式化的json了,我们可以定义一个对象mes,利用JSON.parse把这个格式化的字符串再变回一个对象,然后在showMessage传递mes.data和mes.type,然后在showMessage在传入一个type参数,在里面判断一下type如果是enter进入的消息,把他的颜色变为蓝色,如果是leave离开的消息,把它变为红色,这样我们通过div的颜色来区分消息的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<script>
    var websocket = new WebSocket("ws://localhost:3000/");
    function showMessage(str, type) {
        var div = document.createElement('div');
        div.innerHTML = str;
        if(type == "enter"){
            div.style.color = "blue";
        }else if (type == "leave"){
            div.style.color = "red"
        }
        document.body.appendChild(div);
    }
    websocket.onopen = function () {
        console.log('wbsocket open');
        document.getElementById("sendBtn").onclick = function () {
            var txt = document.getElementById("sendTxt").value;
            if(txt) {
                websocket.send(txt);
            }
        }
    }
    websocket.onclose = function () {
        console.log('websocket close');
    }
    websocket.onmessage = function (e) {
        console.log(e.data);
        var mes = JSON.parse(e.data);
        showMessage(mes.data, mes.type);
    }
</script>

  还有一个需要我们解决的问题是,我们不能判断是谁发送的消息,这个很简单在我们聊天消息的地方message,把它的名字加上即可“conn.nickname+'says:'+str”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
var ws = require("nodejs-websocket")

var PORT = 3000;

var clientCount = 0;

// Scream server example: "hi" -> "HI!!!"
var server = ws.createServer(function (conn) {
    console.log("New connection")
    clientCount ++;
    conn.nickname = 'user' + clientCount;
    var mes = {}
    mes.type = "enter"
    mes.data = conn.nickname + ' comes in'
    broadcast(JSON.stringify(mes))
    conn.on("text", function (str) {
        console.log("Received "+str)
        var mes = {}
        mes.type = "message"
        mes.data = conn.nickname + ' says: ' + str
        broadcast(JSON.stringify(mes))
    })
    conn.on("close", function (code, reason) {
        console.log("Connection closed")
        var mes = {}
        mes.type = "leave"
        mes.data = conn.nickname + ' left'
        broadcast(JSON.stringify(mes))
    })
    conn.on("error", function (err) {
        console.log("handle err");
        console.log(err)
    })
}).listen(PORT)

console.log("websocket server listening on port " + PORT);

function broadcast(str) {
    server.connections.forEach(function(connection){
        connection.sendText(str)
    })
}
聊天功能优化完整版

  到这里一个简单的聊天功能就实现了,但是回头看一下我们的代码,还有一些问题,比如在我们的代码中出现大量类似于下面的这种代码:

1
2
3
4
var mes = {}
mes.type = "message"
mes.data = conn.nickname + ' says: ' + str
broadcast(JSON.stringify(mes))

  这种代码就是为了把一个对象发送过去而进行的一些格式化,在index页面里呢又把这个字符串反格式化变成一个对象,然后再根据这个对象里面的某个属性比如说类型,根据这个类型进行判断去做相应的业务逻辑。其实这个过程呢和我们的业务逻辑是无关的,什么意思呢,就是现在我们的聊天室需要去做,而俄罗斯方块也需要做这个过程,之后的这个websocket通信可能都需要做这个过程,这种过程就不需要我们来做了,应该交给框架去做,而这种框架早就被人实现好了,所以接下来我会带大家通过socket.io来实现这一通信过程,然后大家和我们自己实现的通信过程做个对比看看有什么不同。

socket.io入门

打开socket.io官网,通过指令下载安装

1
npm install socket.io

将server代码放入到我们新建好的server.js

1
2
3
4
5
6
7
8
9
10
11
var app = require('http').createServer()
var io = require('socket.io')(app);

app.listen(3000);

io.on('connection', function (socket) {
    socket.emit('news', { hello: 'world' });
    socket.on('my other event', function (data) {
        console.log(data);
    });
});

index.html内容,引入socket.io.js(建议下载到本地引入)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Websocket</title>
    <script src="socket.io.js"></script>
</head>
<body>

<script>
    var socket = io('ws://localhost:3000');
    socket.on('news', function (data) {
        console.log(data);
        socket.emit('my other event', { my: 'data' });
    });
</script>
</body>
</html>

然后我们运行一下,它的结果是在控制台输出,里面有一个“hello:world”

socketio运行结果展示

在我们的而服务器端有一个my:'data'

服务器端数据返回

我们简单看一下上面代码,在server中on中有一个function函数里面有一个socket,代表和客户端之间的连接,主要看一下emit,emit就是发送数据,里面有一个参数news,第二个参数直接就是一个对象,那么,在index页面上是怎么接收的呢?直接就是socket.on里面一个function,然后数据data直接过来,emit的发送和服务器上的一模一样。

socketio主要给我们带来两个方面的优势,一是可以直接发送对象,不需要我们之前那样将对象变成字符串接收到之后再把这个字符串变成一个对象;二是大家可以看到'my other event'显然是我们自定义的一个事件,也就是说它这个socket.on任何东西,这样就不需要我们在对象中去定一个类似type的东西。这就是socketio 给我们带来的两个好处。

socket.io改造聊天功能

index.html代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<head>
    <meta charset="UTF-8">
    <title>Websocket</title>
    <script src="socket.io.js"></script>
</head>
<body>
<h1>Chat Room</h1>
<input id="sendTxt" type="text"/>
<button id="sendBtn">发送</button>
<script>
    var socket = io('ws://localhost:3000/');
    function showMessage(str, type) {
        var div = document.createElement('div');
        div.innerHTML = str;
        if(type == "enter"){
            div.style.color = "blue";
        }else if (type == "leave"){
            div.style.color = "red"
        }
        document.body.appendChild(div);
    }
    document.getElementById("sendBtn").onclick = function () {
        var txt = document.getElementById("sendTxt").value;
        if(txt) {
            socket.emit('message', txt);
        }
    }
    socket.on('enter', function (data) {
        showMessage(data, 'enter');
    })

    socket.on('message', function (data) {
        showMessage(data, 'message');
    })

    socket.on('leave', function (data) {
        showMessage(data, 'leave');
    })
</script>
</body>

server代码部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var app = require('http').createServer()
var io = require('socket.io')(app);

var PORT = 3000;

var clientCount = 0;

app.listen(3000);

io.on('connection', function (socket) {
    clientCount ++;
    socket.nickname = 'user' + clientCount;
    io.emit('enter',socket.nickname + ' comes in')

    socket.on('message', function (str) {
        io.emit('message', socket.nickname + ' says: ' + str)
    })

    socket.on('disconnect', function () {
        io.emit('leave', socket.nickname + ' left')
    })
});

console.log("websocket server listening on port " + PORT);

效果展示:

socket.io改造聊天功能

使用socket.io改造代码,可以看出比之前我们写的代码经济了许多,不再那么臃肿。源码下载websocket

结束语

以上所有知识点就为大家介绍完了,给大家再来一下小小的总结, 首先websocket特点是允许浏览器和服务器建立持久的连接,如果你的业务中需要服务器向浏览器发送数据的场景呢,使用websocket是再合适不过了;接下来又为大家介绍了HTML5的websocket API,主要包括on.open,on.message,on.close;然后又给大家介绍了怎样使用nodejs-websocket去实现一个简单的websocket server;然后我们利用这两个知识点去实现一个简单的聊天功能,但是nodejs-websocket模块功能不够强大,所以又给大家介绍socket.io这个模块,它主要有两个方面的优势,第一,在发送数据的时候,可以直接发送js对象,这样就不需要在发送数据前对对象进行格式化,然后在收到数据之后呢,又把格式话后的字符串反序列化成一个对象,省去了这些步骤;第二,它可以去自定义消息,这样就不需要在数据里面设计type这样一个字段,最后利用socket.io把我们之前写的聊天功能改造了一下,代码精简了许多,希望大家下去之后对websocket多多联系,熟练掌握,在接下来的俄罗斯方块案例它的背后通讯原理都是根据本章介绍的websocket。
如有问题,留言探讨,语句不当之处请多多包涵!

「除特别注明外,本站所有文章均为码云笔记原创,转载请保留出处!」

赞(7) 打赏

觉得文章有用就打赏一下文章作者

支付宝
微信
7

觉得文章有用就打赏一下文章作者

支付宝
微信

上一篇:

下一篇:

你可能感兴趣

共有 0 条评论 - HTML5游戏之Websocket俄罗斯方块基础版(一)

博客简介

码云笔记 mybj123.com,一个专注Web前端开发技术的博客,主要记录和总结博主在前端开发工作中常用的实战技能及前端资源分享,分享各种科普知识和实用优秀的代码,以及分享些热门的互联网资讯和福利!码云笔记有你更精彩!
更多博客详情请看关于博客

精彩评论

站点统计

  • 文章总数: 472 篇
  • 分类数目: 13 个
  • 独立页面: 8 个
  • 评论总数: 228 条
  • 链接总数: 15 个
  • 标签总数: 1036 个
  • 建站时间: 522 天
  • 访问总量: 8681340 次
  • 最近更新: 2019年11月18日