Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
LilDrippyMyFnf
GitHub Repository: LilDrippyMyFnf/FNF-BitEnigne
Path: blob/master/Source/android/flixel/FlxJoyStick.hx
2174 views
package android.flixel;

import flixel.FlxG;
import flixel.FlxSprite;
import flixel.group.FlxSpriteGroup;
import flixel.input.touch.FlxTouch;
import flixel.math.FlxAngle;
import flixel.math.FlxMath;
import flixel.math.FlxPoint;
import flixel.math.FlxRect;
import flixel.graphics.FlxGraphic;
import flixel.util.FlxDestroyUtil;
import flixel.graphics.frames.FlxAtlasFrames;
import openfl.utils.Assets;

/**
 * A virtual thumbstick - useful for input on mobile devices.
 *
 * @author Ka Wing Chin
 * @modification author Saw (M.A. Jigsaw) to work only with touch and to use custom assets
 */
class FlxJoyStick extends FlxSpriteGroup
{
	/**
	 * This function is called when the button is released.
	 */
	public var onUp:Void->Void;

	/**
	 * This function is called when the button is pressed down.
	 */
	public var onDown:Void->Void;

	/**
	 * This function is called when the touch goes over the button.
	 */
	public var onOver:Void->Void;

	/**
	 * This function is called when the button is hold down.
	 */
	public var onPressed:Void->Void;

	/**
	 * Used with public variable status, means not highlighted or pressed.
	 */
	static inline var NORMAL:Int = 0;

	/**
	 * Used with public variable status, means highlighted (usually from touch over).
	 */
	static inline var HIGHLIGHT:Int = 1;

	/**
	 * Used with public variable status, means pressed (usually from touch click).
	 */
	static inline var PRESSED:Int = 2;

	/**
	 * Shows the current state of the button.
	 */
	public var status:Int = NORMAL;

	/**
	 * The background of the joystick, also known as the base.
	 */
	public var base:FlxSprite;

	public var thumb:FlxSprite;

	/**
	 * A list of analogs that are currently active.
	 */
	static var _analogs:Array<FlxJoyStick> = [];

	/**
	 * The current pointer that's active on the analog.
	 */
	var _currentTouch:FlxTouch;

	/**
	 * Helper array for checking touches
	 */
	var _tempTouches:Array<FlxTouch> = [];

	/**
	 * The area which the joystick will react.
	 */
	var _zone:FlxRect = FlxRect.get();

	/**
	 * The radius in which the stick can move.
	 */
	var _radius:Float = 0;

	public var _direction:Float = 0;
	public var _amount:Float = 0;

	/**
	 * The speed of easing when the thumb is released.
	 */
	var _ease:Float;

	/**
	 * Create a virtual thumbstick - useful for input on android devices.
	 *
	 * @param   X            The X-coordinate of the point in space.
	 * @param   Y            The Y-coordinate of the point in space.
	 * @param   Radius       The radius where the thumb can move. If 0, half the base's width will be used.
	 * @param   Ease         Used to smoothly back thumb to center. Must be between 0 and (FlxG.updateFrameRate / 60).
	 */
	public function new(X:Float = 0, Y:Float = 0, Radius:Float = 0, Ease:Float = 0.25)
	{
		super(X, Y);

		_radius = Radius;
		_ease = FlxMath.bound(Ease, 0, 60 / FlxG.updateFramerate);

		_analogs.push(this);

		_point = FlxPoint.get();

		createBase();
		createThumb();
		createZone();

		scrollFactor.set();
		moves = false;
	}

	/**
	 * Creates the background of the analog stick.
	 */
	function createBase():Void
	{
		base = new FlxSprite(0,
			0).loadGraphic(FlxGraphic.fromFrame(FlxAtlasFrames.fromSparrow(Assets.getBitmapData('assets/android/joystick.png'),
				Assets.getText('assets/android/joystick.xml'))
				.getByName('base')));
		base.resetSizeFromFrame();
		base.x += -base.width * 0.5;
		base.y += -base.height * 0.5;
		base.scrollFactor.set();
		base.solid = false;
		base.immovable = true;
		base.alpha = AndroidControls.getOpacity();
		#if FLX_DEBUG
		base.ignoreDrawDebug = true;
		#end
		add(base);
	}

	/**
	 * Creates the thumb of the analog stick.
	 */
	function createThumb():Void
	{
		thumb = new FlxSprite(0,
			0).loadGraphic(FlxGraphic.fromFrame(FlxAtlasFrames.fromSparrow(Assets.getBitmapData('assets/android/joystick.png'),
				Assets.getText('assets/android/joystick.xml'))
				.getByName('thumb')));
		thumb.resetSizeFromFrame();
		thumb.x += -thumb.width * 0.5;
		thumb.y += -thumb.height * 0.5;
		thumb.scrollFactor.set();
		thumb.solid = false;
		thumb.immovable = true;
		thumb.alpha = AndroidControls.getOpacity();
		#if FLX_DEBUG
		thumb.ignoreDrawDebug = true;
		#end
		add(thumb);
	}

	/**
	 * Creates the touch zone. It's based on the size of the background.
	 * The thumb will react when the touch is in the zone.
	 */
	public function createZone():Void
	{
		if (base != null && _radius == 0)
			_radius = base.width * 0.5;

		_zone.set(x - _radius, y - _radius, 2 * _radius, 2 * _radius);
	}

	/**
	 * Clean up memory.
	 */
	override public function destroy():Void
	{
		super.destroy();

		_zone = FlxDestroyUtil.put(_zone);

		_analogs.remove(this);
		onUp = null;
		onDown = null;
		onOver = null;
		onPressed = null;
		thumb = null;
		base = null;

		_currentTouch = null;
		_tempTouches = null;
	}

	/**
	 * Update the behavior.
	 */
	override public function update(elapsed:Float):Void
	{
		var offAll:Bool = true;

		// There is no reason to get into the loop if their is already a pointer on the analog
		if (_currentTouch != null)
			_tempTouches.push(_currentTouch);
		else
		{
			for (touch in FlxG.touches.list)
			{
				var touchInserted:Bool = false;

				for (analog in _analogs)
				{
					// Check whether the pointer is already taken by another analog.
					// TODO: check this place. This line was 'if (analog != this && analog._currentTouch != touch && touchInserted == false)'
					if (analog == this && analog._currentTouch != touch && !touchInserted)
					{
						_tempTouches.push(touch);
						touchInserted = true;
					}
				}
			}
		}

		for (touch in _tempTouches)
		{
			_point.set(touch.screenX, touch.screenY);

			if (!updateAnalog(_point, touch.pressed, touch.justPressed, touch.justReleased, touch))
			{
				offAll = false;
				break;
			}
		}

		if ((status == HIGHLIGHT || status == NORMAL) && _amount != 0)
		{
			_amount -= _amount * _ease * FlxG.updateFramerate / 60;

			if (Math.abs(_amount) < 0.1)
			{
				_amount = 0;
				_direction = 0;
			}
		}

		thumb.x = x + Math.cos(_direction) * _amount * _radius - (thumb.width * 0.5);
		thumb.y = y + Math.sin(_direction) * _amount * _radius - (thumb.height * 0.5);

		if (offAll)
			status = NORMAL;

		_tempTouches.splice(0, _tempTouches.length);

		super.update(elapsed);
	}

	function updateAnalog(TouchPoint:FlxPoint, Pressed:Bool, JustPressed:Bool, JustReleased:Bool, Touch:FlxTouch):Bool
	{
		var offAll:Bool = true;

		if (_zone.containsPoint(TouchPoint) || (status == PRESSED))
		{
			offAll = false;

			if (Pressed)
			{
				if (Touch != null)
					_currentTouch = Touch;

				status = PRESSED;

				if (JustPressed && onDown != null)
					onDown();

				if (status == PRESSED)
				{
					if (onPressed != null)
						onPressed();

					var dx:Float = TouchPoint.x - x;
					var dy:Float = TouchPoint.y - y;

					var dist:Float = Math.sqrt(dx * dx + dy * dy);

					if (dist < 1)
						dist = 0;

					_direction = Math.atan2(dy, dx);
					_amount = Math.min(_radius, dist) / _radius;

					acceleration.x = Math.cos(_direction) * _amount;
					acceleration.y = Math.sin(_direction) * _amount;
				}
			}
			else if (JustReleased && status == PRESSED)
			{
				_currentTouch = null;

				status = HIGHLIGHT;

				if (onUp != null)
					onUp();

				acceleration.set();
			}

			if (status == NORMAL)
			{
				status = HIGHLIGHT;

				if (onOver != null)
					onOver();
			}
		}

		return offAll;
	}

	/**
	 * Returns the angle in degrees.
	 */
	public function getAngle():Float
		return _direction * FlxAngle.TO_DEG;

	/**
	 * Whether the thumb is pressed or not.
	 */
	public var pressed(get, never):Bool;

	inline function get_pressed():Bool
		return status == PRESSED;

	/**
	 * Whether the thumb is just pressed or not.
	 */
	public var justPressed(get, never):Bool;

	function get_justPressed():Bool
	{
		if (_currentTouch != null)
			return _currentTouch.justPressed && status == PRESSED;

		return false;
	}

	/**
	 * Whether the thumb is just released or not.
	 */
	public var justReleased(get, never):Bool;

	function get_justReleased():Bool
	{
		if (_currentTouch != null)
			return _currentTouch.justReleased && status == HIGHLIGHT;

		return false;
	}

	override public function set_x(X:Float):Float
	{
		super.set_x(X);
		createZone();

		return X;
	}

	override public function set_y(Y:Float):Float
	{
		super.set_y(Y);
		createZone();

		return Y;
	}
}