H5 游戏 俄罗斯方块 双人互动游戏

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/CJXBShowZhouyujuan/article/details/77823993

最近在慕课网上看到了一个课程是关于俄罗斯方块的。用到了socket.io 做双屏互动的游戏。正好最近在看websocket所以就把整个课程看完了,感觉很有意思,这里用一篇文章仔细的分析下这个游戏的制作思路。

这里写图片描述

这里写图片描述
实际在操作的时候,对方游戏区域会同步对方的操作。

js部分先进行逻辑的分析

先不讲socket.io 同步部分,先分析游戏实现的逻辑。由于这部分js代码量比较大,所以我们先组织好开发的架构

根据作者提供的结构图。可以看出。我们使用了

script.js

这部分是初始化远程和本地游戏的一个入口,将来也是和服务器代码进行交互的一个入口。
local.js
这个文件主要处理的是本地,单机版的游戏时,操作的逻辑
本地操作包括:

启动游戏
点击不同的键盘的触发事件,旋转、左、右、下、下落
启动之后出现自动下落的方块
游戏结束的方法
离开游戏的方法
游戏成功,失败的方法

remote.js

在双人版是 负责监听 服务器传送过来的事件。用于同步数据。

game.js

这里处理的是游戏的事件执行逻辑
在local.js 中点击键盘触发的事件逻辑都是在game.js 中实现的

square.js

这是方块的设定。包括当前界面和next界面。
随机出现的方块的样子,是否还可以再移动,移动是否到了边界等。

squareFactory.js

这里使用了原型链的方式实现了对square.js 的继承
分别构造出了7中方块的不同形态变化。

以上就是整个的架构模式。

现在分析一下整个架构之间的通信

1、游戏刚开始

先加载了script.js 这个文件,在这个文件中我们初始化 local 和 remote
那多对象后调用start方法。游戏开始了。

这里可以吧local对象理解为一个类,在这个类里面封装了本地需要的操作。暴露出去需要在外部调用的接口。这样可以保护私有变量。

var Local = function(socket) {
    //游戏对象
    var game;
    //方块下落的时间间隔
    var INTERVAL = 200;
    var timer = null;
    //时间次数
    var timeCount = 0;
    //游戏了多久
    var time = 0;
    //绑定键盘事件
    var bindKeyEvent = function() {
        document.onkeydown = function(e) {
        }
    }
        ......
    //开始方法
    var start = function() {
        var doms = {
            gameDiv: document.getElementById('local_game'),
            nextDiv: document.getElementById('local_next'),
            timeDiv: document.getElementById('local_time'),
            scoreDiv: document.getElementById('local_score'),
            gameoverDiv: document.getElementById('local_gameover')
        }
        game = new Game();
        var type = generateType();
        var dir = generateDir();
        game.init(doms, type, dir);
        socket.emit('init', { type: type, dir: dir });

        bindKeyEvent();
        var t = generateType();
        var d = generateDir();
        game.performNext(t, d);
        socket.emit('next', { type: t, dir: d });
        //让方块自己下落
        timer = setInterval(move, INTERVAL);
    }
  ........
    //导出API   这个方法可以在外部访问到。上面的都是私有的不可被访问到。
     this.start = start;
}

这算是单利的模式。
我们看到了这里初始化了game对象

game.js 是同样的模式

var Game = function() {
    //dom 元素
    var gameDiv;
    var nextDiv;

    var timeDiv;
    var scoreDiv;
    var gameoverDiv;
    //保留得分
    var score = 0;

    //游戏矩阵
    // 10*20
    var gameData = [
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    ];

    //当前方块
    var cur;
    //下一个方块
    var next;

    //divs
    var nextDivs = [];
    var gameDivs = [];

    //初始化
    var init = function(doms, type, dir) {
        gameDiv = doms.gameDiv;
        nextDiv = doms.nextDiv;
        timeDiv = doms.timeDiv;
        scoreDiv = doms.scoreDiv;
        gameoverDiv = doms.gameoverDiv;
        // 这里要修改为随机的
        // cur = SquareFactory.prototype.make(0,0);
        next = SquareFactory.prototype.make(type, dir);
        initDiv(gameDiv, gameData, gameDivs);
        initDiv(nextDiv, next.data, nextDivs);
        // setData();
        // console.log('gameData', gameData);
        // refreshDiv(gameData, gameDivs);
        refreshDiv(next.data, nextDivs);
    }

    //导出API
    this.init = init;
    this.down = down;
    this.left = left;
    this.right = right;
    this.rotate = rotate;
    this.fall = function() {
        while (down());
    }
    this.fixed = fixed;
    this.performNext = performNext;
    this.checkClear = checkClear;
    this.checkGameOver = checkGameOver;
    this.setTime = setTime;
    this.addScore = addScore;
    this.gameover = gameover;
    this.addTailLines = addTailLines;
}

在game.js中导出了这么多的方法。在其他的文件中都是可以调用的。只要我们 new 一个game对象就可以了。这样就可以实现两个 对象之间的通信了。

Square.js

这个对象的作用是抽出所的方块都需要的变量和方法。然后在实例化单个对象,每个对象都继承自这个父对象。
这里的处理方法就是构造函数和原型链的结合

var Square = function() {
    //方块数据
    this.data = [
        [0, 0, 0, 0],
        [0, 0, 0, 0],
        [0, 0, 0, 0],
        [0, 0, 0, 0]
    ];

    //原点
    this.origin = {
        x: 0,
        y: 0
    }

    //旋转的方向,也就是旋转数组中的索引
    this.dir = 0;

}

//是否还可以旋转
Square.prototype.canRotate = function(isValid) {
    var d = (this.dir + 1)%4;

    var test = [
        [0, 0, 0, 0],
        [0, 0, 0, 0],
        [0, 0, 0, 0],
        [0, 0, 0, 0]
    ];
    for (var i = 0; i < this.data.length; i++) {
        for (var j = 0; j < this.data[0].length; j++) {
            test[i][j] = this.rotates[d][i][j];
        }
    }
    return isValid(this.origin, test);
}

Square.prototype.rotate = function(num) {
    if(!num){
        num = 1;
    }
    this.dir = (this.dir + num)%4;


    for (var i = 0; i < this.data.length; i++) {
        for (var j = 0; j < this.data[0].length; j++) {
            this.data[i][j] = this.rotates[this.dir][i][j];
        }
    }
}




//是否还可以下降
Square.prototype.canDown = function(isValid) {
    var test = {};
    test.x = this.origin.x + 1;
    test.y = this.origin.y;
    return isValid(test, this.data);
}

Square.prototype.down = function() {
    this.origin.x = this.origin.x + 1;
    console.log(this.origin.x);
}

//是否还可以左移
Square.prototype.canLeft = function(isValid) {
    var test = {};
    test.x = this.origin.x;
    test.y = this.origin.y - 1;
    return isValid(test, this.data);
}

Square.prototype.left = function() {
    this.origin.y = this.origin.y - 1;
    console.log(this.origin.x);
}

//是否还可以右移
Square.prototype.canRight = function(isValid) {
    var test = {};
    test.x = this.origin.x;
    test.y = this.origin.y + 1;
    return isValid(test, this.data);
}

Square.prototype.right = function() {
    this.origin.y = this.origin.y + 1;
}

这个对象中,对Squrae的方法都绑定到了原型链上。这样子对象,就可以通过原型链找到这个公共的方法。

SquareFactory.js
这是个初始化不同类型的方块的工程,根据随机的方块样式 和 方块的旋转方向来初始化不同的方块。

var Square1 = function() {
   Square.call(this);
    //旋转数组后,枚举出来的值
    this.rotates = [
        [
            [0, 2, 0, 0],
            [0, 2, 0, 0],
            [0, 2, 0, 0],
            [0, 2, 0, 0]
        ],
        [
            [0, 0, 0, 0],
            [2, 2, 2, 2],
            [0, 0, 0, 0],
            [0, 0, 0, 0]
        ],
        [
            [0, 2, 0, 0],
            [0, 2, 0, 0],
            [0, 2, 0, 0],
            [0, 2, 0, 0]
        ],
        [
            [0, 0, 0, 0],
            [2, 2, 2, 2],
            [0, 0, 0, 0],
            [0, 0, 0, 0]
        ]

    ]
}

Square1.prototype = Square.prototype;

这里初始化第一个方块类型;
使用的是构造函数和原型链方法
Square.call(this); 这个方法就扩展了this的范围。使得Square1 获取了Square的变量。
在这个之后在声明Square1 自己的变量;

Square1.prototype = Square.prototype ;
把父对象的原型链赋值给了Square1. 这样就完成了继承。

var SquareFactory = function() {};
SquareFactory.prototype.make = function(index, dir) {
    var s;
    index = index + 1;
    switch (index) {
        case 1:
                s = new Square1();
                break;
        case 2:
                s = new Square2();
                break;
        case 3:
                s = new Square3();
                break;
        case 4:
                s = new Square4();
                break;
        case 5:
                s = new Square5();
                break;
        case 6:
                s = new Square6();
                break;
        case 7:
                s = new Square7();
                break;
                default:
                break;

    }

    s.origin.x = 0;
    s.origin.y = 3;
    s.rotate(dir);
    return s;
}

这里的SquareFactory 创建的对象,在其他文件是可访问到的。

这里涉及到了很多知识。原型链,继承等,这部分就是面向对象的程序设计的思想。

下一个知识点 socket.io

https://socket.io/docs/
先下载socket.io。
同时还要引入socket.io.js 文件
https://socket.io/blog/

然后我们创建服务端
创建wsWerver.js 文件

var app = require('http').createServer();
var io = require('socket.io')(app);
var PORT = 3000;
app.listen(PORT);
//客户端的计数
var clientCount = 0;
//用来存储客户端的socket
var socketMap = {};
var bindListener = function(socket, event) {
    socket.on(event, function(data) {
        if (socket.clientNum % 2 == 0) {
            //有两个人了
            if (socketMap[socket.clientNum - 1]) {
                socketMap[socket.clientNum - 1].emit(event, data);
            }
        } else {
            if(socketMap[socket.clientNum + 1]){
                socketMap[socket.clientNum + 1].emit(event, data);
            }

        }
    })
}

io.on('connection', function(socket) {
    clientCount = clientCount + 1;
    // 把clientCount 存储在socketsocket.clientNum = clientCount;
    socketMap[clientCount] = socket;
    if (clientCount % 2 == 1) {
        socket.emit('waiting', 'waiting for another persion');
    } else {
        //配对的socket
        if(socketMap[(clientCount - 1)]){
            socket.emit('start');
            socketMap[(clientCount - 1)].emit('start');
        }else{
            socket.emit('leave');
        }
    }

    bindListener(socket, 'init');
    bindListener(socket, 'next');
    bindListener(socket, 'rotate');
    bindListener(socket, 'right');
    bindListener(socket, 'down');
    bindListener(socket, 'left');
    bindListener(socket, 'fall');
    bindListener(socket, 'fixed');
    bindListener(socket, 'line');
    bindListener(socket, 'time');
    bindListener(socket, 'lose');
    bindListener(socket, 'bottomLines');
    bindListener(socket, 'addTailLines');
    socket.on('disconnect', function() {
          if (socket.clientNum % 2 == 0) {
            //有两个人了
            if (socketMap[socket.clientNum - 1]) {
                socketMap[socket.clientNum - 1].emit('leave');
            }
        } else {
            if(socketMap[socket.clientNum + 1]){
                socketMap[socket.clientNum + 1].emit('leave');
            }
        }
        delete(socketMap[socket.clientNum]);
    });
})
console.log('websocket listening on port' + PORT);

服务器的作用是为了监听 客户端发送的需要同步到对方区域的数据。
socket.on() 监听事件 socket.emit() 发送事件

本地与服务器的链接在script.js 中

var socket = io('ws://localhost:3000');
var local = new Local(socket);
var remote = new Remote(socket);

这样就可以吧链接的socket 传递到local和remote中了。

比如服务器检测到有两个玩家了,发去了可以开始游戏的指令“start”。
这时在local 中就检测到了 “start”.然后就可以出发start()方法了。

 socket.on('start', function() {
        document.getElementById('waiting').innerHTML = "";
        start();
    });

在local中 触发了初始化游戏的方法,同时也要同步到对方游戏区域。这是就发送给服务器一个消息
告诉他我的游戏初始化了,并传递过去初始化的参数
game.init(doms, type, dir);
socket.emit(‘init’, { type: type, dir: dir });

游戏的整体整体结构就是这样了,具体的游戏实现细节就不用讲了,感兴趣的可以去看视频。
我也把代码上传到了我的github上了,需要node环境。执行命令是 node wsServer.js
https://github.com/zhouyujuan/games/tree/master

猜你喜欢

转载自blog.csdn.net/CJXBShowZhouyujuan/article/details/77823993