---
tags: Learning Resources
---
# P5.js
線上連結:<https://wangshuan.github.io/Creative-Coding-Collections/>
## 初體驗
註冊 [OpenProcessing](https://openprocessing.org/) 然後點擊右上角的『Create a Sketch』
點擊播放鍵可開始繪製、點擊`</>`可查看/編輯原始碼
初始建立時原始碼如下:
```javascript=
function setup() { // 初始化
// 建立畫布
createCanvas(windowWidth, windowHeight);
// windowWidth 是瀏覽器寬度, windowHeight 是瀏覽器高度
background(100);
// 設置畫布背景色 範圍 0~255(黑到白) 這邊預設是 100 即為灰色
}
function draw() { // 繪製
circle(mouseX, mouseY, 20);
// circle 表示圓形
// mouseX 是鼠標離視窗左側的位置, mouseY 是鼠標離視窗上方的位置
// 20 是指圓形的寬高(單位像素)
}
```
> 常用的程式碼可進入此頁查找: https://p5js.org/zh-Hans/reference/
> 範例可參考: https://p5js.org/zh-Hans/examples/
> 延伸使用的 library 可從此頁查找: https://p5js.org/zh-Hans/libraries/
## 常用程式碼說明
### fill 填色
在繪製的 draw() 函數中,新增`fill()`,
下方所有繪製的圖形之顏色就會使用 fill 中設定的顏色進行填充。
請參考下方程式碼:
```javascript=
function draw() {
fill(0) // 填充黑色
circle(mouseX, mouseY, 20);
}
```
這段程式碼執行後,所繪製出來的圓形就會被填充黑色
`fill()` 可傳入:
- RGB => `fill(0,255,0)` or `fill('rgb(0,255,0)')` or `fill('rgb(100%,0%,10%)')`
- RGBA => `fill('rgba(0,255,0, 0.25)')` or `fill('rgba(100%,0%,100%,0.5)')`
- 灰階 => `fill(100)`
- 十六進制色碼 => `fill('#70d44b')` or `fill('#ddd')`
- 顏色名稱 => `fill('red')` or `fill('gold')`
- Color Object => `fill(color(0, 0, 255))`
> 假設程式碼中 沒有設定到 `fill()` 則預設是填充白色+黑色外框線
### stroke 外框線
在繪製的 draw() 函數中,新增`stroke()`,
下方所有繪製的圖形之邊框色就會使用 stroke 中設定的顏色進行填充。
請參考下方程式碼:
```javascript=
function draw() {
stroke(255) // 邊框白色
circle(mouseX, mouseY, 20);
}
```
這段程式碼執行後,所繪製出來的圓形就會有白色邊框
另外還可用 `strokeWeight()` 設置邊框的粗細,裡面傳入數字(單位像素)
### 常見的變數
1. `frameCount` 當前繪製到什麼次數,會持續一直++
2. `frameRate` 每秒鐘繪製多少次,傳入1每秒就繪製1次
3. `mouseX` 鼠標離視窗左側的位置
4. `mouseY` 鼠標離視窗上方的位置
5. `mouseIsPressed` 滑鼠鍵是否按下中,放開滑鼠鍵就停止了
這樣就可以用這些變數,讓程式碼中傳入的大小、色號產生變化
比如我希望產生的圓形位置是跟著滑鼠位置生成:
```
circle(mouseX,mouseY,20)
```
或是我希望圓形的半徑根據現在繪製的次數變化:
```
circle(mouseX,mouseY,frameCount)
```
又或是我希望判斷滑鼠是否按住不放,變換形狀:
```
if(mouseIsPressed){
circle(mouseX,mouseY,frameCount)
}else{
rect(mouseX,mouseY,frameCount)
}
```
## P5.js Color Object
1. 顏色分成兩種,一種是 RGB 另一種是 HSB,可用 colorMode(RGB)/colorMode(HSB) 來切換,預設是 RGB 模式
2. 可以通過 colorMode() 的第二個參數開始設定各項目的最大值,比如 colorMode(RGB, 255, 255, 255, 1) => R/G/B 各為 0~255,透明度為 0~1
3. 設定好顏色模式後,可通過 color(R,G,B,A) 來使用顏色 => fill(color(100,100,100,0.5))
4. 產生隨機顏色建議使用網站 => https://coolors.co/ 生成顏色後可直接複製 url 得到十六進制色號
## 常用的小技巧
1. 通過 https://coolors.co/ 複製 url 得到十六進制色號轉換成陣列的技巧:
`"01295f-437f97-849324-ffb30f-fd151b-fff".split("-").map(c=>"#"+c)`
2. push()、pop()組合技:
當遇到旋轉畫布 rotate() 或是改變原點位置 translate() 時,
因會影響到整個畫布的設定,此時就可以透過 push() 與 pop() 將操作的部分設置在一個區間內,
這樣每次執行就可以恢復初始設定,再重新進行旋轉畫布 rotate() 或是改變原點位置 translate() 而不影響到其他東西了
舉例來說,我想要隨機生成多個方形在畫布任意位置上,且生成的每個方形會在同位置上進行旋轉,
程式碼片段就如下:
```
// setup 中 會先有一段城市去隨機建立物件塞到 arr 中
let arr = []
function setup() {
createCanvas(windowWidth, windowHeight);
background(100);
for(let i=0;i<10;i++){
arr.push({
x:random(width),
y:random(height),
color:random(255)
})
}
}
// draw 中遇到使用 rotate() 與 translate() 時,就用 push() pop() 包裹住即可
function draw() {
for(let i=0;i<arr.length;i++){
push()
translate(arr[i].x,arr[i].y)
fill(arr[i].color)
for(let j=0;j<5;j++){
rotate(PI*2/5)
rect(0,0,30)
}
pop()
}
}
```
3. 產生會動的東西時如果超過畫面範圍就讓他改變移動的方向:
```
// 判斷 y 的部分是否超過可視範圍,是的話將移動速度改為相反值
// 比如原先 obj.v = 1 就會一直讓物體往下方移動,當 obj.v 改為 -1 就會讓物體變成往上移動了
if(obj.y > height){
obj.v = -abs(obj.v)
}
```
>這邊舉例是用 y,實際上 x 也是一樣的邏輯。
4. 用 dist 計算兩點之間的位置,可以判斷 A 點到 B 點之間的距離,進而實現當 A 碰到 B 時就發生某事的設定,可參考此作品:
https://openprocessing.org/sketch/1784428
```
for(let i=0;i<flowers.length;i++){
drawFlower(flowers[i])
for(let j=0;j<bees.length;j++){
drawBee(bees[j])
if(dist(bees[j].x,bees[j].y,flowers[i].x,flowers[i].y)<50){ // 如果距離小於 50 則開花(讓花的尺寸趨近90)
flowers[i].size = lerp(flowers[i].size,90,0.5)
}else{ // 如果距離大於 50 則變回不開花(讓花的尺寸趨近10)
flowers[i].size = lerp(flowers[i].size,10,0.01)
}
}
}
```
>這邊的 lerp 也是一個小技巧,可以讓某數字往某數字逐漸趨近
>lerp(要逐漸變化的數值,要往哪個數字趨近,趨近的變化量為何)
>要逐漸變化的數值=>flowers[i].size
>要往哪個數字趨近=>90
>趨近的變化量為何(0~1之間)=>0.5
5. noise() 隨機生成一個亂數,參考作品:https://openprocessing.org/sketch/1785160
6. sin() 與 cos() 產生連續波形。
7. for loop 的簡寫(適用於只需獲取當下的物件,不需要獲取 index 時使用):
原本寫法:
```
for(let i=0;i<balls.length;i++){
let ball = balls[i]
print(ball.x)
}
```
簡寫:
```
for(let ball of balls){
print(ball.x)
}
```
8. angleMode(DEGREES) 將 rotate() 傳入的參數從 3.14 或 PI 改為 deg(角度)
## P5.js DOM
開啟 SKETCH 設定,往下找到 LIBRARIES ,點擊 SHOW ALL,找到 UI Helpers 區塊,將裡面的 p5.dom 開啟即可使用。
參考作品:
https://openprocessing.org/sketch/1785861
https://openprocessing.org/sketch/1785865
https://openprocessing.org/sketch/1787009
### DOM 的各種用法
1. input 的 placeholder 可以在建立時通過參數設定方式帶入:
`createInput().attribute('placeholder', 'Enter word to submit!');`
2. button 建立時必須帶入按鈕的文字:
`createButton('Click Me!')`
3. 利用 position(x,y) 可以設定建立的 DOM 位置,否則預設建立出來的位置會在靠最左邊、畫布高度之外的第一個位置
=> 假設畫布高度 500px 生成 DOM 的位置就會是 (0,500)
4. 監聽輸入框輸入的時候執行事件:
```
let oInput = createInput()
oInput.input(函數名稱)
```
5. 監聽按鈕被點擊的時候執行事件:
```
let oBtn = createButton('Click Me!')
oBtn.mousePressed(函數名稱)
```
6. 建立色彩選擇器的方式:
```
createColorPicker()
```
>可以傳入顏色當預設顏色 => createColorPicker("red")
>如不傳入預設是黑
7. 建立滑桿的方式:
```
createSlider(最小值,最大值,預設值)
```
8. 建立單選框的方式:
```
oRadio = createRadio(); // 建立
oRadio.position(0,0) // 設置位置
oRadio.option('normal'); // 新增選項
oRadio.option('rotate'); // 新增選項
oRadio.option('scale'); // 新增選項
oRadio.style('width', '75px'); // 設定寬度讓他不要往右排列而是往下
oRadio.style('background-color', 'white'); // 設定背景色才不會讓文字顏色被黑背景吃掉
let val = oRadio.value(); // 獲取選項名字
text(val, 0,0); // 顯示選項名字在圈圈旁邊
oRadio.selected('normal'); // 設置初始選中的值
```
9. 獲取 DOM 的值的方式:
```
oInput.value() // 獲取輸入框中的字
oRadio.value() // 獲取選中的項目
oColor.value() // 獲取選中的顏色
```
## 建制工廠函數 Class
參考作品:https://openprocessing.org/sketch/1787066
```
class Ball{ // 建立一個 ball 的 class
constructor(args){ // 預設執行
this.r=args.r // 設定半徑
this.p = args.p // 設定 x,y
this.v = { // 設定速度
x: random(-1,1),
y: random(-1,1)
}
this.color = random(["#9800fd","#1554dc","#00ff00"]) // 設定顏色
}
draw(){ // 呼叫 ball.draw() 執行
fill(this.color)
circle(this.p.x,this.p.y,this.r)
}
update(){ // 呼叫 ball.update() 執行
this.p.x+=this.v.x
this.p.y+=this.v.y
}
}
```
## 好用的 P5.js 方法
1. createVector(x,y) => 創建向量
```
this.p = createVector(random(width),random(height))
```
2. p5.Vector.random2D() => 產生 -1~1 之間的隨機數
```
this.v = p5.Vector.random2D()
```
3. add() => 向量加法
```
this.p.add(this.v)
```
4. mult() => 向量乘法
```
this.v.mult(0.99)
```
5. sub() => 向量減法
```
this.p.sub(this.v)
```
6. copy() => 拷貝向量來使用,以防更動到原本的向量
```
this.p2 = this.p.copy()
```
7. setMag() => 將目前的向量重新設定成固定的長度
```
let p = createVector(random(width),random(height))
print(p) // x: 543.072975561772 y: 130.60968698503854
p.setMag(10)
print(p) // x: 9.722766154580002 y: 2.3383366531262375
```
8. limit() => 設定向量的上限值
```
// 取得點到滑鼠位置的距離,然後限制結果在5以下,就算超過5也會變成5
createVector(mouseX, mouseY).sub(this.p).limit(5)
```
## 圖片的使用
首先建立新的 SKETCH
然後直接先存擋
接著開啟右側欄位的『FILES』即可上傳圖片檔案
上傳後可通過以下程式碼使用圖片:
```
let fishImg;
function preload() {
fishImg = loadImage("fish.png")
}
```
* preload() 與 setup() 函式同層
* loadImage("FILES中的檔案完整名稱或是一個外部的圖片連結")
將圖片放置到滑鼠位置:
```
function draw() {
imageMode(CENTER)
push()
image(fishImg,mouseX,mouseY,300,300)
pop()
}
```
* imageMode(CENTER) 讓圖片生成位置是在正中心
* image(fishImg,mouseX,mouseY,寬度大小,高度大小)
抓取圖片像素位置的顏色:
```
let fishColor = fishImg.get(mouseX,mouseY)
fill(fishColor)
rect(mouseX,mouseY,20)
```
* fishColor 會是一個陣列 `[r,g,b,a]`
* 參考作品:https://openprocessing.org/sketch/1788731
## 音樂的使用
首先建立新的 SKETCH
然後直接先存擋
接著開啟右側欄位的『FILES』即可上傳 mp3 檔案
最後再開啟右側欄位的 SKETCH 將最下方的 p5.sound 開啟
上傳 mp3 檔案後可通過下方程式碼啟用:
```
let sound1, sound2, sound3
function preload() {
sound1 = loadSound("sound1.mp3")
sound2 = loadSound("sound2.mp3")
sound3 = loadSound("sound3.mp3")
}
```
通過點擊事件觸發播放音樂(`sound.play()`):
```
function mousePressed() {
if(mouseX<width/3){
sound1.play()
}else if(mouseX<width/3*2){
sound2.play()
}else{
sound3.play()
}
}
```
透過點擊事件獲取點擊當下的 frameCount 計算並觸發動畫:
```
let sound1Ts = 0;
let sound2Ts = 0;
let sound3Ts = 0;
function mousePressed() {
if(mouseX<width/3){
sound1Ts = frameCount
sound1.play()
}else if(mouseX<width/3*2){
sound2Ts = frameCount
sound2.play()
}else{
sound3Ts = frameCount
sound3.play()
}
}
function draw() {
push()
let r1 = map(frameCount-sound1Ts,0,15,100,0,true)
translate(width/3/2,height/4*3.5)
circle(0,0,50)
fill("orange")
circle(0,0,r1)
pop()
push()
let r2 = map(frameCount-sound2Ts,0,15,100,0,true)
translate(width/3/2+width/3,height/4*3.5)
circle(0,0,50)
fill("orange")
circle(0,0,r2)
pop()
push()
let r3 = map(frameCount-sound3Ts,0,15,100,0,true)
translate(width/3/2+width/3*2,height/4*3.5)
circle(0,0,50)
fill("orange")
circle(0,0,r3)
pop()
}
```
## 影片的使用
開啟新的 SKETCH 後
從右側設定中開啟『p5.dom』
然後直接先存擋
接著再到 FILES 中上傳影片
>免費影片可到 pixabay 上找
影片較類似 dom 的引入方式:
```
let vid;
function setup() {
createCanvas(windowWidth, windowHeight);
background(255);
vid = createVideo("flower.mp4")
vid.position(0,0)
}
```
針對影片的控制則建議另外封裝一個函數:
```
function videoLoad() {
vid.loop()
vid.volume = 0
vid.play()
}
// 然後在 setup 中改寫一下 createVideo("flower.mp4") => createVideo("flower.mp4",videoLoad)
```
如果想要取得影片中的每一個畫面則可以在 draw 中這樣:
```
function draw() {
background(100)
image(vid,0,0)
}
```
## 麥克風的使用
先開啟一個新的 SKETCH,
然後開啟右側欄位的 SKETCH ,將最下方的 p5.sound 開啟
使用方式:
```
function setup() {
createCanvas(windowWidth, windowHeight);
background(0)
mic = new p5.AudioIn()
mic.start()
}
```
通過聲音控制繪製的圖樣尺寸:
```
function draw() {
let micLv = mic.getLevel() // 獲取音量大小,是一個 0~1 的值
circle(width/2,height/2,100+micLv*1000)
}
```
## 音頻的使用
先開啟一個新的 SKETCH,
然後開啟右側欄位的 SKETCH ,將最下方的 p5.sound 開啟
使用時一樣需載入麥克風,接著另外還要載入『FFT』,如下:
```
function setup() {
createCanvas(windowWidth, windowHeight);
background(0)
mic = new p5.AudioIn() // 載入麥克風
mic.start()
fft = new p5.FFT() // 載入 FFT
fft.setInput(mic)
}
```
繪製圖樣時可用的小技巧:
```
let spectrum = fft.analyze(); // 取得聲音頻率,是一個 0~255 的陣列,長度共 1024
let spectralCentroid = fft.getCentroid(); // 可取得 analyze 陣列的質量中心頻率 可能是從零~萬的任一個數字
```
獲取到聲音頻率後就可以透過類似以下的方式繪製圖樣:
```
for (i = 0; i < spectrum.length/1.5; i+=5) {
rect(i,height/2-spectrum[i],5)
}
```
或這樣繪製:
```
beginShape()
vertex(0,height/2)
for (i = 0; i < spectrum.length/1.5; i+=5) {
vertex(i,height/2-spectrum[i])
}
vertex(width,height/2)
endShape()
pop()
```
## 視訊的使用
首先要開啟新的 SKETCH
然後一樣用右側設定把 p5.dom 開啟
使用方式:
```
let capture;
function setup() {
capture = createCapture(VIDEO);
}
```
抓到當下視訊中的顯示畫面:
```
function draw() {
image(capture,mouseX,mouseY)
}
```
利用建立暫存畫布來獲取像素並實現馬賽克效果:
```
let capture,pg;
function setup() {
createCanvas(640,480);
background(100)
noStroke()
capture = createCapture(VIDEO);
capture.size(640,480)
capture.hide();
pg = createGraphics(640,480); // 建立暫存畫布
}
function draw() {
pg.image(capture,0,0) // 利用暫存畫布保存當前顯示畫面
for(let i=0;i<pg.width;i+=10){
for(let j=0;j<pg.height;j+=10){
let p = pg.get(i,j) // 獲取畫面上的像素位置顏色
fill(p) // 設置顏色
rect(i,j,10) // 繪製方塊呈現馬賽克感覺
}
}
}
```
處理視訊與畫面水平對調問題:
```
function setup() {
createCanvas(640,480);
background(100)
noStroke()
capture = createCapture(VIDEO);
capture.size(640,480)
capture.hide();
pg = createGraphics(640,480);
pg.translate(640,0)
pg.scale(-1,1)
}
```
## 資料視覺化(JSON)
首先建立新的 SKETCH
然後直接先存擋
接著開啟右側欄位的『FILES』即可上傳 json 檔案
上傳 json 檔案後可通過下方程式碼啟用:
```
let treeData,useData=[];
function preload() {
treeData = loadJSON("agrstatUnit.json")
}
```
初始化先處理一下陣列:
```
function setup() {
createCanvas(windowWidth, windowHeight);
background(0);
treeData = Object.values(treeData) // 把物件轉陣列
for(let d of treeData){ // 資料太多時,重新過濾、抓出需要用的資料
if(d.dname1=="相思樹"){
useData.push(d)
}
}
}
```
>過濾的部分也可以使用 filter 處理。
接下來就可以自由繪製了,參考作品:https://openprocessing.org/sketch/1818264
## 其他函式庫用法
### Track.js
[顏色追蹤(黃色)](https://openprocessing.org/sketch/1796318)
[臉部追蹤](https://openprocessing.org/sketch/1796069)
### Rita.js+p5.speech
英文詞性操作(需引入 Rita.js):
```
myText = "The quick brown fox jumps over the lazy dog."
allPos = RiTa.pos(myText); // 獲取詞性(EX: nn名詞/jj形容詞)
words = RiTa.tokenize(myText) // 獲取一段句子中的單個字詞
randomNWord = RiTa.randomWord("nn"); // 生成一個隨機名詞
```
>官方文件:https://rednoise.org/rita/index.html#reference
語音唸字(需引入 p5.speech):
```
word = 'unbelievable'
voice = new p5.Speech()
voice.speak(word)
```
[英文詞性操作+語音唸字](https://openprocessing.org/sketch/1818264)
### Matter.js
[從上往下掉落至地板](https://openprocessing.org/sketch/1834790)
[懸掛物體](https://openprocessing.org/sketch/1837998)
## P5.js 3D(WEBGL)
首先開啟新的 sketch
接著在 setup 中,將 ` createCanvas(windowWidth, windowHeight);` 加上第三個參數:
`createCanvas(windowWidth, windowHeight, WEBGL);` 啟用 3D
繪製立體形狀:
1. 方塊: box(長,寬,高) or box(長寬高) => box(100,120,150) or box(100)
2. 球體: sphere(半徑) => sphere(50)
3. 圓錐: cone(半徑,高度) => cone(50,100)