unity3d (2d!) - Camera to centre on player, but never exceed "map" bounds
Asked Answered
L

4

6

I am creating a game which has up to 4 orthographic cameras (for up to 4 players), which take up various amounts of the screen, depending on the amount of players.

2 cameras (with 0.7 each)

4 Cameras

I have a script which controls all the cameras, setting their position relative to the players they are watching.

What I want to achieve is a simple overhead-runner style of movement, whereby the camera will follow the player, but not go outside the bounds of the map.

required behaviour

I have managed to get the boundaries working in the top-left camera, when the cameras are 'square' (as in the 4-player layout). However, the other cameras don't track properly at all, and in the rectangular 2-player mode, the top camera still goes too far left-and right. I'm pretty sure I know exactly which line of code is causing the problem... but I don't know what I need to do to fix it...

SpriteRenderer spriteBounds = GameObject.Find("Map").GetComponentInChildren<SpriteRenderer>();
float leftBound =0;
float rightBound =0;
float bottomBound =0;
float topBound = 0;

if((trackPlayer1 == null))
{
    camPlayer1.transform.position = this.transform.position;
}
else
{
    float vertExtent = camPlayer1.orthographicSize;
    float horzExtent = vertExtent * Screen.width / Screen.height; //I guess the problem is here... but how do I fix this??

    leftBound = (float)(horzExtent - spriteBounds.sprite.bounds.size.x / 2.0f);
    rightBound = (float)(spriteBounds.sprite.bounds.size.x / 2.0f - horzExtent);
    bottomBound = (float)(vertExtent - spriteBounds.sprite.bounds.size.y / 2.0f);
    topBound = (float)(spriteBounds.sprite.bounds.size.y / 2.0f - vertExtent);

    camPlayer1.transform.position = new Vector3(Mathf.Clamp(trackPlayer1.transform.position.x, leftBound, rightBound), Mathf.Clamp(trackPlayer1.transform.position.y, bottomBound, topBound), camPlayer1.transform.position.z);
}
if((trackPlayer2 == null))
{
    camPlayer2.transform.position = this.transform.position;
}
else
{
    float vertExtent = camPlayer2.orthographicSize ;
    float horzExtent = vertExtent * Screen.width / Screen.height; //I guess the problem is here... but how do I fix this??

    leftBound = (float)(horzExtent - spriteBounds.sprite.bounds.size.x / 2.0f);
    rightBound = (float)(spriteBounds.sprite.bounds.size.x / 2.0f - horzExtent);
    bottomBound = (float)(vertExtent - spriteBounds.sprite.bounds.size.y / 2.0f);
    topBound = (float)(spriteBounds.sprite.bounds.size.y / 2.0f - vertExtent);

    camPlayer2.transform.position = new Vector3(Mathf.Clamp(trackPlayer2.transform.position.x, leftBound, rightBound), Mathf.Clamp(trackPlayer2.transform.position.y, topBound, bottomBound), camPlayer2.transform.position.z);
}
if((trackPlayer3 == null))
{
    camPlayer3.transform.position = this.transform.position;
}
else
{
    float vertExtent = camPlayer3.orthographicSize;
    float horzExtent = vertExtent * Screen.width / Screen.height; //I guess the problem is here... but how do I fix this??

    leftBound = (float)(horzExtent - spriteBounds.sprite.bounds.size.x / 2.0f);
    rightBound = (float)(spriteBounds.sprite.bounds.size.x / 2.0f - horzExtent);
    bottomBound = (float)(vertExtent - spriteBounds.sprite.bounds.size.y / 2.0f);
    topBound = (float)(spriteBounds.sprite.bounds.size.y / 2.0f - vertExtent);

    camPlayer3.transform.position = new Vector3(Mathf.Clamp(trackPlayer3.transform.position.x, leftBound, rightBound), Mathf.Clamp(trackPlayer3.transform.position.y, topBound, bottomBound), camPlayer3.transform.position.z);         
}
if((trackPlayer4 == null))
{
    camPlayer4.transform.position = this.transform.position;
}
else
{
    float vertExtent = camPlayer4.orthographicSize;
    float horzExtent = vertExtent * Screen.width / Screen.height; //I guess the problem is here... but how do I fix this??

    leftBound = (float)(horzExtent - spriteBounds.sprite.bounds.size.x / 2.0f);
    rightBound = (float)(spriteBounds.sprite.bounds.size.x / 2.0f - horzExtent);
    bottomBound = (float)(vertExtent - spriteBounds.sprite.bounds.size.y / 2.0f);
    topBound = (float)(spriteBounds.sprite.bounds.size.y / 2.0f - vertExtent);

    camPlayer4.transform.position = new Vector3(Mathf.Clamp(trackPlayer4.transform.position.x, leftBound, rightBound), Mathf.Clamp(trackPlayer4.transform.position.y, topBound, bottomBound), camPlayer4.transform.position.z);
}

So I'm pretty sure that I need to be checking against the cameras size and relative position on the screen, but I am lost as to exactly what I need to be doing.

(edit) Script explanation:

  • The script is a global script attached to the main camera object, which is never seen by the player
  • The four player cams (camPlayer1 - camPlayer4) are public variables and assigned to the script in the designer
  • trackPlayer1-trackPlayer4 are public gameobjects, which are assigned in the designer - they are assigned to the player objects
  • Player tracking works on all cams... for example if I change camPlayer2.transform.position = new Vector3(Mathf.Clamp(trackPlayer2.transform.position.x, leftBound, rightBound), Mathf.Clamp(trackPlayer2.transform.position.y, topBound, bottomBound), camPlayer2.transform.position.z); to camPlayer2.transform.position = trackPlayer2.transform.position;, the code has the expected effect, the camera follows the player. It is only the clamping to the bounds of the map that I am having issues with
  • The camera's orthographic sizes are set to 2

code which positions the cameras on screen at startup:

    switch (playerCount)
    {
        case 1:
            camPlayer1.enabled = true;
            camPlayer2.enabled = false;
            camPlayer3.enabled = false;
            camPlayer4.enabled = false;

            camPlayer1.rect = new Rect(0, 0, 1, 1);
            camPlayer1.orthographicSize = CamManager.CAMERA_SIZE;

            camPlayer2.rect = new Rect(0, 0, 0, 0);
            camPlayer2.orthographicSize = CamManager.CAMERA_SIZE;

            camPlayer3.rect = new Rect(0, 0, 0, 0);
            camPlayer3.orthographicSize = CamManager.CAMERA_SIZE;

            camPlayer4.rect = new Rect(0, 0, 0, 0);
            camPlayer4.orthographicSize = CamManager.CAMERA_SIZE;

            break;
        case 2:
            camPlayer1.enabled = true;
            camPlayer2.enabled = true;
            camPlayer3.enabled = false;
            camPlayer4.enabled = false;


            camPlayer1.rect = new Rect(0, 0.5f, 0.7f, 0.5f);
            camPlayer1.orthographicSize = CamManager.CAMERA_SIZE;

            camPlayer2.rect = new Rect(0.3f, 0, 0.7f, 0.5f);
            camPlayer2.orthographicSize = CamManager.CAMERA_SIZE;

            camPlayer3.rect = new Rect(0, 0, 0, 0);
            camPlayer3.orthographicSize = CamManager.CAMERA_SIZE;

            camPlayer4.rect = new Rect(0, 0, 0, 0);
            camPlayer4.orthographicSize = CamManager.CAMERA_SIZE;


            Destroy(play3);
            Destroy(play4);

            break;
        case 3:
            camPlayer1.enabled = true;
            camPlayer2.enabled = true;
            camPlayer3.enabled = true;
            camPlayer4.enabled = false;

            camPlayer1.rect = new Rect(0, 0.5f, 0.5f, 0.5f);
            camPlayer1.orthographicSize = CamManager.CAMERA_SIZE;

            camPlayer2.rect = new Rect(0.5f, 0.5f, 0.5f, 0.5f);
            camPlayer2.orthographicSize = CamManager.CAMERA_SIZE;

            camPlayer3.rect = new Rect(0.25f, 0, 0.5f, 0.5f);
            camPlayer3.orthographicSize = CamManager.CAMERA_SIZE;

            camPlayer4.rect = new Rect(0, 0, 0, 0);
            camPlayer4.orthographicSize = CamManager.CAMERA_SIZE;

            Destroy(play4);


            break;
        case 4:
            camPlayer1.enabled = true;
            camPlayer2.enabled = true;
            camPlayer3.enabled = true;
            camPlayer4.enabled = true;

            camPlayer1.rect = new Rect(0, 0.5f, 0.5f, 0.5f);
            camPlayer1.orthographicSize = CamManager.CAMERA_SIZE;

            camPlayer2.rect = new Rect(0.5f, 0.5f, 0.5f, 0.5f);
            camPlayer2.orthographicSize = CamManager.CAMERA_SIZE;

            camPlayer3.rect = new Rect(0, 0, 0.5f, 0.5f);
            camPlayer3.orthographicSize = CamManager.CAMERA_SIZE;

            camPlayer4.rect = new Rect(0.5f, 0, 0.5f, 0.5f);
            camPlayer4.orthographicSize = CamManager.CAMERA_SIZE;

            break;
    }
}

edit, so I can get the first camera to tract regardless of shape (so in 2-player mode, with a rectangular camera, the player-1 cam will respect the boundaries of the map) with the code below. I'm guessing then that I need to apply some offsets to the leftBound, rightBound, topBound and bottomBound dependent on the rects of the other cams. How to establish and calculate these I have no idea

if((trackPlayer1 == null))
{
 camPlayer1.transform.position = this.transform.position;
}
else
{
 float vertExtent = camPlayer1.orthographicSize;
 float horzExtent = vertExtent * (Screen.width * (camPlayer1.rect.width * 2)) / Screen.height; //I guess the problem is here... but how do I fix this??
Land answered 3/1, 2015 at 7:48 Comment(7)
You're code is quite difficult to follow. It would be helpful if you added some comments that explain what you are trying to calculate at certain parts. Did you take into account that the orthographicSize property is half the vertical length of the screen and not the full length?Vermillion
So I'm not awfully familiar with Unity, but I would imagine that the way in which you calculate the aspect ratio is at least part of the problem. You calculate the aspect ratio for each camera as Screen.width / Screen.height but then you obviously don't render to the whole screen area. You need to use the aspect ratio for the camera, which may be different to the aspect ratio of the screen.Vermillion
Looking at Unity's Camera class, you can probably use the rect property of the camera to calculate the aspect ratio for each camera.Vermillion
@Homar. I dnt think that's the issue. ex in 4-plyr mode, with current script, cam1 tracks the player & respects boundaries of map/background. It is the same aspect ratio as the other cameras, which do not respect the boundaries (in fact the tracking goes totally off when the clamping is applied, not even focusing on player). I guess ur right that it has smthing 2 do with rect of the cam, but I have no idea what, unfortunately. I tried using cam1.cameraToWorldPoint() to translate top, bottom, left and right bounds, then using cam2.worldToCameraPoint, but that just make things even more bizzare.Land
I think that you probably have quite a few issues. For instance, SpriteRenderer::bounds::size is the dimensions of the AABB and does not take into account the position. You should probably use the min and max properties of Bounds to obtain the coordinate extends instead.Vermillion
The method of calculating the aspect ratio that I mentioned may or may not have an effect. It depends on whether the aspect ratio of the screen will always equal the aspect ratios of the four renderable areas. This will certainly not be the case when you have a two player mode that splits the screen in half.Vermillion
Also, I just noticed that you have provided your arguments in the wrong order to Mathf.Clamp for players 2 - 4. For example, Mathf.Clamp(trackPlayer2.transform.position.y, topBound, bottomBound) should be Mathf.Clamp(trackPlayer2.transform.position.y, bottomBound, topBound). This is probably why your tracking is not working correctly.Vermillion
D
15

There are several issues with your calculations. I guess it's just luck that it works for some situations. While your general idea is correct, you're calculating with the wrong properties. Basically you need to understand the aspect ratio of the cameras, the bounding box of your map and how a camera should behave at the borders.

We need the aspect ratio of the camera to calculate its width or extent. aspect ratios and screen size

As you can see in the picture, Screen.width and Screen.height are referring to the game's window or the monitor resolution in case you're running the game fullscreen. You can also see that the cameras may have a different aspect ratio when compared to the game window. The good news is that Unity provides a property to get the camera's aspect ratio.

float camVertExtent = cam.orthograpicSize;
float camHorzExtent = cam.aspect * camVertExtent;

Now that we have the camera's extents, let's take a look at your map and its bounding box. bounding box and camera position

You tried calculating with bounds.size, but as you can see bounds.size.x is the width of the bounding box. Using the size will only work if your map's bottom-left starts at (0,0) in world space. A better approach is to use bounds.min and bounds.max which already return coordinates in world space.

Your ultimate goal is that the camera should stay within the bounds of the map, i.e., its position is restricted by the following four conditions:

(cam.transform.position.x - camHorzExtent) >= bounds.min.x // left
(cam.transform.position.x + camHorzExtent) <= bounds.max.x // right
(cam.transform.position.y - camVertExtent) >= bounds.min.y // bottom
(cam.transform.position.y + camVertExtent) <= bounds.max.y // top

Now you only need to take the player position and limit the camera to these conditions:

float leftBound   = bounds.min.x + camHorzExtent;
float rightBound  = bounds.max.x - camHorzExtent;
float bottomBound = bounds.min.y + camVertExtent;
float topBound    = bounds.max.y - camVertExtent;

float camX = Mathf.Clamp(player.transform.position.x, leftBound, rightBound);
float camY = Mathf.Clamp(player.transform.position.y, bottomBound, topBound);

cam.transform.position = new Vector3(camX, camY, cam.transform.position.z);

If all cameras have the same size and aspect ratio, you can use the same bounds for all player cameras and only calculate camX and camY for each player.

Dawson answered 12/1, 2015 at 20:35 Comment(3)
Hi there. I have amended the code as suggested. In 4-player mode, players 2&3 (who are top-right and bottom-left on the screen respectively) track correctly but 1&4 are off. In 2-player mode player 2 (bottom right!) tracks correctly, but player 1 only tracks vertically, not horizontally.Land
@Land I guess there are some copy&paste errors. Check if you use the correct player and camera variables for each camera. If you can't find an error, you could paste your code at pastebin.com and link it in a comment.Dawson
Amazing the typos a tired eye can miss... yes there were indeed a couple of c&p errors on cam1 and 4. Excellent answer, very well explained. Thankyou.Land
M
2

I'd like to suggest you another approach for your problem.

Is it possible to you to add high colliders at the border of your map, and a collider for the camera? You can then make the camera follow the player (use physics!), and when the player reaches a boundary, the camera won't go over, because of the collider.

Then, you can easily split the screen knowing how many players you have. One player? --> all screen size Two players? --> camera 1 ends ad screen height / 2, camera 2 starts at screen heigth /2 +1 and so on

Meetly answered 9/1, 2015 at 11:25 Comment(2)
the map already has colliders which prevent the player going outside of the area. The idea of using physics and adding a collider to the camera sounds... confusing. How you would you recommend going about this? Do you have any links to sources where the method has been used before?Land
It's used like in MMORPGs to not make the camera going below the terrain (that was my case, it was in 3D of course). I can't remember what I searched 2 years ago, but some clues may be found here answers.unity3d.com/questions/14693/… or answers.unity3d.com/questions/19864/… I think the same principle can be applied to your 2DMeetly
S
0

Hi everyone for some people that need the complete code for work into game object with many childrens sprites into Empty Gameobject like:

Background
--mountains1(sprite)
--mountains2(sprite)
--mountains3(sprite)

This Code will be loop into Background object and take the bound of all childrens sprites :D, I make a Frankestein code hahhaa thanks to @Stefan Hoffmann explanation. I hope this will be helpful to others, Sorry for my bad English :(

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraFollow : MonoBehaviour
{

    private float rightBound;
    private float leftBound;
    private float topBound;
    private float bottomBound;
    private Vector3 pos;
    private Transform target;

    private Camera cam;
    private Bounds bounds;


    // Use this for initialization
    void Start()
    {
        target = GameObject.FindWithTag("Player").transform;


        foreach (SpriteRenderer spriteBounds in GameObject.Find("Background").GetComponentsInChildren<SpriteRenderer>())
        {
            bounds.Encapsulate(spriteBounds.bounds);
        }


        cam = this.gameObject.GetComponent<Camera>();
        float camVertExtent = cam.orthographicSize;
        float camHorzExtent = cam.aspect * camVertExtent;

        Debug.Log(camVertExtent);
        Debug.Log(camHorzExtent);
        Debug.Log(cam.aspect);
        Debug.Log(cam.orthographicSize);



        leftBound = bounds.min.x + camHorzExtent;
        rightBound = bounds.max.x - camHorzExtent;
        bottomBound = bounds.min.y + camVertExtent;
        topBound = bounds.max.y - camVertExtent;

        Debug.Log("leftBound=" + leftBound);
        Debug.Log("rightBound=" + rightBound);
        Debug.Log("bottomBound=" + bottomBound);
        Debug.Log("topBound=" + topBound);
    }

    // Update is called once per frame
    void Update()
    {


        float camX = Mathf.Clamp(target.transform.position.x, leftBound, rightBound);
        float camY = Mathf.Clamp(target.transform.position.y, bottomBound, topBound);

        cam.transform.position = new Vector3(camX, camY, cam.transform.position.z);
    }

}
Scoggins answered 22/6, 2017 at 7:41 Comment(0)
A
-2

You should use this this simple yet useful unity 2d camera follow script
also available on github. https://gist.github.com/unity3diy/5aa0b098cb06b3ccbe47


using UnityEngine;
using System.Collections;

public class FollowCamera : MonoBehaviour {

public float interpVelocity;
public float minDistance;
public float followDistance;
public GameObject target;
public Vector3 offset;
Vector3 targetPos;
// Use this for initialization
void Start () {
    targetPos = transform.position;
}

// Update is called once per frame
void FixedUpdate () {
    if (target)
    {
        Vector3 posNoZ = transform.position;
        posNoZ.z = target.transform.position.z;

        Vector3 targetDirection = (target.transform.position - posNoZ);

        interpVelocity = targetDirection.magnitude * 5f;

        targetPos = transform.position + (targetDirection.normalized * interpVelocity * Time.deltaTime); 

        transform.position = Vector3.Lerp( transform.position, targetPos + offset, 0.25f);

    }
  }
 }
Assorted answered 27/2, 2015 at 12:32 Comment(2)
How does this answer the question in any way at all? The issue was not with the camera tracking the player (which can be done with a single line of code!), it was about making the camera not go outside the boundaries of the map.Land
This had nothing to do with what the OP was talking about.Ogee

© 2022 - 2024 — McMap. All rights reserved.