Outlaw
Outlaw

Reputation: 359

Unity, Android: how to set the aspect/scale of the game properly

I've developed an Android game using Unity. And in the end I've faced the next problem:

Each level of my game has strict borders (like a box) along the sides of the screen. I've been developing the game with aspect 9:16 (pic. 1). When I've built it and run on my phone (it's 9:19.5) I found out that leftmost and rightmost parts of the levels are out of the screen (pic. 2).

How can I fix this, so the game fits every aspect ratio?

I've tried different solutions, but nothing satisfied me. I'm so desperate that I'm already agree to just scale it down from 9:16 so that it fits the WIDTH of the screen plus adding black bars at the top and at the bottom (pic. 3). Height is not so important in my game, but width is crucial. Or is there some better solution?

enter image description here enter image description here enter image description here

UPDATE 2: My project's design (kinda grayboxing): enter image description here Here's how it looks FINE with different aspects in play mode: enter image description here And here's how it looks POORLY with different aspects in play mode: enter image description here

Upvotes: 1

Views: 4797

Answers (2)

CodeRonnie
CodeRonnie

Reputation: 39

Unity's camera assumes that games will be played in a landscape orientation. Therefore the amount of the game world that is rendered vertically will always be the same on any screen, regardless of aspect ratio. If you are restricting your game to be played only on mobile platforms, and only in portrait mode, then you will want to force Unity to do the opposite. You want the camera to always render the same width, and show a variable amount of height depending on aspect ratio. (Helpful hint: Develop with the smallest aspect ratio you expect, like 4:5, not 9:16. Configure all of the aspect ratios or resolutions you want to test in the dropdown menu at the top of the Game View. Switch between these for testing, and allow your UI and game view to expand out for taller aspect ratios. Otherwise your UI will probably run together on smaller aspect ratios. See my post on CanvasScaler here.)

Anyway, to change the default camera behavior, you'll need to add a script to your camera. Something like this should do:

Edit: I modified the component to handle both perspective and orthographic cameras. Based on your game it appears you are, or perhaps should be, using an orthographic camera, which my previous answer did not handle. Also, there are no more properties on the component. Simply attach to a camera, then use the camera's properties as normal. Also, it now handles viewports, so it works with cameras that don't occupy the entire screen.

There is one known issue. With an orthographic camera, if you attempt to adjust the viewport to be greater than the entire screen size, something internal to Unity resets the projection matrix and overrides this component's behavior. To fix, simply toggle the component, or reload the scene or editor, anything that triggers the component to do its thing again.

using UnityEngine;

[ExecuteAlways]
[RequireComponent(typeof(Camera))]
public class HorizontallyAlignedCamera : MonoBehaviour
{
    private Camera _camera;
    private float _aspectRatio;
    private float _fieldOfView;
    private bool _isOrthographic;
    private float _orthographicSize;

    private void Awake()
    {
        _camera = GetComponent<Camera>();
    }

    private void OnEnable()
    {
        CachePropertiesAndRecalculate();
    }

    private void LateUpdate()
    {
        if(CameraPropertyHasChanged())
            CachePropertiesAndRecalculate();
    }

    private void OnDisable()
    {
        _camera.ResetProjectionMatrix();
    }

    private bool CameraPropertyHasChanged()
    {
        bool hasChanged = (_aspectRatio != _camera.aspect
            || _fieldOfView != _camera.fieldOfView
            || _isOrthographic != _camera.orthographic
            || _orthographicSize != _camera.orthographicSize);

        return hasChanged;
    }

    private void CacheCameraProperties()
    {
        _aspectRatio = _camera.aspect;
        _fieldOfView = _camera.fieldOfView;
        _isOrthographic = _camera.orthographic;
        _orthographicSize = _camera.orthographicSize;
    }

    private void CachePropertiesAndRecalculate()
    {
        CacheCameraProperties();

        if(_camera.orthographic)
            RecalculateOrthographicMatrix();
        else
            RecalculatePerspectiveMatrix();
    }

    private void RecalculatePerspectiveMatrix()
    {
        float near = _camera.nearClipPlane;
        float nearx2 = near * 2.0f;
        float far = _camera.farClipPlane;
        float halfFovRad = _camera.fieldOfView * 0.5f * Mathf.Deg2Rad;

        // This is what aligns the camera horizontally.
        float width = nearx2 * Mathf.Tan(halfFovRad);
        float height = width / _camera.aspect;

        // This is the default behavior.
        //float height = nearx2 * Mathf.Tan(halfFovRad);
        //float width = height * _camera.aspect;

        float a = nearx2 / width;
        float b = nearx2 / height;
        float c = -(far + near) / (far - near);
        float d = -(nearx2 * far) / (far - near);

        Matrix4x4 newProjectionMatrix = new Matrix4x4(
            new Vector4(a, 0.0f, 0.0f, 0.0f),
            new Vector4(0.0f, b, 0.0f, 0.0f),
            new Vector4(0.0f, 0.0f, c, -1.0f),
            new Vector4(0.0f, 0.0f, d, 0.0f));

        _camera.projectionMatrix = newProjectionMatrix;
    }

    private void RecalculateOrthographicMatrix()
    {
        // This is what aligns the camera horizontally.
        float width = 2.0f * _camera.orthographicSize;
        float height = width / _camera.aspect;

        // This is the default behavior.
        //float height = 2.0f * _camera.orthographicSize;
        //float width = height * _camera.aspect;

        float near = _camera.nearClipPlane;
        float far = _camera.farClipPlane;
        float a = 2.0f / width;
        float b = 2.0f / height;
        float c = -2.0f / (far - near);
        float d = -(far + near) / (far - near);

        Matrix4x4 newProjectionMatrix = new Matrix4x4(
            new Vector4(a, 0.0f, 0.0f, 0.0f),
            new Vector4(0.0f, b, 0.0f, 0.0f),
            new Vector4(0.0f, 0.0f, c, 0.0f),
            new Vector4(0.0f, 0.0f, d, 1.0f));

        _camera.projectionMatrix = newProjectionMatrix;
    }
}

Upvotes: 1

Outlaw
Outlaw

Reputation: 359

So, I found a way to achieve the goal. I added an empty game object to the scene and child'ed to it all the level structure, plus adding two black bars (as simple cubes) above and below the level. With that approach I can scale that parent game object so that all the level structure scales proportionally. All I have to do then is creating a script for scaling the parent game object according to the width of the screen.

Upvotes: 0

Related Questions