--- 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)