下落的矩形

一個簡單的 WebGL 遊戲,演示了使用純色進行清除、剪刀式裁剪、動畫和使用者互動。

帶剪刀式裁剪的動畫和使用者互動

這是一個簡單的遊戲。目標是:透過點選儘可能多的下落的矩形來捕捉它們。在此示例中,我們對顯示的矩形使用了面向物件的方 法,這有助於將矩形的狀態(其位置、顏色等)集中管理,並使整個程式碼更簡潔、更易於複用。

此示例結合了使用純色清除繪圖緩衝區和剪刀式裁剪操作。它是處理 WebGL 圖形管線和狀態機的各個階段的完整圖形應用程式的預覽。

此外,該示例還演示瞭如何在遊戲迴圈中整合 WebGL 函式呼叫。遊戲迴圈負責繪製動畫幀,並使動畫響應使用者輸入。在這裡,遊戲迴圈是使用 timeouts 實現的。

js
const canvas = document.querySelector("canvas");
const [scoreDisplay, missesDisplay] = document.querySelectorAll("strong");

function getRenderingContext() {
  canvas.width = canvas.clientWidth;
  canvas.height = canvas.clientHeight;
  const gl = canvas.getContext("webgl");
  gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT);
  return gl;
}

const gl = getRenderingContext();
gl.enable(gl.SCISSOR_TEST);

function getRandomVector() {
  return [Math.random(), Math.random(), Math.random()];
}

class Rectangle {
  constructor() {
    // We get three random numbers and use them for new rectangle
    // size and position. For each we use a different number,
    // because we want horizontal size, vertical size and
    // position to be determined independently.
    const randVec = getRandomVector();
    this.size = [5 + 120 * randVec[0], 5 + 120 * randVec[1]];
    this.position = [
      randVec[2] * (gl.drawingBufferWidth - this.size[0]),
      gl.drawingBufferHeight,
    ];
    this.velocity = 1.0 + 6.0 * Math.random();
    this.color = getRandomVector();
    gl.clearColor(this.color[0], this.color[1], this.color[2], 1.0);
  }
}

let rainingRect = new Rectangle();

let score = 0;
let misses = 0;
let timer = null;
function drawAnimation() {
  gl.scissor(
    rainingRect.position[0],
    rainingRect.position[1],
    rainingRect.size[0],
    rainingRect.size[1],
  );
  gl.clear(gl.COLOR_BUFFER_BIT);
  rainingRect.position[1] -= rainingRect.velocity;
  if (rainingRect.position[1] < 0) {
    misses += 1;
    missesDisplay.textContent = misses;
    rainingRect = new Rectangle();
  }
  // We are using setTimeout for animation. So we reschedule
  // the timeout to call drawAnimation again in 17ms.
  // Otherwise we won't get any animation.
  timer = setTimeout(drawAnimation, 17);
}

function playerClick(evt) {
  // We need to transform the position of the click event from
  // window coordinates to relative position inside the canvas.
  // In addition we need to remember that vertical position in
  // WebGL increases from bottom to top, unlike in the browser
  // window.
  const position = [
    evt.pageX - evt.target.offsetLeft,
    gl.drawingBufferHeight - (evt.pageY - evt.target.offsetTop),
  ];
  // If the click falls inside the rectangle, we caught it.

  // Increment score and create a new rectangle.
  const diffPos = [
    position[0] - rainingRect.position[0],
    position[1] - rainingRect.position[1],
  ];
  if (
    diffPos[0] >= 0 &&
    diffPos[0] < rainingRect.size[0] &&
    diffPos[1] >= 0 &&
    diffPos[1] < rainingRect.size[1]
  ) {
    score += 1;
    scoreDisplay.textContent = score;
    rainingRect = new Rectangle();
  }
}

timer = setTimeout(drawAnimation, 17);
canvas.addEventListener("click", playerClick);

此示例的原始碼也可在 GitHub 上找到。