下雨的矩形

一个简单的 WebGL 游戏,演示了使用纯色、剪切、动画和用户交互进行清除。

使用剪切的动画和用户交互

这是一个简单的游戏。目标:尝试通过点击尽可能多的下雨的矩形来捕捉它们。在这个例子中,我们对显示的矩形使用面向对象的方法,这有助于将矩形的状态(其位置、颜色等)组织在一个地方,并且整体代码更加紧凑和可重用。

本例结合了使用纯色清除绘图缓冲区和剪切操作。它是一个完整图形应用程序的预览,该应用程序操作了 WebGL 图形管线和状态机的各个阶段。

此外,该示例演示了如何在游戏循环中集成 WebGL 函数调用。游戏循环负责绘制动画帧,并保持动画对用户输入的响应。在这里,游戏循环是使用超时实现的。

js
window.addEventListener("load", setupAnimation, false);
let gl;
let timer;
let rainingRect;
let scoreDisplay;
let missesDisplay;
function setupAnimation(evt) {
  window.removeEventListener(evt.type, setupAnimation, false);
  if (!(gl = getRenderingContext())) return;
  gl.enable(gl.SCISSOR_TEST);

  rainingRect = new Rectangle();
  timer = setTimeout(drawAnimation, 17);
  document
    .querySelector("canvas")
    .addEventListener("click", playerClick, false);
  [scoreDisplay, missesDisplay] = document.querySelectorAll("strong");
}

let score = 0;
let misses = 0;
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();
  }
}

function Rectangle() {
  // Keeping a reference to the new Rectangle object, rather
  // than using the confusing this keyword.
  const rect = this;
  // 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 randNums = getRandomVector();
  rect.size = [5 + 120 * randNums[0], 5 + 120 * randNums[1]];
  rect.position = [
    randNums[2] * (gl.drawingBufferWidth - rect.size[0]),
    gl.drawingBufferHeight,
  ];
  rect.velocity = 1.0 + 6.0 * Math.random();
  rect.color = getRandomVector();
  gl.clearColor(rect.color[0], rect.color[1], rect.color[2], 1.0);
  function getRandomVector() {
    return [Math.random(), Math.random(), Math.random()];
  }
}

此示例的源代码也可以在 GitHub 上获得。