/src/input/input.js

https://github.com/pujie/melonJS · JavaScript · 744 lines · 340 code · 81 blank · 323 comment · 70 complexity · 8e7de8ae7df85282ca2411fd1703ad78 MD5 · raw file

  1. /*
  2. * MelonJS Game Engine
  3. * Copyright (C) 2011 - 2013, Olivier BIOT
  4. * http://www.melonjs.org
  5. *
  6. */
  7. (function($) {
  8. /**
  9. * There is no constructor function for me.input.
  10. * @final
  11. * @memberOf me
  12. * @constructor Should not be called by the user.
  13. */
  14. me.input = (function() {
  15. // hold public stuff in our singleton
  16. var obj = {};
  17. /*---------------------------------------------
  18. PRIVATE STUFF
  19. ---------------------------------------------*/
  20. // list of binded keys
  21. var KeyBinding = {};
  22. // corresponding actions
  23. var keyStatus = {};
  24. // lock enable flag for keys
  25. var keyLock = {};
  26. // actual lock status of each key
  27. var keyLocked = {};
  28. // some usefull flags
  29. var keyboardInitialized = false;
  30. var mouseInitialized = false;
  31. var accelInitialized = false;
  32. // list of supported mouse & touch events
  33. var mouseEventList = ['mousewheel', 'mousemove', 'mousedown', 'mouseup', 'click', 'dblclick'];
  34. var touchEventList = [ undefined, 'touchmove', 'touchstart', 'touchend', 'tap' , 'dbltap'];
  35. /**
  36. * enable keyboard event
  37. * @private
  38. */
  39. function enableKeyboardEvent() {
  40. if (!keyboardInitialized) {
  41. $.addEventListener('keydown', keydown, false);
  42. $.addEventListener('keyup', keyup, false);
  43. keyboardInitialized = true;
  44. }
  45. }
  46. /**
  47. * enable mouse event
  48. * @private
  49. */
  50. function enableMouseEvent() {
  51. if (!mouseInitialized) {
  52. // initialize mouse pos (0,0)
  53. obj.touches.push({ x: 0, y: 0 });
  54. obj.mouse.pos = new me.Vector2d(0,0);
  55. // get relative canvas position in the page
  56. obj.mouse.offset = me.video.getPos();
  57. // add event listener for mouse & touch event
  58. if (me.sys.touch) {
  59. me.video.getScreenCanvas().addEventListener('touchmove', onMouseMove, false );
  60. for (var x = 2; x < touchEventList.length;++x) {
  61. me.video.getScreenCanvas().addEventListener(touchEventList[x], onTouchEvent, false );
  62. }
  63. } else {
  64. me.video.getScreenCanvas().addEventListener('mousemove', onMouseMove, false);
  65. $.addEventListener('mousewheel', onMouseWheel, false );
  66. for (var x = 2; x < mouseEventList.length;++x) {
  67. me.video.getScreenCanvas().addEventListener(mouseEventList[x], onMouseEvent, false );
  68. }
  69. }
  70. mouseInitialized = true;
  71. }
  72. }
  73. /**
  74. * prevent event propagation
  75. * @private
  76. */
  77. function preventDefault(e) {
  78. // stop event propagation
  79. if (e.stopPropagation) {
  80. e.stopPropagation();
  81. }
  82. else {
  83. e.cancelBubble = true;
  84. }
  85. // stop event default processing
  86. if (e.preventDefault) {
  87. e.preventDefault();
  88. }
  89. else {
  90. e.returnValue = false;
  91. }
  92. return false;
  93. }
  94. /**
  95. * key down event
  96. * @private
  97. */
  98. function keydown(e, keyCode) {
  99. var action = KeyBinding[keyCode || e.keyCode || e.which];
  100. if (action) {
  101. if (!keyLocked[action]) {
  102. keyStatus[action] = true;
  103. // lock the key if requested
  104. keyLocked[action] = keyLock[action];
  105. // publish a message for keydown event
  106. me.event.publish(me.event.KEYDOWN, [ action ]);
  107. }
  108. // prevent event propagation
  109. return preventDefault(e);
  110. }
  111. return true;
  112. }
  113. /**
  114. * key up event
  115. * @private
  116. */
  117. function keyup(e, keyCode) {
  118. var action = KeyBinding[keyCode || e.keyCode || e.which];
  119. if (action) {
  120. keyStatus[action] = false;
  121. keyLocked[action] = false;
  122. // publish message for keyup event
  123. me.event.publish(me.event.KEYUP, [ action ]);
  124. // prevent the event propagation
  125. return preventDefault(e);
  126. }
  127. return true;
  128. }
  129. /**
  130. * propagate mouse event to registed object
  131. * @private
  132. */
  133. function dispatchMouseEvent(e) {
  134. var handled = false;
  135. var handlers = obj.mouse.handlers[e.type];
  136. if (handlers) {
  137. var vpos = me.game.viewport.pos;
  138. var map_pos = me.game.currentLevel.pos;
  139. for(var t=0, l=obj.touches.length; t<l; t++) {
  140. // cache the x/y coordinates
  141. var x = obj.touches[t].x;
  142. var y = obj.touches[t].y;
  143. for (var i = handlers.length, handler; i--, handler = handlers[i];) {
  144. // adjust to world coordinates if not a floating object
  145. if (handler.floating===false) {
  146. var v = {x: x + vpos.x - map_pos.x, y: y + vpos.y - map_pos.y };
  147. } else {
  148. var v = {x: x, y: y};
  149. }
  150. // call the defined handler
  151. if ((handler.rect === null) || handler.rect.containsPoint(v)) {
  152. // trigger the corresponding callback
  153. if (handler.cb(e) === false) {
  154. // stop propagating the event if return false
  155. handled = true;
  156. break;
  157. }
  158. }
  159. }
  160. }
  161. }
  162. return handled;
  163. }
  164. /**
  165. * translate Mouse Coordinates
  166. * @private
  167. */
  168. function updateCoordFromEvent(e) {
  169. // reset the touch array cache
  170. obj.touches.length=0;
  171. // non touch event (mouse)
  172. if (!e.touches) {
  173. var offset = obj.mouse.offset;
  174. var x = e.pageX - offset.x;
  175. var y = e.pageY - offset.y;
  176. var scale = me.sys.scale;
  177. if (scale.x != 1.0 || scale.y != 1.0) {
  178. x/=scale.x;
  179. y/=scale.y;
  180. }
  181. obj.touches.push({ x: x, y: y, id: 0});
  182. }
  183. // touch event
  184. else {
  185. var offset = obj.mouse.offset;
  186. for(var i=0, l=e.changedTouches.length; i<l; i++) {
  187. var t = e.changedTouches[i];
  188. var x = t.clientX - offset.x;
  189. var y = t.clientY - offset.y;
  190. var scale = me.sys.scale;
  191. if (scale.x != 1.0 || scale.y != 1.0) {
  192. x/=scale.x;
  193. y/=scale.y;
  194. }
  195. obj.touches.push({ x: x, y: y, id: t.identifier });
  196. }
  197. }
  198. obj.mouse.pos.set(obj.touches[0].x,obj.touches[0].y);
  199. }
  200. /**
  201. * mouse event management (mousewheel)
  202. * @private
  203. */
  204. function onMouseWheel(e) {
  205. if (e.target == me.video.getScreenCanvas()) {
  206. // dispatch mouse event to registered object
  207. if (dispatchMouseEvent(e)) {
  208. // prevent default action
  209. return preventDefault(e);
  210. }
  211. }
  212. return true;
  213. }
  214. /**
  215. * mouse event management (mousemove)
  216. * @private
  217. */
  218. function onMouseMove(e) {
  219. // update position
  220. updateCoordFromEvent(e);
  221. // dispatch mouse event to registered object
  222. if (dispatchMouseEvent(e)) {
  223. // prevent default action
  224. return preventDefault(e);
  225. }
  226. return true;
  227. }
  228. /**
  229. * mouse event management (mousedown, mouseup)
  230. * @private
  231. */
  232. function onMouseEvent(e) {
  233. // dispatch event to registered objects
  234. if (dispatchMouseEvent(e)) {
  235. // prevent default action
  236. return preventDefault(e);
  237. }
  238. // in case of touch event button is undefined
  239. var keycode = obj.mouse.bind[e.button || 0];
  240. // check if mapped to a key
  241. if (keycode) {
  242. if (e.type === 'mousedown' || e.type === 'touchstart')
  243. return keydown(e, keycode);
  244. else // 'mouseup' or 'touchend'
  245. return keyup(e, keycode);
  246. }
  247. return true;
  248. }
  249. /**
  250. * mouse event management (touchstart, touchend)
  251. * @private
  252. */
  253. function onTouchEvent(e) {
  254. // update the new touch position
  255. updateCoordFromEvent(e);
  256. // reuse the mouse event function
  257. return onMouseEvent(e);
  258. }
  259. /**
  260. * event management (Accelerometer)
  261. * http://www.mobilexweb.com/samples/ball.html
  262. * http://www.mobilexweb.com/blog/safari-ios-accelerometer-websockets-html5
  263. * @private
  264. */
  265. function onDeviceMotion(e) {
  266. // Accelerometer information
  267. obj.accel = e.accelerationIncludingGravity;
  268. }
  269. /*---------------------------------------------
  270. PUBLIC STUFF
  271. ---------------------------------------------*/
  272. /**
  273. * Accelerometer information<br>
  274. * properties : x, y, z
  275. * @public
  276. * @enum {number}
  277. * @name me.input#accel
  278. */
  279. obj.accel = {
  280. x: 0,
  281. y: 0,
  282. z: 0
  283. };
  284. /**
  285. * Mouse information<br>
  286. * properties : <br>
  287. * pos (me.Vector2d) : pointer position (in screen coordinates) <br>
  288. * LEFT : constant for left button <br>
  289. * MIDDLE : constant for middle button <br>
  290. * RIGHT : constant for right button <br>
  291. * @public
  292. * @enum {number}
  293. * @name me.input#mouse
  294. */
  295. obj.mouse = {
  296. // mouse position
  297. pos : null,
  298. // canvas offset
  299. offset : null,
  300. // button constants (W3C)
  301. LEFT: 0,
  302. MIDDLE: 1,
  303. RIGHT: 2,
  304. // bind list for mouse buttons
  305. bind: [ 0, 0, 0 ],
  306. handlers:{}
  307. };
  308. /**
  309. * Array of object containing touch information<br>
  310. * properties : <br>
  311. * x : x position of the touch event in the canvas (screen coordinates)<br>
  312. * y : y position of the touch event in the canvas (screen coordinates)<br>
  313. * id : unique finger identifier<br>
  314. * @public
  315. * @type Array
  316. * @name me.input#touches
  317. */
  318. obj.touches = [];
  319. /**
  320. * list of mappable keys :
  321. * LEFT, UP, RIGHT, DOWN, ENTER, SHIFT, CTRL, ALT, PAUSE, ESC, ESCAPE, [0..9], [A..Z]
  322. * @public
  323. * @enum {number}
  324. * @name me.input#KEY
  325. */
  326. obj.KEY = {
  327. 'LEFT' : 37,
  328. 'UP' : 38,
  329. 'RIGHT' : 39,
  330. 'DOWN' : 40,
  331. 'ENTER' : 13,
  332. 'SHIFT' : 16,
  333. 'CTRL' : 17,
  334. 'ALT' : 18,
  335. 'PAUSE' : 19,
  336. 'ESC' : 27,
  337. 'SPACE' : 32,
  338. 'NUM0' : 48,
  339. 'NUM1' : 49,
  340. 'NUM2' : 50,
  341. 'NUM3' : 51,
  342. 'NUM4' : 52,
  343. 'NUM5' : 53,
  344. 'NUM6' : 54,
  345. 'NUM7' : 55,
  346. 'NUM8' : 56,
  347. 'NUM9' : 57,
  348. 'A' : 65,
  349. 'B' : 66,
  350. 'C' : 67,
  351. 'D' : 68,
  352. 'E' : 69,
  353. 'F' : 70,
  354. 'G' : 71,
  355. 'H' : 72,
  356. 'I' : 73,
  357. 'J' : 74,
  358. 'K' : 75,
  359. 'L' : 76,
  360. 'M' : 77,
  361. 'N' : 78,
  362. 'O' : 79,
  363. 'P' : 80,
  364. 'Q' : 81,
  365. 'R' : 82,
  366. 'S' : 83,
  367. 'T' : 84,
  368. 'U' : 85,
  369. 'V' : 86,
  370. 'W' : 87,
  371. 'X' : 88,
  372. 'Y' : 89,
  373. 'Z' : 90
  374. };
  375. /**
  376. * return the key press status of the specified action
  377. * @name me.input#isKeyPressed
  378. * @public
  379. * @function
  380. * @param {String} action user defined corresponding action
  381. * @return {boolean} true if pressed
  382. * @example
  383. * if (me.input.isKeyPressed('left'))
  384. * {
  385. * //do something
  386. * }
  387. * else if (me.input.isKeyPressed('right'))
  388. * {
  389. * //do something else...
  390. * }
  391. *
  392. */
  393. obj.isKeyPressed = function(action) {
  394. if (keyStatus[action]) {
  395. if (keyLock[action]) {
  396. keyLocked[action] = true;
  397. // "eat" the event
  398. keyStatus[action] = false;
  399. }
  400. return true;
  401. }
  402. return false;
  403. };
  404. /**
  405. * return the key status of the specified action
  406. * @name me.input#keyStatus
  407. * @public
  408. * @function
  409. * @param {String} action user defined corresponding action
  410. * @return {boolean} down (true) or up(false)
  411. */
  412. obj.keyStatus = function(action) {
  413. return (keyLocked[action] === true) ? true : keyStatus[action];
  414. };
  415. /**
  416. * trigger the specified key (simulated) event <br>
  417. * @name me.input#triggerKeyEvent
  418. * @public
  419. * @function
  420. * @param {me.input#KEY} keycode
  421. * @param {boolean} true to trigger a key press, or false for key release
  422. * @example
  423. * // trigger a key press
  424. * me.input.triggerKeyEvent(me.input.KEY.LEFT, true);
  425. */
  426. obj.triggerKeyEvent = function(keycode, status) {
  427. if (status) {
  428. keydown({}, keycode);
  429. }
  430. else {
  431. keyup({}, keycode);
  432. }
  433. };
  434. /**
  435. * associate a user defined action to a keycode
  436. * @name me.input#bindKey
  437. * @public
  438. * @function
  439. * @param {me.input#KEY} keycode
  440. * @param {String} action user defined corresponding action
  441. * @param {boolean} lock cancel the keypress event once read
  442. * @example
  443. * // enable the keyboard
  444. * me.input.bindKey(me.input.KEY.LEFT, "left");
  445. * me.input.bindKey(me.input.KEY.RIGHT, "right");
  446. * me.input.bindKey(me.input.KEY.X, "jump", true);
  447. */
  448. obj.bindKey = function(keycode, action, lock) {
  449. // make sure the keyboard is enable
  450. enableKeyboardEvent();
  451. KeyBinding[keycode] = action;
  452. keyStatus[action] = false;
  453. keyLock[action] = lock ? lock : false;
  454. keyLocked[action] = false;
  455. };
  456. /**
  457. * unbind the defined keycode
  458. * @name me.input#unbindKey
  459. * @public
  460. * @function
  461. * @param {me.input#KEY} keycode
  462. * @example
  463. * me.input.unbindKey(me.input.KEY.LEFT);
  464. */
  465. obj.unbindKey = function(keycode) {
  466. // clear the event status
  467. keyStatus[KeyBinding[keycode]] = false;
  468. keyLock[KeyBinding[keycode]] = false;
  469. // remove the key binding
  470. KeyBinding[keycode] = null;
  471. };
  472. /**
  473. * Associate a mouse (button) action to a keycode
  474. * Left button – 0
  475. * Middle button – 1
  476. * Right button – 2
  477. * @name me.input#bindMouse
  478. * @public
  479. * @function
  480. * @param {Integer} button (accordingly to W3C values : 0,1,2 for left, middle and right buttons)
  481. * @param {me.input#KEY} keyCode
  482. * @example
  483. * // enable the keyboard
  484. * me.input.bindKey(me.input.KEY.X, "shoot");
  485. * // map the left button click on the X key
  486. * me.input.bindMouse(me.input.mouse.LEFT, me.input.KEY.X);
  487. */
  488. obj.bindMouse = function (button, keyCode)
  489. {
  490. // make sure the mouse is initialized
  491. enableMouseEvent();
  492. // throw an exception if no action is defined for the specified keycode
  493. if (!KeyBinding[keyCode])
  494. throw "melonJS : no action defined for keycode " + keyCode;
  495. // map the mouse button to the keycode
  496. obj.mouse.bind[button] = keyCode;
  497. };
  498. /**
  499. * unbind the defined keycode
  500. * @name me.input#unbindMouse
  501. * @public
  502. * @function
  503. * @param {Integer} button (accordingly to W3C values : 0,1,2 for left, middle and right buttons)
  504. * @example
  505. * me.input.unbindMouse(me.input.mouse.LEFT);
  506. */
  507. obj.unbindMouse = function(button) {
  508. // clear the event status
  509. obj.mouse.bind[button] = null;
  510. };
  511. /**
  512. * Associate a touch action to a keycode
  513. * @name me.input#bindTouch
  514. * @public
  515. * @function
  516. * @param {me.input#KEY} keyCode
  517. * @example
  518. * // enable the keyboard
  519. * me.input.bindKey(me.input.KEY.X, "shoot");
  520. * // map the touch event on the X key
  521. * me.input.bindTouch(me.input.KEY.X);
  522. */
  523. obj.bindTouch = function (keyCode)
  524. {
  525. // reuse the mouse emulation stuff
  526. // where left mouse button is map to touch event
  527. obj.bindMouse(me.input.mouse.LEFT, keyCode);
  528. };
  529. /**
  530. * unbind the defined touch binding
  531. * @name me.input#unbindTouch
  532. * @public
  533. * @function
  534. * @example
  535. * me.input.unbindTouch();
  536. */
  537. obj.unbindTouch = function() {
  538. // clear the key binding
  539. obj.unbindMouse(me.input.mouse.LEFT);
  540. };
  541. /**
  542. * register on a mouse event for a given region
  543. * note : on a touch enabled device mouse event will automatically be converted to touch event
  544. * @name me.input#registerMouseEvent
  545. * @public
  546. * @function
  547. * @param {String} eventType ('mousemove','mousedown','mouseup','mousewheel','touchstart','touchmove','touchend')
  548. * @param {me.Rect} rect (object must inherits from me.Rect)
  549. * @param {Function} callback
  550. * @param {Boolean} [floating="floating property of the given object"] specify if the object is a floating object (if yes, screen coordinates are used, if not mouse/touch coordinates will be converted to world coordinates)
  551. * @example
  552. * // register on the 'mousemove' event
  553. * me.input.registerMouseEvent('mousemove', this.collisionBox, this.mouseMove.bind(this));
  554. */
  555. obj.registerMouseEvent = function(eventType, rect, callback, floating) {
  556. // make sure the mouse is initialized
  557. enableMouseEvent();
  558. // convert the mouse event into a touch event
  559. // if we are on a touch device
  560. if ( me.sys.touch && (mouseEventList.indexOf(eventType) !== -1)) {
  561. eventType = touchEventList[mouseEventList.indexOf(eventType)];
  562. }
  563. // check if this is supported event
  564. if (eventType && ((mouseEventList.indexOf(eventType) !== -1) ||
  565. (touchEventList.indexOf(eventType) !== -1))) {
  566. // register the event
  567. if (!obj.mouse.handlers[eventType]) {
  568. obj.mouse.handlers[eventType] = [];
  569. }
  570. // check if this is a floating object or not
  571. var _float = rect.floating===true?true:false;
  572. // check if there is a given parameter
  573. if (floating) {
  574. // ovveride the previous value
  575. _float = floating===true?true:false;
  576. }
  577. // initialize the handler
  578. obj.mouse.handlers[eventType].push({rect:rect||null,cb:callback,floating:_float});
  579. return;
  580. }
  581. throw "melonJS : invalid event type : " + eventType;
  582. };
  583. /**
  584. * release the previously registered mouse event callback
  585. * note : on a touch enabled device mouse event will automatically be converted to touch event
  586. * @name me.input#releaseMouseEvent
  587. * @public
  588. * @function
  589. * @param {String} eventType ('mousemove', 'mousedown', 'mouseup', 'mousewheel', 'click', 'dblclick', 'touchstart', 'touchmove', 'touchend', 'tap', 'dbltap')
  590. * @param {me.Rect} region
  591. * @example
  592. * // release the registered callback on the 'mousemove' event
  593. * me.input.releaseMouseEvent('mousemove', this.collisionBox);
  594. */
  595. obj.releaseMouseEvent = function(eventType, rect) {
  596. // convert the mouse event into a touch event
  597. // if we are on a touch device
  598. if ( me.sys.touch && (mouseEventList.indexOf(eventType) !== -1)) {
  599. eventType = touchEventList[mouseEventList.indexOf(eventType)];
  600. }
  601. // check if this is supported event
  602. if (eventType && ((mouseEventList.indexOf(eventType) !== -1) ||
  603. (touchEventList.indexOf(eventType) !== -1))) {
  604. // unregister the event
  605. if (!obj.mouse.handlers[eventType]) {
  606. obj.mouse.handlers[eventType] = [];
  607. }
  608. var handlers = obj.mouse.handlers[eventType];
  609. if (handlers) {
  610. for (var i = handlers.length, handler; i--, handler = handlers[i];) {
  611. if (handler.rect === rect) {
  612. // make sure all references are null
  613. handler.rect = handler.cb = handler.floating = null;
  614. obj.mouse.handlers[eventType].splice(i, 1);
  615. }
  616. }
  617. }
  618. return;
  619. }
  620. throw "melonJS : invalid event type : " + eventType;
  621. };
  622. /**
  623. * watch Accelerator event
  624. * @name me.input#watchAccelerometer
  625. * @public
  626. * @function
  627. * @return {boolean} false if not supported by the device
  628. */
  629. obj.watchAccelerometer = function() {
  630. if ($.sys.gyro) {
  631. if (!accelInitialized) {
  632. // add a listener for the mouse
  633. $.addEventListener('devicemotion', onDeviceMotion, false);
  634. accelInitialized = true;
  635. }
  636. return true;
  637. }
  638. return false;
  639. };
  640. /**
  641. * unwatch Accelerometor event
  642. * @name me.input#unwatchAccelerometer
  643. * @public
  644. * @function
  645. */
  646. obj.unwatchAccelerometer = function() {
  647. if (accelInitialized) {
  648. // add a listener for the mouse
  649. $.removeEventListener('devicemotion', onDeviceMotion, false);
  650. accelInitialized = false;
  651. }
  652. };
  653. // return our object
  654. return obj;
  655. })();
  656. /*---------------------------------------------------------*/
  657. // END END END
  658. /*---------------------------------------------------------*/
  659. })(window);