详解通过 JavaScript 编写一个游戏主循环
沉沙 2018-07-10 来源 : 阅读 1264 评论 0

摘要:“游戏主循环”是一种能够随时间改变状态的用于渲染动画和游戏的技术。它的核心是一个尽可能频繁地运行的方法,来接收用户输入,更新随时间改变的状态,然后绘制当前帧。希望阅读本篇文章以后大家有所收获,帮助大家对JavaScript的理解更加深入。

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

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

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

function update(progress) {
  // Update the state of the world for the elapsed time since last render
}
 
function draw() {
  // Draw the state of the world
}
 
function loop(timestamp) {
  var progress = timestamp - lastRender
 
  update(progress)
  draw()
 
  lastRender = timestamp
  window.requestAnimationFrame(loop)
}
var lastRender = 0
window.requestAnimationFrame(loop)

   

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

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

更新位置

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

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


var canvas = document.getElementById("canvas")

var width = canvas.width

var height = canvas.height

var ctx = canvas.getContext("2d")

ctx.fillStyle = "red"

 

function draw() {

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

 

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

}

   

绘制新一帧

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

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

var canvas = document.getElementById("canvas")

var width = canvas.width

var height = canvas.height

var ctx = canvas.getContext("2d")

ctx.fillStyle = "red"

 

function draw() {

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

 

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

}。

   

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

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

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

响应用户输入

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

var state = {

  x: (width / 2),

  y: (height / 2),

  pressedKeys: {

    left: false,

    right: false,

    up: false,

    down: false

  }

}

   

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

var keyMap = {
  68: 'right',
  65: 'left',
  87: 'up',
  83: 'down'
}
function keydown(event) {
  var key = keyMap[event.keyCode]
  state.pressedKeys[key] = true
}
function keyup(event) {
  var key = keyMap[event.keyCode]
  state.pressedKeys[key] = false
}
 
window.addEventListener("keydown", keydown, false)
window.addEventListener("keyup", keyup, false)

   

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

function update(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
  }
  else if (state.x < 0) {
    state.x += width
  }
  if (state.y > height) {
    state.y -= height
  }
  else if (state.y < 0) {
    state.y += height
  }
}

   

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

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

行星游戏

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

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

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

var state = {

  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)


function update(progress) {
  // Make a smaller time value that's easier to work with
  var p = progress / 16
 
  updateRotation(p)
  updateMovement(p)
  updatePosition(p)
}
 
function updateRotation(p) {
  if (state.pressedKeys.left) {
    state.rotation -= p * 5
  }
  else if (state.pressedKeys.right) {
    state.rotation += p * 5
  }
}
 
function updateMovement(p) {
  // Behold! Mathematics for mapping a rotation to it's x, y components
  var accelerationVector = {
    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
  }
  else if (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
  }
  else if (state.movement.x < -40) {
    state.movement.x = -40
  }
  if (state.movement.y > 40) {
    state.movement.y = 40
  }
  else if (state.movement.y < -40) {
    state.movement.y = -40
  }
}
 
function updatePosition(p) {
  state.position.x += state.movement.x
  state.position.y += state.movement.y
 
  // Detect boundaries
  if (state.position.x > width) {
    state.position.x -= width
  }
  else if (state.position.x < 0) {
    state.position.x += width
  }
  if (state.position.y > height) {
    state.position.y -= height
  }
  else if (state.position.y < 0) {
    state.position.y += height
  }
}

   

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


function draw() {
  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 JavaScript: Recreating Asteroids。

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


本文由职坐标整理发布,学习更多的JavaScript相关知识,请关注职坐标WEB前端JavaScript频道!

本文由 @沉沙 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程