HTML5遊戲開發(十)

HTML5遊戲開發(十)

一、動畫

1、動畫循環

  在Canvas中這病動畫效果很簡單:只需要在播放動畫時持續更新並繪製就行了。這種持續更新與重繪叫就動畫循環。

(1)通過requestAnimationFrame()方法讓瀏覽器來自行決定幀速率:

  使用window.setInterval()或window.setTimeout()製作出的動畫,其效果可能並不如預期般流暢,而且還可能會佔用額外的資源。這是因爲setInterval()與setTimeout方法具有如下特徵:
==它們都是通過法,並不是專爲製作動畫而用的。
即使向其傳遞以毫秒爲單位的參數值,它們也達不到毫秒級的精確性。
沒有對調用動畫循環的機制作優化。
不考慮繪製動畫的最佳時機,而只會一味地以某個大致的時間時隔來調用動畫循環。==

(2)Ployfill

window.requestNextAnimationFrame =
   (function () {
      var originalWebkitRequestAnimationFrame = undefined,
          wrapper = undefined,
          callback = undefined,
          geckoVersion = 0,
          userAgent = navigator.userAgent,
          index = 0,
          self = this;

      //chrome瀏覽器
      if (window.webkitRequestAnimationFrame) {
         wrapper = function (time) {
           if (time === undefined) {
              time = +new Date();
           }
           self.callback(time);
         };
         //原生
         originalWebkitRequestAnimationFrame = window.webkitRequestAnimationFrame;    
         window.webkitRequestAnimationFrame = function (callback, element) {
            self.callback = callback;           
            originalWebkitRequestAnimationFrame(wrapper, element);
         }
      }
     //火狐瀏覽器
      if (window.mozRequestAnimationFrame) {
         index = userAgent.indexOf('rv:');
         if (userAgent.indexOf('Gecko') != -1) {
            geckoVersion = userAgent.substr(index + 3, 3);
            if (geckoVersion === '2.0') {
               //原生
               window.mozRequestAnimationFrame = undefined;
            }
         }
      }

      return window.requestAnimationFrame   ||
         window.webkitRequestAnimationFrame ||
         window.mozRequestAnimationFrame    ||
         window.oRequestAnimationFrame      ||
         window.msRequestAnimationFrame     ||
         //回調函數 
         function (callback, element) {
            var start,
                finish;
            window.setTimeout( function () {
               start = +new Date();
               callback(start);
               finish = +new Date();
               self.timeout = 1000 / 60 - (finish - start);
            }, self.timeout);
         };
      }
   )();

2.幀速率計算

 &ems;動畫是由一系列叫做幀的圖像組成的,這些圖像的顯示頻率就叫做幀速率。通常來說,有必要計算一下幀速率。在基於時間的運動效果是地,可能會用到動畫的幀速率,或是有時爲了保證動畫能夠播放得足夠流暢,我們也需要知道幀速率。 #### (1) 基本功能

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>幀速率計算</title>
        <style>
            body {
                background: #dddddd;
            }

            #canvas {
                position: absolute;
                left: 0px;
                top: 20px;
                margin: 20px;
                background: #ffffff;
                border: thin inset rgba(100, 150, 230, 0.5);
            }

            #controls {
                margin-top: 10px;
                margin-left: 15px;
            }
        </style>
    </head>

    <body>
        <div id='controls'>
            <input id='animateButton' type='button' value='開始' />
        </div>
        <canvas id='canvas' width='500' height='300'>
       </canvas>
        <script src='js/Frame.js'>
        </script>
    </body>

</html>

JS腳本

var canvas = document.querySelector('#canvas'),
    context = canvas.getContext('2d'),
    paused = true,
    //定義三個小球數據
    discs = [{
        x: 150,
        y: 50,
        lastX: 150,
        lastY: 50,
        velocityX: 3.2,
        velocityY: 3.5,
        radius: 25,
        innerColor: 'rgba(0,255,255,0.3)',
        middleColor: 'rgba(0,255,255,0.9)',
        outerColor: 'rgba(0,255,255,0.3)',
        strokeStyle: 'slateblue',
    }, {
        x: 75,
        y: 200,
        lastX: 75,
        lastY: 200,
        velocityX: 2.2,
        velocityY: 2.5,
        radius: 25,
        innerColor: 'rgba(225,225,225,0.1)',
        middleColor: 'rgba(225,225,225,0.9)',
        outerColor: 'rgba(225,225,225,0.3)',
        strokeStyle: 'gray'
    }, {
        x: 100,
        y: 150,
        lastX: 150,
        lastY: 50,
        velocityX: 1.2,
        velocityY: 1.5,
        radius: 25,
        innerColor: 'orange',
        middleColor: 'yellow',
        outerColor: 'gold',
        shadowColor: 'rgba(255,0,0,0.7)',
        strokeStyle: 'orange'
    }, ],
    //小球數量
    numDiscs = discs.length,
    //時間
    lastTime = 0,
    //
    lastFpsUpdateTime = 0,
    frameCount = 0,
    animateButton = document.querySelector('#animateButton');

//擦除背景
function eraseBackground() {
    context.clearRect(0, 0, canvas.width, canvas.height);
}

//更新小球位置,進行座標計算
function update() {
    var i = numDiscs,
        disc = null;
    while(i--) {
        disc = discs[i];
//      座標計算
        if(disc.x + disc.velocityX + disc.radius > context.canvas.width ||
            disc.x + disc.velocityX - disc.radius < 0)
            //橫座標
            disc.velocityX = -disc.velocityX;

        if(disc.y + disc.velocityY + disc.radius > context.canvas.height ||
            disc.y + disc.velocityY - disc.radius < 0)
            //縱座標
            disc.velocityY = -disc.velocityY;
//      默認爲相加 每次移動距離
        disc.x += disc.velocityX;
        disc.y += disc.velocityY;
    }
}
//繪製小球
function drawDisc(disc) {
    var gradient = context.createRadialGradient(disc.x, disc.y, 0,
        disc.x, disc.y, disc.radius);

    gradient.addColorStop(0.3, disc.innerColor);
    gradient.addColorStop(0.7, disc.middleColor);
    gradient.addColorStop(1.0, disc.outerColor);

    context.save();
    context.beginPath();
    context.arc(disc.x, disc.y, disc.radius, 0, Math.PI * 2, false);
    context.clip();

    context.fillStyle = gradient;
    context.strokeStyle = disc.strokeStyle;
    context.lineWidth = 2;
    context.fill();
    context.stroke();

    context.restore();
}
//繪製小球
function draw() {
    var i = numDiscs,
        disc;
    i = numDiscs;
    while(i--) {
        disc = discs[i];
        //小球繪製
        drawDisc(disc);
        disc.lastX = disc.x;
        disc.lastY = disc.y;
    }

    if(frameCount === 100) {
        frameCount = -1;
    }

    if(frameCount !== -1 && frameCount < 100) {
        frameCount++;
    }
}
//計算幀率
function calculateFps() {
    //每秒的播發幀數
    var now = (+new Date),
        fps = 1000 / (now - lastTime);
    lastTime = now;
    return fps;
}
//動畫
function animate() {
    //獲取當前時間
    var now = (+new Date),
        fps = 0;
    //啓動動畫
    if(!paused) {
        //擦除重繪
        eraseBackground();
        //更新
        update();
        //繪製
        draw();
        //計算幀率
        fps = calculateFps();
        //如果當前時間與最後更新時間大於1000
        if(now - lastFpsUpdateTime > 1000) {
            lastFpsUpdateTime = now;
            //最後幀率
            lastFpsUpdate = fps;
        }
        //設置幀率顯示樣式
        context.fillStyle = 'cornflowerblue';
        //四捨五入取值
        context.fillText(lastFpsUpdate.toFixed() + ' fps', 45, 50);
    }
    //chrome可以使用標準的requestAnimationFrame
    window.requestAnimationFrame(animate);
}

//加載就調用
window.requestAnimationFrame(animate);

(2)事件處理

//-------------------------------事件處理
canvas.onclick = function(e) {
    paused = paused ? false : true;
};

animateButton.onclick = function(e) {
    paused = paused ? false : true;
    if(paused) {
        animateButton.value = '開始';
    } else {
        animateButton.value = '停止';
    }
};

context.canvas.width = canvas.width;
context.canvas.height = canvas.height;
//設定字體
context.font = '48px Helvetica';

顯示效果:
image

3.恢復動畫背景

從來質上來看,恢復動畫背景無外乎三種方法:
1、將所有內容擦除,並重繪製;
2、僅繪內容發生變化的區域;
3、從離屏緩衝區中將內容發生變化的那部分背景圖像恢復到屏幕上。

4.利用剪輯區域來重繪背景

//重繪背景
function drawBackground(){
    context.fillStyle="#99CC99";
    context.fillRect(0,0,canvas.width,canvas.height);
}
//重繪小球區域
function drawDiscBackground(disc){
    //保存原來
    context.save();
    context.beginPath();
    context.arc(disc.lastX,disc.lastY,disc.radius+1,0,Math.PI*2,false);
    //剪輯
    context.clip();
    //擦除背景
    eraseBackground();
    //重繪背景
    drawBackground();
    //恢復
    context.restore();
}

//繪製小球
function draw() {
    var i = numDiscs,
        disc;
    i = numDiscs;
    while(i--) {
        drawDiscBackground(discs[i]);
        disc = discs[i];
        //小球繪製
        drawDisc(disc);
        disc.lastX = disc.x;
        disc.lastY = disc.y;
    }

    if(frameCount === 100) {
        frameCount = -1;
    }

    if(frameCount !== -1 && frameCount < 100) {
        frameCount++;
    }
}

5.利用圖塊複製技術重繪背景

將整個背景一次性地繪製到離屏canvas中,稍後從離屏canvas中只將修復動畫背景所需的那一塊圖像複製到屏幕上即可。

//創建離屏對象
var offscreenCanvas = document.createElement('canvas');
offscreenContext = offscreenCanvas.getContext('2d');
//離屏的大小設定
offscreenCanvas.width = canvas.width;
offscreenCanvas.height = canvas.height;
//將原畫布複製給離屏
function drawBackground() {
    //將當前畫布給離屏
    offscreenContext.drawImage(canvas, 0, 0,
                              canvas.width, canvas.height);
}
//重繪小球區域
function drawDiscBackground(context, disc) {
    var x = disc.lastX,
        y = disc.lastY,
        r = disc.radius,
        w = r * 2,
        h = r * 2;
    //保存原來
    context.save();
    context.beginPath();
    context.arc(x, y, r + 1, 0, Math.PI * 2, false);
    //剪輯
    context.clip();
    //擦除背景
    eraseBackground();
    //重繪背景--使用離屏技術
    context.drawImage(offscreenCanvas,x-r,y-r,w,h,x-r,y-r,w,h);
    //恢復
    context.restore();
}

默認情況下:瀏覽器使用了雙緩衝技術繪製動畫

6.基於時間的運動

例:每秒移動100個像素,那麼每毫秒移動的像素爲:100/1000=0.1px,計算每一幀移動多少像素:假設這一幀對於上一幀過去的時間爲200ms,那麼這一幀移動的像素即爲:200ms*0.1px=20px

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>幀速率計算</title>
        <style>
            body {
                background: #dddddd;
            }

            #canvas {
                position: absolute;
                left: 0px;
                top: 20px;
                margin: 20px;
                background: #ffffff;
                border: thin inset rgba(100, 150, 230, 0.5);
            }

            #controls {
                margin-top: 10px;
                margin-left: 15px;
            }
        </style>
    </head>

    <body>
        <div id='controls'>
            <input id='animateButton' type='button' value='開始' />
        </div>
        <canvas id='canvas' width='500' height='300'>
       </canvas>
        <script src='js/TimeFrame.js'>
        </script>
    </body>

</html>

JS腳本

var canvas = document.querySelector('#canvas'),
    context = canvas.getContext('2d'),
    paused = true,
    discs = [{
            x: 215,
            y: 175,
            lastX: 150,
            lastY: 75,
            velocityX: -3.2,
            velocityY: 4.5,
            radius: 25,
            innerColor: 'rgba(255,0,0,1.0)',
            middleColor: 'rgba(255,0,0,0.7)',
            outerColor: 'rgba(255,0,0,0.5)',
            shadowColor: 'rgba(255,0,0,0.7)',
            strokeStyle: 'orange'
        }, {
            x: 250,
            y: 150,
            lastX: 150,
            lastY: 80,
            velocityX: 2.2,
            velocityY: -4.5,
            radius: 25,
            innerColor: 'rgba(255,255,0,1)',
            middleColor: 'rgba(255,255,0,0.7)',
            outerColor: 'rgba(255,255,0,0.5)',
            shadowColor: 'rgba(175,175,175,0.7)',
            strokeStyle: 'gray',
        },

        {
            x: 150,
            y: 75,
            lastX: 50,
            lastY: 150,
            velocityX: 2.2,
            velocityY: -1.5,
            radius: 25,
            innerColor: 'rgba(100,145,230,1.0)',
            middleColor: 'rgba(100,145,230,0.7)',
            outerColor: 'rgba(100,145,230,0.5)',
            shadowColor: 'rgba(100,145,230,0.8)',
            strokeStyle: 'blue'
        },

        {
            x: 100,
            y: 100,
            lastX: 150,
            lastY: 75,
            velocityX: -5.9,//X軸速度
            velocityY: -0.2,//Y軸速度
            radius: 25,
            innerColor: 'rgba(255,0,0,1.0)',
            middleColor: 'rgba(255,0,0,0.7)',
            outerColor: 'rgba(255,0,0,0.5)',
            shadowColor: 'rgba(255,0,0,0.7)',
            strokeStyle: 'orange'
        },
    ],
    numDiscs = discs.length,
    startTime = 0, //開始時間
    lastTime = 0, //結束時間
    elapsedTime = 0, //運行時間,每幀時間差
    fps = 0, //幀率
    lastFpsUpdate = {
        time: 0,
        value: 0
    },
    animateButton = document.querySelector('#animateButton');
//擦除背影
function eraseBackground() {
    context.clearRect(0, 0, canvas.width, canvas.height);
}
//更新小球
function update() {
    var i = numDiscs,
        disc = null;

    while(i--) {
        disc = discs[i];

        if(disc.x + disc.velocityX + disc.radius > context.canvas.width ||
            disc.x + disc.velocityX - disc.radius < 0)
            disc.velocityX = -disc.velocityX;

        if(disc.y + disc.velocityY + disc.radius > context.canvas.height ||
            disc.y + disc.velocityY - disc.radius < 0)
            disc.velocityY = -disc.velocityY;

        disc.x += disc.velocityX;
        disc.y += disc.velocityY;
    }
}
//基於時間更新
function updateTimeBased(time) {
    var i = numDiscs,
        disc = null;

    if(fps == 0)
        return;

    while(i--) {
        disc = discs[i];
        deltaX = disc.velocityX
        //elapsedTime爲每幀時間差,速度(像素)X每幀秒數,移動座標像素
        deltaX = disc.velocityX * (elapsedTime / 1000);
        deltaY = disc.velocityY * (elapsedTime / 1000);

        if(disc.x + deltaX + disc.radius > context.canvas.width ||
            disc.x + deltaX - disc.radius < 0) {
            disc.velocityX = -disc.velocityX;
            deltaX = -deltaX;
        }

        if(disc.y + deltaY + disc.radius > context.canvas.height ||
            disc.y + deltaY - disc.radius < 0) {
            disc.velocityY = -disc.velocityY;
            deltaY = -deltaY;
        }

        disc.x = disc.x + deltaX;
        disc.y = disc.y + deltaY;
    }
}
//繪製小球
function draw() {
    var i = numDiscs,
        disc = discs[i];

    while(i--) {
        disc = discs[i];
        //
        gradient = context.createRadialGradient(disc.x, disc.y, 0,
            disc.x, disc.y, disc.radius);

        gradient.addColorStop(0.3, disc.innerColor);
        gradient.addColorStop(0.5, disc.middleColor);
        gradient.addColorStop(1.0, disc.outerColor);

        context.beginPath();
        context.arc(disc.x, disc.y, disc.radius, 0, Math.PI * 2, false);

        context.save();

        context.fillStyle = gradient;
        context.strokeStyle = disc.strokeStyle;
        context.fill();
        context.stroke();
        context.restore();

    }
}
//計算幀率
function calculateFps(now) {
    //第一幀所用時間單位爲毫秒
    elapsedTime = now - lastTime;
    //計算幀率,每一秒多少幀
    fps = 1000 / elapsedTime;
    lastTime = now;
}
//更新幀率
function updateFps() {
    var now = (+new Date);
    //計算幀率
    calculateFps(now);

    if(now - startTime < 2000) {
        return;
    }

    if(now - lastFpsUpdate.time > 1000) {
        lastFpsUpdate.time = now;
        lastFpsUpdate.value = fps;
    }
    if(!paused) {
        context.fillStyle = 'cornflowerblue';
        context.fillText(lastFpsUpdate.value.toFixed() + ' fps', 50, 48);
    }
}
//基於時間運行動畫
function animate(time) {
    if(time === undefined) {
        time = +new Date;
    }
    //console.log(time)
    if(!paused) {
        //擦除重繪背景
        eraseBackground();
        //drawBackground();
        //基於時間
        updateTimeBased(time);
        //非基於時間
        //update();
        draw();
    }
    //更新幀率
    updateFps();
}
//按鈕事件
animateButton.addEventListener('click', function(e) {
    paused = paused ? false : true;
    if(paused) {
        animateButton.value = '開始';
    } else {
        for(var i = 0; i < discs.length; ++i) {
            discs[i].velocityX *= 20;
            discs[i].velocityY *= 50;
        }
        animateButton.value = '停止';
    }
});

context.font = '36px Helvetica';
//獲取開始是境
startTime = +new Date;
//計算每一幀所用時長爲多少毫秒,以下爲每秒60幀動畫
setInterval(animate, 1000 / 60);

6.視差動畫

視差動畫,就是動畫製作者讓各個動畫圖層以不同的速度滾動,這樣就實現了視差效果。近的移動快,遠的移動慢。

(1)初始設置

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>視差動畫</title>
    <style> 
           body {
                background: #dddddd;
            }
            #canvas {
            position: absolute;
            left: 20px;
            top: 30px;
            background: #ffffff;
            margin-left: 10px;
            margin-top: 10px;
            box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
            }
            margin-left: 15px;
            }

        </style>
   </head>

  <body>
    <input id='animateButton' type='button' value='動畫'/>
    <canvas id='canvas' width='1000' height='440'>
    </canvas>
    <script src='js/requestNextAnimationFrame.js'></script>
    <script src='js/Parallax.js'></script>
  </body>
</html>

JS腳本

var canvas = document.getElementById('canvas'),
    context = canvas.getContext('2d'),
    animateButton = document.getElementById('animateButton'),
    //樹
    tree = new Image(),
    //大數
    nearTree = new Image(),
    //草
    grass = new Image(),
    grass2 = new Image(),
    //天空
    sky = new Image(),

    paused = true,
    lastTime = 0,
    lastFpsUpdate = { time: 0, value: 0 },
    fps=60,
    //天空偏移
    skyOffset = 0,
    //草地偏移
    grassOffset = 0,
    //樹偏移
    treeOffset = 0,
    //大樹偏移
    nearTreeOffset = 0,
    //速度
    TREE_VELOCITY = 20,
    FAST_TREE_VELOCITY = 40,
    SKY_VELOCITY = 8,
    GRASS_VELOCITY = 75;

//----------------------初始設置
context.font = '48px Helvetica';

tree.src = 'img/smalltree.png';
nearTree.src = 'img/tree-twotrunks.png';
grass.src = 'img/grass.png';
grass2.src = 'img/grass2.png';
sky.src = 'img/sky.png';


 //擦除背景   
function erase() {
   context.clearRect(0,0,canvas.width,canvas.height);
}
//繪製圖像
function draw() {
   //天空
   skyOffset = skyOffset < canvas.width ?
               skyOffset + SKY_VELOCITY/fps : 0;
   //草地
   grassOffset = grassOffset < canvas.width ?
                 grassOffset +  GRASS_VELOCITY/fps : 0;
    //樹
   treeOffset = treeOffset < canvas.width ?
                treeOffset + TREE_VELOCITY/fps : 0;
   //大樹
   nearTreeOffset = nearTreeOffset < canvas.width ?
                    nearTreeOffset + FAST_TREE_VELOCITY/fps : 0;
    //保存畫布 
   context.save();
   //平移
   context.translate(-skyOffset, 0);
   //繪製兩個天空,實現背景滾動
   context.drawImage(sky, 0, 0);
   context.drawImage(sky, sky.width-2, 0);
   //恢復畫布
   context.restore();

    //保存畫布 
   context.save();
   context.translate(-treeOffset, 0);
   context.drawImage(tree, 100, 240);
   context.drawImage(tree, 1100, 240);
   context.drawImage(tree, 400, 240);
   context.drawImage(tree, 1400, 240);
   context.drawImage(tree, 700, 240);
   context.drawImage(tree, 1700, 240);
   //恢復畫布
   context.restore();

   context.save();
   context.translate(-nearTreeOffset, 0);
   context.drawImage(nearTree, 250, 220);
   context.drawImage(nearTree, 1250, 220);
   context.drawImage(nearTree, 800, 220);
   context.drawImage(nearTree, 1800, 220);
   context.restore();

   context.save();
   context.translate(-grassOffset, 0);
   context.drawImage(grass, 0, canvas.height-grass.height);
   context.drawImage(grass, grass.width-5,
                     canvas.height-grass.height);
   context.drawImage(grass2, 0, canvas.height-grass2.height);
   context.drawImage(grass2, grass2.width,
                     canvas.height-grass2.height);
   context.restore();

}

(2)動畫實現

//--------------------幀率
//幀率
function calculateFps(now) {
   var fps = 1000 / (now - lastTime);
   lastTime = now;
   return fps; 
}
//動畫
function animate(now) {
   if (now === undefined) {
      now = +new Date;
   }
   fps = calculateFps(now);
   if (!paused) {
      erase();
       draw();
   }
   requestNextAnimationFrame(animate);
}

animateButton.onclick = function (e) {
   paused = paused ? false : true;
   if (paused) {
      animateButton.value = '動畫';
   }
   else {
      animateButton.value = '暫停';
   }
};


//加載繪製
sky.onload = function (e) {
   draw();
};
//動畫加載
requestNextAnimationFrame(animate);

顯示效果:
image

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章