• Runtimes
  • I would like to fix the problem when the armature is moved by touch.

sqrt(30) is 5, so you only call dragged if the touch is > 5 from the current distance. Typically a "tap square" requires some distance threshold, then ALL movement after that is considered a drag.
set_render is never used. 🙂

Nothing stands out as being wrong in your code. Note that you may want to reset the start position when the drag starts. Consider a large tap square threshold, like 50. Nothing happens until I move the touch > 50. Say I move 55, what happens? startX and startY are the touch down position, so the bone will be moved to the touch position -- it will jump 55. If you reset startX and startY to the position where the drag exceeded the threshold (50 in this example), then it will not actually move when the threshold is exceeded (55), but if the touch moves after that it will begin to move.

It's commonly done like this:

if (!dragging && (abs(distanceX) > 5 || abs(distanceY) > 5)) {
    dragging = true;
    startX = x;
    startY = y;
}
if (dragging) dragged(...);

Your circle is also fine but a square is usually good enough.

    Related Discussions
    ...

    Nate
    thank you for your reply
    Based on your answer, my code is written like this.

              input.addListener({
                down: (x, y) => {
                  startX = x;
                  startY = y;
                    mouse.set(x, canvas_1.clientHeight - y, 0)
                    var bestDistance = 20,
                      index = 0;
                    var best;
                    for (var i = 0; i < controlbones.length; i++) {
                      hoverTargets[i] = null;
                      let bone = skeleton.findBone(controlbones[i]);
                      var position = new spine.Vector2(bone.length, 0);
                      bone.localToWorld(position);
                      let distance = renderer.camera.worldToScreen(
                        coords.set(bone.worldX, bone.worldY, 0),
                        canvas_1.clientWidth, canvas_1.clientHeight).distance(mouse);
                      // let distance = Math.sqrt((coords.x - bone.x) * (coords.x - bone.x) + (coords.y - bone.y) * (coords.y - bone.y));
                      if (distance < bestDistance) {
                        bestDistance = distance;
                        best = bone;
                        index = i;
                      }
                    }
                    if (best) hoverTargets[index] = best;
                    target = best;
                },
                up: (x, y) => {
                  target = null;
                  dragging = false;
                },
                dragged: (x, y) => {
                  currentX = x;
                  currentY = y;
                  const distanceX = currentX - startX;
                  const distanceY = currentY - startY;
                  if(!dragging && (Math.abs(distanceX) > 5 || Math.abs(distanceY) > 5)){
                    dragging = true;
                    startX = x;
                    startY = y;
                  }
    
                  // hair_distance = Math.sqrt(distanceX ** 2 + distanceY ** 2);
                  if (dragging) {
                    dragged(canvas_1, renderer, target, x, y);
                  }
                }
            })
    
    
    
            function dragged(canvas_1, renderer, target, x, y){
            if (target) {
             x = spine.MathUtils.clamp(x, 0, canvas_1.clientWidth);
             y = spine.MathUtils.clamp(y, 0, canvas_1.clientHeight);
    
             dragX = x;
             dragY = y;
    
    
             var newX = startX + 0.20*(dragX - startX);
             var newY = startY + 0.20*(dragY - startY);
    
    
             renderer.camera.screenToWorld(coords.set(newX, newY, 0), canvas_1.clientWidth, canvas_1.clientHeight);
             if (target.parent !== null) {
                target.parent.worldToLocal(position.set(coords.x, coords.y));
    
                target.x = position.x;
                target.y = position.y;
    
             } else {
                target.x = coords.x;
                target.y = coords.y;
             }
            }
          }

    I wonder if this is a problem of the skeleton rather than a problem of touch.

    This is because the same phenomenon occurs when the skeleton is moved by clicking with the mouse as well as by touching.

    When the skeleton is pulled to the place where the hair is, it comes down to the outside and then goes up again.

    When the skeleton is moved to the outside where there is no hair, the skeleton is naturally moved, but when the skeleton is moved to the inside where there is hair, the phenomenon of moving outside and then moving is still present.

    Your code looks good. It's not clear why the problem would be with the skeleton, but I suppose it's possible. If you move the bone in the Spine editor, do you get the behavior your want? You can use Preview if you need to move the bone while an animation is playing.

      Nate
      Even after modifying the bones, the same thing still happens.
      This phenomenon continues to appear when moving bones into the mesh.

      Like the video I uploaded, the same problem appears, so I ask for help again.

      Not being familiar with your project, I'm afraid I don't find these videos very useful. It looks like the bone is moving in the opposite direction of the touch, but I can't be sure. It would help to draw the bones so we can see what is happening. Why are you touching so fast? How does it look when touching more slowly? How does it look when it is working correctly? How does it look when you move the bones in the Spine editor?

        Nate

        When you grab the skeleton, there is a phenomenon in which it goes down once and then goes up.
        That's why it seems that this issue is appearing.
        Slowly framing these problems will be greatly reduced, but sometimes similar problems appear.
        It appears normally in the spine editor.

        I'll send you a testable URL, so can you test it? You can test it with an image that we send, not your own image.

        https://hairservice.edugreen.shop/mask_on_profile

        I tried to use your app, but the wig overlay appears too large and it's hard to see what is going on.

        Try drawing the bones using the skeleton debug renderer.

          Nate

            var bestDistance = 50

          Reduced the bestDistance number.
          And I set the skeleton debug renderer to true.
          If there is anything else I need to set up, please let me know.

          Your page behaves strangely when the window is wide, but it works when it is taller than it is wide. Seeing the bones helps. It looks like the code is working fine. The reason the bone jumps when you start dragging but moves fine after that is because you move the bone to the touch position when the drag starts. The drag start position is almost always some distance away from where the bone is, so the bone jumps to that position.

          You need to remember the distance from the bone position to the touch position when the drag starts. That offset needs to be added to the touch position as you drag the bone around. Something like:

          dragOffset = new spine.Vector2();
          ...
          // When the drag starts:
          dragging = true;
          startX = x;
          startY = y;
          target.localToWorld(dragOffset.set(0, 0)).sub(x, y);
          // When the drag moves:
          var newX = startX + 0.20 * (dragX - startX) + dragOffset.x;
          var newY = startY + 0.20 * (dragY - startY) + dragOffset.y;

            Nate
            Thank you very much for your reply.
            We want to provide it only on mobile screens, not on wide screens.
            In the code you answered, the sub(x,y) part is giving an error.
            Can you tell me what "sub" is used for?
            The code I wrote is as follows, but it is impossible to check due to an error, so I am asking again.

                        input.addListener({
                        down: (x, y) => {
                          startX = x;
                          startY = y;
                            // mouse.set(x, canvas_1.clientHeight - y, 0)
                            var bestDistance = 50,
                              index = 0;
                            var best;
                            for (var i = 0; i < controlbones.length; i++) {
                              hoverTargets[i] = null;
                              let bone = skeleton.findBone(controlbones[i]);
                              var position = new spine.Vector2(bone.length, 0);
                              bone.localToWorld(position);
                              // let distance = renderer.camera.worldToScreen(
                              //   coords.set(bone.worldX, bone.worldY, 0),
                              //   canvas_1.clientWidth, canvas_1.clientHeight).distance(mouse);
                              renderer.camera.screenToWorld(coords.set(x, y, 0), canvas_1.clientWidth, canvas_1.clientHeight);
                              let distance = Math.sqrt((coords.x - bone.x) * (coords.x - bone.x) + (coords.y - bone.y) * (coords.y - bone.y));
                              if (distance < bestDistance) {
                                bestDistance = distance;
                                best = bone;
                                index = i;
                              }
                            }
                            if (best) hoverTargets[index] = best;
                            target = best;
                        },
                        up: (x, y) => {
                          target = null;
                          dragging = false;
                        },
                        dragged: (x, y) => {
                          currentX = x;
                          currentY = y;
                          const distanceX = currentX - startX;
                          const distanceY = currentY - startY;
                          if(!dragging && (Math.abs(distanceX) > 5 || Math.abs(distanceY) > 5)){
                            dragging = true;
                            startX = x;
                            startY = y;
                          }
            
                          // hair_distance = Math.sqrt(distanceX ** 2 + distanceY ** 2);
                          if (dragging) {
                            dragged(canvas_1, renderer, target, x, y);
                          }
                        }
                    })
            
            
            
                    function dragged(canvas_1, renderer, target, x, y){
                    if (target) {
                     x = spine.MathUtils.clamp(x, 0, canvas_1.clientWidth);
                     y = spine.MathUtils.clamp(y, 0, canvas_1.clientHeight);
                     target.localToWorld(dragOffset.set(0, 0)).sub(x,y);
                     dragX = x;
                     dragY = y;
            
            
                     var newX = startX + 0.20*(dragX - startX) + dragOffset.x;
                     var newY = startY + 0.20*(dragY - startY) + dragOffset.y;
            
            
                     renderer.camera.screenToWorld(coords.set(newX, newY, 0), canvas_1.clientWidth, canvas_1.clientHeight);
                     if (target.parent !== null) {
                        target.parent.worldToLocal(position.set(coords.x, coords.y));
            
                        target.x = position.x;
                        target.y = position.y;
            
                     } else {
                        target.x = coords.x;
                        target.y = coords.y;
                     }
                    }
                  }
            • 已编辑

            Oh, sorry, the spine-ts Vecotr2 class doesn't have the sub method, which is for subtraction. You can use:

            target.localToWorld(dragOffset.set(0, 0));
            dragOffset.x -= x;
            dragOffset.y -= y;

              Nate

              sorry. I didn't understand your answer.
              According to the answer, I showed the code like this, but it didn't work as I wanted, so I'm asking again.

                    function dragged(canvas_1, renderer, target, x, y){
                      if (target) {
                       x = spine.MathUtils.clamp(x, 0, canvas_1.clientWidth);
                       y = spine.MathUtils.clamp(y, 0, canvas_1.clientHeight);
                       target.localToWorld(dragOffset.set(0, 0));
                       target.x -= x;
                       target.y -= y;
                       dragX = x;
                       dragY = y;
              
              
                       var newX = startX + 0.20*(dragX - startX);
                       var newY = startY + 0.20*(dragY - startY);
              
              
                       renderer.camera.screenToWorld(coords.set(newX, newY, 0), canvas_1.clientWidth, canvas_1.clientHeight);
                       if (target.parent !== null) {
                          target.parent.worldToLocal(position.set(coords.x, coords.y));
              
                          target.x = position.x;
                          target.y = position.y;
              
                       } else {
                          target.x = coords.x;
                          target.y = coords.y;
                       }
                      }
                    }

              You explained it in a simple way, but I'm sorry I didn't understand.

              No problem. You should understand the concept: when you start dragging, if you move the bone to the drag position, it will jump from where it was to there. You need to remember the offset from the position where you start dragging to the bone. As you drag, the bone needs to be set to the drag position plus this offset. That way the bone does not jump when your drag begins.

              You placed the computation of the offset in the code that happens when the drag is moved. Instead you want to compute the offset only once, when the drag starts. Also you could make your code a little more organized and nicer to read. Try this:

              input.addListener({
              	down: (x, y) => {
              		startX = x;
              		startY = y;
              		var bestDistance = 50,
              		index = 0;
              		var best;
              		for (var i = 0; i < controlbones.length; i++) {
              			hoverTargets[i] = null;
              			let bone = skeleton.findBone(controlbones[i]);
              			var position = new spine.Vector2(bone.length, 0);
              			bone.localToWorld(position);
              			renderer.camera.screenToWorld(coords.set(x, y, 0), canvas_1.clientWidth, canvas_1.clientHeight);
              			let distance = Math.sqrt((coords.x - bone.x) * (coords.x - bone.x) + (coords.y - bone.y) * (coords.y - bone.y));
              			if (distance < bestDistance) {
              				bestDistance = distance;
              				best = bone;
              				index = i;
              			}
              		}
              		if (best) hoverTargets[index] = best;
              		target = best;
              	},
              	up: (x, y) => {
              		target = null;
              		dragging = false;
              	},
              	dragged: (x, y) => {
              		currentX = x;
              		currentY = y;
              		if (!dragging && (Math.abs(currentX - startX) > 5 || Math.abs(currentY - startY) > 5)) {
              			dragging = true;
              			startX = x;
              			startY = y;
              			target.localToWorld(dragOffset.set(0, 0));
              			dragOffset.x -= x;
              			dragOffset.y -= y;
              		}
              		if (dragging) {
              			dragged(canvas_1, renderer, target, x, y);
              		}
              	}
              });
              
              function dragged(canvas_1, renderer, target, x, y) {
              	if (!target) return;
              
              	x = spine.MathUtils.clamp(x, 0, canvas_1.clientWidth);
              	y = spine.MathUtils.clamp(y, 0, canvas_1.clientHeight);
              	dragX = x;
              	dragY = y;
              
              	var newX = startX + 0.20 * (dragX - startX) + dragOffset.x;
              	var newY = startY + 0.20 * (dragY - startY) + dragOffset.y;
              
              	renderer.camera.screenToWorld(coords.set(newX, newY, 0), canvas_1.clientWidth, canvas_1.clientHeight);
              	position.set(coords.x, coords.y);
              	if (target.parent) target.parent.worldToLocal(position);
              	target.x = position.x;
              	target.y = position.y;
              }

              target.localToWorld(dragOffset.set(0, 0)); is the world position of the bone before it is dragged. You could also use dragOffset.set(target.worldX, target.worldY). You then subtract the position of the start of the drag, leaving you with the offset from the drag start position to the bone. Later you add that to the bone position as it is dragged.

                Nate
                Thank you very much for your reply. I understand your answer.
                Thank you for the easy-to-understand code.

                I guess this will be my last question.

                target.localToWorld(dragOffset.set(target.worldX, target.worldY));

                The standard position before dragging the skeleton is currently set to target.world.X, target.world.Y, but the standard position does not match the screen coordinates, so it is out of the screen.

                To match the coordinates on the screen, shouldn't we use the renderer.camera.screenToWorld code to match the coordinates?

                It seems that only the corresponding coordinates need to be solved in the code currently used, but I would like to ask you again how to do this part.

                Thanks as always for your answers.

                This code doesn't make sense:

                target.localToWorld(dragOffset.set(target.worldX, target.worldY));

                You don't want to use a world position as a local position. You can use:

                target.localToWorld(dragOffset.set(0, 0));

                The local position 0,0 for the target bone is the bone's origin and this converts it to world coordinates. This gives the same result:

                target.parent.localToWorld(dragOffset.set(target.x, target.y));

                There we are using the target bone's local position in its parent bone coordinates. The root bone doesn't have a parent bone though, so needing to check if parent is null is annoying.

                And finally this also gives the same result:

                dragOffset.set(target.worldX, target.worldY);

                When the bone's world transform is computed, using by calling Skeleton updateWorldTransform, the world position is stored, so you can just use that.

                Using one of the 3 correct options above may solve your problem of the bone being off screen. Usually it's easiest to keep things in world coordinates and only use screenToWorld to map the mouse position into the world coordinates.

                  Nate
                  I used the code you provided.

                  target.localToWorld(dragOffset.set(0, 0));

                  However, in this code, a problem with broken coordinates appears, so we continue to ask.
                  I'll upload it as a video, so please check it out.

                  It's hard to say what's wrong. Probably it has to do with half your code using screen coordinates, as you mentioned. Convert the touch from screen to world coordinates and do everything in world coordinates. For example, you do this for every control bone:

                  renderer.camera.screenToWorld(coords.set(x, y, 0), canvas_1.clientWidth, canvas_1.clientHeight);

                  You only need to do that once and then use world coordinates for everything else.

                  Otherwise you will need to debug your code. Check the value of dragOffset after you subtract x and y. It should be the distance from the bone to the touch when the drag started.

                    Nate
                    I'll send you an example using the two codes you provided.

                    target.parent.localToWorld(dragOffset.set(target.x, target.y));

                    When using this code, the values of dragOffset.x and dragOffset.y are all expressed as 0.

                    We will upload a video when working with the result value.

                    I'm still seeing the same symptoms.

                    So I proceeded to work with the code you originally sent.

                               target.localToWorld(dragOffset.set(0, 0));

                    When using this code, as I said, the coordinates did not match, so I adjusted the coordinates.

                               renderer.camera.screenToWorld(coords.set(x, y, 0), canvas_1.clientWidth, canvas_1.clientHeight);
                    
                               target.localToWorld(dragOffset.set(0, 0));
                               dragOffset.x -= coords.x;
                               dragOffset.y -= coords.y;

                    If modified as described above, the values of dragOffset.x and dragOffset.y are modified.

                    When I do this, the values of dragOffset.x and dragOffset.y appear, but the problem is still visible.

                    We will upload a video of the modified code.

                    Same issue. Is there anything else that needs to be changed?

                    I'm afraid I can't debug your application code like this. You will need to understand your code and what is happening so you can determine how to get the behavior you desire. I suggest to build a simpler example. Add lots of drawing that shows the position of everything involved: touch down, drag start, the bone position when the drag started, etc. It helps to visualize those positions so it will be obvious when one is wrong.

                      Nate
                      I found the problem in the code.

                             var newX = startX + 0.20 * (dragX - startX);
                             var newY = startY + 0.20 * (dragY - startY);

                      The code that makes it move by 0.2 was the problem, and the problem can be solved by applying the code below.

                             var newX = dragX;
                             var newY = dragY;

                      But the code I want is to move only .0.2, is there any other way?