封校心態不好,寫了個解壓遊戲,背景音樂和音效讓人很舒心!

語言: CN / TW / HK

theme: nico

我正在參加「碼上掘金挑戰賽」詳情請看:碼上掘金挑戰賽來了!

前言

疫情一波接著一波,學校都不敢放我們出去了,一直關在學校,現在學校特別注重心理健康,不要因為封校出現心理問題。希望疫情早日結束,見到想見到人,去想玩的地方玩,過節回家團圓,早日回到正軌!

遊戲體驗

遊戲線上體驗有背景音樂和音效,建議各位去github上體驗!

遊戲線上體驗地址:github線上

程式碼片段

遊戲玩法

  • 中間的白色圓球是玩家。
  • 點選黑色地區,會向那個地方發射白色子彈,並且會有發生子彈的音效,碼上掘金沒有聲音!
  • 在隨機地方生成敵人,隨機顏色,會往玩家位置飛過來。
  • 玩家需要點選敵人大概的位置,射出子彈攔住敵人。
  • 擊中敵人發出音效,碼上掘金沒有聲音!,如果敵人較大,還需要繼續射擊!
  • 遊戲有背景音樂!碼上掘金沒有聲音!
  • 敵人碰到玩家,遊戲結束!

遊戲製作

遊戲不需要引入其它js庫。

遊戲使用canvas製作。

遊戲設計

通用樣式

去除內外邊距,網頁超出隱藏。 ```css * { margin: 0; padding: 0; box-sizing: border-box; }

html, body { width: 100%; height: 100%; overflow: hidden; } ```

遊戲開始介面

html <button class="startgame">開 始 遊 戲</button> 給開始遊戲和重新開始按鈕新增樣式 css .startgame, .resgame { width: 200px; height: 60px; background-color: black; position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); outline: none; border: none; border-radius: 20px; color: white; font-size: 25px; cursor: pointer; transition: all .2s linear; } .resgame { margin-top: 50px; } image.png

分數

```html

分數:0

給分數新增樣式css .fs { font-size: 30px; color: white; position: fixed; } ``` image.png

遊戲結束

敵人碰到玩家彈出gameover的div。 ```html

分數:0

重新開始的按鈕和開始遊戲的按鈕樣式一樣,但是添加了外邊距,這樣看起來更美觀。css .resgame { margin-top: 50px; } ``` image.png

背景音樂

先寫好,到時候用JavaScript重複呼叫開啟,這樣就能保證背景音樂的迴圈播放。 html <audio src="bgsound.mp3"></audio>

遊戲功能

基礎功能

把瀏覽器右鍵開啟選單關閉。 js document.oncontextmenu = function () { return false; }; 射擊的時候,會選中文字,導致失誤,所以這裡新增文字無法選中。 js document.addEventListener("selectstart", function (e) { e.preventDefault(); });

開始遊戲

獲取開始遊戲按鈕。點選之後禁用按鈕,防止重複點選。 js let btn1 = document.querySelector('.startgame'); btn1.disabled = true; 開始按鈕點選後,獲取cnavas,設定canvas寬高與瀏覽器可視寬高一樣。js let canvas = document.querySelector('canvas'); let ctx = canvas.getContext('2d'); canvas.width = window.innerWidth; canvas.height = window.innerHeight; ```

通用類

傳入x,y,圓的大小,圓的顏色,移動速度。

使用canvas繪製圓形,寫好移動ai函式,每次執行ai都會移動位置並且執行繪製函式,變化位置。 ```js class Item { constructor(x, y, radius, color, velocity) { this.x = x; this.y = y; this.radius = radius; this.color = color; this.velocity = velocity; };

draw() {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, false);
    ctx.fillStyle = this.color;
    ctx.fill();
};

ai() {
    this.draw();
    this.x = this.x + this.velocity.x;
    this.y = this.y + this.velocity.y;
};

}; ```

玩家、敵人、子彈、粒子效果

玩家、敵人、子彈繼承通用類。

玩家類自動執行繪製函式,因為玩家點選開始遊戲就會出現。 ```js // 玩家 class Player extends Item { constructor(x, y, radius, color) { super(x, y, radius, color); this.draw(); }; };

// 敵人 class Ele extends Item { constructor(x, y, radius, color, velocity) { super(x, y, radius, color, velocity); }; };

// 子彈 class Bullet extends Item { constructor(x, y, radius, color, velocity) { super(x, y, radius, color, velocity); }; }; ``` 粒子效果函式,射中敵人會出現粒子效果。粒子效果函式名與通用類函式名字一致,會優先執行粒子效果類的函式。

image.png ```js class Particle extends Item { constructor(x, y, radius, color, velocity, friction) { super(x, y, radius, color, velocity); this.x = x; this.y = y; this.radius = radius; this.color = color; this.velocity = velocity; this.alpha = 1; this.friction = friction; };

draw() {
    ctx.save();
    ctx.globalAlpha = this.alpha;
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, false);
    ctx.fillStyle = this.color;
    ctx.fill();
    ctx.restore();
};

ai() {
    this.draw();
    this.velocity.x *= this.friction;
    this.velocity.y *= this.friction;
    this.x = this.x + this.velocity.x;
    this.y = this.y + this.velocity.y;
    this.alpha -= 0.01;
};

}; ```

建立場景

定義三個陣列:子彈陣列、敵人陣列、粒子陣列。

新增到陣列為了更好管理、更好統一操作、效能會更好。 js let bulletArray = []; let eleArray = []; let particles = []; let oScore = document.querySelector('.score'); let oGameOver = document.querySelector('.gameover'); let Gscore = document.querySelector('.Gscore'); let score = 0; let flag = true; 函式使用requestAnimationFrame重複呼叫。

填充背景和建立玩家,填充背景透明度為0.1是為了慢慢的填充,這樣可以看到運動軌跡。 image.png

後面就是遍歷三個陣列,把遍歷出來的元素進行判斷或修改。

  1. 敵人被打中後需要刪除,也要把陣列中的敵人刪除。
  2. 如果敵人較大,打中後減去一定的大小。
  3. 遍歷敵人執行ai移動函式,判斷敵人和玩家是否碰撞。碰撞將flag改為false,彈出遊戲結束介面。
  4. 子彈超出邊界和擊中敵人需要刪除,陣列中也需要刪除,效能會更好。

```js function animate() { if (flag) requestAnimationFrame(animate); // 填充背景顏色 ctx.fillStyle = 'rgba(0, 0, 0, .1)'; ctx.fillRect(0, 0, canvas.width, canvas.height); // 建立玩家 let player = new Player(canvas.width / 2, canvas.height / 2, 20, 'white'); // 遍歷子彈 bulletArray.map((item, index) => { item.ai(); // 刪除 if (item.x <= 0 - item.radius || item.y <= 0 - item.radius || item.x >= canvas.width || item.y >= canvas.height) { bulletArray.splice(index, 1); } // 判斷 eleArray.map((ele, i) => { let dist = Math.hypot(ele.x - item.x, ele.y - item.y); // 新增粒子效果 if (dist - item.radius - ele.radius < 1) { for (let i = 0; i < item.radius * 8; i++) { particles.push(new Particle(ele.x, ele.y, Math.random() * 4, ele.color, { x: (Math.random() - 0.5) * (Math.random() * 8), y: (Math.random() - 0.5) * (Math.random() * 8), }, 0.97)); } let a = new Audio(); a.src = 'a.mp3'; a.play(); // 打中後減小 if (ele.radius - 16 > 10) { ele.radius -= 16; bulletArray.splice(index, 1); score += 100; oScore.innerHTML = score; } else { bulletArray.splice(index, 1); eleArray.splice(i, 1); score += 250; oScore.innerHTML = score; } } }); }); // 遍歷敵人 eleArray.map((item) => { item.ai(); // 判斷玩家和敵人碰撞 遊戲結束 let dist = Math.hypot(player.x - item.x, player.y - item.y); if (dist - item.radius - player.radius < 1) { flag = false; document.querySelector("audio").pause(); Gscore.innerHTML = score; oGameOver.style.display = 'block'; } }); particles.map((item, index) => { if (item.alpha <= 0) { particles.splice(index, 1); } else { item.ai(); } });

} animate(); ```

建立子彈

滑鼠點選之後,建立一個音樂器,播放設計的音效,使用Math獲取點選位置,然後轉換成角度發射出去,新增到陣列之中。

QQ錄屏20220912210412.gif js window.addEventListener('mousedown', (e) => { if (!flag) return; let b = new Audio(); b.src = 'b.mp3'; b.play(); // 返回原點到點的線段與x軸正方向之間的平面角度 let angle = Math.atan2(e.clientY - canvas.height / 2, e.clientX - canvas.width / 2); // 把角度轉換 let velocity = { x: Math.cos(angle) * 5, y: Math.sin(angle) * 5 }; // 新增到數組裡 bulletArray.push(new Bullet(canvas.width / 2, canvas.height / 2, 5, 'white', velocity)); });

建立敵人

建立敵人的地方重複執行背景音樂的執行操作

QQ錄屏20220912210329.gif

1.5秒建立一個敵人,使用隨機數製作隨機位置,用Math.atan2轉換成位置資料,使用Math.cos和Math.sin把位置資料調成x,y。

最後把創建出來的敵人新增到陣列當中。 js setInterval(() => { if (!flag) return; document.querySelector('audio').play(); // 隨機大小 let radius = Math.random() * (35 - 15) + 15; // 隨機顏色 let color = `hsl(${Math.random() * 360}, 50%, 50%)`; let x, y; // 隨機位置 if (Math.random() < 0.5) { x = Math.random() < 0.5 ? 0 - radius : canvas.width + radius; y = Math.random() * canvas.height + radius; } else { x = Math.random() * canvas.width + radius; y = Math.random() < 0.5 ? 0 - radius : canvas.height + radius; } let angle = Math.atan2(canvas.height / 2 - y, canvas.width / 2 - x); let velocity = { x: Math.cos(angle) * 2.5, y: Math.sin(angle) * 2.5, }; eleArray.push(new Ele(x, y, radius, color, velocity)); }, 1500); });

總結

本次把所有需要移動,執行的元素放在陣列當中,能更好的控制和執行還能節約效能。

我是學生,很多不足,請諒解!

國慶還有18天,回家倒計時18天,已經200多天沒回家了,好想爸媽呢。

3R(ZCE%{H)LY5IJNAMQ_P@S.gif