這次老闆跟 Hahow 好學校合作進行臉書直播活動,讓大家在這個嚴峻的疫情下,待在家就能創作生成式藝術。相信初學者聽到 creative coding 生成式藝術,一定會非常驚慌,想著自己不會寫 code,要如何 coding 來做藝術創作?今天老闆將會帶領你利用 p5.js 製作互動機器人,不論你是否是工程師,都能透過這個程式語言來進行創作。
由於這次是體驗課程,主要讓大家在短時間內,完成生成式藝術的創作,如果想要了解更詳細的製作流程和其他創作內容,可以去支持老闆的互動藝術程式創作入門。
這次直播筆記會帶大家學會以下內容:
- 如何利用線上工具 openprocessing 進行 Creative Coding 創作
- 利用 p5.js 繪製不同大小顏色的方塊和圓形,組裝出基本的機器人
- 結合滑鼠監測,讓機器人與使用者互動
事前準備
創作過程會使用到 p5.js 的 API 如下:
- translate(x, y) 將畫筆移動到(x, y)
- background(0) 背景色黑色
- fill(‘#fff’) 將畫筆顏色換成 #fff
- noStroke() 不使用邊線
- rectMode(CENTER) 畫方塊的模式,傳入的參數可以是 CORNER、CORNERS、RADIUS、CENTER,預設為CORNER,連結
- rect(x, y, width, height) 在(x, y)畫一個寬度 width 高度 height 的方形
- ellipse(x, y, width, height) 在(x, y)畫一個寬度 width 高度 height 的橢圓形
- push() 儲存目前畫布的樣式設定
- pop() 恢復剛剛 push 所儲存的樣式設定
- Object.assign() 用來複製一個或多個物件自身所有可數的屬性到另一個目標物件。連結
環境:
這次創作以線上工具 openprocessing 示範,大家也可以利用 codepen 進行開發,只要在 codepen 內引用 p5.js 即可。
跟著老闆開始動手做
進入 openporcessing 網頁並申請帳號後,就能開始創作。初始網頁就會提供大家最基礎的兩個 function (setup跟draw),如果要看效果,可以點選 Editor 來切換 Layout。
萬事起頭難,直接看成品就要創作,該如何下手?首先先將最終目標拆分成不同階段任務,第一步我們會先製作出第一隻機器人,接著再從一隻複製成很多隻,並賦予每隻機器人不同顏色與樣式,最後只要結合滑鼠互動,讓機器人跟著動起來,就大功告成了。
做第一隻機器人
要做機器人的所有部位需要有顏色,所以第一步驟便是製作一個顏色陣列,來儲存所有我們會用到的顏色,這邊推薦使用配色工具:coolors https://coolors.co/。能夠將吸到的顏色製作成陣列,老闆利用這個工具抓顏色時,習慣將喜歡的顏色按 lock 鎖住後,按空白鍵來替換其他顏色。確定了整組顏色之後,可以在網址列上獲得一組用 “-” 符號分開的色碼,只要利用以下程式碼便能將色碼變成陣列。
// main.js var colors = ('003049-d62828-f77f00-fcbf49-eae2b7-226f54-2d6a4f-276fbf').split("-").map(a => "#" + a)
- (字串).split(“-“) 將整串字串用-符號做切割,分成整組陣列
- [].map(a ⇒ “#” + a) 陣列中每個項目都稱作 a,將每個 a 都進行加工回傳 “#” + a
藉由前面列出的 API ,我們使用幾何圖形先製作出機器人的頭和五官,這邊大家可以自行調整每個部位的大小以及顏色。若是想要使用更多顏色讓畫面不單調,可以再透過colors工具產出新的色碼,增加到變數 colors 的字串中,只要記得每個顏色都要使用 “-” 分開就好。沒有程式經驗的同學也不用緊張,只要把使用這些 API 的過程想像成,使用一行程式語言告訴電腦,幫我換顏料顏色,或是畫出什麼圖型就好,透過不斷地嘗試顏色及大小,直到畫出自己滿意的狀態。
// main.js function draw() { translate(width/2, height/2) rectMode(CENTER) background(0) noStroke() // 不要有邊框 // 機器人頭 fill(colors[1]) rect(0, 0, 500, 300) // 左眼 fill(colors[3]) ellipse(-120, 0, 60, 60) // 右眼 fill(colors[2]) ellipse(100, 0, 60, 60) // 左眉毛 fill(colors[3]) rect(-90, -100, 150, 30) // 右眉毛 fill(colors[4]) rect(100, -100, 150, 30) // 嘴巴 fill(colors[4]) rect(0, 100, 200, 30) // 鼻子 fill(colors[7]) rect(0, 10, 30, 80) // 左耳 fill(colors[6]) rect(-250, 10, 50, 150) // 右耳 fill(colors[7]) rect(250, 10, 60, 150) }
讓機器人動起來
賦予機器人五官之後,接著來為機器人加上一些動態,賦予它情緒。在創作的過程,大家可以思考並結合生活中的經驗在作品中,例如機器人可以有胖瘦不同身材,或是不同的情緒。
我們為了讓眼睛能夠縮放,將眼睛的寬度高度加上一個隨機值。眉毛則隨著時間去旋轉,不過這邊要注意的是,旋轉會影響到整張畫布,讓眉毛旋轉前,要先將目前畫布狀態存起來 push(),旋轉完之後再使用 pop() 恢復畫布,如果忘記做這件事情,會發現後面的程式碼都被影響到了,大家可以嘗試把 push 和 pop 拿掉來看看失控的機器人。
// main.js function draw() { ... // 左眼 fill(colors[3]) ellipse(-120, 0, 60 + random(-30, 30), 60 + random(-30, 30)) ... // 左眉毛 push() fill(colors[3]) rotate(0.3 + sin(frameCount/30)/5) rect(-90, -100, 150, 30) pop() // 右眉毛 push() fill(colors[4]) rotate(-0.25 + sin(frameCount/50)/5) rect(100, -100, 150, 30) pop() ... }
小技巧:openprocessing 有提供分支的功能,可以讓大家階段性儲存檔案,在之後的開發,隨時都能回到前一版所儲存的狀態。
機器人分身
接著我們會將機器人包裝成 class,這可以想像成我們將機器人包裝成客製化的印章,並且每個印章都可以有自己的屬性(顏色大小等)。先在 openprocessing 上面開一個新的 tab,在裡面開始撰寫 class。openprocessing 很方便的地方在於,開不同 tab 撰寫程式碼時,不用在 main.js 中 import robot.js ,就會自動幫我們引入。
首先我們先來處理 robot.js, 可以將 class 內分三個區塊解說
- constructor:初始化印章,可以指定數字或顏色給這個 robot 使用 這邊我們會在裡面預設一個參數,並使用 Object.assign 將 args 的值複製到預設參數中,再將預設參數中的值複製給本體。
- draw:繪製相關的函數 將剛剛第一個機器人的相關內容複製進來後,在前後分別加入 push, pop ,避免每次畫完一個機器人,畫布狀態又被影響。 此外也要注意要將原本 translate(x, y) 的值在這裡改成使用 constructor 內的值
- update:資料相關的更新
// robot.js class Robot { // 初始化印章,可以指定數字或顏色給這個 robot 使用 constructor(args){ // 預設參數 let def = { p: createVector(width/2, height/2) } Object.assign(def, args) Object.assign(this, def) } // 繪製相關的函數 draw(){ push() rectMode(CENTER) translate(this.p.x, this.p.y) ... pop() } // 資料相關的更新 update(){} }
完成 class 後,我們將 class 重複使用,製作出兩隻機器人。
main.js 中分為三個步驟:
- 創造一個新變數指定為空陣列 將每次創造的機器人使用 js 中的 push() 儲存到陣列中。要注意的是,這邊使用的 push () 是 js 語法,而我們先前在繪製機器人使用的 push 則是 p5.js 內儲存畫布狀態的語法。
- setup() 中,new 出不同的機器人,並傳入不同的位置
- draw() 中, 先將剛剛繪製機器人的函數移到 robot.js 內的 draw() 中,接著將 robots 中的所有機器人的資料更新後,並繪製出來
// main.js var robots= [] function setup() { createCanvas(windowWidth, windowHeight); background(100); robots.push(new Robot({ p: createVector(width/3, height/2) })) robots.push(new Robot({ p: createVector(width/1.5, height/2) })) } function draw() { background(0) robots.forEach(robot => robot.update()) robots.forEach(robot => robot.draw()) }
兩隻機器人完成之後,該如何製作出更多機器人呢?一個一個創造嗎?我們可以使用 for 迴圈來產出機器人的 (x,y),每次迴圈跑完增加的 x 和 y 都可以慢慢去微調,並在 robot.js 中,在 draw() 加入 scale ,調出大家喜歡的樣子。
// main.js function setup() { ... for(var x= 0; x < width + 200; x += 300) { for(var y = 0; y< height; y += 200) { robots.push(new Robot({ p: createVector(x, y) })) } } ... }
// robot.js class Robot { constructor(args){ ... } draw(){ push() rectMode(CENTER) translate(this.p.x, this.p.y) scale(0.5) ... update() {} }
讓每隻機器人都不一樣
創作後期會增加更多的色票,讓作品顏色更豐富,並微調樣式給每隻機器人有不同的外貌,例如:顏色、臉的大小、臉的圓角、耳朵高低、眉毛大小、眼睛大小…等。
在 robot.js 中的 constructor 內產生不一樣的 key 值,都能利用 random 來隨機產生不一樣的值,並將這些值組合進 draw 中。而左眼的眼眶則可以利用兩個圓形依序疊在一起來製作,大家可以利用這些不同的值來做調整,也能試試看新增其他變數來調整自己的機器人。
如果覺得每隻機器人變化的狀態都一樣,老闆也會多產生一個 randomId,以下稱之為亂數種子,亂數種子是在一開始隨機產生的數字,可以讓每隻機器人都能有不一樣的數值,結合到畫面中就能讓每隻機器人有所差異。
// robot.js class Robot { constructor(args){ let def = { p: createVector(width/2, height/2), colors: [random(colors), random(colors), random(colors), random(colors), random(colors), random(colors), random(colors), random(colors), random(colors), random(colors) ], // 亂數的尺寸大小 size: createVector( random(400, 300), random(300, 200) ), borderRadius: random(50), eyeSize:createVector( random(10, 50), random(50, 100) ), scale: random(1), // 亂數種子 randomId: random(1000000) } ... } // 繪製相關的函數 draw(){ push() rectMode(CENTER) translate(this.p.x, this.p.y) //頭旋轉,因每個機器人的 randomId 都不同,所以旋轉的角度都會不一樣 rotate(sin(frameCount/50 + this.randomId)/2) scale(0.3) noStroke() // 臉 fill(this.colors[1]) // rect(0, 0, this.size.x, this.size.y, this.borderRadius) // 左眼眶 fill(this.colors[3]) circle(-120, 0, 60 + this.eyeSize.x) // 右眼 fill(this.colors[2]) circle(100, 0, this.eyeSize.y) // 左眼球 fill(this.colors[5]) circle(-120, 0, 20 + this.eyeSize.x) push() // 左眉毛 fill(this.colors[3]) rotate(0.3 + sin(frameCount/30)/5) rect(-90, -100, 150, 30) pop() push() // 右眉毛 fill(this.colors[4]) rotate(-0.25 + sin(frameCount/50)/5) rect(100, -100, 150, 30) pop() // 嘴巴 fill(this.colors[4]) rect(0, 100, 200, 30) // 鼻子 fill(this.colors[7]) rect(0, 10, 30, 80) // 左耳朵 fill(this.colors[6]) rect(-this.size.x/2, 10, 50, 150) // 右耳朵 fill(this.colors[7]) rect(this.size.x/2 + 20, 10, 60, 150) pop() } update(){} }
跟滑鼠互動
老闆接下來示範如何讓機器人與滑鼠互動,透過滑鼠移動的 X 量 mouseX,來調整機器人與它的左眉毛,除以 50 是避免抖動的幅度過大,所以固定除以某個數字。此外也可以結合亂數種子,讓每隻機器人左眉毛挑動的時間都能不同。大家也可以試著使用 mouseX, mouseY, randomId 到不同位置,嘗試不同的互動方式,像是前面就有將亂數種子結合到整顆頭的旋轉,也可以試試看讓眼睛大小結合亂數種子或是滑鼠互動使用。
// roboyt.js class Robot { constructor(args){...} draw(){ ... // 開始繪製前先旋轉畫布 rotate(sin(mouseX / 50 + this.randomId) / 5) ... // 左眉毛 push() fill(this.colors[3]) translate(0, 50 + sin(mouseX / 50 + this.randomId) * 20) rotate(0.3 + sin(frameCount/30)/5) rect(-90, -100, 150, 30) pop() ... } // 資料相關的更新 update(){} }
天線
要幫機器人加上天線非常的容易,我們產出一個隨機的值 ll,讓每個機器人的天線長度都不同,結合前面製作旋轉的方式,讓天線隨著時間旋轉。
// robot.js class Robot{ constructor(args){ let def = { ... ll: random(20,80), ... } } draw(){ ... push() // 天線 rotate(sin(frameCount/10+this.randomId)/5) fill(this.colors[8]) rect(0,-200,30,this.ll) pop() ... } update(){ } }
提升質感與輸出作品
作品完成後,老闆提供一個小方法,讓整個作品質感提升,通常老闆創作結束最後一個階段,會將作品疊上材質,讓原本數位感比較重的作品,多一層質感,使用的方法非常簡單,大家可以將老闆的 code 貼到以下位置即可使用。
// main.js let overAllTexture function setup(){ ... pixelDensity(2) overAllTexture=createGraphics(width,height) overAllTexture.loadPixels() for(var i=0;i<width+50;i++){ for(var o=0;o<height+50;o++){ overAllTexture.set(i,o,color(100,noise(i/3,o/3,i*o/50)*random([0,50,100]))) } } overAllTexture.updatePixels() } function draw(){ push() blendMode(MULTIPLY) image(overAllTexture,0,0) pop() }
完成的作品當然要好好分享一波,讓大家看看你的作品。如果要匯出 gif ,可以使用 screenflow,不過需要付費。如果只是要輸出圖片,可以在 setup 中加入 pixelDensity(2) ,另存的圖片就會以兩倍像素密度呈現。
快速回顧
首先讓我們快速回顧一下,這次利用 p5.js 創作出互動機器人的流程:
- 透過不同大小顏色的方塊和圓形,組合出機器人雛型
- 利用 class 的方式,產出大量的機器人
- 藉由 class 內的 constructor ,讓每組機器人都能有不同的設定
- 除了讓機器人隨著時間變化外,結合使用者的滑鼠來讓機器人有更豐富的動態
- 匯出作品
這次跟 Hahow 好學校的合作,在臉書上利用直播跟大家聊聊程式的藝術創作,利用 p5.js 寫出可以和使用者互動的生成式藝術,大家也可以思考如何將這些方法拿來做出其他作品呢?
而在創作的過程中,不是一定要一次就到位,慢慢去調整作品,並思考生活中有什麼經驗或經歷能夠融入作品中,讓作品更精緻完整。
如果你因此對互動藝術程式創作產生興趣,歡迎加入老闆開的 Hahow 課程互動藝術程式創作入門,讓老闆跟你分享不同的創作!
互動藝術程式創作入門是為了不會程式的人設計的課程,課程中會帶你看看不一樣的作品,並從基礎引導大家一步步完成作品,透過每次的賞析、實作到修正作品,讓大家覺得寫 code 不是這麼難的事情,將這個過程想像成,拿一隻比較難的畫筆在進行創作,如果有機會使用它,便能夠做出和與眾不同的創作。
直播提問
最後,老闆也將這次直播中觀眾提問的問題整理出來,提供大家參考:
問:creative coding 是專門給設計師嗎?
答:不是一種程式語言,是一種方式。只要你能利用程式進行創作,都能算是 creative coding 的範疇。
問:processing.js 和 p5.js 的差別?
答:近幾年兩種 js 的功能和特效已經可以達到一致。相較之下 processing.js 較早問世,但當時要創作時必須先裝好 java 的執行環境,才能安裝 processing 進行創作,最後還要打包成執行檔,在散佈作品方面相對較難。後來紐約大學的教授決定要讓 processing 更好,才著手開始優化。
問:老闆有哪些印象深刻的專案?
答:在美國讀了兩年研究所,其中有個作品是用實體的傘,透過傘的傾斜,在 VR 世界中飛行。連結
問:看別人的作品時,程式邏輯太複雜可以怎麼做?
答:學習的方式還是要循序漸進,先從基礎簡單的開始後,再嘗試解讀別人專案中有興趣的部分,慢慢加強難度,功力也會越來越高。
問:老師創作方式蠻特別的,不是先算好所有參數,而是邊做邊調整?
答:對老闆來說,創作的過程是一種心流,慢慢地去調整作品。因為是創作,所以如果一開始先規劃好,然後按照規劃去執行,過程就會比較死板,試錯過程也是創作的一部份。
問:有規劃 creative coding 或科技藝術相關工具的課程嗎?
答:有,但是還是要靠大家捧場。目前台灣這塊大家還是認識的比較少,要推廣會比較辛苦一點,未來希望能夠擴張到不同的社群,有更多的學生,規劃的進階課比較有趣,大家可以敬請期待。目前有規劃開網頁的進階課和 creative coding 的進階課,大家若是有任何想法,都可以私訊告訴我們。
問:學習科技藝術的過程有什麼經驗可以分享嗎?
答:由於老闆本身是工程師出生,工程師為了解決問題而做東西,比較難想到新點子,很常被自己當下具備的能力限制住,不過透過多看別人的作品,有改善自己在創作時的困難。
此篇直播筆記由幫手 H 協助整理