遊戲開發入門之俄羅斯方塊
阿新 • • 發佈:2019-01-22
程式分析
俄羅斯方塊是由多種型別的方塊與遊戲邊界背景組成,根據面向物件的方法,把整個程式分隔成兩部分--遊戲主體、形狀兩個物件。
其中游戲主體處理的事情包括:
- 繪製遊戲介面與邊界。
- 容納方塊與形狀並繪製。
- 控制器:監聽鍵盤事件,並將之轉換成對形狀物件的控制,如變形、左移、右移、下移以及直接落地操作。
- 遊戲規則控制:包括形狀物件的生成,形狀物件每次下落的時間間隔,邊界檢測(形狀物件不能移除遊戲邊界之外)。
形狀物件處理的事情:
- 根據指定的方塊排列座標,生成對應數量的方塊以及各方塊的座標資訊。
- 處理物件移動或變形操作對各方塊座標的影響,並使得遊戲主體可以根據這些座標正確繪製方塊資訊。
確定了程式的分割及功能邏輯劃分下面要就要進行編碼了。
程式程式碼
形狀物件:Shape
上面是形狀物件的定義,可以看到形狀物件定義了turn、move、toRight、toLeft、toUp、toDown以及detectCross等基本方法。其中detectCross方法是用於檢測變型後各個方塊的座標是否還在遊戲邊界內,並返回最大的超出距離資訊,供重置方塊座標。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); } };
方塊編輯完了後就是要寫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