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]);
}
}