Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

8 way Direction Joystick Only

Discussion in '2D' started by piggybank1974, Oct 30, 2016.

  1. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    Some of the users to my game have complained about the free movement on my virtual joystick, I've been trying to figure out a new idea to improve this, and only allow the 8 Directions for the knobImage to travel in, but I just cannot work it out, I spent today creating a new 8 buttoned version but hated it like a passion as it just did not work right for me.

    I've posted the code I use, if anybody could shed some light on this subject please do, it's go me so far.

    I think it need some sort of snapping idea.

    Code (CSharp):
    1. public class KnobComponent : MonoBehaviour, IDragHandler, IPointerUpHandler, IPointerDownHandler
    2. {
    3. private Image mBaseImage;
    4. private Image mKnobImage;
    5.  
    6. private Vector3 screenPoint;
    7.  
    8. private DirectionTypes mDirection;
    9.  
    10. private Boolean mDisableInput;
    11.  
    12. private double mOffset = 5;
    13.  
    14. public delegate void MovementEventHandler(object sender, JoyStickEventArgs e);
    15. public event MovementEventHandler MovementChanged;
    16.  
    17. protected virtual void OnStickMovement(JoyStickEventArgs e)
    18. {
    19.   if (MovementChanged != null)
    20.    MovementChanged(this, e);
    21. }
    22.  
    23. private void Start()
    24. {
    25.   mDisableInput = false;
    26.   mDirection = DirectionTypes.None;
    27.   mBaseImage = GetComponent<Image>();
    28.   mKnobImage = transform.GetChild(0).GetComponent<Image>();
    29. }
    30.  
    31. public virtual void OnDrag(PointerEventData eventData)
    32. {
    33.   if (mDisableInput == false)
    34.     {
    35.      Vector2 pos;
    36.      if (RectTransformUtility.ScreenPointToLocalPointInRectangle(mBaseImage.rectTransform, eventData.position, eventData.enterEventCamera, out pos) == true)
    37.        {
    38.         pos.x = (pos.x / mBaseImage.rectTransform.sizeDelta.x);
    39.         pos.y = (pos.y / mBaseImage.rectTransform.sizeDelta.y);
    40.  
    41.         Vector3 InputVector = new Vector3(pos.x * 2 + 1, pos.y * 2 - 1, 0);
    42.         InputVector = (InputVector.magnitude > 1.0F) ? InputVector.normalized : InputVector;
    43.  
    44.         float mDistance = Vector3.Distance(Vector3.zero, InputVector);
    45.         mKnobImage.rectTransform.anchoredPosition = new Vector3(InputVector.x * (mBaseImage.rectTransform.sizeDelta.x / 2.9F), InputVector.y * (mBaseImage.rectTransform.sizeDelta.y / 2.9F), 0);
    46.  
    47.         double mAngle = RotationAngleInDegrees(screenPoint, mKnobImage.rectTransform.position);//Camera.main.WorldToScreenPoint(JoystickImg.rectTransform.position)
    48.  
    49.         if (mAngle >= 338 || mAngle <= 23)
    50.           {
    51.            mDirection = DirectionTypes.North;
    52.            OnStickMovement(new JoyStickEventArgs(mDirection, 0, mDistance));
    53.           }
    54.  
    55.         if (mAngle > 23 && mAngle <= 68)
    56.           {
    57.            mDirection = DirectionTypes.NorthEast;
    58.            OnStickMovement(new JoyStickEventArgs(DirectionTypes.NorthEast, mDistance, mDistance));
    59.           }
    60.  
    61.         if (mAngle > 68 && mAngle <= 113)
    62.           {
    63.            mDirection = DirectionTypes.East;
    64.            OnStickMovement(new JoyStickEventArgs(mDirection, mDistance, 0));
    65.           }
    66.  
    67.         if (mAngle > 113 && mAngle <= 158)
    68.           {
    69.            mDirection = DirectionTypes.SouthEast;
    70.            OnStickMovement(new JoyStickEventArgs(mDirection, -mDistance, mDistance));
    71.           }
    72.  
    73.         if (mAngle > 158 && mAngle <= 203)
    74.           {
    75.            mDirection = DirectionTypes.South;
    76.            OnStickMovement(new JoyStickEventArgs(mDirection, 0, -mDistance));
    77.           }
    78.  
    79.         if (mAngle > 203 && mAngle <= 248)
    80.           {
    81.            mDirection = DirectionTypes.SouthWest;
    82.            OnStickMovement(new JoyStickEventArgs(mDirection, -mDistance, -mDistance));
    83.           }
    84.  
    85.         if (mAngle > 248 && mAngle <= 293)
    86.           {
    87.            mDirection = DirectionTypes.West;
    88.            OnStickMovement(new JoyStickEventArgs(mDirection, -mDistance, 0));
    89.           }
    90.  
    91.         if (mAngle > 293 && mAngle <= 338)
    92.           {
    93.            mDirection = DirectionTypes.NorthWest;
    94.            OnStickMovement(new JoyStickEventArgs(mDirection, mDistance, mDistance));
    95.           }
    96.        }
    97.     }
    98. }
    99.  
    100. public virtual void OnPointerDown(PointerEventData eventData)
    101. {
    102.   if (mDisableInput == false)
    103.     {
    104.      screenPoint = eventData.position;
    105.      OnDrag(eventData);
    106.     }
    107. }
    108.  
    109. public virtual void OnPointerUp(PointerEventData eventData)
    110. {
    111.   mKnobImage.rectTransform.anchoredPosition = Vector3.zero;
    112.   OnStickMovement(new JoyStickEventArgs(mDirection, 0, 0));
    113. }
    114.  
    115. private double RotationAngleInDegrees(Vector3 centerPt, Vector3 targetPt)
    116. {
    117.   // calculate the angle theta from the deltaY and deltaX values
    118.   // (atan2 returns radians values from [-PI,PI])
    119.   // 0 currently points EAST.
    120.   // NOTE: By preserving Y and X param order to atan2,  we are expecting
    121.   // a CLOCKWISE angle direction.
    122.   double theta = System.Math.Atan2(targetPt.y - centerPt.y, targetPt.x - centerPt.x);
    123.  
    124.   // rotate the theta angle counter-clockwise by 90 degrees
    125.   // (this makes 0 point NORTH)
    126.   // NOTE: adding to an angle rotates it clockwise.
    127.   // subtracting would rotate it counter-clockwise
    128.   theta -= System.Math.PI / 2.0;
    129.  
    130.   // convert from radians to degrees
    131.   // this will give you an angle from [0->270],[-180,0]
    132.   double angle = (theta * Mathf.Rad2Deg);  //Degrees(theta);
    133.  
    134.   if (angle < 0)
    135.    angle += 360;
    136.  
    137.   angle -= 360;
    138.  
    139.   return System.Math.Abs(angle);
    140. }
    141.  
    142. public Boolean DisableInput
    143. {
    144.   get { return mDisableInput; }
    145.   set { mDisableInput = value; }
    146. }
    147.  
    148. }
     

    Attached Files:

  2. Hyblademin

    Hyblademin

    Joined:
    Oct 14, 2013
    Posts:
    725
    @piggybank1974

    What I think you're saying is that the above code is working, but you don't like the way it feels. I'm going to run with that.

    What if you transformed the inputs to favor the 8 directions, rather than to snap directly to them?

    You could use something like f(x) = -a*sin(c*x) + x; a can be tweaked to alter the behavior of the output, and c should be 180/pi * 8, which converts degrees to radians and scales to the appropriate frequency (8 cycles per rotation). As it happens, we don't need a phase adjustment for our purpose. You can get an input-output relationship that looks like this:

    upload_2016-10-31_8-31-45.png

    You can see that there is a flat band about 20 degrees wide every 45 degrees. Look here to see the values I used. Larger values of
    a will increase the width and slope of the flat bands, and vice versa.

    The biggest problem with this is that, for certain values of
    a, the flat bands adopt a negative slope. That would be totally unwieldy, and even the smaller negative slopes would still look weird if they weren't negligible (the graph above actually does have a tiny negative slope). This means that if you want to make the flat bands any wider than a certain maximum width, you need to use a piecewise function.

    Anyway, this should give a good compromise between a free stick and a snapping one. If you decide to try it, let us know how it performs.
     
    Last edited: Oct 31, 2016
    Marrt likes this.
  3. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    Hi Hyblademin,

    Sorry for taking so long to reply, it's been a busy prototyping week.

    Thank you very much for you detailed reply, it's a lot more than I expected, must admit it went over my head a bit, I hope it's not to much to ask if you could point me in the correct direct to adjust my code to get it working.

    parts of it i understand but not much.

    Thanks in advance.
     
  4. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,840
    It looks to me like your code is already converting the analog input into an 8-directional one. There are a lot of small ways it could be improved (like just using InputVector.magnitude instead of calculating the distance from Vector2.zero, or using math instead of a ton of if-blocks to calculate which octant the input is in), but the basic idea seems right. What doesn't work about it?

    One thing that jumps out as really odd: you take the input, then use this to position a knob, and then take the position of the knob to calculate the direction. Why not just calculate the direction from the input? That would be simpler and harder to mess up.
     
  5. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    I did write this in early 2016 when I first started unity, I pieced this together for several examples on youtube, yeah magnitude is a good thing.

    JoeStrout it's not about it not working it does, even though it's a bit err!! simple, I would like to change it to Hyblademin idea, I think he is on about initial movement and then snapping to the correct direction, this should allow me to get rid of the nasty rotation formula, I'm just not really understanding how to implement Hyblademin idea in my code

    any help would be great.
     
  6. Hyblademin

    Hyblademin

    Joined:
    Oct 14, 2013
    Posts:
    725
    I'll try to explain myself better:

    Figuring It Out

    I imagined a system to convert raw input angles into output angles that favored the 8 directions at each 45 degree interval. This can be done by "plugging in" the input into some formula, then using the output instead. A sine relationship is recognizable in that the behavior should adjust the angle toward the nearest primary angle MORE if it's closer to it (but without overshooting), and less if it's more distant. I played with it in WolframAlpha until I came up with

    ω' = -7.5 * sin(ω * π/180 * 8) + ω

    where ω' is the output angle and ω is the input angle.

    Implementing It

    You'll probably want to do something like this:

    Code (CSharp):
    1. float A_CONST = -7.5;    //Adjustable constant for the formula (I like to use caps for constants)
    2. float inpAngle;    //The input angle in degrees
    3. float outAngle;    //The output angle in degrees, to pass to the player character
    4.  
    5. //Other stuff...
    6.  
    7. outAngle = A_CONST * Mathf.Sin(inpAngle * Mathf.Deg2Rad * 8) + inpAngle;
    8.  
    9. //Give outAngle to your character object
    The formula uses A_CONST because you're probably going to want to adjust it to get it to feel just right. You'll notice that the above uses Mathf.Deg2Rad, which is exactly equal to π/180, as a conversion constant from degrees to radians.

    To reiterate, the 8 is for the number of "sticking points" on the output mapping. If you wanted, you could change it, like if you wanted to go from 8 down to 4 primary directions. If you were making a hex-tiled game, you might change it to 6.

    See if this gets you closer to what you're looking for, and let us know if it works.
     
    Marrt and piggybank1974 like this.
  7. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    stupendous Hyblademin I cannot thank you enough for helping.

    somethings I'm replying might sound stupid, but I'm trying to get in my head(I wish I did higher maths when I was a kid :( )

    So A_CONST, this will be before the snapping starts I believe? so you can adjust it makes sense I think.

    Now you have wrote it in this way I think I understand it much better as to what you where trying to hit me on the head with, I usually understand the code better than the maths if it presented in code. Some of the maths formula's are so confusing in maths terms but when converted to code, I usually get the idea, I'm in the process of teaching myself this stuff, while I'm writing other games etc, I've been lucky as a coder not to have to do a lot of higher maths over the years, but now I need to my tender age(42) it just does not sink in easily :)

    I'll start the process later this week, I'll post back the project if the forum allows the upload of a few megabits plus.

    just in case others would like to know.

    thanks again.
     
  8. Hyblademin

    Hyblademin

    Joined:
    Oct 14, 2013
    Posts:
    725
    Happy to help, I hope it's what you need.

    A_CONST isn't quite that straightforward. It's the magnitude of the sine component, though that fact becomes a bit meaningless in our context. Bottom line: wiggle it between about -3.5 and -7.5 and see which value feels the best. It will change how "sticky" each primary angle is, with larger values (closer to 0) causing the behavior to be more like the raw input. Values smaller than -7.5 will probably give useless/absurd behavior.
     
    Last edited: Nov 3, 2016
  9. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    Will do my friend, top banana to you!!
     
  10. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    Hi Hyblademin, JoeStrout

    I created a new Joystick Demo Project (unity 5.4.1F1)

    I have been changing the original joystick routine(not cleaned it up yet), to your idea, but somehow it does not really feel any different unless I'm doing it incorrectly, could either of you have a look or anybody else.

    Thanks
     

    Attached Files:

  11. Hyblademin

    Hyblademin

    Joined:
    Oct 14, 2013
    Posts:
    725
    I actually decided to test it before coming back here to check on your progress. Here's a visualization of what the mapping does (input is cyan, output is magenta):

    example filter.gif

    I'll have a look at your project.
     
  12. Hyblademin

    Hyblademin

    Joined:
    Oct 14, 2013
    Posts:
    725
    I can't give example code at the moment, but in the project you provided, outAngle is calculated properly. It doesn't use this angle to display the joystick position, though, because it flows like this:

    Get mouse position > Calculate joystick graphic position from mouse position > Calculate angle/magnitude from joystick graphic position > Use this angle for game

    I don't really like this. I think it would make more sense to do it like this:

    Get mouse position > Calculate angle/magnitude from mouse position > Use this angle/magnitude for game AND joystick graphic position

    Anyway, it's not being represented in the joystick graphic. Honestly I think this is ok, since a real joystick wouldn't change its actual position based on a filter.

    Did you try implementing it into your character movement in your project?
     
  13. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
  14. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    Hi Hyblademin,

    Well this is bizarre, your correct, I've just put the changes in the game and it does feel less wayward, it feels just as I joystick should be or there about's, I did make several other changes, I made the constant a property with a range field of -3.5 to -7.5, and added an enumerator direction type so you can pick from 4/6/8 directions.

    I just need to do some more controls as I was never happy with these in good old photoshop.

    Yeah this does sound the correct what to do it.

    I'll post a new project version once it's done just in case others want to know, and add it in there project.
     
    Hyblademin likes this.
  15. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    612
    If you want Code to implement such a Deadzone, i think mine does exactly what your article explains:
    https://forum.unity3d.com/threads/axis-input-dead-problematic-for-diagonal-input.399269/

    EDIT:

    Here is my current implementation modified by some insights of this thread, assign cameraTrans if you want debuging Gizmos
    Code (CSharp):
    1.  
    2. /// <summary>
    3. /// Snaps directional Input (magnitudes from 0-1F) to 8 directions or Vector2.zero if below Dead,
    4. /// - keeps magnitude
    5. /// - axis dead uses magnitude
    6. /// - option to scale values from zero after dead has been exceeded rather than its actual value. Set "axisScaleFromZero" to true
    7. /// - option to smooth snapping. Set "axisSmoothSnapMode" to .Smooth https://forum.unity3d.com/threads/8-way-direction-joystick-only.438758/#post-2840450
    8. /// </summary>
    9. ///
    10. private enum    Vector2SnapMode { Discrete, Smooth }
    11.  
    12. //parameters
    13. private float            axisDead            =  0.4F;    //radius of axis dead, from 0 to zero
    14. private float            smoothConst            = -7.5F;    //some factor that controls smoothing, everything less than -7.5 makes values overshoot
    15. private    bool            axisScaleFromZero    =  true;  
    16. private    Vector2SnapMode    axisSmoothSnapMode    =  Vector2SnapMode.Smooth;
    17.  
    18. private Vector2 SnapVector2Dir(Vector2 raw){
    19.  
    20.     Vector2 vec = raw;
    21.  
    22.     float sign            = Mathf.Sign(raw.x * Vector2.up.y - raw.y * Vector2.up.x);
    23.     float angle            = Vector2.Angle(Vector2.up, raw) *sign;
    24.  
    25.     float clampedAngle = 0F;
    26.  
    27.     if(axisSmoothSnapMode == Vector2SnapMode.Smooth) {
    28.         clampedAngle    = smoothConst * Mathf.Sin(angle * Mathf.Deg2Rad * 8) + angle;
    29.     }else{
    30.         clampedAngle    = Mathf.Round( angle/45F )*45F;
    31.     }
    32.  
    33.     vec = new Vector2( Mathf.Sin(clampedAngle *Mathf.Deg2Rad), Mathf.Cos(clampedAngle *Mathf.Deg2Rad) ).normalized *raw.magnitude;
    34.  
    35.     //Debug
    36.     print(angle.ToString("F2")+"\t-> "+clampedAngle.ToString("F2"));
    37.     Vector3 origin = cameraTrans.position+cameraTrans.forward*20F;
    38.     Debug.DrawLine(origin +cameraTrans.right *-6F, origin +cameraTrans.right * 6F,            Color.white, 0F, false);    //herz
    39.     Debug.DrawLine(origin +cameraTrans.up *-6F, origin +cameraTrans.up * 6F,                Color.white, 0F, false);    //vert
    40.     Debug.DrawLine(origin, origin + (cameraTrans.right * raw.x+cameraTrans.up * raw.y) *5F, Color.cyan, 0F, false);
    41.     Debug.DrawLine(origin, origin + (cameraTrans.right * vec.x+cameraTrans.up * vec.y) *5F, Color.magenta, 0F, false);
    42.  
    43.      
    44.     //Magnitude Clamping and Dead
    45.     float mag = vec.magnitude;
    46.     if(mag < axisDead){
    47.         vec = Vector3.zero;
    48.     }else{
    49.         if(axisScaleFromZero)    { vec = vec.normalized *Mathf.InverseLerp(axisDead, 1F, mag);    }    //start from zero after exceeding axis dead
    50.         else                    { vec = vec.normalized *Mathf.Clamp(mag, axisDead, 1F);            }    //start from axis dead
    51.     }
    52.      
    53.     return vec ;
    54.  
    55. }
    56.  
     
    Last edited: Nov 8, 2016
  16. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    Hi Hyblademin,

    I wanted to thank you again for your efforts with my issue, I've since recompiled a new version and add 2 more levels, plus some new joystick graphics, and it should be release tomorrow (Friday 11th Nov) hope the users like the new control.

    If your ever here in good old UK look me up I owe you a beer :)
     
  17. Deleted User

    Deleted User

    Guest

    Try this:

    Code (CSharp):
    1. //InputDirection - Vector2 which you get from your typical joystick
    2.  
    3. float angle = Mathf.Atan2(inputDirection.x, inputDirection.y) * Mathf.Rad2Deg;
    4. angle = Mathf.Round(angle / 45.0f) * 45.0f;