以下是详细的js html5 css俄罗斯方块游戏再现攻略:
1.前置知识准备
在开始实现俄罗斯方块游戏前,需要掌握HTML5、CSS、JavaScript等前端技术。特别是JavaScript中的面向对象编程、事件响应等知识。同时,也需要掌握Canvas绘图技术。
2.实现思路
俄罗斯方块游戏的基本思路是:方块下落、方块移动、方块旋转、方块消除等操作。因此,可以将方块对象以及相关操作封装成一个类,通过定时器模拟下落及用户键盘事件来控制方块的移动、旋转等操作。同时,还需要考虑到边界判断、方块落地后是否可以消除等问题。
3.实现步骤
3.1. 构造方块类
在JavaScript中定义一个方块类,该类包含方块的形状、位置、大小信息,以及方块移动、旋转、下落等方法。其中,方块的形状通过二维数组表示,只需要定义7种常见形状的方块类型。具体示例如下:
方块类定义
/**
* 方块类
*/
class Block {
constructor(type, x, y, size) {
this.type = type; //方块类型
this.x = x; //方块左上角x坐标
this.y = y; //方块左上角y坐标
this.size = size; //方块大小
this.shape = BLOCK_TYPE[type]; //方块形状
}
// 方块下落
down(){
this.y += this.size;
}
// 方块向左移动
left(){
this.x -= this.size;
}
// 方块向右移动
right(){
this.x += this.size;
}
// 方块旋转操作
rotate(){
let newShape = rotateShape(this.shape);
// 判断旋转后是否超过画布边界
if(isExceed(this.x, this.y, newShape)){
return;
}
this.shape = newShape;
}
//绘制方块
draw(ctx){
ctx.fillStyle = BLOCK_COLOR[this.type];
for(let i = 0; i < this.shape.length; i++){
for(let j = 0; j < this.shape[i].length; j++){
if(this.shape[i][j] > 0){//绘制实心方块
ctx.fillRect(this.x + j*this.size, this.y + i*this.size, this.size, this.size);
}
}
}
}
}
//7种常见方块的形状
const BLOCK_TYPE = [
[
[1, 1],
[1, 1]
],
[
[1, 0, 0],
[1, 1, 1]
],
[
[0, 0, 1],
[1, 1, 1]
],
[
[1, 1, 0],
[0, 1, 1]
],
[
[0, 1, 1],
[1, 1, 0]
],
[
[1, 1, 1, 1]
],
[
[1, 1, 1],
[0, 1, 0]
]
];
//方块颜色
const BLOCK_COLOR = [
"#fff", "#f00", "#0f0", "#00f", "#ff0", "#f0f", "#0ff"
];
//方块旋转算法,实现方块顺时针旋转功能
const rotateShape = shape => {
let newShape = [];
for(let i = 0; i < shape[0].length; i++){ //先遍历数组列数
newShape.push([])
for(let j = shape.length-1; j >= 0 ; j--){ //在遍历行数,并倒序扫描
newShape[i].push(shape[j][i]);
}
}
return newShape;
}
//是否超出边界判断, x:方块左上角横坐标,y:方块左上角纵坐标,shape:方块形状数组
const isExceed = (x, y, shape) => {
for(let i = 0; i < shape.length; i++){
for(let j = 0; j < shape[i].length; j++){
if(shape[i][j] > 0){ //只判断实心方块
let newX = x + j*BLOCK_SIZE;
let newY = y + i*BLOCK_SIZE;
if(newX < 0 || newX >= WIDTH || newY < 0 || newY >= HEIGHT){
return true;
}
}
}
}
return false;
}
3.2. 定义游戏操作类
游戏操作类包含游戏的初始化、开始、暂停、易位等操作。其中,开始操作用于启动方块下落的定时器;易位则用于在方块下落后快速移动方块到底部。具体实现如下:
游戏操作类定义
/**
* 游戏操作类
*/
class Game {
constructor(ctx){
this.ctx = ctx;
this.block = null; //当前活动方块
this.fallTime = 500; //方块下落时间间隔
this.canMove = true; //是否可以移动方块
this.canRotate = true; //是否可以旋转方块
this.status = "init"; //游戏状态
this.score = 0; //游戏分数
this.map = this.createMap(); //游戏地图
}
//初始化游戏
init(){
this.ctx.clearRect(0, 0, WIDTH, HEIGHT);
this.fallTime = 500;
this.score = 0;
this.map = this.createMap();
this.block = new Block(this.getRandomType(), 4*BLOCK_SIZE, 0, BLOCK_SIZE);
this.drawMap();
this.drawBlock();
}
//开始游戏
start(){
if(this.status == "init" || this.status == "end"){
this.status = "running";
this.timer = setInterval(()=>{
this.block.down();
if(this.isDead()){ //游戏结束
this.status = "end";
clearInterval(this.timer);
alert('game over! score=' + this.score);
return;
}
if(this.isCollision()){ //方块落地
this.mapBlock();
this.eliminate();
this.block = new Block(this.getRandomType(), 4*BLOCK_SIZE, 0, BLOCK_SIZE); //新建下一个活动方块
}
this.drawMap();
this.drawBlock();
}, this.fallTime);
}else if(this.status == "pause"){
this.status = "running";
this.timer = setInterval(()=>{
this.block.down();
if(this.isDead()){
this.status = "end";
clearInterval(this.timer);
alert('game over! score=' + this.score);
return;
}
if(this.isCollision()){
this.mapBlock();
this.eliminate();
this.block = new Block(this.getRandomType(), 4*BLOCK_SIZE, 0, BLOCK_SIZE);
}
this.drawMap();
this.drawBlock();
}, this.fallTime);
}
}
//暂停游戏
pause(){
if(this.status == "running"){
clearInterval(this.timer);
this.status = "pause";
}
}
//方块快速移动到底部
moveDown(){
if(this.canMove){ //控制移动频率
this.canMove = false;
clearInterval(this.timer);
while(!this.isCollision()){
this.block.down();
}
this.mapBlock();
this.eliminate();
this.block = new Block(this.getRandomType(), 4*BLOCK_SIZE, 0, BLOCK_SIZE);
this.drawMap();
this.drawBlock();
this.canMove = true;
}
}
//方块向左移动
moveLeft(){
if(this.canMove){
this.canMove = false;
this.block.left();
if(this.isExceed() || this.isCollision()){
this.block.right(); //操作无效时复原
}else{
this.drawMap();
this.drawBlock();
}
this.canMove = true;
}
}
//方块向右移动
moveRight(){
if(this.canMove){
this.canMove = false;
this.block.right();
if(this.isExceed() || this.isCollision()){
this.block.left();
}else{
this.drawMap();
this.drawBlock();
}
this.canMove = true;
}
}
//方块顺时针旋转
rotate(){
if(this.canRotate){
this.canRotate = false;
this.block.rotate();
if(this.isExceed() || this.isCollision()){
this.block.rotate();
this.block.rotate();
this.block.rotate();
}else{
this.drawMap();
this.drawBlock();
}
this.canRotate = true;
}
}
// 画方块
drawBlock() {
this.block.draw(this.ctx);
}
// 画地图
drawMap(){
this.ctx.clearRect(0,0,WIDTH,HEIGHT);
for(let i=0; i<this.map.length; i++){
for(let j=0; j<this.map[i].length; j++){
if(this.map[i][j] > 0){
this.ctx.fillStyle = BLOCK_COLOR[this.map[i][j]];
this.ctx.fillRect(j*BLOCK_SIZE, i*BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
}
}
}
}
//落地,固定方块
mapBlock(){
for(let i=0; i<this.block.shape.length; i++){
for(let j=0; j<this.block.shape[i].length; j++){
if(this.block.shape[i][j] > 0){
let x = this.block.x/BLOCK_SIZE + j;
let y = this.block.y/BLOCK_SIZE + i;
this.map[y][x] = this.block.type;
}
}
}
}
//消除一行方块
eliminate(){
for(let i=this.map.length-1; i>=0; i--){
let flag = true;
for(let j=0; j<this.map[i].length; j++){
if(this.map[i][j] == 0){
flag = false;
break;
}
}
if(flag){
for(let k=i-1; k>=0; k--){
for(let j=0; j<this.map[k].length; j++){
this.map[k+1][j] = this.map[k][j];
}
}
this.score += 10;
this.fallTime -= 10; //方块下落速度逐渐变快
this.drawMap();
}
}
}
//是否死亡
isDead(){
for(let i=0; i<this.block.shape.length; i++){
for(let j=0; j<this.block.shape[i].length; j++){
if(this.block.shape[i][j] > 0){
let x = Math.floor(this.block.x/BLOCK_SIZE) + j;
let y = Math.floor(this.block.y/BLOCK_SIZE) + i;
if(y < 0 || this.map[y][x] > 0){
return true;
}
}
}
}
return false;
}
//是否发生碰撞
isCollision(){
if(this.block.y + this.block.shape.length*this.block.size > HEIGHT){ //是否触底
return true;
}
for(let i=0; i<this.block.shape.length; i++){
for(let j=0; j<this.block.shape[i].length; j++){
if(this.block.shape[i][j] > 0){
let x = Math.floor(this.block.x/BLOCK_SIZE) + j;
let y = Math.floor(this.block.y/BLOCK_SIZE) + i;
if(x < 0 || x >= this.map[0].length || y >= this.map.length || this.map[y][x] > 0){
return true;
}
}
}
}
return false;
}
//是否超出边界
isExceed(){
return isExceed(this.block.x, this.block.y, this.block.shape);
}
//生成随机方块
getRandomType(){
return Math.floor(Math.random()*BLOCK_TYPE.length);
}
//生成游戏地图
createMap(){
const map = [];
for(let i=0; i<ROW; i++){
map[i] = [];
for(let j=0; j<COLUMN; j++){
map[i][j] = 0;
}
}
return map;
}
}
3.3. 完成键盘事件响应
通过添加键盘事件监听器,实现对方块下落、移动、旋转等操作。具体实现如下:
键盘事件响应
/**
* 添加键盘事件监听器
*/
window.addEventListener('keydown', function(e) {
switch (e.key) {
case "ArrowLeft"://方块左移
game.moveLeft();
break;
case "ArrowRight"://方块右移
game.moveRight();
break;
case "ArrowDown"://方块快速下落
game.moveDown();
break;
case "ArrowUp"://方块旋转
game.rotate();
break;
}
});
/**
* 点击“开始/重新开始”按钮
*/
btnStart.addEventListener("click", function(){
game.init();
game.start();
})
/**
* 点击“暂停”按钮
*/
btnPause.addEventListener("click", function(){
game.pause();
})
/**
* 点击“易位”按钮
*/
btnMove.addEventListener("click", function(){
game.moveDown();
})
4.示例运行和演示
4.1. 示例1
本案例中实现的俄罗斯方块游戏在线运行地址如下:
https://htmlpreview.github.io/?https://github.com/aitiger/russian-block/blob/master/index.html
在页面中,点击“开始/重新开始”按钮,即可开始游戏。
4.2. 示例2
也可以在本地计算机上运行该游戏。具体操作如下:
- 下载源代码:
git clone https://github.com/aitiger/russian-block.git
2. 进入项目目录:
cd russian-block
3. 直接用浏览器打开index.html文件即可开始游戏。
5.总结
通过上述步骤,即可实现一个简单的俄罗斯方块游戏。在此基础上,可以进一步优化游戏界面和交互,如添加游戏难度、音效等。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:js html5 css俄罗斯方块游戏再现 - Python技术站