制作一个可以多屏互动的酷炫3D贪吃蛇

本文总浏览量


最终效果

demo只能访问到无websocket服务器的一部分,按键盘上下左右控制贪吃蛇的移动

如何愉快地玩耍

  • 第一步,当然是先star啦这是必须的呀:)git clone把项目下到本地
  • 第二步,当然你电脑得node,因为我的node_modules已经直接放在github上了,就不用你npm install了,你就可以直接一步node websocket.js开启websocket服务器
  • 第三步就是把index.html和index2.html都打开就可以啦,可以通过index2.html的遥控来控制index.html

如果用手机控制电脑游戏效果更佳哦~就像一个游戏手柄一样,你也可以本地再起个服务器,然后电脑和手机同一个局域网,手机访问index2.html就行了(注意要变动下localhost),或者也可以挂到云主机上,这个我试过了效果还不错,因为这个我做的这个只支持单用户,就不放地址了

项目细节

一、threejs构建3D立体效果

游戏中使用threejs创建了场景,摆好相机,然后加上一个渲染器便实现了酷炫的3D效果
(以下只给关键代码,省去部分代码)

1.创建场景

1
2
3
4
5
6
7
8
9
10
11
12
13
//场景
scene = new THREE.Scene();
//照相机
camera = new THREE.OrthographicCamera(window.innerWidth / -2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / -2, -500, 1000);
camera.position.x = 100;
camera.position.y = 100;
camera.position.z = 100;
//渲染器
renderer = new THREE.CanvasRenderer();
renderer.setClearColor( 0xf0f0f0 );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );

2.场景加入必要的东西

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
//加入线条
var size = 500, step = 50;
var geometry = new THREE.Geometry();
for ( var i = - size; i <= size; i += step) {
geometry.vertices.push( new THREE.Vector3( - size, 0, i ) );
geometry.vertices.push( new THREE.Vector3( size, 0, i ) );
geometry.vertices.push( new THREE.Vector3( i, 0, - size ) );
geometry.vertices.push( new THREE.Vector3( i, 0, size ) );
}
var material = new THREE.LineBasicMaterial( { color: 0x000000, opacity: 0.2 } );
var line = new THREE.LineSegments( geometry, material );
scene.add( line );
//加入灯光
var ambientLight = new THREE.AmbientLight( Math.random() * 0x10 );
scene.add( ambientLight );
var directionalLight = new THREE.DirectionalLight( 0xffffff );
directionalLight.position.x = -0.3;
directionalLight.position.y = 0.8;
directionalLight.position.z = 0.3;
directionalLight.position.normalize();
scene.add( directionalLight );

可能有读者疑问,那个蛇的部分哪里加进去了,蛇的部分就是一些正方体,以为蛇的长度会变动,所以并没有在init代码中具体加入,只给出了cubeGroup = new THREE.Object3D();
而在贪吃蛇游戏部分封装了一个绘制Cube的韩式易于调用绘制蛇身,也是往scene加入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Cube(x, y, z, a, color) {
this.x = x;
this.y = y;
this.z = z;
this.a = a;
this.color = color;
}
Cube.prototype.draw = function() {
var geometry = new THREE.BoxGeometry( this.a, this.a, this.a );
var material = new THREE.MeshLambertMaterial( { color: this.color, overdraw: 0.5 } );
var cube = new THREE.Mesh( geometry, material );
cube.position.x = this.x;
cube.position.z = this.z;
cubeGroup.add(cube);
scene.add(cubeGroup);
}

3.创建交互动画

其实此部分也只是实现一直重绘,而不是空间移动,通过照相机的视角实现3D效果

1
2
3
4
5
6
7
8
9
10
11
12
function render() {
//写变化
scene.remove(cubeGroup);
cubeGroup = new THREE.Object3D();
snake.draw();
if(GameStart) {
snake.move();
}
food.draw();
camera.lookAt( scene.position );
renderer.render( scene, camera );
}

二、贪吃蛇游戏算法部分

其实贪吃蛇的算法并不难,就是刚开始先定好初始长度,其实蛇的身子就是一个数组,移动的关键在于蛇头,要特别的取出蛇头,在键盘交互或者消息传输来后给出移动方向后,在蛇头处“缓存”一个相同的蛇头,根据坐标判断是否吃到东西,假如蛇头碰到食物,就直接让原来的蛇头往那个方向移动,刚刚放的那个相同的蛇头就在原处(就是其他不变),这样就实现了增长和移动,假如没碰到食物,还是让原来那个蛇头也是往那个方向移动,但是要把这个蛇的身子的这个数组pop()掉最后一个元素,实现蛇身长度没变。这样一直做下去,其实也就实现了贪吃蛇游戏。

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
function Snake () {
var snakeArr = [];
for (var i = 0; i < 4; i++) {
var cube = new Cube(i*50, 50, 0, 50, 0xffffff);
snakeArr.splice(0,0,cube);
}
var head = snakeArr[0];
head.color = "red";
this.head = snakeArr[0];
this.snakeArr = snakeArr;
this.direction = 39;
}
Snake.prototype.draw = function () {
if(this.isover) {
return;
}
for (var i = 0; i < this.snakeArr.length; i++) {
this.snakeArr[i].draw();
}
}
Snake.prototype.move = function () {
var cube = new Cube(this.head.x, this.head.y, this.head.z, this.head.a, 0xffffff);
this.snakeArr.splice(1, 0, cube);
if (isEat()) {
food = new getRandomFood();
} else {
this.snakeArr.pop();
}
switch (this.direction) {
case 37://左
this.head.x -= this.head.a;
break;
case 38://上
this.head.z -= this.head.a;
break;
case 39: //右
this.head.x += this.head.a;
break;
case 40://下
this.head.z += this.head.a;
break;
default:
break;
}
if (this.head.x > 450 || this.head.x < -500 || this.head.z > 450 || this.head.z < -500){
this.isover= true;
stop();
}
for (var i = 1; i < this.snakeArr.length; i++) {
if (this.snakeArr[i].x == this.head.x && this.snakeArr[i].z == this.head.z){
this.isover= true;
stop();
}
}
}

到这里基本贪吃蛇游戏关键部分讲解也就结束了,我这里也只是粗略的讲解,代码只截取部分,有需要的可以直接上https://github.com/BUPT-HJM/3d-snake查看。建议读者可以先尝试用canvas去写,原理都是一样的。

三、websocket多屏互动部分

这里我使用了nodejs-websocket,其实用起来也不是很复杂

1.websocket服务器部分
在服务器这里的部分就是需要开server,listen一下端口,判断游戏端和控制端是否都加载完成,并且要判断是游戏端还是控制端来保存connection对象,具体可以参考https://www.npmjs.com/package/nodejs-websocket

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
var ws = require("nodejs-websocket");
console.log("开始建立连接...")
var gameReady = false;
var controlReady = false;
var game = null;
var cotrol = null;
var server = ws.createServer(function(conn) {
conn.on("text", function(str) {
console.log("收到的信息为:" + str)
if (str === "game") {
game = conn;
gameReady = true;
console.log("Game is ready")
}
if (str === "control") {
control = conn;
controlReady = true;
console.log("Control is ready")
}
if (gameReady && controlReady) {
game.sendText(str);
}
conn.sendText(str);
})
conn.on("close", function(code, reason) {
console.log("关闭连接")
});
conn.on("error", function(code, reason) {
console.log("异常关闭")
});
}).listen(8001)
console.log("WebSocket建立完毕")

2.游戏端部分
游戏端通过onmessage来接收消息,然后根据消息对页面交互

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
43
44
45
46
47
48
49
50
51
52
53
54
if (window.WebSocket) {
var ws = new WebSocket('ws://localhost:8001');
ws.onopen = function(e) {
console.log("连接服务器成功");
ws.send("game");
}
ws.onclose = function(e) {
console.log("服务器关闭");
}
ws.onerror = function() {
console.log("连接出错");
}
ws.onmessage = function(e) {
switch(e.data){
case "left":{
if (snake.direction !== 39){
GameStart = true;
snake.direction = 37;
}
break;
}
case "top":{
if (snake.direction !== 40){
GameStart = true;
snake.direction = 38;
}
break;
}
case "right":{
if (snake.direction !== 37){
GameStart = true;
snake.direction = 39;
}
break;
}
case "bottom":{
if (snake.direction !== 38){
GameStart = true;
snake.direction = 40;
}
break;
}
case "restart": {
location.reload();
break;
}
default:
break;
}
}
}

3.控制端部分
通过点击send不同的消息

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
if (window.WebSocket) {
var ws = new WebSocket('ws://localhost:8001');
ws.onopen = function(e) {
console.log("连接服务器成功");
ws.send("control");
}
ws.onclose = function(e) {
console.log("服务器关闭");
}
ws.onerror = function() {
console.log("连接出错");
}
ws.onmessage = function(e) {
}
}
document.querySelector(".item-1 .item-tri-1").onclick = function() {
ws.send("top");
}
document.querySelector(".item-1 .item-tri-2").onclick = function() {
ws.send("bottom");
}
document.querySelector(".item-2 .item-tri-1").onclick = function() {
ws.send("left");
}
document.querySelector(".item-2 .item-tri-2").onclick = function() {
ws.send("right");
}
document.querySelector("#restart").onclick = function() {
ws.send("restart");
}

最后大功告成啦哈哈哈~看完记得给star啦~送上github地址https://github.com/BUPT-HJM/3d-snake,欢迎clone愉快地玩耍~

参考


可自由转载、引用,但需署名作者且注明文章出处。