游戏开发入门之俄罗斯方块

程序分析

俄罗斯方块是由多种类型的方块与游戏边界背景组成,根据面向对象的方法,把整个程序分隔成两部分--游戏主体、形状两个对象。

其中游戏主体处理的事情包括:

  1. 绘制游戏界面与边界。
  2. 容纳方块与形状并绘制。
  3. 控制器:监听键盘事件,并将之转换成对形状对象的控制,如变形、左移、右移、下移以及直接落地操作。
  4. 游戏规则控制:包括形状对象的生成,形状对象每次下落的时间间隔,边界检测(形状对象不能移除游戏边界之外)。

形状对象处理的事情:

  1. 根据指定的方块排列坐标,生成对应数量的方块以及各方块的坐标信息。
  2. 处理对象移动或变形操作对各方块坐标的影响,并使得游戏主体可以根据这些坐标正确绘制方块信息。

确定了程序的分割及功能逻辑划分下面要就要进行编码了。

程序代码

形状对象:Shape

var Shape = function(x, y, gc){//形状/方块对象,提供方块初始位置
		this.gc = gc;
		this.x = x;
		this.y = y;
	};
	Shape.prototype = {
			init : function(boxes, centerIndex){//初始化方块内容(提供方块各格子与初始位置的偏移量, 以及变型时中心位置(或传入false等于不可变型))
				var me = this, gc = me.gc;
				me.boxes = boxes.slice();
				this.turnAble = true;
				if(centerIndex === false){
					this.turnAble = false;
				}else{
					this.centerIndex = (typeof centerIndex == 'undefined') ? 1 : centerIndex;
					if(this.centerIndex >= boxes.length){
						this.centerIndex = boxes.length >= 2 ? 1 : 0;
					}
				}
				var x = me.x, y = me.y;
				me.nodes = [];
				for(var i = 0; i < me.boxes.length; i++){
					var node = {x : x + me.boxes[i].x, y : y + me.boxes[i].y, dom : gc.createBox()};
					me.nodes.push(node);
				}
			},
			putIn : function($p){
				for(var i = 0; i < this.nodes.length; i++){
					$p.appendChild(this.nodes[i].dom);
				}
			},
			turn : function(flag){//逆时针旋转, flag = true时顺时针旋转
				var me = this, nodes = me.nodes, gc = me.gc, centerBox = 1;
				if(me.turnAble){
					var cx = nodes[centerBox].x, cy = nodes[centerBox].y;
					for(var i = 0; i < nodes.length; i++){
						var rx = nodes[i].x - cx, ry = nodes[i].y - cy;
						rx = !!flag ? rx : -rx;
						ry = !!flag ? -ry : ry;
						nodes[i].x = cx + ry;
						nodes[i].y = cy + rx;
					}
					var crossInfo = me.detectCross();
					if(crossInfo){
						me.move(-crossInfo.x, -crossInfo.y, true);
					}
				}
			},
			detectCross : function(){//检测是否跃出当前游戏网格(旋转后可能会有此问题), 返回超出范围的最大值,x轴和y轴
				var me = this, nodes = me.nodes, gc = me.gc;
				var crossX = 0, crossY = 0;
				function absMax(a1, a2){return Math.abs(a1) > Math.abs(a2) ? a1 : a2;}
				for(var i = 0; i < nodes.length; i++){
					var node = nodes[i];
					var absX = 0, absY = 0;
					absX = node.x < gc.w && node.x >= 0 ? 0 : node.x % (gc.w - 1);
					absY = node.y >= 0 && node.y < gc.h ? 0 : node.y % (gc.h - 1);
					crossX = absMax(absX, crossX);
					crossY = absMax(absY, crossY);
				}
				return crossX != 0 || crossY != 0 ? {x : crossX, y : crossY} : false;
			},
			moveAble : function(x, y){
				var me = this, nodes = me.nodes, gc = me.gc, maxX = gc.w, maxY = gc.h;
				var flag = true;
				for(var i = 0; i < nodes.length; i++){
					var node = nodes[i], index = i;
					if(nodes[index].x + x >= maxX || nodes[index].y + y >= maxY){
						flag = false;
						break;
					}
					if(nodes[index].x + x < 0 || nodes[index].y + y < 0){
						flag = false;
						break;
					}
				}
				return flag;
			},
			move : function(x, y, force){
				var me = this, nodes = me.nodes, gc = me.gc, maxX = gc.w, maxY = gc.h;
				var flag = !!force || me.moveAble(x, y);
				if(flag){
					var len = nodes.length;
					for(var i = 0; i < nodes.length; i++){
						nodes[i].x += x;
						nodes[i].y += y;
					}
				}
				return flag;
			},
			toRight : function(){
				return this.move(1, 0);
			},
			toLeft : function(){
				return this.move(-1, 0);
			},
			toUp : function(){
				return this.move(0, -1);
			},
			toDown : function(){
				return this.move(0, 1);
			}
	};
上面是形状对象的定义,可以看到形状对象定义了turn、move、toRight、toLeft、toUp、toDown以及detectCross等基本方法。其中detectCross方法是用于检测变型后各个方块的坐标是否还在游戏边界内,并返回最大的超出距离信息,供重置方块坐标。

方块编辑完了后就是要写Game游戏主体对象了。

Game:游戏主体对象

var extend = function(source){
		var argLen = arguments.length;
		for(var i = 1; i < argLen; i++){
			var arg = arguments[i];
			for(var i in arg){
				source[i] = arg[i];
			}
		}
	};
	var Game = function(w, h, frame, score){//游戏世界对象
		var me = this;
		me.w = w;
		me.h = h;
		me.holder = new Array(w * h);
		me.container = me.$c = frame;
		me.score = score;
		me.scoreCount = 0;
		var scoreCount = 0;
		me.getScoreCount = function(){
			return scoreCount;
		};
		me.setScoreCount = function(record){
			scoreCount = record;
		};
		var box = me.createBox();
		me.container.appendChild(box);
		me.boxWidth = box.clientWidth + 2;//2为边框宽度
		me.boxHeight = box.clientHeight + 2;//2为边框宽度
		var bw = me.boxWidth, bh = me.boxHeight;
		me.container.removeChild(box);
		extend(me.container.style, {width : w * bw + 'px', height : h * bh + 'px', position : 'relative', display : 'block'});//设置容器高度
		me.eventListener = me.createEveltListener();
		var level = 1;
		me.setLevel = function(level2){
			if(level2){
				me.speed = 1000 / level2;
				level = level2;
			}
		};
		me.getLevel = function(){
			return level;
		};
		me.setLevel(level);
	};
	
	Game.prototype = {
		restore : function(){
			var me = this, w = me.w, h = me.h;
			me.pause();
			me.holder = new Array(w * h);
			me.curShape = false;
			me.speed = 1000;
			me.scoreCount = 0;
			var children = me.container.children;
			for(var i = 0; i < children.length; i++){
				me.container.removeChild(children[i]);
			}
			
		},
		createBox : function(){//创建格子元素
			var box = doc.createElement('div');
			box.className = 'box bg-yellow';
			//box.setAttribute('className', 'box bg-yellow');
			//box.setAttribute('class', 'box bg-yellow');
			return box;
		},
		drawShape : function(shape){//绘制方块的位置
			var me = this, bw = me.boxWidth, bh = me.boxHeight;
			for(var i = 0, nodes = shape.nodes; i < nodes.length; i++){
				extend(nodes[i].dom.style, {left : nodes[i].x * bw + 'px', top : nodes[i].y * bh + 'px'});
			}
		},
		refreshDisplay : function(){//绘制/刷新已有方块的位置
			var me = this, holder = me.holder, bw = me.boxWidth, bh = me.boxHeight;
			var len = holder.length;
			var w = me.w, h = me.h;
			var index = 0;
			for(var row = 0; row < h; row++){//此处不使用除法运算,由于除法运算精度会导致行数计算错误.
				for(var col = 0; col < w; col++){
					var $dom = holder[index];
					if($dom && $dom.nodeType){
						extend($dom.style, {left : col * bw + 'px', top : row * bh + 'px'});
					}
					index++;
				}
			}
		},
		detectShapeImpact : function(shape){//检测方块是否与世界冲突(碰撞检测)
			var me = this, holder = me.holder,
			w = me.w, h = me.h, nodes = shape.nodes;
			var flag = false;
			for(var index = 0; index < shape.nodes.length; index++){
				var node = shape.nodes[index];
				var nx = node.x, ny = node.y;
				var i = ny * w + nx;
				if(holder[i] && holder[i].nodeType){//与现有方块冲突
					flag = true;
					break;
				}
			}
			return flag;
		},
		boxesSupport : [
		    {boxes : [{x:0,y:0},{x:0,y:1},{x:0,y:2},{x:0,y:3}], centerIndex : 1},
		    {boxes : [{x:0,y:0},{x:0,y:1},{x:1,y:1},{x:1,y:2}], centerIndex : 1},
		    {boxes : [{x:0,y:0},{x:0,y:1},{x:-1,y:1},{x:-1,y:2}], centerIndex : 1},
		    {boxes : [{x:0,y:0},{x:0,y:1},{x:0,y:2},{x:1,y:2}], centerIndex : 2},
		    {boxes : [{x:0,y:0},{x:1,y:0},{x:1,y:1},{x:1,y:2}], centerIndex : 1},
		    {boxes : [{x:0,y:0},{x:0,y:1},{x:0,y:2},{x:1,y:1}], centerIndex : 2},
		    {boxes : [{x:0,y:0},{x:1,y:0},{x:0,y:1},{x:1,y:1}], centerIndex : false},
		    {boxes : [{x:0,y:0}], centerIndex : false}
		],
		randomShape : function(){
			var me = this, len = me.boxesSupport.length, x = Math.round(me.w / 2), y = 0,
			rand = Math.floor(Math.random() * len);
			var shape = new Shape(x, y, me);
			shape.init(me.boxesSupport[rand].boxes, me.boxesSupport[rand].centerIndex);
			shape.putIn(me.container);
			return shape;
		},
		createEveltListener : function(){
			var me = this;
			return function(event){
				var shape = me.curShape;
				if(!shape){
					return;
				}
				var mi = false;//MoveInfo移动信息
				var isTurn = false;
				var ev = event || win.event, key = ev.keyCode || ev.which || ev.charCode;
				switch(key){
				case 37 :
					mi = {x:-1, y:0};
					break;
				case 39 :
					mi = {x:1, y:0};
					break;
				case 40 :
					mi = {x:0, y:1};
					break;
				case 38 :
					//shape.toUp();
					//break;
				case 32 :
					shape.turn();
					isTurn = true;
					break;
				default :
					isTurn = false;
					mi = false;
					//console.log('Unknow operate with code ' + event.keyCode);
				}
				if(mi){//移动操作
					if(shape.moveAble(mi.x, mi.y)){
						shape.move(mi.x, mi.y);
						var flag = me.detectShapeImpact(shape);//检测碰撞信息
						if(flag){
							shape.move(-mi.x, -mi.y);//还原用于检测碰撞的位置信息.
							if(mi.y > 0){//有碰撞,且为向下移动
								me.mergeShape(shape);
								return;
							}
						}
					}else if(mi.y > 0){//向下,写不能移动
						me.mergeShape(shape);
						return;
					}
				}
				if(isTurn){//旋转操作(变型操作)
					var flag = me.detectShapeImpact(shape);//检测碰撞信息
					if(flag){
						shape.turn(true);//旋转回去
					}
				}
				me.drawShape(shape);
			};
		},
		eventListener : function(event){
		},
		mergeShape : function(shape){
			var me = this, holder = me.holder,
			w = me.w, h = me.h, nodes = shape.nodes;
			var flag = false;
			for(var index = 0; index < shape.nodes.length; index++){
				var node = shape.nodes[index];
				var nx = node.x, ny = node.y;
				var i = ny * w + nx;
				holder[i] = node.dom;
			}
			me.clearLine();
			me.refreshDisplay();
			me.curShape = me.randomShape();
			var flag = me.detectShapeImpact(me.curShape);//检测碰撞信息
			me.drawShape(me.curShape);
			if(flag){
				me.pause();
				alert('游戏结束!');
			}
		},
		clearLine : function(){
			//清除满行的方块,并移动其上层下来
			var me = this, holder = me.holder,
				w = me.w, h = me.h, len = holder.length;
			var clearLineCount = 0;
			for(var i = 0; i < h; i++){
				var lineFullFlag = true;
				for(var j = 0; j < w; j++){
					if(!holder[i * w + j]){
						lineFullFlag = false;
						break;
					}
				}
				if(lineFullFlag){//当前行被填满
					clearLineCount++;
					for(var col = 0; col < w; col++){//删除元素
						me.container.removeChild(holder[i * w + col]);
					}
					var oldHolder = holder, tmpHolder;
					tmpHolder = holder.slice(0, i * w);//复制前段
					tmpHolder = tmpHolder.concat(holder.slice((i+1) * w));//复制后段
					tmpHolder = new Array(w).concat(tmpHolder);//填充全段
					holder = tmpHolder;//变量交互
					me.holder = holder;//维护全局变量
				}
			}
			//console.log('Clear line (' + clearLineCount + ');');
			if(clearLineCount > 0){
				me.scoreCount += Math.pow(2, clearLineCount);
				if(me.score){
					me.score.innerHTML = me.scoreCount;
				}
			}
		},
		start : function(){
			var me = this;
			if(!me.started){
				me.started = true;
				if(!me.curShape){
					me.curShape = me.randomShape();
					me.drawShape(me.curShape);
				}
				//document.addEventListener('keydown', me.eventListener);
				document.onkeydown = me.eventListener;
				var roundFunc = function(){
					//下移方块
					me.eventListener({keyCode:40});
				};
				me.runInterval = setInterval(roundFunc, me.speed);
			}			
		},
		pause : function(){
			var me = this;
			me.started = false;
			win.clearInterval(me.runInterval);
			document.onkeydown = null;
			//document.removeEventListener('keydown', me.eventListener);
		}
	};

HTML代码以及运行代码

<html>
<head>
<meta charset="UTF-8" content="text/html;charset=utf-8">
<title>俄罗斯方块</title>
<style type="text/css" rel="stylesheet">
.box{width:18px; height:18px; border:1px solid lightgray; display: block; position: absolute;}
.bg-red{background-color: red;}
.bg-blue{background-color: blue;}
.bg-yellow{background-color: yellow;}
.bg-gray{background-color: gray;}
.frame{border: 1px solid; position: relative; display: block; background-color: lightblue;}
.score{border: 1px solid; display: block; background-color: lightgray; width:200px; height:36px; font-size: 18px; line-height: 36px; margin-top:5px;}
.wrap{display: block;}
.hide{display:none;}
.hide2{visibility: hidden;}
</style>
<script type="text/javascript" src="js/tetris.js"></script>
</head>
<body>
<div class="wrap">
	<div class="frame" id="frame">
		
	</div>
	<div class="score" id="score"></div>
</div>
<script type="text/javascript">
(function(win, doc){
	
	g = new Game(15, 20, document.getElementById('frame'), document.getElementById('score'));
	g.start();
})(window, document);
</script>
</body>
</html>

程序源代码下载


程序源代码下载地址: http://download.csdn.net/detail/zyb134506/6928983

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值