项目作者: shadaileng

项目描述 :
https://developer.mozilla.org/zh-CN/docs/Games 的学习案例
高级语言: JavaScript
项目地址: git://github.com/shadaileng/game.git
创建时间: 2018-10-24T09:29:58Z
项目社区:https://github.com/shadaileng/game

开源协议:

下载


https://developer.mozilla.org/zh-CN/docs/Games 的学习案例

游戏开发

[toc]

一. 序言

得益于JavaScript实时编译技术性能的大幅提升,以及新开放的API,开发基于HTML5的游戏更加容易.

1 功能与对应的技术

浏览器web平台实现的功能与对应的技术:

功能 技术
音频 Web Audio API
图形 WebGL (OpenGL ES 2.0)
输入 Touch events, Gamepad API, 设备传感器, WebRTC,Full Screen API, Pointer Lock API
语言 JavaScript (或是C/C++使用Emscripten来编译成JavaScript)
网络 WebRTC和/或WebSockets
存储 IndexedDB或是 “云(存储)”
Web HTML, CSS, SVG,Social API(还有其他很多很多东西!)

2 游戏结构与工作流程

2.1 呈现\接受\解析\计算\重复

每个视频游戏的目标是向用户呈现一个场景,接受用户的输入,将输入解析成动作信息,并计算这些动作在场景中的变化,游戏场景是不断重复的,直到有终止游戏的信号出现.

有一些游戏是通过用户输入来驱动游戏循环的,通过输入来判断下一个要呈现的游戏场景,如: 找不同游戏

有一些游戏是控制每个时间点,每一帧都遍历游戏逻辑,实现呈现场景状态.

2.2 构建一个游戏循环

游戏场景是循环渲染的结果,在循环过程中响应事件回调函数,处理用户输入事件.window.requestAnimationFrame()回调调用函数本身可以形成循环.

  1. window.main = function(){
  2. window.requestAnimationFrame(main);
  3. //无论你的主循环需要做什么
  4. };
  5. main(); //开始循环

上面的程序中出现了两个问题:

  • main()函数污染了window对象
  • 没有提供停止循环的方法

第一个问题可以使用立即调用的函数表达式(IIFE)创建创建游戏循环.第二个问题可以创建一个Game主体对象,保存requestAnimationFrame返回值stopKey,需要停止循环时,调用window.cancelAnimationFrame()方法,并传入stopKey.

  1. ;(function(){
  2. function main(){
  3. Game.stopKey = window.requestAnimationFrame(main);
  4. //你的主循环内容
  5. }
  6. main(); //开始循环
  7. })();

requestAnimationFrame方法会给回调函数传入一个精确到千分之一毫秒的事件对象DOMHighResTimeStamp,用来记录循环时间.

  1. ;(function(){
  2. Game = {}
  3. function main(tFrame) {
  4. Game.stopKey = requestAnimationFrame(main)
  5. console.log(tFrame)
  6. }
  7. main()
  8. })();

每帧的工作时间

  1. ;(function(){
  2. function init() {
  3. Game = {
  4. tickLength: 16, // 20Hz
  5. lastTick: performance.now(), // 最后更新时间
  6. stopKey: 0
  7. }
  8. }
  9. function updateBatch(tickNum) {
  10. for(let i = 0; i < tickNum; i++) {
  11. Game.lastTick = Game.lastTick + Game.tickLength
  12. update(Game.lastTick)
  13. }
  14. }
  15. function update(lastTick) {
  16. console.log('update: ' + lastTick)
  17. }
  18. function render(tFrame) {
  19. }
  20. function main(tFrame) {
  21. Game.stopKey = requestAnimationFrame(main)
  22. let nextTick = Game.lastTick + Game.tickLength
  23. let tickNum = 0
  24. /*
  25. console.log('tFrame: ' + tFrame)
  26. console.log('Game.lastTick: ' + Game.lastTick)
  27. console.log('nextTick: ' + nextTick)
  28. */
  29. if (tFrame > nextTick) {
  30. let sinceTick = tFrame - Game.lastTick
  31. tickNum = Math.floor((tFrame - Game.lastTick) / Game.tickLength)
  32. }
  33. // console.log('tFrame: ' + tFrame)
  34. // console.log('now: ' + performance.now())
  35. updateBatch(tickNum)
  36. render(tFrame)
  37. Game.lastTick = tFrame
  38. }
  39. init()
  40. main()
  41. })();

二. 游戏开发技术

1 Canvas

<canvas>HTML5新增的元素,可用于通过使用JavaScript中的脚本来绘制图形。它可以用于绘制图形,制作照片,创建动画,甚至可以进行实时视频处理或渲染。

  • <canvas>标签
    1. <canvas id="canvas"></canvas>
  • js获取canvas对象以及上下文(context)
    1. var canvas = document.getElementById('canvas');
    2. var ctx = canvas.getContext('2d');
  • 根据上下文(context)绘制图像
    1. ctx.fillStyle = 'green';
    2. ctx.fillRect(10, 10, 100, 100);

    1.1 基本用法

1.1.1 canvas标签

<canvas> 标签只有两个属性 —— widthheight,默认值分别为300px150px,用来定义画布的大小.

如果浏览器不支持canvas标签,会显示canvas标签的内部元素,闭合标签</canvas>不能省略.

  1. <canvas id="stockGraph" width="150" height="150">
  2. current stock price: $3.15 +0.15
  3. </canvas>
  4. <canvas id="clock" width="150" height="150">
  5. <img src="images/clock.png" width="150" height="150" alt=""/>
  6. </canvas>

1.1.2 context渲染上下文

<canvas>元素创造了一个固定大小的画布,它公开了一个或多个渲染上下文,其可以用来绘制和处理要展示的内容

通过getContext()方法可以获取画布的context,2D渲染上下文需要传入参数'2d'.

  1. var canvas = document.getElementById('canvas');
  2. var ctx = canvas.getContext('2d');

为了防止浏览器不支持canvas标签而获取context失败,需要在获取context之气那检查支持性

  1. var canvas = document.getElementById('canvas');
  2. if (canvas.getContext){
  3. var ctx = canvas.getContext('2d');
  4. // drawing code here
  5. } else {
  6. // canvas-unsupported code here
  7. }

模板:

  1. <html>
  2. <head>
  3. <title>Canvas tutorial</title>
  4. <script type="text/javascript">
  5. function draw(){
  6. var canvas = document.getElementById('tutorial');
  7. if (canvas.getContext){
  8. var ctx = canvas.getContext('2d');
  9. }
  10. }
  11. </script>
  12. <style type="text/css">
  13. canvas { border: 1px solid black; }
  14. </style>
  15. </head>
  16. <body onload="draw();">
  17. <canvas id="tutorial" width="150" height="150"></canvas>
  18. </body>
  19. </html>

1.2 绘制图形

Canvas坐标系是从左往右为x轴正轴,从上到下是y轴正轴.

1.2.1 矩形

  1. 填充矩形
    1. context.fillRect(x, y, width, height)
  2. 矩形边框
    1. context.strokeRect(x, y, width, height)
  3. 清除矩形区域
    1. context.clearRect(x, y, width, height)

1.2.2 路径

路径是由许多子路径构成的,所有子路径存放在一个列表中,当调用beginPath()方法时,清空列表,重新绘制路径.

  1. 开始路径
    1. context.beginPath()
  2. 移动笔触,设置起始位置
    1. context.moveTo(x, y)
  3. 绘制路径

    • 线
      1. context.lineTo(x, y)
    • 圆弧

      1. // x, y: 位置
      2. // r: 半径
      3. // startRadian: 起点弧度
      4. // endRadian: 终点弧度
      5. // anticlockwise: 是否顺时针,默认true
      6. context.arc(x, y, r, startRadian, endRadian, anticlockwise)
      7. // 根据给定的控制点和半径画一段圆弧,再以直线连接两个控制点
      8. context.arcTo(x1, y1, x2, y2, r)
    • 贝塞尔曲线
      • 二次贝塞尔曲线
        1. // cp1x, cp1y: 控制点
        2. // x, y: 结束点
        3. context.quadraticCurveTo(cp1x, cp1y, x, y)
      • 三次贝塞尔曲线
        1. // cp1x, cp1y: 第一控制点
        2. // cp2x, cp2y: 第二控制点
        3. // x, y: 结束点
        4. context.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
      • 矩形
        1. context.rect(x, y, width, height)
  4. 闭合路径,路径首尾连接
    1. context.closePath()
  5. 绘制路径

    • 绘制边框
      1. context.stroke()
    • 填充图形
      1. context.fille()
  6. Path2D对象

    1. let path = new Path2D
    2. path.rect(200, 100, 100, 50)
    3. path.moveTo(250, 100)
    4. path.arc(250, 125, 25, - Math.PI / 2, Math.PI / 2)
    5. path.bezierCurveTo(237.5, 112.5, 262.5, 137.5, 250, 100)
    6. context.stroke(path)

    1.2.3 色彩

context有两种色彩属性:

  • fillStyle: 填充颜色
  • strokeStyle: 边框颜色
  1. ctx.fillStyle = "orange";
  2. ctx.fillStyle = "#FFA500";
  3. ctx.fillStyle = "rgb(255,165,0)";
  4. ctx.fillStyle = "rgba(255,165,0,1)";

参考