Regennde Rechtecke
Ein einfaches WebGL-Spiel, das das Löschen mit Vollfarben, Scissoring, Animation und Benutzerinteraktion demonstriert.
Animation und Benutzerinteraktion mit Scissoring
Dies ist ein einfaches Spiel. Das Ziel: Versuchen Sie, so viele der regnenden Rechtecke wie möglich durch Anklicken zu fangen. In diesem Beispiel verwenden wir einen objektorientierten Ansatz für die angezeigten Rechtecke, was hilft, den Zustand des Rechtecks (seine Position, Farbe usw.) an einem Ort zu organisieren und den gesamten Code kompakter und wiederverwendbarer zu machen.
Dieses Beispiel kombiniert das Löschen des Zeichenpuffers mit Vollfarben und Scissoring-Operationen. Es ist eine Vorschau einer vollständigen grafischen Anwendung, die verschiedene Phasen der WebGL-Grafikpipeline und Zustandsmaschine manipuliert.
Darüber hinaus demonstriert das Beispiel, wie die WebGL-Funktionsaufrufe innerhalb einer Spielschleife integriert werden. Die Spielschleife ist dafür verantwortlich, die Animationsframes zu zeichnen und die Animation für Benutzereingaben reaktionsfähig zu halten. Hier wird die Spielschleife mithilfe von Timeouts implementiert.
const canvas = document.querySelector("canvas");
const gl = getRenderingContext();
gl.enable(gl.SCISSOR_TEST);
let rainingRect = new Rectangle();
let timer = setTimeout(drawAnimation, 17);
canvas.addEventListener("click", playerClick);
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;
}
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();
}
}
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);
}
}
function getRandomVector() {
return [Math.random(), Math.random(), Math.random()];
}
Der Quellcode dieses Beispiels ist auch auf GitHub verfügbar.