How to force an object to stay in the visible camera area in Unity

by on Mar.10, 2013, under Technical posts, Unity3D

Imagine that you are developing certain game that needs an object to stay in the camera’s visible area. For example a vertical space shooter where we would like to have our space ship to stay all the time in the visible area.

Let’s imagine that our player is free to move the space ship all around the screen, shooting enemies, collecting items, etc. Of course we would like to force our player to stay within the game screen when approaching the borders. As there are many devices with different screen resolutions, will make impossible to handle all of them manually. Then our first step will be get the screen resolution in order to calculate the borders. Let us say for this example that the player will be able to go maximum till 5% off the horizontal border and 10% off the vertical border.

Our first step will be to detect the current screen resolution and calculate what is 5% of the current width and 10% of the current height (we will use these values later):

void Start () 
{
     screenX = (Screen.width * 5) / 100;
     screenY = (Screen.height * 10) / 100;
}

Now in our Update function, just after all the transformations we would like to implement (according to movement, player input, etc) we will check if the player is inside the margins.

The first step is to translate the world coordinates into screen coordinates. Unity provides a function for it at our main camera WorldToScreenPoint that will do this for us. This process consists in translating the player’s position inside the world, to a camera relative position according to what we “see”.

void Update () 
{
     // ........ All player transformations before the following:

     Vector3 playerPosScreen;
     // Check if the player's ship is on screen.         
     playerPosScreen = Camera.main.WorldToScreenPoint(transform.position);

Now we have the current player’s position according to our camera. It is time to check if after all the transformations, is it still inside our margins. We will check this for X and Y positions (width height). We will do this checking the playerPosScreen.y and the margins we have calculated before:

    // Z - axis ( Vertical on screen)
    if (playerPosScreen.y < screenY)
    {
        // Force to stay at bottom margin
    }

If the Y position is lower than the vertical margin we calculated before we will force the player to stay at the minimum position. We accomplish that using the following code:

     transform.position = Camera.main.ScreenToWorldPoint(
                   new Vector3(playerPosScreen.x, screenY, playerPosScreen.z));

Basically what we are doing is assigning the current position to a new position consisting of:

  • current x position (playerPosScreen.x)
  • current z position (playerPosScreen.z)
  • For y, we will use the bottom margin (Screen.height – screenY), as it is the minimum y position.

Of course as we where using the camera relative positions, before applying this to our transform position, we should convert to world position. Unity provides a function in the main camera to do it ScreenToWorldPoint

Now as usual, let’s put all the checks together (Left <-> Right margins and Top <-> Bottom margins):

 

    
        // Z - axis ( Vertical on screen)
        if (playerPosScreen.y < screenY)
        {
            transform.position = Camera.main.ScreenToWorldPoint(new Vector3(
                                                              playerPosScreen.x, 
                                                              screenY, 
                                                              playerPosScreen.z));
        }
        else if (playerPosScreen.y > Screen.height - screenY)
        {
            transform.position = Camera.main.ScreenToWorldPoint(new Vector3(
                                                              playerPosScreen.x, 
                                                              Screen.height - screenY, 
                                                              playerPosScreen.z));
        }

        // X - axis ( Horizontal on screen)
        if (playerPosScreen.x < screenX)
        {
            transform.position = Camera.main.ScreenToWorldPoint(new Vector3(
                                                              screenX, 
                                                              playerPosScreen.y, 
                                                              playerPosScreen.z));
        }
        else if (playerPosScreen.x > Screen.width / 2 - screenX)
        {
            transform.position = Camera.main.ScreenToWorldPoint(new Vector3(
                                                              Screen.width / 2 - screenX, 
                                                              playerPosScreen.y, 
                                                              playerPosScreen.z));
        }

And this is it for today. Any questions or comments, please let me know.


3 Comments for this entry

  • Soviet Bear

    I have two issues with this script. 1. to get the correct margin on the right, I had to take out the /2 from the else if statement:
    else if (playerPosScreen.x > Screen.width / 2 – screenX)
    {
    transform.position = Camera.main.ScreenToWorldPoint(new Vector3(
    Screen.width / 2 – screenX,
    playerPosScreen.y,
    playerPosScreen.z));
    }

    Secondly, If you got to the side margin, keep pressing into it, and go down/up, you can get passed the bottom and top margin, I can’t figure out how to fix this, I’ll look at the code more, if I figure it out, I’ll post the code here.

  • aeonphyxius

    I actually improved more this code to work with different screen aspect ratios (sometimes we use a black line on the left & right margins).

    Will try to post an updated version of this code.

Leave a Reply