unity3d (2d!) - Camera to centre on player, but ne

2019-04-10 15:07发布

问题:

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.

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.

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??

回答1:

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.

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.

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.



回答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



回答3:

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);
    }

}


回答4:

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);

    }
  }
 }