Reference Source

src/controller.mjs

import { Tween } from './vendor/tween.min.mjs';
import { Utils } from './vendor/utils.min.mjs';
import { MobileHandler } from './mobile-handler.mjs'

/**
 * When using a controller that is of the traversal type, or the static type
 * a zone is REQUIRED. Only two controllers of traversal or static or one of each can be used at a time. 
 * These controllers have to have opposing zones. Left | Right
 * Traversal: spawns at the touch position and when dragged, follows the finger across the screen
 * Static: spawns at the touch position, cannot move from that location, just updates the joystick and will clamp at its limit
 * Stationary: cannot move from it's position at all, will just update the joystick and clamp it at its limit, can be pressed from anywhere on screen.
 */
export class Controller {
	/**
	 * The maximum layer
	 * @private
	 * @type {number}
	 */
	static MAX_LAYER = 1999998;
    /**
     * A reference to the tween instance this controller uses to tween the alpha when it becomes inactive.
     * This reference is so that developers can pause this tween and resume it when needed.
     * Calling this.tween.pause() and this.tween.resume() for instance when pausing and unpausing the game.
	 * @private
	 * @type {Tween}
     */
    tween = new Tween();
	/**
	 * The joyring element of this controller.
	 * @private
	 * @type {Object}
	 */
	joyring = null;
	/**
	 * The joystick element of this controller.
	 * @private
	 * @type {Object}
	 */
	joystick = null;
	/**
	 * The version of the module.
	 */
	version = "VERSION_REPLACE_ME";
	/**
	 * The options of this controller.
	 * @private
     * @property {Object} options - The options of this controller.
     * @property {string} options.type - The way this controller will behave. stationary | traversal | static.
     * @property {number} options.size - The width/height of the joystick. The width & height of the joystick should be the same.
     * @property {Object} options.position - The initial position of the joystick.
     * @property {string} options.position.x - The initial x position of the joystick.
     * @property {string} options.position.y - The initial y position of the joystick.
     * @property {string} options.lockedDimension - The locked dimension of the joystick. both | vertical | horizontal. This is used to lock the joystick from moving in certain dimensions. If this joystick's type is traversal it cannot be locked.
     * @property {string} options.zone - The zone the joystick will occupy. If there is already a controller of the traversal or static type, then you must use a zone. If there is only one controller no zone is needed. left | right This will give each controller equal space on the left | right sides of the screen.
     * @property {number} options.inactiveAlpha - The alpha value the joystick will be when it is considered to be inactive.
     * @property {number} options.transitionTime - How long it takes in ms to transition to the inactiveAlpha value.
     * @property {number} options.scale - The scale you want the joystick controller to be.
     * @property {number} options.plane - The plane of the joystick controller.
     * @property {number} options.layer - The layer of the joystick controller.
     * @property {string} options.atlasName - The atlasName of the joystick.
     * @property {string} options.joystickIconName - The iconName of the joystick.
     * @property {string} options.joyringIconName - The iconName of the joyring.
     * @property {Object} options.callback - An object holding options callbacks to attach to events the joystick emits. 
     * @property {Function} options.callback.onTouchBegin - Callback to be called when the joystick is touched after being released.
     * @property {Function} options.callback.onRelease - Callback to be called when the joystick is released and no longer held.
     * @property {Function} options.callback.onMove - Callback to be called when the joystick is moved.
	 * @type {Object}
	 */
	options = null;
	/**
	 * The locked dimension of the joystick. both | vertical | horizontal. This is used to lock the joystick from moving in certain dimensions. If this joystick's type is traversal it cannot be locked.
	 * @private
	 * @type {string}
	 */
	lockedDimension = null;
	/**
	 * The zone the joystick will occupy. If there is already a controller of the traversal or static type, then you must use a zone. If there is only one controller no zone is needed. left | right This will give each controller equal space on the left | right sides of the screen.
	 * @private
	 * @type {string}
	 */
	zone = null;
	/**
	 * Whether this controller is active in zone. e.g the controller is zoned to the left, and the user taps on the left side of the screen.
	 * @private
	 * @type {boolean}
	 */
	activeInZone = false;
	/**
	 * The fingerID that is controlling the joystick.
	 * @private
	 * @type {number}
	 */
	controllingFinger = null;
	/**
	 * Whether this controller is active
	 * @private
	 * @type {boolean}
	 */
	active = false;
	/**
	 * Event function for when the joystick is released.
	 * @private
	 * @type {Function}
	 */
	onRelease = null;
	/**
	 * Event function for when the joystick is moved.
	 * @private
	 * @type {Function}
	 */
	onMove = null;
	/**
	 * @private
	 */
	constructor(pOptions) {
		this.build(pOptions);
	}
    /**
     * @private
     * Setup the controller with the options that this controller class instance has from being initiated
     */
	setup() {
        // Setup the joyring element
		this.joyring.atlasName = this.options.atlasName;
		this.joyring.iconName = this.options.joyringIconName;
		this.joyring.touchOpacity = MobileHandler.constructor.MULTI_TOUCH;
		this.joyring.interfaceType = 'default';
		this.joyring.color = { 'tint': 0xFFFFFF };
		this.joyring.scale = 1;
		this.joyring.anchor = { 'x': 0.5, 'y': 0.5 };
		this.joyring.plane = this.options.plane;
		this.joyring.layer = this.options.layer;
		this.joyring.width = this.options.size;
		this.joyring.height = this.options.size;
		this.joyring.halfSize = this.joyring.width / 2;
		this.joyring.child = this.joystick;
		this.joyring.isMobileHandlerController = true;
		this.joyring.originalPos = { 'x': this.options.position.x, 'y': this.options.position.y };
        // Setup the joystick element
		this.joystick.atlasName = this.options.atlasName;
		this.joystick.iconName = this.options.joystickIconName;
		this.joystick.touchOpacity = MobileHandler.constructor.NO_TOUCH;
		this.joystick.interfaceType = 'default';
		this.joystick.color = { 'tint': 0xFFFFFF };
		this.joystick.scale = 1;
		this.joystick.anchor = { 'x': 0.5, 'y': 0.5 };
		this.joystick.startPos = { 'x': 0, 'y': 0 };
		this.joystick.width = this.options.size / 2;
		this.joystick.height = this.options.size / 2;
		this.joystick.plane = this.options.plane;
        // The inner ring must be layered above the outer ring
		this.joystick.layer = this.options.layer + 1;
		this.joystick.anglePoint = 0;
		this.joystick.halfSize = this.joystick.width / 2;
		this.joystick.parent = this.joyring;
		this.joystick.originalPos = { 'x': this.joyring.originalPos.x + this.joystick.halfSize, 'y': this.joyring.originalPos.y + this.joystick.halfSize };
        // Keep references to this joystick
		this.joyring.controller = this;
		this.joystick.controller = this;
        // Assign event funcs
		this.joyring.onTouchBegin = this.options.callback.onTouchBegin;
		this.onRelease = this.options.callback.onRelease;
		this.onMove = this.options.callback.onMove;
        // Assign locked status
		this.lockedDimension = this.options.lockedDimension;
		this.lock(this.lockedDimension);
        // Create the joystick and joyring elements
		VYLO.Client.addInterfaceElement(this.joyring, MobileHandler.interfaceHandle, this.joyring.id);
		VYLO.Client.addInterfaceElement(this.joystick, MobileHandler.interfaceHandle, this.joystick.id);
		// Show interface
		if (!VYLO.Client.checkInterfaceShown(MobileHandler.interfaceHandle)) {
			VYLO.Client.showInterface(this.interfaceHandle);
		}
        // Track this controller
		MobileHandler.activeControllers.push(this);
		this.show();
	}
    /**
     * Tweens the controller to it's inactive alpha preset, or from it's inactive value preset to full alpha.
     * @private
     * @param {boolean} pFade - Whether to fade the joystick to it's inactive alpha preset
     */
	handleTransition(pFade) {
        let start = { 'alpha': this.joyring.alpha };
        let end;
        const duration = this.options.transitionTime;
        const easing = Tween.easeInOutQuad;
        
		if (this.options.inactiveAlpha || this.options.inactiveAlpha === 0) {
            // Stop any ongoing tween animation
            this.tween.stop();
			// Determine which direction the fade should go in
			end = pFade ? { 'alpha': this.options.inactiveAlpha } : { 'alpha': 1 };
            // Makes no sense to tween a animation if the start and end value is the same
            if (start.alpha === end.alpha) return;
            // Animate the transition
            this.tween.build({
                start,
                end,
                duration,
                easing
            }).animate(({ alpha }) => {
                this.joyring.alpha = alpha;
                this.joystick.alpha = alpha;
            });
		}
	}
    /**
     * Resets the joystick to default.
     * @private
     * @param {*} pSoft - Softly resets the joystick in the event it is hidden
     */
	reset(pSoft) {
		const angle = this.joystick.anglePoint;
		this.joystick.alpha = this.options.inactiveAlpha;
		this.joystick.startPos.x = this.joystick.startPos.y = 0;
		this.joystick.anglePoint = 0;
		this.joyring.layer = this.options.layer;
		this.joystick.layer = this.options.layer + 10;
		this.activeInZone = false;
		this.controllingFinger = null;

		if (this.active) {
			this.active = false;
			if (typeof(this.onRelease) === 'function') {
				this.onRelease(VYLO.Client, angle);
			}
		}
		if (!pSoft) {
			this.onRelease = null;
			this.onMove = null;
			this.joyring.onTouchBegin = null;
			if (this.zone) {
				if (MobileHandler.reservedScreenZones.includes(this.zone)) {
					MobileHandler.reservedScreenZones.splice(MobileHandler.reservedScreenZones.indexOf(this.zone), 1);
				}
				if (this.zone === 'left' || this.zone === 'right') {
					MobileHandler.zonedControllers[this.zone] = null;
				}
			}
			this.zone = null;
			this.lockedDimension = null;
			this.options = {};
			if (MobileHandler.touchedInstances.includes(this.joyring)) {
				MobileHandler.touchedInstances.splice(MobileHandler.touchedInstances.indexOf(this.joyring), 1);
			}
		}
	}
    /**
     * Builds this controller with the options that were passed in.
     * @param {Object} pOptions - The options of this controller.
     * @param {string} pOptions.type - The way this controller will behave. stationary | traversal | static.
     * @param {number} pOptions.size - The width/height of the joystick. The width & height of the joystick should be the same. The inner ring will be 50% of this size.
     * @param {Object} pOptions.position - The initial position of the joystick.
     * @param {string} pOptions.position.x - The initial x position of the joystick.
     * @param {string} pOptions.position.y - The initial y position of the joystick.
     * @param {string} pOptions.lockedDimension - The locked dimension of the joystick. both | vertical | horizontal. This is used to lock the joystick from moving in certain dimensions. If this joystick's type is traversal it cannot be locked.
     * @param {string} pOptions.zone - The zone the joystick will occupy. If there is already a controller of the traversal or static type, then you must use a zone. If there is only one controller no zone is needed. left | right This will give each controller equal space on the left / right sides of the screen.
     * @param {number} pOptions.inactiveAlpha - The alpha value the joystick will be when it is considered to be inactive.
     * @param {number} pOptions.transitionTime - How long it takes in ms to transition to the inactiveAlpha value.
     * @param {number} pOptions.scale - The scale you want the joystick controller to be.
     * @param {number} pOptions.plane - The plane of the joystick controller.
     * @param {number} pOptions.layer - The layer of the joystick controller.
     * @param {string} pOptions.atlasName - The atlasName of the joystick.
     * @param {string} pOptions.joystickIconName - The iconName of the joystick.
     * @param {string} pOptions.joyringIconName - The iconName of the joyring.
     * @param {Object} pOptions.callback - An object holding options callbacks to attach to events the joystick emits. 
     * @param {Function} pOptions.callback.onTouchBegin - Callback to be called when the joystick is touched after being released.
     * @param {Function} pOptions.callback.onRelease - Callback to be called when the joystick is released and no longer held.
     * @param {Function} pOptions.callback.onMove - Callback to be called when the joystick is moved.
     */
	build(pOptions = { 'type': 'stationary', 'size': 100, 'position': { 'x': 100, 'y': 100 }, 'lockedDimension': null, 'zone': null, 'inactiveAlpha': 0.5, 'transitionTime': 500, 'scale': 1, 'plane': 1, 'layer': 1, 'atlasName': '', 'joystickIconName': '', 'joyringIconName': '', 'callback': { 'onTouchBegin': null, 'onRelease': null, 'onMove': null } }) {
		if (!this.joyring && !this.joystick) {
			const joyring = VYLO.newDiob('Interface');
			const joystick = VYLO.newDiob('Interface');
			this.joyring = joyring;
			this.joystick = joystick;
		}
		if (!Number.isInteger(pOptions.size)) {
			pOptions.size = 100;
			MobileHandler.logger.prefix('MobileHandler-Module').warn('pOptions.size was not an integer. Default value of 100 has been used.');
		}

		// the type of the controller
		if (typeof(pOptions.type) === 'string') {
			if (pOptions.type !== 'traversal' && pOptions.type !== 'static' && pOptions.type !== 'stationary') {
				pOptions.type = 'stationary';
			}
		} else {
			pOptions.type = 'stationary';
			MobileHandler.logger.prefix('MobileHandler-Module').warn('pOptions.type was not a string. Default value of stationary has been used.');
		}
		
		if (pOptions.type === 'traversal' || pOptions.type === 'static') {
			// if there is already a controller taking up a space, then you must use a zone. If there is no controller, then the entire screen is the zone
			// do not define a zone if you know this controller will be the only controller on screen
			if (MobileHandler.reservedScreenZones.length) {
				if (!pOptions.zone || typeof(pOptions.zone) !== 'string' || MobileHandler.reservedScreenZones.includes(pOptions.zone)) {
					MobileHandler.logger.prefix('MobileHandler-Module').error('When using a controller that is of the traversal type, or the static type. A zone is REQUIRED. Only two controllers of traversal or static or one of each can be used at a time. These controllers have to have opposing zones. Left | Right');
					return;
				}
			}
			if (pOptions.zone === 'left' || pOptions.zone === 'right') {
				MobileHandler.reservedScreenZones.push(pOptions.zone);
				MobileHandler.zonedControllers[pOptions.zone] = this;
				this.zone = pOptions.zone;
			}
		}

		if (typeof(pOptions.atlasName) !== 'string') {
			pOptions.atlasName = '';
			MobileHandler.logger.prefix('MobileHandler-Module').warn('pOptions.atlasName was not a string. No value has been used.');
		}

		// If the type is traversal it cannot be locked
		if (pOptions.lockedDimension) {
			if (pOptions.type === 'traversal') {
				MobileHandler.logger.prefix('MobileHandler-Module').warn('pOptions.type is traversal. A traversal controller cannot be locked.');
			} else if (typeof(pOptions.lockedDimension) !== 'string' || (pOptions.lockedDimension !== 'both' && pOptions.lockedDimension !== 'vertical' && pOptions.lockedDimension !== 'horizontal')) {
				pOptions.lockedDimension = null;
				MobileHandler.logger.prefix('MobileHandler-Module').warn('Invalid value for pOptions.lockedDimension has been passed. No value has been set.');
			}
		}

		if (typeof(pOptions.joystickIconName) !== 'string') {
			pOptions.joystickIconName = '';
			MobileHandler.logger.prefix('MobileHandler-Module').warn('pOptions.joystickIconName was not a string. No value has been used.');
		}

		if (typeof(pOptions.joyringIconName) !== 'string') {
			pOptions.joyringIconName = '';
			MobileHandler.logger.prefix('MobileHandler-Module').warn('pOptions.joyringIconName was not a string. No value has been used.');
		}

		// the plane this controller will use
		if (typeof(pOptions.transitionTime) !== 'number') {
			pOptions.transitionTime = 500;
		}

		if (typeof(pOptions.inactiveAlpha) !== 'number') {
			pOptions.inactiveAlpha = 0.5;
		}

		// Feature coming soon
		if (!Number.isInteger(pOptions.scale)) {
			pOptions.scale = 1;
			MobileHandler.logger.prefix('MobileHandler-Module').warn('A non integer value was found in option for scale. Default scale of 1 has been used.');
		}

		if (typeof(pOptions.plane) !== 'number') {
			pOptions.plane = 1;
		}

		if (typeof(pOptions.layer) !== 'number') {
			pOptions.layer = 1;
		}

		if (pOptions.position.constructor === Object) {
			if (!Number.isInteger(pOptions.position.x)) {
				pOptions.position.x = 100;
				MobileHandler.logger.prefix('MobileHandler-Module').warn('position.x variable found in options was not a number. Default position.x value has been used.');
			}
			if (!Number.isInteger(pOptions.position.y)) {
				pOptions.position.y = 100;
				MobileHandler.logger.prefix('MobileHandler-Module').warn('position.y variable found in options was not a number. Default position.y value has been used.');
			}
		} else {
			pOptions.position = { 'x': 100, 'y': 100 };
			MobileHandler.logger.prefix('MobileHandler-Module').warn('position variable found in options was not an object. Default position has been used.');
		}
		this.options = pOptions;
		this.setup();
	}
	/**
	 * Returns the type of this controller.
	 * @returns Returns the type of this controller.
	 */
	getType() {
		return this.options.type;
	}
    /**
     * Updates the controllers position with the latest information from touch events
     * @private
	 * @param {number} pX - The x position on the screen where the user tapped.
	 * @param {number} pY - The y position on the screen where the user tapped.
     * @param {boolean} pTouchStart - If this was the first time the joystick was touched.
     * @returns 
     */
	update(pX, pY, pTouchStart) {
		if (this.lockedDimension === 'both') return;
		if (pTouchStart) {
			this.handleTransition();
			this.joyring.layer = Controller.MAX_LAYER;
			this.joystick.layer = Controller.MAX_LAYER + 10;
			this.active = true;
		}
		if (this.active) {
			// traversal: spawns at the touch position and when dragged, follows the finger across the screen
			// static: spawns at the touch position, cannot move from that location, just updates the joystick and will clamp at its limit
			// stationary: cannot move from it's position at all, will just update the joystick and clamp it at its limit, can be pressed from anywhere on screen.

			const touchPos = { 'x': pX - this.joystick.halfSize, 'y': pY - this.joystick.halfSize };
			// Start position is always the center of the joyring
			let startPos;
			// Distance is how far away the joystick is from the start position
			let distance;
			// Angle is the angle in degrees from the start position to the touched position
			let angle;
			// ClampedDistance is the max distance allowed for the joystick to move
			let clampedDistance;
			// ClampedPos is the position that was clamped when the joystick tried to go past it's clampedDistance
			let clampedPos;

			if (this.options.type === 'stationary') {
				// This joystick is stationary therefore the start position is it's default position
				startPos = this.joystick.originalPos;
				// If a certain axis is locked, clamp that position to it's start position
				if (this.lockedDimension === 'horizontal') {
					touchPos.y = startPos.y;
				} else if (this.lockedDimension === 'vertical') {
					touchPos.x = startPos.x;
				}
				distance = Utils.getDistance(startPos, touchPos);
				angle = Utils.getAngle(startPos, touchPos);
				clampedDistance = Math.min(distance, this.joyring.halfSize);
				clampedPos = Utils.calculateNewPositionFromDistanceAndAngle(startPos, clampedDistance, angle);
				// Set the position to the clamped position so that it is locked to it's clampedPos
				this.joystick.setPos(clampedPos.x, clampedPos.y);
			} else if (this.options.type === 'traversal' || this.options.type === 'static') {
				// Position the joystick centered to the position of where the screen was touched if this is the first time touching the joystick
				if (pTouchStart) {
					this.joystick.startPos = touchPos;
					this.joyring.setPos(touchPos.x - this.joystick.halfSize, touchPos.y - this.joystick.halfSize);
					this.joystick.setPos(touchPos.x, touchPos.y);
					return;
				}

				if (this.options.type === 'traversal') {
					// The start position for traversal is wherever you pressed on the screen originally
					// The start position was set in the `pTouchStart` portion
					distance = Utils.getDistance(this.joystick.startPos, touchPos);
					angle = Utils.getAngle(this.joystick.startPos, touchPos);
					clampedDistance = Math.min(distance, this.joyring.halfSize);
					clampedPos = Utils.calculateNewPositionFromDistanceAndAngle(this.joystick.startPos, clampedDistance, angle);
					// Set the position to the position touched
					this.joystick.setPos(touchPos.x, touchPos.y);
					// Update the parent to follow the child after it is placed
					const parentAngle = Utils.getAngle(touchPos, this.joystick.startPos);
					const parentClampedPos = Utils.calculateNewPositionFromDistanceAndAngle(touchPos, clampedDistance, parentAngle);
					this.joyring.setPos(parentClampedPos.x - this.joystick.halfSize, parentClampedPos.y - this.joystick.halfSize);

					// If the distance is greater that the clamped position then we need to update the start position of the joystick
					if (distance > clampedDistance) {
						this.joystick.startPos.x = this.joyring.xPos + this.joystick.halfSize;
						this.joystick.startPos.y = this.joyring.yPos + this.joystick.halfSize;
					}
				} else if (this.options.type === 'static') {
					// The start position for static is wherever you pressed on the screen originally
					// The start position set in the `pTouchStart` portion
					// If a certain axis is locked, clamp that position to it's start position
					if (this.lockedDimension === 'horizontal') {
						touchPos.y = this.joystick.startPos.y;
					} else if (this.lockedDimension === 'vertical') {
						touchPos.x = this.joystick.startPos.x;
					}
					distance = Utils.getDistance(this.joystick.startPos, touchPos);
					angle = Utils.getAngle(this.joystick.startPos, touchPos);
					clampedDistance = Math.min(distance, this.joyring.halfSize);
					clampedPos = Utils.calculateNewPositionFromDistanceAndAngle(this.joystick.startPos, clampedDistance, angle);
					// Set the position to the clamped position so that it is locked
					this.joystick.setPos(clampedPos.x, clampedPos.y);
				}
			}

			// Set the angle point for reading (-1 is to convert it for use in an environment where up is down, and down is up)
			this.joystick.anglePoint = Utils.convertRaWAngleToVyloCoords(angle);
			// Calculate a threshold based on a percentage of the joystick's size
			const thresholdPercentage = 0.1; // Adjust this value based on your preference
			const centerThreshold = this.joyring.width * thresholdPercentage;
			// Check if the joystick is in the center
			const inCenter = (Math.abs(clampedDistance) < centerThreshold);
			// We need to get the starting position of the joystick, but in different types this can be different so we get the proper one based on the type.
			const startingPos = (this.options.type === 'stationary' ? startPos : this.joystick.startPos);
			// Get a normalized range of the position of the joystick to pass as analog values
			const normalizedX = Utils.normalizeRanged(startingPos.x, touchPos.x - this.joyring.halfSize, touchPos.x + this.joyring.halfSize);
			const normalizedY = Utils.normalizeRanged(startingPos.y, touchPos.y - this.joyring.halfSize, touchPos.y + this.joyring.halfSize);

			// Check if the joystick is in the center
			if (typeof(this.onMove) === 'function') {
				this.onMove(VYLO.Client, normalizedX, normalizedY, this.joystick.anglePoint, inCenter);
			}
		}
	}
    /**
     * API called when this joystick is released
     * @private
	 * @param {boolean} pForce - If this was a forced release. Called progamatically.
     */
	release(pForce) {
		if (this.lockedDimension === 'both') return;
		if (!pForce) {
			this.reset(true);
		}
        // Reset the position to the default position
		this.joyring.setPos(this.joyring.originalPos.x, this.joyring.originalPos.y);
		this.joystick.setPos(this.joystick.originalPos.x, this.joystick.originalPos.y);
		this.handleTransition(true);
	}
    /**
     * Locks a joystick from moving in a certain dimension or both
     * @param {string} pDimension - The dimension to lock. both | vertical | horizontal
     */
	lock(pDimension) {
        if (pDimension) {
            pDimension = pDimension.toLowerCase();
        }
		if (typeof(pDimension) === 'string') {
			if (pDimension === 'horizontal') {
				this.lockedDimension = 'horizontal';
			} else if (pDimension === 'vertical') {
				this.lockedDimension = 'vertical';
			} else {
				this.lockedDimension = 'both';
			}
		}
	}
    /**
     * Unlocks the joystick from being locked in the passed dimension.
     * @param {string} pDimension - The dimension to unlock. both | vertical | horizontal
     */
	unlock(pDimension) {
        if (pDimension) {
            pDimension = pDimension.toLowerCase();
        }
		if (typeof(pDimension) === 'string') {
			if (pDimension === 'horizontal') {
				if (this.lockedDimension === 'both') {
					this.lockedDimension = 'vertical';
				} else {
					this.lockedDimension = null;
				}
			} else if (pDimension === 'vertical') {
				if (this.lockedDimension === 'both') {
					this.lockedDimension = 'horizontal';
				} else {
					this.lockedDimension = null;
				}
			} else {
				this.lockedDimension = null;
			}
		}
	}
    /**
     * Returns the components that make up this controller. Which are the joystick element, and the joyring element.
     * @returns {Object} - An object containing references to the joytick and the joyring that makeup this controller.
     */
	getComponents() {
		return { 'joystick': this.joystick, 'joyring': this.joyring };
	}
    /**
     * Hides this controller
     */
	hide() {
		this.joyring.hide();
		this.joystick.hide();
		this.reset(true);
	}
    /**
     * Shows this controller
     */
	show() {
		this.joyring.setPos(this.joyring.originalPos.x, this.joyring.originalPos.y);
		this.joystick.setPos(this.joystick.originalPos.x, this.joystick.originalPos.y);
		this.joyring.show();
		this.joystick.show();
		this.handleTransition(true);
	}
}