如何使用WebGL允许2D形状环绕画布?

时间:2017-02-17 06:53:09

标签: javascript html animation 2d webgl

我在WebGL(html& javascript)中创建了一个简单的动画,其中2D形状在画布上进行动画处理和操作。默认动画是以设定的速度向右移动的形状,然后使用“WASD”改变其方向。即使在离开画布并离开剪辑空间之后,形状也会无限期地沿给定方向移动。我希望将形状包裹在画布周围,而不是仅仅在看不见之后继续。例如,如果形状向右移动并完全离开画布,我希望它显示在左侧仍然向右移动并继续循环。如果它向左或向上或向下移动也是如此。

有关如何实施此建议的任何建议?

1 个答案:

答案 0 :(得分:0)

你需要画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减去。