Html游戏总结(套路)

游戏总结(套路)

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

游戏展示

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

游戏的简单布局

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

加载图片

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

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

// 背景图片 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,判断图片是否加载好了就可以
以此为判断依据;

那么对于这个游戏模板来说我们会用到两张图片(背景、所有对象的合成图),对这两张图盘片的操
作如下:

(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();

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

转载于:https://www.cnblogs.com/Alisa-0604/p/4162954.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值