问题
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