1. 程式人生 > >1.7 開始第一幅“碼繪”——用時間控制變數,讓懵逼臉動起來

1.7 開始第一幅“碼繪”——用時間控制變數,讓懵逼臉動起來

引言——碼繪的缺點和優點

在之前的程式中,所有懵逼臉的表情都是僵住不動的。

如此一來,相比於傳統紙面繪畫,碼繪技術還體現不出什麼優點,可以說還有一系列缺點:

  • 編寫程式碼麻煩容易出現筆誤,
  • 學習程式語言代價高,
  • 不夠直觀,
  • 缺乏操縱畫筆時的控制感和愉悅感,
  • 要用完全邏輯的方式來考慮作畫過程,抒情性、即興性和表意性顯得不足。

但是,用程式設計來作畫,可以實現紙面繪畫做不出的效果,主要就是兩種:動態和互動

在之前的程式中,互動性已經略有體現,我們已經實現了讓懵逼臉可以跟隨滑鼠移動。

然而由於缺乏動態,表情僵硬,還很難說有”碼繪“什麼特別的優勢。

這一節,我們將讓懵逼臉動起來,它不僅可以跟隨滑鼠移動,自身的尺寸/表情/五官位置還繪隨時間而發生變化。

一個簡單的動畫程式案例

先看一個極簡的程式例:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
function setup() { createCanvas(640,480);}function draw() { // 呼叫函式second(),獲得程式執行經過的秒數 var s = second(); // 在螢幕中心畫圓圈,長寬等於秒數 ellipse(320,240,s,s);}
它的執行效果是一個隨時間緩慢增大的圓形:


程式碼也示意了簡單的動畫技術,其要點有二:

  • draw()函式隨時間反覆執行;
  • 每一次執行draw()函式,通過呼叫函式second()獲得程式當前經過的秒數,並且在繪製圓形時用獲得的時間數值來指定圓形的尺寸。

製造動態的基本方法

從上述簡單的動畫示例,可以匯出製作動態的基本方法,寫成一個函式如下:
 1
 2
 3
 4
 5
function LoopFunction()// 這個函式應該被框架程式持續反覆呼叫{ UpdateEveryThing(); // 更新每一個東西 DrawEveryThing(); // 繪製每一個東西}

可見,有兩個要領:1.要定義一個在程式框架中被持續反覆呼叫的函式;2.在該函式中,要更新並繪製一些東西; 再對應到p5.js提供的程式框架,可見其draw()函式符合上述要求,只要能夠在draw()函式中更新需要繪製物體的資訊並將它們繪製出來,就能產生動畫效果。

讓懵逼臉移動起來

利用之前寫出的drawConfuseFace()函式,即:
   1
   2
   3
   4
   5
   6
   7
   8
   9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
  28
  29
  30
  31
  32
  33
  34
  35
  36
  37
  38
  39
  40
  41
  42
  43
  44
  45
  46
  47
  48
  49
  50
  51
  52
  53
  54
  55
  56
  57
  58
  59
  60
  61
  62
  63
  64
  65
  66
  67
  68
  69
  70
  71
  72
  73
  74
  75
  76
  77
  78
  79
  80
  81
  82
  83
  84
  85
  86
  87
  88
  89
  90
  91
  92
  93
  94
  95
  96
  97
  98
  99
 100
 101
 102
 103
 104
 105
// 畫懵逼臉function drawConfuseFace( posX, posY, // 臉部中心位置 faceSize, // 臉部尺寸 scaleMouth, // 嘴巴尺度比例,相對於臉部尺寸 scaleLEye, // 左眼尺度比例, 相對於臉部尺寸 scaleREye) // 右眼尺度比例, 相對於臉部尺寸{ // -------------- 1 畫臉 --------------- fill(255);// 填充白色 ellipse(posX,posY,faceSize,faceSize);// 圓圈 // -------------- 2 畫眼睛 --------------- // 2.1 計算眼睛相對於臉中心點的偏移量 var EyeOffsetX = 0.2 * faceSize; // 眼睛橫向偏移量為臉部尺寸的0.2倍 var EyeOffsetY = 0 * faceSize; // 眼睛縱向偏移量為臉部尺寸的0倍 // 2.2 計算眼睛尺寸 // 左右眼尺寸 var LEyeSize = faceSize * scaleLEye; var REyeSize = faceSize * scaleREye; // 左右眼珠尺寸 var LIrisSize = LEyeSize * 0.4; var RIrisSize = REyeSize * 0.4; // 2.2 畫出左眼 fill(255);// 填充白色 ellipse( posX-EyeOffsetX, // 臉的中心位置向左偏移EyeOffsetX posY+EyeOffsetY, // 臉的中心位置向下偏移EyeOffsetY LEyeSize, LEyeSize); // 2.3 畫出右眼 fill(255);// 填充白色 ellipse( posX+EyeOffsetX, posY+EyeOffsetY, REyeSize, REyeSize); // 5 左眼珠 fill(0);// 填充黑色 ellipse( posX-EyeOffsetX, // 位置與左眼一樣 posY+EyeOffsetY, LIrisSize, // 尺寸則採用比左眼小的尺寸 LIrisSize); // 6 右眼珠 fill(0);// 填充黑色 ellipse( posX+EyeOffsetX, posY+EyeOffsetY, RIrisSize, RIrisSize); // -------------- 3 畫嘴巴 --------------- // 3.1 計算嘴巴相對於臉部中心位置的偏移量 var MouthOffsetX = 0.0; var MouthOffsetY = 0.3*faceSize; // 3.2 計算嘴巴尺寸 var MouthWidth = faceSize * scaleMouth; var MouthHeight = MouthWidth/2.0; // 3.3 畫出嘴巴 fill(255); // 填充白色 ellipse( posX + MouthOffsetX, posY + MouthOffsetY, MouthWidth, MouthHeight); // -------------- 4 畫頭髮 --------------- drawOneHair(posX,posY,faceSize,-0.3); drawOneHair(posX,posY,faceSize,-0.2); drawOneHair(posX,posY,faceSize,-0.1); drawOneHair(posX,posY,faceSize,0); drawOneHair(posX,posY,faceSize,0.1); drawOneHair(posX,posY,faceSize,0.2); drawOneHair(posX,posY,faceSize,0.3);}// 繪製一根頭髮function drawOneHair( faceX,faceY, // 臉的中心位置 faceSize, // 臉的尺寸 offsetXOnFaceSize) // 頭髮X座標的的偏移量,以臉部尺寸為單位尺寸{ // ------------- 1 計算尺寸和位置 ---------// // 頭髮相對臉部中心的Y偏移量 var HairOffsetY = faceSize * 0.3; // 計算X偏移量 var offsetX = offsetXOnFaceSize * faceSize; // 頭髮長度 var HairLength = faceSize * 0.4; // --------------- 2 畫頭髮 ---------------// line( faceX - offsetX, faceY - HairOffsetY, faceX - offsetX, faceY - (HairOffsetY + HairLength) );}

我們重新寫一個簡單的draw()函式,實現一個持續移動的懵逼臉:
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
// 函式draw():作畫階段function draw() { // -------- 獲取時間 ------------------- // // 獲得毫秒數 var Millis = millis(); // 獲得秒數,相比second(),能獲得小數點後的部分 var Second = millis()/1000; // -------- 計算位置 --------------------// var posX = Second * 80; posX = posX%width; // 讓posX不超過螢幕寬度 var posY = Second * 61; posY = posY%height; // 讓posY不超過螢幕高度 // --------- 繪製懵逼臉------------------// drawConfuseFace(posX,posY,80,0.3,0.2,0.2); }

這樣便實現了移動的懵逼臉,效果如下:
在上述程式碼中,使用了millis()函式來獲取時間,單位為毫秒,然後再通過除法運算轉化為秒數,這種方式相比直接呼叫second()而言,可以獲得精度更高的時間值。而用second()函式只能獲得整數的讀秒計數。 加粗的程式碼便是實現動態效果的關鍵。對於posX,首先通過語句“varposX=Second*80;"通過語句讓posX的數值正比於秒數Second, 於是,在不同時刻,這幾計算出的posX的實際數值也不同;然後,為了讓懵逼臉始終位於畫布內,用了求餘數的算符 %, 讓posX對畫布寬度width求餘數(width是p5.js提供的變數,其數值就是畫布寬度:https://p5js.org/reference/#/p5/width )。算符%的作用是求餘數,其用法如下列程式碼:
 1
 2
var a = 5%3; // 對5求3的餘數,結果為2,並賦值給avar b = 9%a; // 對9求a的餘數,結果為1, 並賦值給b

posY的計算方式於posX完全一樣。 於是,在最後呼叫drawConfuseFace()函式時,由於用隨時間發生變化的量posX和posY作為位置,因此畫出來的懵逼臉便隨著時間移動了。