FlawlessHappiness
FlawlessHappiness

Reputation: 81

How can I figure out the length in units from the player to the end of the camera view?

I'm working with Unity, in c#, and this is my setup.

The camera is facing down, to look at the cube. I'll be referring to the cube as the 'player' from now on.

The problem:

I want to spawn something where the camera cannot see it. This means I need to know exactly how far there is from the player to the end of the FOV, at the player's position.y (Which is always 0), in units.

The reason this cannot just be a constant variable is that the camera's position.y is not constant, and thus the player is allowed to see more of the game area the longer he plays.

There is extra info on what I've tried below, but if you have a better solution, I'll gladly take it.

Extra info:

This is the current script I'm sitting with:

using UnityEngine;
using System.Collections;

public class cameraFollowTarget : MonoBehaviour {

private Transform target;

private float defaultHeight = 3.0f;
private float cameraY;
private float playerSize;
private float moveSpeed = 2.0f;

//Playing with FOV//
private float verticalFOVrad;
private float verticalFOV;
private float cameraHeightAt1;
private float horizontalFOVrad;
private float horizontalFOV;

private float angleToPlayer;
private float defaultAngleToPlayer;

private Vector3 currentPos;
private Vector3 targetPos;
private Vector3 targetLookDir;
private Vector3 leftSideToCamera;
private Vector3 rightSideToCamera;

// Use this for initialization
void Start () {
    target = GameObject.FindGameObjectWithTag ("Player").transform;
    playerSize = target.GetComponent<playerCollect>().getPlayerSizeStart();

    transform.position = new Vector3(transform.position.x, defaultHeight, transform.position.z);
    cameraY = defaultHeight;

    calculateAngleToPlayer();
    defaultAngleToPlayer = angleToPlayer;
}

// Update is called once per frame
void FixedUpdate () {
    if (transform.position != target.position) {
        playerSize = GameObject.FindGameObjectWithTag("Player").GetComponent<playerCollect>().getPlayerSize();
        currentPos = transform.position;

        calculateAngleToPlayer();

        while(angleToPlayer > defaultAngleToPlayer)
        {
            cameraY += 0.1f;
            calculateAngleToPlayer();
        }

        targetPos = new Vector3(target.position.x, cameraY, target.position.z);
        transform.position = Vector3.Slerp(currentPos, targetPos, moveSpeed * Time.deltaTime);
        Camera.main.farClipPlane = cameraY + playerSize / 2;
    }

    if (transform.rotation != target.rotation) {
        Vector3 targetRot;

        //Not working
        //targetRot = Vector3.Lerp (transform.eulerAngles, new Vector3(90, target.transform.eulerAngles.y, 0), turnSpeed * Time.deltaTime);

        //Working but ugly
        targetRot = new Vector3(90, target.transform.eulerAngles.y, 0);

        transform.eulerAngles = targetRot;
    }

    //Playing with FOV//
    getVerticalFOV ();
    getHorizontalFOV ();

    print ("Horizontal = " + getHorizontalFOV());
    print ("Vertical = " + getVerticalFOV());
}

float getVerticalFOV()
{
    horizontalFOVrad = Camera.main.fieldOfView * Mathf.Deg2Rad;
    cameraHeightAt1 = Mathf.Tan(horizontalFOVrad * 0.5f);
    verticalFOVrad = Mathf.Atan(cameraHeightAt1 * Camera.main.aspect) * 2;
    verticalFOV = verticalFOVrad * Mathf.Rad2Deg;
    return verticalFOV;
}

float getHorizontalFOV()
{
    horizontalFOV = Camera.main.fieldOfView;
    return horizontalFOV;
}

float calculateAngleToPlayer()
{
    Vector3 imaginaryPos = new Vector3(target.position.x, transform.position.y + cameraY, target.position.z);

    leftSideToCamera = new Vector3(target.position.x - (playerSize / 2.0f), target.position.y, target.position.z) - imaginaryPos;
    rightSideToCamera = new Vector3(target.position.x + (playerSize / 2.0f), target.position.y, target.position.z) - imaginaryPos;
    angleToPlayer = Vector3.Angle(leftSideToCamera, rightSideToCamera);

    return angleToPlayer;
}

public float getScreenUnitHorizontal(GameObject target)
{
    GameObject imaginaryPos = new GameObject ();
    imaginaryPos.transform.position = target.transform.position;
    imaginaryPos.transform.rotation = Camera.main.transform.rotation;
    float screenUnitHorizontal = 0.0f;

    Vector3 vecMidToCamera = Camera.main.transform.position - new Vector3 (Camera.main.transform.position.x, 0.0f, Camera.main.transform.position.z);
    Vector3 vecImagiToCamera = Camera.main.transform.position - imaginaryPos.transform.position;

    while (Vector3.Angle(vecMidToCamera, vecImagiToCamera) <= getHorizontalFOV() / 2) {
        screenUnitHorizontal += 0.1f;

        imaginaryPos.transform.position = target.transform.forward * screenUnitHorizontal * Time.deltaTime;

        vecMidToCamera = Camera.main.transform.position - new Vector3 (Camera.main.transform.position.x, 0.0f, Camera.main.transform.position.z);
        vecImagiToCamera = Camera.main.transform.position - imaginaryPos.transform.position;
    }

    Debug.DrawLine(Camera.main.transform.position, new Vector3 (Camera.main.transform.position.x, 0.0f, Camera.main.transform.position.z), Color.red);
    Debug.DrawLine(Camera.main.transform.position, imaginaryPos.transform.position, Color.red);

    Destroy (imaginaryPos);
    return screenUnitHorizontal;
}

}

"GetScreenUnitHorizontal" is the main problematic function. It is working, but it is not doing what I was intending.

My intention:

Create a gameobject at the player's position. Then move the gameobject forward until it reaches an angle that is greater than the FOV.

This works fine, in (0, 0, 0). It does not if I move away, and rotate.

If I keep rotating one way, the gameobject seem to place itself in some kind of curve. Possibly a sinus curve.

The solution

31eee384 was the one who solved it for me.

The part of my code that is the solution looks like this:

public class cameraFollowTarget : MonoBehaviour {

private Ray lowerLeft;
private Ray lowerRight;
private Ray upperLeft;
private Ray upperRight;

void FixedUpdate () {
    lowerLeft = Camera.main.ScreenPointToRay(new Vector3(0, 0));
    lowerRight = Camera.main.ScreenPointToRay(new Vector3(Camera.main.pixelWidth, 0));
    upperLeft = Camera.main.ScreenPointToRay(new Vector3(0, Camera.main.pixelHeight));
    upperRight = Camera.main.ScreenPointToRay(new Vector3(Camera.main.pixelWidth, Camera.main.pixelHeight));
}

Vector3 getScreenCollisionPoint(Ray corner)
{
    Plane spawnPlane = new Plane (new Vector3 (0, 1, 0), Vector3.zero);

    float _Distance;
    Vector3 collisionPoint;

    if (spawnPlane.Raycast (corner, out _Distance) == true) {
        collisionPoint = corner.GetPoint(_Distance);
    }

    Debug.DrawLine(transform.position, collisionPoint, Color.red);
    return collisionPoint;
}

If the function getScreenCollisionPoint is called in Update(), it will keep showing where the corners are.

In my setup I also have:

    print ("upperLeft = " + getScreenCollisionPoint (upperLeft).z + "," + getScreenCollisionPoint (upperLeft).z);
    print ("upperRight = " + getScreenCollisionPoint (upperRight).z + "," + getScreenCollisionPoint (upperRight).z);
    print ("lowerLeft = " + getScreenCollisionPoint (lowerLeft).z + "," + getScreenCollisionPoint (lowerLeft).z);
    print ("lowerRight = " + getScreenCollisionPoint (lowerRight).z + "," + getScreenCollisionPoint (lowerRight).z);

so that I can see what the values of x and z are, for each of the corners.

Upvotes: 1

Views: 166

Answers (2)

31eee384
31eee384

Reputation: 2803

Check out Camera.ScreenPointToRay. You can call that with four vectors to get a Ray at each corner of the screen (passing <0,0,0> <x,0,0> <0,y,0> <x,y,0> where x = pixelWidth and y = pixelHeight).

To do this with the main camera:

Ray upperLeft = Camera.main.ScreenPointToRay(new Vector3(0, Camera.main.pixelHeight, 0));

Then, you need to do a Plane.Raycast on a x-z (flat) plane to find the position of each corner where y = 0.

Plane spawnPlane = new Plane(new Vector3(0, 1, 0), Vector3.zero);
float distance;
if (spawnPlane.Raycast(upperLeft, out distance))
{
    // The cast has collided! Now find out where it hit.
    Vector3 collisionPoint = upperLeft.GetPoint(distance);
}

collisionPoint is then the point where y = 0 corresponding to the upper-left corner of the screen!

Doing this for each corner of the screen gives you the square where the camera can see if you connect up the points. (A trapezoid if you choose to rotate the camera.) You can use that "viewable x-z plane shape" to do whatever else you need to do!

To do what you're trying to do now directly, you can instead use ScreenPointToRay with <pixelWidth/2, pixelHeight, 0> to find the ray at the top-center of the camera view.


To see this at work, I made a script that draws debug lines onto the x-z origin plane using this technique. Copy this code into a new component and add it to the camera:

using System.Linq;
using UnityEngine;

public class Test : MonoBehaviour
{
    void Update()
    {
        Camera cam = GetComponent<Camera>();

        Ray[] rays = new[]
        {
            new Vector3(0, 0),
            new Vector3(0, cam.pixelHeight),
            new Vector3(cam.pixelWidth, 0),
            new Vector3(cam.pixelWidth, cam.pixelHeight)
        }.Select(ray => cam.ScreenPointToRay(ray)).ToArray();

        Plane xz = new Plane(new Vector3(0, 1, 0), Vector3.zero);

        foreach (Ray ray in rays)
        {
            float distanceAlongRay;
            if (xz.Raycast(ray, out distanceAlongRay))
            {
                Vector3 intersect = ray.GetPoint(distanceAlongRay);
                Debug.DrawLine(transform.position, intersect, Color.red);
            }
        }
    }
}

Upvotes: 1

zstewart
zstewart

Reputation: 2177

Assuming the camera always stays exactly above the player position, the distance from the player position to the edge of the camera's field of view is:

tan(0.5 * FOV) * camera_height

Where FOV is the field of view in the direction you want to move the object, tan is the tangent (Mathf.Tan), and camera_height is the vertical distance from the player to the camera.

Upvotes: 0

Related Questions