topical media & game development
mobile-game-ch21-pong-public-js-quintus-input.js / js
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;
_(keys).each(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.attr('tabindex',0).css('outline',0);
Q.el.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();
});
Q.el.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();
});
this.keyboardEnabled = true;
},
touchLocation: function(touch) {
var el = Q.el,
pageX = touch.pageX,
pageY = touch.pageY,
pos = el.offset(),
touchX = (el.attr('width')||Q.width) * (pageX - pos.left) / el.width(),
touchY = (el.attr('height')||Q.height) * (pageY - pos.top) / el.height();
return { x: touchX, y: touchY };
},
touchControls: function(opts) {
if(this.touchEnabled) return false;
if(!hasTouch) return false;
Q.input.keypad = opts = _({
left: 0,
gutter:10,
controls: DEFAULT_TOUCH_CONTROLS,
width: Q.el.attr('width') || Q.width,
bottom: Q.el.attr('height') || Q.height
}).extend(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 elemPos = Q.el.position(),
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;
}
Q.el.on('touchstart touchend touchmove touchcancel',function(e) {
touchDispatch(e.originalEvent);
e.preventDefault();
});
this.touchEnabled = true;
},
disableTouchControls: function() {
Q.el.off('touchstart touchend touchmove touchcancel');
this.touchEnabled = false;
},
joypadControls: function(opts) {
if(this.joypadEnabled) return false;
//if(!hasTouch) return false;
var joypad = Q.joypad = _.defaults(opts || {},{
size: 50,
trigger: 20,
center: 25,
color: "#CCC",
background: "#000",
alpha: 0.5,
zone: (Q.el.attr('width')||Q.width) / 2,
joypadTouch: null,
inputs: DEFAULT_JOYPAD_INPUTS,
triggers: []
});
Q.el.on('touchstart',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;
}
}
});
Q.el.on('touchmove',function(e) {
if(joypad.joypadTouch !== null) {
var evt = e.originalEvent;
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");
}
}
}
_.extend(joypad, {
dx: dx, dy: dy,
x: joypad.centerX + dx,
y: joypad.centerY + dy,
dist: dist,
ang: ang,
triggers: triggers
});
break;
}
}
}
e.preventDefault();
});
Q.el.on('touchend touchcancel',function(e) {
var evt = e.originalEvent;
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();
});
this.joypadEnabled = true;
},
drawButtons: function() {
var keypad = Q.input.keypad,
ctx = Q.ctx;
for(var i=0;i<keypad.controls.length;i++) {
var control = keypad.controls[i];
if(control[0]) {
if(control[2]) {
// TODO: Draw an asset instead of squares
} else {
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]],
txtSize = ctx.measureText(control[1]);
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-txtSize.width/2,
y+3*keypad.size/4);
}
}
}
},
drawCircle: function(x,y,color,size) {
var ctx = Q.ctx,
joypad = Q.joypad;
ctx.beginPath();
ctx.globalAlpha=joypad.alpha;
ctx.fillStyle = color;
ctx.arc(x, y, size, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
},
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() {
Q.ctx.save();
if(this.touchEnabled) {
this.drawButtons();
}
if(this.joypadEnabled) {
this.drawJoypad();
}
Q.ctx.restore();
}
});
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;
};
};
(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.