我在 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。


评论关闭
IT虾米网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!