我在 WebGL(html 和 javascript)中创建了一个简单的动画,其中 2D 形状在 Canvas 上进行动画处理和操作。默认动画是形状以设定速度向右移动,然后使用“WASD”更改其方向。即使形状离开 Canvas 并超出剪辑空间,形状也会无限期地沿给定方向移动。我想让形状环绕 Canvas ,而不是在看不见它之后继续。例如,如果形状向右移动并完全移出 Canvas ,我希望它出现在左侧,但仍向右移动并继续循环。如果向左、向上或向下移动,情况也是如此。
关于如何实现这一点有什么建议吗?
请您参考如下方法:
你必须绘制它 2 到 4 次,具体取决于你是否想同时包裹左/右和上/下
假设我们只想水平环绕。如果玩家靠近左边缘,我们还需要将玩家 1 屏幕宽度向右绘制。如果玩家靠近右边缘,我们需要将玩家再次向左绘制一屏。与上、下相同
这是一个使用 canvas 2D 的示例,但 WebGL 的唯一区别是您可以使用 WebGL 进行绘制。概念是一样的。
示例:
var x = 150;
var y = 100;
var vx = 0;
var vy = 0;
const maxSpeed = 250;
const acceleration = 1000;
const ctx = document.querySelector("canvas").getContext("2d");
const keys = {};
const LEFT = 65;
const RIGHT = 68;
const DOWN = 83;
const UP = 87;
const width = ctx.canvas.width;
const height = ctx.canvas.height;
var then = 0;
function render(now) {
now *= 0.001; // seconds
const deltaTime = now - then;
then = now;
ctx.clearRect(0, 0, width, height);
if (keys[UP]) { vy -= acceleration * deltaTime; }
if (keys[DOWN]) { vy += acceleration * deltaTime; }
if (keys[LEFT]) { vx -= acceleration * deltaTime; }
if (keys[RIGHT]) { vx += acceleration * deltaTime; }
// keep speed under max
vx = absmin(vx, maxSpeed);
vy = absmin(vy, maxSpeed);
// move based on velociy
x += vx * deltaTime;
y += vy * deltaTime;
// wrap
x = euclideanModulo(x, width);
y = euclideanModulo(y, height);
// draw player 4 times
const xoff = x < width / 2 ? width : -width;
const yoff = y < height / 2 ? height : -height;
drawPlayer(x, y);
drawPlayer(x + xoff, y);
drawPlayer(x, y + yoff);
drawPlayer(x + xoff, y + yoff);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
function drawPlayer(x, y) {
ctx.fillStyle = "blue";
ctx.strokeStyle = "red";
ctx.lineWidth = 4;
ctx.beginPath();
ctx.arc(x, y, 20, 0, Math.PI * 2, false);
ctx.fill();
ctx.stroke();
}
function absmin(v, max) {
return Math.min(Math.abs(v), max) * Math.sign(v);
}
function euclideanModulo(n, m) {
return ((n % m) + m) % m;
}
window.addEventListener('keydown', e => {
keys[e.keyCode] = true;
});
window.addEventListener('keyup', e => {
keys[e.keyCode] = false;
});
canvas {
display: block;
border: 1px solid black;
}
<canvas></canvas>
<p><span style="color:red;">click here</span> then use ASWD to move</p>
WebGL 版本没有更改与包装相关的代码。
var x = 150;
var y = 100;
var vx = 0;
var vy = 0;
const maxSpeed = 250;
const acceleration = 1000;
const gl = document.querySelector("canvas").getContext("webgl");
const keys = {};
const LEFT = 65;
const RIGHT = 68;
const DOWN = 83;
const UP = 87;
const width = gl.canvas.width;
const height = gl.canvas.height;
var program = setupWebGL();
var positionLoc = gl.getAttribLocation(program, "position");
var then = 0;
function render(now) {
now *= 0.001; // seconds
const deltaTime = now - then;
then = now;
if (keys[UP]) { vy -= acceleration * deltaTime; }
if (keys[DOWN]) { vy += acceleration * deltaTime; }
if (keys[LEFT]) { vx -= acceleration * deltaTime; }
if (keys[RIGHT]) { vx += acceleration * deltaTime; }
// keep speed under max
vx = absmin(vx, maxSpeed);
vy = absmin(vy, maxSpeed);
// move based on velociy
x += vx * deltaTime;
y += vy * deltaTime;
// wrap
x = euclideanModulo(x, width);
y = euclideanModulo(y, height);
// draw player 4 times
const xoff = x < width / 2 ? width : -width;
const yoff = y < height / 2 ? height : -height;
drawPlayer(x, y);
drawPlayer(x + xoff, y);
drawPlayer(x, y + yoff);
drawPlayer(x + xoff, y + yoff);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
function drawPlayer(x, y) {
gl.useProgram(program);
// only drawing a single point so no need to use a buffer
gl.vertexAttrib2f(
positionLoc,
x / width * 2 - 1,
y / height * -2 + 1);
gl.drawArrays(gl.POINTS, 0, 1);
}
function absmin(v, max) {
return Math.min(Math.abs(v), max) * Math.sign(v);
}
function euclideanModulo(n, m) {
return ((n % m) + m) % m;
}
window.addEventListener('keydown', e => {
keys[e.keyCode] = true;
});
window.addEventListener('keyup', e => {
keys[e.keyCode] = false;
});
function setupWebGL() {
const vs = `
attribute vec4 position;
void main() {
gl_Position = position;
gl_PointSize = 40.;
}
`;
const fs = `
void main() {
gl_FragColor = vec4(1,0,1,1);
}
`;
// compiles and links shaders and assigns position to location 0
return twgl.createProgramFromSources(gl, [vs, fs]);
}
canvas {
display: block;
border: 1px solid black;
}
<canvas></canvas>
<p><span style="color:red;">click here</span> then use ASWD to move</p>
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
如果您不希望玩家出现在两侧,那么您的问题与图形无关,您只需等到玩家的 x 位置至少为 screenWidth + haflPlayerWidth
即可。这意味着它们完全偏离右侧,然后您将它们的 x 位置设置为 -halfPlayerWidth
这将使它们位于左侧,反之亦然
var x = 150;
var y = 100;
var vx = 0;
var vy = 0;
const maxSpeed = 250;
const acceleration = 1000;
const ctx = document.querySelector("canvas").getContext("2d");
const keys = {};
const LEFT = 65;
const RIGHT = 68;
const DOWN = 83;
const UP = 87;
const width = ctx.canvas.width;
const height = ctx.canvas.height;
const playerSize = 40;
const halfPlayerSize = playerSize / 2;
var then = 0;
function render(now) {
now *= 0.001; // seconds
const deltaTime = now - then;
then = now;
ctx.clearRect(0, 0, width, height);
if (keys[UP]) { vy -= acceleration * deltaTime; }
if (keys[DOWN]) { vy += acceleration * deltaTime; }
if (keys[LEFT]) { vx -= acceleration * deltaTime; }
if (keys[RIGHT]) { vx += acceleration * deltaTime; }
// keep speed under max
vx = absmin(vx, maxSpeed);
vy = absmin(vy, maxSpeed);
// move based on velociy
x += vx * deltaTime;
y += vy * deltaTime;
// wrap
x = euclideanModulo(x + halfPlayerSize, width + playerSize) - halfPlayerSize;
y = euclideanModulo(y + halfPlayerSize, height + playerSize) - halfPlayerSize;
// draw player
drawPlayer(x, y);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
function drawPlayer(x, y) {
ctx.fillStyle = "blue";
ctx.strokeStyle = "red";
ctx.lineWidth = 4;
ctx.beginPath();
ctx.arc(x, y, halfPlayerSize, 0, Math.PI * 2, false);
ctx.fill();
ctx.stroke();
}
function absmin(v, max) {
return Math.min(Math.abs(v), max) * Math.sign(v);
}
function euclideanModulo(n, m) {
return ((n % m) + m) % m;
}
window.addEventListener('keydown', e => {
keys[e.keyCode] = true;
});
window.addEventListener('keyup', e => {
keys[e.keyCode] = false;
});
canvas {
display: block;
border: 1px solid black;
}
<canvas></canvas>
<p><span style="color:red;">click here</span> then use ASWD to move</p>
这段代码可能需要解释
x = euclideanModulo(x + haflPlayerSize, width + playerSize) - haflPlayerSize;
y = euclideanModulo(y + haflPlayerSize, height + playerSize) - haflPlayerSize;
首先euclideanModulo
就像正常的 %
模运算符,它返回除法后的余数,但欧几里得模即使对于负数也保持相同的余数。换句话说
3 % 5 = 3
8 % 5 = 3
13 % 5 = 3
-2 % 5 = -2
-7 % 5 = -2
-12 % 5 = -2
但是
3 euclideanMod 5 = 3
8 euclideanMod 5 = 3
13 euclideanMod 5 = 3
-2 euclideanMod 5 = 3
-7 euclideanMod 5 = 3
-12 euclideanMod 5 = 3
所以这是一种非常简单的包装方式。
x = euclideanModulo(x, screenWidth)
类似于
if (x < 0) x += screenWidth;
if (x >= screenWidth) x -= screenWidth;
除非如果 x > screenWidth * 2
则会失败例如,使用 euclideanModulo 的人不会。
所以,回到
x = euclideanModulo(x + haflPlayerSize, width + playerSize) - haflPlayerSize;
y = euclideanModulo(y + haflPlayerSize, height + playerSize) - haflPlayerSize;
我们知道玩家(在本例中是一个圆圈)的位置位于其中心。因此,我们知道当它的中心距离屏幕左侧或右侧玩家大小的一半时,它就完全离开屏幕,因此我们希望将其移动到另一侧。这意味着我们可以想象屏幕实际上是 width + halfPlayerSize + halfPlayerSize
宽的。第一个halfPlayerSize
是为了从左侧迈出,第二个halfPlayerSize
是为了从右侧迈出。换句话说,它只是 width + playerSize
宽的。然后,我们希望播放器在 x < -halfPlayerSize
时从左到右换行。 。所以我们添加halfPlayerSize
到玩家的位置,然后执行 euclideanModulo 来包裹该位置,然后减去 halfPlayerSize。