Skip to content

Commit

Permalink
add FlxVirtualStick deprecate FlxAnalog
Browse files Browse the repository at this point in the history
  • Loading branch information
Geokureli committed Jan 22, 2025
1 parent 17262c0 commit a1c630c
Show file tree
Hide file tree
Showing 3 changed files with 246 additions and 69 deletions.
5 changes: 3 additions & 2 deletions flixel/ui/FlxAnalog.hx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package flixel.ui;

import flixel.FlxG;
import flixel.FlxSprite;
import flixel.group.FlxSpriteGroup;
import flixel.group.FlxSpriteContainer;
import flixel.input.touch.FlxTouch;
import flixel.math.FlxAngle;
import flixel.math.FlxMath;
Expand All @@ -16,7 +16,8 @@ import flixel.util.FlxDestroyUtil;
*
* @author Ka Wing Chin
*/
class FlxAnalog extends FlxSpriteGroup
@:deprecated("FlxAnalog is deprecated, use FlxVirtualStick or FlxVirtualPad, instead")
class FlxAnalog extends FlxSpriteContainer
{
/**
* Shows the current state of the button.
Expand Down
76 changes: 9 additions & 67 deletions flixel/ui/FlxVirtualPad.hx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package flixel.ui;

import flixel.ui.FlxAnalog;
import flixel.FlxG;
import flixel.graphics.frames.FlxTileFrames;
import flixel.group.FlxSpriteContainer;
Expand Down Expand Up @@ -94,6 +95,10 @@ class FlxVirtualPad extends FlxSpriteContainer
actions.y = height - actions.height;

y = FlxG.height - height;

#if FLX_DEBUG
this.ignoreDrawDebug = true;
#end
}

public function getButton(id:FlxVirtualInputID)
Expand All @@ -115,6 +120,10 @@ class FlxVirtualPadButtons extends FlxTypedSpriteContainer<FlxVirtualPadButton>
{
super(x, y);
scrollFactor.set();

#if FLX_DEBUG
this.ignoreDrawDebug = true;
#end
}

override public function destroy():Void
Expand Down Expand Up @@ -260,71 +269,4 @@ private class FlxVirtualInputIDTools
final frame = FlxAssets.getVirtualInputFrames().getByName(name);
return FlxTileFrames.fromFrame(frame, FlxPoint.get(44, 45));
}
}

class FlxVirtualStick extends FlxSpriteContainer
{
public static inline var TOP_RATIO = 1/3;

public var value:FlxReadOnlyPoint = FlxPoint.get();

final back:CircleSprite;
final top:CircleSprite;
var dragging = false;

public function new (x = 0.0, y = 0.0, radius = 60)
{
super(x, y);
add(back = new CircleSprite(0, 0, radius, 0x80ffffff));
final topRadius = Math.round(radius * TOP_RATIO);
add(top = new CircleSprite(0, 0, 20));
}

override function update(elapsed:Float)
{
super.update(elapsed);

#if FLX_MOUSE
final pos = FlxG.mouse.getViewPosition(getCameras()[0], cast value);
pos.subtract(back.x + back.radius, back.y + back.radius);

final mouseOver = pos.lengthSquared < back.radiusSquared;
if (FlxG.mouse.pressed && mouseOver)
dragging = true;

if (FlxG.mouse.released)
dragging = false;

if (dragging)
{
if (mouseOver)
pos.scale(1 / back.radius);
else
pos.normalize();
}
else
pos.zero();
#end

top.x = back.x + back.radius - top.radius + value.x * (back.radius - top.radius);
top.y = back.y + back.radius - top.radius + value.y * (back.radius - top.radius);
}
}

@:forward
abstract CircleSprite(FlxSprite) to FlxSprite
{
public var radius(get, never):Float;
public var radiusSquared(get, never):Float;

public function new (x = 0.0, y = 0.0, radius:Int, color = FlxColor.WHITE)
{
this = new FlxSprite(x, y);
this.makeGraphic(radius * 2, radius * 2, 0x0);

FlxSpriteUtil.drawCircle(this, -1, -1, -1, color);
}

inline function get_radius() return this.frameWidth * 0.5;
inline function get_radiusSquared() return radius * radius;
}
234 changes: 234 additions & 0 deletions flixel/ui/FlxVirtualStick.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
package flixel.ui;

import flixel.input.FlxPointer;
import flixel.util.FlxColor;
import flixel.FlxG;
import flixel.FlxSprite;
import flixel.group.FlxSpriteContainer;
import flixel.input.touch.FlxTouch;
import flixel.math.FlxAngle;
import flixel.math.FlxMath;
import flixel.math.FlxPoint;
import flixel.math.FlxRect;
import flixel.system.FlxAssets;
import flixel.ui.FlxButton;
import flixel.util.FlxDestroyUtil;
import flixel.util.FlxSignal;

/**
* A virtual thumbstick - useful for input on mobile devices.
*/
class FlxVirtualStick extends FlxSpriteContainer
{
/** The current state of the button */
public final value:FlxReadOnlyPoint = FlxPoint.get();

/** The top of the joystick, the part that moves */
public final thumb:CircleSprite;

/** The background of the joystick, also known as the base */
public final base:CircleSprite;

/** The radius in which the stick can move */
public final radius:Float;

/** The speed of easing when the thumb is released */
public var lerp:Float;

/** The minimum absolute value, to consider this input active */
public var deadzone = 0.0;

public var onMoveStart = new FlxSignal();
public var onMoveEnd = new FlxSignal();
public var onMove = new FlxSignal();

/** Used to track press events */
final button:InvisibleCircleButton;

var dragging = false;

/**
* Create a virtual thumbstick - useful for input on mobile devices.
*
* @param x The location in screen space.
* @param y The location in screen space.
* @param radius The radius where the thumb can move. If 0, half the base's width will be used.
* @param lerp Used to smoothly back thumb to center. Should be between 0 and 1.0.
* @param baseGraphic The graphic you want to display as base of the joystick.
* @param thumbGraphic The graphic you want to display as thumb of the joystick.
*/
public function new(x = 0.0, y = 0.0, radius = 0.0, lerp = 0.25, ?baseGraphic:FlxGraphicAsset, ?thumbGraphic:FlxGraphicAsset)
{
this.lerp = lerp;
super(x, y);

add(base = new CircleSprite(0, 0, baseGraphic, "base"));
add(thumb = new CircleSprite(0, 0, thumbGraphic, "thumb"));

if (radius <= 0)
radius = base.radius;
this.radius = radius;

base.x += radius;
base.y += radius;
thumb.x += radius;
thumb.y += radius;

add(button = new InvisibleCircleButton(0, 0, this.radius));

moves = false;
solid = false;

FlxG.watch.addFunction("stick.state", ()->button.status.toString());
FlxG.watch.addFunction("base.x|y", ()->'${base.x} | ${base.y}');
FlxG.watch.addFunction("thumb.x|y", ()->'${thumb.x} | ${thumb.y}');
}

override function destroy()
{
super.destroy();

thumb.destroy();
base.destroy();

onMoveStart.removeAll();
onMoveEnd.removeAll();
onMove.removeAll();
}

override function update(elapsed:Float)
{
super.update(elapsed);

updateValue(cast value);

final adjustedLerp = FlxMath.getFrameLerp(lerp, elapsed);
final newX = x + radius - thumb.radius + value.x * radius;
final newY = y + radius - thumb.radius + value.y * radius;
thumb.x += (newX - thumb.x) * adjustedLerp;
thumb.y += (newY - thumb.y) * adjustedLerp;
}

function updateValue(pos:FlxPoint)
{
final oldX = value.x;
final oldY = value.y;

#if FLX_MOUSE
if (button.justPressed)
{
onMoveStart.dispatch();
dragging = true;
}
else if (FlxG.mouse.justReleased)
{
onMoveEnd.dispatch();
dragging = false;
}

final pos:FlxPoint = cast value;
if (dragging)
{
button.calcDeltaToPointer(getCameras()[0], pos);
pos.scale(1 / radius);
if (pos.lengthSquared > 1.0)
pos.normalize();
}
else
pos.zero();
#end

if (value.x != oldX || value.y != oldY)
onMove.dispatch();
}
}

@:forward
@:forward.new
abstract CircleSprite(FlxSprite) to FlxSprite
{
public var radius(get, never):Float;
public var radiusSquared(get, never):Float;

public function new (centerX = 0.0, centerY = 0.0, graphic, ?backupId:String)
{
this = new FlxSprite(centerX, centerY, graphic);
if (graphic == null)
{
this.frames = FlxAssets.getVirtualInputFrames();
this.animation.frameName = backupId;
this.resetSizeFromFrame();
}
this.x -= radius;
this.y -= radius;
this.moves = false;

#if FLX_DEBUG
// this.ignoreDrawDebug = true;
#end
}

inline function get_radius() return this.frameWidth * 0.5;
inline function get_radiusSquared() return radius * radius;
}

class InvisibleCircleButton extends FlxTypedButton<FlxSprite>
{
public var radius(get, never):Float;
public var lastPointer(default, null):Null<FlxPointer>;

inline function get_radius():Float return frameWidth * 0.5;

public function new (x = 0.0, y = 0.0, radius:Float, ?onClick)
{
super(x, y, onClick);
final size = Math.ceil(radius * 2);
loadGraphic(FlxG.bitmap.create(size, size * 4, FlxColor.WHITE), true, size, size);
}

override function draw()
{
if (FlxG.debugger.drawDebug)
drawDebug();
}

override function checkInput(pointer, input, justPressedPosition, camera):Bool
{
if (super.checkInput(pointer, input, justPressedPosition, camera))
{
lastPointer = pointer;
return true;
}

return false;
}

override function overlapsPoint(point:FlxPoint, inScreenSpace = false, ?camera:FlxCamera):Bool
{
if (!inScreenSpace)
return point.distanceSquaredTo(x + radius, y + radius) < radius * radius;

if (camera == null)
camera = getCameras()[0];

return calcDeltaTo(point, camera, _point).lengthSquared < radius * radius;
}

public function calcDeltaTo(point:FlxPoint, camera:FlxCamera, ?result:FlxPoint)
{
if (result == null)
result = FlxPoint.get();

final xPos = point.x - camera.scroll.x - radius;
final yPos = point.y - camera.scroll.y - radius;
getScreenPosition(result, camera);
point.putWeak();
return result.subtract(xPos, yPos).negate();
}

public function calcDeltaToPointer(camera:FlxCamera, ?result:FlxPoint)
{
final point = lastPointer.getViewPosition(camera, FlxPoint.weak());
return calcDeltaTo(point, camera, result);
}
}

0 comments on commit a1c630c

Please sign in to comment.