game.html
js/
里面建立game.js
images/
里面放三张图片,一张背景图片(background.png),一张英雄图片(hero.png),一张怪物的图片(monster.png)在game.html
里面写上如下几行简单的HTML代码:html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Simple Canvas Game</title> </head> <body> <script src="js/game.js"></script> </body> </html>
咱们在game.html
引入了game.js
文件,没错,剩下的全部工做都是在操做game.js
,为其添加游戏的js代码。前端
在game.js 里面,咱们首先须要为游戏的舞台建立一张画布(canvas):web
var canvas = document.createElement("canvas"); var ctx = canvas.getContext("2d"); canvas.width = 512; canvas.height = 480; document.body.appendChild(canvas);
这里经过js来建立了一个元素并设置canvas
的宽和高,最后将其添加到<body>标签后。var ctx = canvas.getContext("2d");
中的ctx
变量是咱们后面会用到的,具体的canvas用法查看这里的连接:canvas
https://developer.mozilla.org/en/canvas_tutorial浏览器
游戏须要加载咱们以前存放在images文件夹下面的三张图片:app
// Background image var bgReady = false; var bgImage = new Image(); bgImage.onload = function () { bgReady = true; }; bgImage.src = "images/background.png"; // Hero image var heroReady = false; var heroImage = new Image(); heroImage.onload = function () { heroReady = true; }; heroImage.src = "images/hero.png"; // Monster image var monsterReady = false; var monsterImage = new Image(); monsterImage.onload = function () { monsterReady = true; }; monsterImage.src = "images/monster.png";
以上三张图片都是经过建立简单的图片对象来实现加载的,相似bgReady的三个变量用来标识图片是否已经加载完成,若是若是在图片加载未完成状况下进行绘制是会报错的。若是你不太肯定new Image()
究竟是个什么东西,你能够在bgImage.src = "images/background.png";
以后使用console.log(bgImage);
来查看,你看到的将是相似:dom
<img src="images/background.png" >
咱们须要定义一些对象,以便咱们在后面会用到:异步
var hero = { speed: 256 // movement in pixels per second }; var monster = {}; var monstersCaught = 0;
既然是英雄抓获怪物,咱们得要有一个英雄
和怪物
的对象。而英雄
有一个speed
属性用来控制他每秒移动多少像素。怪物游戏过程当中不会移动,因此暂时不用设置属性。monstersCaught
则用来存储怪物被捉住的次数,初始值固然为0了。函数
游戏是给人玩的,那么咱们怎么知道用户到底在这个过程当中干了什么?按了键盘?点了鼠标?这些都是用户在玩游戏的时候的输入,因此咱们一旦捕获到这些输入,咱们就能够根据游戏的逻辑对用户的输入进行处理了:oop
// Handle keyboard controls var keysDown = {}; addEventListener("keydown", function (e) { keysDown[e.keyCode] = true; }, false); addEventListener("keyup", function (e) { delete keysDown[e.keyCode]; }, false);
这里咱们只是监听两个用户的输入:
而后咱们将用户的输入先保存起来,并无当即响应。为此,咱们用keysDown
这个对象来保存用户按下的键值(keyCode)
,若是按下的键值在这个对象里,那么咱们就作相应处理。
在前端开发中,通常是用户触发了点击事件而后才去执行动画或发起异步请求之类的
游戏在结束的时候,咱们须要开始新的一轮游戏,因此在game.js
添加reset
函数
// Reset the game when the player catches a monster var reset = function () { hero.x = canvas.width / 2; hero.y = canvas.height / 2; // Throw the monster somewhere on the screen randomly monster.x = 32 + (Math.random() * (canvas.width - 64)); monster.y = 32 + (Math.random() * (canvas.height - 64)); };
reset()
函数用于开始新一轮和游戏,在这个方法里咱们将英雄放回画布中心同时将怪物放到一个随机的地方。
在游戏的过程当中,不论是用户在玩(有正确输入的状态)仍是游戏结束,咱们都是须要及时更新游戏的对象:
var update = function (modifier) { if (38 in keysDown) { // Player holding up hero.y -= hero.speed * modifier; } if (40 in keysDown) { // Player holding down hero.y += hero.speed * modifier; } if (37 in keysDown) { // Player holding left hero.x -= hero.speed * modifier; } if (39 in keysDown) { // Player holding right hero.x += hero.speed * modifier; } // Are they touching? if ( hero.x <= (monster.x + 32) && monster.x <= (hero.x + 32) && hero.y <= (monster.y + 32) && monster.y <= (hero.y + 32) ) { ++monstersCaught; reset(); } };
update
函数负责更新游戏的各个对象,会被规律地重复调用。首先它负责检查用户当前按住的是中方向键,而后将英雄往相应方向移动。
有点费脑力的或许是这个传入的modifier
变量。你能够后面
将要实现的main
方法里看到它的来源,但这里仍是有必要详细解释一下。它是基于1开始且随时间变化的一个因子。例如1秒过去了,它的值就是1,英雄的速度将会乘以1,也就是每秒移动256像素;若是半秒钟则它的值为0.5,英雄的速度就乘以0.5也就是说这半秒内英雄以正常速度一半的速度移动。理论上说由于这个update
函数被调用的很是快且频繁,因此modifier
的值会很小,但有了这一因子后,无论咱们的代码跑得快慢,都可以保证英雄的移动速度是恒定的。
这里须要说明一下下面的判断怪物和英雄是什么根据:
if ( hero.x <= (monster.x + 31) && monster.x <= (hero.x + 31) && hero.y <= (monster.y + 32) && monster.y <= (hero.y + 32) )
上面的31
,32
是由hero
和monster
图片的大小决定的,咱们的hero图片是32x32
,monster图片是30x32
,因此根据坐标的位于图片中心的法制,就能够获得上面的判断条件。
如今英雄的移动已是基于用户的输入(按下上
,下
,左
,右
键)了,接下来该检查移动过程当中所触发的事件了,也就是英雄与怪物相遇。这就是本游戏的胜利点,monstersCaught +1
而后从新开始新一轮。
以前写的代码都是在准备前期工做和处理一些游戏的状态等,下面将进入正题:咱们须要将全部的东西画出来
// Draw everything var render = function () { if (bgReady) { ctx.drawImage(bgImage, 0, 0); } if (heroReady) { ctx.drawImage(heroImage, hero.x, hero.y); } if (monsterReady) { ctx.drawImage(monsterImage, monster.x, monster.y); } // Score ctx.fillStyle = "rgb(250, 250, 250)"; ctx.font = "24px Helvetica"; ctx.textAlign = "left"; ctx.textBaseline = "top"; ctx.fillText("Goblins caught: " + monstersCaught, 32, 32); };
这里的ctx
就是最前面咱们建立的变量。而后利用canvas
的drawImage()
首先固然是把背景图画出来。而后如法炮制将英雄和怪物也画出来。这个过程当中的顺序是有讲究的,由于后画的物体会覆盖以前的物体。
这以后咱们改变了一下Canvas
的绘图上下文的样式并调用fillText
来绘制文字,也就是记分板那一部分。本游戏没有其余复杂的动画效果和打斗场面,绘制部分大功告成
咱们实现了将画面画出来之后,咱们紧接着须要实现的就是游戏的循环结构,因而将它放在main
函数里:
// The main game loop var main = function () { var now = Date.now(); var delta = now - then; //console.log(delta); update(delta / 1000); render(); then = now; // Request to do this again ASAP requestAnimationFrame(main); };
上面的主函数控制了整个游戏的流程。先是拿到当前的时间用来计算时间差(距离上次主函数被调用时过了多少毫秒)。获得modifier
后除以1000(也就是1秒中的毫秒数)再传入update
函数。最后调用render
函数而且将本次的时间保存下来。
在上面的main
函数中,咱们经过requestAnimationFrame()
调用了main
函数,因此咱们须要声明:
var w = window; requestAnimationFrame = w.requestAnimationFrame || w.webkitRequestAnimationFrame || w.msRequestAnimationFrame || w.mozRequestAnimationFrame;
这里这么多的||
,不为别的,就是考虑到浏览器兼容问题而已。
万事具有,只欠东风。到此,全部的游戏代码基本就写完了,咱们如今须要作的就是调用相应的函数来启动游戏:
// Let's play this game! var then = Date.now(); reset(); main();
到这里代码就写完了。先是设置一个初始的时间变量then
用于首先运行main
函数使用。而后调用 reset
函数来开始新一轮游戏(若是你还记得的话,这个函数的做用是将英雄放到画面中间同时将怪物放到随机的地方以方便英雄去捉它)
用浏览器打开game.html
,开始玩游戏吧!
在玩游戏的过程当中,你会发现每一次hero
捕获到monster
,hero
就回到了canvas
画布的正中间。那么如今须要作的就是,将hero
在捕捉到monster
的时候让hero
就停留在捕获的位置,再也不是回到canvas
正中间。
这个现象的出现主要是由于在reset
函数中将hero.x
和hero.y
写死了,因此一个最简单的方法就是在reset
中传入参数:
var reset = function (x,y) { hero.x = x; hero.y = x; };
而后在update
调用reset
的时候传入捕获时位置的参数:
var update = function (modifier) { //...other codes ++monstersCaught; reset(heor.x,hero.y); };
最后在开始游戏的时候将hero
放在canvas
最中间便可:
var then = Date.now(); reset(canvas.width / 2,canvas.height / 2); main();
大功告成!
Hapyy Hacking