I don't know. My previous advice stands. Can you show what it looks like?
I would like to fix the problem when the armature is moved by touch.
I don't really understand what is going in the video or what the desired behavior would look like. If you are moving bones by touch then it seems like the problem is likely in your code that moves the bones.
this is my code.
input.addListener({
down: (x, y) => {
startX = x;
startY = y;
mouse.set(x, canvas_1.clientHeight - y, 0)
var bestDistance = 50,
index = 0;
var best;
var set_render =renderer.camera.screenToWorld(coords.set(x, y, 0), canvas_1.clientWidth, canvas_1.clientHeight);
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;
},
dragged: (x, y) => {
currentX = x;
currentY = y;
const distanceX = currentX - startX;
const distanceY = currentY - startY;
hair_distance = Math.sqrt(distanceX ** 2 + distanceY ** 2);
if (30 < hair_distance) {
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.25*(dragX - startX);
var newY = startY + 0.25*(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;
}
}
}
}
The code to move the armature is the same as the example.
There is no bouncing if you move the bone outside of the hairless area. However, if you move the skeleton to the inside where the hair is, bouncing occurs.
When moving with a very short touch, the problem is coming out. Is there any way to fix this?
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.
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.
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?
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.
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.
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;
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.