ホームページ > 記事 > ウェブフロントエンド > HTML5 テトリスを実際に作成(画像とテキスト)
本文が始まる前に言っておきたいのですが、タイトルにあるいわゆる自己満足とは、このゲームがデザインのアイデアを一切参考にせずに開発されたことを意味します。もちろん、参照するかしないかは別として、苦労して何十ものバグ修正を行った結果、最終的に完成した場合の利点について話しています。私は他の人に誇らしげにこう言うことができます。「ほら、私がこのゲームを開発したのよ!」また、振り返ってみると、他の人のアイデアを振り返ると、「なぜ今まで思いつかなかったのか」、「この問題はこうすれば解決するのか」、「このデザインは」と思うことがあります。 「アイデアは私のアイデアよりもはるかに優れています!」、最初から他の人のアイデアを直接見て自分の思考をブロックするよりははるかに良いですよね?
それでは、テキストを始めましょう~
最初に効果を確認したい場合は、ジャンプしてみてください!
テトリス、メインのゲーム インターフェイスは、以下に示すように 1 つずつブロックで構成されている必要があります。もちろん、これらのグリッドは完成品には表示されません。これは、メイン インターフェイスのサイズが 400×500 であることを示しています。 . 各ブロックを設定する レンガ(グリッド)のサイズは 20×20 なので、各行に 20 個、各列に 25 個のレンガが配置されます。 関連コード:
brickWidth = 20, //砖块大小 width = 400, height = 500; //画布宽高,20X25
メインインターフェイスのグリッドに関しては、非常に重要な変数について言及する必要があります。それは、視覚的に言えば、二次元配列です。は 20×26、保存されている値は 0 または 1 です。0 はこの位置にレンガがないことを意味し、1 はこの位置にレンガがあることを意味します。これは、その後のゲームについてのいくつかの決定において重要な役割を果たします。メインインターフェイスグリッドに対応する20×25ではなく、20×26である理由がわかるかもしれませんが、後で行と値を追加すると気づきました。この行はすべて 1 です。メイン インターフェイスの一番下まで到達しましたか? 関連コード:
// 初始化BOARD,注意纵向有26个,最后一排用来判断是否触底 for(i=0;i<20;i++){ BOARD[i] = []; for(j=0;j<26;j++) { if(j==25) { BOARD[i][j] = 1 } else { BOARD[i][j] = 0; } } }
次に、4つのレンガで構成される「形」を見てください。 説明のために、それらに名前を付けました。 ( Hoe)、Tu (膨らみ)、Thunder (稲妻)、Line (一本の水平線)、笑 興味深い名前ですが、英語の名前が見つからないことをお許しください。
最初にブリッククラス Brick を定義します:
function Brick() { }
その下にいくつかのプロトタイプ変数とメソッドがあります:
Brick.prototype.embattle = null; //砖块的布局(需重载) Brick.prototype.isOverturn = 0; //是否翻转 Brick.prototype.originX = 9; //砖头的绘制起点X Brick.prototype.originY = -3; //砖头的绘制起点Y Brick.prototype.direction = 0; //砖头朝向 Brick.prototype.autoMoveTimer = null; //自动移动计时器 Brick.prototype.draw = function() { …… } //画砖块的方法 Brick.prototype.move = function(moveX, moveY) { …… } //移动的方法 Brick.prototype.autoMove = function() { …… } //自动移动的方法 Brick.prototype.change = function() { …… } //变换砖头朝向
Brick のサブクラスは次のとおりです: Tian、Chu、Tu、Thunder、Line、それぞれがオーバーロードされています Brick の攻防変数、エンバトルとは英語で言うとエンバトルという意味ですが、この配列は何でしょうか?まず最初に、生徒たちは私の考えを理解する必要があります。Tu のエンバトルを例に挙げます。コードは次のとおりです。
this.embattle = [ [ [0,4,5,8], [1,4,5,6], [1,4,5,9], [0,1,2,5] ], //布局表为4X4表格,数字为砖头位置 [ [0,4,5,8], [1,4,5,6], [1,4,5,9], [0,1,2,5] ] //次行为翻转的情况];
embattle は 3 次元配列です (視覚的に言うと、それは次のようなものです)。 画像の水平反転)、2番目の次元は方向(上、左、下、右)、3番目の次元は形状の4つのレンガの分布です。それぞれの新しい形状オブジェクトを定義します。 4×4 配列、たとえば、Tu のこれは [0,4,5,8] で、以下に示すように、数字はレンガの位置です。したがって、形状の位置と外観を決定するには、それを反転するかどうかを決定する isOverturn が必要で、その方向を決定するには direct が必要で、「配列」の位置を決定するにはoriginX とoriginY が必要です。
次に、Brickの4つのプロトタイプメソッドをそれぞれ説明します。
Brick.prototype.draw
ctx.fillStyle = 'rgb('+Math.floor(Math.random()*256)+','+Math.floor(Math.random()*256)+', '+Math.floor(Math.random()*256)+')'; for(i=0;i<4;i++) { tmp = this.embattle[this.isOverturn][this.direction][i]; ctx.fillRect((this.originX+tmp%4)*brickWidth, (this.originY+Math.floor(tmp/4))*brickWidth, brickWidth, brickWidth); ctx.strokeRect((this.originX+tmp%4)*brickWidth+1, (this.originY+Math.floor(tmp/4))*brickWidth+1, brickWidth-2, brickWidth-2); //注意+1和减2 }
有上面说的确定形状的位置和样子的方法,之后就是纯粹canvas画图,4个砖块一个一个地画,不要看代码很长其实就是那么一点点,originX、originY和砖块在阵中的位置就可以确定画砖块的起点了。注意到代码的注释了没有,画边框的时候,它是从起点向外面画的,就像我把一个塑料袋套在另一个塑料袋的外面,为了以后的清除的方便且不影响其他的砖块,把边框画进fillRect的领土,就像我现在把这个塑料袋不套在外面而是放进这另一个塑料袋里面一样,就这个意思。
Brick.prototype.move
这是最长的一个了,移动的时候,moveX和moveY表示横纵的增量,没有同时非0的情况(这是人为的设定,要么横向移动要么纵向移动),当然要判断即将移动到的位置是否违规:
横向:
如果阵贴靠主界面左侧则不能向左移即moveX不能为-1
(this.originX==0 && moveX==-1)
判断右边时比较麻烦,因为不能直接用阵来判断是否贴靠右侧(看前面的图就知道阵的右边和下边可能没有砖块的),这时要一个个地判断4个砖块是否有至少有一个在最右,这时不能向右移动
|| (this.originX+tmp[0]%4==19 && moveX==1) || (this.originX+tmp[1]%4==19 && moveX==1) || (this.originX+tmp[2]%4==19 && moveX==1) || (this.originX+tmp[3]%4==19 && moveX==1)
最后还要判断即将到达的位置是否已经有砖块了。
|| (BOARD[this.originX+tmp[0]%4+moveX][this.originY+Math.floor(tmp[0]/4)]==1) || (BOARD[this.originX+tmp[1]%4+moveX][this.originY+Math.floor(tmp[1]/4)]==1) || (BOARD[this.originX+tmp[2]%4+moveX][this.originY+Math.floor(tmp[2]/4)]==1) || (BOARD[this.originX+tmp[3]%4+moveX][this.originY+Math.floor(tmp[3]/4)]==1)
纵向:
即将到达的位置是否已经有砖块了,注意到下面的代码的&& moveX==0,原来是没有的,后来发现每次砖块怎么刚刚靠上下面堆着的砖块就不能再移动了,原来横向移动的时候也进行了这个判断,即刚刚靠上下面的砖块,如果这时想左右移动,但下方有砖块,但是问题来了,下面有没有砖块跟我左右移动有什么关系呢?是吧。
if((as==1 || bs==1 || cs==1 || ds==1) && moveX==0) { …… }
纵向终止判断里面主要做了几件事:清除autoMoveTimer,设置BOARD在该形状当前位置的值为1,有可以消除的整行就消除,加分改分,判断胜利/失败,删除当前对象,召唤下一个形状。
横纵都没违规时:
这时,把该形状前一个位置的砖块清除,更新originX和originY,再画出来。
for(i=0;i<4;i++) { tmp = this.embattle[this.isOverturn][this.direction][i]; ctx.clearRect((this.originX+tmp%4)*brickWidth, (this.originY+Math.floor(tmp/4))*brickWidth, brickWidth, brickWidth); } this.originX += moveX; this.originY += moveY; this.draw();
Brick.prototype.autoMove
只做一件事,设置计时器,定时向下移动。
var status, self = this;this.autoMoveTimer = setInterval(function() { status = self.move(0,1); },speed);
Brick.prototype.change
改变形状的朝向,很好办啊,不是有embattle数组了吗?当然没有那么简单,不只是换个数组这么简单。要考虑改变方向之后占用的位置是否已经有砖块了,如果形状是贴着主界面右边界就更糟糕了,比如原来是竖着的Line,改变其方向变为横,占用阵的0、1、2、3,如果Line贴着右边界,originX为19,变为横向,占用阵的0、1、2、3,后面三个砖块已经溢出了主界面。
解决方案是:如果有越界的砖块就把阵往左挪一挪,直到不再越界。
while(ox+tmp[0]%4 > 19 || ox+tmp[1]%4 > 19 || ox+tmp[2]%4 > 19 || ox+tmp[3]%4 > 19) { ox -= 1; }
最后,如果都没事,就可以清除原位置,画出改变方向之后的形状了。
并不是太完美,因为有些卡位的情况没考虑进来,什么是卡位,看下图,你知道Line实例调用change方法的结果是什么了吗?事实上,它不应该成功改变方向的,对吧?还有其他一些卡位的情况。
Brickの4つのプロトタイプメソッドをここで紹介します。ここで、右側の情報インターフェースに次の図形を表示したい場合、最も直接的な方法は、図形の コンストラクター を介してオブジェクトをインスタンス化することです。autoMove が自動的に呼び出されないようにするために、コンストラクターに isModel を追加しました。それがプロンプト目的ではないかどうかを判断します。
重要なeventlistening、deleteObjもあります。ゲームの入り口はNextBrick関数です。
また、deleteObj によって実際に GC がオブジェクトをリサイクルできるかどうかもわかりません。
あと、元々レベル機能も付けたかったのですが、速度を自由に設定できる(速度可変)ので、この機能は脇に置きました。
以上がHTML5 テトリスを実際に作成(画像とテキスト)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。