`
mycream
  • 浏览: 54213 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

favicon 小游戏探索

 
阅读更多

老早之前在 cnBeta 上看到一款用 favicon 做成的游戏(我是传送门)。16*16的显示窗里放下一个飞机游戏,创意不错。游戏地址:http://www.p01.org/defender_of_the_favicon/

今天抽时间,把游戏弄了出来,主要是使用 canvas 画板绘制游戏,再复制到 favicon 中。原理很是简单。下面把简化版放上,有兴趣的童鞋可以自行研究下。(相对完整的版本见后面的附件)

是了,遇到canvas加载问题,那就请开 http-server。因为画板在本地运行有个图片绘制权限问题。

<!doctype html>
<html>
<head>
  <meta charset="utf-8" />
  <title>DEFENDER OF THE FAVICON</title>
  <link rel="shortcut icon" type="image/ico" href="favicon.ico"></link>
</head>
<body>
  <!-- 游戏素材 -->
  <img id="title" crossorigin="anonymous" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADcAAAAQCAYAAACycufIAAABe0lEQVRIS91W0RLDIAjT//9odzjwYgSLbntZX3q1FQhJqLXA1Vpp+FxrqfLM67Im727XLS7G9uLd5jcMvXi7bovlhmTicOGfNMsjpBPgApNV5XBKCq2oBZg7XdekGHvJc5ofNGeNW8EBsA59EqpV1btyLUvs9JAQyjxqLm8M6vsIXCShGy964EJGM+Ck66rIZWBspQLETb46laXT8S0gVlNUB4CXeOE0VPjP0ptGUiBjnq62h3ziDaJe70Z+TYxjceg7t/hdwEV6IIPot8F7OkvmZm9wkPx2NpiAL+D8kfEeGIl3pu8xdQ/2YPxsPsd2o06OkQLwFHD6Vx6A60zr9z8B543lf1njcfDupPhAvWQmxyMTe8s8wXfz2sSs2n/4jnK5sbSmaI/NCLbICq6V5iXw2BTgeIzK7MvssW/4bk2NmskEpMB5DGCiiMlH5oJGfhWcSTHbkTEZv8DcdEJxLIE1ZZjrNlJLpTzHdGfBRcxFUvbUgD7L+BTt8wK4FusW3zOi6QAAAABJRU5ErkJggg==" />
  <img id="city" crossorigin="anonymous" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARAAAAAgCAYAAADaBycMAAAEx0lEQVR4Xu1czWoUQRCuRoMEJBAw7iTmJjkEES8iJNdoEESY5AGi4gPkmlwEQTTXvIIPkPgUySKoIOol4E0yC4EFrx4ic8hud+9sV1dXz+78VG6dqvrqq6+qK62HUQBwCRF++v0+zM/Pj0XC7BEoOCE4+TmxZdfFwW9qXVeaNKG+qtegYi0QziBXNRZr3tnZGaysrLDo+2BgPFgEIgbbtbhq86k7IrVgqEnynEQufZZi5JMFEjxafoEnJyewvr7u52x5cWKDEgJAjKEKzR0aF0OnOtYdqlfMuIkukBiNjlk8F+v4+Bi2trYGMPZZx3fZuDxc8eM0t/k0rTdFmrShxnGzUFbtrAUyrUtBvXCxeMbCofJvu7/oXs0JyPviXCCHh4ewu7tLZh8a50pUBqaer2x8u7Yy88XA9sHw8SEPTwsCqLrZ/tT40Dn3ycN6gcTo9d7eHhwcHAyg7DMlByWW4qtzCI1z1VEGJkW3KvliWmB2n1p8MHx8fHI13WfqC6TpAlPra9Lgpjv78OnjB6oEhn+T9GAJUdHgwgWyvb0NR0dHFaVcHq1p1x0rfyyc8pQuRq4r73E6cerhxHL7RsntfIGsra3B6ekpl493vJ5v0rm9SU7Jsel6UOuz/anxehspsRTf0By+IxbKJSa+/BPGV03xm5oCZV8UrLBp58f4FdmTJIEsy0JCSTEjCwRLjNl9svtg+Pj45BIfugKT1H4SufQck8hHV7w6EZg+tr1aL5CZOYB/f0fVtH8/zq86feAzkRr5GsZCoPSC4huLny9OCdyqtUB8hRA/UUAUqIQCskAq0QYhIQrUUwFZIPXsm7AWBSqhgCyQSrRBSIgC9VRAgboe5YNC9SxfWIsCogBHAQXXZmWBcBSUWFGgxQoomJmTBdLiAZDSRQGOAgpuLMgC4SgosaJAixVQMLssC6TFAyCliwIcBRTcvCsLhKOgxIoCLVZAwdw9WSAtHgApXRTgKKDSnf3BAsl6F9D9/JWDJ7GigChQIwXS55sDtiH3X6Uv3lxm2fkApPvlV43KF6qigCjAUSB9tgGc+z/6Avn2m8NHYkUBUaBGCqRPH5kvEOL9H3mBJJ1bBqB97n7/45QnffLAJIT4Y1rbeFQ+GD7XzuVHjcf8qf2L3S+untR44U9VzPRPNx8aLxDq/Kj05dvh/4FkGSSdBRMwWTSfOD8uDAbp4/vDhZGdQxLgfwWQP6W6Bfj6E4uKX4TnykdtR16/i1/OV89XdKbWh+Xj4nH0KZoHrH5KPmzebH3t/tv9xfhi8RgeFl80Py69MDxsfkfqXVzW5pN+/1X66t2l/umzfIEMC8gg/wKRfu7+7JsLZGPV+HSa7V90NvJZn14rwnfx8cG34+38rvpGBmRj1amPDx9Xfh+9qXpQ/bn8XP21v2hln+3+F+mP8dPtIXjUeNd8+eTH5pMyr6k1n+h9Xbxj3l/i/Vfp6/faC6QHye38BTL8lmJRw7GB5MZTBaUMlM8F59aH8aHWR/XH9Mfs1HyYXmXbqXrH5sPNT43H/DG7Uf/SkvYHkX7/RxdI0jEBC85Z1hv4JEkH7POQYA9yu33G4ql4GD7XHpsPFY/qj+mP2an5qP2M7Y/1N3Y+TB9qPip/zB+zY/wp8/EfaBbMJmzqBoAAAAAASUVORK5CYII=" />
  <img id="player" crossorigin="anonymous" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAgCAYAAAAxOQljAAAAdklEQVQoU7WS0Q2AIAxErzMYHUx30DFkDd3BxQzOcKaQECUIfMiFn/YRXqAIPiLaJ0nZASziak0ArnjAsCM+MQ+SDtuP7M7DCxs5uIIwbOsgCF3/3CO87EZyBkQTN53rBaKBVA6q5LiGyX+GVCpByQEj7R3IOG4RCUntPt+ZagAAAABJRU5ErkJggg==" />
  <img id="digits" crossorigin="anonymous" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAUCAYAAADPym6aAAABIUlEQVRIS+1Wiw7CMAgsf7Z92bYv2/4MU5XmJAzwkaYaTRbtepTj4OaIS+GiPlQK4f26rhCN1Tg8RmKiOA+neXnrRhhJaeKYDIlFZCWxCBCRjs7zhEwXkumc7ogV43U/KtSNRbW0ct6ePtQjnVU6i7NG7MELAvBmP5rbZ1XN+irC3UzMzERE8r0sC6/rWuq1bVu7j7gaV2Oiwnrtm4Xows7WvUhm8rxcyLAdaf64jxiuz0Yto1QvTOuIeAK9IiOFPpLfQ3ZEyM3zXI7jIOkAqinGn6aJ930fyuhXUZGsZWpRXu/1GplsHvPxaT1mccyyh/fEmR1BH6DptVd6Eo1ytUKq4uIRy9zy5/cVHomqHnl/mFeMd0X6F/Kugp+O/5mOXADYeR4aGRLmGgAAAABJRU5ErkJggg==" />
  <img id="sprites" crossorigin="anonymous" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAj0lEQVQ4T81TQQ6AIAxrPytvws/WbAgiIiHBgwsJZLBR2kIAkCSbSdLmHG/5+owXLDXwYhKMgrYLheW5Awq0Gx7oMoob5Bra7Lo8wW5DaDgAEjc++pE2YiKx22AA/2w+C3aEYKHHdzKaVLWZijdMykreFuw3Mi5QAAoSzYpNCNBI/+JEt2zzifL/6OX/x8EBupROEXY6Qx8AAAAASUVORK5CYII=" />
  <!-- 画板,通过画板绘制游戏,再复制到 favicon 中 -->
  <canvas id="render" width="16" height="16"></canvas>
  <br/>
  <h3>操作</h3>
  <code>N</code>:开始/射击<br/>
  <code>WASD</code> 或方向键: 方向控制
  <script>
  // 重定义 getElementById 方法,为方便获取页面元素
  function $(id) {
    return document.getElementById(id)
  }
  Function.prototype.bind || (Function.prototype.bind = function(what) {
    var _method = this;
    return function(){ return _method.apply(what, arguments) };
  });
  var game = new (function() {
    var x = 0,
      y = 8,
      shield = 16,
      score = 0,
      wave = 0,
      goLeft = true,
      speed = 0,
      state = '',
      stateBefore = '',
      update = {},
      now = 0,
      then = 0,
      keyDown ={},
      keyUp ={},
      keyMapping =
      {
        38: 'up',
        87: 'up',
        40: 'down',
        83: 'down',
        37: 'left',
        65: 'left',
        39: 'right',
        68: 'right',
        68: 'right',
        78: 'fire',
        13: 'select',
        0:  '** dummy **'
      },
      fireDelay = 0,
      fireRate = 3,
      enemies = [],
      humanoids = [],
      warmupDuration = 1500,
      frame = 0,
      ctx = null,
      useIcon = true,
      stupidBrowser = false;
    /*
     *  initialize
     */
    window.addEventListener
    (
      'load',
      (function(/*onload*/) {
        // give favicon an id
        var icons = document.getElementsByTagName('link');
        for (var i = 0; i < icons.length; i++) {
          var icon = icons[i];
          if (icon.getAttribute('rel') == 'shortcut icon') {
            icon.setAttribute('id', 'favicon');
            i = icons.length;
          }
        }
        // keyboard
        for (var i in keyMapping) {
          keyDown[keyMapping[i]] = keyUp[keyMapping[i]] = 0;
        }
        onkeyup = onkeydown = handleKeyEvents.bind(this);
        //  blur/focus = pause/resume
        onblur = onfocus = (function(event){
          var event = event || window.event;
          if (state == 'play'|| state == 'pause')
            setState(event.type.toLowerCase() == 'blur' ? 'pause' : stateBefore);
        }).bind(this);
        //  get rendering context
        ctx = $('render').getContext('2d');
        //  is this a stupidBrowser that do not support toDataURL ?
        if ('function' != typeof(ctx.canvas.toDataURL)) {
          stupidBrowser = true;
          ctx.canvas.className = 'stupidBrowser';
          toggleIconUse();
        }
        //  start with the 'title' state
        setState('title');
        // there we go
        main();
      }).bind(this),
      false
   );
    /*
     *  handleKeyEvents
     */
    function handleKeyEvents(event) {
      var event = event || window.event,
        type = event.type.toLowerCase(),
        keyCode = keyMapping[event.keyCode] || 'unknown';
      keyDown[keyCode] = type == 'keydown' ? 1 : 0;
      keyUp[keyCode] = type == 'keyup' ? 1 : 0;
      if (keyCode != 'unknown' && state == 'play' && event.preventDefault) {
        event.preventDefault();
      }
    }
    /*
     *  toggleIconUse
     */
    function toggleIconUse() {
      useIcon =! useIcon;
      ctx.canvas.setAttribute('style', 'visibility:' + (useIcon ? 'hidden' : 'visible'));
      if (!useIcon) {
        document.getElementById('favicon').setAttribute('href', 'favicon.png');
      }
    }
    /*
     *  setState
     */
    function setState(newState) {
      if ('function' !== typeof(update[newState])) {
        throw new Error('state "' + newState + '"not supported');
      }
      then = new Date().getTime();
      stateBefore = state;
      state = newState;
    }
    /*
     *  initializeWave
     */
    function initializeWave(newWave) {
      wave = newWave || 1;
      if (wave == 1) {
        score = 0;
      }
      //  reset player
      x = 0;
      y = 8;
      shield = 16;
      //  enemies
      enemies = [];
      var len = wave + 10;
      for (var i = 0; i < len; i++) {
        enemies[i] =
        {
          x: Math.random()*255&255,
          y: -8,
          type: (i < 10 ? 0 : (1 + (i & 1))),  // 10 Landers then it's half Baiter half Bomber
          isAlive: true
        };
      }
      //  humanoids
      for (var i = 0; i < 10; i++) {
        humanoids[i] =
        {
          x: Math.random() * 255 & 255,
          y: Math.random() * 13 & 15,
          isGrabbed: false
        };
      }
      //  play!
      setState('play');
    }
    /*
     *  update.title
     */
    update.title = function() {
      ctx.drawImage($('title'), 0,0, 55,16, Math.round(39*Math.cos((now/1337)%6.283)-16),0, 55,16);
      if (keyUp.fire && now > 500) {
        initializeWave(1);
      }
    }
    /*
     *  gameover
     */
    update.gameover = function() {
      // ...
      if (now < warmupDuration) {
        now = warmupDuration - now;
        update.play();
        ctx.globalCompositeOperation = 'lighter';
        ctx.fillStyle = '#f00';
        ctx.globalAlpha = now/warmupDuration;
        ctx.fillRect(0,0,16,16);
        ctx.globalCompositeOperation = 'source-over';
        ctx.globalAlpha = 1;
        now = warmupDuration - now;
      }
      else
      {
        var yOfs = -Math.round(4 * Math.sin(((now - warmupDuration) / 241) % 6.283));
        ctx.drawImage($('digits'), 0, (state == 'gameover' ? 10 : 5), 16, 5, 0, yOfs + 2, 16, 5);
        ctx.globalAlpha = now & 256 ? 1 : .5;
        ctx.drawImage($('digits'), Math.floor(wave / 10 % 10) * 5, 0, 5, 5, 2, yOfs + 9, 5, 5);
        ctx.drawImage($('digits'), Math.floor(wave % 10) * 5, 0, 5, 5, 9, yOfs + 9, 5, 5);
        ctx.globalAlpha = 1;
      }
      if (keyUp.fire && now > 2000) {
        setState('title');
      }
    }
    /*
     *  update.pause
     */
    update.pause = function() {
      ctx.drawImage($('digits'), 0,5,16,5, 0, 2,16,5);
      ctx.globalAlpha= now&256?1:.5;
      ctx.drawImage($('digits'), Math.floor(wave/10%10)*5,0,5,5, 2, 9,5,5);
      ctx.drawImage($('digits'), Math.floor(wave%10)*5,0,5,5, 9, 9,5,5);
      ctx.globalAlpha= 1;
    }
    /*
     *  update.play
     */
    update.play = function() {
      var isStarting = now < warmupDuration;
      var isTouched = false;
      var yOfs = isStarting?Math.round((stateBefore=='play'?48:32)*Math.cos(Math.PI/2*now/warmupDuration)):0;
      if (!isStarting && shield) {
        // update player's position
        speed = Math.max(-3, Math.min(3, speed-(!speed?0:speed/Math.abs(speed)/4)+keyDown.left-keyDown.right));
        x = (x+256-Math.round(speed))&255;
        goLeft = speed?speed>0:goLeft;
        y = Math.max(0, Math.min(14, y+keyDown.down-keyDown.up));
      }
      var xxPlayer = Math.round(5+speed*4/3);
      //  draw city
      ctx.drawImage($('city'), -((x+16384)&255), yOfs-16);
      //  start transition
      if (isStarting) {
        ctx.drawImage($('digits'), 0,(state=='gameover'?10:5),16,5, 0, yOfs-(state=='gameover'?48:32)+2,16,5);
        ctx.globalAlpha= now&256?1:.5;
        ctx.drawImage($('digits'), Math.floor(wave/10%10)*5,0,5,5, 2, yOfs-(state=='gameover'?48:32)+9,5,5);
        ctx.drawImage($('digits'), Math.floor(wave%10)*5,0,5,5, 9, yOfs-(state=='gameover'?48:32)+9,5,5);
        ctx.globalAlpha= 1;
      }
      //  playing
      else
      {
        //  fire
        if (fireDelay) {
          fireDelay--;
        }
        else if (keyUp.fire && shield) {
          fireDelay = fireRate;
          var fireX = ([0,Math.round(5+speed+3),16]).splice(goLeft?0:1,2);
          ctx.fillStyle = (['#ff0','#0f0','#0ff'])[frame%3];
          ctx.fillRect(fireX[0], y+1, fireX[1]-fireX[0], 1 );
        }
        //  enemies
        var enemiesAlive = 0;
        for (var i = 0, enemy; enemy = enemies[i]; i++) {
          if (!enemy.isAlive) {
            continue;
          }
          enemiesAlive++;
          var iCanHasThrust = Math.random()*4&3;
          //  y
          if (iCanHasThrust== 0) {
            var s = y-enemy.y+Math.random()*2-1
            //  landers ?
            if (!enemy.type) {
              if (humanoids[i].isGrabbed) {
                //  abduct humanoid
                s = -1;
              }
              else
              {
                s = enemy.x== humanoids[i].x?1:Math.random()-.5;
              }
            }
            if ((s<0 && enemy.y>(enemy.type?0:-5)) || (s>0 && enemy.y<(enemy.type?13:11))) {
              enemy.y += s/Math.abs(s);
              if (!enemy.type) {
                if (enemy.y== 11 && enemy.x== humanoids[i].x && humanoids[i].y== 14) {
                  humanoids[i].isGrabbed = true;
                }
                else if (enemy.y==-5 && humanoids[i].isGrabbed) {
                  humanoids[i].y = 16;
                  humanoids[i].isGrabbed = false;
                }
              }
            }
          }
          //  x
          else if (iCanHasThrust== 2) {
            var s = (enemy.type?x+Math.random()-.5:humanoids[i].x+Math.random()-.5)-enemy.x;
            if (s) {
              enemy.x = (enemy.x+256+s/Math.abs(s))&255;
            }
          }
          //  lander grabbing a humanoid ?
          if (!enemy.type && humanoids[i].isGrabbed) {
            humanoids[i].x = enemy.x;
            humanoids[i].y = enemy.y+3;
          }
          //  draw enemy
          var xx = (Math.round(256+16+enemy.x-x)&255)-8;
          ctx.drawImage($('sprites'), 6*((i+frame)&1),enemy.type*4,4,3, xx-2, Math.round(enemy.y)+yOfs,4,3 );
          //  aligned with the player ?
          if (shield && Math.abs(enemy.y-y)<(enemy.type?3:2)) {
            //  shoot ?
            if (fireDelay== fireRate && xx-2<fireX[1] && xx+2>fireX[0]) {
              enemy.isAlive = false;
              if (!enemy.type && humanoids[i].isGrabbed) {
                humanoids[i].isGrabbed = false;
              }
            }
            //  crash on player ?
            else if (xx-2+(enemy.type&1)<xxPlayer+5 && xx+2-(enemy.type&1)>xxPlayer) {
              isTouched = true;
            }
          }
        }
        var lostHumanoids = 0;
        //  humanoids' "logic"
        for (var i = 0, humanoid; humanoid = humanoids[i]; i++) {
          //  abducted ?
          if (humanoid.y== 16) {
            humanoid.x = Math.random()*255&255; // toss the lander around
            lostHumanoids++;
          }
          //  free fall
          else if (!humanoid.isGrabbed && humanoid.y<14) {
            humanoid.y++;
          }
        }
      }
      //  draw humanoids
      for (var i = 0, humanoid; humanoid = humanoids[i]; i++) {
        var xx = (Math.round(256+16+humanoid.x-x)&255)-8;
        ctx.drawImage($('sprites'), 6*((i+frame)&1),12,1,2, xx-(humanoid.isGrabbed?(frame+i)&1:0), Math.round(humanoid.y)+yOfs,1,2 );
      }
      //  draw player
      if (shield) {
        ctx.drawImage($('player'), 0,(goLeft?16:0)+Math.round(Math.abs(speed))*4, 6,4, xxPlayer,y-1+yOfs, 6,4);
        if (isTouched) {
          ctx.globalCompositeOperation = 'lighter';
          ctx.fillStyle = '#f00';
          ctx.fillRect(0,0,16,16);
          ctx.globalCompositeOperation = 'source-over';
          shield--;
          if (lostHumanoids== 10 || !shield) {
            setState('gameover');
          }
        }
        //  no more enemies ? -> next wave
        if (!isStarting && !enemiesAlive) {
          initializeWave(wave+1);
        }
      }
    }
    /*
     *  main
     */
    function main() {
      now = new Date().getTime()-then;
      //  toggleIconUse ?
      if (!stupidBrowser && keyUp.select) {
        toggleIconUse();
      }
      //  clear
      ctx.globalCompositeOperation = 'source-over';
      ctx.fillStyle = '#000';
      ctx.fillRect(0,0,16,16);
      // actual update
      if (update[state]) {
        update[state]();
      }
      // set favicon
      if (!stupidBrowser && useIcon) {
        var icon=$('favicon');
        (newIcon = icon.cloneNode(true)).setAttribute('href',ctx.canvas.toDataURL());
        icon.parentNode.replaceChild(newIcon,icon);
      }
      //  reset keyUp
      for (var i in keyMapping) {
        keyUp[keyMapping[i]]= 0;
      }
      //  update
      frame++;
      setTimeout(main.bind(this), 50);
    }
  })();
  </script>
</body>
</html>
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics