本节书摘来异步社区《HTML5游戏编程核心技术与实战》一书中的第1章,第1.2节,作者: 向峰 责编: 杨海玲,更多章节内容可以访问云栖社区“异步社区”公众号查看。
HTML5游戏编程核心技术与实战准备好HTML5开发工具之后,我都迫不急待地想看看HTML5给我们带来了哪些让人激动不已的新特性,相信,你也会和我一样,对它感到兴奋。由于HTML5新增加了很多元素,这里,我们重点简单介绍一下在游戏中会使用到的一些元素,在介绍每一个新的元素时,我都会列举一些小的例子。现在假定读者有一定的HTML以及JavaScript方面的知识,如果您对这些代码完全不清楚,没关系,后面的章节中会详细地解说。目前,我们只需要知道HTML5能做什么就足够了。
canvas是HTML5中一个非常重要的元素,canvas元素提供了硬件加速机制,能够让我们实现高效的绘图功能。第2章我们会详细介绍canvas功能,这里,我们先来领略一下canvas的魅力。
打开你的编辑工具,输入以下代码:
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/> </head> <style type="text/css"> body, div{margin:0px;padding:0px;text-align:center} #cav{ border:2px solid black; border-radius:4px; box-shadow:0px 0px 10px black; -webkit-box-shadow:0px 0px 10px black; -moz-box-shadow:0px 0px 10px black; } #bk{ margin:10px auto; width:400px; height:36px; } .bk{ text-align:center; width:20px; height:20px; margin:12px; display:inline-block; border:1px dotted gray; } </style> <body> <div id="bk" align="center"></div> <canvas id="cav" width="400" height="300"></canvas> </body> <script src="../js/jquery.js"></script> <script charset="utf-8"> //定义画笔颜色 var bColor = ["#000000", "#999999", "#CC66FF", "#FF0000", "#FF9900", "#FFFF00", "#008000", "#00CCFF"]; //当前画笔颜色 var col="#FF0000"; function initBrush() { for(var i=0;i<bColor.length;i++) { var bk = $("<span class='bk'></span>") .css("background-color", bColor[i]) .click(function(){ col = $(this).css("background-color"); }); $("#bk").append(bk) } } function initPainter() { //绑定绘图canvas var can = $("#cav"), self = this, x=0, y=0; var ctx = can[0].getContext("2d"); ctx.lineWidth = 2; //绑定鼠标按下时间 can.on("mousedown", function(e){ e.preventDefault(); ctx.strokeStyle = col; x = e.offsetX, y = e.offsetY; //开始路径 ctx.beginPath(); ctx.moveTo(x, y); //绑定鼠标移动事件 can.on("mousemove", function(e){ var nx = event.offsetX, ny = event.offsetY; ctx.lineTo(x, y); ctx.stroke(); x = nx; y = ny; }); //绑定鼠标抬起事件 can.on("mouseup", function(){ //取消鼠标移动事件 can.off("mousemove"); }); }) } $(document).ready(function(){ initBrush(); initPainter(); }) </script> </html>虽然有点长,但当你完成它的时候,我想,一定不会让你失望的,这是一个不错的开始,也许现在你可能还看不太懂,没关系,第2章就会对canvas元素进行详细介绍。以上代码通过canvas元素完成了一个在浏览器中使用的画板,可以通过Chrome浏览器打开它,看到的效果如图1-9所示,你可以选择不同颜色的画笔在画板上进行涂鸦。
除了支持2D渲染技术外,canvas还提供了通过WebGL的方式,利用硬件加速提供3D渲染技术。以下代码使用了开源的three.js的3D引擎,完成了一个简单的旋转方块,注意,该3D效果完全使用WebGL技术,不需要安装任何插件。顺便说一下,three.js是一款完全免费使用的3D引擎,该引擎完全基于WebGL编写,事实上WebGL也是在canvas上进行渲染,目前对WebGL支持比较好的浏览器有Chrome、Firefox以及Safari等。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Cube</title> <script src="../js/jquery.js"></script> <!--引入Three.js--> <script src="three.min.js"></script> </head> <style type="text/css"> body, div{margin:0px;padding:0px;text-align:center} #sce{ border:2px solid black; border-radius:4px; width:480px; height:320px; margin:10px auto; box-shadow:0px 0px 10px black; -webkit-box-shadow:0px 0px 10px black; -moz-box-shadow:0px 0px 10px black; } </style> <body> <div id="sce"></canvas> <script> $(document).ready(function(){ init(); }) //定义3D场景,摄像机光源等和立方体 var scene, cam, render, cube, light; //初始化 function init() { initScene(); initCube(); animate(); } function initScene() { scene = new THREE.Scene(); cam = new THREE.PerspectiveCamera(45, $("#sce").width()/$("#sce"). height(), 0.1, 2000); cam.position.x= cam.position.y = cam.position.z=2; cam.lookAt(new THREE.Vector3(0, 0, 0 )); render = new THREE.WebGLRenderer({antialias:true}); render.setSize($("#sce").width(), $("#sce").height()); $("#sce").append(render.domElement); } function initCube() { //加载纹理 var text = THREE.ImageUtils.loadTexture( 'box.gif' ); text.anisotropy = render.getMaxAnisotropy(); //创建几何图形和材质 var g = new THREE.CubeGeometry(1, 1, 1), m = new THREE.MeshBasicMaterial({ map: text }); cube = new THREE.Mesh(g, m); scene.add(cube); } function animate() { requestAnimationFrame(animate); cube.rotation.y += Math.PI/180; render.render(scene, cam); } </script> </body> </html>整个代码实现了一个绕Y轴旋转的方块,最好使用Chrome浏览器进行浏览,运行效果如图1-10所示。
当然,对于代码的细节暂时可以不用去关注,目前,我们只需要知道使用WebGL能做什么就足够了,对于目前的HTML5来说,2D的游戏将会是比3D更好的选择。
正如我们所期待的一样,HTML5中增加了两个多媒体元素以及
比如,以下代码可以直接在浏览器中播放mp3音频文件。
<!DOCTYPE html> <meta charset="utf-8" /> <style type="text/css"> body{text-align:center;} </style> <body> <h2>简单的音视频</h2> <audio id="a1" src="media/scg.mp3" autoplay mediagroup="gp" controls> 您的浏览器不支持audio元素 </audio><br> </body> </html>在浏览器中可以看到一个带有简单控制面板的音频播放器,如图1-11所示。
HTML5提供了一组关于地理信息的API,通过这组API我们可以获取到客户端所在的经度和纬度,利用这些信息可以向这个坐标附近的区域提供服务。这些服务一般指的是基于位置的服务(LBS),LBS能够广泛支持需要动态地理空间信息的应用,从寻找旅馆、急救服务到导航,从社交游戏到生活服务,几乎可以覆盖生活中的所有方面。常见的一些LBS的应用有:位置信息查询、急救服务、道路辅助与导航、社交游戏等。
借助HTML提供的Geolocation API功能,很容易获取到用户当前的位置,结合这组API,我们可以编写基于LBS的社交类游戏。
同样地,我们给出一段简单的代码,可以获取用户所在的位置信息。
<!DOCTYPE html> <html> <head> <title> Location </title> <meta charset="utf-8" /> </head> <body> <input type="button" id="btnLocation" value="获取位置信息"/> </body> <script src="js/jquery.js"></script> <script> //检测是否支持地理信息 function isSupportGeo() { return (navigator.geolocation!=null) } //绑定click事件 $("#btnLocation").click(function(){ if(!isSupportGeo()) { alert("浏览器不支持地理位置定位!"); } else { navigator.geolocation.getCurrentPosition(doSucc, doFail); } }); //处理成功 function doSucc(position) { // alert("成功获取地理位置!"); alert("成功获取地理位置,您当前所处的位置位于经度:"+position.coords.longitude+" 纬度:"+position.coords.latitude); } //处理失败 function doFail(error) { var errors=["拒绝地理信息服务", "获取不到地理信息", "获取信息超时"] alert(errors[error.code+1]); } </script> </html>代码绑定一个“获取位置信息”的按钮,单击按钮将会调用isSupportGeo方法检测浏览器是否支持地理信息,如果支持的话就调用getCurrentPosition获取当前设备的地理信息。需要注意的是,第一次访问地理信息的时候由于个人隐私的问题,浏览器会弹出一个窗口询问用户是否允许让浏览器访问自己的位置,用户可以选择接受或者拒绝,如果拒绝的话将会得到一个错误。
我们知道在传统的Web应用程序开发过程中,当客户端和服务器进行数据交互时,远程服务器需要存储客户端的各种数据,如果需要在客户端保留数据,可以采用“Cookie”的形式,但Cookie的存储量很小,通常只有4 kB,而且安全性和使用性都还有所欠缺。为了能够满足于HTML5本地应用的迅速发展,弥补HTML在本地数据持久化的弱势,HTML5推出了新的本地存储规范,提供了容量更大、更安全和更易于使用的本地存储方案。
HTML5中使用Web Storage技术进行本地存储,能够在Web客户端进行数据存储。Web Storage曾经属于HTML5的规范,目前已经被独立出来形成单独的规范体系。简单来说使用Web本地存储类似于HashMap一样,采用键值对的形式保存数据,而且保存的数据会根据需要持久化在浏览器所在的客户端,具体来说Web Storage又可以分为以下两种。
localStorage。将数据保存在客户端本地的硬件设备(通常指硬盘,但也可以是其他硬件设备)中,即使浏览器被关闭了,该数据仍然存在,下次打开浏览器访问网站时仍然可以继续使用。sessionStorage。将数据保存在session对象中。session对象就是会话对象,session中存储的数据独立于每个客户,该数据会随着浏览器的关闭而消失。这两者的区别在于,sessionStorage为临时保存,而localStorage为永久保存。localStorage提供了5 MB的存储空间,而sessiongStorage甚至没有限制,取决于内存的大小。
对于这两种本地存储对象都可以使用以下方法进行数据存取。
setItem(key, value)。使用键值对的方式保存数据,key为键,value为值,键是一个字符串,而数据则可以是任何类型的JavaScript基本数据类型,包括字符串、Boolean、整数和浮点数。需要注意的是,这些数据在存储时实际上是以字符串保存的。因此在访问数据时需要进行数据类型的转换,也可以直接用localStorage.key = value或者localStorage[key] = value的方式设置,对于复杂的数据类型,可以使用JSON格式的对象转换成字符串对象存储。getItem(key)。根据key键值来获取数据,也可以直接用localStorage.key或者localStorage[key]获取,需要注意的是,这些数据在取出来时实际上是字符串。因此在访问数据时需要进行数据类型的转换。
removeItem(key)。清除key键值。 clear()。清除所有的键值对。例如,以下简单的代码用于记录用户访问页面的总次数:
<body> <h2>您是第<span id="ct"></span>次访问本页面</h2> </body> <script> $(document).ready(function(){ //获取访问次数 readCount(); }) //获取访问次数 function readCount() { var count = localStorage["count"]; if(count==null){count=1;} else {count = Number(count)+1;} $("#ct").text(count); localStorage["count"] = count; } </script>上面代码中记录了用户访问页面次数,第一次访问时,获取的count为空,设置其为1,随后的访问中获取数据后通过Number函数转成数字进行累加并重新设置到localStorage中。需要注意的是,即使关闭浏览器,下次打开时,数据依然存在,因为localStorage的数据实际上持久化在本地文件中。
一直以来,HTTP的无状态性和单向性作为常用的应用已经足够,但还无法实现游戏中需要的实时双向通信技术,而HTML5中新增加的WebSocket通信元素则弥补了这一缺陷,使用新的Web sockets技术对于实现高效的Web网络游戏提供了基础。
我们可以使用以下代码连接支持Web sockets协议的服务器端,并发送数据。
var ws = new WebSocket(“ws://localhost:9000/socket”); ws.onopen = function(){ try { ws.send(“Hello, Web Socket”); } catch(e) { console.log(“发送消息失败!”); } }当然,需要注意的是,以上只是使用WebSocket元素的客户端代码,需要有支持WebSocket协议的服务器端支持,在本书的第5章会详细介绍使用WebSocket的客户端以及Node.js的服务器端通信技术。
Web Worker技术填补了JavaScript中一直所缺少的多线程技术,之前的HTML中,进行DOM元素渲染以及执行JavaScript脚本都是在单一的线程中执行,一旦在JavaScript中需要执行比较耗时间的操作,则会阻塞当前线程的执行。所带来的效果就是,当前的浏览器处于无响应状态,直到浏览器会弹出一个提示脚本运行时间过长的信息。这里,我们看一个简单的例子。
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/> </head> <style type="text/css"> body, div{margin:0px;padding:0px;text-align:center} </style> <body> <input type="button" id="btn_calc" value="计算圆周率" /> <input id="txt_rt" /> </body> <script src="../js/jquery.js"></script> <script charset="utf-8"> $(document).ready(function(){ $("#btn_calc").on("click", function(){ $("#txt_rt").val(getPI(80000000)); }) }) //求圆周率 function getPI(n) { var a=0; for (i=0;i<=n;i++) { a = a + 4*Math.pow(-1, i)/(2*i+1); } return a; } </script> </html>在该例子中,提供了一个通过级数获取圆周率的函数,参数n表示迭代次数,次数越高,计算越精确。然后提供一个按钮计算圆周率在文本框中显示,在浏览器中运行,我们发现当迭代次数很高的时候,计算量较大,在得到计算结果之前,按钮和文本框都是处于无法响应的状态,这也符合实际情况,因为此时DOM元素的更新被大计算量的程序给阻塞了。如果使用Web Worker则不会出现此情况,我们可以把计算量大的工作放到一个单独的线程中运行,不会阻塞当前线程。
创建后台线程可以使用以下方式:
var worker = new Worker(“worker.js”)我们把计算工作放到worker.js文件中,前台可以通过postmessage方法发送消息给Worker线程,请求Worker进行工作,直到Worker线程工作完毕返回结果。
对于前面计算PI的例子,我们可以修改如下,首先,创建worker.js文件,代码如下:
function getPI(n) { var a=0; for (i=0;i<=n;i++) { a = a + 4*Math.pow(-1, i)/(2*i+1); } return a; } onmessage = function(e) { var result = getPI(e.data); postMessage(result); }worker.js文件中定义了计算PI的函数,我们需要定义一个onmessage函数,当前台通过调用worker对象的postmessage方法时,该回调函数被响应。
然后,把显示结果的页面修改如下:
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/> </head> <style type="text/css"> body, div{margin:0px;padding:0px;text-align:center} </style> <body> <input type="button" id="btn_calc" value="计算圆周率" /> <input id="txt_rt" /> </body> <script src="../js/jquery.js"></script> <script charset="utf-8"> $(document).ready(function(){ $("#btn_calc").on("click", function(){ var wk = new Worker("worker.js"); //注册返回结果,当woker发送postmessage时,该回调函数响应 wk.onmessage = function(e) { $("#txt_rt").val(e.data); } //发送计算请求 wk.postMessage(80000000); }) }) </script> </html>这里,首先创建了一个Worker对象,然后在Workder对象中注册一个onmessage回调函数。注意,这个回调函数和workder.js文件中的onmessage是不同的,这里的回调函数当woker.js文件中的onmessage函数执行完毕后响应,最后,调用Worker对象的postMessage方法发送计算请求,此时,woker.js文件中的onmessage函数就会执行。
修改成Worker版本后,你会发现,当单击按钮得到计算结果之前,按钮和文本框都是可以响应的,因为此时计算线程被独立出来了。
相关资源:敏捷开发V1.0.pptx