Multi-Touch Core Support

Discussion in 'JS Plugin Releases (RMMV)' started by bluebooth, Mar 15, 2016.

  1. bluebooth

    bluebooth Veteran Veteran

    Messages:
    95
    Likes Received:
    115
    First Language:
    English
    Primarily Uses:
    N/A

    Multi-Touch 1.02



    Author: Michael Morris @Blue Booth Studios



    Introduction


    Separates out multi-touch core support from Map Controls, to facilitate further growth of the plugin, and allows for potential incorporation into core upgrades.  Includes swipe gesture support.


    Features


    - Full (tested) multi-touch support!  WARNING: This required me to redo most of TouchInput core, which may introduce some incompatibilities.


    - Swipe (up/down/left/right) gesture support.


    - Input History tracking.


    Screenshots


    No visual effect by itself.


    How to Use
    - Copy script into your game js/plugins directory. 


    - Import this script into your project and set all parameters.


    - Set up events to use with the plugin.  See demo provided.


    Demo


    See Map Controls plugin demo.


    Script

    //=============================================================================
    // Bluebooth Plugins - MultiTouch
    // BBS_MultiTouch.js
    //=============================================================================

    //=============================================================================
    /*:
    * @title MultiTouch Plugin
    * @author Michael Morris (https://www.patreon.com/bluebooth)
    * @date March 4, 2016
    * @filename BBS_MultiTouch.js
    * If you enjoy my work, consider supporting me on Patreon!
    *
    * https://www.patreon.com/bluebooth
    *
    * @plugindesc v1.02 Overrides TouchInput core scripts to provide true multi-touch
    * and gesture support for RPGMaker MV. Originally developed in conjunction with
    * BBS_MapControls.
    *
    * Special Thanks to Tsukihime for all the help.
    * Special Thanks to 'Ramza' Michael Sweeney and Lakaroth for helping so much with testing.
    * Special Thanks to Jairaks89 for https://github.com/jairajs89/Touchy.js/blob/master/touchy.js
    * on which the multi-touch structure was loosely based.
    *
    * ============================================================================
    * Terms of Use
    * ============================================================================
    * - Free for use in non-commercial projects with credits
    * - Contact me for commercial use
    *
    * ============================================================================
    * Parameters
    * ============================================================================
    * @param Debug Mode
    * @desc Enable to activate console variable logging. Use for debugging odd behaviour.
    * true to enable console variable logging.
    * @default false
    *
    * @param -----Input History Tracking-----
    * @param Button History Max. Size
    * @desc How many buttons to track at once. Keep small. Use value of 1 for single touch, 2+ for multi-touch.
    * @default 2
    *
    * @param -----Gesture Handling-----
    * @param Gesture Distance Threshold
    * @desc Minimum distance touch travels (in pixels) to be considered a swipe gesture.
    * @default 150
    *
    * @param Gesture Distance Restraint
    * @desc Maximum distance touch can travel (in pixels) in perpendicular direction to swipe to be considered a swipe.
    * @default 100
    *
    * @param Gesture Maximum Time
    * @desc Maximum time before a touch times out, and ceases to be considered a gesture (in milliseconds).
    * @default 300
    *
    * @help
    * ============================================================================
    * Description
    * ============================================================================
    *
    * Provides full multi-touch and gesture support to RPGMaker MV, adds input history
    * tracking of MRU input (from keyboard, gamepad, mouse, touch), to allow for custom
    * input handling.
    *
    * ============================================================================
    * Script Commands
    * ============================================================================
    * Script Commands:
    *
    * TouchInput.wasGestured('swipe left'); # Returns true if swipe left was gestured most recently.
    *
    * ============================================================================
    * Change Log
    * ============================================================================
    * 1.02 - fixed issue where multi-touch being enabled on its own would fail to accept map touch input.
    * 1.01 - multi-touch fixed to still work with mouse. Click and touch event handling separated.
    * 1.00 - multi-touch seperated into own script to facilitate further growth.
    */
    //=============================================================================

    //=============================================================================
    var Imported = Imported || {} ;
    var BBS = BBS || {};
    Imported.MultiTouch = 1;
    BBS.MultiTouch = BBS.MultiTouch || {};

    (function() {

    //=============================================================================
    // Parameter Variables
    //=============================================================================
    var parameters = PluginManager.parameters('BBS_MultiTouch');
    var pDebugging = eval(String(parameters['Debug Mode'] || 'false'));
    var pBtnHistoryMaxSize= Number(parameters['Button History Max. Size'] || '2');
    var pThreshold = Number(parameters['Gesture Distance Threshold'] || '150');
    var pRestraint = Number(parameters['Gesture Distance Restraint'] || '100');
    var pAllowedTime = Number(parameters['Gesture Maximum Time'] || '300');

    //=============================================================================
    // Input/Button History
    //=============================================================================
    function InputHistory() {
    this.initialize.apply(this, arguments);
    };

    InputHistory.prototype = Object.create(InputHistory.prototype);
    InputHistory.prototype.constructor = InputHistory;

    InputHistory.prototype.initialize = function() {
    this._btnHistory = Array();
    this._swipeHistory = null;
    };

    InputHistory.prototype.wasPressed = function(btn) {
    if (this._btnHistory.length < 1) return false;

    var pressed = false;
    for (var i = ; i < this._btnHistory.length; i++) {
    if (this._btnHistory === btn) {
    pressed = true;
    break;
    }
    }

    return pressed;
    };

    InputHistory.prototype.wasGestured = function(gesture) {
    return (this._swipeHistory === gesture);
    };

    InputHistory.prototype.addBtn = function(btn) {
    // If btn already stored, don't need to store it again.
    if (!this.wasPressed(btn)) {
    // Shift if history size exceeded.
    if (this._btnHistory.length >= pBtnHistoryMaxSize) {
    this._btnHistory.shift();
    }

    this._btnHistory.push(btn);

    if (pDebugging) {
    $gameScreen.showPicture(1, this._btnHistory[], , , , 100, 100, 255, );

    if (this._btnHistory.length > 1) {
    $gameScreen.showPicture(2, this._btnHistory[1], , 40, , 100, 100, 255, );
    }
    }

    }
    };

    InputHistory.prototype.addGesture = function(gesture) {
    this._swipeHistory = gesture;
    };

    InputHistory.prototype.removeBtn = function(btn) {
    var index = this._btnHistory.indexOf(btn);
    if (index >= ) {
    this._btnHistory.splice(index, 1);
    }
    };

    InputHistory.prototype.removeGesture = function(gesture) {
    this._swipeHistory = null;
    };

    InputHistory.prototype._clear = function() {
    if (pDebugging) {
    $gameScreen.erasePicture(1);

    if (this._btnHistory.length > 1) {
    $gameScreen.erasePicture(2);
    }
    }

    this._btnHistory = [];
    this._swipeHistory = null;
    };

    //=============================================================================
    // Finger - Object to manage single-finger touch interactions
    //=============================================================================
    function Finger(id) {
    this.id = id;
    this._x = null;
    this._y = null;
    this._startX = null;
    this._startY = null;
    this._date = ;

    // Release not stored, because released fingers are deleted.
    };

    //=============================================================================
    // Touch Input New Functions: https://github.com/jairajs89/Touchy.js/blob/master/touchy.js
    //=============================================================================

    /* Create a new hand based on the current touches on the screen */
    TouchInput.fingersRestart = function(touches) {
    if (touches.length <= ) {
    return;
    }

    this.fingers = [];
    for (var i = ; i < event.touches.length; i++)
    {
    var touch = event.touches;
    var x = Graphics.pageToCanvasX(touch.pageX);
    var y = Graphics.pageToCanvasY(touch.pageY);

    if (Graphics.isInsideCanvas(x, y)) {
    var finger = new Finger(touch.identifier);
    this.fingers.push(finger);
    this._screenPressed = true;

    finger._x = x;
    finger._startX = x;
    finger._y = y;
    finger._startY = y;
    finger._date = Date.now();
    finger._pressedTime = ;

    if (pDebugging) {
    console.log("Touch trigger activated");
    console.log(this.fingers);
    }

    this._onTrigger(x, y);
    }
    }
    };

    TouchInput.fingersUpdate = function() {
    if (!this.fingers) {
    return;
    }

    for (var i = ; i < this.fingers.length; i++) {
    var finger = this.fingers;
    finger._pressedTime++;
    }
    };

    /* Check a finger just prior to release to detect whether it was a swipe. */
    TouchInput.checkForSwipe = function(finger) {
    if (!finger) {
    return;
    }

    var elapsedTime = new Date().getTime() - finger._date; // get time elapsed
    if (elapsedTime > pAllowedTime) {
    return;
    }

    var distX = finger._x - finger._startX; // get horizontal dist traveled by finger while in contact with surface
    var distY = finger._y - finger._startY; // get vertical dist traveled by finger while in contact with surface

    if (pDebugging) {
    console.log(elapsedTime + " vs. " + pAllowedTime);
    console.log("X Dist: " + distX + " Y Dist: " + distY);
    console.log("Threshold: " + pThreshold + " Restraint: " + pRestraint);
    }

    if (Math.abs(distX) >= pThreshold && Math.abs(distY) <= pRestraint) { // 2nd condition for horizontal swipe met
    // If dist traveled is negative, it indicates left swipe
    if (distX < ) {
    this._onSwipedLeft();
    } else {
    this._onSwipedRight();
    }
    }
    else if (Math.abs(distY) >= pThreshold && Math.abs(distX) <= pRestraint) { // 2nd condition for vertical swipe met
    // If dist traveled is negative, it indicates up swipe
    if (distY < ) {
    this._onSwipedUp();
    } else {
    this._onSwipedDown();
    }
    }

    return;
    };

    /* Destroy the current hand regardless of fingers on the screen */
    TouchInput.fingersDestroy = function() {
    if (!this.fingers) {
    return;
    }

    for (var i = ; i < this.fingers.length; i++) {
    var finger = this.fingers;

    // Gesture handling happens here.
    this.checkForSwipe(finger);
    this._onRelease(finger._x, finger._y);
    }

    this._screenPressed = false;
    this.fingers = null;
    };

    /* Get finger by id */
    TouchInput.getFinger = function(id) {
    var foundFinger = null;

    if (!this.fingers) return foundFinger;

    // Direct iteration significantly faster than foreach.
    for(var i = ; i < this.fingers.length; i++) {
    if (this.fingers.id === id) {
    foundFinger = this.fingers;
    break;
    }
    }

    return foundFinger;
    };

    /* last touch.x and .y will not work with multi-touch. Need to override. */
    TouchInput.isTouchingArea = function(rect) {
    if (this.fingers === null) return false;

    for (var i = ; i < this.fingers.length; i++) {
    var finger = this.fingers;
    if (pDebugging) {
    var strOut = "Finger at: " + finger._x + "; " + finger._y + " vs. touch area: " + rect.x + "; " + rect.y;
    console.log(strOut);
    }

    if (rect.contains(finger._x, finger._y)) {
    return true;
    }
    }

    return false;
    };

    /* last touch.x and .y used only for mouse click. Required function, mouse input was non-functional.*/
    TouchInput.isClicked = function(rect) {
    if ( !this.isPressed() ) return false;

    if (pDebugging) {
    var strOut = "Click at: " + this._x + "; " + this._y + " vs. click area: " + rect.x + "; " + rect.y;
    console.log(strOut);
    }

    if (rect.contains(this._x, this._y)) {
    return true;
    }

    return false;
    };

    /* Input check for directional swipe */
    TouchInput.swipedLeft = function() {
    var swipedLeft = this._history.wasGestured('swipe left');
    return swipedLeft;
    };

    /* Input check for directional swipe */
    TouchInput.swipedRight = function() {
    var swipedRight = this._history.wasGestured('swipe right');
    return swipedRight;
    };

    /* Input check for directional swipe */
    TouchInput.swipedUp = function() {
    var swipedUp = this._history.wasGestured('swipe up');
    return swipedUp;
    };

    /* Input check for directional swipe */
    TouchInput.swipedDown = function() {
    var swipedDown = this._history.wasGestured('swipe down');
    return swipedDown;
    };

    /**
    * @static
    * @method _onSwipeLeft
    * @private
    */
    TouchInput._onSwipedLeft = function() {
    this._history.addGesture('swipe left');
    this._events.swipedLeft = true;
    if (pDebugging) {
    console.log("Left swipe detected!");
    }
    };

    /**
    * @static
    * @method _onSwipeRight
    * @private
    */
    TouchInput._onSwipedRight = function() {
    this._history.addGesture('swipe right');
    this._events.swipedRight = true;
    if (pDebugging) {
    console.log("Right swipe detected!");
    }
    };

    /**
    * @static
    * @method _onSwipeUp
    * @private
    */
    TouchInput._onSwipedUp = function() {
    this._history.addGesture('swipe up');
    this._events.swipedUp = true;
    if (pDebugging) {
    console.log("Up swipe detected!");
    }
    };

    /**
    * @static
    * @method _onSwipeDown
    * @private
    */
    TouchInput._onSwipedDown = function() {
    this._history.addGesture('swipe down');
    this._events.swipedDown = true;
    if (pDebugging) {
    console.log("Down swipe detected!");
    }
    };

    TouchInput.prototype.isGestured = function(gest) {
    var gestSafe = gest.toLowerCase();
    var pressed = this._history.wasGestured(gestSafe);
    if (pressed) {
    if (pDebugging) console.log(gestSafe);
    this._history.removeGesture(gestSafe);
    }

    return pressed;
    };

    /* Adds button to input history if not already in input history. */
    TouchInput.addLastButton = function(btn) {
    this._history.addBtn(btn);
    };

    /* Clears input history. */
    TouchInput._clearHistory = function() {
    this._history._clear();
    };

    //=============================================================================
    // Touch Input Core Overrides
    //=============================================================================

    /**
    * Initializes the touch system.
    *
    * @static
    * @method initialize
    */
    var BBS_MC_TouchInputInitialize = TouchInput.initialize;
    TouchInput.initialize = function() {
    BBS_MC_TouchInputInitialize.call(this, arguments);
    this._history = new InputHistory();
    this.fingers = null;
    };

    /**
    * @static
    * @method _onTouchStart
    * @param {TouchEvent} event
    * @private
    */
    // Replaces core functionality. Do not modify!
    TouchInput._onTouchStart = function(event) {
    this.fingersDestroy();
    this.fingersRestart(event.touches);
    if (window.cordova || window.navigator.standalone) {
    event.preventDefault();
    }
    };

    /**
    * @static
    * @method _onTouchMove
    * @param {TouchEvent} event
    * @private
    */
    // Replaces core functionality. Do not modify!
    TouchInput._onTouchMove = function(event) {
    // Each changed touch for this event represents a moved touch.
    // Direct iteration significantly faster than foreach.
    for (var i = ; i < event.changedTouches.length; i++) {
    var touch = event.changedTouches;
    var finger = this.getFinger(touch.identifier);
    if( !finger ) {
    return;
    }

    var x = Graphics.pageToCanvasX(touch.pageX);
    var y = Graphics.pageToCanvasY(touch.pageY);
    finger._x = x;
    finger._y = y;

    this._onMove(x, y);
    }
    };

    /**
    * @static
    * @method _onTouchEnd
    * @param {TouchEvent} event
    * @private
    */
    // Replaces core functionality. Do not modify!
    TouchInput._onTouchEnd = function(event) {
    this.fingersDestroy();

    var remainingTouches = [];
    // Direct iteration significantly faster than foreach.
    for (var i = ; i < event.touches.length; i++) {
    var unChanged = true;

    // Each changed touch for this event represents a released touch.
    for (var j = ; j < event.changedTouches.length; j++) {
    if (event.changedTouches[j].identifier === event.touches.identifier) {
    unChanged = false;
    break;
    }
    }

    if (unChanged) {
    remainingTouches.push(event.touches);
    }
    }

    this.fingersRestart(remainingTouches);
    event.preventDefault();
    };

    /**
    * Clears all the touch data.
    *
    * @static
    * @method clear
    */
    var BBS_MC_TouchInputclear = TouchInput.clear;
    TouchInput.clear = function() {
    BBS_MC_TouchInputclear.call(this);
    this._events.swipedLeft = false;
    this._events.swipedRight = false;
    this._events.swipedUp = false;
    this._events.swipedDown = false;
    this._swipedLeft = false;
    this._swipedRight = false;
    this._swipedUp = false;
    this._swipedDown = false;
    this.fingersDestroy();
    };

    /**
    * Updates the touch data.
    *
    * @static
    * @method update
    */
    var BBS_MC_TouchInputupdate = TouchInput.update;
    TouchInput.update = function() {
    BBS_MC_TouchInputupdate.call(this);
    this._swipedLeft = this._events.swipedLeft;
    this._swipedRight = this._events.swipedRight;
    this._swipedUp = this._events.swipedUp;
    this._swipedDown = this._events.swipedDown;
    this._events.swipedLeft = false;
    this._events.swipedRight = false;
    this._events.swipedUp = false;
    this._events.swipedDown = false;
    };

    })(BBS.MultiTouch);
    //=============================================================================
    // End of File
    //=============================================================================




     


    Known Bugs / TODO


    - If feature requested, extend plugin to work with all scenes.


    - If feature requested, extend plugin to work with more gestures (one gesture per version).


    - Checking with major script developers to ensure compatibility with their plugins.


    - Make your suggestions or report bugs here!


     


    History


     

    v1.02


      - fixed issue where multi-touch being enabled on its own would fail to accept map touch input.


    v1.01


      - multi-touch fixed to still work with mouse.  Click and touch event handling separated.
    v1.00



      - multi-touch seperated into own script to facilitate further growth.

    Suggestions, bug reports, and feature requests are welcomed!


     


    Compatibility Issues


    None known.  Compatible with Klaus Map Overlays, Terrax Lighting System, and most (if not all) of Yanfly's plugins.


     


    FAQ


    Q: The demo won't open, how to open it?
    A: Download Winrar and try again.


     


    Credit and Thanks
    - Micheal Morris @Blue Booth Studios
    - Credit to Tsukihime for continuing to be so helpful!


    - Jairajs89 - Touchy.js, which serves as basis for some multi-touch functionality.


    - Ramza, Lakaroth and friends for all their help testing multi-touch.


     


    Author's Notes
    Free for non-commercial usage as long as credit is given, contact me for commercial use.


    View attachment BBS_MultiTouch.js
     
    Last edited by a moderator: Apr 21, 2016
    #1
  2. Lidnel

    Lidnel Warper Member

    Messages:
    3
    Likes Received:
    2
    First Language:
    Ukrainian
    Hello. Maybe I did something wrong but it just stuns movement. I tested in a pure project on android tablet by usb run. Really interested in your work, thanks
     
    #2
    bluebooth likes this.
  3. bluebooth

    bluebooth Veteran Veteran

    Messages:
    95
    Likes Received:
    115
    First Language:
    English
    Primarily Uses:
    N/A


    Hey, Lidnel, sorry to hear you're having trouble.  Can you expand on what you mean by "stuns movement"?  How is your project setup?
     
    #3
  4. Lidnel

    Lidnel Warper Member

    Messages:
    3
    Likes Received:
    2
    First Language:
    Ukrainian
    When I touch screen on MAP scene nothing happens, menu works
     
    #4
  5. bluebooth

    bluebooth Veteran Veteran

    Messages:
    95
    Likes Received:
    115
    First Language:
    English
    Primarily Uses:
    N/A


    I'll take a look into that, thank you.  Does it cause any issues in combat, too?
     
    #5
  6. Lidnel

    Lidnel Warper Member

    Messages:
    3
    Likes Received:
    2
    First Language:
    Ukrainian
    Sorry, I don't use combat in my game. I can test it later.



    Standard new project without plugins etc.
     
    #6
    bluebooth likes this.
  7. bluebooth

    bluebooth Veteran Veteran

    Messages:
    95
    Likes Received:
    115
    First Language:
    English
    Primarily Uses:
    N/A


    Sounds good.  Did you change any of the default plugin values for multi-touch and if so, to what?


    The issue is likely a conflict between the way default RMMV stores map touch as a single x, y value, whereas multi-touch uses an array of values (it must by nature).  I'm in the middle of busy week at university, but I'll take a look at the plugin to correct this really soon.


    UPDATE: Still crazy busy, but I've been able to replicate your issue.  Will have a fix within the next two weeks (mainly dependent on University, I have had a major deadline every 24 hours there for the past two weeks, and I still have to survive until the end of the month before projects are all over).


    EDIT: That moment when it turns out you forgot to set InputTouch.isTouched to true when a finger gesture occurs.  I've fixed the issue, and will be posting an update now.  Sorry it took a while, @Lidnel!
     
    Last edited by a moderator: Apr 21, 2016
    #7
    Lidnel likes this.
  8. TenTranVN

    TenTranVN Veteran Veteran

    Messages:
    125
    Likes Received:
    16
    First Language:
    Vietnam
    Primarily Uses:
    RMMV
    Support Webbrowser ?, MV 1.5.1 ?
     
    #8

Share This Page