lufylegend引擎俄羅斯方塊的實現
- 旋轉座標的實現
俄羅斯方塊的實現第一步就是旋轉座標的獲得,下面將會詳細寫出推導過程,首先必須要分配好方塊的構成,這裏需要用4個精靈來形成基本的block,如長方形,L形等,之後就是選擇原點座標和各點的相對座標,這裏先把選轉的座標公式推導一下,如下圖:
旋轉前的座標爲x,y,旋轉前的角度爲ζ度,旋轉後的座標爲x1,y1,旋轉後的角度爲(ζ+β)度,因爲旋轉後,該點到原點的距離並不會發生改變,所以我們需要先用已只條件得到該點到原點的距離h=y/sinζ=x/cosζ
那麼對應的旋轉後距離就是h1=y1/sin(ζ+β)=x1/cos(ζ+β);因爲h1=h,所以可以推導出y1/sin(ζ+β)=x1/cos(ζ+β)=y/sinζ=x/cosζ;
接下來要用到和角公式
sin(a+b)=sinacosb+sinbcosa
cos(a+b)=cosacosb-sinasinb
y1=y*sin(ζ+β)/sinζ=y(sinζcosβ+sinβcosζ)/sinζ=ycosβ+ycosζ/sinζ*sinβ;
又因爲y/x=sinζ/cosζ,所以x=ycosζ/sinζ,所以y1=ycosβ+xsinβ
以此類推x1=xcos(ζ+β)/cosζ=x(cosζcosβ-sinζsinβ)/cosζ=xcosβ-xsinζ/cosζ*sinβ=xcosβ-ysinβ,即x1=xcosβ-ysinβ
這樣第一步旋轉公式的推導就ok了,接下來是應用了,這裏選擇將第三個方塊的起始x,y作爲座標原點,如下圖以L形方塊爲例:
可以看到第一個爲初始L形,此時第三個方塊的座標看做0,0,那麼剩餘方塊座標爲1號方塊(-1,1),2號方塊爲(-1,0),4號方塊爲(1,0),則根據之前的旋轉公式可以得到,x1=xcosβ-ysinβ,y1=ycosβ+xsinβ,此中p爲90度,則實際x1=-y,y1=x,所以旋轉後坐標1號方塊爲(-1,-1),2號方塊爲(0,-1),4號方塊爲(0,1),以此類推可以求出第三次旋轉,第四次旋轉的座標。
所以實現的代碼爲
javascript
for(var i=0;i<4;i++){
//求得相對座標
var x=sprite[i].x-sprite[2].x;
var y=-(sprite[i].y-sprite[2].y);
//求出相對座標後在換算成實際座標
sprite[i].x=-y+sprite[2].x;
sprite[i].y=-x+sprite[2].y;
}
因爲實際lufylegend的引擎座標系是以左上角爲原點的,所以這裏做了一下換算,將座標系反過來了
這樣旋轉後的方塊就可以很方便的畫出來了
- 方塊的最下方的判斷
對於俄羅斯方塊來說都有着最低層,並且之後檢測碰撞的方便都會用到方塊的最低點的座標,所以接下來的工作就是實現這個目的,以L形爲例子,初始的最低點爲,第一個方塊的Y座標就是方塊的最低點,所以在初始化方塊的時候可以事先將maxY賦值成1,之後在方塊的下落過程中,方塊的座標是在不斷變動的,以及方塊旋轉後其最低點的座標也是會發生變化的。這裏需要考慮到這兩個條件,所以接下來就要修改上面的旋轉函數和方塊的下落函數,並在其中求最大的Y座標:
javascript
//旋轉函數
for(var i=0;i<4;i++){
var x=sprite[i].x-sprite[2].x;
var y=-(sprite[i].y-sprite[2].y);
sprite[i].x=-y+sprite[2].x;
sprite[i].y=-x+sprite[2].y;
//求最大X的座標
if(sprite[maxX].x<sprite[i].x)
maxX=i;
//求最大Y的座標
if(sprite[maxY].y<sprite[i].y)
maxY=i;
}
javascript
if(s.childList[maxY].y<290){
for(var i=0;i<s.childList.length;i++){
s.childList[i].y=s.childList[i].y+10;
//更新最大的Y座標
if(s.childList[maxY].y<=s.childList[i].y){
maxY=i;
}
}
}else{
s.activemode=false;
}
這樣方塊的最下方的判斷就完成了
3.方塊的生成
俄羅斯方塊中是由方塊的掉落來進行遊戲的,所以需要對方塊進行生成,如果是寫定的方塊的話容易產生一種厭煩感,所以這裏需要對方塊進行隨機生成,首先需要確定是第一個方塊的位置,這個很容易,因爲Y座標必定是0,所以只需要對x進行隨機就行了
var x=Math.round(Math.random()*13)*10;
var y=0;
這樣就可以生成一個0,130的隨機數,需要注意一下math.random()是產生0到1的隨機數,如果不用math.round的話,產生的x值並不是10的整數倍,所以需要在這裏先用math.round得到一個整數,之後再乘以10,獲得就是0,10,20,30.。。130的隨機數
而對於下一個方塊來說,座標取值只有4種,[-10,0],[10,0],[0,10],[0,-10],也就是上下左右4個領接位置,但是當方塊座標爲0,或者130時爲了使方塊不躍出邊界,就只能選擇3方向的方塊,0:[10,0],[0,10],[0,-10],130:[-10,0],[0,10],[0,-10],這樣就確定好了3種情況所能隨機的方塊位置了,還需要注意一下的就是之前的方塊位置,避免兩個方塊差生在同一個位置,所以源代碼如下:
LBlock.prototype.οnshοw=function(){
var s=this;
var x=Math.round(Math.random()*13)*10;
//var x=200;
var y=0;
var i;
var a=new Array();
var save=new Array();
a[0]=[x,y];
save=[[x,y],[-1,-1],[-1,-1],[-1,-1]];
for(i=1;i<4;i++){
if(a[i-1][0]==130){
var b=[[-10,0],[0,10],[0,-10]]
}else if(a[i-1][0]==10){
var b=[[10,0],[0,10],[0,-10]]
}else{
var b=[[-10,0],[10,0],[0,10],[0,-10]];
}
var select=Math.round(Math.random()*(b.length-1));
var m=a[i-1][0]+b[select][0];
var n=a[i-1][1]+b[select][1];
if(find([m,n],save)){
a[i]=[m,n]
save[i]=[m,n]
}else{
b.splice(select,1);
select=Math.round(Math.random()*(b.length-1));
m=a[i-1][0]+b[select][0];
n=a[i-1][1]+b[select][1];
a[i]=[m,n]
}
}
var sprite=[];
for(i=0;i<4;i++){
sprite[i]=new LSprite();
sprite[i].x=a[i][0];
sprite[i].y=a[i][1];
sprite[i].graphics.drawRect(2,"blue",[0,0,10,10],true,"green");
s.addChild(sprite[i]);
}
}
這樣就可以隨機生成不同座標的不一樣形狀的方塊了
4.方塊的碰撞檢測
俄羅斯方塊遊戲中的重點,方塊與方塊,方塊與邊界的碰撞,在整個遊戲中需要注意以下這幾種情況1.旋轉時的碰撞檢測,如果旋轉後的座標上有方塊或邊界就不能進行旋轉 2.左右移動時的碰撞檢測,3.向下移動時的碰撞檢測
首先需要定義一個用來存放方塊的容器,這裏採用高200,寬120的大小容器,定義爲一個20,14的數組
var mapData=[[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0]
]
初始值全爲0,當方塊停止後,就將對應位置的數組值置爲1
1.向下移動時的碰撞檢測
因爲方塊需要一起進行移動,所以要優先判斷所有方塊是否由碰撞,如有就不移動方塊,否則會出現一部分方塊停止後,另一部分依然正常運動的情況,所以這裏採用先對所有方塊的碰撞進行判斷,之後再移動方塊
LBlock.prototype.onframe=function(){
var s=this;
var flag=true;
if(s.childList[maxY].y<190){
for(var i=0;i
flag=true;
for(var i=0;i<4;i++){
var x=sprite[i].x-10;
var y=sprite[i].y;
var m=190-y;
var n=x;
if(y>=0){
if((mapData[Math.floor(m/10)][Math.floor(n/10)]==1)||(n>130)||(n<0)||(m>190)){
flag=false;
}
}
}
if(flag){
for(var i=0;i<4;i++){
sprite[i].x=sprite[i].x-10;
if(sprite[maxX].x<sprite[i].x)
maxX=i;
if(sprite[maxY].y<sprite[i].y)
maxY=i;
}
}
3.旋轉判定
if(button==65){
//以sprite2爲座標原點
flag=true;
for(var i=0;i<4;i++){
var x=sprite[i].x-sprite[2].x;
var y=-(sprite[i].y-sprite[2].y);
var m=-y+sprite[2].x;
var n=190-(-x+sprite[2].y);
if((mapData[Math.floor(n/10)][Math.floor(m/10)]==1)||(m>130)||(m<0)||(n>190)){
flag=false;
}
}
if(flag){
for(var i=0;i<4;i++){
var x=sprite[i].x-sprite[2].x;
var y=-(sprite[i].y-sprite[2].y);
sprite[i].x=-y+sprite[2].x;
sprite[i].y=-x+sprite[2].y;
if(sprite[maxX].x<sprite[i].x)
maxX=i;
if(sprite[maxY].y<sprite[i].y)
maxY=i;
}
}
5.消行
上述工作完成後,對於俄羅斯方塊而言,僅缺少的就是消行,因爲之前已經定義了mapData這個容器數組,所以只需要在onframe函數中,檢測mapData中一橫行的數值是否全爲,如果全爲1既消去整一行,並刪掉改行的方塊,爲了實現這個,首先需要對s.result這個在方塊停止的函數進行改動,因爲對於blocklayer來講下屬的精靈類非常不好進行查詢,所以這裏在停止時會刪掉這個部分,並將這部分的值賦給一個事先定義好的mapsprite數組,這樣定義的話,數組就是有序的,方便進行刪除,下面就是s.result函數的修改後的代碼,在將座標獲得後直接拿掉改對象,改用mapsprite進行繪製
LBlock.prototype.result=function(){
var s=this;
var b=[];
for(var i=0;i<s.childList.length;i++){
var x=s.childList[i].x;
var y=190-s.childList[i].y;
//求得數組座標
var m=Math.round(x/10);
var n=Math.round(y/10);
mapData[n][m]=1;
mapsprite[n][m]=s.childList[i];
mapsprite[n][m].graphics.drawRect(2,"blue",[0,0,10,10],true,"green");
//mapLayer.addChild(mapsprite[n][m]);
b[i]=[n,m];
}
for(var i=0;i<b.length;i++){
mapLayer.addChild(mapsprite[b[i][0]][b[i][1]]);
}
s.remove();
}
這一步完成後遊戲中就可以獲得行對應行,列對應列的有序數組了,這樣刪除起來就方便多了,接下來是刪除函數,對於俄羅斯方塊的消行來說,需要完成兩個步驟,第一個步驟就是將mapData後一行的值賦給前一行,並將最後一行的所有值置爲0,第二個步驟就是將mapsprite繪製的方塊全部下移一行,這兩個步驟完成後,消行也就完成了,所以代碼需要這樣寫:
function canclemap(n){
for(var i=0;i<mapsprite[n].length;i++){
mapsprite[n][i].remove();
}
for(var i=n;i<mapData.length;i++){
if(i<mapData.length-1){
mapData[i]=mapData[i+1];
for(var j=0;j<mapsprite[n].length;j++){
mapsprite[i+1][j].y=mapsprite[i+1][j].y+10;
mapsprite[i]=mapsprite[i+1];
}
}else{
mapData[i]=[0,0,0,0,0,0,0,0,0,0,0,0,0,0];
for(var j=0;j<mapsprite[n].length;j++){
mapsprite[i][j].remove();
}
}
}
}
源代碼:
index.php
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>LTetris</title>
<script type="text/javascript" src="lufylegend.js-lufylegend-1.9.7/lufylegend-1.9.7.min.js"></script>
<script src="jquery/jquery-1.8.3.js"></script>
<script src="tetrisblock.js"></script>
<script>
LInit(30,"legend",450,320,gameInit);
LGlobal.setDebug(true);
var backLayer,loadingLayer,blockLayer,mapLayer;
var block;//方塊
var point;//分數
var next;
var nextLayer,nextblockLayer;//下一個方塊
//整個方框的大小,長20,寬14,即長200,寬140
var mapData=[[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0]
]
var mapsprite=new Array();
var totalnum=1;//同時只有一個方塊
/*var loadData = [
{name:"cr",path:"./images/charc.jpg"},
{name:"map",path:"./images/map.jpg"},
{name:"money",path:"./images/bird.png"},
{name:"photo1",path:"./images/66RPG_002_f.png"},
{name:"photo2",path:"./images/66RPG_005_f.png"}
];
function main(){
//加入進度條
loadingLayer = new LoadingSample3();
addChild(loadingLayer);
//加載圖片並顯示進度
LLoadManage.load(
loadData,
function(progress){
loadingLayer.setProgress(progress);
},
gameInit
);
} */
function gameInit(){
initmap();
backLayer = new LSprite();
backLayer.graphics.drawRect(2,"green",[0, 0, 140,200],true,"white");
addChild(backLayer);
blockLayer=new LSprite();
backLayer.addChild(blockLayer);
mapLayer=new LSprite();
backLayer.addChild(mapLayer);
//添加分數區
//添加下一個方塊的提示區
//block=new LBlock();
//blockLayer.addChild(block);
//加入動畫和按鍵事件監聽
backLayer.addEventListener(LEvent.ENTER_FRAME,onframe);
LEvent.addEventListener(LGlobal.window, LKeyboardEvent.KEY_DOWN,move);
LEvent.addEventListener(LGlobal.window, LKeyboardEvent.KEY_UP,stop);
}
function addblock(){
if(totalnum==1){
block=new LBlock();
blockLayer.addChild(block);
totalnum--;
}
}
function onframe(){
addblock();
if(block.mode!="die"){
block.onframe();
}else{
totalnum++;
}
for(var i=0;i<mapData.length;i++){
if(mapData[i].toString()==[1,1,1,1,1,1,1,1,1,1,1,1,1,1].toString()){
canclemap(i);
}
}
checkEnd();
}
function initmap(){
for(var i=0;i<20;i++){
mapsprite[i]=new Array();
for(var j=0;j<14;j++){
mapsprite[i][j]=new LSprite();
}
}
}
function move(e){
block.onbutton(e.keyCode);
}
function canclemap(n){
for(var i=0;i<mapsprite[n].length;i++){
mapsprite[n][i].remove();
}
for(var i=n;i<mapData.length;i++){
if(i<mapData.length-1){
mapData[i]=mapData[i+1];
for(var j=0;j<mapsprite[n].length;j++){
mapsprite[i+1][j].y=mapsprite[i+1][j].y+10;
mapsprite[i]=mapsprite[i+1];
}
}else{
mapData[i]=[0,0,0,0,0,0,0,0,0,0,0,0,0,0];
for(var j=0;j<mapsprite[n].length;j++){
mapsprite[i][j].remove();
}
}
}
}
function checkEnd(){
for(var j=0;j<14;j++){
if(mapData[19][j]==1){
alert("game over");
}
}
}
</script>
</head>
<body>
<div id="legend"></div>
</body>
</html>
tetrisblock.js
var maxX=0;
var maxY=0;
var speed=1;//初始爲5
function LBlock(){
var s=this;
base(s,LSprite,[]);
s.i=Math.round(Math.random()*6+1);
//s.i=7;
//1爲豎形,2爲L形,3位反L形,4爲正方形,5爲樓梯形,6爲反樓梯形,7爲T形
s.status=1;//1爲默認,2爲
s.activemode=true;//true可操作,false不可
s.mode="live";
s.onshow();
}
LBlock.prototype.setspeed=function(speedc){
speed=speedc;
}
LBlock.prototype.getvalue=function (){
var s=this;
return s;
}
LBlock.prototype.onframe=function(){
var s=this;
var flag=true;
if(s.childList[maxY].y<190){
for(var i=0;i<s.childList.length;i++){
if(s.childList[i].y>=0){
var m=Math.floor((190-(s.childList[i].y+speed))/10);
var n=Math.floor(s.childList[i].x/10);
if(mapData[m][n]==1){
s.activemode=false;
s.mode="die";
s.result();
flag=false;
}
}
}
if(flag){
for(var i=0;i<s.childList.length;i++){
s.childList[i].y=s.childList[i].y+speed;
if(s.childList[maxY].y<=s.childList[i].y){
maxY=i;
}
}
}
}else{
s.activemode=false;
s.mode="die";
s.result();
}
}
LBlock.prototype.result=function(){
var s=this;
var b=[];
for(var i=0;i<s.childList.length;i++){
var x=s.childList[i].x;
var y=190-s.childList[i].y;
//求得數組座標
var m=Math.round(x/10);
var n=Math.round(y/10);
mapData[n][m]=1;
mapsprite[n][m]=s.childList[i];
mapsprite[n][m].graphics.drawRect(2,"blue",[0,0,10,10],true,"green");
//mapLayer.addChild(mapsprite[n][m]);
b[i]=[n,m];
}
for(var i=0;i<b.length;i++){
mapLayer.addChild(mapsprite[b[i][0]][b[i][1]]);
}
s.remove();
}
LBlock.prototype.onbutton=function(button){
var s=this;
var flag=true;
if(s.activemode){
var sprite=[];
for(i=0;i<4;i++){
sprite[i]=new LSprite();
sprite[i]=s.childList[i];
}
//旋轉
if(button==65){
//以sprite2爲座標原點
flag=true;
for(var i=0;i<4;i++){
var x=sprite[i].x-sprite[2].x;
var y=-(sprite[i].y-sprite[2].y);
var m=-y+sprite[2].x;
var n=190-(-x+sprite[2].y);
if((mapData[Math.floor(n/10)][Math.floor(m/10)]==1)||(m>130)||(m<0)||(n>190)){
flag=false;
}
}
if(flag){
for(var i=0;i<4;i++){
var x=sprite[i].x-sprite[2].x;
var y=-(sprite[i].y-sprite[2].y);
sprite[i].x=-y+sprite[2].x;
sprite[i].y=-x+sprite[2].y;
if(sprite[maxX].x<sprite[i].x)
maxX=i;
if(sprite[maxY].y<sprite[i].y)
maxY=i;
}
}
}else if(button==37){
//左移右移
flag=true;
for(var i=0;i<4;i++){
var x=sprite[i].x-10;
var y=sprite[i].y;
var m=190-y;
var n=x;
if(y>=0){
if((mapData[Math.floor(m/10)][Math.floor(n/10)]==1)||(n>130)||(n<0)||(m>190)){
flag=false;
}
}
}
if(flag){
for(var i=0;i<4;i++){
sprite[i].x=sprite[i].x-10;
if(sprite[maxX].x<sprite[i].x)
maxX=i;
if(sprite[maxY].y<sprite[i].y)
maxY=i;
}
}
}else if(button==39){
//左移右移
flag=true;
for(var i=0;i<4;i++){
var x=sprite[i].x+10;
var y=sprite[i].y;
var m=190-y;
var n=x;
if(y>=0){
if((mapData[Math.floor(m/10)][Math.floor(n/10)]==1)||(n>130)||(n<0)||(m>190)){
flag=false;
}
}
}
if(flag){
for(var i=0;i<4;i++){
sprite[i].x=sprite[i].x+10;
if(sprite[maxX].x<sprite[i].x)
maxX=i;
if(sprite[maxY].y<sprite[i].y)
maxY=i;
}
}
}else if(button==40){
//左移右移
flag=true;
for(var i=0;i<4;i++){
var x=sprite[i].x;
var y=sprite[i].y+20;
var m=190-y;
var n=x;
if(y>=0){
if((mapData[Math.floor(m/10)][Math.floor(n/10)]==1)||(n>130)||(n<0)||(m>190)){
flag=false;
}
}
}
if(flag){
for(var i=0;i<4;i++){
sprite[i].y=sprite[i].y+20;
if(sprite[maxX].x<sprite[i].x)
maxX=i;
if(sprite[maxY].y<sprite[i].y)
maxY=i;
}
}
}
}
}
function find(f,a){
for(var i=0;i<a.length;i++){
if((a[i][1]==f[1])&&(a[i][0]==f[0])){
return false;
}
}
return true;
}
LBlock.prototype.οnshοw=function(){
var s=this;
var x=Math.floor(Math.random()*13)*10;
//var x=200;
var y=0;
var i;
var a=new Array();
var save=new Array();
a[0]=[x,y];
save=[[x,y],[-1,-1],[-1,-1],[-1,-1]];
for(i=1;i<4;i++){
if(a[i-1][0]==130){
var b=[[-10,0],[0,10],[0,-10]]
}else if(a[i-1][0]==0){
var b=[[10,0],[0,10],[0,-10]]
}else{
var b=[[-10,0],[10,0],[0,10],[0,-10]];
}
var select=Math.round(Math.random()*(b.length-1));
var m=a[i-1][0]+b[select][0];
var n=a[i-1][1]+b[select][1];
if(find([m,n],save)){
a[i]=[m,n]
save[i]=[m,n]
}else{
b.splice(select,1);
select=Math.round(Math.random()*(b.length-1));
m=a[i-1][0]+b[select][0];
n=a[i-1][1]+b[select][1];
a[i]=[m,n];
}
}
var sprite=[];
for(i=0;i<4;i++){
sprite[i]=new LSprite();
sprite[i].x=a[i][0];
sprite[i].y=a[i][1];
sprite[i].graphics.drawRect(2,"blue",[0,0,10,10],true,"green");
s.addChild(sprite[i]);
}
}