C# WPF - Virtual joystick using polar coordinates

核能气质少年 提交于 2021-02-10 05:48:26

问题


I am trying to make a virtual joystick that a user can move around with the mouse in WPF in C#. I am trying to use the polar coordinate system because I want the knob of the joystick to remain in the circle.

I am getting some really weird behavior - if anybody has experience with this sort of thing any tips would be nice. Thank you

EDIT: I got it working. I posted the updated code below. It is by no means a good/professional solution to this but it works. So I hope if somebody in the future is trying to do this same task it may help. I tried to add some comments to explain what was going on. Here you go!

NOTE: If you are trying to use this for your program make note that there are two hardcoded values you must change. The first is the x_starting/y_starting. Thats where your virtual joystick knob should reset to. And next is the radius when calculating the max possible value. Make sure it is half the width of the background joystick.

The code:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;


namespace WpfApplication4
{
    public partial class MainWindow : Window
    {

        public MainWindow()
        { 
            InitializeComponent();
        }
        double radius;
        bool captured = false;
        double x_shape, x_canvas, y_shape, y_canvas;    //Canvas is used to keep track of where the joystick is on screen,
                                                        //  shape is used for where the knob is.
        UIElement source = null;
        double y_starting = 180;        //The starting X and Y position for the Knob. (CHANGE TO WHERE UR CANVAS.TOP/CANVAS.LEFT IS FOR THE KNOB)
        double x_starting = 105;

        private void Ellipse_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            string objname = ((Ellipse)sender).Name;
            if (objname == "Knob")
            {
                source = (UIElement)sender;
                Mouse.Capture(source);
                captured = true;
                x_shape = Canvas.GetLeft(reference);
                x_canvas = e.GetPosition(Knob).X;
                y_shape = Canvas.GetTop(reference);
                y_canvas = e.GetPosition(Knob).Y;
            }
        }

        private void Ellipse_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            string objname = ((Ellipse)sender).Name;
            Mouse.Capture(null);
            captured = false;
            if (objname == "Knob") {
              //  x_shape = x_starting;
              //  y_shape = y_starting;
                Canvas.SetLeft(source, x_starting);
                Canvas.SetTop(source, y_starting);          //Reset to our starting values
                XTextBlock.Text = x_starting.ToString();
                YTextBlock.Text = y_starting.ToString();

            }
        }

        private void Ellipse_MouseMove(object sender, MouseEventArgs e)
        {
            double x = e.GetPosition(reference).X;      //Getting mouse pos relative to the center of your joystick (I have an empty textblock there called reference)
            double y = e.GetPosition(reference).Y;
            double r = Math.Sqrt((x * x) + (y * y));    //Calculate radius..
            XMousePos.Text = x.ToString();
            YMousePos.Text = y.ToString();

            string objname = ((Ellipse)sender).Name;

            double theta = Math.Atan2(y, x);        //Calculate theta..
            Theta.Text = theta.ToString();


            double x1 = (r * Math.Cos(theta));      //This converts polar coordinates to cartesian plane coordinates.
            double y1 = (r * Math.Sin(theta));
            XPolPos.Text = x1.ToString();
            YPolPos.Text = y1.ToString();


            double xmax = (62.5 * Math.Cos(theta)); //Calculate a max so that your knob stays within the circle. The radius value should be half the width of the
            double ymax = (62.5 * Math.Sin(theta)); //      background of your joystick.
            X2PolPos.Text = xmax.ToString();
            Y2PolPos.Text = ymax.ToString();

            if (objname == "Knob") { 
                if (captured)
                {
                    if ((((x1 > 0) && (x1 < xmax)) || ((x1 <= 0) && (x1 > xmax))) && (((y1 > 0) && (y1 < ymax)) || ((y1 <= 0) && (y1 > ymax)))) //Seems like bad way to do it. But this is how i check to see if knob is in bounds.
                    {
                        x = e.GetPosition(reference).X;         //Get the values and calculate it again.
                        y = e.GetPosition(reference).Y;
                        r = Math.Sqrt((x * x) + (y * y));
                        theta = Math.Atan2(y, x);
                        x1 = (r * Math.Cos(theta));
                        y1 = (r * Math.Sin(theta));         

                        x_shape += x1 - x_canvas;               //Changing our values and moving the knob.
                        Canvas.SetLeft(source, x_shape);
                        x_canvas = x1;
                        y_shape += y1 - y_canvas;
                        Canvas.SetTop(source, y_shape);
                        y_canvas = y1;

                        XTextBlock.Text = x_shape.ToString();
                        YTextBlock.Text = y_shape.ToString();
                    }
                }
            }
        }
    }
}

And the XAML just in case:

<Window x:Class="WpfApplication4.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525" MouseMove="Window_MouseMove" KeyDown="Window_KeyDown">
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>

    </Grid.ColumnDefinitions>
    <StackPanel Orientation="Vertical">
        <TextBlock Text="KNOB POSITION"/>
        <TextBlock Name="XTextBlock"/>
        <TextBlock Name="YTextBlock"/>
        <TextBlock Text="MOUSE POSITION"/>
        <TextBlock Name="XMousePos"/>
        <TextBlock Name="YMousePos"/>
        <TextBlock Text="POLAR COORDINATES"/>
        <TextBlock Name="XPolPos"/>
        <TextBlock Name="YPolPos"/>
    </StackPanel>
    <Canvas Name="LayoutRoot" Grid.Column="1">
        <Ellipse Fill="#FFF4F4F5" Name ="Joystick" Height="125" Canvas.Left="51" Stroke="Black" Canvas.Top="128" Width="125" MouseLeftButtonDown="Ellipse_MouseLeftButtonDown" MouseLeftButtonUp="Ellipse_MouseLeftButtonUp" MouseMove="Ellipse_MouseMove"/>
        <Ellipse Fill="#FFF4F4F5" Name="Knob" Height="15" Canvas.Left="105" Stroke="Black" Canvas.Top="180" Width="15" MouseLeftButtonDown="Ellipse_MouseLeftButtonDown" MouseLeftButtonUp="Ellipse_MouseLeftButtonUp" MouseMove="Ellipse_MouseMove"/>
    </Canvas>
</Grid>


回答1:


When we're talking about joystick the polar coordinate system does not seems to be helpful.

What we need is X and Y offset in range [-1; 1]. We can easily evaluate it knowing Field (big) Radius, Filed Center point and Mouse coordinates.

Here is how it works (remove all events except Ellipse_MouseMove). Member m_vtJoystickPos holds selected Joystick position.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        UpdateKnobPosition();
    }

    /// <summary>
    /// Current joystick position
    /// </summary>
    Vector m_vtJoystickPos = new Vector();
    private void Ellipse_MouseMove(object sender, MouseEventArgs e)
    {
        double fJoystickRadius = Joystick.Height * 0.5;

        //Make coords related to the center
        Vector vtJoystickPos = e.GetPosition(Joystick) - 
            new Point(fJoystickRadius, fJoystickRadius);

        //Normalize coords
        vtJoystickPos /= fJoystickRadius;

        //Limit R [0; 1]
        if (vtJoystickPos.Length > 1.0)
            vtJoystickPos.Normalize();

        XMousePos.Text = vtJoystickPos.X.ToString();
        YMousePos.Text = vtJoystickPos.Y.ToString();

        //Polar coord system
        double fTheta = Math.Atan2(vtJoystickPos.Y, vtJoystickPos.X);
        XPolPos.Text = fTheta.ToString(); //Angle
        YPolPos.Text = vtJoystickPos.Length.ToString(); //Radius

        if (e.LeftButton == MouseButtonState.Pressed)
        {                                
            m_vtJoystickPos = vtJoystickPos;
            UpdateKnobPosition();
        }
    }

    void UpdateKnobPosition()
    {
        double fJoystickRadius = Joystick.Height * 0.5;
        double fKnobRadius = Knob.Width * 0.5;
        Canvas.SetLeft(Knob, Canvas.GetLeft(Joystick) +
            m_vtJoystickPos.X * fJoystickRadius + fJoystickRadius - fKnobRadius);
        Canvas.SetTop(Knob, Canvas.GetTop(Joystick) +
            m_vtJoystickPos.Y * fJoystickRadius + fJoystickRadius - fKnobRadius);
    }
}

I've also included Polar CS evaluation (commented). BTW Polar CS is (R, Angle).

XAML:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication1"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>

        </Grid.ColumnDefinitions>
        <StackPanel Orientation="Vertical">
            <TextBlock Text="KNOB POSITION"/>
            <TextBlock Name="XTextBlock"/>
            <TextBlock Name="YTextBlock"/>
            <TextBlock Text="MOUSE POSITION"/>
            <TextBlock Name="XMousePos"/>
            <TextBlock Name="YMousePos"/>
            <TextBlock Text="POLAR COORDINATES"/>
            <TextBlock Name="XPolPos"/>
            <TextBlock Name="YPolPos"/>
        </StackPanel>
        <Canvas Name="LayoutRoot" Grid.Column="1">
            <Ellipse Fill="#FFF4F4F5" Name ="Joystick" Height="125" Canvas.Left="51" Stroke="Black" Canvas.Top="127" Width="125" MouseMove="Ellipse_MouseMove"/>
            <Ellipse Fill="#FFF4F4F5" Name="Knob" Height="16" Canvas.Left="106" Stroke="Black" Canvas.Top="182" Width="15" MouseMove="Ellipse_MouseMove"/>
        </Canvas>
    </Grid>
</Window>


来源:https://stackoverflow.com/questions/37421431/c-sharp-wpf-virtual-joystick-using-polar-coordinates

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!