Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

3D game, Offscreen Target Indicator?

Discussion in 'Scripting' started by Rob21894, Sep 15, 2016.

  1. Rob21894

    Rob21894

    Joined:
    Nov 21, 2013
    Posts:
    309
    Hey guys

    I was wondering how I would go about making an offscreen target indicator that Rotates around the edge's of the screen based on the players/cameras current position?

    I know there's asset packages but I wan't to attempt this myself


    Any steps/tutorials would be great to see

    I found this but nothing is explained :/
     
    Last edited: Sep 15, 2016
  2. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Is this a fully 3D movement like a 3D Space game, or are we talking about 3D running around on terrain, so all we really care about is where our target is on the XZ plane?
     
  3. Rob21894

    Rob21894

    Joined:
    Nov 21, 2013
    Posts:
    309
    Only on the XZ plane :p
     
  4. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    I would handle it like this:. First off we want to work in a system where directly forwards is 0 degrees. And anything to our left goes from 0 to -180 degrees (directly left would be -90). Similarly, things to our right are 0 to +180.
    Also we want to draw an arrow on the edge of the screen. We can say between -45 and +45 degrees is the top of the screen. -45 to -135 is the left side and, +45 to +135 is the right side. We probably don't want to draw on the bottom of the screen, I imagine all kinds of UI elements are down there, so we just clamp the angle between -135 and 135.
    • Calculate the angle between you and the target
    • Adjust that angle based on your rotation (you might not be facing directly forward)
    • Get our Arrow Image and rotate it based on this angle
    • Place our arrow image on the correct screen edge adjusted along its length based on angle.
    Here is some code to get an angle between you and a target adjusted for rotation and clamped between -135 and 135
    Code (CSharp):
    1. float GetAngle(GameObject target)
    2.     {
    3.         float angle;
    4.         float xDiff = target.transform.position.x - transform.position.x;
    5.         float zDiff = target.transform.position.z - transform.position.z;
    6.    
    7.         angle = Mathf.Atan(xDiff / zDiff) * 180f / Mathf.PI;
    8.         // tangent only returns an angle from -90 to +90.  we need to check if its behind us and adjust.
    9.         if (zDiff < 0)
    10.         {
    11.             if (xDiff >= 0)
    12.                 angle += 180f;
    13.             else
    14.                 angle -= 180f;
    15.         }
    16.  
    17.         // this is our angle of rotation from 0->360
    18.         float playerAngle = transform.eulerAngles.y;
    19.         // we  need to adjust this to our -180->180 system.
    20.         if (playerAngle > 180f)
    21.             playerAngle = 360f - playerAngle;
    22.  
    23.         // now subtract the player angle to get our relative angle to target.
    24.         angle -= playerAngle;
    25.  
    26.         // Make sure we didn't rotate past 180 in either direction
    27.         if (angle < -180f)
    28.             angle += 360;
    29.         else if (angle > 180f)
    30.             angle -= 360;
    31.  
    32.         // now we have our correct relative angle to the target between -180 and 180
    33.         // Lets clamp it between -135 and 135
    34.         Mathf.Clamp(angle, -135f, 135f);
    35.         return angle;
    36.     }
    Now that you have the angle you just need to get your image your going to display. I suggest using the new UI, and just create an arrow Sprite. You can enable/disable it based on if your target is off screen or not. Here is some code to place the UI sprite to the correct edge and rotate based on an angle:

    This code assumes the Image is created with the default settings. Anchors and the pivot are all in the centered positions (0.5f) for all settings
    Code (CSharp):
    1. using UnityEngine.UI;
    2.  
    3. public Image arrow;
    4.  
    5. void PlaceSprite(float angle)
    6.     {
    7.         // Get half the Images width and height to adjust it off the screen edge;
    8.         RectTransform arrowRect = arrow.GetComponent<RectTransform>();
    9.         float halfImageWidth = arrowRect.sizeDelta.x / 2f;
    10.         float halfImageHeight = arrowRect.sizeDelta.y / 2f;
    11.  
    12.         // Get Half the ScreenHeight and Width to position the image
    13.         float halfScreenWidth = (float)Screen.width / 2f;
    14.         float halfScreenHeight = (float)Screen.height / 2f;
    15.  
    16.         float xPos = 0f;
    17.         float yPos = 0f;
    18.  
    19.         // Left side of screen;
    20.         if (angle < -45)
    21.         {
    22.             xPos =  -halfScreenWidth+ halfImageWidth;
    23.             // Ypos can go between +ScreenHeight/2  down to -ScreenHeight/2
    24.             // angle goes between -45 and -135
    25.             // change angle to a value between 0f and 1.0f and Lerp on that
    26.             float normalizedAngle = (angle + 45f) / -90f;
    27.             yPos = Mathf.Lerp(halfScreenHeight, -halfScreenHeight, normalizedAngle);
    28.             // at the top of the screen we need to move the image down half its height
    29.             // at the bottom of the screen we need to move it up half its height
    30.             // in the middle we need to do nothing. so we lerp on the angle again
    31.             float yImageOffset = Mathf.Lerp(-halfImageHeight, halfImageHeight, normalizedAngle);
    32.             yPos += yImageOffset;
    33.        
    34.         }
    35.         // top of screen
    36.         else if (angle < 45)
    37.         {
    38.             yPos = halfScreenHeight - halfImageHeight;
    39.             float normalizedAngle = (angle + 45f) / 90f;
    40.             xPos = Mathf.Lerp(-halfScreenWidth, halfScreenWidth, normalizedAngle);
    41.             float xImageOffset = Mathf.Lerp(halfImageWidth, -halfImageWidth, normalizedAngle);
    42.             xPos += xImageOffset;
    43.         }
    44.         // right side of screen
    45.         else
    46.         {
    47.             xPos = halfScreenWidth - halfImageWidth;
    48.             float normalizedAngle = (angle - 45) / 90f;
    49.             yPos = Mathf.Lerp(halfScreenHeight, -halfScreenHeight, normalizedAngle);
    50.             float yImageOffset = Mathf.Lerp(-halfImageHeight, halfImageHeight, normalizedAngle);
    51.             yPos += yImageOffset;
    52.         }
    53.  
    54.         arrowRect.anchoredPosition = new Vector3(xPos, yPos, 0);
    55.         // UI rotation is backwards from our system.  Positive angles go counterclockwise
    56.         arrowRect.Rotate(Vector3.forward, -angle);
    57.     }
     
    Last edited: Sep 15, 2016
    ycanatilgan likes this.
  5. ChadrickEvans

    ChadrickEvans

    Joined:
    Mar 14, 2015
    Posts:
    46
    How would I go about using this indicator but for the bottom edge of the screen only?
     
  6. ycanatilgan

    ycanatilgan

    Joined:
    Aug 23, 2017
    Posts:
    30
    I found a little bug in this code. Except that, it worked perfectly, thanks!

    I leave the fix for those who may use this code in the future.

    In the first code block, line 20, use this instead:

    Code (CSharp):
    1.         if (playerAngle > 180f)
    2.             playerAngle = playerAngle - 360f;