Search Unity

Question Get Vector perpendicular to another Vector

Discussion in 'Scripting' started by bearybro, May 30, 2023.

  1. bearybro

    bearybro

    Joined:
    Feb 4, 2021
    Posts:
    4
    This should be very easy but I've been stumped for way too long on this,

    I want to make it so enemies are slightly realigned towards the front of the player when he's attacking them. I don't want them to move directly towards the player since that would make them get sucked in while being knocked back, so I only want them to move horizontally relative to the player, scaling with distance. But how would I get this Vector3?
    Can't get Cross Product to work
    budini.png
     
  2. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    2,433
    Cross product will give you a vector that is perpendicular to TWO other vectors. The two vectors you give need to be significantly different, and ideally also perpendicular to each other but that's not necessary. If you really only have one vector, you can find an arbitrary perpendicular vector with something like
    Vector3.Cross(myVector, new Vector3(1, -2, 3)*myVector + new Vector(3, -2, 1))
    [the numbers aren't all that important, they're just trying to invent another vector that isn't likely to be equal/parallel to myVector].

    But I don't think that's really what you want here. Having a little hard time figuring out what you mean by "sucked in" or "horizontally relative to the player."

    I am looking at your diagram as if it's a top-down view on a 3D scene. I am assuming enemy.transform.up and player.transform.up are both facing upward towards us. If you want something that is perpendicular to player.transform.forward, you already know that player.transform.right AND player.transform.up both meet that criteria.

    If you want the enemy to face a point that is N meters ahead of player, on your red dot, that's
    player.transform.position + N * player.transform.forward
    . Have your enemy face that point with Transform.LookAt() or compute that goal rotation with Quaternion.LookRotation() and slerp or torque your way to it.
     
  3. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,619
    Yeah, the red line you've got there coming from the "Enemy" box is going to be pretty similar to the Player box's transform.right, unless your player is tilted sideways.

    As halley says, a cross product requires two vectors, and will give you a vector which is perpendicular to both. In this case a cross product of the two red vectors would give you a new vector pointing up or down. If you want a horizontal vector then you can get that via a cross product between your original direction and Vector3.up.

    So when the player attacks, the enemy should move to be more directly in front of the player, or look at the player? Your meaning here is ambiguous.
     
  4. koirat

    koirat

    Joined:
    Jul 7, 2012
    Posts:
    2,073
    Stuck - probably.
     
  5. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,105
    No he means "sucked in". If you imagine a bunch of enemies ganging up, when he knocks them back, they all rush in towards the player i.e. the player is like a vacuum.
     
    koirat likes this.
  6. koirat

    koirat

    Joined:
    Jul 7, 2012
    Posts:
    2,073
    What he needs is some steering behaviors, so enemies are repelling each other.
     
    orionsyndrome likes this.
  7. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,105
    OP you can very easily take a projection on XZ plane, if that is indeed your top-down view of the situation.
    Code (csharp):
    1. static public Vector2 ToXZ(Vector3 v) => new Vector2(v.x, v.z);
    and you can go back
    Code (csharp):
    1. static public Vector3 FromXZ(Vector2 v, float y = 0f) => new Vector3(v.x, y, v.y);
    Such projections are simple 2D vectors then, and then you can find a perpendicular vector as trivially as this
    Code (csharp):
    1. static public Vector2 Perp(Vector2 v) => new Vector2(-v.y, v.x);
    If you still want an orthogonal 3D vector, you can use the following method (which is very close to Unity's own)
    Code (csharp):
    1. static public Vector3 FastOrthogonal(Vector3 v, bool normalize = true) {
    2.   var sqr = v.x * v.x + v.y * v.y;
    3.   if(sqr > 0f) { // (0,0,1) x (x,y,z)
    4.     var im = normalize? 1f / MathF.Sqrt(sqr) : 1f;
    5.     return new Vector3(-v.y * im, v.x * im, 0f);
    6.   } else {      // (1,0,0) x (x,y,z)
    7.     sqr = v.y * v.y + v.z * v.z;
    8.     var im = normalize? 1f / MathF.Sqrt(sqr) : 1f;
    9.     return new Vector3(0f, -v.z * im, v.y * im);
    10.   }
    11. }
    Leave normalize as
    true
    only if the vector you supply is not of unit length.
    This should probably be called FastOrthonormal when I think about it.

    However, this is only really useful for some advanced math, as there are no guarantees that this vector won't end up pointed away from your plane altogether. The resulting vector is simply on a plane that is dual to a given vector. Quite useful when trying to find binormals etc.

    So what you can do instead is to not use a cross, but to use a dot. How?
    Well, take the player's forward, and take (enemy - player) delta.
    When you dot these two you'll get the length from the player to the dot you've drawn on the picture.
    Now all you have to do is to find a vector perpendicular to player's forward, thus
    Code (csharp):
    1. var fwd = player.transform.forward;
    2. var perp = FromXZ(Perp(ToXZ(fwd), normalize: false), fwd.y);
    3. var delta = player.transform.position - enemy.transform.position;
    4. var enemyDir = Vector3.Dot(delta, fwd) * perp;
    Edit3: this code is wrong, check the end of the post (the description below is okay, though)

    Here's an image why this works (imagine if B was player forward, so what you're getting is some factor of vector A getting projected on this line, but since you know B is a unit vector, you can scale that and find the where the vector A would land if projected)



    Edit: I'm pretty sure delta should be player - enemy, not enemy - player, because it should be inversed, so fixed it. But just so you know, I'm doing this from my head, and maybe the result is the same, can't tell.

    Edit2: I think Perp will rotate the whole thing wrongly, so there are some extra steps, it's either
    * perp
    or
    * -perp
    in the last line, I would have to get my hands dirty to sort that out

    Edit3: And I actually wrote a description that doesn't fit the code! :D (now I'm officially becoming like ChatGPT) You don't need perp at all, simply multiply the result with fwd, and you get the point. Now make a direction from this point. Just focus on the image.
    Code (csharp):
    1. var fwd = player.transform.forward;
    2. var player = player.transform.position;
    3. var enemy = enemy.transform.position;
    4. var perpPoint = player + Vector3.Dot(player - enemy, fwd) * fwd;
    5. var enemyDir = (perpPoint - enemy).normalized;
     
    Last edited: May 30, 2023
  8. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,105
    Yes, makes sense. But he'd still need to learn how to manipulate directions for that.
     
  9. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,105
    By discovering even more arcane secrets, of course!
     
    angrypenguin likes this.
  10. bearybro

    bearybro

    Joined:
    Feb 4, 2021
    Posts:
    4
    Thank you all for the replies, sorry I phrased the question poorly but I was very tired and angry when I wrote it lol

    I just realised I could simply do it like this:

    Code (CSharp):
    1. float pullValue = player.InverseTransformPoint(enemy.position).x;
    2. enemy.AddForce(player.right * pullValue, ForceMode.VelocityChange);