本文作者:sukai

编程红色元素(编程红色元素有哪些)

sukai 04-20 119

(点击上方公众号,可快速关注)

  英文:Mark Brown 译文:伯乐在线专栏作者 - 王浩

  欢迎投稿,请点击这里查看详情;

  如需转载,发送「转载」二字查看说明

  “游戏主循环”是一种能够随时间改变状态的用于渲染动画和游戏的技术。它的核心是一个尽可能频繁地运行的方法,来接收用户输入,更新随时间改变的状态,然后绘制当前帧。

  在这篇短文中你将了解这些基础技术是如何工作的,并且可以自己制作出基于浏览器的游戏和动画。

  Java 中的“游戏主循环”看起来像这样:

  functionupdate(progress){

  // Update the state of the world for the elapsed time since last render

  }

  functiondraw(){

  // Draw the state of the world

  }

  functionloop(timestamp){

  varprogress= timestamp- lastRender

  update(progress)

  draw()

  lastRender= timestamp

  window.requestAnimationFrame(loop)

  }

  varlastRender= 0

  window.requestAnimationFrame(loop)

  requestAnimationFrame 方法请求浏览器在下一次重绘之前尽可能快地调用特定的方法。它是渲染动画专用的 API,但你也可以用 setTimeout 方法设置一个短的超时时间来达到相似的效果。当回调函数开始触发时,requestAnimationFrame 传入一个时间戳作为参数,它包含从窗口加载到现在的毫秒数,等价于 performance.now()。

  progress 值,或者说每次渲染的时间间隔对于创建流畅的动画是至关重要的。我们通过它来调整 update 方法中的 x 轴和 y 轴的位置,保证动画以稳定的速度运动。

  更新位置

  我们的第一个动画简单到不行。一个红色的方块向右移动直到碰到画布的边缘,然后回到起始位置。

  我们需要存储方块的位置,以及在 update 方法中 x 轴位置的增量。当到达边界时我们可以减掉画布的宽度来让它回到起点。

  varcanvas= document.getElementById("canvas")

  varwidth= canvas.width

  varheight= canvas.height

  varctx= canvas.getContext("2d")

  ctx.fillStyle= "red"

  functiondraw(){

  ctx.clearRect(0,0,width,height)

  ctx.fillRect(state.x- 5,state.y- 5,10,10)

  }

  绘制新一帧

  本例使用 canvas 元素来渲染图像,不过游戏主循环也可以结合其他输出,比如 HTML 或者 SVG 来使用。

  draw 方法简单地渲染游戏世界的当前状态。每一帧我们都要清空画布,然后在state 对象中保存的位置上重新画一个 10px 的红方块。

  varcanvas= document.getElementById("canvas")

  varwidth= canvas.width

  varheight= canvas.height

  varctx= canvas.getContext("2d")

  ctx.fillStyle= "red"

  functiondraw(){

  ctx.clearRect(0,0,width,height)

  ctx.fillRect(state.x- 5,state.y- 5,10,10)

  }

  然后我们就发现它动起来了!

  在 SitePoint 的 CodePen 可以查看示例:Game Loop in Java: Basic Movement。

  注:在这个例子中你可能会注意到画布的大小是通过 CSS 和 HTML 元素的 width, height 属设置的。CSS 样式设置了画布在页面绘画的真实尺寸,而 HTML 属性则设置了画布 API 需要用到的坐标系或者网格的大小。看看 Stack Overflow 上的这个问题来了解更多。

  响应用户输入

  下面我们要获取键盘输入来控制对象的位置,state.pressedKeys 会追踪用户按下了哪一个键。

  varstate= {

  x: (width/ 2),

  y: (height/ 2),

  pressedKeys: {

  left: false,

  right: false,

  up: false,

  down: false

  }

  }

  我们监听所有的 keydown 和 keyup 事件,并且同步更新 update.pressedKeys。我用 D 键作为向右方向,A 为左,W 为上,S 为下。你可以在这里找到键盘码列表。

  varkeyMap= {

  68: 'right',

  65: 'left',

  87: 'up',

  83: 'down'

  }

  functionkeydown(event){

  varkey= keyMap[event.keyCode]

  state.pressedKeys[key]= true

编程红色元素(编程红色元素有哪些)

  }

  functionkeyup(event){

  varkey= keyMap[event.keyCode]

  state.pressedKeys[key]= false

  }

  window.addEventListener("keydown",keydown,false)

  window.addEventListener("keyup",keyup,false)

  然后我们就只需要根据按下的键来更新 x轴 和 y轴 的值,并保证对象在边界以内。

  functionupdate(progress){

  if(state.pressedKeys.left){

  state.x-= progress

  }

  if(state.pressedKeys.right){

  state.x+= progress

  }

  if(state.pressedKeys.up){

  state.y-= progress

  }

  if(state.pressedKeys.down){

  state.y+= progress

  }

  // Flip position at boundaries

  if(state.x width){

  state.x-= width

  }

  elseif(state.x 0){

  state.x+= width

  }

  if(state.y height){

  state.y-= height

  }

  elseif(state.y 0){

  state.y+= height

  }

  }

  现在我们就可以响应用户输入了!

  在 SitePoint的 CodePen 可以查看示例:Game Loop in Java: Dealing with User Input。

  行星游戏

  既然现在我们已经掌握了基本原理,那么就可以做些更有意思的事了。

  做一艘看起来像经典游戏“行星”里的飞船其实一点都不复杂。

  state 对象需要额外存储一个向量(一个 x、y 对)用来移动,还要保存一个 rotation 值来标记飞船的方向。

  varstate= {

  position: {

  x: (width/ 2),

  y: (height/ 2)

  },

  movement: {

  x: 0,

  y: 0

  },

  rotation: 0,

  pressedKeys: {

  left: false,

  right: false,

  up: false,

  down: false

  }

  }

  update 方法需要做三件事:

根据左右键更新方向(rotation)

根据上下键和方向更新移动向量(movement)

根据移动向量和画布边界更新对象位置(position)

  functionupdate(progress){

  // Make a smaller time value that's easier to work with

  varp= progress/ 16

  updateRotation(p)

  updateMovement(p)

  updatePosition(p)

  }

  functionupdateRotation(p){

  if(state.pressedKeys.left){

  state.rotation-= p* 5

  }

  elseif(state.pressedKeys.right){

  state.rotation+= p* 5

  }

  }

  functionupdateMovement(p){

  // Behold! Mathematics for mapping a rotation to it's x, y components

  varaccelerationVector= {

  x: p* .3* Math.cos((state.rotation-90)* (Math.PI/180)),

  y: p* .3* Math.sin((state.rotation-90)* (Math.PI/180))

  }

  if(state.pressedKeys.up){

  state.movement.x+= accelerationVector.x

  state.movement.y+= accelerationVector.y

  }

  elseif(state.pressedKeys.down){

  state.movement.x-= accelerationVector.x

  state.movement.y-= accelerationVector.y

  }

  // Limit movement speed

  if(state.movement.x 40){

  state.movement.x= 40

  }

  elseif(state.movement.x -40){

  state.movement.x= -40

  }

  if(state.movement.y 40){

  state.movement.y= 40

  }

  elseif(state.movement.y -40){

  state.movement.y= -40

  }

  }

  functionupdatePosition(p){

  state.position.x+= state.movement.x

  state.position.y+= state.movement.y

  // Detect boundaries

  if(state.position.x width){

  state.position.x-= width

  }

  elseif(state.position.x 0){

  state.position.x+= width

  }

  if(state.position.y height){

  state.position.y-= height

  }

  elseif(state.position.y 0){

  state.position.y+= height

  }

  }

  draw 方法在绘制箭头之前会移动并转动画布的原点。

  functiondraw(){

  ctx.clearRect(0,0,width,height)

  ctx.save()

  ctx.translate(state.position.x,state.position.y)

  ctx.rotate((Math.PI/180)* state.rotation)

  ctx.strokeStyle= 'white'

  ctx.lineWidth= 2

  ctx.beginPath()

  ctx.moveTo(0,0)

  ctx.lineTo(10,10)

  ctx.lineTo(0,-20)

  ctx.lineTo(-10,10)

  ctx.lineTo(0,0)

  ctx.closePath()

  ctx.stroke()

  ctx.restore()

  }

  这就是我们需要重建类似“行星”游戏飞船的所有代码。本例的操作按键和前面那个完全一样(D键向右,A 向左,W向上,S 向下)

  在 SitePoint的 CodePen 可以查看示例:Game Loop in Java: Recreating Asteroids。

  添加行星、子弹和碰撞监测的工作就交给你了~

  升级

  如果你对本文很感兴趣,那你肯定会喜欢阅读这篇《Mary Rose Cook live-code Space Invaders form scrach》来看一个更复杂的例子。虽然是发表于几年前,但它是一篇介绍开发浏览器游戏的非常棒的文章。

觉得本文对你有帮助?请分享给更多人

关注「前端大全」,提升前端技能

  译者简介 ( 点击 → 加入专栏作者)

  王浩:phper @深圳

阅读
分享