/*global Quintus:false, module:false */ /** Quintus HTML5 Game Engine - Input Module The code in `quintus_input.js` defines the `Quintus.Input` module, which concerns itself with game-type (pretty anything besides touchscreen input) @module Quintus.Input */ var quintusInput = function(Quintus) { "use strict"; /** * Quintus Input Module * * @class Quintus.Input */ Quintus.Input = function(Q) { /** * Provided key names mapped to key codes - add more names and key codes as necessary * * @for Quintus.Input * @property KEY_NAMES * @type Object * @static */ var KEY_NAMES = Q.KEY_NAMES = { LEFT: 37, RIGHT: 39, UP: 38, DOWN: 40, ZERO : 48, ONE : 49, TWO : 50, THREE : 51, FOUR : 52, FIVE : 53, SIX : 54, SEVEN : 55, EIGHT : 56, NINE : 57, A : 65, B : 66, C : 67, D : 68, E : 69, F : 70, G : 71, H : 72, I : 73, J : 74, K : 75, L : 76, M : 77, N : 78, O : 79, P : 80, Q : 81, R : 82, S : 83, T : 84, U : 85, V : 86, W : 87, X : 88, Y : 89, Z : 90, ENTER: 13, ESC: 27, BACKSPACE : 8, TAB : 9, SHIFT : 16, CTRL : 17, ALT : 18, SPACE: 32, HOME : 36, END : 35, PGGUP : 33, PGDOWN : 34 }; var DEFAULT_KEYS = { LEFT: 'left', RIGHT: 'right', UP: 'up', DOWN: 'down', SPACE: 'fire', Z: 'fire', X: 'action', ENTER: 'confirm', ESC: 'esc', P: 'P', S: 'S' }; var DEFAULT_TOUCH_CONTROLS = [ ['left','<' ], ['right','>' ], [], ['action','b'], ['fire', 'a' ]]; // Clockwise from midnight (a la CSS) var DEFAULT_JOYPAD_INPUTS = [ 'up','right','down','left']; /** * Current state of bound inputs * * @for Quintus.Input * @property Q.inputs * @type Object */ Q.inputs = {}; Q.joypad = {}; var hasTouch = !!('ontouchstart' in window); /** * * Convert a canvas point to a stage point, x dimension * * @method Q.canvasToStageX * @for Quintus.Input * @param {Float} x * @param {Q.Stage} stage * @returns {Integer} x */ Q.canvasToStageX = function(x,stage) { x = x / Q.cssWidth * Q.width; if(stage.viewport) { x /= stage.viewport.scale; x += stage.viewport.x; } return x; }; /** * * Convert a canvas point to a stage point, y dimension * * @method Q.canvasToStageY * @param {Float} y * @param {Q.Stage} stage * @returns {Integer} y */ Q.canvasToStageY = function(y,stage) { y = y / Q.cssWidth * Q.width; if(stage.viewport) { y /= stage.viewport.scale; y += stage.viewport.y; } return y; }; /** * * Button and mouse input subsystem for Quintus. * An instance of this class is auto-created as {{#crossLink "Q.input"}}{{/crossLink}} * * @class Q.InputSystem * @extends Q.Evented * @for Quintus.Input */ Q.InputSystem = Q.Evented.extend({ keys: {}, keypad: {}, keyboardEnabled: false, touchEnabled: false, joypadEnabled: false, /** * Bind a key name or keycode to an action name (used by `keyboardControls`) * * @method bindKey * @for Q.InputSystem * @param {String or Integer} key - name or integer keycode for to bind * @param {String} name - name of action to bind to */ bindKey: function(key,name) { Q.input.keys[KEY_NAMES[key] || key] = name; }, /** * Enable keyboard controls by binding to events * * @for Q.InputSystem * @method 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); } if(!e.ctrlKey && !e.metaKey) { 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); if(Q.options.autoFocus) { Q.el.focus(); } this.keyboardEnabled = true; }, /** * Convenience method to activate keyboard controls (call `bindKey` and `enableKeyboard` internally) * * @method keyboardControls * @for Q.InputSystem * @param {Object} [keys] - hash of key names or codes to actions */ keyboardControls: function(keys) { keys = keys || DEFAULT_KEYS; Q._each(keys,function(name,key) { this.bindKey(key,name); },Q.input); this.enableKeyboard(); }, _containerOffset: function() { Q.input.offsetX = 0; Q.input.offsetY = 0; var el = Q.el; do { Q.input.offsetX += el.offsetLeft; Q.input.offsetY += el.offsetTop; } while(el = el.offsetParent); }, 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)) { if(Q.input.offsetX === void 0) { Q.input._containerOffset(); } posX = touch.pageX - Q.input.offsetX; posY = touch.pageY - Q.input.offsetY; } touchX = Q.width * posX / Q.cssWidth; touchY = Q.height * posY / Q.cssHeight; return { x: touchX, y: touchY }; }, /** * Activate touch button controls - pass in an options hash to override * * Default Options: * * { * left: 0, * gutter:10, * controls: DEFAULT_TOUCH_CONTROLS, * width: Q.width, * bottom: Q.height * } * * Default controls are left and right buttons, a space, and 'a' and 'b' buttons, as defined as an Array of Arrays below: * * [ ['left','<' ], * ['right','>' ], * [], // use an empty array as a spacer * ['action','b'], * ['fire', 'a' ]] * * @method touchControls * @for Q.InputSystem * @param {Object} [opts] - Options hash */ 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, fullHeight: false },opts); opts.unit = (opts.width / opts.controls.length); opts.size = opts.unit - (opts.gutter * 2); function getKey(touch) { var pos = Q.input.touchLocation(touch), minY = opts.bottom - opts.unit; for(var i=0,len=opts.controls.length;i= minX && pos.x <= (minX+opts.size) && (opts.fullHeight || (pos.y >= minY + opts.gutter && pos.y <= (minY+opts.unit - opts.gutter)))) { 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 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 0 && (Q.inputs['left'] || Q.inputs['right'] || p.landed > 0)) { if(p.collisions.length === 1) { collision = p.collisions[0]; } else { // If there's more than one possible slope, follow slope with negative Y normal collision = null; for(var i = 0; i < p.collisions.length; i++) { if(p.collisions[i].normalY < 0) { collision = p.collisions[i]; } } } // Don't climb up walls. if(collision !== null && collision.normalY > -0.3 && collision.normalY < 0.3) { collision = null; } } if(Q.inputs['left']) { p.direction = 'left'; if(collision && p.landed > 0) { p.vx = p.speed * collision.normalY; p.vy = -p.speed * collision.normalX; } else { p.vx = -p.speed; } } else if(Q.inputs['right']) { p.direction = 'right'; if(collision && p.landed > 0) { p.vx = -p.speed * collision.normalY; p.vy = p.speed * collision.normalX; } else { p.vx = p.speed; } } else { p.vx = 0; if(collision && p.landed > 0) { p.vy = 0; } } if(p.landed > 0 && (Q.inputs['up'] || Q.inputs['action']) && !p.jumping) { p.vy = p.jumpSpeed; p.landed = -dt; p.jumping = true; } else if(Q.inputs['up'] || Q.inputs['action']) { this.entity.trigger('jump', this.entity); p.jumping = true; } if(p.jumping && !(Q.inputs['up'] || Q.inputs['action'])) { p.jumping = false; this.entity.trigger('jumped', this.entity); if(p.vy < p.jumpSpeed / 3) { p.vy = p.jumpSpeed / 3; } } } p.landed -= dt; } }); /** * Step Controls component * * Adds Step (square grid based) 4-ways controls onto a Sprite * * Adds the following properties to the entity: * * { * stepDistance: 32, // should be tile size * stepDelay: 0.2 // seconds to delay before next step * } * * * @class stepControls * @for Quintus.Input */ 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; } } }); }; }; if(typeof Quintus === 'undefined') { module.exports = quintusInput; } else { quintusInput(Quintus); }