p5.js實現(xiàn)聲音控制警察抓小偷游戲示例解析
一、游戲介紹
之前一直用原生canvas寫小游戲,很多邏輯都需要自己一點點封裝,最近看了下p5.js,哇哦,好的很嘞!就用它開發(fā)了一款名為“警察抓小偷”的游戲。這是一款非常有趣的游戲,玩家扮演警察追捕小偷,通過大聲喊話來控制警察的速度,抓住小偷。游戲中,小偷會在棋盤上移動,而警察則會向小偷靠近。如果警察追上小偷,就算警察勝利;如果小偷逃脫到棋盤的最下方,那么小偷勝利。老少皆宜哦??。
1. 玩法指南
點擊開始游戲按鈕小偷會在棋盤上移動,游戲會調(diào)用麥克風(fēng),玩家通過大聲狂呼亂叫,此時警察則會向小偷靠近,你喊得越大聲,警察移動速度越快,當(dāng)警察追上小偷,就算警察勝利;如果小偷跑到的右下方出口處逃脫,那么小偷勝利。
2.運行效果
游戲體驗鏈接:警察抓小偷游戲 (forrestyuan.github.io)由于GitHub page的緣故,頁面加載可能會慢一點,大家耐心等待哈!記得要同意麥克風(fēng)的調(diào)用權(quán)限申請哦。
代碼倉庫地址:forrestyuan/catchThief警察抓小偷(github.com)



二、用到的JS庫
p5.js庫基于 Processing,是一個 JavaScript 庫,可以輕松地創(chuàng)建交互式圖形和動態(tài)圖像。它的核心是使用 HTML5 Canvas 元素來創(chuàng)建圖形。p5.js 提供了簡單易用的 API,讓編寫和理解代碼變得更加容易。p5.sound.min.js是 p5.js 庫的音頻處理插件,它基于 Web Audio API,可以加載、播放和操縱音頻文件。Web Audio API 是 HTML5 中的一項標(biāo)準(zhǔn),提供了低延遲、高品質(zhì)的音頻處理能力,可以通過 JavaScript 對音頻進(jìn)行混合、變形、過濾等操作。
三、游戲開發(fā)思路
- 設(shè)計游戲規(guī)則和界面
- 使用p5.js繪制游戲界面和元素,這比自己用原生canvas寫迅速多了。
- 實現(xiàn)小偷和警察的移動邏輯
- 實現(xiàn)通過音量控制警察的速度
- 實現(xiàn)勝利邏輯和動畫效果
- 添加開始游戲和重新開始的按鈕
- 預(yù)加載音樂和圖片資源
四、核心功能點
核心功能點主要包括繪制棋盤、小偷的移動邏輯、警察的移動邏輯以及勝利邏輯函數(shù)。大家著重理解這些開發(fā)思路。
1. 繪制棋盤
在 p5.js 的 draw() 函數(shù)中,調(diào)用rect()方法來繪制20X15的游戲棋盤,image()方法來繪制小偷、警察元素,并實時繪制小偷和警察的新位置和動態(tài)更新頁面上玩家的麥克風(fēng)音量刻度條,通過whoWin變量檢查是否勝利,以及執(zhí)行相應(yīng)的文案繪制效果。
//循環(huán)體
function draw() {
//.....
// 繪制格子盤
for (let i = 1; i < col + 1; i++) {
for (let j = 0; j < row; j++) {
fill('pink')
rect(i * gridW, j * gridH, gridW, gridH - 1); // 將高度改為gridH-1
}
}
// 繪制小偷和警察
image(thiefImg, thiefXPos, thiefYPos - 5, gridW, gridH + 5);
image(policeImg, policeXPos, policeYPos - 20, gridW, gridH + 25);
if (whoWin === 'police') {
fill(255, 0, 0);
textSize(30)
text("恭喜你抓住小偷啦,警察勝利嘍!", col * gridW / 6, 60);
}
if (whoWin === 'thief') {
fill(255, 0, 0);
textSize(30)
text("小樣,還想抓住我,哈哈哈", col * gridW / 5, 60);
}
//.....
// 顯示當(dāng)前玩家數(shù)據(jù)
thiefVoiceValNode.innerText = thiefProgressNode.value = policeDir / policeStepLen;
policeValNode.innerText = policeProgressNode.value = Math.abs(policeDir);
}
2. 小偷的移動邏輯
小偷的移動邏輯是每隔一定時間在 X 軸方向上以固定的步長移動,步長由stepLen常量控制,在到達(dá)棋盤邊界if (thiefXPos >= (col + 1) * gridW)改變運動方向dir = -stepLen,并在 Y 軸方向上增加一個棋盤格子高thiefYPos += gridH。小偷的移動步長可以通過調(diào)整 stepLen 的值來改變,值越大,小偷移動的越快。
3. 警察的移動邏輯
警察的移動邏輯比較復(fù)雜,需要根據(jù)下面代碼中getVoiceSize()獲取的音量值來動態(tài)調(diào)整警察的速度policeDir = Math.floor(voiceSize * 100) / 10 * policeStepLen。通過 setInterval() 函數(shù)來定時改變警察的橫坐標(biāo)的位置policeXPos += policeDir;,并根據(jù)警察的位置和小偷的位置來判斷是否勝利。
//獲取音量大小
async function getVoiceSize() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true } });// echoCancellation: true以減少回音和噪聲的影響
const audioContext = new AudioContext();
const source = audioContext.createMediaStreamSource(stream);
const processor = audioContext.createScriptProcessor(1024, 1, 1);
source.connect(processor);
processor.connect(audioContext.destination);
processor.onaudioprocess = function (event) {
const buffer = event.inputBuffer.getChannelData(0);
const sum = buffer.reduce((acc, val) => acc + val ** 2, 0);//計算音量大小
voiceSize = Math.sqrt(sum / buffer.length);
};
} catch (err) {
console.log('getUserMedia error: ', err);
}
}
4. 勝利邏輯
在開始游戲點擊事件的回調(diào)函數(shù)中,判斷游戲勝利的條件并播放勝利音樂。在 draw()函數(shù)中通過whoWin來判斷游戲勝利的條件繪制頁面上的文案。當(dāng)游戲勝利時,頁面上繪制對應(yīng)文案并播放勝利音樂。
//開始游戲事件
startBtn.addEventListener('click', () => {
//....省略其它邏輯代碼
//賊勝利邏輯
if (thiefYPos >= row * gridH) {
winLogicFunc('thief')
}
//警察勝利邏輯
if (policeYPos === thiefYPos && (policeStepLen > 0 && policeXPos >= thiefXPos || policeStepLen < 0 && Math.abs(policeXPos) <= thiefXPos)) {
winLogicFunc('police')
}
}, moveTime);
});
五、待實現(xiàn)的創(chuàng)意玩法
為了增加游戲的趣味性,可以考慮增加更多的道具和障礙物。
- 道具:可以設(shè)置一些可以幫助小偷逃脫警察追捕的道具,例如加速藥水、隱身衣、煙霧彈等。這些道具可以增加小偷的移動速度、暫時讓小偷隱身躲避警察的視線、或者釋放煙霧干擾警察的追蹤。
- 障礙物,可以設(shè)置一些可以對小偷和警察造成影響的障礙物,例如路障、警車、人群等。這些障礙物可以阻礙小偷的逃脫,或者讓警察無法前進(jìn),從而給小偷爭取時間。
除了增加道具和障礙物,我們還可以增加多種不同的游戲模式,例如多人模式、計時模式等等。
- 多人模式:可以讓兩個玩家分別扮演小偷和警察,進(jìn)行對抗。
- 計時模式中,可以設(shè)置一個時間限制,讓警察在規(guī)定時間內(nèi)抓住小偷。
還有可以美化頁面還有音效、動效等,讓游戲更加豐富多彩,讓玩家有更多的選擇和挑戰(zhàn)。
六、代碼實現(xiàn)
為了避免文章過于臃腫,這里只放js的實現(xiàn)代碼,對于頁面布局代碼可以到我的github上去閱讀代碼,代碼里都有豐富的注釋,相信大家可以快速讀懂代碼,如果我用原生canvas開發(fā),估計工作量會大很多。不過,代碼在封裝方面還有待提高,除此之外,玩法也還有更多創(chuàng)意有待實現(xiàn)。
各位如果更好玩的實現(xiàn)玩法,不妨把代碼fork過去實現(xiàn),記得評論區(qū)告訴我,我期待你的實現(xiàn)。
1. js代碼
const gridW = 30; //棋盤格子寬
const gridH = 30; //棋盤格子高
const row = 15; //棋盤行數(shù)
const col = 20; //棋盤列數(shù)
let sone = null;//勝利音樂
let whoWin = ''; //誰勝利可選值'police|thief'
let interval = null;//循環(huán)句柄
const moveTime = 16;//循環(huán)時間
let thiefImg = null;//小偷圖片
let thiefXPos = 40;//小偷橫坐標(biāo)
let thiefYPos = 0;//小偷縱坐標(biāo)
const stepLen = 2; //小偷移動步長(速度)
let policeImg = null;//警察圖片
let policeXPos = 0;//警察橫坐標(biāo)
let policeYPos = 0;//警察縱坐標(biāo)
let policeStepLen = 2;//警察移動步長基數(shù)
let policeDir = 0; //警察動態(tài)移動步長(速度)
let voiceSize = 0;
//獲取DOM節(jié)點
const thiefProgressNode = document.getElementById('progress');
const thiefVoiceValNode = document.getElementById('voiceVal');
const policeProgressNode = document.getElementById('policeProgress');
const policeValNode = document.getElementById('policeVal');
const startBtn = document.getElementById('start-btn');
const restartBtn = document.getElementById('restart-btn');
const thiefWinGifNode = document.getElementById('thiefGif'); //小偷勝利動圖
const policeWinGifNode = document.getElementById('policeGif'); //警察勝利動圖
//預(yù)加載圖片
function preload() {
song = loadSound('./resources/勝利配樂.mp3');
thiefImg = loadImage('./resources/thief.png');
policeImg = loadImage('./resources/police.png');
}
//初次調(diào)用
function setup() {
createCanvas(col * gridW + 60, row * gridH).parent('gameCanvas')
document.querySelector('.page').style.cssText = `width:${(col + 2) * gridW}px`
}
//循環(huán)體
function draw() {
noStroke();
background('#fff');
// 繪制格子盤
for (let i = 1; i < col + 1; i++) {
for (let j = 0; j < row; j++) {
fill('pink')
rect(i * gridW, j * gridH, gridW, gridH - 1); // 將高度改為gridH-1
}
}
// 繪制小偷和警察
image(thiefImg, thiefXPos, thiefYPos - 5, gridW, gridH + 5);
image(policeImg, policeXPos, policeYPos - 20, gridW, gridH + 25);
if (whoWin === 'police') {
fill(255, 0, 0);
textSize(30)
text("恭喜你抓住小偷啦,警察勝利嘍!", col * gridW / 6, 60);
}
if (whoWin === 'thief') {
fill(255, 0, 0);
textSize(30)
text("小樣,還想抓住我,哈哈哈", col * gridW / 5, 60);
}
//繪制通道
for (let i = 1; i < row; i++) {
fill('pink');
(i % 2 === 0) ? rect((col + 1) * gridW, gridH * i - 1, gridW, 3) : rect(0, gridH * i - 1, gridW, 3);
}
//繪制出口文字
fill(0, 0, 0);
textSize(12)
text("出口", (col + 1) * gridW + 2, row * gridH - 10);
// 顯示當(dāng)前玩家數(shù)據(jù)
thiefVoiceValNode.innerText = thiefProgressNode.value = policeDir / policeStepLen;
policeValNode.innerText = policeProgressNode.value = Math.abs(policeDir);
}
//獲取音量大小
async function getVoiceSize() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true } });// echoCancellation: true以減少回音和噪聲的影響
const audioContext = new AudioContext();
const source = audioContext.createMediaStreamSource(stream);
const processor = audioContext.createScriptProcessor(1024, 1, 1);
source.connect(processor);
processor.connect(audioContext.destination);
processor.onaudioprocess = function (event) {
const buffer = event.inputBuffer.getChannelData(0);
const sum = buffer.reduce((acc, val) => acc + val ** 2, 0);//計算音量大小
voiceSize = Math.sqrt(sum / buffer.length);
};
} catch (err) {
console.log('getUserMedia error: ', err);
}
}
//勝利邏輯函數(shù)
function winLogicFunc(who) {
if (who == 'thief' || who == 'police') {
clearInterval(interval);
song.play();
whoWin = who
noLoop();
(who === 'thief' ? thiefWinGifNode : policeWinGifNode).style.visibility = 'visible';
}
}
//開始游戲事件
startBtn.addEventListener('click', () => {
getVoiceSize()
startBtn.disabled = true;
let dir = stepLen;
// policeDir = Math.floor(voiceSize * 100) / 10 * policeStepLen;
//小偷、警察運動循環(huán)體
interval = setInterval(() => {
policeDir = Math.floor(voiceSize * 100) / 10 * policeStepLen
thiefXPos += dir;
policeXPos += policeDir;
//小偷的改變方向邏輯
if (thiefXPos >= (col + 1) * gridW) {
thiefXPos = (col + 1) * gridW - stepLen
dir = -stepLen
thiefYPos += gridH
} else if (thiefXPos < 0) {
thiefXPos = -stepLen
dir = stepLen
thiefYPos += gridH
}
//警察的改變方向邏輯
if (policeXPos >= (col + 1) * gridW) {
policeXPos = (col + 1) * gridW - stepLen
policeStepLen = -policeStepLen
policeYPos += gridH
} else if (policeXPos < 0) {
policeXPos = 1
policeStepLen = -policeStepLen
policeYPos += gridH
}
//賊勝利邏輯
if (thiefYPos >= row * gridH) {
winLogicFunc('thief')
}
//警察勝利邏輯
if (policeYPos === thiefYPos && (policeStepLen > 0 && policeXPos >= thiefXPos || policeStepLen < 0 && Math.abs(policeXPos) <= thiefXPos)) {
winLogicFunc('police')
}
}, moveTime);
});
//重新開始游戲
restartBtn.addEventListener('click', () => {
thiefWinGifNode.style.visibility = 'hidden';
policeWinGifNode.style.visibility = 'hidden';
startBtn.disabled = false;
// 重新開始游戲
clearInterval(interval);
thiefXPos = 40;
thiefYPos = 0;
policeXPos = 0;
policeYPos = 0;
whoWin = '';
loop();
});
2.頁面布局和樣式
頁面的布局結(jié)構(gòu)相對簡單,大家可以到github頁面上去閱讀源碼。頁面布局每個人的審美不同,實現(xiàn)的各有千秋,大家著重理js核心代碼,頁面實現(xiàn)那是三下五除二的。 代碼地址:forrestyuan/catchThief警察抓小偷(github.com)
七、寫在最后
通過這個小游戲的開發(fā),我們可以了解到如何使用 p5.js 庫來開發(fā)交互式游戲,包括繪制游戲界面和元素、實現(xiàn)游戲的邏輯和交互、預(yù)加載音樂和圖片資源的能力和便捷性。我發(fā)現(xiàn)開發(fā)用原生canvas開發(fā)突然不香了??,好吧,我會用原生canvas開發(fā)更鍛煉人這個點來說服自己。
以上就是p5.js實現(xiàn)聲音控制警察抓小偷游戲示例解析的詳細(xì)內(nèi)容,更多關(guān)于p5.js 警察抓小偷游戲的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JS中的算法與數(shù)據(jù)結(jié)構(gòu)之二叉查找樹(Binary Sort Tree)實例詳解
這篇文章主要介紹了JS中的算法與數(shù)據(jù)結(jié)構(gòu)之二叉查找樹(Binary Sort Tree),結(jié)合實例形式詳細(xì)分析了二叉查找樹(Binary Sort Tree)的原理、定義、遍歷、查找、插入、刪除等常見操作技巧,需要的朋友可以參考下2019-08-08
javascript與CSS復(fù)習(xí)(《精通javascript》)
js和css結(jié)合來產(chǎn)生醒目的交互效果,我們可以快速的訪問元素自身的樣式屬性2010-06-06

