带剪刀式裁剪的动画和用户交互
这是一个简单的游戏。目标是:通过点击尽可能多的下落的矩形来捕捉它们。在此示例中,我们对显示的矩形使用了面向对象的方 法,这有助于将矩形的状态(其位置、颜色等)集中管理,并使整个代码更简洁、更易于复用。
此示例结合了使用纯色清除绘图缓冲区和剪刀式裁剪操作。它是处理 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 上找到。