用p5.js玩創作,讓小機器人動起來!哲宇的互動藝術體驗(直播筆記)

這次老闆跟 Hahow 好學校合作進行臉書直播活動,讓大家在這個嚴峻的疫情下,待在家就能創作生成式藝術。相信初學者聽到 creative coding 生成式藝術,一定會非常驚慌,想著自己不會寫 code,要如何 coding 來做藝術創作?今天老闆將會帶領你利用 p5.js 製作互動機器人,不論你是否是工程師,都能透過這個程式語言來進行創作。

用 p5.js 創作互動藝術,讓小機器人動起來
用 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 中分為三個步驟:

  1. 創造一個新變數指定為空陣列 將每次創造的機器人使用 js 中的 push() 儲存到陣列中。要注意的是,這邊使用的 push () 是 js 語法,而我們先前在繪製機器人使用的 push 則是 p5.js 內儲存畫布狀態的語法。
  2. setup() 中,new 出不同的機器人,並傳入不同的位置
  3. 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 創作出互動機器人的流程:

  1. 透過不同大小顏色的方塊和圓形,組合出機器人雛型
  2. 利用 class 的方式,產出大量的機器人
  3. 藉由 class 內的 constructor ,讓每組機器人都能有不同的設定
  4. 除了讓機器人隨著時間變化外,結合使用者的滑鼠來讓機器人有更豐富的動態
  5. 匯出作品

這次跟 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 協助整理

墨雨設計banner

分享
PHP Code Snippets Powered By : XYZScripts.com