2021年4月29日 星期四

p5.js 學習筆記 (七) : 動態圖形 (上)

經過前面幾篇測試已熟悉使用 p5.js 繪製靜態圖與重複圖形的方法後, 就可正式進行 p5.js 的互動與動態圖形繪製了, 這部分也是 p5.js 最迷人的地方, 需要一點創意加上數學造詣. 

本系列之前的文章參考 : 


以下測試使用之參考文件 : 

https://p5js.org/reference/ (教學文件)
https://editor.p5js.org (線上編輯器)


1. 利用滑鼠座標移動畫動態圖 : 

製作動態圖形除了會用到 Javascript 的流程控制 (迴圈與分支) 語法以及數學函式外, p5.js 本身也提供了一些與滑鼠座標與按鈕狀態有關的內建變數可資運用 : 


 mouseIsPressed 滑鼠左鍵是否被按下 (true/false)
 mouseButton 被按下的滑鼠按鍵 (LEFT/CENTER/RIGHT)
 mouseX 目前滑鼠的 X 座標
 mouseY 目前滑鼠的 Y 座標
 pmouseX 前一個 frame 之滑鼠 X 座標
 pmouseY 前一個 frame 之滑鼠 Y 座標


常用內建函式如下 (需自行實作函式內容) :


 mouseClicked() 滑鼠按鈕左鍵按下又放開時呼叫之函式, 其內容需自訂
 mousePressed() 滑鼠按鈕左鍵按下時呼叫之函式, 其內容需自訂
 mouserReleased() 滑鼠按鈕左鍵放開時呼叫之函式, 其內容需自訂
 doubleClicked() 滑鼠按鈕左鍵雙擊時呼叫之函式, 其內容需自訂
 mouseMoved() 滑鼠移動時呼叫之函式, 其內容需自訂
 mouseDragged() 滑鼠按鈕左鍵按住拖曳時呼叫之函式, 其內容需自訂


首先來看看 mouseX 與 mouseY, 這兩個變數分別記錄目前滑鼠的 X, Y 座標位置 (px), 可以利用它們來繪製追蹤滑鼠移動之動態圖形, 例如 : 



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(400, 300);
      background(200, 200, 200);
      fill('orange');
      }
    function draw() { 
      circle(mouseX, mouseY, 10, 10);   //追蹤滑鼠位置畫圓
      }
  </script>
</body>
</html>

此例直接將目前滑鼠位置傳入 circle() 的 x, y 參數, 當滑鼠在畫布上移動時便會追蹤其位置畫圓, 移動快時圓的間隔大, 反之則間個小, 結果如下 : 




注意, 由於滑鼠座標預設值為 (0, 0), 因此進入 draw() 繪圖迴圈時會固定於左上角畫圓. 要避免這情形可利用 mouseIsPressed 變數偵測滑鼠按鈕狀態來控制繪圖與否. 

內建變數 mouseIsPressed 紀錄目前滑鼠按鈕 (左鍵或右鍵均可) 是否為被按下狀態, 被按下值為 true, 否則為 false, 利用此變數可以控制只有在按住滑鼠按鈕時才追蹤滑鼠位置繪圖, 例如 :



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(400, 300);
      background(1, 70, 100);
      fill(230, 30, 90);
      }
    function draw() { 
      if(mouseIsPressed){     //滑鼠按鈕按下時才會畫圓
        circle(mouseX, mouseY, 10, 10);
        }
      }
  </script>
</body>
</html>

此例利用偵測 mouseIsPressed 的狀態只有在滑鼠左鍵或右鍵被按下時才會畫圓, 結果如下 :




可見不論是按下滑鼠左鍵或右鍵均能在畫布上追蹤滑鼠位置畫圖, 如果要限制只能在按下滑鼠左鍵時才繪圖可利用內建變數 mouseButton 來判斷, 其值有 CENTER/LEFT/RIGHT 三個, 例如 :



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(400, 300);
      background(1, 70, 100);
      fill(230, 30, 90);
      }
    function draw() { 
      if(mouseIsPressed){
        if(mouseButton == LEFT){    //若被按住之滑鼠按鈕為左鍵才繪圖
          circle(mouseX, mouseY, 10, 10);
          }
        }
      }
  </script>
</body>
</html>

此例在偵測滑鼠按鈕是否被按下的判斷裡添加偵測被按下的是否為左鍵 (LEFT) 之判斷, 這樣只有在按住滑鼠左鍵時才繪圖, 按住右鍵無作用, 結果如下 : 




上面範例中追蹤滑鼠位置所繪製的動態圖形會因為移動太快速而使得線條間隔太大而不連續, 解決此問題的辦法是使用內建變數 pmouseX 與 pmouseY, 它們會記錄前一次圖框的滑鼠 (x, y) 座標位置, 因此若改用 line(x1, y1, x2, y2) 來繪製移動曲線, 傳入pmouseX 與 pmouseY 作為 (x2, y2), 可使線條更具連續性, 例如 : 



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(400, 300);
      background(1, 70, 100);
      stroke('orange');
      strokeWeight(10);
      }
    function draw() { 
      line(mouseX, mouseY, pmouseX, pmouseY);   //繪製連續性線條
      }
  </script>
</body>
</html>

此例追蹤滑鼠目前位置 mouseX 與 mouseY, 於呼叫 line() 函式時利用 pmoseX 與 pmouseY 將滑鼠目前坐標與前一圖框時之坐標間繪製直線, 從而使線條具有連續性, 結果如下 :




當然也可以加上滑鼠按鈕狀態來控制只有在按下滑鼠左鍵時畫圖, 例如 : 



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(400, 300);
      background(1, 70, 100);
      stroke('orange');
      strokeWeight(10);
      }
    function draw() { 
      if(mouseIsPressed){
        if(mouseButton == LEFT){  //只有在按住滑鼠左鍵時畫圖
          line(mouseX, mouseY, pmouseX, pmouseY);
          }
        }
      }
  </script>
</body>
</html>

此例用 mouseIsPressed 偵測滑鼠是否有按下, 有的話再用 mouseButton 變數判斷是按下左鍵還是右鍵, 只有在按下左鍵時才畫線, 結果如下 :




上面範例中線條的寬度為固定, 可用內建函式 dist(mouseX, mouseY, pmouseX, pmouseY) 取得前後圖框時間間隔滑鼠移動距離 (px), 用此距離作為線條之寬度, 則可產生移動快時線條變粗, 移動慢時線條變細的效果, 例如 :



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(400, 300);
      background(1, 70, 100);
      stroke('orange');
      strokeWeight(10);
      }
    function draw() { 
      d=dist(mouseX, mouseY, pmouseX, pmouseY);   //傳回目前滑鼠座標與前一圖框時之距離
      strokeWeight(d);    //用距離作為畫筆粗細
      line(mouseX, mouseY, pmouseX, pmouseY);
      }
  </script>
</body>
</html>

此例用 dist() 來計算前後圖框時間差內滑鼠移動距離並將其設為畫筆粗細, 結果如下 :




線條細的地方滑鼠移動得慢, 粗的地方則是移動快速. 這樣對畫筆粗細改變速度似乎反應太快, 可以用 easing (緩衝係數) 來將粗細的劇烈變化緩和下來, 這個自訂變數 easing 值在 0~1 之間, 當 easing=1 時為完全緩衝, easing=0 時為無緩衝, 例如 : 



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    var easing=0.03;   //緩衝係數 (0~1)
    var x=0;    //調整後的目前滑鼠 x 座標初始值
    var y=0;    //調整後的目前滑鼠 y 座標初始值
    var px=0;   //調整後的前一圖框滑鼠 x 座標初始值
    var py=0;   //調整後的前一圖框滑鼠 y 座標初始值
    function setup() {
      createCanvas(400, 300);
      background(1, 70, 100);
      stroke('orange');
      strokeWeight(10);
      }
    function draw() { 
      x += (mouseX - x) * easing;   //利用緩衝係數給 x 座標增量
      y += (mouseY - y) * easing;   //利用緩衝係數給 y 座標增量
      d=dist(x, y, px, py);    //計算調整後滑鼠座標 (x, y) 與前一圖框 (px, py) 座標距離
      strokeWeight(d);    //設定畫筆粗細
      line(x, y, px, py);   //於調整後滑鼠座標與前一圖框座標間畫線
      px=x;   //更新前一圖框滑鼠 x 座標
      py=y;   //更新前一圖框滑鼠 y 座標
      }
  </script>
</body>
</html>

此例以自訂的全域變數 easing 來調整目前滑鼠之座標, 並在結束每次 draw() 圖框迴圈前將其設定為調整後的前一圖框座標, easing 越小緩衝效果越明顯, 亦即滑鼠移動速度對畫筆粗細的影響越小 (越不敏感), 結果如下 : 




接下來測試一個較特別的內建函式 mouseClicked(), 此函式會在按一下滑鼠左鍵時被呼叫, 函式內容須自行定義 :

function mouseClicked() {
  //處理程序
  }

下面範例利用此函式在按下滑鼠左鍵時於畫布上畫四條直線, 第五次清空畫布回到初始狀態 :



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    var count=0;   //紀錄滑鼠左鍵被按過的次數
    var clicked=false;   //紀錄滑鼠左鍵被按下一次
    function mouseClicked(){   //自訂內建函式內容
      clicked=true;       //紀錄滑鼠左鍵被按下一次
      ++count;      //增量滑鼠被按下次數
      }
    function setup() {
      createCanvas(400, 300);
      background(1, 70, 100);
      strokeWeight(30);
      stroke('orange');
      }
    function draw() { 
      if(clicked){    //滑鼠剛被按過
        switch(count){   //依據累計被按次數畫不同的直線
          case 1 :
            line(0, 0, width, height);      //左上到右下
            break;
          case 2:
            line(width, 0, 0, height);      //右上到左下
            break;
          case 3:
            line(0, height/2, width, height/2);       //中間水平
            break;
          case 4:
            line(width/2, 0, width/2, height);       //中間垂直
            break;
          default:
            background(1, 70, 100);      //超過 4 次畫布初始化計數器歸零
            count=0;
          }
        clicked=false;
        }
      }
  </script>
</body>
</html>

此例自訂兩個全域變數 count 與 clicked, 並實作了內建函式 mouseClicked(), 每按一次滑鼠右鍵 count 會增量 1, 而 clicked 會被設為 true, 表示滑鼠左鍵有被按一次. 在 draw() 迴圈中會先檢查 clicked, 若為 true 表示滑鼠左鍵剛被按了一次, 用 switch case 分支依據 count 的值依序繪製四種不同的直線, 每畫一條線就將 clicked 重設為 false. 若 count 值超過 4, 表示四條線都畫過了, 這時就會進入 default 將畫布背景重刷, 並將計數器歸零, 結果如下 :




沒有留言 :