1.前提
依舊是需求驅(qū)動(dòng)學(xué)習(xí)新的庫(kù)。。授艰。
需求是要做一個(gè)拼圖的h5,需求如下:
1)共男女兩名角色糟需,男性角色為主要角色,女性角色為彩蛋武花,將兩名角色都由上至下切成四等份体箕,分別在15s的倒計(jì)時(shí)內(nèi)隨機(jī)掉落兩名角色的相同身體部位(略驚悚挑童?)站叼,點(diǎn)擊屏幕則停止當(dāng)前部件掉落,開(kāi)始掉落下一部分的身體部件投储。若在倒計(jì)時(shí)之內(nèi)部件掉出屏幕玛荞,則重復(fù)刷新當(dāng)前部位的零件向下掉落垦缅;若倒計(jì)時(shí)結(jié)束則游戲失敗壁涎。當(dāng)身體四部分都拼好則游戲結(jié)束并計(jì)算得分,根據(jù)得分發(fā)放不同檔位的游戲禮包嚼酝。
2)分?jǐn)?shù)的計(jì)算分為兩部分闽巩,一部分是重合度担汤,一部分是完成度崭歧。重合度,顧名思義就是身體零件與標(biāo)準(zhǔn)坐標(biāo)的偏差值叔营。完成度就是指男性角色拼合的完整度。
3)游戲結(jié)束后跳轉(zhuǎn)至結(jié)果頁(yè)畜挥,結(jié)果頁(yè)根據(jù)不同的檔位顯示不同的文案蟹但,且根據(jù)拼出角色的性別做出不同的頁(yè)面表現(xiàn)勘究。同時(shí)生成相應(yīng)的圖片以便用戶(hù)保存分享。
2.頭腦風(fēng)暴時(shí)間
最初的想法是使用canvas原生的api進(jìn)行開(kāi)發(fā)缅阳,后續(xù)的構(gòu)想下來(lái)發(fā)現(xiàn)單純使用原生api對(duì)于開(kāi)發(fā)非常的累贅且不好控制十办,所以在隔壁前端大佬的慫恿下接觸到pixi.js向族,看了一下github上的簡(jiǎn)易中文文檔(點(diǎn)擊我跳轉(zhuǎn))并嘗試跟著demo打了一下代碼棠绘,發(fā)現(xiàn)這個(gè)庫(kù)提供的api非常好用氧苍,且對(duì)于性能的優(yōu)化也不錯(cuò)(官方自己說(shuō)的),于是就開(kāi)始踩坑之路紊撕。
因?yàn)樽詈笠哑春系慕Y(jié)果加入到生成的分享圖片中(分享圖片中的底和游戲界面的底不一樣对扶,不能直接將游戲界面導(dǎo)出圖片惭缰,需要先將拼合的角色導(dǎo)出再與分享圖片的底進(jìn)行拼接)漱受,所以決定在游戲?qū)犹砑觾蓚€(gè)canvas,一個(gè)負(fù)責(zé)繪制背景及一些動(dòng)效(純展示),另一個(gè)用于繪制身體零件的掉落效果紧憾。這樣到最后可以直接將負(fù)責(zé)繪制身體零件的canvas通過(guò)pixi提供的api轉(zhuǎn)成base64格式(png)赴穗,上傳到服務(wù)器中并拿到相應(yīng)的url。拿到url后再新建一個(gè)canvas用于繪制分享的圖片了赵。
3.html結(jié)構(gòu)劃分
根據(jù)h5功能進(jìn)行html結(jié)構(gòu)的劃分柿汛,共分為五部分:loading頁(yè)埠对,游戲進(jìn)入頁(yè)(主視覺(jué)渲染)项玛,游戲頁(yè),結(jié)果頁(yè)和微信與游戲綁定頁(yè)(用于后續(xù)禮包發(fā)放)
4.開(kāi)始踩坑
主角來(lái)啦~
1) 首先我們要生成一個(gè)canvas并把他插入到頁(yè)面中:
var app = new Application({
width: 750,
height: 1500,
antialias: true,
transparent: true,
resolution: 1
})
var stage = app.stage;
app.view.classList.add('stage');
$('.game-page').append(app.view);
現(xiàn)在添加的幾個(gè)參數(shù)分別是:width:畫(huà)布寬度、height:畫(huà)布高度开伏、antialias:抗鋸齒硅则、transparent:透明和resolution(視網(wǎng)膜屏幕配置選項(xiàng))
application.stage中保存了舞臺(tái)的各種信息,后續(xù)的各種部件都是添加到舞臺(tái)內(nèi)進(jìn)行渲染暑认。
這時(shí)候就可以在頁(yè)面中看到一個(gè)black square拉蘸际。好的徒扶,項(xiàng)目結(jié)束可以回家了(并沒(méi)有。)
2)接下來(lái)我們要將圖片加載到紋理緩存中
PIXI.loader.add(imgArr).load(setup);
PIXI提供了一個(gè)loader屿良,可以用于加載各種圖片資源尘惧,loader.add()方法中可以接受一個(gè)字符串或一個(gè)數(shù)組递递,可以將資源地址放在一個(gè)數(shù)組中直接放在loader.add()中進(jìn)行加載登舞,loader會(huì)在圖片加載完全后調(diào)用setup方法,我們就可以在setup方法中盡情的開(kāi)始操作拉~
3) 先了解下一些生成精靈的方法
在pixi中疙剑,生成一個(gè)精靈非常的簡(jiǎn)單稽煤,例:
var sprite = new PIXI.Sprite(
PIXI.loader.resources["images/anyImage.png"].texture
);
但是!這是一整張圖片作為一個(gè)精靈材質(zhì)的方法轧简,為了減少請(qǐng)求哮独,我們通常會(huì)把一些小的圖片進(jìn)行合并生成雪碧圖察藐,那我們?nèi)绾螐难┍虉D中讀取一小部分作為精靈的材質(zhì)呢!先貼代碼:
function setup() {
//先從緩存中拿到材質(zhì)
let texture = TextureCache["images/tileset.png"];
//新建一個(gè)Rectangle(矩形)悴务,四個(gè)參數(shù)分別是x坐標(biāo), y坐標(biāo)讯檐,width和height
let rectangle = new Rectangle(192, 128, 64, 64);
//把這個(gè)矩形賦值給材質(zhì)的frame
texture.frame = rectangle;
//這樣你就能成功的在雪碧圖中拿到你想要的部分了
let rocket = new Sprite(texture);
}
掌握勒這個(gè)方法以后我就興致沖沖染服,一下子就根據(jù)坐標(biāo)和寬高創(chuàng)建了10個(gè)不同rectangle柳刮,依次賦值給texture.frame再生成精靈痒钝。
結(jié)果刷新頁(yè)面后送矩,發(fā)現(xiàn)居然十個(gè)精靈都是一樣的哪替,當(dāng)時(shí)我的內(nèi)心是絕望的夷家。后來(lái)猜測(cè)可能是因?yàn)橐玫氖峭痪彺鎸?dǎo)致的(云里霧里@_@)后來(lái)想起js中的深度拷貝方法库快,誤打誤撞發(fā)現(xiàn)pixi也提供了texture.clone()方法钥顽,嘗試在每次賦值給texture.frame賦值之前先把texture拷貝一邊,居然成功了闽铐!于是把他封裝成了一個(gè)方法兄墅,接受五個(gè)參數(shù)分別為材質(zhì)澳叉,x坐標(biāo),y坐標(biāo)和寬高五督,方法的返回直接是一個(gè)sprite對(duì)象充包,代碼如下:
function produceSprite(texture, x, y, width, height) {
var itemSprite = texture.clone();
var rectangle = new PIXI.Rectangle(x, y, width, height);
itemSprite.frame = rectangle;
var block = new Sprite(itemSprite);
return block;
}
4)將散落在人間的身體部件添加到舞臺(tái)內(nèi)
在生成角色部件的同時(shí)先將他們的標(biāo)準(zhǔn)坐標(biāo)賦值給finalY基矮,這里的chara為1時(shí)表示是男性角色修壕,為2時(shí)表示是彩蛋角色慈鸠,代碼如下:
function addCharacterBlocks() {
var posArr = [
{chara: 1, width: 512, height: 290, x: 0 , y: 0},
{chara: 2, width: 644, height: 290, x: 520, y: 0},
{chara: 1, width: 512, height: 230, x: 0 , y: 290},
{chara: 2, width: 644, height: 230, x: 520, y: 290},
{chara: 1, width: 512, height: 168, x: 0 , y: 520},
{chara: 2, width: 644, height: 168, x: 520, y: 520},
{chara: 1, width: 512, height: 227, x: 0 , y: 688},
{chara: 2, width: 644, height: 227, x: 520, y: 688}
]
var arrIndex = 0;
posArr.forEach(function(pos, index) {
var block = produceSprite(TextureCache['http://static.sdg-china.com/moon/pic/20181120_Pingtu/character.png'], pos.x, pos.y, pos.width, pos.height);
block.finalY = pos.y + 86;
block.finalTop = pos.y + 86 - pos.height;
block.finalBottom = pos.y + 86 + pos.height * 2;
if (index % 2) {
block.x = 68;
} else {
block.x = 134;
}
block.y = -pos.height;
block.vy = 0;
block.chara = pos.chara;
if (!(index % 2) && index !== 0) {
arrIndex++;
}
blocks[arrIndex].push(block);
})
blocks.forEach(function(block) {
block.forEach(function(b) {
charaContainer.addChild(b)
})
})
}
5 ) 大家一起動(dòng)起來(lái)~
pixi中提供了ticker對(duì)象,用于根據(jù)顯示機(jī)器的刷新率進(jìn)行渲染咖楣,只需在sprite對(duì)象添加兩個(gè)速度屬性:vx,vy來(lái)控制他們的運(yùn)動(dòng)速度:vx被用來(lái)設(shè)置精靈在x軸(水平)的速度和方向芦昔。vy被用來(lái)設(shè)置精靈在y軸(垂直)的速度和方向咕缎。后續(xù)如果要控制精靈的運(yùn)動(dòng)與方向,只需要對(duì)他們的vx或vy屬性進(jìn)行操作就好焙蹭。
app.ticker.add(function(delta) {
gameLoop(delta)
});
function gameLoop(delta){
cat.vx = 1;
cat.vy = 1;
//Apply the velocity values to the cat's
//position to make it move
cat.x += cat.vx;
cat.y += cat.vy;
}
6)點(diǎn)擊事件
pixi似乎是屏蔽了原生的事件孔厉,不論是將事件添加到body上還是添加到canvas上帖努,統(tǒng)統(tǒng)沒(méi)有反應(yīng),原來(lái)是他已經(jīng)提供了一套事件:
eventBlock.on('tap', function() {
getRandomBlockMove();
});
這里的getRandomBlockMove()是一個(gè)自定義的方法拼余,當(dāng)用戶(hù)點(diǎn)擊后將當(dāng)前在運(yùn)動(dòng)的精靈對(duì)象添加到最終結(jié)果的數(shù)組中匙监,以便后續(xù)分?jǐn)?shù)的計(jì)算。同時(shí)使下一部分的身體部件開(kāi)始運(yùn)動(dòng)梭纹。
7)分?jǐn)?shù)計(jì)算
因?yàn)榻o之前生成的每個(gè)身體部件的Sprite都添加了finalY屬性变抽,所以這一步只需要把最后結(jié)果數(shù)組中的四部分最終的坐標(biāo)和他們標(biāo)準(zhǔn)的坐標(biāo)進(jìn)行減法運(yùn)算并取絕對(duì)值绍载,就能拿到身體部件與標(biāo)準(zhǔn)坐標(biāo)的偏差從而計(jì)算得分
8)遮罩
pixi也提供了遮罩的屬性滔蝉,可以用Graphics或Sprite作為container或Sprite的遮罩蝠引,但是Sprite作為遮罩只有當(dāng)當(dāng)前的渲染器是webgl才能夠使用。接下來(lái)是創(chuàng)建一個(gè)graphics并將他作為遮罩的代碼示例~
var headMask = new PIXI.Graphics();
headMask.clear();
headMask.beginFill(0xffffff);
headMask.drawCircle(0, 0, 33);
headMask.endFill();
//head是一個(gè)Sprite
head.mask = headMask;
9)生成圖片
pixi也提供了十分好用的canvas轉(zhuǎn)base64的方法:
//app為生成base64圖片的應(yīng)用矫夯,這個(gè)方法接受一個(gè)參數(shù),就是需要被截圖的容器制肮;
app.renderer.plugins.extract.base64(charaContainer);
到這里豺鼻,最初的設(shè)計(jì)問(wèn)題就出來(lái)了款慨,最初的構(gòu)想是一個(gè)負(fù)責(zé)背景部分樱调,一個(gè)負(fù)責(zé)人物運(yùn)動(dòng)笆凌∑蚨可是這樣需要同時(shí)渲染兩個(gè)canvas慢显,對(duì)性能的要求會(huì)相對(duì)較高荚藻,所以后來(lái)就把兩個(gè)app合并成一個(gè)了:將人物部件添加到一個(gè)container內(nèi),導(dǎo)出圖片時(shí)直接將包裹人物物件的container作為參數(shù)共郭,這樣就能導(dǎo)出透明底的拼合人物了除嘹。
5.總結(jié)
項(xiàng)目結(jié)束后對(duì)pixi.js也算有了一個(gè)初步的認(rèn)識(shí)岸蜗,同時(shí)也領(lǐng)略到了canvas的魅力,中間也是踩了很多的坑(為什么還不出完整版的中文文檔Aг馈!全程都在啃生肉文檔)炎功,好在有隔壁前端大佬的幫助缓溅,有很多想不通的地方都是一起思考出解決方案坛怪。一切終于都結(jié)束了=。= 我的靈魂已經(jīng)飛走了袜匿。更啄。。最后貼一個(gè)項(xiàng)目的鏈接(點(diǎn)擊我跳轉(zhuǎn))居灯。
PS:感覺(jué)寫(xiě)的不夠詳細(xì)祭务,后續(xù)還會(huì)再改進(jìn),感謝進(jìn)來(lái)看我分享的各位大佬