topical media & game development

talk show tell print

mobile-game-present-lib-quintus-input.js / js



  /*global Quintus:false */
  
  Quintus.Input = function(Q) {
    var KEY_NAMES = { LEFT: 37, RIGHT: 39, SPACE: 32,
                      UP: 38, DOWN: 40,
                      Z: 90, X: 88   
                    };
    
    var DEFAULT_KEYS = { LEFT: 'left', RIGHT: 'right',
                         UP: 'up',     DOWN: 'down',
                         SPACE: 'fire',
                         Z: 'fire',
                         X: 'action' };
  
    var DEFAULT_TOUCH_CONTROLS  = [ ['left','<' ],
                              ['right','>' ],
                              [],
                              ['action','b'],
                              ['fire', 'a' ]];
  
    // Clockwise from midnight (a la CSS)
    var DEFAULT_JOYPAD_INPUTS =  [ 'up','right','down','left'];
  
    Q.inputs = {};
    Q.joypad = {};
  
    var hasTouch =  !!('ontouchstart' in window);
  
    Q.InputSystem = Q.Evented.extend({
      keys: {},
      keypad: {},
      keyboardEnabled: false,
      touchEnabled: false,
      joypadEnabled: false,
  
      bindKey: function(key,name) {
        Q.input.keys[KEY_NAMES[key] || key] = name;
      },
  
      keyboardControls: function(keys) {
        keys = keys || DEFAULT_KEYS;
        Q._each(keys,function(name,key) {
         this.bindKey(key,name);
        },Q.input);
        this.enableKeyboard();
      },
  
      enableKeyboard: function() {
        if(this.keyboardEnabled) { return false; }
  
        // Make selectable and remove an :focus outline
        Q.el.tabIndex = 0;
        Q.el.style.outline = 0;
  
        Q.el.addEventListener("keydown",function(e) {
          if(Q.input.keys[e.keyCode]) {
            var actionName = Q.input.keys[e.keyCode];
            Q.inputs[actionName] = true;
            Q.input.trigger(actionName);
            Q.input.trigger('keydown',e.keyCode);
          }
          e.preventDefault();
        },false);
  
        Q.el.addEventListener("keyup",function(e) {
          if(Q.input.keys[e.keyCode]) {
            var actionName = Q.input.keys[e.keyCode];
            Q.inputs[actionName] = false;
            Q.input.trigger(actionName + "Up");
            Q.input.trigger('keyup',e.keyCode);
          }
          e.preventDefault();
        },false);
        this.keyboardEnabled = true;
      },
  
      touchLocation: function(touch) {
        var el = Q.el, 
          posX = touch.offsetX,
          posY = touch.offsetY,
          touchX, touchY;
  
        if(Q._isUndefined(posX) || Q._isUndefined(posY)) {
          posX = touch.layerX;
          posY = touch.layerY;
        }
  
        if(Q._isUndefined(posX) || Q._isUndefined(posY)) {
          posX = touch.clientX - Q.el.offsetLeft - Q.wrapper.offsetLeft;
          posY = touch.clientY - Q.el.offsetTop - Q.wrapper.offsetTop;
        }
  
        touchX = Q.width * posX / Q.cssWidth;
        touchY = Q.height * posY / Q.cssHeight;
  
        return { x: touchX, y: touchY };
      },
  
      touchControls: function(opts) {
        if(this.touchEnabled) { return false; }
        if(!hasTouch) { return false; }
  
        Q.input.keypad = opts = Q._extend({
          left: 0,
          gutter:10,
          controls: DEFAULT_TOUCH_CONTROLS,
          width: Q.width,
          bottom: Q.height
        },opts);
  
        opts.unit = (opts.width / opts.controls.length);
        opts.size = opts.unit - 2 * opts.gutter;
  
        function getKey(touch) {
          var pos = Q.input.touchLocation(touch);
          for(var i=0,len=opts.controls.length;i<len;i++) {
            if(pos.x < opts.unit * (i+1)) {
              return opts.controls[i][0];
            }
          }
        }
  
        function touchDispatch(event) {
          var wasOn = {},
              i, len, tch, key, actionName;
  
          // Reset all the actions bound to controls
          // but keep track of all the actions that were on
          for(i=0,len = opts.controls.length;i<len;i++) {
            actionName = opts.controls[i][0];
            if(Q.inputs[actionName]) { wasOn[actionName] = true; }
            Q.inputs[actionName] = false;
          }
  
          for(i=0,len=event.touches.length;i<len;i++) {
            tch = event.touches[i];
            key = getKey(tch);
  
            if(key) {
              // Mark this input as on
              Q.inputs[key] = true;
  
              // Either trigger a new action
              // or remove from wasOn list
              if(!wasOn[key]) {
                Q.input.trigger(key);
              } else {
                delete wasOn[key];
              }
            }
          }
  
          // Any remaining were on the last frame
          // and need to trigger an up action
          for(actionName in wasOn) {
            Q.input.trigger(actionName + "Up");
          }
  
          return null;
        }
  
        this.touchDispatchHandler = function(e) {
          touchDispatch(e);
          e.preventDefault();
        };
  
        Q._each(["touchstart","touchend","touchmove","touchcancel"],function(evt) {
          Q.el.addEventListener(evt,this.touchDispatchHandler);
        },this);
  
        this.touchEnabled = true;
      },
  
      disableTouchControls: function() {
        Q._each(["touchstart","touchend","touchmove","touchcancel"],function(evt) {
          Q.el.removeEventListener(evt,this.touchDispatchHandler);
        },this);
  
        Q.el.removeEventListener('touchstart',this.joypadStart);
        Q.el.removeEventListener('touchmove',this.joypadMove);
        Q.el.removeEventListener('touchend',this.joypadEnd);
        Q.el.removeEventListener('touchcancel',this.joypadEnd);
        this.touchEnabled = false;
      },
  
     joypadControls: function(opts) {
        if(this.joypadEnabled) { return false; }
        if(!hasTouch) { return false; }
  
        var joypad = Q.joypad = Q._defaults(opts || {},{
          size: 50,
          trigger: 20,
          center: 25,
          color: "#CCC",
          background: "#000",
          alpha: 0.5,
          zone: Q.width / 2,
          joypadTouch: null,
          inputs: DEFAULT_JOYPAD_INPUTS,
          triggers: []
        });
  
        this.joypadStart = function(e) {
          if(joypad.joypadTouch === null) {
            var evt = e.originalEvent,
            touch = evt.changedTouches[0],
            loc = Q.input.touchLocation(touch);
  
            if(loc.x < joypad.zone) {
              joypad.joypadTouch = touch.identifier;
              joypad.centerX = loc.x;
              joypad.centerY = loc.y; 
              joypad.x = null;
              joypad.y = null;
            }
          }
        };
  
        
        this.joypadMove = function(e) {
          if(joypad.joypadTouch !== null) {
            var evt = e;
  
            for(var i=0,len=evt.changedTouches.length;i<len;i++) {
              var touch = evt.changedTouches[i];
  
              if(touch.identifier === joypad.joypadTouch) {
                var loc = Q.input.touchLocation(touch),
                    dx = loc.x - joypad.centerX,
                    dy = loc.y - joypad.centerY,
                    dist = Math.sqrt(dx * dx + dy * dy),
                    overage = Math.max(1,dist / joypad.size),
                    ang =  Math.atan2(dx,dy);
  
                if(overage > 1) {
                  dx /= overage;
                  dy /= overage;
                  dist /= overage;
                }
  
                var triggers = [
                  dy < -joypad.trigger,
                  dx > joypad.trigger,
                  dy > joypad.trigger,
                  dx < -joypad.trigger
                ];
  
                for(var k=0;k<triggers.length;k++) {
                  var actionName = joypad.inputs[k];
                  if(triggers[k]) {
                    Q.inputs[actionName] = true;
  
                    if(!joypad.triggers[k]) { 
                      Q.input.trigger(actionName);
                    }
                  } else {
                    Q.inputs[actionName] = false;
                    if(joypad.triggers[k]) { 
                      Q.input.trigger(actionName + "Up");
                    }
                  }
                }
  
                Q._extend(joypad, {
                  dx: dx, dy: dy,
                  x: joypad.centerX + dx,
                  y: joypad.centerY + dy,
                  dist: dist,
                  ang: ang,
                  triggers: triggers
                });
  
                break;
              }
            }
          }
          e.preventDefault();
        };
  
        this.joypadEnd = function(e) { 
            var evt = e;
  
            if(joypad.joypadTouch !== null) {
              for(var i=0,len=evt.changedTouches.length;i<len;i++) { 
              var touch = evt.changedTouches[i];
                if(touch.identifier === joypad.joypadTouch) {
                  for(var k=0;k<joypad.triggers.length;k++) {
                    var actionName = joypad.inputs[k];
                    Q.inputs[actionName] = false;
                  }
                  joypad.joypadTouch = null;
                  break;
                }
              }
            }
            e.preventDefault();
        };
  
        Q.el.addEventListener("touchstart",this.joypadStart);
        Q.el.addEventListener("touchmove",this.joypadMove);
        Q.el.addEventListener("touchend",this.joypadEnd);
        Q.el.addEventListener("touchcancel",this.joypadEnd);
  
        this.joypadEnabled = true;
      },
  
      drawButtons: function() {
        var keypad = Q.input.keypad,
            ctx = Q.ctx;
  
        ctx.save();
        ctx.textAlign = "center"; 
        ctx.textBaseline = "middle";
  
        for(var i=0;i<keypad.controls.length;i++) {
          var control = keypad.controls[i];
  
          if(control[0]) {
            ctx.font = "bold " + (keypad.size/2) + "px arial";
            var x = i * keypad.unit + keypad.gutter,
                y = keypad.bottom - keypad.unit,
                key = Q.inputs[control[0]];
  
            ctx.fillStyle = keypad.color || "#FFFFFF";
            ctx.globalAlpha = key ? 1.0 : 0.5;
            ctx.fillRect(x,y,keypad.size,keypad.size);
  
            ctx.fillStyle = keypad.text || "#000000";
            ctx.fillText(control[1],
                         x+keypad.size/2,
                         y+keypad.size/2);
          }
        }
  
        ctx.restore();
      },
  
      drawCircle: function(x,y,color,size) {
        var ctx = Q.ctx,
            joypad = Q.joypad;
  
        ctx.save();
        ctx.beginPath();
        ctx.globalAlpha=joypad.alpha;
        ctx.fillStyle = color;
        ctx.arc(x, y, size, 0, Math.PI*2, true); 
        ctx.closePath();
        ctx.fill();
        ctx.restore();
      },
  
      drawJoypad: function() {
        var joypad = Q.joypad;
        if(joypad.joypadTouch !== null) {
          Q.input.drawCircle(joypad.centerX,
                             joypad.centerY,
                             joypad.background,
                             joypad.size);
  
          if(joypad.x !== null) {
            Q.input.drawCircle(joypad.x,
                             joypad.y,
                             joypad.color,
                             joypad.center);
          }
        }
  
      },
  
      drawCanvas: function() {
        if(this.touchEnabled) {
          this.drawButtons();
        }
  
        if(this.joypadEnabled) {
          this.drawJoypad();
        }
      }
  
    });
    
    Q.input = new Q.InputSystem();
  
    Q.controls = function(joypad) {
      Q.input.keyboardControls();
  
      if(joypad) {
        Q.input.touchControls({
          controls: [ [],[],[],['action','b'],['fire','a']]
        });
        Q.input.joypadControls();
      } else {
        Q.input.touchControls();
      }
  
      return Q;
    };
    
  
    Q.component("platformerControls", {
      defaults: {
        speed: 200,
        jumpSpeed: -300
      },
  
      added: function() {
        var p = this.entity.p;
  
        Q._defaults(p,this.defaults);
  
        this.entity.on("step",this,"step");
        this.entity.on("bump.bottom",this,"landed");
  
        p.landed = 0;
        p.direction ='right';
      },
  
      landed: function(col) {
        var p = this.entity.p;
        p.landed = 1/5;
      },
  
      step: function(dt) {
        var p = this.entity.p;
  
        if(Q.inputs['left']) {
          p.vx = -p.speed;
          p.direction = 'left';
        } else if(Q.inputs['right']) {
          p.direction = 'right';
          p.vx = p.speed;
        } else {
          p.vx = 0;
        }
  
        if(p.landed > 0 && (Q.inputs['up'] || Q.inputs['action'])) {
          p.vy = p.jumpSpeed;
          p.landed = -dt;
        }
        p.landed -= dt;
  
      }
    });
  
    Q.component("stepControls", {
  
      added: function() {
        var p = this.entity.p;
  
        if(!p.stepDistance) { p.stepDistance = 32; }
        if(!p.stepDelay) { p.stepDelay = 0.2; }
  
        p.stepWait = 0;
        this.entity.on("step",this,"step");
        this.entity.on("hit", this,"collision");
      },
  
      collision: function(col) {
        var p = this.entity.p;
  
        if(p.stepping) {
          p.stepping = false;
          p.x = p.origX;
          p.y = p.origY;
        }
  
      },
  
      step: function(dt) {
        var p = this.entity.p,
            moved = false;
        p.stepWait -= dt;
  
        if(p.stepping) {
          p.x += p.diffX * dt / p.stepDelay;
          p.y += p.diffY * dt / p.stepDelay;
        }
  
        if(p.stepWait > 0) { return; }
        if(p.stepping) {
          p.x = p.destX;
          p.y = p.destY;
        }
        p.stepping = false;
  
        p.diffX = 0;
        p.diffY = 0;
  
        if(Q.inputs['left']) {
          p.diffX = -p.stepDistance;
        } else if(Q.inputs['right']) {
          p.diffX = p.stepDistance;
        }
  
        if(Q.inputs['up']) {
          p.diffY = -p.stepDistance;
        } else if(Q.inputs['down']) {
          p.diffY = p.stepDistance;
        }
  
        if(p.diffY || p.diffX ) { 
          p.stepping = true;
          p.origX = p.x;
          p.origY = p.y;
          p.destX = p.x + p.diffX;
          p.destY = p.y + p.diffY;
          p.stepWait = p.stepDelay; 
        }
  
      }
  
    });
  };
  
  


(C) Æliens 04/09/2009

You may not copy or print any of this material without explicit permission of the author or the publisher. In case of other copyright issues, contact the author.