When creating a 2D RPG, maps tend to be made from painting a grid using a tilemap and also tend to be rectangular. Since we don’t care about depth, this means we can readily use an orthographic camera.

The nice thing about an orthographic camera is that is quite simple to use and we can easily relate between the size property of the camera and the squares of our map’s grid: the size of the orthographic camera is equal to half the height of the viewport. Knowing this we can compute and cache the equivalent “size” value for width.

Once we know what distance from the character/target is being rendered, we can offset the camera by a suitable amount, or more accurately set it at a distance equal to camera.size or our computed width size, whenever the character is too close to the borders of the map.

Here’s the result, the camera follows the player but does not show Out of Bounds parts of the scene:

camera rpg 2d
The camera gets offset near the map boundary

You can see the code below. The code is also available on my UnitySimpleUtils repository on Github.

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

[RequireComponent(typeof(Camera))]
public class CameraRPG2D : MonoBehaviour
{
    public Transform mTarget;
    public Vector2 mSizeMapInUnits;


    private Vector3 _mPositionTracking; // = new Vector3(0f, 0f, 0f);

    private float _mCameraSize;
    private float _mWidthCameraSize;

    void Start()
    {
        Camera cam = GetComponent<Camera>();


        _mPositionTracking = new Vector3(mTarget.position.x, mTarget.position.y, cam.transform.position.z);


        // Ensure camera is orthographic, get vertical size data (size is 1/2 height of viewport)
        // then compute the equivalent width size based on resolution and cache both values
        cam.orthographic = true;
        _mCameraSize = cam.orthographicSize;
        _mWidthCameraSize = cam.pixelWidth / (float)cam.pixelHeight * _mCameraSize;

    }


    private void LateUpdate()
    {
        _mPositionTracking.x = mTarget.position.x;
        _mPositionTracking.y = mTarget.position.y;

        // Check if outside of map is visible on both x and y axis and adjust camera position accordingly
        if (_mPositionTracking.y - _mCameraSize < 0)
        {
            _mPositionTracking.y = _mCameraSize;

        } else if (_mPositionTracking.y + _mCameraSize > mSizeMapInUnits.y)
        {
            _mPositionTracking.y = mSizeMapInUnits.y - _mCameraSize;

        } 

        if (_mPositionTracking.x - _mWidthCameraSize < 0)
        {
            _mPositionTracking.x = _mWidthCameraSize;

        }
        else if (_mPositionTracking.x + _mWidthCameraSize > mSizeMapInUnits.x)
        {
            _mPositionTracking.x = mSizeMapInUnits.x - _mWidthCameraSize;

        } 

        transform.position = _mPositionTracking;

    }
}