65.9K
CodeProject is changing. Read more.
Home

A Joystick Control

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.14/5 (6 votes)

May 31, 2005

3 min read

viewsIcon

55051

downloadIcon

2665

A joystick control using a simplified polar coordinate system to return an orientation and magnitude, and a custom Vehicle UserControl with an Offset method that uses the said coordinate system.

Introduction

This project provides a joystick control that uses a simplified polar coordinate system (i.e., only the eight grossest compass points for directions — north, northeast, east, southeast, etc.). It also provides a custom vehicle class that can take an offset in the compass coordinate system that the joystick uses.

Background

The control uses a trig function to arrive at the angle of the joystick-- FindOrientation uses Math.Atan2(double, double) to get the angle in radians that the joystick ray makes to the x axis. Don't be frightened though--the Joystick actually returns a member of the enumeration below instead of an angle for its orientation, and you can use simple Euclidean geometry to translate that and the magnitude (radial distance of the mouse pointer from the center of the joystick) into x and y offsets to feed to some (hopefully) moving object. Of course, if I go on and produce version 0.2 that returns orientation in radians, you'll need some more trig to deal with it.

There are actually two custom controls in the project--the Joystick and the Vehicle. The latter is just a UserControl that has a custom Offset method that takes an orientation and a magnitude rather than the usual x and y components of motion. A vector, if you will, and are thinking calculus and not STL.

Using the code

If you want to use the JoyStickControl in your own project, you will have to deal with the polar coordinate system that it uses. Perhaps I should mention here that the control is the result of a school assignment—left to my own, I would have used either plain x-y coordinates, or a full polar coordinate system, with the orientation being passed in radians. As it is, the joystick contains a public enumeration:

public enum compassPoints
{
    north,
    northeast,
    east,
    southeast,
    south,
    southwest,
    west,
    northwest
}

which has all the possible values for orientation that it may pass through its MouseMoving event. In my own use of the thing, I had the main form subscribe to the custom event; the form then calls the Vehicle's Offset method, which deals with the coordinates like so:

public void Offset(Joystick.compassPoints orientation, int magnitude)
{
    this.orientation = orientation;
    this.magnitude = (double)magnitude;
    switch (orientation)
    {
        case Joystick.compassPoints.north :
            this.Location = new Point(this.Location.X, 
                            this.Location.Y - (int)this.magnitude);
            break;
        case Joystick.compassPoints.northeast :
            this.Location = new Point(this.Location.X + 
               ((int)Math.Round(Math.Sqrt((Math.Pow(this.magnitude, 2))/2), 0)), 
               this.Location.Y + 
               (int)-Math.Round((Math.Sqrt((Math.Pow(this.magnitude, 2))/2)), 0));
            break;
        case Joystick.compassPoints.east :
            this.Location = 
                 new Point(this.Location.X + magnitude, this.Location.Y);
            break;
        case Joystick.compassPoints.southeast :
            this.Location = new Point(this.Location.X + 
                 (int)Math.Round(Math.Sqrt((Math.Pow(this.magnitude, 2))/2), 0), 
                 this.Location.Y + 
                 (int)Math.Round(Math.Sqrt((Math.Pow(this.magnitude, 2)/2)), 0));
            break;
        case Joystick.compassPoints.south :
            this.Location = 
                 new Point(this.Location.X, this.Location.Y + (magnitude));
            break;
        case Joystick.compassPoints.southwest :
            this.Location = new Point(this.Location.X + 
                 (int)-Math.Round(Math.Sqrt(Math.Pow(this.magnitude, 2)/2), 0), 
                 this.Location.Y + 
                 (int)Math.Round((Math.Sqrt((Math.Pow(this.magnitude, 2))/2)), 0));
            break;
        case Joystick.compassPoints.west :
            this.Location = 
                 new Point(this.Location.X - (magnitude), this.Location.Y);
            break;
        case Joystick.compassPoints.northwest :
            this.Location = new Point(this.Location.X + 
                 (int)-Math.Round(Math.Sqrt((Math.Pow(this.magnitude, 2))/2), 0), 
                 this.Location.Y + 
                 (int)-Math.Round(Math.Sqrt((Math.Pow(this.magnitude, 2))/2), 0));
            break;
        default :
            break;
    }
}

In other words, we just use the compass points and a bit of simple Euclidean geometry, (opposite)2 + (adjacent)2 = (hypotenuse)2, to arrive at a new location. Simplicity itself, though with lots of typing for me. For reasons of precision, I do all my calculations using doubles, and then carefully round them before casting them to ints, since the cast would otherwise simply strip the decimal places off without rounding, producing sloppy results.

Points of Interest

While working on this, I discovered that the Joystick.MouseMove event fired continuously while the mouse was over the control. I don't know whether or not this is intentional on Microsoft's part, or whether I've found a bug. The MSDN simply states "Occurs when the mouse pointer is moved over the control," which sounds like it should mean that it either fires when the mouse moves over, or enters the control (but this of course would be redundant, since controls already have a MouseEnter event), or that it fires when the mouse moves over the control--moves while within the control. Instead, the thing just fires continuously while the mouse is inside the thing. I ended up having the Joystick screen out the noise by checking to see that the mouse had actually moved before firing its custom MouseMovingEvent, which the form listens to. Movement of the Vehicle is controlled by a timer on the main form, the form calling the Vehicle's Offset method every 18 ms while the mouse is over the Joystick, and what with the Joystick.MouseMovingEvent originally firing continuously in response to its own MouseMove event (firing 87 bazillion times a second) the poor timer never had a chance to do anything until I added my screening code.