Canvas 彙整 | Creative Coding TW - 互動程式創作台灣站 https://creativecoding.in/tag/canvas/ 蒐集互動設計案例、教學與業界資源,幫助你一起進入互動程式創作的產業 Mon, 30 May 2022 14:33:51 +0000 zh-TW hourly 1 https://wordpress.org/?v=6.2.2 https://creativecoding.in/wp-content/uploads/2022/03/cropped-cct-logo-icon-2-32x32.png Canvas 彙整 | Creative Coding TW - 互動程式創作台灣站 https://creativecoding.in/tag/canvas/ 32 32 【互動網頁程式教學】活用GUI Object與繼承概念,完成Canvas物件導向的滑鼠拖曳互動 https://creativecoding.in/2022/06/16/gui-object-canvas/ Thu, 16 Jun 2022 02:19:00 +0000 https://creativecoding.in/?p=2879 利用GUI Object的概念,快速畫出多個物件,利用canvas物件導向概念加上事件偵測,讓滑鼠位置與物件互動,達成亮度的提示以及拖曳物件。

這篇文章 【互動網頁程式教學】活用GUI Object與繼承概念,完成Canvas物件導向的滑鼠拖曳互動 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
很多的讀者經常使用像是canvas這樣的函式庫,東西已經包裝成物件,可以輕鬆的拖曳、偵測點擊。這次的老闆週四寫程式,要來教大家在不使用函示庫的情況下,直接使用純繪圖的canvas生成物件,並偵測事件,來用滑鼠點選跟拖曳物件。

Canvas 物件導向與繼承,來做個土炮架構控制元件吧!(上)成品圖
Canvas 物件導向與繼承,來做個土炮架構控制元件吧!(上)成品圖

目標

這篇文章將會讓你學到:

  1. 向量的基礎知識
  2. 把純繪圖的canvas包成物件的架構
  3. 利用物件導向完成元件與滑鼠的基礎互動

架構

此次影片要實作圖形使用者介面(Graphical User Interface,本篇後以GUI稱之)作為小畫家的操作介面。GUI是採用圖形方式顯示的使用者介面,讓使用者可以使用滑鼠或相關設備操縱螢幕上的圖標或菜單選項,跟早期的電腦使用命令列介面相比,讓使用者在視覺上更容易接受。

實作GUI前,可以先了解一下物件設計的架構:

  1. 成品中的每個方塊都是一個GUI object
  2. 而所有的方塊可以形成一個GUI group
  3. 最後再交由GUI scene 畫出整個場景
GUI結構示意圖
GUI結構示意圖
GUI結構範例
GUI結構範例

了解向量

向量加法示意圖
向量加法示意圖

在畫布的操作中,位置是很重要的一環,位置通常由座標(通常是x,y)構成,物件的起始位置與終點位置,可以看作為「座標的變化量」,而「向量」正是可以輕鬆的表示出座標的變化。(小觀念:向量的定義為 方向的變化量

在程式碼中使用「向量」概念

先來做一個向右跑的小方塊作為使用向量的範例。我們把 HTML 的 Preprocessor 設為 Pug 再把 CSS 設為 Sass,在 HTML 中先畫一個 canvas#mycanvas。

使用「座標」畫出白色方塊物件並讓物件向右移動:

// JS setting
var ww,wh
var canvas = document.getElementById("mycanvas")
var ctx = canvas.getContext("2d")
function init(){
  ww = window.innerWidth
  wh = window.innerHeight
  canvas.width = ww
  canvas.height = wh
}
init()
window.addEventListener("resize",init)
 
//使用「座標」定義物件
var obj = {
  p:{
    x: 0,
    y:0
  },
  size:{
    x:100,
    y:100
  },
  v:{
    x:5,
    y:0
  }
}
function draw(){
  ctx.fillStyle="black" //把上一個畫面蓋掉
  ctx.fillRect(0,0,ww,wh) //把上一個畫面蓋掉
  ctx.fillStyle="white"  
  ctx.fillRect(obj.p.x,obj.p.y,obj.size.x,obj.size.y)
}
setInterval(draw,30)
 
function update(){
  obj.p.x += obj.v.x
  obj.p.y += obj.v.y
}
 
setInterval(update,30)

可以從這一段程式碼中,看到定義物件的位置時,需要個別設置x與y的變量,在 update 物件位置時,也需要個別對x與y做處理。

使用「向量」畫出物件及物件移動:

// 定義向量class,並加入向量的加(add)減(sub)乘(mul)運算
class Vec2{
  constructor(x,y){
    this.x=x
    this.y=y
  }
  add(v){
    return new Vec2(this.x+v.x,this.y+v.y)
  }
  mul(s){
    return new Vec2(this.x*s,this.y*s)
  }
  sub(v){
    return this.add(v.mul(-1))
  }
}
 
var obj = {
  p: new Vec2(0,0), // new 一個向量物件
  size:new Vec2(100,100), // new 一個向量物件
  v: new Vec2(5,0) // new 一個向量物件
}
function draw(){
  ctx.fillStyle="black"
  ctx.fillRect(0,0,ww,wh)
  ctx.fillStyle="white"  
  ctx.fillRect(obj.p.x,obj.p.y,obj.size.x,obj.size.y)
}
setInterval(draw,30)
 
function update(){
  obj.p = obj.p.add(obj.v) //使用向量概念中的「相加」
}

setInterval(update,30)

先實作出一個向量類 Vec2,後續使用 new Vec2 就可以使用向量。在後續需要大量生成物件時,可以比座標更容易生成大量物件。

小結:使用向量物件讓程式碼更簡潔,也讓位置設置變得更容易了。

實作

此次實作會用到老師預先製作好了template,內容包含了向量類 Vec2、畫布設置以及一些滑鼠的事件及記錄,可以參考這裏,需要的同學可以fork一份回自己的codePen再往下繼續做呦!

製作物件

一開始需要建立一個GUIObject 類,方便後續快速製造出大量的GUI object。另外再預先定義一個Scene類,並且在裡面增加 addChild(),把所有的GUIObject可以放在children 中,方便畫出。

class  GUIObject{
  constructor(args){
    let def={
      p: new Vec2(0,0),
      size: new Vec2(0,0)
    }
    Object.assign(def,args)
    Object.assign(this,def)
  }
  draw(){
    ctx.fillStyle="white"
    ctx.fillRect(this.p.x,this.p.y,this.size.x,this.size.y)
  }
}
 
class Scene{
  constructor(args){
    let def={
      children: []
    }
    Object.assign(def,args)
    Object.assign(this,def)
  }
  addChild(obj){
    this.children.push(obj)
  }
  draw(){
    this.children.forEach(obj=>{
      obj.draw()
    })
  }
}

接著可以利用剛剛定義好的GUIObject直接畫出一個長方形。

var rect = new GUIObject({ 
  p: new Vec2(30,30),
  size: new Vec2(100,30)
})
 
function draw(){
  ...
  rect.draw()
  ...
}
利用物件導向畫出一個方塊
利用物件導向畫出一個方塊

函式多載

現在要來做一點程式碼的優化。

函式多載是讓一個同名函式帶入的參數可以有不同類型跟不同數量。在函式內做類型判別跟數量判別,可以讓後續在呼叫函式的時候更容易。

現在scene的addChild只能放入一個物件,並且無法判斷進入的物件是不是scene期待的GUIObject。因此要在這邊修改addChild,讓user給什麼都可用,無論是給一個GUIObject、給多個GUIObject或給一個array都可以運行。

addChild(){
  if (arguments.length==1){ // 如果input的arguments只有一個
    if(arguments[0] instanceof GUIObject){ // 如果input的剛剛好是GUIObject
      this.children.push(arguments[0])
    }
    if(arguments[0] instanceof Array){ // 如果input的是array,這邊暫時不檢查裡面是否皆為GUI object
      this.children = this.children.concat(arguments[0])
    }
  }else{
    for(var i=0;i<arguments.length;i++){ // 如果input了很多個object
      this.children.push(arguments[i])
    }
  }
}

addChild得到的input為arguments, 可以看到程式碼中對arguments進行判斷與操作。我們在這邊繪製兩個長方形,讓Scene蒐集GUIObject後一起畫出。(後續會使用這個方式)

var scene = new Scene()
function init(){
  let rect = new GUIObject({
    p: new Vec2(30,30),
    size: new Vec2(100,30)
  })
  let rect2 = new GUIObject({
    p: new Vec2(130,30),
    size: new Vec2(200,200)
  })
  scene.addChild(rect,rect2) // 防呆裝置已啟動
}
function draw(){
  scene.draw()
}
利用修改後的function生成多個方塊
利用修改後的function生成多個方塊

物件與滑鼠互動

完成物件後,我們最後要加入滑鼠與物件的幾種互動。

(1) 滑鼠靠近方塊,改變顏色

這個小動態的目的是要讓user知道滑鼠有沒有放在物件上,先讓物件亮度降低,當物件與滑鼠位置重疊,便讓物件亮度上升:

a. 新增一個 isHovering 參數,如果是true就調整物件亮度

class GUIObject{ 
  constructor(args){
    let def={
      ...,
      isHovering: false // 新增一個 isHovering 參數,預設為false
    }
   ...
  }
  draw(){
    ctx.fillStyle="rgba(255,255,255,0.4)"
    if (this.isHovering){
      ctx.fillStyle="rgba(255,255,255,1)"
    }
    ...
  }
}

b. 要偵測滑鼠位置(在Scene 類加入事件偵測)

class Scene{ 
  constructor(args){
    let def={
      ...,
      el: null // 要知道是哪個document, 在new的時候指定
    }
    ...
    this.init()
  }
  init(){
    // 事件偵測
    this.el.addEventListener("mousemove",(evt)=>{
      let mousePos = new Vec2(evt.x,evt.y) // 拿滑鼠位置
      // 處理滑鼠移動
      this.children.forEach(obj=>{
        obj.handleMouseMove(mousePos) //傳入滑鼠位置
      })
    })
  }
}
 
// 在new的時候指定document
var scene = new Scene({
  el: document.querySelector("canvas")
})

c. 加入 handleMouseMove 來改變 isHovering 參數的值

class GUIObject{
  ...
  handleMouseMove(pos){
    let point1 = this.p,
        point2 = this.p.add(this.size)
    // 判斷滑鼠是否在物件範圍內
    if (pos.x>point1.x && pos.x < point2.x &&
      pos.y>point1.y && pos.y < point2.y){
      this.isHovering = true
    }else{
      this.isHovering = false
    }
  }
}
滑鼠靠近方塊,改變方塊顏色並改變鼠標圖示
滑鼠靠近方塊,改變方塊顏色並改變鼠標圖示

(2) 用滑鼠拖移方塊

當滑鼠位置與物體重疊時,點擊滑鼠右鍵,可以拖移物件:

a. 新增 isDraggable 參數來決定物件是否可以被拖移

class GUIObject{
  constructor(args){
    let def={
      ...
      isDraggable: false
    }
    ...
  }
 
// 在new GUIObject時要記得把 isDraggable設成true
function init(){
  let rect = new GUIObject({
    p: new Vec2(30,30),
    size: new Vec2(100,30),
    isDraggable: true
  })
}

b. 新增 InRange function 來判斷滑鼠位置是否在物件內 (可以重複使用)

class GUIObject{
  ...
  inRange(pos){
    let point1 = this.p,
        point2 = this.p.add(this.size)
    return pos.x>point1.x && pos.x < point2.x &&
           pos.y > point1.y && pos.y < point2.y
  }
  ...
}

c. 新增 dragging 參數來判斷物件是否正在被拖移中

class GUIObject{
  constructor(args){
    let def={
      ...
      isDraggable: false,
      dragging: false
    }
   ...
  }
}

d. 新增 handleMouseDown 及 handleMouseUp 來處理滑鼠按鍵的事件處理

class GUIObject{
  ...
  handleMouseMove(pos){
  if (this.inRange(pos) ){
    this.isHovering = true
  }else{
    this.isHovering = false
  }
  if (this.dragging){
    this.p = this.p.add(pos.sub(this.lastPos))
    this.lastPos = pos
  }
}
 
handleMouseDown(pos){
  if (this.inRange(pos)){
    this.lastPos = pos
    if (this.isDraggable){
      this.dragging = true
    }
  }
}
handleMouseUp(){
  this.lastPos = null
  this.dragging = false
}

e. 事件偵測加入滑鼠按鍵按下與鬆開的偵測

class Scene{
  ...
  init(){
    ...
    // 按下滑鼠按鍵
    this.el.addEventListener("mousedown",(evt)=>{
      let mousePos = new Vec2(evt.x,evt.y)
      this.children.forEach(obj=>{
        obj.handleMouseDown(mousePos)
      })
    })
    // 放開滑鼠按鍵
    this.el.addEventListener("mouseup",(evt)=>{
      let mousePos = new Vec2(evt.x,evt.y)
      this.children.forEach(obj=>{
        obj.handleMouseUp(mousePos)
      })
    })
 ...

f. 改變鼠標,當鼠標指到物件時,鼠標會從箭頭變成👉🏻

class Scene{
  constructor(args){
    let def={
      …
      flag: false
    }
  }
  ...
  update(){
    if(this.flag){
      this.el.style.cursor="pointer"
    }else{
      this.el.style.cursor="initial"
    }
  }
  init(){
    let flag = false
    this.el.addEventListener("mousemove",(evt)=>{
      this.flag = false
      let mousePos = new Vec2(evt.x,evt.y)
      this.children.forEach(obj=>{
        if(obj.handleMouseMove(mousePos)){
          this.flag = true
        }
      })
    })
  }
  ...
}
// 讓scene update起來
function update(){
  time++
  scene.update()
}
滑鼠拖移方塊位置成果圖
滑鼠拖移方塊位置成果圖

完成滑鼠與物件的基礎互動啦!

結語

以上就是老闆利用Canvas做滑鼠控制物件的網頁基礎互動,這次在前面講解概念的部分花了較多時間,來不及完成小畫家,希望對於剛開始接觸 Canvas 的你有一些幫助。

再次快速總結步驟:

  1. 利用GUI Object的概念,快速畫出多個物件
  2. 加上事件偵測,讓滑鼠位置與物件互動,達成亮度的提示
  3. 加上滑鼠按鍵的事件偵測,讓滑鼠拖移物件

看著框框跟隨著滑鼠的移動,真的很有成就感啊!

老闆的工商時間

想了解更多如何寫出漂亮清晰的網頁嗎?老闆在 Hahow 的教學課程 動畫互動網頁程式入門(HTML/CSS/JS) 用平易近人的語言,用簡單的方式帶你作出不簡單的網頁。已經有網頁程式基礎了嗎?進階課程 動畫互動網頁特效入門(JS/CANVAS) 能讓你紮實掌握 JavaScript 程式與動畫基礎以及進階互動,整合應用掌控資料與顯示的Vue.js前端框架,完成具有設計感的互動網站。

此篇直播筆記由幫手 Y-Y-H 協助整理

墨雨設計banner

這篇文章 【互動網頁程式教學】活用GUI Object與繼承概念,完成Canvas物件導向的滑鼠拖曳互動 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
【Canvas創作教學】畫個偵測敵人的動態雷達圖網頁 https://creativecoding.in/2021/09/23/canvas-creation-enemies-radar/ Thu, 23 Sep 2021 02:45:00 +0000 https://creativecoding.in/?p=1450 本次創作直播內容透過 Canvas 物件,將網頁當作畫布,繪製不同的圖形,完成模擬偵測敵人的雷達機介面;運用三角函數概念,以及模組化程式,簡單製作出動態的網頁,詳細的步驟解釋,無論有沒有基礎,都能輕鬆跟著說明完成,剩下的就由你自行發揮囉!

這篇文章 【Canvas創作教學】畫個偵測敵人的動態雷達圖網頁 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
今天我們要來製作一個可以掃描出敵人的動態雷達圖,讓隱藏在地圖深處的隨機敵人現身。本次工具主要透過 Canvas 物件,將網頁當作畫布繪製各式不同的圖形,而在觀念上與上一次的時鐘是有些相似的,都運用了三角函數的概念,不過會有一些延伸的知識點,像是如何將相似的圖形以模組化的方式來撰寫,以及敵人在被掃略線掃到時,要如何顯現後再漸漸地消失。

透過此次教學,你會學到

  • 認識 Canvas,並將網頁在當作畫布一樣繪製線條、顏色與形狀
  • 學習觀察相似物件的特性,以模組化的方式呈現,減少程式碼的撰寫

認識常用的 Canvas 屬性

在 Canvas 中,每一次繪圖都是分為一個個區段的,在執行步驟上可分為三個步驟,這裡以像是操作機台白話的方式來做比擬。

  1. 按下繪圖開始樣式按鈕
  2. 設定所要畫的圖形,像是圖形或是線條,可以是單一或是多個都沒問題
  3. 確認上述設定圖形沒問題,開始畫圖,將圖形顯示在畫面上

上述三個步驟對應到 Canvas 的屬性分別是:

  1. 繪圖開始 : beginPath()
  2. 圖形設計 : 在圖形設計上可分為兩種,一種實心呈現色塊的圖形,另一種則是單一線條,或是以單一線條所構成的中空圖形,常見的畫圖方式有以下:
    • 畫正方形 rect(x 位置,y 位置,寬度,高度)
    • 畫弧形 arc(x 位置,y 位置,半徑,起始角度,終點角度)
    • 畫線條時會有兩個屬性搭配使用,分別是 moveTo(x 位置,y 位置) ,這僅會移動畫筆,但不畫線,而另一個則是 lineTo(x 位置,y 位置),這則是會以下筆的方式移動到特定位置。
    在圖形設定上也包含樣式,像是以顏色來說有 fillStyle 用於指定色塊,而 strokeStyle 則是指定線條顏色。
  3. 開始畫圖 : 這裡同樣在兩種不同的圖形有特別的指定方式,在色塊上是 fill(),而線條則是 stroke()

在正式進入本章的主題前,老闆找到了一個線上繪製數學圖形的網站,嘗試以不同的方式來說明極座標的概念。在先前的文章〈來用可怕的三角函數做網頁吧!Part 1Part 2〉說明了以極座標表示位置的方式,而在本次範例中,一樣會使用到極座標概念,想要回顧上次的教學內容,歡迎點選上面文章連結複習一下再開始!

在圖1-1中,看到網頁中的步驟三定義的變數 t,用於表示角度,而步驟四則顯示極座標 (cos(t), sin(t))的表示方式畫出圓點,當移動 t 的拉桿時,同時也代表角度正在改變,可以看到在畫面中的點以座標 (0, 0) 的位置為中心,在周圍以半徑為 1 單位的距離移動。

圖1-1 : 變數 t,用於表示角度
圖1-1

接著在圖 1-2 中的步驟五寫了一個定義圓的方程式,讓前面所提的圓心軌跡顯示出來。

而步驟六則定義了 r 也就是圓的半徑範圍,預設上 r 的半徑大小為 1 ,所以可以看到拖拉變數 t 也就是角度範圍時,圓點就在圓形軌跡上面移動,而當改變半徑 r 時,則會改變整個圓的大小。

圖1-2

為了讓圓點可以在圓的軌跡上,而不是固定在半徑為 1 的範圍中,所以在圖 1-3 的步驟四中,在 x 與 y 的座標位置都乘上了半徑 r,這樣一來,當 r 的大小有變化時,不僅圓的大小會改變,可以看到圓點距離中心點的位置也在改變。

在步驟四中所呈現的就是點在座標系統的呈現方式,其涵蓋了兩個變數,分別是距離中心點位置的半徑 r 以及角度 t

圖1-3
圖1-3

前置作業

設定 Code Pen

在 Code Pen上開一個新的pen,將HTML的預處理器設定成Pug、CSS的預處理器設定成Sass、Js 中引入 Jquery。

引入雲端字形

在此次範例會使用到外部字體來作為搭配,讓作品更好看。

  • 首先進入到 Google Fonts 中,搜尋 Abel 後進入頁面。
  • 在 Styles 區塊中的右側有加號 Select this style,點選後在右側會跑出視窗。
  • 開啟 Code Pen 中的 css 設定,將剛剛所選的連結貼在add another resource新增的欄位中,並點選儲存,這樣一來就可以使用了。

上述都設定好後,就要正式進入主題囉!

一、基礎版面

為了可以畫圖,所以需要一張畫布,那就是 Canvas,並指定 ID 為 #myCanvas。另外放置了訊息,分別是標題,以及一些訊息,這裡先暫時以 temp 作為代稱,這個在後面會更改為顯示掃到敵人時,敵人所在的角度與位置。

//HTML
canvas#myCanvas
.info
    h1 Boss, CODING Please
    p.message temp

接下來在樣式上做初始設定,我們希望是滿版的網頁,所以在長與寬都設置為 100%,而在預設上內距與外距會跑出來,但這些我們也不要,所以在 padding 與 margin 上設置為 0。

屬性 overflow 則是決定當物件超出原本的畫面時,該怎麼處理物件的顯示方式,這裡選擇 hidden,代表超出範圍的即隱藏起來。在字體上則是使用先前在 google font 所引入的 Abel。

//CSS
html, body
  //填滿視窗
  width: 100%
  height: 100%
  padding: 0
  margin: 0
  overflow: hidden
  font-family: Abel
步驟1-1設置基礎版面字樣
步驟1-1設置基礎版面字樣

現在畫面上仍看不到 canvas 的蹤影,所以指定背景顏色 #333 讓它顯示出來,所以發現它小小一個,但我們希望它撐滿整個版面,不過這個效果老闆選擇後續在 js 修改,這裡僅先調整訊息的位置。

訊息要放置在畫面的左下角,所以將它的定位改為絕對定位,並讓它離下方與左側各距離 50px。在字體的顏色上,敵人的訊息顏色是使用老闆特調的黃金色 rgb(185, 147, 98),標題的話則是白色,雖然標題暫時隱藏了,不過待會背景會設置為深色,就會看見標題了。而在這兩行字的間距上老闆希望可以距離近一些,所以將兩者 margin 都拿掉設置為 0。

//CSS
html, body
  //填滿視窗
  width: 100%
  height: 100%
  padding: 0
  margin: 0
  overflow: hidden
  font-family: Abel

canvas
  background-color: #333

.info
  position: absolute
  left: 50px
  bottom: 50px

h1
  color: white
  letter-spacing: 3px
  margin: 0

.message
  margin: 0
  color: rgb(185, 147, 98)
步驟1-2

二、基礎版面樣式設置

畫圖的第一步就是要取得畫布這個元素,在預設上我們有引入 Jquery,所以可以使用錢字號$的方式來抓取元素,所以這裡以錢字號加上在 html 中所設定 canvas 的 ID – #myCanvas,並且記得加上第零個的位置,這樣才會是 html 的元素。

接著處理這張畫布的渲染環境,由於是要在平面的範圍上作圖,所以使用 c.getContext("2d") 來存取的繪圖區域。

有了畫布後,要指定畫布的長寬,讓它可以撐滿整個畫面,所以創建兩個變數分別是 wwwh 來記錄畫面上的寬度與長度。另外,由於後續需要將主要的物件放置在畫面中央,所以也需要創建一個名為 center 的變數來記錄中心點的位置。

接著建立 getWindowSize()函數來指定畫布長寬與中心點,透過錢字號抓取 window 網頁元件,並取其視窗長度與高度的屬性 outerWidth()outerHeight(),放入到變數 ww, wh 中。有了長寬的數值後,就可以將它指定為畫布的長與寬了,而中心點的位置則是將兩者數值都除以 2。

設定好函數後,記得在下面呼叫一次剛剛所撰寫的函式,才會呈現所寫的效果。不過這裡有個問題,當我們拉動網頁視窗時,畫面並不會隨之更新,原因在於函式僅執行了一次,為了解決這個問題,所以需要加上 $(window).resize(getWindowSize) ,代表著當畫面有重新改變大小時,會重複執行一次 getWindowSize() 這個函式。

//JavaScript
var c = $("#myCanvas")[0];
var ctx = c.getContext("2d");
var ww, wh;
var center = { x: 0, y: 0 };

function getWindowSize() {
  //設定大小
  ww = $(window).outerWidth();
  wh = $(window).outerHeight();

  c.width = ww
  c.height = wh

  //重新設定中心點
  center = { x: ww / 2, y: wh / 2 };
}

getWindowSize();
//設定當網頁尺寸變動的時候要重新抓跟設定大小、中心
$(window).resize(getWindowSize);
步驟二:基礎版面畫製
步驟二:基礎版面畫製

三、繪製一個矩形

設定好畫布後,接著要嘗試在畫布上繪製圖形,這裡先來畫畫看一個正方形。建立一個名為 draw()的函式,裡面 ctx 也就剛剛所抓取的畫布名稱,而rect 則代表要繪製一個矩形,參數分別為 (x 起始位置,y 起始位置,寬度,長度)。為了讓它可以呈現出動態的效果,所以使用 setInterval(draw, 10),設定每十毫秒就執行一次 draw(),並且創建一個數值會向上遞增的變數 time 放到 x 位置中,這樣一來物件每十秒就會向前方移動一單位的距離。

//JavaScript
setInterval(draw, 10)
var time = 0;

function draw() {
  time += 1;
  ctx.rect(20 + time, 20, 150, 100);
  ctx.stroke();
}
步驟三:畫一個會跑的矩形
步驟三:畫一個會跑的矩形

但是這個時候會發現,舊的元素並沒有被清除掉。解決這個問題的方法為,在每一次繪圖的時候,也重新再一次指定背景。這邊可以注意到繪製填滿圖形與線條是不一樣的,若是要填滿圖形是 ctx.fill(),而繪製線條的話則是 ctx.stroke(),詳細的原理在第四步驟會做說明。

//JavaScript
function draw() {
  time += 1;

  ctx.fillStyle = "#fff"
  ctx.beginPath();
  ctx.rect(0, 0, 500, 500);
  ctx.fill();

  ctx.rect(0 + time, 0, 50, 50);
  ctx.stroke();
}
步驟三:每畫一個新的圖形前都要再蓋一次背景,才可以清除舊的元素
步驟三:每畫一個新的圖形前都要再蓋一次背景,才可以清除舊的元素

這樣子呈現的效果就沒有問題了,不過我們是要在整張畫布上作畫,當然需要再改變畫布的大小,為了確保可完整地覆蓋背景,所以設置一個很大的數值覆蓋在背景上,並將原本測試使用的矩型移除掉。

//JavaScript
function draw() {
  time += 1;

  //更新為整張畫布大小為黑色+放大
  ctx.fillStyle = "#111"
  ctx.beginPath();
  ctx.rect(-2000, -2000, 4000, 4000);
  ctx.fill();
}
步驟三:改變畫布大小
步驟三:改變畫布大小

這是現在畫面上所呈現的樣子,雖然看上去跟步驟二所呈現出的效果是一樣的,但是現在的這個背景會不斷更新,我們接下來將圖形繪製上去的時候,也才不會造成圖形疊加在一起的問題。

四、畫垂直線

背景設定好後,就要來繪圖囉,不過在開始之前,要先來說明在 canvas 中的座標系統:在canvas 中,當增加 y 數值的時候,會發現物體往畫面的下方移動,這是 canvas 預設的座標系統。而我們所熟悉的座標系統中,X 數值增加是向右,而 Y 數值增加則是向上,所以這裡要調整一下,在 sass 中改變 Y 軸的軸向。

//CSS
canvas
  background-color: #333
  transform: scaleY(-1)
//JavaScript
function getWindowSize() {
  ...
  center = { x: ww / 2, y: wh / 2 };
  ctx.restore();
  ctx.translate(center.x, center.y);
}

接下來就是要繪製 x 軸與 y 軸,線條是從左邊至右邊以及從下面至上面。在畫線上會需要使用到兩個指定,分別是 moveTolineTo,可以想像你手中現在拿著一支筆,moveTo代表手移動至該點的位置,但是不接觸紙張,而lineTo 則是從現在這個位置,將畫筆在紙上移動至 lineTo 所指定的位置上,所以繪製 x 軸上就是先移動至畫面的左側 (-ww / 2, 0) 的位置,接著移動到 (ww / 2, 0)的點上,而 y 軸也是相同的道理。

這邊為何會需要在最後加上 stroke() 呢 ? 原因在於,如果每下達一個指令時,渲染機制就馬上執行畫線的話,這樣是很吃效能的,所以系統是預設讓我們在新增完所有路徑的時候,再使用 stroke()將剛剛所指定的路徑繪製出,同樣的概念也適用於 fill()

//JavaScript
function draw() {
  time += 1;

  ctx.fillStyle = "#111"
  ctx.beginPath();
  ctx.rect(-2000, -2000, 4000, 4000);
  ctx.fill();

  // 畫座標軸
  ctx.strokeStyle = "rgba(255,255,255,0.5)";
  //x
  ctx.moveTo(-ww / 2, 0);
  ctx.lineTo(ww / 2, 0);
  //y
  ctx.moveTo(0, -wh / 2);
  ctx.lineTo(0, wh / 2);
  ctx.stroke();
}
步驟四:畫製X、Y軸
步驟四:畫製X、Y軸

五、利用極座標畫線條

建立好座標軸後,就要來開始挑戰本章的大魔王 – 動態掃略線。首先第一步是要以極座標來畫線,會需要兩個重要的數值,分別是線條的長度 r 以及線條的角度 deg。

線條要以中心點為起點,所以將畫筆移動圓點 (0, 0)位置,接著是移動至另一個端點,也就是( r * cos(角度), r * sin(角度) ),但是此時會發現畫面上所呈現的效果怎麼跟預想的不太一樣,看上去很明顯並非是 45 度。

//JavaScript
var color_gold = "185,147,98";
function draw() {
  ...
  // 以極座標方式繪製線條
  ctx.strokeStyle = "rgba(" + color_gold + ",1)";
  var r = 100;
  var deg = 45;
  ctx.moveTo(0, 0);
  ctx.lineTo(r * Math.cos(deg), r * Math.sin(deg));
  ctx.stroke();
}
步驟五:利用極座標畫線條
步驟五:利用極座標畫線條

原因在於口語上我們表達會是 90度、180度,但是實際上它的單位會是弧度,比如說 180 度相當於是角度 PI,其數值的大小為 3.14,而非 180,所以這裡需要進行單位上的轉換,建立一個 deg_to_pi 變數,並將 π 除上 180,接著我們在 console 裡面試試看呈現出的結果,可以看到角度乘上 deg_to_pi 時,就會是正確的徑度數值,這樣一來後續在定義角度的時候,我們就可以用熟悉的角度來去定義囉。

角度轉換為π (Pi)
角度轉換為π (Pi)

下面將定義的變數 deg_to_pi 與角度 deg 做相乘後,所呈現出就會是正確的徑度數值與角度。

//JavaScript
var color_gold = "185,147,98";
var deg_to_pi = Math.PI / 180;  //新增轉換定義

function draw() {
  ...
  // 以極座標方式繪製線條
  ctx.strokeStyle = "rgba(" + color_gold + ",1)";
  var r = 100;
  var deg = 45;
  ctx.moveTo(0, 0);
  ctx.lineTo(r * Math.cos(deg_to_pi * deg), r * Math.sin(deg_to_pi * deg));
  ctx.stroke();
}
步驟五:呈現出正確的45度角
步驟五:呈現出正確的45度角

做到這邊,確實有達成所希望的效果沒錯,不過如果每次要設定點的位置時,都需要寫一長串的話似乎有點麻煩,所以老闆這邊習慣寫一個可以計算點位置的函數,只需要傳入兩個參數,分別是長度以及角度後,就可以得到一個物件,裡面包含點的 x 位置與 y 位置。

除了更改點的呈現方式之外,這裡有個小地方要注意,就是會發現所有的線條,包含前面設定的 xy 軸都變成了金色,原因在於繪圖系統又重新將它們漆上了金色,為了可以與路徑的設定切分,需要加上一個另一個的 beginPath() 告知繪圖系統要建立一個新的路徑。

//JavaScript
var color_gold = "185,147,98";
var deg_to_pi = Math.PI / 180
// 1. 更改為 function 從極座標轉串成點
function Point(r, deg) {
  return {
    x: r * Math.cos(deg * deg_to_pi),
    y: r * Math.sin(deg * deg_to_pi)
  }
}

function draw() {
  ...
  ctx.strokeStyle = "rgba(" + color_gold + ",1)";
  var r = 100;
  var deg = 45;
  var newpoint = Point(r, deg)  // 2.以函數取得 newPoint,
  ctx.beginPath();  // 3. 為了避免前面的軸線再次重新被描一次而變成金色,所以這裡要加上 beginPath
  ctx.moveTo(0, 0);
  ctx.lineTo(newpoint.x, newpoint.y);
  ctx.stroke();
}

六、扇形掃描線

扇型的掃描線我們不使用 arc 來繪製,是因為無法達成透明度變化的效果,而要構成掃描線的樣式,可以透過每一個單位的線條或是三角形組合而成,這裡先以較為簡單的線條方式來繪製看看。

每次角度改變一度,就繪製一條線條,所以創建一個 for 迴圈,迴圈執行的次數 line_deg_len 也就是弧形的角度大小,在線條角度上以 time 這個變數的數值為基準減去迴圈中的 i 值,這樣就有 100 個連續相差 1 的角度數值,另外因為 time 是會隨時間變化的,連帶著這 100 個角度值也會不斷變化,進而生成了動態的旋轉扇形。

在透明度的設定上,則是在每一條線畫的時候就指定個別的透明度,不過由於透明度的值是 0~1,我們不能直接放 i 值,而是要放 i / line_deg_len

//JavaScript
function draw() {
  ...
  ctx.strokeStyle = "rgba(" + color_gold + ",1)";
  var r = 200;
  var deg = time;
  var newpoint = Point(r, deg)

  var line_deg_len = 100;  // 弧線的角度
  for (var i = 0; i < line_deg_len; i++) {
    var deg = (time - i)
    var newpoint = Point(r, deg)

    ctx.beginPath();
    ctx.strokeStyle = "rgba(" + color_gold + "," + (i / line_deg_len) + ")";
    ctx.moveTo(0, 0);
    ctx.lineTo(newpoint.x, newpoint.y);
    ctx.stroke();
  }
}
步驟六:以線條畫製扇形掃描圖像
步驟六:以線條畫製扇形掃描圖像

現在有一個漸層且會動態旋轉的扇形了,但是有兩個地方怪怪的需要調整:一個是它旋轉的方向反了,透明度較低的線條在前方;另一個則是在以線條的方式繪製下,會有明顯紋路的問題,所以接下來我們要改以畫三角形的方式來試試。

三角形相較於線條會複雜一些些,但是原理上是一樣的,只是線條是兩個點形成一條線,而三角形是三個點形成一個面,相較於線條的單一角度 time - i,還需要另一個相鄰的角度time - i - 1,並以這兩個角度來計算出每一個三角形的兩個頂點位置後,再將頂點與 (0, 0) 的位置連接起來。在最後要將原先用於線條的 stroke() 改為用於色塊的 fill()。這樣透過以小三角型色塊的方式呈現,相較於一條條的線條,在視覺上看起來會更加滑順。

接下來處理透明度的方向問題,由於 i 是由 0 開始的,以至於在一開始線條的透明度為 0,這裡有個小技巧就是以 1 減去原先設定的數值,這樣順序就會從由小至大變成由大至小了。

//JavaScript
function draw() {
  ...

  // 改用三角形畫圖
  var line_deg_len = 100;
  for (var i = 0; i < line_deg_len; i++) {
    // var deg = (time-i)
    // var newpoint = Point(r, deg)
    var deg1 = (time - i - 1)
    var deg2 = (time - i)

    var point1 = Point(r, deg1)
    var point2 = Point(r, deg2)
    var opacity = 1 - (i / line_deg_len)

    ctx.beginPath();
    ctx.fillStyle = "rgba(" + color_gold + "," + opacity + ")";
    ctx.moveTo(0, 0);
    ctx.lineTo(point1.x, point1.y);
    ctx.lineTo(point2.x, point2.y);
    // ctx.stroke();
    ctx.fill()
  }
}
步驟六:改為用三角形繪製掃描線,更為平滑,也改好方向
步驟六:改為用三角形繪製掃描線,更為平滑,也改好方向

七、敵人系統

在第七章敵人系統中是比較大的章節,因此將其拆分成四個小節來做說明,分別有:

  • 如何隨機地產生一組敵人
  • 如何將隨機的敵人擺放至畫面上
  • 如何判定掃略線掃到敵人,並且敵人會做出相對應的變化
  • 調整敵人樣式,使敵人看起來更完整

在開始製作敵人系統的這個章節前,要先來建立一個 Color 的函式,透過傳遞一個參數代表透明度來指定需要的顏色,後續也會比較方便。

//JavaScript
// 建立 Color 函數來做使用
function Point(r, deg){...}

function Color(opacity) {
  return "rgba(" + color_gold + "," + opacity + ")";
}

function draw(){...}

7-1 隨機產生敵人

首先創建一個長度為 10 的陣列,並且在裡面放入空的陣列,在圖A中可以看出 enemies 的值為一排空陣列。

接著透過 map 函數做陣列中元素的轉換,將原本空的陣列放入兩個值,分別為 x 與 y,其結果可在圖B中所見。而實際上我們需要創建的資訊一共有三個,分別是半經 r 、角度 deg 以及透明度 opacity,如圖C。

由於位置是隨機產生的,所以在半徑與角度上都是使用 Math.random() ,這裡值得一提的是,角度我們可以直接使用熟悉的 0~360 的角度系統,原因是在於我們使用先前已經寫好了 Point() 函數取得點的位置,而函數中也已經處理好角度轉換的問題了。

// 建立十個空物件的寫法 (圖A)
var enemies = Array(10).fill({})  

// 若建立十個物件,物件中 key 為 X、Y (圖B)
var enemies = Array(10).fill({}).map(
  function (obj) {
    return {
      x: 5,
      y: 5
    }
})

// 我們要繪製敵人則是需要建立十個空陣列,物件中 key 為半徑 r、角度 deg、透明度 opacity (圖C)
var enemies = Array(10).fill({}).map(
  function (obj) {
    return {
      r: Math.random() * 200,
      deg: Math.random() * 360,
      opacity: 1
    }
})
建立十個空物件的寫法 (圖A)
建立十個空物件的寫法 (圖A)
若建立十個物件,物件中 key 為 X、Y (圖B)
若建立十個物件,物件中 key 為 X、Y (圖B)
我們要繪製敵人則是需要建立十個空陣列,物件中 key 為半徑 r、角度 deg、透明度 opacity (圖C)
我們要繪製敵人則是需要建立十個空陣列,物件中 key 為半徑 r、角度 deg、透明度 opacity (圖C)

7-2 敵人在畫面上顯示

隨機產生出敵人後,接著就是將敵人依序顯示在畫面上。

除了使用 for 迴圈來去存取陣列中的物件外,另一種方式則是使用 forEach,這裡使用 obj 來當作每一個元素的代稱,在每一個 obj 中都有三個屬性可做存取,分別是半徑 r 以及角度 deg以及透明度 opacity。由於這裡是要計算出點的位置,所以僅需要前面兩個值,再放入之前所創建的 Point 函數,便可得到該點所在的確切位置 obj_point 了。

敵人先以圓形來做為表示,要畫圓的方式是使用 arc ,其語法為

arc (中心點 x 位置,中心點 y 位置,圓的半徑,起始角度,終點角度)

由於是要畫完整的圓,所以在角度上設定為 0 至 2π。

//JavaScript
function draw(){
  ...

  enemies.forEach(function (obj) {
    //本體
    ctx.fillStyle = Color(1);
    var obj_point = Point(obj.r, obj.deg);

    ctx.beginPath();
    ctx.arc(
      obj_point.x, obj_point.y,
      10, 0, 2 * Math.PI
    );
    ctx.fill();
  })
}
步驟7-2:以原點代表敵人隨機出現的位置
步驟7-2:以原點代表敵人隨機出現的位置

7-3 當線條角度與敵人一樣時,將敵人透明度變成 1

前面只是確認每一個點確實有被設定,而且都有顯示出來。接著要來實作掃略線的功能,當線條與敵人重疊時,敵人才會顯示,並在一段時間後再次消失。

首先要先定義掃略線,在前面的步驟中,定義了 time 這個變數,在每次執行一次 draw() 時,數值便會向上加 1,而且透過 time 定義角度後,將扇形掃略的效果所畫出來,我們要定義掃略線當下的位置,也是使用到 time 這個變數。

不過前面有提到 time 會隨著執行時間不斷地增加,但是角度的範圍僅限於 0~360,所以需要將 time 取 360 的餘數time % 360,讓數值維持在 0~360 之間。

有了掃略線 line_deg 的角度後,接著就是跟敵人的角度來做比較,當兩者的角度差小於一定數值的時候,就代表兩者有重複到了,在這裡老闆以兩者設為兩者的距離取絕對值後小於 1 ,若是符合條件,便將透明度變為 1。而在敵人出現後,要讓他漸漸消失,等待下一次被掃略後出現,只需要將其透明度乘上一個小於 1 即可,透明度的值便會漸漸從 1 趨近於 0。

//JavaScript
var enemies = Array(10).fill({}).map(
  function (obj) {
    return {
      r: Math.random() * 200,
      deg: Math.random() * 360,
      opacity: 0  //  1. 敵人透明度設為 0
    }
})


function draw() {
  var line_deg = time % 360;  // 2. 定義掃略線
		  
  enemies.forEach(function (obj) {
    //本體
    ctx.fillStyle = Color(obj.opacity);
    var obj_point = Point(obj.r, obj.deg);
		
    ctx.beginPath();
    ctx.arc(
      obj_point.x, obj_point.y,
      10, 0, 2 * Math.PI
    );
    ctx.fill();
		
    // 3. 判定掃略線與敵人位置
    if (Math.abs(obj.deg - line_deg) <= 1) {
      obj.opacity = 1;
    }
    obj.opacity *= 0.99
    })
}
步驟7-3:當掃描線的線條角度與敵人一樣時,將敵人透明度變成 1
步驟7-3:當掃描線的線條角度與敵人一樣時,將敵人透明度變成 1

7-4 修改敵人樣式

目前敵人的樣式上只有單個圓圈而已,顯得有些單調,我們想將敵人的符號加上叉叉,以及一個有動態向外擴展的圓圈。在開始之前,先將原先的圈圈大小做調整,將半徑為 10 大小縮小為 4。

叉叉為兩個線交疊而形成的,而線條的長短代表叉叉的大小。老闆先是定義了 x_size 這數值大小,這是繪製線條值所移動的距離,也代表叉叉的大小。兩條線分別是從左下至右上以及右下至左上,中心點的位置與圓圈同為 (obj_point.x obj_point.y),要移動至左下角時,在 X 座標與 Y 座標的值都是減去 x_size,而右上角的點則是都加上 x_size,其餘另外兩個點則以此類推。

步驟7-4:敵人樣式的叉叉解說
步驟7-4:敵人樣式的叉叉解說

接著來畫向外擴張的圈圈,這裡可以直接複製上面的開始畫圈圈的程式碼,並改成 strokeStyle 以線條的方式呈現。一個向外擴展的圈圈代表半徑的大小隨著時間在不斷變化的,在這裡當然是可以再寫一個變數來代表動態的半徑大小,但是其實我們已經有現成的變數可以使用,也就是透明度,所以不需要再額外寫一個。那就將半徑乘上 obj.opacity 吧,此時會發現圈圈反而是從外向內縮小,原因在於 obj.opacity 就是從 1 趨近於 0 漸漸越來越小的。

其解法就是將 1 除上 obj.opacity後,當透明度值越小,所得到相對應的數值會越大,這裡要特別注意的是,由於在除法中除上 0 是沒有意義的,所以需要加上一個極小的數值來避免掉這樣的情況。

// 圈圈由外向內
ctx.arc(point.x, point.y, 20*opacity
        , 0, 2 * Math.PI);

// 圈圈由內向外
ctx.arc(point.x, point.y, 20*(1/(obj.opacity + 0.001));
        , 0, 2 * Math.PI);

以下是改變了實心圓大小、加上叉叉以及向外擴展圈圈的完成程式碼

enemies.forEach(function (obj) {
  //本體
  ctx.fillStyle = Color(obj.opacity);
  var obj_point = Point(obj.r, obj.deg);

  ctx.beginPath();
  ctx.arc(
          obj_point.x, obj_point.y,
          4, 0, 2 * Math.PI  //  1. 半徑縮小為 4
  );
  ctx.fill();

  if (Math.abs(obj.deg - line_deg) <= 1) {
    obj.opacity = 1;
  }
  obj.opacity *= 0.99



  // 2. 畫叉叉   
  ctx.strokeStyle = Color(obj.opacity);
  var x_size = 6;
  ctx.lineWidth = 4;
  ctx.beginPath();
  ctx.moveTo(obj_point.x - x_size, obj_point.y - x_size);
  ctx.lineTo(obj_point.x + x_size, obj_point.y + x_size);
  ctx.moveTo(obj_point.x + x_size, obj_point.y - x_size);
  ctx.lineTo(obj_point.x - x_size, obj_point.y + x_size);
  ctx.stroke();

  // 3. 往外消失的圓線
  ctx.strokeStyle = Color(obj.opacity);   // 線條是strokeStyle
  ctx.lineWidth = 1;

  var point = Point(obj.r, obj.deg);
  var r = 20 * (1 / (obj.opacity + 0.001));

  ctx.beginPath();
  ctx.arc(point.x, point.y, r
          , 0, 2 * Math.PI);
  ctx.stroke(); // 線條是stroke()
}
步驟七:完成雷達掃描出現敵人的動態
步驟七:完成雷達掃描出現敵人的動態

八、修改左下角文字

在掃略線掃到敵人後,除了讓敵人顯現外,當然也要將敵人的位置標示出來。在判定掃略線與敵人碰觸到的判斷式中,用 jquery 的方式抓取左下角顯示文字的元素 .message,再填上要顯示的文字,分別是距離中心的半徑長度以及角度。在預設上系統會顯示非常多位數的小數點,這裡可以透過 toFixed(3) 來限制小數點所呈現的位數,括號內的數值即代表要呈現小數點幾位數,這樣一來讓視覺上比較好看一些。

if (Math.abs(obj.deg - line_deg) <= 1){
  obj.opacity=1;
  $(".message").text("Detected: "+obj.r.toFixed(3) +" at "+ obj.deg.toFixed(3) + "deg");
}

九、外圍刻度

在外圍的刻度上要先定義幾個四個變數來使用,分為是

split : 將圓形切分的份數。
feature : 每隔幾度要以比較長的線條呈現,就像家裡時鐘整點位置的線條會長一點。
tart_r : 距離中心點的起始位置。外圍刻度要為在掃略線的外圍,所以數值比掃略線的 200 再多一些。
len : 線條的長度。

在角度的要特別注意需要轉換,deg=(i/split)*360,這是因為在切分上只有切成了 120 份而非 360 份,所以 i 值並非代表角度,而是要先以i/split 判斷是第幾份,再將數值乘上 360 才會是正確的角度。後續則是將靠內側的點以及靠外側的點計算出來後,再相連就可以囉。

接著再使用判斷式 i % feature == 0,也就是當每隔 15 個單位時,將線條的長度以及粗度都設定得比其他線條的數值更大一些。

function draw(){
  ...

  ctx.strokeStyle=Color(1);
  var split=120;
  var feature=15;
  var start_r=230;
  var len=5;
		  
  for(var i=0;i<split;i++){
  ctx.beginPath();
  // 角度要轉換成 360
  var deg=(i/split)*360;
         
  // 如果在大刻度上就變粗變長
  if (i % feature == 0) {
    len = 10;
    ctx.lineWidth = 3;
  } else {
    len = 5;
    ctx.lineWidth = 1;
   }
		    
  // 轉換內側點以及外側點的位置
  var point1=Point(start_r,deg);
  var point2=Point(start_r+len,deg);
		    
  ctx.moveTo(point1.x,point1.y);
  ctx.lineTo(point2.x,point2.y);
		    
  ctx.stroke();
  }
}
步驟九:增加外圍刻度
步驟九:增加外圍刻度

十、畫龍點睛的線條

剩下還有三個不同的線條需要繪製,分別是最內側的虛線、與掃略線半徑相同的實線,以及最外圍由兩個超過四分之一弧形所組成的外框。在最後一個步驟中,要嘗試使用函式帶入參數的方式來一次繪製三種不同的線條,這也是本次範例中相當精彩的地方。

由於一個圓為 360 度,所以設定一個 1 到 360 的 迴圈,並且透過先前寫過的點位置的函數轉換,轉換成 point,再將這 360個連接在一起。其中從外部所傳進來的參數 r 所代表的為半徑的大小。

function draw(){
  ...

  function CondCircle(r) {
    ctx.beginPath();
    ctx.strokeStyle = Color(1);

    for (var i = 0; i <= 360; i++) {
      var point = Point(r, i);
      ctx.lineTo(point.x, point.y);
    }
    ctx.stroke();
  }
			
  CondCircle(300)
}!
步驟十:先畫個實線
步驟十:先畫個實線

以上是實線的呈現方式,那虛線呢 ? 來看看下圖吧 !

虛線繪製方法解說
虛線繪製方法解說

想像拿的一支畫筆,當從每一個點前往下一個點的位置時,也就是 i 至i+1,可以選擇這一次是要用 lineTo 下筆畫線,還是用 moveTo 不要畫線只要移動就好。上面線條是的虛與實是剛好以 1:1 的方式繪製,那如果是要以其他比例,像是下圖呢 ?

不同比例的需線畫製解說
不同比例的需線畫製解說

假設圖片中所呈現有畫線與無畫線的比例是 4:1 ,那就是抓成五等分,有其中四分要畫線,而一份不須畫線,而這可以使用取餘數的方式來完成,下面實作就以每 180 度為一個區塊,在每一個區塊中其中的 90 度以畫線,另外的 90 度則不會畫線。

function draw(){
  ...

  function CondCircle(r) {
    ctx.beginPath();
    ctx.lineWidth = 1;
    ctx.strokeStyle = Color(1);

    for (var i = 0; i <= 360; i++) {
      var point = Point(r, i);

      if (i % 180 < 90) {
        ctx.lineTo(point.x, point.y);
      } else {
        ctx.moveTo(point.x, point.y);
      }
    }
    ctx.stroke();
  }
			
  CondCircle(300)
}!
步驟十:完成虛線畫製
步驟十:完成虛線畫製

有了使用餘數來畫線的概念後,接著就是要來集大成的時候了。要將決定畫不畫線的地方也就是 i % 180 < 90 改為傳送一個函式進來的方式來進行判斷,這裡命名為 func_cond,另外增加一個可以設定線條粗度的變數lwidth

原本的四分之一弧形取餘數的數值不變,但而外加上了 time,使其可以產生出動態的效果。最內側的虛線則是每個區塊為三等份,其中一等分不畫線,所以回傳值為 (deg % 3) < 1,而與掃略相同的圓圈是實線,代表每一條線條都要連起來,在回傳值上都是 true

function draw(){
  ...

  function CondCircle(r, lwidth, func_cond) {
    ctx.beginPath();
    ctx.lineWidth = lwidth;
    ctx.strokeStyle = Color(1);
		
    for (var i = 0; i <= 360; i++) {
      var point = Point(r, i);
      if (func_cond(i)) {
        ctx.lineTo(point.x, point.y);
      } else {
        ctx.moveTo(point.x, point.y);
      }
    }
    ctx.stroke();
  }
		
  // CondCircle(300)
		
  // 最外圍的四分之一弧形
  CondCircle(300, 2, function (deg) {
    return ((deg + time / 10) % 180) < 90;
  });

  //  最內側的虛線
  CondCircle(100, 1, function (deg) {
    return (deg % 3) < 1;
  });

  // 與掃略線相同長度的實線
  CondCircle(200, 1, function (deg) {
    return true;
  });
}!
步驟十:產生動態虛線,並完成本次作品<偵測敵人的動態雷達圖網頁>
步驟十:產生動態虛線,並完成本次作品<偵測敵人的動態雷達圖網頁>

回顧

在本次範例中,可以發現一整個作品幾乎都是由 Canvas 所繪製出來的,就讓我們一起來回顧製作的流程吧

  1. 建構畫面上所需要的文字與本章主角 Canvas
  2. 抓取 Canvas 物件,並設定畫面長寬可隨頁面大小做更動
  3. 在 Canvas 繪製一個動態圖形,並調整底層背景樣式,讓所繪製圖形不會疊加在一起
  4. 正式開始繪製所需要要的圖形,從建立 X 軸與 Y 軸,再從單一的線條到以多個線條來形成掃描扇形
  5. 建立敵人系統是此次最重要的章節,隨機生成敵人的位置,並與掃略線做搭配,以及設定掃到敵人時所需要呈現的樣式
  6. 加上外圍刻度
  7. 以模組化的方式畫出中內外的線條三種不同的線條

透過這次範例讓我們見識到 Canvas 厲害之處,原來它真的就像畫筆一樣,可以畫出這麼美感與創意兼具的作品,想要看更多動態網頁、互動藝術作品,你可以加入老闆的動畫互動網頁程式入門 (HTML/CSS/JS)或是動畫互動網頁特效入門(JS/CANVAS),或來訂閱老闆 youtube 頻道吧!

此篇直播筆記由幫手 阮柏燁 協助整理

墨雨設計banner

這篇文章 【Canvas創作教學】畫個偵測敵人的動態雷達圖網頁 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
釀造一門結合網頁、設計、數學與特效的線上程式課程 https://creativecoding.in/2021/06/30/%e9%87%80%e9%80%a0%e4%b8%80%e9%96%80%e7%b5%90%e5%90%88%e7%b6%b2%e9%a0%81-%e8%a8%ad%e8%a8%88-%e6%95%b8%e5%ad%b8%e8%88%87%e7%89%b9%e6%95%88%e7%9a%84%e7%b7%9a%e4%b8%8a%e7%a8%8b%e5%bc%8f%e8%aa%b2%e7%a8%8b/ Wed, 30 Jun 2021 02:51:00 +0000 https://creativecoding.in/?p=925 互動藝術家吳哲宇於2018年寫下製作完第二門線上課程之後的心得,用另一個角度發掘數學的美,將課程裡循序漸進的內容以及學生會製作的各項階段性成品範例一一呈現。

這篇文章 釀造一門結合網頁、設計、數學與特效的線上程式課程 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
本篇文章撰寫於2018/09/20,互動藝術家吳哲宇寫下製作完第二門線上課程之後的心得,自教育環境背景開始爬梳,立願將數學與科學之美與設計美感結合,並將總長超過3000分鐘的課程精華在這篇文章中統整呈現。

就在不久前的2017/8–2018/8,我花了整整一年時間,在Hahow製作我的第二門線上課程,這不只是一門線上課程,而是把我對線上課程的想像再做好做滿、做爆,做到未曾有人嘗試的東西,也希望他能夠在深度、廣度上完整超過第一堂「動態互動網頁程式入門」。

而這堂「動態互動網頁特效入門」,便是一條尋找自我的旅程,深掘是什麼造就了對互動網頁跟程式的憧憬,所以在講到這堂課的製作過程之前,我想從這堂課的主題-數學以及教育開始講起。

Hahow — 動態互動網頁特效入門課程節圖
Hahow — 動態互動網頁特效入門

數千年來,數學家與科學家花了大半輩子的時間,看著宇宙的星星月亮,推算出了萬有引力跟萬物運行的軌跡,在沙地上畫著三角形與方形,推算出了畢式定理跟得到了世界上居然有不能用分數表達的數字。

數學與科學真正的美,在於用一條式子可以預測未來,擺脫非理性的判斷跟控制,透過兩千多年來,人類不斷思考世界到底是什麼鬼東西的成果,我們終於在這混亂不堪的世界中找到了秩序,藉以預測今天月亮會怎麼變化,知道了怎麼藉由電力架構邏輯運算變成電腦,更激發了人類的潛能,促進了各種藝術跟互動科技的蓬勃發展。

古代的人看著星空都在想著些什麼呢
古代的人看著星空都在想著些什麼呢

說得很美,但是從小開始,大多數的我們都很討厭數學,有時想著我學這些東西做題目到底要幹嘛

台灣的教育,讓數學在很多人的心中是學生時期的夢靨,提到便心生反感,更何況去主動接觸,於是多少人在一次又一次的小考中,執筆的手一緊,跟不上進度的失落感,壓垮了最後一根稻草,便從此放棄數學了,只好安慰自己說著:我真的做不到,我大概…一輩子不擅長這個吧!

後來的我們,在職場的某一刻察覺做遊戲用到了大量的演算法與數學,後來的我們,也許從商、從醫,也許從事設計,發現原來生活除了找錢做生意會用到加減乘除外,很多創作也能透過數學與演算法脫穎而出。

究竟是我們沒有這樣的可能性,還是因為從沒有人帶我們從有趣的角度去看見這些東西呢?

想起國高中時期就是這樣,一直上課一直考試
想起國高中時期就是這樣,一直上課一直考試

台灣教育環境中升學導向主義極為盛行,我們以分數判斷一個孩子的學習成果,老師也就針對成績盡力的填鴨提升,在我的記憶中,正課上很少老師在教學時說:

你看!比例真的很美,在建築、設計的歷史中像是黃金比例、出版頁的隔線系統設計,都有用到,也有他的歷史故事在,不只講數學,而是能綜合一些歷史、人文、心理學的角度來闡述一個學科。

也許在教自由落體的時候,直接把帶到戶外拿不同的重量的球投擲做實驗,試著劃下那些千變萬化的軌跡,讓我們親見原來,這樣看似複雜的運動可以單純用兩個方向加速度跟速度來描述,引燃與生俱來的好奇心,我們不是沒有可能性,而是沒有看見,也許順便了解下伽利略在比薩斜塔上扔的兩塊石頭確認了下落速度並非由空氣決定的驗證。

我們在乎的,大多是這題會不會考,考試會不會拿高分

畫出反彈軌跡
畫出反彈軌跡

常聽見的是考試少三分打10下,每堂課一下課就發著一張考卷接連著考,聯絡簿上寫著成績變成家長口中彼此比較的話題,今天不努力上課唸書長大你就找不到好工作,過不了好的生活,而過多的外部動機可以摧毀內部動機,讓我們失去做這些事的成就感,不再為了喜歡做這件事而開心,學習亦是如此。

「對於教育,我有著更為浪漫的想像」

雖然不切實際,我仍希望教師的角色是一盞明燈,帶著你撥開混沌的迷霧,看見世界真實的模樣,而不是拿著書本直接塞給你這東西那式子,然後就發下考卷只看分數。

最初的嘗試 — Youtube線上直播教學

2017 四月時,我嘗試經營直播「老闆,來點寇汀吧!」,以自己設計精緻的範例直播寫程式,吸引了一批熱愛技術與視覺設計的工程師跟設計師,雖然備受好評,卻在數次直播之後感到吃力,每次從基礎開始講解太久,後期講到用三角函數做特效時,在留言區大家總是一遍哀嚎。

數學好難,還是放棄來去做視覺吧,我們可能就是沒這天份

聽了當下真的是很生氣XD ,怎麼會還沒嘗試過就直接放棄了呢?於是出國前我花了一整年時間,製作了「動態互動網頁特效入門」,橫跨基礎網頁、JS入門、Canvas與特效動畫到Vue進階應用,超過60個精緻範例與400張以上的投影片的課程,貪婪地把自己的所學化為精緻的教材,不到十個小時募資超過600%,至今已接近2300位學生。

「動態互動網頁特效入門」的掙扎與誕生

這堂課最初的點子,是來自於做在直播時做canvas動態時鐘,講到了畢式定理跟一些三角函數的應用,當時真的很害怕大家會一頭霧水,還好用到的部分數學還沒很難,所以順利地把範例寫出來,也直白地做到讓大家能懂,教東西虐待大家的時候還挺有成就感的XD

Youtube直播「三角函數做科幻時鐘網頁」
當初直播做時鐘前的數學課XD 一堆人在留言崩潰(直播影片

從那之後動機越來越強烈,延伸成第二門課的主題「動態互動網頁特效入門」,也觀察到一個現象,很多前端工程師或設計師在寫動畫或是特效的時候,都是用現成的函式庫或程式碼,我想讓大家看見,怎樣從基礎了解原理,如何巧妙地去應用相關的概念或演算法。

用有趣的範例,從程式、數學、或設計,整合式的帶你走入絢麗的特效網頁世界,從解決真實的問題或創作來看見學習的目的。

從一個完整入門的角度開發各式互動網頁的經驗,敘述自己平常怎麼思考問題、如何理解程式運作,不只是會寫寫網頁語法,而是真的用這些工具,循序漸進地從自己的手中做出絢麗、夠專業的作品。

所以我從要怎麼做出特效動畫開始想起,首先用程式畫一些簡單的圖形,然後因為座標開始複雜了,就會學習如何使用向量來操作東西,接著東西要能互動就要引入物件的概念,並且賦予每個在畫面上的元素特性,最後是一些進階的數學,比如三角函數、極座標等等。

使用Trello規劃每個單元想要教的內容,然後做範例、做簡報、錄製講解跟live coding
我會像這樣用Trello規劃每個單元想要教的內容,然後做範例、做簡報、錄製講解跟live coding

我把所有想要教的概念一個個放到Trello上,頓時覺得我好像在教高中數學啊XD 很希望一教完這些概念就能讓大家看見成果,所以我每個都設計了一個很精緻的案例,為的就是讓大家一學完就有感覺,更會愛上這些抽象卻又美好的工具。

首先起頭的系列單元是「HTML/CSS 快速入門」

因為畢竟是寫網頁,所以還是需要把基礎的元素、語法以及基本的html/css 以及pug/sass講清楚,但是因為很多人是舊生,所以我重新設計了全部的範例,並加入更多漂亮的示範帶大家精煉入門。

課程單元作業成品
課程單元作業成品

課程內容包含flex 、前處理語言模組使用,重新所有的設計範例,帶你熟悉網頁基礎語法,到使用sass/pug 做出漂亮介面,鍛鍊到能夠憑著純粹CSS就能寫出很漂亮的動態效果,我最喜歡的是裡面我做很久的時間盒子,可以切換動態的流星雨與白天,全部用html與css建構成。

全部用html與css建構成的動態網頁:時間盒子
全部用html與css建構成的動態網頁:時間盒子

接著是程式基礎:「JavaScript入門到進階 」

程式語言的邏輯非常重要,如果有學過任何一種語言,應該就能體會到其實有很多基本的概念要掌握,從變數、字串、迴圈到簡單的邏輯判斷、陣列操作,到開始加入時間的控制製作動畫,我認為程式的基礎重要在於說,知道我們要解決問題,然後用有邏輯的語言敘述邏輯給電腦執行,所以不僅是了解語法,而是了解「為什麼會有這個概念,又能用在哪裡」。

JS單元投影片裡面有非常大量概念圖解
JS單元投影片裡面有非常大量概念圖解
講解setTimeout跟setInterval製作可愛的載入動畫
講解setTimeout跟setInterval製作可愛的載入動畫

教完字串操作跟動畫後,有一個Project是做摩斯密碼翻譯器,除了可以打鐵趁熱地讓大家練習怎麼操作字串跟動畫之外,更能夠完整練習函數的使用,把程式模組化、讓他們能夠重複應用。

製作摩斯密碼翻譯器練習字串操作、函數跟介面互動
製作摩斯密碼翻譯器練習字串操作、函數跟介面互動

我以十幾個有趣範例入門JS打基礎,到後面帶物件導向的概念,從寫義大利麵式的程式,到變成可以獨立完成有趣的小應用,開始加入程式之後,就有了更多能做互動的工具,最後一個完成的Project就結合鋼琴音效做了記憶遊戲,教大家遊戲控制跟流程的設計。

最後的範例-記憶方塊 還有搭一些有趣的音效應用
最後的範例-記憶方塊 還有搭一些有趣的音效應用

然後就進入主題 — Canvas與特效動畫了!

從數學座標、物理模擬、三角函數到音效設計,讓你結合數學跟程式,創造互動藝術與各種遊戲(agar.io / 小精靈 / 科幻碼表…等),做10個範例與6個大專案,這是這次課程最精華的部分XD

在製作了第一個單元的繪圖練習之後,我加上了利用偏移跟調整座標系的工具,在一個範例裡面偷渡了九個範例,快速讓大家練習進階的繪圖跟狀態保存,用堆疊的概念保存畫布當下的轉換狀態,就能夠繪製非常複雜的圖形。

練習Canvas Translate / Rotate 跟 Scale的進階繪製
練習Canvas Translate / Rotate 跟 Scale的進階繪製

熟悉了繪製之後,我們了解到畫布上面都是沒有狀態保存跟物件的概念的,所以開始要用es6的class語法製作一個球的物件,並加入滑鼠控制與偵測座標,讓我們可以去跟球互動,並學習物理概念,把位置、速度、加速度包在球的狀態中。

投影片 — 講解如何透過向量來描述加速度跟速度
投影片 — 講解如何透過向量來描述加速度跟速度
透過向量來控制球體的加速度跟速度
透過向量來控制球體的加速度跟速度

在這之後,為了更複雜的座標跟數值計算,引入了向量跟三角函數的概念,讓我們能夠更容易與直觀地去了解物件旋轉、極座標跟電腦世界中這些形狀怎麼繪製出來的過程,如果稍微延伸下剛剛的球的範例,大量的產生物件就能夠變成漂亮的粒子系統。

大量產生球體就變成漂亮的粒子系統
大量產生球體就變成漂亮的粒子系統

接著利用三角函數的概念,就能夠繪製很漂亮的時鐘,製作FUI的科幻感。

科幻時鐘的成品
科幻時鐘的成品

而到這邊,大致上就是基礎概念都教完了,最重要的就是應用了,要如何結合不同的技術建構有趣的互動範例呢?

「直接實戰,大量從頭建構的完整範例練習」

所以接下來的就是直接建構一個大型的應用範例,比如我自己喜歡的遊戲或是互動藝術,分析我想要達到的效果,然後一步一步的解析會用的原理,從頭開始建構出來。

利用機率、遞迴的概念畫出漂亮的大樹
利用機率、遞迴的概念畫出漂亮的大樹

如果結合繪製加上遞迴的概念製作生成式藝術,學會利用機率的概念長初樹枝,加上一點電腦圖學的烏龜繪圖法,就能夠畫出漂亮的大樹。

TweenMax結合速度函數,可以操縱數值做出很漂亮又生動的動畫
TweenMax結合速度函數,可以操縱數值做出很漂亮又生動的動畫

學習完靜態的操作之後,如果使用TweenMax結合速度函數,可以操縱數值做出很漂亮的動畫,所以除了直接操作html元素,還能拿來針對js的物件製作連續的平滑數值,就能夠套用動畫法則讓畫面很生動。

利用向量模型製作引力、斥力再結合物件導向,就能做出很好玩的小遊戲
利用向量模型製作引力、斥力再結合物件導向,就能做出很好玩的小遊戲

如果學完用TweenMax動畫之後利用向量模型,可以製作引力、斥力,與接觸判斷,再結合物件導向從遊戲物件繼承到每個玩家,就能做出一個有幾乎完整功能的agar.io,再加一點點小判斷就能幫其他的球加上AI,我自己做完整個範例玩了很久XDDD

可愛版的小朋友下樓梯XD
可愛版的小朋友下樓梯XD

接著結合物理的概念,製作小朋友下樓梯的遊戲,樓梯有不同的種類、物理性質,加上遊戲控制邏輯之後,使用繼承的概念製作遊戲物件到延伸成不同類型的階梯,懷舊的小遊戲就在手中誕生了。

經典的小精靈
經典的小精靈

在後期的幾個大Project中,我自己最喜歡的是做了經典的小精靈,從地圖的設計、物件生成、牆面繪製,到如何去控制小精靈跟鬼的動畫,最後加上接觸判斷、鬼的吃食物機制,和會追隨玩家的AI,兩個小時的範例,但我用了將近兩個禮拜完成。

太空侵入者 我真的很喜歡這些古早遊戲XD
太空侵入者 我真的很喜歡這些古早遊戲XD

好啦我知道真的很多,所以製作課程的時候真的很崩潰,每次都在埋怨自己當初為什麼要開這麼多單元… 但是我既然答應了大家,我就要完整地完成這個東西,不僅是對自己負責任,而是真的燃燒生命,讓大家看見我所相信的世界。

最後系列是前端資料框架的使用 — Vue.js

因為現代前端框架真的很重要,所以最後從了解為什麼需要資料同步與前端框架,帶你從應用情景(車票、商品列表、電影選擇、撲克牌遊戲)入門,掌握如何將vue.js應用在互動網頁上,也特意設計了互動範例,不只是做個簡單的todolist,而是能拿來製作實務應用中精緻的互動遊戲或介面。

猜撲克牌遊戲
猜撲克牌遊戲

這個系列單元的Project是製作一個電影清單選擇的介面,結合Vue.js先將資料渲染成網頁上的元素,然後解構Vue.js中的事件觸發,加上TweenMax調整裡面使用者動作的觸發跟回饋,在裡面練習了CSS的調整介面、FlexBox、Vue的渲染跟計算屬性、方法、製作非同步的動畫狀態…等,當初也是構思非常地久,如何練習得到每個有教過的細節。

Vue.js製作電影院
Vue.js製作電影院

加碼單元

在最後加碼單元中,我介紹了 TimelineMax / D3 / Three.js ,幾乎每一個單元都可以當成獨立的課程來教了,裡面直接用60分鐘帶大家了解函式庫的特性、使用方式,比如用timelineMax+滑鼠來製作卷動動畫,或使用Three.js製作一個酷炫的原子轉轉畫面。

使用Three.js製作一個酷炫的原子轉轉畫面
使用Three.js製作一個酷炫的原子轉轉畫面

課程內容介紹大概就到這,還有超過一半的範例並沒有放上來,但每一個都是熬了好幾天夜,白了好幾根頭髮才生出來的,好多失敗的草稿,好多覺得不夠好的範例都一再一再地雕琢,為的就是最後端上來,呈現在大家眼前的每個東西都看起來足夠完美。

「結合物理、動畫、音效、藝術、文字、技術知識、古早遊戲,最大程度打開你對互動網頁的想像」

內容的深度、廣度、範例複雜度,如果把三者全部點好點滿…

原本以為這堂課最困難的地方會是範例設計,但是隨著準備投影片的過程,發現要把這些數學跟程式的概念講解得夠清楚直白實在是很難啊!不僅要從日常有趣的範例開始帶入主題,還要一些圖解跟動畫輔助才能一目瞭然,尤其要重新消化一遍自己熟悉的東西。

講義的一部分,JS入門有300張,Canvas特效部分有300多張
講義的一部分,JS入門有300張,Canvas特效部分有300多張

錄製過程中的困難

一直以來,我希望實作可以藉由完整的思路,能從零開始建構每個範例作品,因此在錄製課程時總是花很多的時間在不斷順內容,不斷重錄,以及一直修改投影片,雖然時間上的掌控一直沒能拿適當,常會花比預期長非常多的時間在準備課程。

因為對自己的標準提升,所以錄音、口條與後製都更完美主義了,想更清楚的講解概念,準備了數百張投影片跟說明圖,同時程式變得更難、更複雜,講解難度大幅提高,因為沒有人開設過這樣的課程,得要慢慢濃縮淬煉出整條課程的大綱路線。

Agar.io單元的預覽版本,從基礎原理、應用到livecoding

這個影片大致上就是整堂課的流程,從開頭、講解投影片跟原理,然後到livecoding把整個案例實作出來,一刀未剪,完整的從零開始建構,一邊講解思考流程、為什麼這樣做,以及語法要用什麼。

對我而言,因為製課難度的大幅提升,每個單元的時間都非常難以掌握,常常因為想要設計一個很棒的範例,一兩個禮拜就過去了,再加上還要把觀念消化、講解、程式練習講過幾遍跟重構,所以其實單元上線拖了非常之久,也很感謝所有學生耐心與包容。

另外很感謝的是Hahow負責Review我課程的詩涵,完整地把3000分鐘看過(真的很厲害),給予修改跟調整的建議,以及協助我把整體的課程時程規劃做好,和在過程中處理了很多對外的客服問題,雖然知道這堂課的時程延誤了很多,仍然耐心細心地一起處理完整堂課程。

我都用ScreenFlow剪接螢幕錄影跟聲音
跟Hahow檢核用的單元製作表格
跟Hahow檢核用的單元製作表格

那些瀕臨放棄的時刻

2018三月時是最黑暗的時期,課程上線進度落後,不斷被催稿,每次錄音看天逐漸亮了就覺得好崩潰,想到要連續講3小時的範例就覺得焦慮,要繃緊神經到極限,順暢、有調理、好吸收地把整個程式重新實作一遍。

幾乎都是半夜錄音到早上,還要去上班跟趕案子,真的很崩潰
幾乎都是半夜錄音到早上,還要去上班跟趕案子,真的很崩潰

總想著,我幹嘛把東西做那麼精緻,但是現在回頭看,就好似看到茫茫大雪中,一條深刻且扎實的足跡就這樣踏了過來,唯有熬過那樣的寒冬,才能留下在心中足夠完美的這一門課,迎來百花綻放的春天。

這一年來不斷地在攝取新的領域,同時規劃完整的基礎入門,比如我在音樂學到了合成器、聲音的基礎概念,就把他直接應用到範例中,或是看到了什麼真的很酷的遊戲,就想著怎麼把他用自己的技術與想法詮釋,很希望能夠在此留下一個里程碑,把至今探索數學跟程式的整合道路,以開發軟體、遊戲跟網頁十年左右一路學習過來的脈絡呈現。

為這條艱澀的道路點上一盞一盞的路燈,從此不再漆黑

一場沒有終點的旅行

對於很多人來說,這一堂課就像是一場沒有終點的旅行,而對我來說是很大的里程碑,把從開始寫程式、開工作室到現在的功力都放在這裡,做一個完整的整理希望能幫上想要探索這條路的人,一方面也是告訴自己告一個段落,不要在已經會的東西裡頭打轉,再起步伐去探索未知的世界。

寫網頁真的只有如此嗎?

我們是否因為工作而忘記了創作的初衷?

學會數學或技術到底該怎麼用在日常生活中?

創作到底還能以什麼形式、什麼體驗展現呢?

這些疑問,想透過這堂課也充分的證明了我所相信的事物,而我也放下了至今的包袱,前往紐約攻讀碩士探詢對於這些想像的更多不同解法了。

2018/8 我前往紐約大學念數位整合媒體碩士,想要再探索更多更有趣的互動設計
2018/8 我前往紐約大學念數位整合媒體碩士,想要再探索更多更有趣的互動設計

希望不管你什麼時候回來,這門課都能成為你堅實的後盾不只教程式,更打好數學、架構設計跟特效的內功實戰,不管是剛走上程式路途的你,正在路上跌跌撞撞的,還是已經身經百戰的工程師,都能在此得到收穫。

有一天,如果有人跟我說,嘿! 我看了你的課程,謝謝你把這條足跡留下來,那一切的努力就值得了吧!

歡迎你一起加入這條通往未知的旅行

動態互動網頁特效入門,這堂課從html/css 講到 物件導向 講到 數學、音樂、遊戲的綜合應用,囊括JavaScript完整入門、es6物件導向,到後來自己了解原理並做一個遊戲引擎,範例從記憶遊戲、小精靈、生成式藝術到agar.io。

長達3085分鐘,超過60個精緻範例與400張的投影片以上,以及四個加碼單元vue-cli、GSAP、D3、Three.js的投影片,成為hahow上最長的課程。

到Hahow上課去>>>

動畫互動網頁程式入門(HTML/CSS/JS)
動畫互動網頁特效入門(JS/CANVAS)

延伸閱讀:
2016第一堂課的心得:從不只是一門線上課程-而是一場推動進化的豪賭

撰文:吳哲宇

墨雨設計banner

這篇文章 釀造一門結合網頁、設計、數學與特效的線上程式課程 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>