功能說明:
在一分鍾內,使用鼠標按着左鍵,在畫布上圈泡泡,其中泡泡的分值分別為10(白)、20(淺藍)、30(黃)、-10(紅)、-20(綠)、-30(深藍)分,可以一次圈多個泡泡,倒計時結束即計算總分值,該游戲基於cnGameJS。
效果預覽:
實現分析:
首先每個小球定義一個ball類,由於小球需要使用圖片,並且有一定的尺寸和運動,所以使該類繼承cnGameJS的sprite類。
ball類除了擁有x,y坐標外,還擁有一個z坐標,該坐標用於使小球具有離玩家遠近的視覺差。
/* 小球對象 */ var Ball=function(opt){ this.parent.call(this,opt); this.oriPos=[this.x+this.width/2,this.y+this.height/2]; this.oriSize=opt.size; this.z=opt.z||0; this.score=opt.score||0; this.oriSpeedZ=4+Math.random()*4; this.scale=1; this.resetXY(); } cg.core.inherit(Ball,Sprite);
之后我們為小球添加resetXY方法,該方法根據小球的z坐標,改變小球的位置以及尺寸,使小球有遠近的視覺差。首先根據z計算縮放比scale,然后根據scale調整x,y和width,height,另外我們使小球z大於1000時,小球消失,這樣就避免了小球過大而占據整個屏幕。
cg.core.extendProto(Ball,{ disappear:function(){//小球被選中消失 list.remove(this); }, resetXY:function(){//根據Z改變x,y的位置和尺寸 var oriX=this.oriPos[0]; var oriY=this.oriPos[1]; var oriSize=this.oriSize; this.scale=((center[0]+this.z)/center[0]);//相對於現時的scale this.x=(oriX-center[0])*this.scale+center[0]; this.y=(oriY-center[1])*this.scale+center[1]; this.height=this.width=this.oriSize*this.scale; this.speedZ=this.oriSpeedZ*this.scale; if(this.z>1000){ this.disappear(); } }, update:function(){ this.parent.prototype.update.call(this); this.resetXY(); } });
之后,為了管理多個小球,可以增加一個小球管理器。管理器負責動態改變小球到玩家的距離以及使小球在畫布隨機的位置出現:
/* 小球對象管理器 */ var ballsManager={ createDuration:200, ballSize:30, lastCreateTime:Date.now(), /* 隨機生成小球 */ createRandomBalls:function(num){ var now=Date.now(); if(now-this.lastCreateTime>this.createDuration){ for(var i=0;i<num;i++){ var x=Math.random()* cg.width; var y=Math.random()* cg.height; var randomKind=ballKinds[Math.floor(Math.random()*6)];//隨機獲得的小球種類和分值 var newBall=new Ball({x:x,y:y,size:this.ballSize,z:-280,score:randomKind[1]}); newBall.setCurrentImage(srcObj[randomKind[0]]);//設置圖片 list.add(newBall); } this.lastCreateTime=now; } }, /* 改變小球位置 */ changeBallsPos:function(){ var ballsArr=list.get(function(elem){ return elem instanceof Ball; }); for(var i=0,len=ballsArr.length;i<len;i++){ var ball=ballsArr[i]; ball.z+=ball.speedZ; } } }
關於小球管理就介紹到這里,之后主要介紹怎樣實現鼠標的圈選。
如果我們在每次幀更新時,根據鼠標現時的位置以及上一次的位置繪制一條線段,那么鼠標的移動軌跡就可以被一條曲線表示出來,該曲線由每次繪制的線段組成,因此我們也可以說該
曲線是一條由多條線段首尾相連組成的曲線。因此我們可以首先實現一個線段類:
/* 直線 */ var line=function(options){ if (!(this instanceof arguments.callee)) { return new arguments.callee(options); } this.init(options); } line.prototype = { /** *初始化 **/ init: function(options) { this.start=[0,0]; this.end=[0,0]; this.style="red"; this.lineWidth=1; this.context=cg.context; options = options || {}; cg.core.extend(this,options); },
之后需要考慮的就是怎樣實現圈選了。當我們用鼠標畫出一個圈時,每條小線段就組成了一個閉合的多邊形,這時我們就可以說鼠標圈出了一個閉合區域,之后就可以進一步計算哪些小球在該區域里面。
但是怎樣判斷鼠標是否圈出了一個閉合區域呢?這里使用的方法是:
遍歷每一條線段,從該線段的下一條再下一條線段開始遍歷余下的線段,判斷它們中是否有線段和開始的那條線段相交,如果相交,則證明曲線閉合了。注意這里從線段的下條再下條線段開始遍歷是為了跳過線段首尾相連的情況。(例如,第一條線段肯定和第二條線段相交,因此從第三條線段開始判斷,跳過相鄰線段收尾相交的情況),代碼如下:
/* 返回軌跡是否閉合 */ var isClose=function(lines){ var hasClose=false; for(var i=0;i<lines.length;i++){ var l1=lines[i]; for(var j=i+2;j<lines.length;j++){ var l2=lines[j]; if(l2){ var point=l1.isCross(l2);//交點坐標 if(point){//非連接的相交 resetLineSegs(lines,i,j,point); hasClosed=true; return true; } } } } return false; };
isCross方法返回線段交點的坐標,我們獲得該坐標后,還需要把多邊形修正成為真正的多邊形,因為用鼠標圈出來的多邊形並不是一個真正的多邊形,它的開始和結束部分很可能會有突出的地方,如下圖:
我們假設鼠標從綠色部分開始圈一個圈,到藍色部分結束。
這樣的話軌跡就並不是一個嚴格的多邊形,因為它多出了藍色和綠色的部分。因此我們需要對圈出來的多邊形進行一個修正操作,使其變成一個真正的閉合多邊形:
/* 重置線段 */ var resetLineSegs=function(lines,i,j,point){ lines[i].end[0]=point[0]; lines[i].end[1]=point[1]; lines[i+1].start[0]=point[0]; lines[i+1].start[1]=point[1]; lines[j].start[0]=point[0]; lines[j].start[1]=point[1]; lines[j-1].end[0]=point[0]; lines[j-1].end[1]=point[1]; for(var m=i+1;m<j;m++){ closedLineSegsArr.push(lines[m]); } }
for(var i=0,len=closedLineSegsArr.length;i<len;i++){ pointsArr.push([closedLineSegsArr[i].start[0],closedLineSegsArr[i].start[1]]); } polygon=new Polygon({pointsArr:pointsArr,style:"rgba(241,46,8,0.5)"});
判斷小球是否在多邊形里,可以轉化為判斷小球的中點是否在多邊形里,這里使用的方法叫
射線法,意思是
從一點向左發射出一條射線,如果射線和多邊形有奇數個交點,則證明點在多邊形內部。根據該定理實現的isInside方法如下:
/** *判斷某點是否在多邊形內(射線法) **/ isInside:function(point){ var lines=this.getLineSegs(); var count=0;//相交的邊的數量 var lLine=new Line({start:[point[0],point[1]],end:[-9999,point[1]]});//左射線 var crossPointArr=[];//相交的點的數組 for(var i=0,len=lines.length;i<len;i++){ var crossPoint=lLine.isCross(lines[i]); if(crossPoint){ for(var j=0,len2=crossPointArr.length;j<len2;j++){ //如果交點和之前的交點相同,即表明交點為多邊形的頂點 if(crossPointArr[j][0]==crossPoint[0]&&crossPointArr[j][1]==crossPoint[1]){ break; } } if(j==len2){ crossPointArr.push(crossPoint); count++; } } } if(count%2==0){//不包含 return false; } return true;//包含 },
大概就說這么多了,歡迎交流~
demo下載地址:點擊下載
