MarcoFazioRandom / Virtual-Joystick-Godot

A simple virtual joystick for touchscreens, for both 2D and 3D games, with useful options.
MIT License
743 stars 81 forks source link

C# version of plugin #81

Open iamfobey opened 1 month ago

iamfobey commented 1 month ago

Hello! I converted plugin (main file haha) to C# version, because there is no C# versions of virtual joystick for mobile. And I fixed problem for 4.4dev3 version (removed get_viewport().set_input_as_handled()). I don't know is good way or not, but it solved my problem lol.

public partial class TouchScreenVirtualJoystick : Control
{
    public enum EJoystickMode
    {
        FIXED,
        DYNAMIC,
        FOLLOWING
    }

    public enum EVisibilityMode
    {
        ALWAYS,
        TOUCHSCREEN_ONLY,
        WHEN_TOUCHED
    }

    [Export]
    public Color PressedColor = Colors.Gray;

    [Export(PropertyHint.Range, "0, 200, 1")]
    public float DeadZoneSize = 10.0f;

    [Export(PropertyHint.Range, "0, 500, 1")]
    public float ClampZoneSize = 75.0f;

    [Export] 
    public EJoystickMode JoystickMode = EJoystickMode.FIXED;
    [Export]
    public EVisibilityMode VisibilityMode = EVisibilityMode.ALWAYS;

    [Export]
    public bool UseInputActions = true;
    [Export]
    public string ActionLeft = "ui_left";
    [Export]
    public string ActionRight = "ui_right";
    [Export]
    public string ActionUp = "ui_up";
    [Export]
    public string ActionDown = "ui_down";

    public bool IsPressed = false;
    public Vector2 InputDirection = Vector2.Zero;

    private int _touchIndex = -1;
    private TextureRect _base;
    private TextureRect _tip;
    private Vector2 _baseDefaultPosition;
    private Vector2 _tipDefaultPosition;
    private Color _defaultColor;

    public override void _Ready()
    {
        _base = GetNode<TextureRect>("Base");
        _tip = GetNode<TextureRect>("Base/Tip");
        _baseDefaultPosition = _base.Position;
        _tipDefaultPosition = _tip.Position;
        _defaultColor = _tip.Modulate;

        if (!DisplayServer.IsTouchscreenAvailable() && VisibilityMode == EVisibilityMode.TOUCHSCREEN_ONLY)
            Hide();

        if (VisibilityMode == EVisibilityMode.WHEN_TOUCHED)
            Hide();
    }

    public override void _Input(InputEvent @event)
    {
        if (@event is InputEventScreenTouch eventTouch)
        {
            if (eventTouch.Pressed)
            {
                if (IsPointInsideJoystickArea(eventTouch.Position) && _touchIndex == -1)
                {
                    if (JoystickMode == EJoystickMode.DYNAMIC || JoystickMode == EJoystickMode.FOLLOWING ||
                        (JoystickMode == EJoystickMode.FIXED && IsPointInsideBase(eventTouch.Position)))
                    {
                        if (JoystickMode == EJoystickMode.DYNAMIC || JoystickMode == EJoystickMode.FOLLOWING)
                            MoveBase(eventTouch.Position);

                        if (VisibilityMode == EVisibilityMode.WHEN_TOUCHED)
                            Show();

                        _touchIndex = eventTouch.Index;
                        _tip.Modulate = PressedColor;

                        UpdateJoystick(eventTouch.Position);
                    }
                }
            }
            else if (eventTouch.Index == _touchIndex)
            {
                ResetJoystick();
                if (VisibilityMode == EVisibilityMode.WHEN_TOUCHED)
                    Hide();
            }
        }
        else if (@event is InputEventScreenDrag eventDrag)
        {
            if (eventDrag.Index == _touchIndex)
                UpdateJoystick(eventDrag.Position);
        }
    }

    private void MoveBase(Vector2 newPosition)
    {
        _base.GlobalPosition = newPosition - (_base.PivotOffset * GetGlobalTransformWithCanvas().Scale);
    }

    private void MoveTip(Vector2 newPosition)
    {
        _tip.GlobalPosition = newPosition - (_tip.PivotOffset * _base.GetGlobalTransformWithCanvas().Scale);
    }

    private bool IsPointInsideJoystickArea(Vector2 point)
    {
        bool x = point.X >= GlobalPosition.X && point.X <= GlobalPosition.X + (Size.X * GetGlobalTransformWithCanvas().Scale.X);
        bool y = point.Y >= GlobalPosition.Y && point.Y <= GlobalPosition.Y + (Size.Y * GetGlobalTransformWithCanvas().Scale.Y);

        return x && y;
    }

    private Vector2 GetBaseRadius()
    {
        return _base.Size * _base.GetGlobalTransformWithCanvas().Scale / 2;
    }

    private bool IsPointInsideBase(Vector2 point)
    {
        var baseRadius = GetBaseRadius();
        var center = _base.GlobalPosition + baseRadius;

        return (point - center).LengthSquared() <= baseRadius.X * baseRadius.X;
    }

    private void UpdateJoystick(Vector2 touchPosition)
    {
        var baseRadius = GetBaseRadius();
        var center = _base.GlobalPosition + baseRadius;
        var vector = (touchPosition - center).LimitLength(ClampZoneSize);

        if (JoystickMode == EJoystickMode.FOLLOWING && touchPosition.DistanceTo(center) > ClampZoneSize)
            MoveBase(touchPosition - vector);

        MoveTip(center + vector);

        if (vector.LengthSquared() > DeadZoneSize * DeadZoneSize)
        {
            IsPressed = true;
            InputDirection = (vector - (vector.Normalized() * DeadZoneSize)) / (ClampZoneSize - DeadZoneSize);
        }
        else
        {
            IsPressed = false;
            InputDirection = Vector2.Zero;
        }

        if (UseInputActions)
            HandleInputActions();
    }

    private void HandleInputActions()
    {
        if (InputDirection.X >= 0 && Input.IsActionPressed(ActionLeft))
            Input.ActionRelease(ActionLeft);
        if (InputDirection.X <= 0 && Input.IsActionPressed(ActionRight))
            Input.ActionRelease(ActionRight);
        if (InputDirection.Y >= 0 && Input.IsActionPressed(ActionUp))
            Input.ActionRelease(ActionUp);
        if (InputDirection.Y <= 0 && Input.IsActionPressed(ActionDown))
            Input.ActionRelease(ActionDown);

        if (InputDirection.X < 0)
            Input.ActionPress(ActionLeft, -InputDirection.X);
        if (InputDirection.X > 0)
            Input.ActionPress(ActionRight, InputDirection.X);
        if (InputDirection.Y < 0)
            Input.ActionPress(ActionUp, -InputDirection.Y);
        if (InputDirection.Y > 0)
            Input.ActionPress(ActionDown, InputDirection.Y);
    }

    private void ResetJoystick()
    {
        IsPressed = false;
        InputDirection = Vector2.Zero;

        _touchIndex = -1;
        _tip.Modulate = _defaultColor;
        _base.Position = _baseDefaultPosition;
        _tip.Position = _tipDefaultPosition;

        if (UseInputActions)
            foreach (string action in new[] { ActionLeft, ActionRight, ActionUp, ActionDown })
                Input.ActionRelease(action);
    }
}