【30分鐘學完】canvas動畫|遊戲基礎(2):從零開始畫畫

前言

上篇主要是理論的概述,本篇會多些實踐,來說講canvas的基礎用法,幷包含一些基礎三角函數的應用,推薦沒有canvas基礎的朋友閱讀,熟悉的朋友能夠跳過。
本人能力有限,歡迎牛人共同討論,批評指正。javascript

一塊兒來畫畫吧

canvas的API有不少,若是一一列舉30分鐘你是絕對看不完的,並且怎麼流水帳還不如本身去看文檔呢(笑),本教程的思路是用實例一步一步從無到有講解基礎用法。
canvas相關文檔html

準備工做

  1. 佈置畫布:經過添加<canvas>標籤,添加canvas元素;
  2. 獲取畫布:經過<canvas>標籤的id,得到canvas對象;
  3. 得到畫筆:經過canvas對象的getContext("2d")方法,得到2D環境。
<canvas id="canvas" width="400" height="400"></canvas>
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');

畫個箭頭

首先咱們來畫個紅邊黃底的箭頭,使用面向對象的代碼組織方式,所有代碼以下。
類名爲Arrow。它擁有x軸座標、y軸座標、底的顏色color、旋轉弧度rotation四個屬性。
實例方法是draw(),它須要一個context對象做爲參數,就是準備工做裏的context,它就至關因而畫筆,這裏實際上是相似依賴注入的過程,將canvas的畫筆交給實例的draw()方法,實例用這個畫筆去畫出箭頭,繪畫過程見代碼註釋。特別注意如下幾點:java

  • beginPath()方法調用後moveTo()和lineTo移動座標是相對與beginPath()時畫筆的座標的,能夠理解成畫筆自帶一個座標系,它能夠旋轉和在畫布上移動,繪製工做的座標都是屬於這個座標系的;
  • beginPath()是繪製設置狀態的起始點,它以後代碼設置的繪製狀態的做用域結束於繪製方法stroke()、fill()或者closePath();
  • save()的做用是保存筆的狀態,由於一個畫布的筆只有一支,會在不一樣對象中傳遞,爲了避免污染後續的畫就應該先保存,畫完再restore()還原;
  • <canvas>自己是透明的,可使用CSS給它個背景,例子中廣泛使用白色背景。
/**
 * 箭頭類
 * @class Representing a arrow.
 */
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "Arrow" }] */
class Arrow {
  /**
    * Create a arrow.
    */
  constructor() {
    this.x = 0;
    this.y = 0;
    this.color = '#ffff00';
    this.rotation = 0;
  }
  /**
   * Draw the arrow.
   * @param {Object} _context - The canvas context.
   */
  draw(_context) {
    const context = _context;
    // 會先保存畫筆狀態
    context.save();
    // 移動畫筆
    context.translate(this.x, this.y);
    // 旋轉畫筆
    context.rotate(this.rotation);
    // 設置線條寬度
    context.lineWidth = 2;
    // 設置線條顏色
    context.strokeStyle = '#ff0000';
    // 設置填充顏色
    context.fillStyle = this.color;
    // 開始路徑
    context.beginPath();
    // 將筆移動到相對位置
    context.moveTo(-50, -25);
    // 畫線到相對位置
    context.lineTo(0, -25);
    context.lineTo(0, -50);
    context.lineTo(50, 0);
    context.lineTo(0, 50);
    context.lineTo(0, 25);
    context.lineTo(-50, 25);
    context.lineTo(-50, -25);
    // 閉合路徑
    context.closePath();
    // 填充路徑包圍區
    context.fill();
    // 繪製路徑
    context.stroke();
    // 載入保存的筆信息
    context.restore();
  }
}

同理咱們還能夠畫點其餘的,好比一個圓ball.js,稍微多些參數,慢慢理解。
成品的效果能夠先看這個(稍微劇透):一個會跟蹤鼠標位置的箭頭git

加入循環動起來

如今咱們已經掌握了畫畫的基本功,而且能夠畫箭頭arrow.js和圓ball.js,然而這樣只是靜止畫,接下來咱們須要一個循環,不斷的執行擦除和重畫的工做才能實現幀動畫。
下面這段代碼的中繪圖函數drawFrame被當即執行,並遞歸調用自身,你將會在大部分例子中看到。
循環原理上一篇已經說明,再也不重複。這裏要說明的是clearRect(),這個函數接受一個矩形座標,也就是(x軸座標,y軸座標,矩形寬度,矩形高度),用於清除矩形區域內的畫。
例子裏直接是清除了整個畫布,但這不是絕對的,刷不刷新,是局部刷新仍是所有刷新,都須要靈活處理。
這裏有個不刷新的例子:鼠標畫圖工具github

(function drawFrame() {
  // 相似setTimeout的操做
  window.requestAnimationFrame(drawFrame, canvas);
  // 將畫布擦乾淨
  context.clearRect(0, 0, canvas.width, canvas.height);
  // ...繼續你的做畫
}());

給它點動力

如今畫面已是在不斷的重繪,但爲何仍是靜止的呢?由於每一次刷新都沒有改變要畫的內容。
那咱們就給它一個目標吧,這樣它才能動起來,好比就讓箭頭始終指向鼠標。
下面是核心代碼,主要目的就是求出每幀arrow的旋轉角度,這裏使用的工具類mouse會實時返回鼠標的x,y軸座標,封裝原理上一篇已經講過,根據這鼠標的座標和arrow的座標,便可獲得鼠標的相對於arrow的距離dx和dy,以下圖: canvas

箭頭角度演示

而arrow的旋轉角度便可以經過dx和dy使用反正切函數獲得,這裏須要注意幾點:app

  • 仔細看上面代碼中arrow的繪製過程,可知其原點是在中心位置的,因此恰好旋轉角度就是畫筆的旋轉角度;
  • dx和dy是鼠標相對與arrow的座標,因此圖中把座標系挪動箭頭中心是沒毛病的;
  • 用atan2,而不是atan,是由於tan值原本就多是重複的,好比-1/2和1/(-2)兩個都是-0.5,沒法區分象限,而atan2就能夠區分開。

完整實例:一個會跟蹤鼠標位置的箭頭dom

window.onload = function () {
  const canvas = document.getElementById('canvas');
  const context = canvas.getContext('2d');
  const mouse = utils.captureMouse(canvas);
  const arrow = new Arrow();

  arrow.x = canvas.width / 2;
  arrow.y = canvas.height / 2;

  (function drawFrame() {
    window.requestAnimationFrame(drawFrame, canvas);
    context.clearRect(0, 0, canvas.width, canvas.height);
    const dx = mouse.x - arrow.x;
    const dy = mouse.y - arrow.y;

    arrow.rotation = Math.atan2(dy, dx);
    arrow.draw(context);
  }());
};

三角函數

上下運動

終於順利過渡到三角函數的話題(笑)。三角函數不止有反正切一個應用,下面再看一個例子。
下面是一個ball在上下運動的核心代碼,重點就是ball的y軸座標改變,就是這句:函數

ball.y = clientY + Math.sin(angle) * range;

利用Math.sin(angle)的取值範圍是-1到1,而且會隨着angle增大而反覆,使ball在必定範圍上下運動。
完整例子:一個上下運動的球(可調參數版)工具

window.onload = function () {
  const canvas = document.getElementById('canvas');
  const context = canvas.getContext('2d');
  const ball = new Ball();
  let angle = 0;
  // 運動中心
  const clientY = 200;
  // 範圍
  const range = 50;
  // 速度
  const speed = 0.05;

  ball.x = canvas.width / 2;
  ball.y = canvas.height / 2;

  (function drawFrame() {
    window.requestAnimationFrame(drawFrame, canvas);
    context.clearRect(0, 0, canvas.width, canvas.height);

    ball.y = clientY + Math.sin(angle) * range;
    angle += speed;
    ball.draw(context);
  }());
};

向前運動

只是上下運動不過癮,那就讓圓前進吧,其實就是每幀改變x軸的位置。
核心代碼以下,相比前面的上下運動,多了x軸的速度,每幀移動一點就造成了波浪前進的效果。
完整實例:一個波浪運動的球

window.onload = function () {
  const canvas = document.getElementById('canvas');
  const context = canvas.getContext('2d');
  const ball = new Ball();
  let angle = 0;
  const centerY = 200;
  const range = 50;
  const xspeed = 1;
  const yspeed = 0.05;

  ball.x = 0;
  (function drawFrame() {
    window.requestAnimationFrame(drawFrame, canvas);
    context.clearRect(0, 0, canvas.width, canvas.height);
    ball.x += xspeed;
    ball.y = centerY + Math.sin(angle) * range;
    angle += yspeed;
    ball.draw(context);
  }());
};

其餘示例

其餘的應用就不一一講解,羅列出來一些:

相關文章
相關標籤/搜索