1

Note: I've asked this question again because I was not be able to edit my old question. (No idea if this was a SO bug or a bug with my beta safari.)

So I want to generate a joystick, as it is used in many games. The joystick stands out of a background and a movable billet. The billet may only be moved within the background.

Here you can find both images

background

stick

let background = new Image()
let stick = new Image()
let enableMaxDistance = false

background.onload = function() {
  $(this).css({
    position: "absolute",
    left: "2%",
    bottom: "2%",
    width: "30%"
  }).appendTo(document.body)
}
stick.onload = function() {
  $(this).css({
    position: "absolute",
    left: "2%",
    bottom: "2%",
    width: "30%"
  }).appendTo(document.body)
  let zeroPosition = $(this).offset()
  $(this).draggable({
    drag: function(e, ui) {
      let distance = Math.sqrt(Math.pow(zeroPosition.top - $(this).offset().top, 2) + Math.pow(zeroPosition.left - $(this).offset().left, 2));
      if (distance > 60 && enableMaxDistance) {
        e.preventDefault();
      }
    },
    scroll: false

  })
}
background.src = "https://i.stack.imgur.com/5My6q.png"
stick.src = "https://i.stack.imgur.com/YEoJ4.png"
html, body {
  overflow: hidden;
  height: 100%;
}
input {
  margin-top: 10%;
  margin-left: 50%;
}
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>


<input onclick="enableMaxDistance = !enableMaxDistance " value="toggle maximum distance" type="button"/>

But while implementing this joystick some problems occurred:

My idea was to make the stick maneuverable by using jqueryUI and to calculate its distance to the origin with each drag event. If the distance is too large, the event will be stopped (not executed) using e.preventDefault();. --> If the distance in the frame, the stick is wearable. The problem is that ...

  1. The stick is no longer draggable after moving out the maximum distance.

  2. The stick should be just be movable inside the bounds without canceling the event so that I have to grab the stick again and again if I'm touching the bounds (going out of the maximum distance).

How to implement a working joystick using jQuery + jQueryUI?

  • You're canceling the event if the bounds limit is hit, but that includes when trying to move back inwards (because the current distance is greater than 60). You should be allowing the event to fire but not allow the top/left positions to go out of bounds. If the user tries to drag it outside of the max distance just keep setting the position to the max. Then you could still bring it back in. – skyline3000 Jul 05 '18 at 15:55
  • Sounds like a working way @skyline3000 . Thanks - but would you mind to share some lines? Because I'm not really sure how to "implement `setting the position to the max`". :) –  Jul 05 '18 at 16:11

1 Answers1

1

The issue with your logic is that as soon as the drag event is prevented the distance value will be over 60 due to the inherent delays in JS processing time. Therefore the logic in the next drag is immediately cancelled as the distance > 60 check is immediately hit. While it would be possible to fix this, a much better solution would be to not allow the value to ever be greater than the limit you set.

To achieve this I would not recommend using jQueryUI. You can do it quite easily using native methods which give you more direct control of the positioning without having to fight against any built in logic.

It's also slightly more performant, which is vital when dealing with game mechanics; especially when dealing with direct user input which needs to be as responsive as possible.

With that said, you can use modify the basic logic as laid out in Twisty's comment on this question. Then it simply becomes a question of changing the size of the relevant elements, which is a trivial task. Try this:

var $canvas = $('#background');
var $pointer = $('#stick');
var $window = $(window);

var settings = {
  width: $canvas.prop('offsetWidth'),
  height: $canvas.prop('offsetHeight'),
  top: $canvas.prop('offsetTop'),
  left: $canvas.prop('offsetLeft')
};
settings.center = [settings.left + settings.width / 2, settings.top + settings.height / 2];
settings.radius = settings.width / 2;

let mousedown = false;
$window.on('mouseup', function() { mousedown = false; });
$pointer.on('mousedown', function() { mousedown = true; });
$pointer.on('mouseup', function() { mousedown = false; });
$pointer.on('dragstart', function(e) { e.preventDefault(); });

$window.on('mousemove', function(e) {
  if (!mousedown)
    return;

  var result = limit(e.clientX, e.clientY);
  $pointer.css('left', result.x + 'px');
  $pointer.css('top', result.y + 'px');
});

function limit(x, y) {
  var dist = distance([x, y], settings.center);
  if (dist <= settings.radius) {
    return {
      x: x,
      y: y
    };
  } else {
    x = x - settings.center[0];
    y = y - settings.center[1];
    var radians = Math.atan2(y, x)
    return {
      x: Math.cos(radians) * settings.radius + settings.center[0],
      y: Math.sin(radians) * settings.radius + settings.center[1]
    }
  }
}

function distance(dot1, dot2) {
  var x1 = dot1[0],
    y1 = dot1[1],
    x2 = dot2[0],
    y2 = dot2[1];
  return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
}
html,
body {
  height: 100%;
}

#background {
  background-image: url('https://i.stack.imgur.com/5My6q.png');
  position: absolute;
  height: 200px;
  width: 200px;
  top: 50%;
  left: 50%;
  margin: -100px 0 0 -100px;
  border-radius: 200px;
  border: dashed #ccc 1px;
}

#stick {
  background: transparent url('https://i.stack.imgur.com/YEoJ4.png') 50% 50%;
  position: absolute;
  width: 100px;
  height: 100px;
  border-radius: 100px;
  margin: -50px 0 0 -50px;
  top: 50%;
  left: 50%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="background"></div>
<div id="stick"></div>
Rory McCrossan
  • 331,213
  • 40
  • 305
  • 339
  • 1
    Looks like a good solution but the OP asked for a jQuery solution `How to implement a working joystick using jQuery?` –  Jul 05 '18 at 16:32
  • True, updated. The important part here is to get rid of jQueryUI draggable. The logic which takes care of the drag limit is purely JS in either case. – Rory McCrossan Jul 05 '18 at 16:40
  • Thanks for your answer @RoryMcCrossan. But tbh I was looking for a solution using pure jQuery + jQueryUI instead of hard coding something like `on('mouseup', function()...`. However - thanks a lot. –  Jul 05 '18 at 16:53
  • No problem. I'd still recommend this as the best path to follow. Using jQueryUI will mean you're attempting to do this with one hand behind your back. – Rory McCrossan Jul 05 '18 at 17:36