Arrow rotating to face cursor needs to only do so while inside an angle made by two given directions
Asked Answered
N

1

6

I have a 2d arrow rotating to always face the a target (the target in this case is the cursor), the pivot is my player character. I need to restrict this arrow to only follow the target if it is inside an angle of the player, an example would be 90 degrees, so it would only follow if the cursor is in the top right part of the screen.

I have worked with vector directions and methods such as Vector2D.angle, but they all seem to have some restriction i can't workaround, Vector2D.angles restriction is that the 3rd position it uses to calculate the angle is the world center(0, 0), my player is mobile so that doesn't work. So i think what im asking is if theres a way to store an angle, and then check if something is within that.

Here is the code i use for rotating my arrow, theres more to the script but i removed the unnecesary parts:

    public float speed;
    public Transform target;

 void Update()
    {
        Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector2 direction = target.position - transform.position;
            target.position = mousePosition;
        float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
Quaternion rotation = Quaternion.AngleAxis(angle, Vector3.forward);
        transform.rotation = Quaternion.Slerp(transform.rotation, rotation, speed * Time.deltaTime);

Sorry if this is formatted poorly, its my first time posting here, thank you a million times if you are able to help me, i have been stuck on this for days.

Was asked to clarify question so here is an attempt:

Example

This picture shows an example of what i mean, the arrow is rotating around the center of the circle, (it is rotating so it always points towards my cursor). What i need is a way to restrict it so it only points towards the cursor if it is within a specific angle (red lines in picture), that's what im having trouble with. I can't find a way to store this threshold and i can't seem to find a way to compare the cursors direction with it.

I think it would be possible if it was possible to choose a custom center for Vector2D.angle, but that doesn't seem to be the case.

I hope this clarifies what my question, i may just very well be stupid and overlooking something obvious but i really can't find a way to make this possible. thanks again.

Naif answered 1/7, 2019 at 19:56 Comment(6)
Love the username reference, but just FYI, you might get warned about using offensive language in a post/question ("***load"), as it's not looked upon nicely by people hereThiazole
Oh im sorry about that, ill edit it out thanksNaif
Hey, can you please send some images(or videos) to tell what you exactly need? I couldn't get what's you goal.Become
Alright will edit into the bottom of the questionNaif
how are you defining the red lines? Is one always pointing up and you have the angle to the other line in degrees?Circumspection
Also, what is the local direction of the arrow's tip? Is that the arrow's right direction (red local axis arrow)? Maybe up (green local axis arrow) ?Circumspection
C
1

Important fields/inputs

First, we need to know the boundary directions in world space:

public Vector2 boundaryDirectionLeft;
public Vector2 boundaryDirectionRight;

The important piece is that the angle made clockwise from boundaryDirectionLeft to boundaryDirectionRight is the region the arrow shall remain inside. In the case of your image, boundaryDirectionLeft could be Vector2.up and boundaryDirectionRight could be something like new Vector2(1f,-1f).

We also need to know which local direction the arrow is facing before any rotation is applied. E.g., if the arrow is always pointing with the local red arrow axis (the local right direction), this would be Vector2.right:

public Vector2 localArrowDirection;

And lastly, we need a top speed for our rotation, so we don't warp the arrow around when it's time to rotate. A good value to start with might be 360f, 360 degrees per second. Try experimenting with different values to find one that you like:

public float maxRotationSpeed;

The update procedure

In Update, determine the direction to the target:

Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector2 direction = mousePosition - transform.position;

Then, we need to know where the arrow is currently pointing. We can use Transform.TransformVector to find where the local localArrowDirection is pointing in world space:

Vector2 currentDirection = transform.TransformVector(localArrowDirection);

Then, determine the signed angle from boundaryDirectionLeft to the target direction, the same from boundaryDirectionLeft to boundaryDirectionRight, and the same from boundaryDirectionLeft to the current facing direction:

float directionAngle = Vector2.SignedAngle(boundaryDirectionLeft, direction);
float boundaryAngle = Vector2.SignedAngle(boundaryDirectionLeft, boundaryDirectionRight);
float currentAngle = Vector2.SignedAngle(boundaryDirectionLeft, currentDirection);

These values range from [-180,180], but we want them expressed in the range [0,360) to make the math easier later, so we can add 360f and use Mathf.Repeat on that sum:

directionAngle = Mathf.Repeat(directionAngle+360f, 360f);
boundaryAngle = Mathf.Repeat(boundaryAngle+360f, 360f);
currentAngle = Mathf.Repeat(currentAngle+360f, 360f);

At this point directionAngle is how many clockwise degrees from boundaryDirectionLeft that target is, and boundaryAngle is how many boundaryDirectionRight is, and currentAngle is the same for what direction we're currently facing.

So, now, we need to know how to properly clamp the angle between 0 and boundaryAngle. Anything too far above boundaryAngle is actually closer to the left boundary and should be clamped to the left boundary. In fact, since everything is between 0 and 360, anything higher than boundaryAngle+(360f-boundaryAngle)/2f is closer to the left. So, we just set anything higher than that to be 0 degrees away from boundaryDirectionLeft:

if (directionAngle > boundaryAngle + (360f - boundaryAngle)/2f)
{
    directionAngle = 0f;
}

So, now we can clamp directionAngle with a high of boundaryAngle (it is already bottom clamped at 0f, so we can use Mathf.Min here):

directionAngle = Mathf.Min(directionAngle, boundaryAngle);

Now we can limit the angular difference between directionAngle and currentAngle using our maxRotationSpeed:

float deltaAngle = Mathf.Clamp(directionAngle-currentAngle,
                               -maxRotationSpeed * Time.deltaTime, 
                               maxRotationSpeed * Time.deltaTime);

Now we can rotate the transform deltaAngle degrees clockwise (in world space):

transform.Rotate(0f,0f,deltaAngle,Space.World);
Circumspection answered 1/7, 2019 at 22:36 Comment(6)
You are a hero! This makes total sense, you saved me of potential days of more research thanks a bunch!Naif
@14thcowboyoframranch I realized I didn't account for certain situations so I changed the answer substantially in a few place. Hope this helps!Circumspection
Thanks again, the edited pretty much fixed all problems i had with it before, except the behavior when the cursor exits boundaryAngle, it always rotates to the right boundary, instead of just staying at the boundary witch the cursor left at. If you see this comment and have the time, i would absolutely love if you could give me a way of changing that behaviour. I get that its a little late for me to comment though, thanks anyways you really saved me.Naif
@14thcowboyoframranch oop! I found the mistake I made. I mistakenly put if (directionAngle > directionAngle + (360f - boundaryAngle)/2f) instead of the correct condition if (directionAngle > boundaryAngle + (360f - boundaryAngle)/2f). This meant that it never evaluated to true when it should have. Try again with the now correct condition!Circumspection
Man you are a literal god, this works more than perfectly!Naif
Aw shucks 😊 you are too kind. Thanks for sticking around and offering support when it didn't work 100% at first.Circumspection

© 2022 - 2024 — McMap. All rights reserved.