Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create Ofscreen Enemy indicator in Unity 3D?

Tags:

unity3d

I am working on creating an Ofscreen Enemy indicator using the tutorial mentioned on below link. However I can get the indicator to rotate to point to the enemy but the indicator does not move from end to end of screen.

http://gamedevelopment.tutsplus.com/tutorials/positioning-on-screen-indicators-to-point-to-off-screen-targets--gamedev-6644

This is the desired outcome:

desired-outcome

Until now i have managed to figure out the below Please help.

var screenCenter:Vector3 = new Vector3(0.5, 0.5, 0f);
    //Note coordinates are translated
    //Make 00 the  centre of the screen instead of bottom left
    screenpos -= screenCenter;

    //find angle from center of screen instead of bototom left
    var angle:float = Mathf.Atan2(screenpos.y, screenpos.x);
    angle -= 90 * Mathf.Deg2Rad;

    var cos:float = Mathf.Cos(angle);
    var sin:float = -Mathf.Cos(angle);

    screenpos = screenCenter + new Vector3(sin*150, cos*150, 0);

    //y=mx + b format
    var m:float = cos/sin;
    var ScreenBounds:Vector3 = screenCenter;// * 0.9f;

    //Check up and down first
    if(cos > 0){
        screenpos = new Vector3(ScreenBounds.y/m, ScreenBounds.y, 0);
    }else{//down
        screenpos = new Vector3(-ScreenBounds.y/m, -ScreenBounds.y, 0);
    }
    //If out of bound then get point on appropriate side
    if(screenpos.x > ScreenBounds.x){//Out of bound must be on right
        screenpos = new Vector3(ScreenBounds.x, ScreenBounds.y*m, 0);
    }else if(screenpos.x < ScreenBounds.x){//Out of bound must be on left
        screenpos = new Vector3(-ScreenBounds.x, -ScreenBounds.y*m, 0);
    }
    //Remove the co ordinate translation
    screenpos += screenCenter;
    var DistanceIndicatorRectT = DistanceIndicator.GetComponent(RectTransform);
    DistanceIndicatorRectT.localPosition = new Vector3(screenpos.x * scrWidth/2, screenpos.y * scrHeight/2, DistanceIndicatorRectT.localPosition.z * screenpos.z);
    DistanceIndicator.transform.rotation = Quaternion.Euler(0, 0, angle*Mathf.Rad2Deg);
like image 912
DJ IV DJIV Avatar asked Aug 14 '15 08:08

DJ IV DJIV


2 Answers

I did a bit of a different approach than you, what Carlos suggested but without using physics.

If "t" is your target, this way you can get it's position on screen in pixels (if it's off screen it just goes to negative values or values higher that width)

Vector3 targetPosOnScreen = Camera.main.WorldToScreenPoint (t.position);

And this function that return a bool whether the Vector3 (in pixels) is on screen

bool onScreen(Vector2 input){
    return !(input.x > Screen.width || input.x < 0 || input.y > Screen.height || input.y < 0);
}

First thing we should do is check if the target is on screen, if it's not then proceed with code.

if (onScreen (targetPosOnScreen)) {
        //Some code to destroy indicator or make it invisible
        return;
    }

Then a simple calculation of angle between center of screen and target.

Vector3 center = new Vector3 (Screen.width / 2f, Screen.height / 2f, 0);
float angle = Mathf.Atan2(targetPosOnScreen.y-center.y, targetPosOnScreen.x-center.x) * Mathf.Rad2Deg;

Next part of code determines where the object is compared to camera based on angle we just calculated.

float coef;
if (Screen.width > Screen.height)
    coef = Screen.width / Screen.height;
else
    coef = Screen.height / Screen.width;

float degreeRange = 360f / (coef + 1);

if(angle < 0) angle = angle + 360;
int edgeLine;
if(angle < degreeRange / 4f) edgeLine = 0;
else if (angle < 180 - degreeRange / 4f) edgeLine = 1;
else if (angle < 180 + degreeRange /  4f) edgeLine = 2;
else if (angle < 360 - degreeRange / 4f) edgeLine = 3;
else edgeLine = 0;

http://s23.postimg.org/ytpm82ad7/Untitled_1.png

Image represents what value "edgeLine" will have based on target position (red represents camera's view) and black lines division of space.

And then we have this code which sets Transform "t2" (indicator) to correct position and angle.

t2.position = Camera.main.ScreenToWorldPoint(intersect(edgeLine, center, targetPosOnScreen)+new Vector3(0,0,10));
t2.eulerAngles = new Vector3 (0, 0, angle);

Below we have function "intersect" which code is:

Vector3 intersect(int edgeLine, Vector3 line2point1, Vector3 line2point2){
    float[] A1 = {-Screen.height, 0, Screen.height, 0};
    float[] B1 = {0, -Screen.width, 0, Screen.width};
    float[] C1 = {-Screen.width * Screen.height,-Screen.width * Screen.height,0, 0};

    float A2 = line2point2.y - line2point1.y;
    float B2 = line2point1.x - line2point2.x;
    float C2 = A2 * line2point1.x + B2 * line2point1.y;

    float det = A1[edgeLine] * B2 - A2 * B1[edgeLine];

    return new Vector3 ((B2 * C1[edgeLine] - B1[edgeLine] * C2) / det, (A1[edgeLine] * C2 - A2 * C1[edgeLine]) / det, 0);
}

We send to this function index of which line of camera's view (rectangle) we need to check intersection with, and construct a line between center of screen and target position.

For better explanation of this function look here : https://www.topcoder.com/community/data-science/data-science-tutorials/geometry-concepts-line-intersection-and-its-applications/

I just modified values of A1, B1 and C1, each of them is now array of 4 and each values represents value needed for one line of camera's view (rectangle).

If you want to implement margins just change the pivot of indicator (put the actual sprite renderer as child and move it in local space as you want).

Next thing would be making this work for array of targets and putting all those targets in given array. Hope this helps and don't be too hard on me, it's my first time posting here :)

like image 112
Neven Ignjic Avatar answered Oct 09 '22 20:10

Neven Ignjic


Create a rectangle box collider delimiting the borders of the screen and use Physics2D.Raycast in the direction of the enemy.

The point of collision will tell you where the green arrow needs to be drawn.

like image 24
cahen Avatar answered Oct 09 '22 18:10

cahen