Html游戏总结(套路)

游戏总结(套路)

一个简单的2D网页小游戏它的制做过程是有规律可寻的,它每一个部分都有必定的套路,咱们应该
把有规律的部分总结在一块儿,造成一个模板,把相应的模板写好了,到要生产某个对象时就能够用
模板,还有游戏的整个流程每一个函数,每一个js文件之间都是分工明确的;咱们要总结好了才写,
不要想到哪就写哪,这样虽然结果是相同的,但可能代码的可读性和维护性不是很强,思路不是很
清晰;那么就要总结出一个清晰的游戏流程;web

游戏展现

游戏很是简单,无非就是我放飞机移动发射子弹设计敌机相碰撞就得分,若碰到敌机就over,而后
游戏从新开始,怪物出如今地图的随机位置,英雄初始化在地图的中间。canvas

游戏的简单布局

首先是静态界面--->第二创建js文件(
1.一个游戏的主要的js便是包括了整个游戏的主循环函数,还有改变全部具备动态效果的对象的位
置的函数,还有当全部具备动态效果的对象的位置改变时从新化界面的函数-app.js
2.加载全部图片的js便是要等游戏中要用到的全部图片都加载完成后在开始进行下一步操做-
resources.js
3.全部的对象的一个精灵js即便把游戏中全部对象所共有的一些属性或是行为封装成一个新的对象-
Sprite.js
四、对全部输入事件进行处理操做的js-input.js;)数组

加载图片

2D网页小游戏它的重点就是靠图片来美化界面,显而易见图片很重要,全部操做都基于图片上,因此
要等全部的图片都加载好了才进行动态操做 这就涉及到如何判断所有图片都加载好了 浏览器

咱们先举个列子来看如何等图片加载好了在进行操做:app

// 背景图片 bgImage
    var bgReady = false;
    var bgImage = new Image();
    bgImage.onload = function () {
            alert("ssssss")
        bgReady = true;
    };
    bgImage.src = "images/background.png";  

 

这里须要注意的一点,把bgImage.src写在bgImage.onload以后是为了解决IE显示的bug,
因此建议你们都这么么写。框架

由此段代码咱们能够发现当图片加载好了以后 bgReady 就是true,判断图片是否加载好了就能够
以此为判断依据; dom

那么对于这个游戏模板来讲咱们会用到两张图片(背景、全部对象的合成图),对这两张图盘片的操
做以下:ide

(function(){//这样写是为了减小全局变量,节约浏览器的空间(全局变量要等浏览器生命周期结  
  束时才销毁)
    var resourceCache = {};//这个对象的建立是为了装全部图片的url,有两个值(1.一个  
    img对象或2.false// 做用是为了判断是否全部的图片都加载完成  

    var readyCallback = [];//这个数组的建立是为了装回调函数,而且到必定的时候用  
    forEach调用回调行数;

    //将传入的参数进行分类,若是是数组就用forEach循环将每一个元素做为参数传入调用)  
     _load()方法;若是不是数组
    //而是单独的一个图片路径就直接传入调用_load()方法;
    function load(urlOrArray){
        if(urlOrArray instanceof Array){
            urlOrArray.forEach(function(url){//forEach(function  
             (item,index,array){}),
            // 第一个参数表示数组中的每一个元素,第二个表示每一个元素下表,第三个表示数组  
             自己,
            // 只传一个参数表示数组里的每一个元素;
                _load(url);
            })
        }else{
            _load(urlOrArray);
        }
    }


    function _load(url){
        //首先判断url是否有值,如有就说明resourceCache[url]是一个img对象,直接返回
        if(resourceCache[url]){
            return resourceCache[url]
        }else{//若没有值就先将建立一个Image()对象,将resourceCache[url] 设置为   
     false,后面就调用isReady()方法
              // (就是判断每一个URL是不是false,判断是否图片全都加载好了,若好了表示  
            全部的图片都加载好了,才调用回调函数;
            var img = new Image();

            img.onload = function(){
                resourceCache[url] = img;

                if(isReady()){
                    readyCallback.forEach(function(func){
                        func();
                    });
                }
            }

            resourceCache[url] = false;
            img.src = url;
        }
    }
//判断是否全部的图片都加载好了,是就返回true,否就返回false;
    function isReady(){
        var flag = true;
        for(var i in resourceCache){
            if(!resourceCache[i]){
                flag = false;
            }
        }
        return flag;

    }
//将回调函数加入readyCallback数组;
    function onReady(func){
        readyCallback.push(func);
    }
//得到单个img对象
    function get(url){
        return resourceCache[url];
    }
//创建一命名空间,好实如今某个局部范围里的函数在全局(window)里均可以调用,但  
又不失局部函数的特色;
    window.resources = {
        load: load,
        onReady: onReady,
        get: get
    }

})();

 

在上面的代码中提到回调函数这个概念,那么什么是回调函数呢?函数

咱们看一个例子就显而易见了 : 函数是对象,也就意味着函数能够看成参数传入另一个函数中。给函数writeCode()传入一个
函数参数introduceBugs(),在某个时刻writeCode()执行了(或调用了)introduceBugs (),在这种状况下,咱们称introduceBugs()是一个“回调函数”,简称“回调”(以个人理解就
是把一个函数写好先放着,当要调用时再把函数做为参数传入后调用): 布局

function writeCode(callback) {
        // 作点什么……
        callback();
        // ……
    }

    function introduceBugs() {
        // ……
    }

    writeCode(introduceBugs);

 

注意introduceBugs()做为参数传入writeCode()时,函数后面是不带括号的。括号的意思是
执行函数,而这里咱们但愿传入一个引用,让writeCode()在合适的时机执行它(调用它)。

回调的例子

咱们从一个例子开始,首先介绍无回调的状况,而后再进行修改。假设你有一张图片你要在加载好
时,1.改变他距离左边的距离,2.写好后你又想给它加一个边框,3.有些好后又像改变他距离上边
的距离 以通常的思路咱们会这样写:

 var img = new Image();
    img.onload = function(){
      img.style.position = 'absolute';
        img.style.left = "100px";
        img.style.top = "100px";
        img.style.border = "1px solid red";
    }
    img.src = 'images/1.jpg';

 

这样写虽然效果是达到了,但若是我又想修改的话,就只有在源代码上修改,这样就会破坏代码原生
态性;那咱们就加上回调行数后就能够这样写:

 var readyCallback = [];

    var img = new Image();
    img.onload = function(){
        readyCallback.forEach(function(func){
            func();
        })
    }
    img.src = 'images/1.jpg';

    document.body.appendChild(img);

    function onReady(func){

        readyCallback.push(func);
    }

    function move(){
        img.style.position = 'absolute';
        img.style.left = "100px";
    }

    function move2(){
        img.style.top = "100px";
    }

    function border(){
        img.style.border = "1px solid red";
    }

    onReady(move);
    onReady(move2);
    onReady(border);

 

这样用了回调函数后就增长函数的完整性,不用破坏代码的源生态性,能够随便添加或修改删除;
这个游戏的回调函数是:

//初始化函数
//要作游戏,那么在初始化的时候,最重要的首先是先要确保图片所有都先加载进来了
//第一个难点:确保全部图片加载完成以后,再调用初始化函数,这个时候就能够用到回调模式
function init(){
    //画背景
    terrainPattern = ctx.createPattern(resources.get('img/ 
    terrain.png'),'repeat');

    main();
}

 

初始化一些全局变量

var terrainPattern;//背景 var isGameOver=false; var singleCount= 0,doubleCount=0;//控制敌机出现的时间间隔;

 

//建立飞机/敌机对象,与其余对象都有的属性就在sprite里已添加,本身不一样或独有的就新加在上面; var player = { pos : [0, 50], sprite : new Sprite('img/sprites.png',[0, 0], [39,39], [0, 1], 16) } var enemies=[];//装敌机的数组;
var bullets=[];//装子弹的数组; var explosions=[];//装子弹碰撞敌机后的爆炸的数组; var lastFire=Date.now();//控制发射子弹的时间间隔; var enemySpeed=100;//敌机向左运动的速度; var playerSpeed=200;//我放飞机的运动速度; var bulletSpeed=500;//子弹的运动速度; var scores=0;//计算分数

定义游戏要使用的对象

在一个游戏中不少对象都拥有一些相同的属性和一些动态行为等,但咱们常常忽略了这一点,只是要
用那个对象时才临时的建立,这样既浪费了空间又不是横方便简洁,若是咱们事先将这些属性和一些
动态行为都结合在一块儿,封装成一个新的对象,到要建一个新的对象是把它加进去,若是是这个对象
本身所独有的或与其与对象不一样的一些属性就行加载本身的属性中;这样就节约了咱们的时间,应为
在那个大的精灵对象里该作的事都处理好了,咱们就只须要传入一参数就行了;

对于这个游戏模板来讲有这些对象:飞机(我放飞机,敌机,爆炸的敌机)子弹......对它们来讲
所共有的属性有以下:

function Sprite(url, pos, size, frames, speed, dir, once){
        this.url = url; //图片路径
        this.pos = pos; //游戏对象在图片上的位置(偏移量) []
        this.size = size; //游戏对象在图片上的大小 []
        this.speed = speed || 0;//游戏对象自身的运动速度
        this.frames = frames; //动画每次的偏移数组[0,1,2,3,2,1]
        this.dir = dir || 'horizontal';//游戏对象的方向默认为水平
        this.once = once || false;
        this._index = 0;
        this.done=false;//是否爆炸完成
    }

 

所共有的动态属性(经过改变背景偏移量来改变图片,从而达到可以动态效果)有:

Sprite.prototype = {
        constructor: Sprite,//为了达到对象的完整性将拥有prototype这个原型的函数有  
         指向Sprite;

        update: function(dt){
             
          this._index += this.speed * dt;//这个数会一直增大,它的做用是为告终合  
          this.frames数组长度算余数
            //经过余数来获取this.frames[idx % max];
        },

        render: function(ctx){
            var frame;
            if(this.speed > 0){
                var max = this.frames.length;
                var idx = Math.floor(this._index);
                frame = this.frames[idx % max];
                
               //判断若是this.done传入为true而且idx与max知足关系,就改变this.done  
                的值-->后面用来判断爆炸图片是否删除
                if(this.once && idx>=max){
                    this.done=true;
                    return;
                }

            }else{
                frame = 0;
            }

            var x = this.pos[0];
            var y = this.pos[1];
            //经过取于后获得下标,获取数组里的数从而改变背景偏移量

            if(this.dir == "vertical"){
                y += frame * this.size[1];
            }else{
                x += frame * this.size[0];
            }
            //改变了背景偏移量后又从新画图片

            ctx.drawImage(resources.get(this.url),
                            x, y,
                            this.size[0], this.size[1],
                            0, 0,
                            this.size[0], this.size[1]);
        }
    }

 

这段代码的实现原理了就是: (咱们以甲虫图片为例)

若是全部的对应图片的位置在一条水平线上,Y轴不变,第一个图片的坐标假设为(0,0),它自身
宽为79,一共有四张图片,若要在界面改变图片的样子,就只改变图片的背景偏移量而已,它们的背
景偏移量分别是(0,79),(0,179),(0,279),(0,379),这样很快就能看出规律,咱们生命一个
数组[0,1,2,3,2,1],将数组里的元素一次
单个图片的宽度,就能够达到图片循环变化的样子; 在这个游戏中就是运用了这个原理的;但就是得到数组元素的方式有点差异;

处理玩家输入

对于每一个游戏来讲都要处理玩家的输入,不一样的处理方式,效果虽然都同样,但若是处理的不当那么可能咱们快速切换按键或者是其余时候会出现Bug,这样看来输入的处理方式很重要;

对于这个游戏咱们的处理方式将所键的keyCode传化成字符串,这样避免了要记每一个键对应的keyCode值,咱们看下面代码:

  var pressKey = {};//这个数组是用来装输入或松开时的key属性的值(true,false)
    //将对应的keyCode处理成对应的字符串;
    function setKey(e, flag){
        var code = e.keyCode;
        var key;
        switch (code){
            case 37:
                key = 'LEFT';
                break;
            case 38:
                key = 'UP';
                break;
            case 39:
                key = 'RIGHT';
                break;
            case 40:
                key = 'DOWN';
                break;
            case 32:
                key = 'SPACE';
                break;
            default:
                key = String.fromCharCode(code);//除了上面列举的以外全都转化为字  
                符串;
                break;
        }
        pressKey[key] = flag;
    }

 

处理判读是否按下某个键的操做是这样实现的:当咱们按下时就把 pressKey[key] 设置为true,
松开时是就设置为false; 具体操做为:

 document.addEventListener('keydown',function(e){
        setKey(e,true);
    },false);

    document.addEventListener('keyup',function(e){
        setKey(e,false);
    },false);

    window.addEventListener('blur',function(e){
        pressKey = {};
    },false);

 

结合本游戏咱们的按键对应的操做是:

function handleInput(dt){
    if(input.isDown('LEFT') || input.isDown('A')){//按的是A键或左键X轴减  
      小......
       if( player.pos[0]<=0){
           player.pos[0]=0;
       }else{
           player.pos[0] -= playerSpeed * dt;
       }
    }
    if(input.isDown('UP') || input.isDown('W')){
        if(player.pos[1]<=0){
            player.pos[1]=0;
        }else{
            player.pos[1] -= playerSpeed * dt;
        }
    }
    if(input.isDown('RIGHT') || input.isDown('D')){
        if( player.pos[0]>canvas.width-39){
            player.pos[0]=canvas.width-39;
        } else{
            player.pos[0] += playerSpeed * dt;
        }
    }
    if(input.isDown('DOWN') || input.isDown('S')){
       if( player.pos[1]>canvas.height-35){
           player.pos[1]=canvas.height-32;
       }else{
           player.pos[1] += playerSpeed * dt;
       }
    }

    if(input.isDown('SPACE') && (Date.now() - lastFire > 100)){//按的是空格键键  
        而且时间间隔>100毫秒是添加子弹......
        //三种子弹的方向不一样,但都是子弹能够用同一个数组,就加了一个dir-->后面判断方向  
        后改变不一样的坐标就是了;
        bullets.push({
              
            pos: [player.pos[0] + player.sprite.size[0]/2,player.pos[1] +    
            player.sprite.size[1]/2],
            sprite: new Sprite('img/sprites.png',[0, 39],[18,8]),
            dir: 'forward'
        });//向水平方向的子弹
        bullets.push({
              
            pos: [player.pos[0] + player.sprite.size[0]/2,player.pos[1] +   
            player.sprite.size[1]/2],
            sprite: new Sprite('img/sprites.png',[0, 50], [9, 5]),
            dir: 'upward'
        });//向上方的子弹
        bullets.push({
            pos: [player.pos[0] + player.sprite.size[0]/2,player.pos[1] +   
            player.sprite.size[1]/2],
            sprite: new Sprite('img/sprites.png',[0, 60], [9, 5]),
            dir: 'downward'
        });//向下向的子弹

        lastFire = Date.now();
    }

}

 

6. 更新对象

游戏中对象的位置呀,行为呀时刻都在变化中,那咱们就习惯把它们都写在updat函数中: 在这个游戏中咱们变化的有子弹,敌机,我方飞机,爆炸,他们分为一个自身某个部位的运动,另外
还有他们相对于坐标的一个移动,自身的移动咱们把它看作全部对象的共有行为。把它封装在
sprite精灵中,因此咱们还要结合sprite对象使用,敌机、子弹爆炸都不是一个,因此还结合了循环一块儿完成;

1.对于敌机的移动咱们这样写的:

singleCount++;
    if(singleCount % 2==0){
        doubleCount++;
        if(doubleCount % 5==0){
            //前面的判断就是手动的给敌机的添加设置阻碍,让先后有必定的时间间隔;
           enemies.push({
               //全部的敌机外置都是在画布的右边出现,那X轴就是画布的宽,Y轴就是(0,  
               canvas.height-敌机自己的高);
               //一个数的变化范围若是在(0,自己),那么就把这个数*(0,1);
               pos:[canvas.width,(canvas.height-39)*Math.random()],//  
               Math.random()的范围[0.1),0<=Math.random() <1;
               sprite:new Sprite('img/sprites.png',[0,79],[80,39],  
               [0,1,2,3,2,1],6)
           });
            doubleCount=0;
        }
    }

 

因为咱们用的时间函数时间都肯定了, 那咱们就认为的加了一些条件开控制时间;

2.对与敌机的我只改变:

 for(var i=0;i<enemies.length;i++){
    enemies[i].pos[0]-=enemySpeed * dt;
    enemies[i].sprite.update(dt);
    if(enemies[i].pos[0]+enemies[i].sprite.size[0]<0){
        enemies.splice(i,1);
        i--;
    }

}

 

3.对于子弹的运动:

//处理每一个子弹的运动而且判断是否出界,若出界就删除当前的值,数组长度相应减小一个;
for(var i=0;i<bullets.length;i++){
    var bullet = bullets[i];
   //根具子弹的dir判断它应向哪一个方向运动,对应的改变相应的坐标;
    switch(bullet.dir) {
        case 'upward': bullet.pos[1] -= bulletSpeed * dt; break;
        case 'downward': bullet.pos[1] += bulletSpeed * dt; break;
        default://forward
            bullet.pos[0] += bulletSpeed * dt;
    }
     bullet.sprite.update(dt);

    if(bullet.sprite.size[0] + bullet.pos[0] > canvas.width){
        bullets.splice(i,1);
        i --;
    }
}

 

4.对于爆炸的运动改变:

//处理每一个子弹碰撞敌机后的爆炸图片自身的一个动态变化,若变化完以后就删除当前的值,
    数组长度相应减小一个;
     for(var i=0;i<explosions.length;i++){
        explosions[i].sprite.update(dt);
      if(explosions[i].done){
            explosions.splice(i,1);
            i --;
        }
    }

 

 

碰撞判断

对于碰撞事件的判断咱们通常用的是矩形判断。所谓的矩形判断就是把两个相判断的对象装在一个矩形里,用四角的坐标进行比较;具体咱们来看一个例子就显而易见了: 假设有两个矩形,一个在左,一个在右边: var lastTime = Date.now(); var box1Speed = 100; var box2Speed = 200;

var box1 = document.getElementById('box1');
var box2 = document.getElementById('box2');

function main(){
    var now = Date.now();
    var dt = (now - lastTime) / 1000;

    var rectA = getDimensions(box1);
    var rectB = getDimensions(box2);

    var flag = collides(rectA,rectB);

    if(flag){
        alert("撞上了!");
        return;
    }else{
        update(dt);
    }



    lastTime = now;
    setTimeout(main,1000/60);
}

function update(dt){
    box1.style.left = parseInt(box1.style.left) + box1Speed * dt + "px";
    box2.style.left = parseInt(box2.style.left) - box2Speed * dt + "px";
}

function getDimensions(element){

    return {
        x: element.offsetLeft,
        y: element.offsetTop,
        width: element.offsetWidth,
        height: element.offsetHeight
    };
}

function collides(rectA,rectB){
    return !(rectA.x + rectA.width < rectB.x ||
            rectB.x + rectB.width < rectA.x ||
            rectA.y + rectA.height < rectB.y ||
            rectB.y + rectB.height < rectA.y)
}

main();

 

因而可知这个方法可使用在形状比较太匀称的图片上,在这个游戏中咱们也是这样处理的: 先封装判断碰撞的方法:

function collides(x, y, r, b, x2, y2, r2, b2){
    return !(r < x2 || r2 < x || b < y2 || b2< y);
}

function boxCollides(pos,size,pos2,size2){//传入的是两个对象的坐标位置和大小;
    return collides(pos[0], pos[1],
        pos[0] + size[0], pos[1] + size[1],
        pos2[0], pos2[1],
        pos2[0] + size2[0], pos2[1] + size2[1]);
}

 

封装好了再结合循环使用:

function checkCollisions(){
for(var i=0;i<enemies.length;i++){
    var pos = enemies[i].pos;
    var size = enemies[i].sprite.size;

    for(var j=0;j<bullets.length;j++){
        var pos2 = bullets[j].pos;
        var size2 = bullets[j].sprite.size;
        //我方飞机的子弹与敌机相碰撞,而且相撞的敌机立刻删除,数组长度减小一个
        if(boxCollides(pos,size,pos2,size2)){
            enemies.splice(i,1);
            i--;
            scores+=10;

            //我方飞机的子弹与敌机相碰撞后添加爆炸
            explosions.push({
                pos: [pos[0], pos[1]],
                sprite: new Sprite('img/sprites.png',
                    [0, 117],
                    [39,39],
                    [0,1,2,3,4,5,6,7,8,9,10,11,12],
                    16,
                    null,
                    true)
            });

            bullets.splice(j,1);
            j--;

        }
    }

    //我方飞机与敌机相碰撞
    if(boxCollides(pos,size,player.pos,player.sprite.size)){
        gameOver();
    }
}
scoreDiv.innerHTML=scores;
}

 

重画界面

界面上的全部对象都在变化,因此要一直重复的画界面,咱们把每一个对象的画装在sprite里,就是
对象本身画本身:

//重画界面
     function render(){
    if(!isGameOver){
        ctx.clearRect(0,0,canvas.width,canvas.height)
        //画背景
        ctx.fillStyle = terrainPattern;
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        renderEntity(player);
        renderEntities(enemies)
        renderEntities(bullets);
        renderEntities(explosions);
    }else{
        enemies=[];
        bullets=[];
        explosions=[];
        bullets=[];
        return false;
 

}

}

function renderEntities(entities){ for(var i=0;i<entities.length;i++){ renderEntity(entities[i]) } }

function renderEntity(entity){ ctx.save(); ctx.translate(entity.pos[0], entity.pos[1]); entity.sprite.render(ctx); ctx.restore(); }

 

 

游戏主循环

游戏的主循环用来控制游戏流程。首先咱们要得到当前的时间,这样咱们才能计算时间差(自上次循
环以来通过的时间)。而后计算modifier的值并交给update(须要将delta除以1000以将其转换为
毫秒)。最后调用render并更新记录的时间。

游戏主循环是游戏中最重要的概念,不管游戏怎么变化,无非就是移动,消失。而移动消失,无非又
是画布的重画,因此把移动或者消失的位置放在update函数里面,把画布重画放在render函数里
面。而随着时间的变化,无非就是这两个函数函数一直在执行而已。

//游戏主循环

//dt的做用组要是为了经过得到一个灵活的速度(根据每台电脑的当前的计算速度()不一样来得到 
一个灵活的速度)
var lastTime = Date.now();
function main(){
    var now = Date.now();
    var dt = (now - lastTime) / 1000;

    update(dt);//改变对象的位置
    render();//重画界面

    lastTime = now;
    requestAnimaFrame(main);
}

 

在游戏中咱们若是不把随着时间变化的对象都写在一个时间函数里的话,那么就要用不少歌时间函数
(setInterval),这样既浪费资源,代码又不简洁,若是只用一个时间函数的话那咱们就用
requestAnimationFrame,那咱们相比较(setInterval)以为requestAnimationFrame要好些; 咱们来看一个经典的动画函数:

function animate(element, name, from, to, time) {
    time = time || 800; //默认0.8秒
    
    var style = element.style,
    latency = 60, // 每60ms一次变化
    count = time / latency, //变化的次数
    step = Math.round((to - from) / count), //每一步的变化量
    now = from;
    
    function go() {
    count--;
    now = count ? now + step : to;
    style[name] = now + 'px';
    
    if (count) {
    setTimeout(go, latency);
    }
    }
    
    style[name] = from + 'px';
    setTimeout(go, latency);
    }

 

姑且不论这个函数的设计存在局限性,如只能对以px为单位的样式进行修改。仅从函数的实现上看,
这能够是一个很是经典的动画理念,其基本逻辑由如下部分组成:

获取起点值from和终点值to,经过动画须要进行的时间time,以及每侦间隔latency的要求,计算
出值的改变次数count和每次改变的量step。 开启setTimeout(fn, latency);来步进到下一侦。 在下一侦中,设置属性步进一次,若是动画还没结束,再回到第2步继续下一侦。 这个函数工做得很好,服务了千千万万的站点和系统,事实上jQuery的animate函数的核心也无非
是setInterval函数。

可是,随着如今系统复杂度的稳步上升,动画效果也愈来愈多,同时对动画的流畅度也有了更多的重视,这致使上面的函数会出现一些问题。例如同时打开100个动画效果,根据上面的函数,很明显会有100个定时器在同时运行,这些定时器之间的调度会对性能有轻微的影响。虽然在正常的环境中,这些许的影响并不会有什么关系,可是在动画这种对流畅度有很高要求的环境下,任何细微的影响均可能产生出很差的用户体验。在这样的状况下,有一些开发者就发明了一种基于统一帧管理的动画框架,他使用一个定时器触发动

画帧,不一样的动画来注册这些帧,在每一帧上处理多个动画的属性变化。这样的好处是减小了定时器调度的开销,可是对于动画框架的开发者来讲,统一帧管理、提供监听帧的API等,都是须要开发和维护的。浏览器的直接支持 最终,浏览器厂商们发现这件事其实能够由他们来作,而且基于浏览器层面,还能够有更多的优化,

好比:

对于一个侦中对DOM的全部操做,只进行一次Layout和Paint。 若是发生动画的元素被隐藏了,那么就再也不去Paint。 因而,浏览器开始推出一个API,叫作requestAnimationFrame,关于这个函数,MDC的相关页面
有比较详细的介绍,简单来讲,这个函数有2种使用方法:

调用requestAnimationFrame函数,传递一个callback参数,则在下一个动画帧时,会调用
callback。 不传递参数地直接调用该函数,启动动画帧,下一个帧触发时,会同时触发
window.onmozbeforepaint事件,能够经过注册该事件来进行动画。 第2种方法因为依赖于Firefox本身的事件,且beforepaint事件还没进入到标准中,因此不推荐使
用,仍是使用第1种方式比较好。此时,咱们的动画逻辑能够变成这样:

记录当前时间startTime,做为动画开始的时间。 请求下一帧,带上回调函数。 下一帧触发时,回调函数的第一个参数为当前的时间,再与startTime进行比较,肯定时间间隔
ellapseTime。 判断ellapseTime是否已经超过事先设定的动画时间time,若是超过,则结束动画。 计算动画属性变化的差值differ = to - from,再肯定在ellapseTime的时候应该变化多少step
= differ / time * ellapseTime。 计算出如今应该变化到的位置Math.round(from + step),并从新对样式赋值。 继续请求下一帧。

在游戏的主循环中咱们用了一个新的时间函数 requestAnimaFrame(main):

//跨浏览器的RequestAnimationFrame函数(动画接口)
var requestAnimaFrame = (function(){
    return window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||//Google Chromeo
        window.mozRequestAnimationFrame ||//Mozilla Firefox
        window.msRequestAnimationFrame ||//IE
        window.oRequestAnimationFrame ||
        function(callback){
            window.setTimeout(callback, 1000 / 60);
        }
})();

 

requestAnimationFrame是什么? 之前咱们作动画须要一个定时器,每间隔多少毫秒就作出一些改变。如今有个好消息:浏览器厂商已 经决定提供一个专门作动画的方法,即requestAnimationFrame(),并且基于浏览器的层面也能更
好的进行优化。可是呢,这只是一个作动画的基础API,即不基于DOM元素的style变化,也不基于
canvas,或者WebGL。因此,具体的动画细节须要咱们本身写。

在上面咱们用到一个方法巧妙的得到一个比较灵活的速度速度,咱们给定一个每秒的速度,而后得到
时间间隔,根据每一个机子当前的计算速度来决定那个对象的速度是快是慢;咱们看下面例子: 假设有两个矩形,一个在左,一个在右边:

var lastTime = Date.now();
    var box1Speed = 100;
    var box2Speed = 200;

    var box1 = document.getElementById('box1');
    var box2 = document.getElementById('box2');

    function main(){
        var now = Date.now();
        var dt = (now - lastTime) / 1000;

        update(dt);

        lastTime = now;
        setTimeout(main,1000/60);
    }

    function update(dt){

        box1.style.left = parseInt(box1.style.left) + box1Speed * dt + "px";
        box2.style.left = parseInt(box2.style.left) - box2Speed * dt + "px";
    }

    main();

经过上面的终结,咱们写通常的小游戏就有了必定的思路,不至于想到啥就写啥,毫无头绪了!

相关文章
相关标签/搜索