Download

Using mouse position and line trace to drag object has major jittering and lag.

So I’m working on making it so users can drag objects along a surface in 3d using the mouse. The issue I’m running into is it’s all jerky and lags behind the mouse. Initially I was using a hardware mouse cursor but I have changed to software that made the lag better but it’s still pretty bad as you can see:

https://www.youtube.com/watch?v=k9ChGSKN-2U

https://www.youtube.com/watch?v=ADwN5bvW-qA

https://youtube.com/watch?v=wKVi3h6N3Q8

Here is the code I am using for the drag operation (in PlayerController):



void AMyPlayerController::Tick(float DeltaTime) {
    Super::Tick(DeltaTime);
    if (isGrabbingPiece && !isRotatingCamera) {
        UWorld* const World = GetWorld();
        if (World) {
            float mouseX;
            float mouseY;
            FVector startLocation;
            FVector WorldDirectionReturned;

            UGameplayStatics::GetPlayerController(World, 0)->GetMousePosition(mouseX, mouseY);
            DeprojectScreenPositionToWorld(mouseX, mouseY, startLocation, WorldDirectionReturned);
            FVector endLocation = startLocation + (WorldDirectionReturned* traceDistance);

            TArray<AActor*> ActorsToIgnore;
            ActorsToIgnore.Add(this);
            ActorsToIgnore.Add(currentMovingPiece);

            const FName TraceTag("MyTraceTag");
            World->DebugDrawTraceTag = TraceTag;
            FCollisionQueryParams TraceParams(TraceTag, true, ActorsToIgnore[0]);
            TraceParams.bTraceComplex = false;
            TraceParams.AddIgnoredActors(ActorsToIgnore);

            FHitResult hitObject;
            hitObject = FHitResult(ForceInit);
            World->LineTraceSingleByChannel(hitObject, startLocation, endLocation, ECollisionChannel::ECC_Visibility, TraceParams);
            //GetHitResultUnderCursorByChannel(UEngineTypes::ConvertToTraceType(ECC_Visibility), true, hitObject);

            FCollisionObjectQueryParams ObjectList;
            ObjectList.AddObjectTypesToQuery(ECC_Pawn);
            FVector hitPoint = hitObject.Location;
            if (hitObject.GetActor()) {
                if (hitObject.GetActor()->IsA(AtableActor::StaticClass())) {
                    currentMovingPiece->puzzleMesh->SetWorldLocation(FVector(hitPoint.X, hitPoint.Y, hitPoint.Z + 20));
                }
            }
        }
    }
}


I have tried it in Tick, with a mouse X/Y listener, and on a timer with similar results.

Sometimes when moving and then stopping the mouse the cube doesn’t move and you can see it is way off where it should be. Even though it should be calculating mouse position on Tick it will stay this way until I move the mouse a tiny bit then it snaps into place:

https://youtube.com/watch?v=869_hV3zVPk

My only guess, since it is recalculating every tick is the mouse position isn’t updated often enough.

Any suggestions on how to improve this? The game I’m making requires fast and precise dragging of objects and at it’s current state it would not work at all.

This may not be anything to do with it, but could it be that you are getting worlds, player controllers, setting query params etc all in the tick. Could you pre-cache these?

I’ll give it a shot in a bit. I think the last example that shows the object getting off position from the mouse indefinitely until I tap the mouse shows it’s not a performance problem exactly though.

Actually good point though as it made me realize I’m getting a playercontroller IN the playercontroller. Woops :slight_smile:

Ok I cleaned it up a bit but no difference. Here is the modified code:



void AMyPlayerController::Tick(float DeltaTime) {
    Super::Tick(DeltaTime);
    if (isGrabbingPiece && !isRotatingCamera) {
        if (World) {
            float mouseX;
            float mouseY;
            FVector startLocation;
            FVector WorldDirectionReturned;

            GetMousePosition(mouseX, mouseY);
            DeprojectScreenPositionToWorld(mouseX, mouseY, startLocation, WorldDirectionReturned);
            FVector endLocation = startLocation + (WorldDirectionReturned* traceDistance);

            TArray<AActor*> ActorsToIgnore;
            ActorsToIgnore.Add(this);
            ActorsToIgnore.Add(currentMovingPiece);

            const FName TraceTag("MyTraceTag");
            World->DebugDrawTraceTag = TraceTag;
            FCollisionQueryParams TraceParams(TraceTag, true, ActorsToIgnore[0]);
            TraceParams.bTraceComplex = false;
            TraceParams.AddIgnoredActors(ActorsToIgnore);

            FHitResult hitObject;
            hitObject = FHitResult(ForceInit);
            World->LineTraceSingleByChannel(hitObject, startLocation, endLocation, ECollisionChannel::ECC_Visibility, TraceParams);
            //GetHitResultUnderCursorByChannel(UEngineTypes::ConvertToTraceType(ECC_Visibility), true, hitObject);

            FCollisionObjectQueryParams ObjectList;
            ObjectList.AddObjectTypesToQuery(ECC_Pawn);
            FVector hitPoint = hitObject.Location;
            if (hitObject.GetActor()) {
                if (hitObject.GetActor()->IsA(AtableActor::StaticClass())) {
                    currentMovingPiece->puzzleMesh->SetWorldLocation(FVector(hitPoint.X, hitPoint.Y, hitPoint.Z + 20));
                }
            }
        }
    }
}

Don’t do that in Tick. The result will always come in next frame, causing stutter.

You can just attach/detach the picket target to a pivot transform relative to camera instead of trying to force it’s current location to a vector that is only returning from hit result the next frame.

First of all thanks for the response!

I’m sorry, maybe just inexperienced here but I have no idea what this means. “picket target” = the thing I’m trying to drag? Also how can I get the object to slide on the surface relative to mouse position with pivot transform from camera. Do you have any examples of methods used for this to point me in the right direction?

Also about the Tick in the last example it shows how the mouse position might not be updated no matter how many frames have passed. As it doesn’t snap to the piece until I move the mouse again.

I mean there’s no reason for a hitcast every frame.
Is better to just code a pickup / drop off mechanic, like drag & drop UI items.

The way you’re doing this it will always lag behind current mouse location because you are always using old hit results inside of that tick.

Ok yea I am trying to code a pickup/drop of mechanic. I guess I’m unsure of how to do that without hitcasting from the mouse screen position into the world to keep the object under the mouse cursor.

I’ve been messing around with using mouse axis input and adding that to the last known mouse x/y and updating with camera position. Seems promising as it moves smooth however I need to multiply the value sent (from 0 to 1) by a certain value and not sure if that’s going to be consistant. Grabbing the GetMousePosition in the axis listener has the same issues so a no go.

Edit: Yea using mouse axis won’t work with different camera angles cause movement of object isn’t linear on the surface. As well as it won’t work for uneven surfaces like going over other objects.

So using mouse Axis instead of checking mouse position per frame is super smooth. Issue is the value sent is a 0-1 and I cannot come up with a way to use that to keep track of exact mouse position on screen. It will always drift off.

However since it is very smooth that really leads me to believe that GetMousePosition is the culprit. I was still using all the same other line trace code except for instead of grabbing GetMousePosition I was trying to guess the position with the amount sent from the axis.

So I guess what I really need now is an alternative to GetMousePosition that updates more often.

I found the issue! It is for sure GetMousePosition, but in a strange way.

Came across this old post Mouse Position Values not correctly updating - UE4 AnswerHub and further down someone explains how when left mouse button is pressed the updating of mouse x/y can get messed up. They seem to think in low fps cases but it’s actually all the time. Just in the low frame rate cases things like that shown in my last video where it gets stuck happen more frequently.

I changed my code to drag the object all the time (currently I “grab” it by holding down the left mouse button). Sure enough with the exact same code it runs smoothly.

Then as soon as I hold down left mouse it gets all jerky like in the example videos. No mouse button down, smooth / mouse button down, jerky.

Well I know the issue but unfortunately it’s deep in the engine. Wish there was a way to fix it without recompiling.