1. 程式人生 > >遊戲開發入門之俄羅斯方塊

遊戲開發入門之俄羅斯方塊

程式分析

俄羅斯方塊是由多種型別的方塊與遊戲邊界背景組成,根據面向物件的方法,把整個程式分隔成兩部分--遊戲主體、形狀兩個物件。

其中游戲主體處理的事情包括:

  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