使用 P5.js:實現遊戲邏輯
這是 P5.js(從此處為“P5”)的三部分系列中的第二部分 - 一個創造性的編碼庫,使使用 Canvas API 變得更加容易。在第一部分中,我們介紹瞭如何在屏幕上繪製元素並對鍵盤和鼠標輸入做出反應。
今天,我們將利用這些理論知識並構建一些您在創建遊戲時可能需要的功能。然後,在下週的最後一部分,我們將使用 Deepgram 為我們的遊戲添加語音功能。
碰撞檢測
您在 P5 草圖中繪製的每個元素都有特定的位置和大小。遊戲中的碰撞檢測可讓您知道一個元素何時與另一個元素重疊或接觸牆壁等位置。這通常用於避免用戶穿過牆壁或地板或“拾取”食物或心臟等物品。
假設您(“玩家”)和另一個實體(“拾取”)之間進行了碰撞檢查,碰撞檢測依賴於四個條件檢查:
- 您的 x 位置是否大於拾音器最左側的 x 位置?
- 您的 x 位置是否小於拾音器最右側的 x 位置?
- 您的 y 位置是否大於拾取器的最頂部 y 位置?
- 您的 y 位置是否小於拾音器的最底部 y 位置?
讓我們開始把它付諸實踐。創建一個index.html
文件,在代碼編輯器中打開它,然後添加以下內容:
<!DOCTYPE html>
<html>
<head></head>
<body>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/p5.js"></script>
<script>
const pickupX = 200
const pickupY = 50
const pickupSize = 100
function setup() {
createCanvas(500, 200)
}
function draw() {
background(100)
const collisionX = mouseX>pickupX && mouseX<pickupX+pickupSize
const collisionY = mouseY>pickupY && mouseY<pickupY+pickupSize
if(collisionX && collisionY) fill('green')
else fill('red')
square(pickupX, pickupY, pickupSize)
}
</script>
</body>
</html>
要查看您的草圖運行,只需雙擊 index.html
文件在您的文件資源管理器中,它將在您的默認瀏覽器中打開。要在保存代碼後查看新更改,請刷新瀏覽器。
如果播放器大於單個像素點,則需要通過播放器的大小來抵消條件。嘗試這個:
const pickupX = 225
const pickupY = 75
const pickupSize = 50
const playerSize = 50
function setup() {
createCanvas(500, 200)
}
function draw() {
background(100)
fill('black')
square(pickupX, pickupY, pickupSize)
const collisionX = mouseX>pickupX-pickupSize && mouseX<pickupX+pickupSize
const collisionY = mouseY>pickupY-pickupSize && mouseY<pickupY+pickupSize
if(collisionX && collisionY) fill('green')
else fill('white')
square(mouseX, mouseY, playerSize)
}
如果您想了解有關碰撞檢測的更多信息,請觀看 Dan Shiffman 製作的這段精彩視頻。
示例:擋牆
P5 提供的 width
和 height
變量總是設置為 createCanvas()
中提供的畫布值 .您可以將它們與上面的碰撞檢測條件一起使用,以確保用戶無法在畫布之外導航。
在上週的帖子中擴展我們的鍵盤用戶輸入介紹,試試這個:
let playerX = 20
let playerY = 20
const playerSize = 10
function setup() {
createCanvas(500, 200)
}
function draw() {
background(100)
if(keyIsPressed) {
if(key == 'ArrowLeft') playerX -= 1
if(key == 'ArrowRight') playerX += 1
if(key == 'ArrowUp') playerY -= 1
if(key == 'ArrowDown') playerY += 1
}
// Not allowing out-of-bounds values
if(playerX < 0) playerX = 0
if(playerX > width - playerSize) playerX = width - playerSize
if(playerY < 0) playerY = 0
if(playerY > height - playerSize) playerY = height - playerSize
square(playerX, playerY, playerSize)
}
如果玩家嘗試設置 playerX
或 playerY
在允許的範圍之外,它們被設置在邊界上。這意味著玩家將看到他們的方塊停止移動。
實體管理
遊戲通常有許多實體:玩家、敵人和物品。同一類別的實體可能具有相似的邏輯,但需要保持自己的狀態。在 P5 草圖中,通常使用 JavaScript 類進行遊戲實體管理。類為對象提供了藍圖。它們有自己的屬性,包括數據和函數(在類中稱為“方法”)。試試這個代碼,然後我們將通過它:
const bubbles = []
function setup() {
createCanvas(500, 200)
for(let i = 0; i < 100; i++) {
bubbles.push(new Bubble(250, 100))
}
}
function draw() {
background(100)
for(let bubble of bubbles) {
bubble.move()
bubble.display()
}
}
class Bubble {
constructor(x, y) {
this.x = x
this.y = y
this.xOff = random(0, 1000)
this.yOff = random(0, 1000)
}
move() {
this.xOff += 0.01
this.yOff += 0.01
this.x = noise(this.xOff) * width
this.y = noise(this.yOff) * height
}
display() {
circle(this.x, this.y, 5)
}
}
從底部以 Bubble
開始 班級。當一個新的類實例被創建時,它需要一個起始的 x 和 y 值,這在類內部作為稱為 this.x
的成員屬性提供 和 this.y
.還創建了另外兩個成員屬性 - xOff
(x 偏移量) 和 yOff
(y 偏移)。稍後再詳細介紹。
這個類有兩個方法 - 你可以隨意命名方法,但是 move
和 display
在 P5 草圖中很常見。
move()
方法使用 P5 提供的 noise()
函數返回 Perlin 噪聲序列中的值。 Perlin 噪聲生成一個隨機值,該值以更自然的順序存在 - 通過非常輕微地修改傳遞給 noise()
的值 ,氣泡看起來遵循“路徑”。 xOff
中的小改動 和 yOff
用於平滑移動氣泡。 Perlin 噪音令人著迷,我鼓勵您閱讀有關 noise()
的更多信息 .
display()
方法在 this.x
中存儲的新值處繪製一個圓圈 和 this.y
.
在 setup()
期間 , 100 Bubble
實例的起始位置為 (250, 100)
並存儲在 bubbles
大批。每 draw()
, 每個 bubble
有它的 move()
和 display()
方法運行。
下一個示例結合了碰撞檢測和實體管理:
const bubbles = []
function setup() {
createCanvas(500, 200)
frameRate(10)
for(let i = 0; i < 10; i++) {
bubbles.push(new Bubble(250, 100))
}
}
function draw() {
background(100)
for(let bubble of bubbles) {
bubble.move()
bubble.checkIfTouched()
bubble.display()
}
}
class Bubble {
constructor(x, y) {
this.x = x
this.y = y
this.xOff = random(0, 1000)
this.yOff = random(0, 1000)
this.radius = 10
this.touched = false
}
move() {
this.xOff += 0.01
this.yOff += 0.01
this.x = noise(this.xOff) * width
this.y = noise(this.yOff) * height
}
checkIfTouched() {
const d = dist(mouseX, mouseY, this.x, this.y)
if(d < this.radius) {
this.touched = true
}
}
display() {
if(this.touched) fill('green')
else fill('white')
circle(this.x, this.y, this.radius * 2)
}
}
有什麼變化?
frameRate(10)
setup()
中的函數 大大減慢了draw()
的速度 每秒運行大約 60 次到 10 次。這樣做只是為了讓這款遊戲可玩。Bubble
只有十個實例 創建而不是 100。Bubble
中現在包含兩個新屬性 -radius
和touched
.radius
用於碰撞檢測和繪製氣泡時。- 一個新的
checkifTouched()
Bubble
中包含方法 .此方法確定距離(dist()
) 在鼠標位置和氣泡中心 (x, y) 之間。如果它小於半徑,你就知道發生了碰撞並設置this.touched
到true
. - 觸摸後氣泡的顏色會發生變化。
checkIfTouched()
draw()
中的每個氣泡都會調用該方法 .
保持得分
目前,每個泡泡目前都在跟踪自己的狀態,但沒有全球性的跡象表明球員的得分情況。這可以通過全局變量來實現。請按以下步驟操作:
- 添加一個名為
score
的全局變量 值為0
. Bubble.checkIfTouched()
內部 方法,在this.touched
之前 設置為true
, 檢查是否this.touched
仍然是假的,然後也增加score
.- 在
draw()
函數,使用fill('white')
將顏色設置為白色 ,然後顯示score
通過使用text()
.
如果您不記得 text()
的參數 我們在上一篇文章中提到過,text()
接受三個參數 - 要顯示的文本和 (x,y) 坐標。
對於第 2 步,需要額外檢查以停止 score
增加不止一次。如果成功,你的草圖應該是這樣的:
開始、獲勝和失敗
大多數遊戲都有許多狀態——加載時的登錄頁面、遊戲本身和殘局。這種狀態通常可以保持在全局範圍內,並且在 draw()
中運行的代碼 可以因此而改變。留下你的 Bubble
類不變,試試這個實現遊戲狀態管理:
const bubbles = []
let score = 0
let win = false
function setup() {
createCanvas(500, 200)
frameRate(10)
for(let i = 0; i < 3; i++) {
bubbles.push(new Bubble(250, 100))
}
}
function draw() {
background(100)
if(score >= 3) win = true
if(!win) {
for(let bubble of bubbles) {
bubble.move()
bubble.checkIfTouched()
bubble.display()
}
fill('white')
text(score, 10, 20)
} else {
textSize(36)
textAlign(CENTER)
text('You Win!', width/2, height/2-16)
}
}
win
變量以 false 開頭,當 score
達到三個或更多,遊戲邏輯停止運行,並顯示文字“你贏了!”將改為顯示。
這是一個簡單的示例,但可以採用相同的方法來實現更多遊戲狀態。
總結
連同本系列的第一篇文章,我希望您擁有使用這些遊戲邏輯實現使用 P5.js 構建有趣遊戲所需的工具。為了獲得更多靈感,這裡有一些我最喜歡的 P5 示例:
- 流場中的粒子 - 此示例以有助於進一步說明其工作原理的方式使用 perlin 噪聲。
- 蛇遊戲
- 完整的 2D 平台遊戲
- 萬花筒繪圖程序
- 帶有動畫互動明星的互動藝術品
- 生成式繪畫程序
- John Conway 的生命遊戲元胞自動機
- L-Systems 生成藝術
- 應用重力等逼真的力
- 彩虹肉丸著色器 - 這涉及編寫著色器,這是一個高級主題,但看起來非常酷。
下週在本系列的第三部分也是最後一部分中,我們將介紹如何將語音集成到您的 P5 草圖中。在此之前,如果您有任何問題或想法,請隨時在 Twitter 上@DeepgramDevs 與我們聯繫。