1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        用vue3+pixijs復(fù)刻童年記憶里的游戲-獵鴨季節(jié)

        共 6682字,需瀏覽 14分鐘

         ·

        2022-04-15 16:35

        術(shù)??堅(jiān)??

        前言

        本期將用vue3與pixijs復(fù)刻出童年在小霸王里面玩的游戲-獵鴨季節(jié),當(dāng)初玩它需要光線槍才行,非常不好瞄準(zhǔn),每當(dāng)打中鴨子就非常激動(dòng),打不中就會(huì)有收到狗子的嘲笑,想想那時(shí)候快樂(lè)真的很簡(jiǎn)單?,F(xiàn)在,就想辦改造了一下,讓他不用光線槍用鼠標(biāo)也能擊中它,圓童年一個(gè)夢(mèng)。

        廢話不多說(shuō),我們先來(lái)康康展示效果怎樣吧:


        演示地址:http://jsmask.gitee.io/duck-hunt/

        介紹

        因?yàn)檫@個(gè)游戲大部分界面都是用pixi.js繪制出來(lái)的,本身代碼量也是比較多的而且比較繁瑣,不可能一次全講完,所以本期主要講解一下,它里面是如何進(jìn)行加載,如何繪制界面,如何做游戲動(dòng)畫,如何進(jìn)行擊中判定,以及怎么適配屏幕尺寸等等。當(dāng)最開(kāi)始我們先來(lái)進(jìn)行一些準(zhǔn)備工作。

        游戲規(guī)則

        1. 進(jìn)入游戲每輪五回合,每回合會(huì)出現(xiàn)兩只鴨子,和三發(fā)子彈。
        2. 每擊中一直鴨子則獎(jiǎng)勵(lì)500分,若每輪擊中全部鴨子則有特別獎(jiǎng)勵(lì)。
        3. 若子彈用盡或超時(shí)則鴨子都會(huì)飛走,請(qǐng)把握每一發(fā)子彈和珍惜時(shí)間。
        4. 每輪如果打中超過(guò)6只以上鴨子,才有資格晉級(jí)下一輪,最多三輪。

        游戲流程

        1. 點(diǎn)擊初始畫面開(kāi)始進(jìn)入游戲界面。
        2. 每輪游戲開(kāi)始出現(xiàn)獵犬過(guò)場(chǎng)動(dòng)畫。
        3. 獵犬過(guò)場(chǎng)動(dòng)畫后,游戲可進(jìn)行射擊操作。
        4. 每次點(diǎn)擊鼠標(biāo),則表示發(fā)射子彈,點(diǎn)中鴨子則加分,點(diǎn)中鴨子,超時(shí)或子彈用光則鴨子加速飛走等邏輯。
        5. 游戲超過(guò)三輪或者未滿足晉級(jí)條件,則強(qiáng)制退回初始畫面并記錄分?jǐn)?shù),可以重新開(kāi)始。

        主要技術(shù)

        1. vite:負(fù)責(zé)整個(gè)項(xiàng)目的模塊構(gòu)建打包任務(wù)。
        2. vue3:作為前端框架,方便完成一些界面的響應(yīng)式、組件化等。
        3. scss:負(fù)責(zé)初始加載界面的css動(dòng)畫,與一些界面比例調(diào)整的樣式工作。
        4. mitt.js:負(fù)責(zé)發(fā)布訂閱的任務(wù)。
        5. pixi.js:游戲引擎,游戲中絕大部分任務(wù)都在這里完成。
        6. gsap.js:負(fù)責(zé)一些動(dòng)畫的操作。

        游戲素材

        微信截圖_20220327112836.png

        這里為了縮小游戲本體尺寸也是為了更接近原作用Press Start 2P用了像素字體,聲音統(tǒng)一做了壓縮處理從wav轉(zhuǎn)成mp3格式。而圖片原本是一張大圖這里看到的碎圖是因?yàn)橛昧薙hoeBox軟件去完成了拆分,但是我沒(méi)有做進(jìn)一步處理圖片(將它們尺寸統(tǒng)一等再進(jìn)行TexturePackerGUI拼合),后面會(huì)講明我用了另一種方法來(lái)處理這些動(dòng)畫。

        開(kāi)始

        發(fā)布訂閱

        import?mitt?from?"mitt";

        const?bus?=?{};

        const?emitter?=?mitt();

        bus.$on?=?emitter.on;
        bus.$off?=?emitter.off;
        bus.$emit?=?emitter.emit;

        export?default?bus;

        因?yàn)関ue3里面沒(méi)有了off,所以我們使用mitt來(lái)去代替發(fā)布訂閱這個(gè)任務(wù),里面很多的狀態(tài)改變的通知都借助于它來(lái)完成的。

        文件結(jié)構(gòu)


        加載動(dòng)畫組件我之前寫的一篇文章,就是拿它來(lái)完成加載動(dòng)畫的:WEB加載動(dòng)畫之像素字動(dòng)畫

        DuckGame組件就是我們游戲的主容器了。


        new?Game({
        ????width,
        ????height,
        ????el:?canvas.value,
        ????resolution:?1,
        ????onProgress:?n?=>?{
        ????????Bus.$emit("changeProgress",?n);
        ????}
        }).init();

        我們這里要把游戲容器傳到Game中,生成一個(gè)實(shí)例,當(dāng)然在你可以看到在onProgress中,發(fā)給加載動(dòng)畫組件通知當(dāng)前的進(jìn)度狀況。

        微信截圖_20220327120321.png

        游戲場(chǎng)景

        import?{?Container?}?from?"pixi.js";
        export?default?class?Scene?{
        ????constructor(game)?{
        ????????this.game?=?game;
        ????????this.stage?=?new?Container();
        ????????this.stage.interactive?=?true;
        ????????this.stage.buttonMode?=?true;
        ????????this.stage.sortableChildren?=?true
        ????????this.stage.zIndex?=?1
        ????????return?this
        ????}
        ????onStart()?{?}
        ????init()?{?}
        ????show()?{
        ????????this.stage.visible?=?true
        ????}
        ????hide()?{
        ????????this.stage.visible?=?false
        ????}
        ????update(delta)?{
        ????????if?(!this.stage.visible)?return;
        ????}
        }

        游戲里所有的場(chǎng)景都繼承了Scene,因?yàn)檫壿嫳容^簡(jiǎn)單只涉及了開(kāi)始界面和游戲界面兩個(gè)場(chǎng)景,所有比較的簡(jiǎn)單,目前只有顯示隱藏更新這些基礎(chǔ)方法。

        我們每當(dāng)創(chuàng)建新界面就會(huì)有繼承它,如開(kāi)始界面:

        import?Scene?from?"./scene"

        class?StartScene?extends?Scene?{
        ????constructor(game)?{
        ????????super(game)
        ????????this.topScore?=?null;
        ????????return?this
        ????}
        }

        export?default?StartScene

        加載素材

        因?yàn)槲覀冇昧藇ue3所以就借雞下蛋,用了URL的方式去獲取對(duì)應(yīng)的素材。

        export?function?getImageUrl(name,?ext?=?"png")?{
        ????return?new?URL(`/src/assets/${name}.${ext}`,?import.meta.url).href
        }

        然后進(jìn)行配置:

        const?audioList?=?{
        ????fire:?getImageUrl("fire",?"mp3"),
        ????//?...more
        }

        const?stage?=?getImageUrl("stage");
        //?...more

        export?default?{
        ????stage,
        ????...audioList,
        ????//?more
        }

        通過(guò)pixi.js中的Loader去完成加載任務(wù),同時(shí)通知vue3加載動(dòng)畫組件當(dāng)前的加載進(jìn)度。同時(shí)還要將他們變成紋理圖存儲(chǔ)起來(lái)以方便后面pixi.js繪圖使用。

        export?default?class?Game?{
        ????//?...
        ????init()?{
        ????????this.loaderTextures().then(res?=>?{
        ??????????????Object.entries(res).forEach(([key,?value])?=>?setTextures(key,?value.texture))
        ??????????????this.render()
        ????????})
        ????},
        ????loaderTextures()?{
        ????????const?{?loader,?onProgress?}?=?this;
        ????????return?new?Promise((resolve,?reject)?=>?{
        ??????????Object.entries(assets).forEach(([key,?value])?=>?loader.add(key,?value,?()?=>?{
        ????????????onProgress(loader.progress)
        ??????????}))
        ??????????loader.load((loader,?resources)?=>?{
        ????????????onProgress(loader.progress)
        ????????????resolve(resources)
        ??????????})
        ????????})
        ????},
        ????reader(){
        ??????//?渲染界面??
        ????},
        ????//?...
        }
        微信截圖_20220327121512.png

        繪制界面

        本作大部分的界面都是pixi.js中的繪圖API來(lái)完成,主要是體力勞動(dòng),可以參考pixi.js官網(wǎng)的API來(lái)學(xué)習(xí)。這里制作簡(jiǎn)單的介紹,如下面的背景黑塊繪制,和總積分繪制。

        import?{?Text,?Graphics,?Container?}?from?"pixi.js";

        class?StartScene?extends?Scene?{
        ???//?...
        ????drawBg()?{
        ????????const?{?width,?height?}?=?this.game;
        ????????const?graphics?=?new?Graphics();
        ????????graphics.beginFill(0x000000,?1);
        ????????graphics.drawRect(0,?0,?width,?height);
        ????????graphics.endFill();
        ????????this.stage.addChild(graphics)
        ????}
        ????drawTopScore(score?=?0)?{
        ????????const?{?width,?height?}?=?this.game;
        ????????this.topScore?=?new?Text("top?score?=?".toUpperCase()?+?score,?{
        ????????????fontFamily:?'Press?Start?2P',
        ????????????fontSize:?24,
        ????????????leading:?20,
        ????????????fill:?0x66DB33,
        ????????????align:?'center',
        ????????????letterSpacing:?4
        ????????});
        ????????this.topScore.anchor.set(0.5,?0.5);
        ????????this.topScore.position.set(width?/?2,?height?-?60)
        ????????this.stage.addChild(this.topScore)
        ????}
        }

        export?default?StartScene
        微信截圖_20220327122115.png

        游戲動(dòng)畫

        因?yàn)閜ixi.js并不是一個(gè)可視化的游戲引擎,所以為了更方便的制作游戲動(dòng)畫我們使用了gsap.js來(lái)代替。游戲里會(huì)出現(xiàn)的一些閃動(dòng)的動(dòng)畫,如開(kāi)始界面中的click to start the game這段文字按鈕的閃動(dòng),利用SteppedEase緩動(dòng),看起來(lái)符合那個(gè)年代的味道。

        import?{?TimelineMax?}?from?"gsap"

        let?btnAni?=?new?TimelineMax().fromTo(this.btn,?{?alpha:?0?},?{?alpha:?1,?duration:?.45,?immediateRender:?true,?ease:?"SteppedEase(1)"?});
        btnAni.repeat(-1)
        btnAni.yoyo(true);
        VID_20220327_150632.gif

        當(dāng)然涉及到更多的還有里面的幀動(dòng)畫,比如獵犬的搜尋,嘲笑,鴨子的飛行等等都是幀動(dòng)畫來(lái)完成的。pixi.js卻也有幀動(dòng)畫執(zhí)行的方案,但是我這里素材沒(méi)有進(jìn)一步處理所以取了個(gè)巧,還是用gsap.js的SteppedEase緩動(dòng)模擬幀,這樣的好處是每一幀都可以有方法去調(diào)節(jié)圖片的位置去彌補(bǔ)圖片大小不一產(chǎn)生的位移問(wèn)題。

        let?dogSearchAni?=?new?TimelineMax()
        dogSearchAni
        ????.from(dog,?0.16,?{?texture:?getTextures("dog0"),?ease:?"SteppedEase(1)"?})
        ????.to(dog,?0.16,?{?texture:?getTextures("dog1"),?ease:?"SteppedEase(1)"?})
        ????.to(dog,?0.16,?{?texture:?getTextures("dog2"),?ease:?"SteppedEase(1)"?})
        ????.to(dog,?0.16,?{?texture:?getTextures("dog3"),?ease:?"SteppedEase(1)"?})
        ????.to(dog,?0.2,?{?texture:?getTextures("dog4"),?ease:?"SteppedEase(1)"?})
        dogSearchAni.repeat(-1)
        dogSearchAni.play()

        擊中判定

        判定有兩種方式,第一種是包圍盒檢測(cè),判定鼠標(biāo)點(diǎn)擊的點(diǎn)是否與鴨子存在重合,若重合則表示擊中。第二種是pixi.js存在的pointerdown事件。這里偷了個(gè)懶,也防止一箭雙雕的事件產(chǎn)生就用了pointerdown事件。當(dāng)我們點(diǎn)擊到鴨子之時(shí),就改變當(dāng)前該鴨子的狀態(tài)表示擊中。同時(shí),我們的系統(tǒng)還會(huì)發(fā)出一個(gè)子彈事件,如果鴨子的isHit狀態(tài)變成true并且isDie是false表示擊中未死,那么就要執(zhí)行顯示分?jǐn)?shù),掉落死亡動(dòng)畫,最后銷毀掉。

        export?default?class?Duck?{
        ????constructor({?dIndex?=?0,?x?=?0,?y?=?0,?speed?=?3,?direction?=?1,?stage,?rect?=?[0,?0,?1200,?759]?})?{
        ????????//?...
        ????????this.target?=?new?Container();
        ????????
        ????????//?點(diǎn)中改變狀態(tài)
        ????????this.target.on("pointerdown",?()?=>?{
        ????????????if?(!this.isHit)?this.isHit?=?true;
        ????????})
        ????????
        ????????//?接收子彈事件
        ????????Bus.$on("sendBullet",?({?e,?callback?})?=>?{
        ????????????if?(this.isHit?&&?!this.isDie)?{
        ????????????????this.isDie?=?true;
        ????????????????this.hit();
        ????????????????this.duck_sound.play()
        ????????????????callback?&&?callback(this)
        ????????????}
        ????????})
        ????????//?接收飛走事件
        ????????Bus.$on("flyaway",?()?=>?{
        ????????????this.isFlyaway?=?true;
        ????????})
        ????????return?this;
        ????}
        ????move(delta)?{
        ????????//?移動(dòng)
        ????}
        ????async?hit()?{
        ????????//?擊中
        ????????const?{?sprite,?score,?target?}?=?this;
        ????????this.normalAni.kill();
        ????????sprite.texture?=?getTextures("duck_9")
        ????????sprite.width?=?getTextures("duck_9").width
        ????????sprite.height?=?getTextures("duck_9").height
        ????????showScore({
        ????????????parent:?this.stage,
        ????????????score,
        ????????????x:?target.x?-?(this.vx?0???+?sprite.width?:?0),
        ????????????y:?target.y
        ????????})
        ????????await?wait(.35)
        ????????this.die()
        ????}
        ????die()?{
        ????????//?死亡
        ????}
        ????fly()?{
        ????????//?飛行
        ????}
        ????destroy()?{
        ????????//?銷毀
        ????????if?(this.target.parent)?{
        ????????????this.target.parent.removeChild(this.target)
        ????????}
        ????}
        }
        微信截圖_20220327152946.png

        適配屏幕

        為了讓界面不變形的情況下最大程度顯示出來(lái),我用了一個(gè)取巧的方案,用了css的transform:scale+v-bind的方法,讓vue計(jì)算出最大比例,然后綁定到css里面。






        微信截圖_20220327154002.png

        結(jié)語(yǔ)

        總體來(lái)說(shuō),pixi.js還是非常強(qiáng)大的,處理此類的游戲再合適也不過(guò)了。如果場(chǎng)景界面非常多,動(dòng)畫也非常多的話,還是建議使用cocos creator,可以節(jié)省很多工作量。

        本期的游戲也是我盡可能注意一些游戲細(xì)節(jié)做的,童年記憶從模糊也漸漸變得清晰了許多,希望各位童年也說(shuō)過(guò)我長(zhǎng)大以后要做游戲的小伙伴不忘初心,有時(shí)間把自己童年喜愛(ài)的游戲用自己的方式創(chuàng)作出來(lái),也未嘗不是一種技術(shù)上的鍛煉和追憶童年無(wú)憂無(wú)慮生活的一種方式。

        ???H5-Dooring,讓H5制作更簡(jiǎn)單

        目前H5-Dooring架構(gòu)升級(jí), 已支持多種搭建布局模式, 如網(wǎng)格布局,?自由布局, 可以一鍵切換布局模式:


        歡迎體驗(yàn):?http://h5.dooring.cn/h5_plus

        ???

        便內(nèi),對(duì)^_^

        ?點(diǎn)、?~。

        關(guān)號(hào)?趣談前端?獲前端~

        瀏覽 45
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            中文字幕在线视频网站国产免费 | 久久久久久久久久久一区 | 色逼视频网站 | 欧美亚洲一级黄片 | 性乱伦视频 | 男女免费看 | 国产超碰欧美 | 欧美性生活视频 | 疯狂三人交性欧美 | 免费无码成人片在线播放 |