81
\$\begingroup\$

I've gotten a lot of answers to this question, but they are all generic and generally not very useful. None of the tutorials talk about aspect ratio and dealing with mobile devices and there are a zillion ways to do it, all seem to have gotcha's and flaws.

I really would love to know what successful games have used to handle different aspect ratios on iOS and Android without making a zillion different sized assets.

I am strictly speaking mobile, not desktop, specifically with Unity and I don't care about the UI, I only care about the gameplay canvas.

Issues I have in mind is when there are key things that have to be in certain places and cannot fall off the screen. Using black bars on top or bottom is unacceptable these days.

\$\endgroup\$
6
  • 3
    \$\begingroup\$ This question is very broad, as the right way depends on almost anything. What have you tried? Why did it not work? \$\endgroup\$
    – Anko
    Commented Jun 30, 2014 at 20:05
  • 4
    \$\begingroup\$ I've tried all sorts of things, I've tried adjusting ortho camera size, I've tried attaching all sprites to list and scaling them by difference in aspect ratio, setting ortho size to screen.height/2/100, many other ideas. Some work, but all of them have issues. I know different games handle it different but there is absolutely no discussion of this topic anywhere and it isn't as easy as "just letting unity handle it" as many claim. \$\endgroup\$
    – Michael
    Commented Jun 30, 2014 at 20:34
  • 1
    \$\begingroup\$ So, why did they not work? What would a good solution look like? (By the way, you can edit the question to clarify too.) \$\endgroup\$
    – Anko
    Commented Jun 30, 2014 at 20:44
  • 9
    \$\begingroup\$ Some distorted the images, some didn't line up right. Many different issues, but 65% of games developed with Unity are 2D, and they made it work. I just want to know what people are using, and not having to reinvent the wheel. No one talks about it and there is no guides or docs on how to handle it. Yet you can't get far in a mobile project without having a system in place to do it. \$\endgroup\$
    – Michael
    Commented Jun 30, 2014 at 22:38
  • 1
    \$\begingroup\$ "Issues I have in mind is when there are key things that have to be in certain places and cannot fall off the screen. Using black bars on top or bottom is unacceptable these days." Guarantee of elements not falling off screen, zero distortion, but no letter/pillar-boxing (black bars and the like). These requirements are irreconcilable. The last requirement is probably the least important, or can be hidden by padding out the canvas beyond what must be on-screen. Most games I've seen with such strict requirements will have decorated pillarbox/borders. \$\endgroup\$
    – Jibb Smart
    Commented Sep 2, 2015 at 3:39

13 Answers 13

46
\$\begingroup\$

What you want is to constrain the camera viewport on portrait or landscape(depending on your needs), by computing camera.orthographicSize property, so you can build your 2d scene regardless of aspect ratio and resolution:

// Attach this script on your main ortohgraphic camera:

/* The MIT License (MIT)

Copyright (c) 2014, Marcel Căşvan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. */

using System;
using System.Collections;
using UnityEngine;

[ExecuteInEditMode]
[RequireComponent (typeof (Camera))]
public class ViewportHandler : MonoBehaviour
{
    #region FIELDS
    public Color wireColor = Color.white;
    public float UnitsSize = 1; // size of your scene in unity units
    public Constraint constraint = Constraint.Portrait;
    public static ViewportHandler Instance;
    public new Camera camera;

    private float _width;
    private float _height;
    //*** bottom screen
    private Vector3 _bl;
    private Vector3 _bc;
    private Vector3 _br;
    //*** middle screen
    private Vector3 _ml;
    private Vector3 _mc;
    private Vector3 _mr;
    //*** top screen
    private Vector3 _tl;
    private Vector3 _tc;
    private Vector3 _tr;
    #endregion

    #region PROPERTIES
    public float Width {
        get {
            return _width;
        }
    }
    public float Height {
        get {
            return _height;
        }
    }

    // helper points:
    public Vector3 BottomLeft {
        get {
            return _bl;
        }
    }
    public Vector3 BottomCenter {
        get {
            return _bc;
        }
    }
    public Vector3 BottomRight {
        get {
            return _br;
        }
    }
    public Vector3 MiddleLeft {
        get {
            return _ml;
        }
    }
    public Vector3 MiddleCenter {
        get {
            return _mc;
        }
    }
    public Vector3 MiddleRight {
        get {
            return _mr;
        }
    }
    public Vector3 TopLeft {
        get {
            return _tl;
        }
    }
    public Vector3 TopCenter {
        get {
            return _tc;
        }
    }
    public Vector3 TopRight {
        get {
            return _tr;
        }
    }
    #endregion

    #region METHODS
    private void Awake()
    {
        camera = GetComponent<Camera>();
        Instance = this;
        ComputeResolution();
    }

    private void ComputeResolution()
    {
        float leftX, rightX, topY, bottomY;

        if(constraint == Constraint.Landscape){
            camera.orthographicSize = 1f / camera.aspect * UnitsSize / 2f;    
        }else{
            camera.orthographicSize = UnitsSize / 2f;
        }

        _height = 2f * camera.orthographicSize;
        _width = _height * camera.aspect;

        float cameraX, cameraY;
        cameraX = camera.transform.position.x;
        cameraY = camera.transform.position.y;

        leftX = cameraX - _width / 2;
        rightX = cameraX + _width / 2;
        topY = cameraY + _height / 2;
        bottomY = cameraY - _height / 2;

        //*** bottom
        _bl = new Vector3(leftX, bottomY, 0);
        _bc = new Vector3(cameraX, bottomY, 0);
        _br = new Vector3(rightX, bottomY, 0);
        //*** middle
        _ml = new Vector3(leftX, cameraY, 0);
        _mc = new Vector3(cameraX, cameraY, 0);
        _mr = new Vector3(rightX, cameraY, 0);
        //*** top
        _tl = new Vector3(leftX, topY, 0);
        _tc = new Vector3(cameraX, topY , 0);
        _tr = new Vector3(rightX, topY, 0);           
    }

    private void Update()
    {
        #if UNITY_EDITOR
        ComputeResolution();
        #endif
    }

    void OnDrawGizmos() {
        Gizmos.color = wireColor;
        
        Matrix4x4 temp = Gizmos.matrix;
        Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one);
        if (camera.orthographic) {
            float spread = camera.farClipPlane - camera.nearClipPlane;
            float center = (camera.farClipPlane + camera.nearClipPlane)*0.5f;
            Gizmos.DrawWireCube(new Vector3(0,0,center), new Vector3(camera.orthographicSize*2*camera.aspect, camera.orthographicSize*2, spread));
        } else {
            Gizmos.DrawFrustum(Vector3.zero, camera.fieldOfView, camera.farClipPlane, camera.nearClipPlane, camera.aspect);
        }
        Gizmos.matrix = temp;
    }
    #endregion

    public enum Constraint { Landscape, Portrait }
}

If you need more info on this please ask and I will reply. ;) Regards and cheers.

UPDATE: Use Eliot Lash's object anchoring script together with this one to place objects at key positions on the screen if needed(relative to screen corners/borders).

Preview simulating various aspect ratios screens: enter image description here

\$\endgroup\$
15
  • 2
    \$\begingroup\$ Thanks, I tried this out and it seems to work well. Can you release this script under a permissive open-source license like MIT-style? I'd like to use this in my game and I'm happy to attribute you, but as it stands this is only available under StackExchange's default CC-BY-SA 3.0 license which isn't meant for software. \$\endgroup\$
    – Eliot
    Commented Jan 7, 2015 at 11:54
  • 6
    \$\begingroup\$ @Eliot Added MIT license above. Good luck with your projects! \$\endgroup\$
    – androidu
    Commented Jan 7, 2015 at 12:16
  • 8
    \$\begingroup\$ Fantastic, thanks! One good turn deserves another, so here's an open-source component I just wrote for simple anchoring of GameObjects to the helper points defined by your script: gist.github.com/fadookie/256947788c364400abe1 \$\endgroup\$
    – Eliot
    Commented Jan 7, 2015 at 12:39
  • 1
    \$\begingroup\$ MarcelCăşvan Very nice script! I added an enum for selecting if you want to define unit size for potrait for landscape (height/width). Just had to add a couple of lines to the script, and I use GetComponent<Camera>().orthographicSize = UnitSize / 2f; for potrait/defining height units \$\endgroup\$
    – am_
    Commented Mar 26, 2015 at 14:11
  • 2
    \$\begingroup\$ @MarcelCăşvan great stuff! Thanks. I see that the deviceWidth and deviceHeight variables from ComputeFunction() are unused. Perhaps consider deleting these. \$\endgroup\$ Commented Apr 20, 2016 at 10:26
6
\$\begingroup\$

You typically don't need different sizes of assets - imported textures and sprites with automatically-generated mip maps will look nice when rendered at any size less than or equal to the original pixel size of the image.

The scene layout is the challenge. One good approach is as follows (and FYI I use a 3D camera looking at 2D content positioned at z=0):

  1. Arbitrarily decide on a minimum "logical" display size in either pixels or tiles. This doesn't need to correspond to any real-world resolution, but it should reflect the narrowest/shortest aspect ratio that you want to support. For example, for a landscape game I wouldn't choose 480x320 because that's a wider aspect ratio than the iPad. So I might pick 1024x768 - or even 480x360, which gives me an original iPhone-sized coordinate system to work with and the same aspect ratio as every iPad (including iPad Air 2, etc.). Also note you can just as easily work in tile coordinates rather than pixel coordinates - 15x11.25 for example.
  2. Program your game logic so that everything important is (or can be) positioned within your minimum display size but be prepared to fill extra room on the sides with additional content, even if it's just decorative filler.
  3. Determine how much you need to scale your content so that either the width or the height matches the minimum value and the other axis is larger than or equal to the minimum needed. To do this "scale to fit", divide the screen pixel size by the minimum display size and take the smaller of the resulting scale values to be your overall view scale.
  4. Use the view scale to calculate the effective (actual) display size for game logic purposes.
  5. Actually scale your content by moving the camera along the Z axis.

In code form:

  // Adjust the camera to show world position 'centeredAt' - (0,0,0) or other - with
  // the display being at least 480 units wide and 360 units high.

  Vector3 minimumDisplaySize = new Vector3( 480, 360, 0 );

  float pixelsWide = camera.pixelWidth;
  float pixelsHigh = camera.pixelHeight;

  // Calculate the per-axis scaling factor necessary to fill the view with
  // the desired minimum size (in arbitrary units).
  float scaleX = pixelsWide / minimumDisplaySize.x;
  float scaleY = pixelsHigh / minimumDisplaySize.y;

  // Select the smaller of the two scale factors to use.
  // The corresponding axis will have the exact size specified and the other 
  // will be *at least* the required size and probably larger.
  float scale = (scaleX < scaleY) ? scaleX : scaleY;

  Vector3 displaySize = new Vector3( pixelsWide/scale, pixelsHigh/scale, 0 );

  // Use some magic code to get the required distance 'z' from the camera to the content to display
  // at the correct size.
  float z = displaySize.y /
            (2 * Mathf.Tan((float)camera.fieldOfView / 2 * Mathf.Deg2Rad));

  // Set the camera back 'z' from the content.  This assumes that the camera
  // is already oriented towards the content.
  camera.transform.position = centeredAt + new Vector3(0,0,-z);

  // The display is showing the region between coordinates 
  // "centeredAt - displaySize/2" and "centeredAt + displaySize/2".

  // After running this code with minimumDisplaySize 480x360, displaySize will
  // have the following values on different devices (and content will be full-screen
  // on all of them):
  //    iPad Air 2 - 480x360
  //    iPhone 1 - 540x360
  //    iPhone 5 - 639x360
  //    Nexus 6 - 640x360

  // As another example, after running this code with minimumDisplaySize 15x11
  // (tile dimensions for a tile-based game), displaySize will end up with the following 
  // actual tile dimensions on different devices (every device will have a display
  // 11 tiles high and 15+ tiles wide):
  //    iPad Air 2 - 14.667x11
  //    iPhone 1 - 16.5x11
  //    iPhone 5 - 19.525x11
  //    Nexus 6 - 19.556x11
\$\endgroup\$
2
\$\begingroup\$

If you come around to using the bars it's actually pretty simple to implement (I'm posting this even though the OP stated the opinion of it being unacceptable because it has the benefit of being not near as bad on mobile and it's a simple solution that requires no code whatsoever)

Camera.orthographicSize is a variable within the ortho camera (which most 2D games use) that fits the measured amount of game units vertically on the screen (divided by 2) (source). Thus, pick an aspect ratio that fits the vast majority of devices (I chose 16:9 as most screens I researched are 16:9, 16:10, 3:2) and add a mask that overlays that at a ratio.

Example:

In my game (not listed here as this is not an ad, can ask in comments if desired) we use portrait mode. To do a nice simple 16:9 I made my Ortho camera at size 16. This means the camera will adapt 32 game units of height (y: 16 through -16 in my case) into the device's vertical of the screen.

I then placed black masks with a game between -9 and +9. Voila, the game's screen looks the exact same on all devices and a little skinnier on devices that are a little wider. I've had absolutely no negative feedback regarding the masks. To do landscape simply flip those values and then you'd make the camera of size 9. Change the values to match whatever you've decided is your game unit scale.

The only place we've observed the black bar to show up significantly is on the iPad at 3:2. Even then, I've had no complaints.

\$\endgroup\$
1
\$\begingroup\$

I'm doing this in a game I am currently working on. I have a background image that is 1140x720. The most important bits (the ones that should never get cropped) are contained in the 960x640 middle area. I run this code on the start function of my camera:

    float aspect = (float)Screen.width / (float)Screen.height;

    if (aspect < 1.5f)
        Camera.main.orthographicSize = 3.6f;
    else
        Camera.main.orthographicSize = 3.2f;

    float vertRatio = Screen.height / 320.0f;
    fontSize = (int)(12 * vertRatio);

I also define sizes other than font size for buttons and such. It works well on every aspect ratio I've tested. It's been a while since I set it up, so I may be missing a step. Let me know if it doesn't work as expected and I'll see if I left anything out.

\$\endgroup\$
1
\$\begingroup\$

@Marcel's answer and code are great and helped me understand what was happening. It's the definitive answer. Just thought someone might also find useful what I ended up doing for my specific case: since I wanted something really really simple, one sprite to be always on screen, I came up with these few lines:

public class CameraFit : MonoBehaviour {

    public SpriteRenderer spriteToFitTo;

    void Start () { // change to Update to test by resizing the Unity editor window
        var bounds = spriteToFitTo.bounds.extents;
        var height = bounds.x / camera.aspect;
        if (height < bounds.y)
            height = bounds.y;
        camera.orthographicSize = height;
    }
}

I added this to the camera and dragged my sprite (it's my background) to the script's only property. If you don't want any black bars (horizontal or vertical) you can put a bigger background behind this one...

\$\endgroup\$
1
\$\begingroup\$

This is an >5 years old question, but it's still not only quite relevant, but also getting more and more complex over the years as there are even more screen ratios on mobile devices nowadays than even when this question was asked.

Since this question was ask, there have been quite a few additional options given in Unity (especially with Unity 2018). Still, there are quite a few tips & tricks when handling the screen ratio so that it can fit with almost every screen sizes.

First, the best universal method is, ironically, the old method: Dynamically build whatever screen-related assets in real-time when, for example, a scene is started. This is a similar thing to what is used for websites where you create a different kind of GUI & experience based on the screen sizes. For example, having a screen less wide than 800px would have 1 kind of GUI while having one of over 2000px would have another kind. It's really time consuming and relatively complex to build (hence more prone to bugs & errors), but offers the best versatility of any kind of setup. In such a way, your goal is to make sure every buttons and texts are always big enough to by pressed by people with big fingers and/or be read by anyone.

The main difference between doing it in a game engine (like Unity) and doing in HTML5 for the web is the fact that you're not stuck with a single "scene" in the game engine while HTML5 might requires you to work in a single web page as redirection scripts might be blocked by the User's browser. This means that you could start your game with a custom flash scene (such as a custom scene where your company logo appears) and, in that scene, you apply a checked that detect the screen sizes. Then, after, you make you scene transition script to decide which "scene" to load depending on the screen pixel sizes. This is far easier than having to script everything into a single scene.

The second way is a quite easier and faster, but it can have quite some impact on the performances depending on how good the optimization was applied. Simply put, using a 3D space (known as "World Canvas" in Unity) based GUI may saves you a ton of work. Unlike other kind of canvas, the World Canvas (3D based canvas) is getting automatically adjusted on the height of the screen based on its active screen rotation. This includes even a built-in Anti-Aliasing as well as a really good pixels ratio conversion of the textures using in the GUI which means that the GUI will look as if it was made especially for that screen resolution as long as each displayed textures doesn't exceed their maximum resolution. Another really important part of this method which is, for now, unique and can't be used with others is that this method also allow you to use basic VECTORS based graphics. Yup, for a while now Unity can render SVG files with flat colors. It's just not yet compatible with the GUI 5.x yet which is why this doesn't work with other GUI methods as this one is the only one who's using the proper Z-Buffer and some other built-in system of the engine to render the UI (which is why you can put both UI and 3D models in the canvas). Note that I precise that, for now, only basic Vectors based graphics can be used such as flat colors and really basic gradients (kinda like the limited HTML5 SVG-based code rendering). No outlines or styles or any kind of complex "rendered" magic can be done (yet?).

Lastly, the 3rd way is to use the "Canvas Scaler" component on the same GameObject as the Canvas' component. Usually, the best result comes from using the UI Scale Mode "Scale with Screen Size" and decide if the screen should be matching either the Width or Height of the target device. This methods comes with some restrictions and requires you to design something that can be well adjusted in one direction with a wide range of screen size. For this, I always suggest the use of something I call fractal UI design. This means to cut your menus in smaller sections while raising how "deep" the menus gets OR getting a menu design that allow you to display limited information at one time.

The best example I could give of how to cut a menu in smaller sections which widen the menu depth is making an option menu with tabs such as Sounds, Graphics, Gameplay, etc. Each of those tabs would have 1 to 5 options each at most. This can easily be displayed well on both small and big screens alike. You sacrifice maybe 60-80 pixel for the Tabs buttons, but you gain many times the remaining amount of pixels. As an example, the smallest yet still used Android-based screen size is 240x320 pixels. Yes, it's quite small, but it's surprisingly still used by some small devices in populated areas of China. If you avoid those, you avoid a tiny part of the market and the choice is yours. Personally, I usually focus on a basic of 480x800 pixels as a basic ratio & minimal screen sizes, but I keep looking at the GUI while in 240x320 now and then to make sure it's still "usable".

The quality of the 3rd method is that you can soft-launch the game with that method and, at a later point if necessary, apply an update that allow you to implement the 1st method. It's quite useful when you're on a tight budget and/or time allotment.

The downside of the 3rd method (and 1st) is mostly that you got to do quite a bit of maths when you're attempting to sync things properly. Such as having sprite-based animations with proper refresh rate based on the screen size because the sprite is tiled over the screen width or height is truly a pain to adjust properly without any wackiness.

As an example, one of my projects has a main menu with 2 animated gears that rotates and 2 chains that move up or down when the user change the menu selection. It's all in 2D and the chains are tiled at 100% pixel fidelity. This means that the chain seems "bigger" on a smaller screen slimmer on a bigger screen, but it has no weird pixels even if the screen reaches thousands of pixels in height. Yet the sprites of the chains are 53x254 and 33x254 and they are all part of a single Atlas 1024x1024 texture. To lighten the time requires to animate the UI properly, I created a general GUI animation script. Something that allows me to easily setup a list of GUI element so that they can animate by either rotation, changing sprites or by movement (translation) and this script's action can be remotely accessed by reference. Pretty straight-forward if you ask me. Still, to make it semi-universal, I had to add a few additional option (as Boolean parameters) that allows me to decide if the animation is affected by the screen ratio and on which direction if it's such a case. I noticed this to be needed when I tried the main menu on an old LG Android phone that has a, nowadays, unpopular screen ratio as the chain animation (which is based on the sprite switching speed like a sub-framerate) was 1.4x too slow while it was alright on more common screen ratios (like Android tablets) I tested it on.

P.S. If you wonder why I used 33x254 and 53x254 for the texture instead of numbers that are multiplication of 2, that is because of built-in Anti-Aliasing of the GUI textures where, sometimes, 1 additional pixel is read and displayed around resized GUI texture in a canvas. So while the texture size is 33x254, it sometimes display 34x254 pixels of the source. It's the usual float precision issue because of the resizing is done in floats (within the engine) that gets rounded up as integer for pixel reading.

\$\endgroup\$
0
\$\begingroup\$

There are a few ways to tackle this issue, and there isn't a perfect solution (at least I haven't found one yet) and the type of solution you go with is going to depend greatly on the type of game you're developing.

Regardless of you what you do, you should start off by picking the lowest possible resolution you'd like to support and build your sprites to that resolution. So if you're interested in developing for iOS, according to http://www.iosres.com/ the lowest iOS device resolution is 480x320.

From there, you can start scaling up the sprites to meet the higher resolutions. The caveat to this is that you'll eventually start to notice the sprites begin to blur the farther up you scale, in which case you can switch to another set of sprites built for the higher resolutions.

Or, you can ignore scaling completely, and just decide to display more of the game screen for higher resolutions (I believe this is how Terraria does it, for example). However, for many games, this isn't appropriate; competitive games for example.

Using black bars on top or bottom is unacceptable these days.

Is it? Lots of games that want to force 4:3 aspect ratios do this. Since you're using Unity, you can make use of the AspectRatioEnforcer script to aid in this.

\$\endgroup\$
2
  • \$\begingroup\$ I'm targeting mobile, so I cannot change the aspect ratio or resolution. Using low resolution assets is not an acceptable solution. Some games (especially 3D) it isn't an issue, you just show more or less. But games like Kingdom Rush, tower defense, and other games like Alex you want to see the same thing regardless of the device. If things do not show up in the right locations, the game won't work properly. \$\endgroup\$
    – Michael
    Commented Jul 6, 2014 at 21:00
  • \$\begingroup\$ Yes you can docs.unity3d.com/ScriptReference/Screen.SetResolution.html \$\endgroup\$ Commented Jul 6, 2014 at 21:44
0
\$\begingroup\$

There are multiple ways to handle this issue, it depends on your game and what will work best. For example, on our game Tyrant Unleashed we simply made wide maps with unimportant detailing on the sides, so that it's okay to cut off the sides on narrower devices. However other games might be better with an approach where you actually shift buttons around or something to fit the screen better.

(also part of our approach is also keeping a consistent height on all devices, only varying the width. This certainly makes life easier for us, but again this may or may not be any good for your specific game. It doesn't matter in our art style if the images are scaled a bit on different screens, whereas this might matter for something like pixel art. Basically, this is the "let Unity handle it" approach)

\$\endgroup\$
2
  • \$\begingroup\$ Have you done any projects where that wasn't an option? Almost all the tower defense style games I've seen and many other styles you can see the entire game view on the device without scrolling, and this is consistent across devices. On IOS you can make assets and swap them, but that becomes impossible (and really just a big PITA anyway) on Android. \$\endgroup\$
    – Michael
    Commented Jul 6, 2014 at 21:55
  • \$\begingroup\$ No I haven't. btw I just added some more detail about screen size \$\endgroup\$
    – jhocking
    Commented Jul 6, 2014 at 23:02
0
\$\begingroup\$

I'm using the following script which adds a targetAspect parameter to the camera and adapts its orthographicSize with respect to the screen ratio (more details in this blog post):

using UnityEngine;
using System.Collections;

public class AspectRatioScript : MonoBehaviour {

    public float targetAspect;

    void Start () 
    {
        float windowAspect = (float)Screen.width / (float)Screen.height;
        float scaleHeight = windowAspect / targetAspect;
        Camera camera = GetComponent<Camera>();

        if (scaleHeight < 1.0f)
        {  
            camera.orthographicSize = camera.orthographicSize / scaleHeight;
        }
    }
}
\$\endgroup\$
0
\$\begingroup\$

My method is mostly similar to the solutions given by others :) I'll try to explain in detail the approach which I take to make the game independent of the screen size.

Screen Orientation

Depending upon the screen orientation (Landscape or Portrait) , you need to consider whether the camera will scale with a fixed height or fixed width. Mostly I choose fixed width for landscape oriented games and fixed height for portrait oriented games.

Camera Scaling

As discussed this can be either fixed height or fixed width.

Fixed Height: The vertical area of the game will always fit to the screen height. And as the screen aspect ratio changes there will be extra space added to the left and right of the screen. To implement this you don't need to code anything, it is the default behaviour of unity camera.

Fixed Width: The horizontal area of the game will always fit the screen width. And extra space will be added to the top and bottom as the aspect ratio of the screen changes. To implement this you need to write a small piece of code. Later on make sure you remove the code form update function, and place it in awake.

using UnityEngine;

[ExecuteInEditMode]
public class ScaleWidthCamera : MonoBehaviour {

    public int targetWidth = 640;
    public float pixelsToUnits = 100;

    void Update() {

        int height = Mathf.RoundToInt(targetWidth / (float)Screen.width * Screen.height);

        camera.orthographicSize = height / pixelsToUnits / 2;
    }
}

In the editor you can change the targetWidth to define the world space area you want to display. This code is explained in the following video along with many other practices for 2D games :)

Unite 2014 - 2D Best Practices In Unity

Aspect Ratio

Following aspect ratio listed from widest to narrowest, covers almost all the screen sizes for both android and iOS

  • 5:4
  • 4:3
  • 3:2
  • 16:10
  • 16:9

I usually set all of these aspect ratios in the given order under the game window, as it is handy while testing for different screen sizes :)

Expendable Area

This is the area which gets added to the screen to the sides or top/bottom depending upon the camera scaling you have chosen.

For fixed height all the game elements should preferably fit in 16:9 ratio which is the narrowest. And the background should extend till it covers the 5:4 ratio. Which makes sure that your game never has black strips to the sides.

For fixed width it almost the same but here the elements should fit in 5:4 ratio and the BG should extend till 16:9.

Bounds

Sometimes we cannot use the expendable area approach as we need to utilise the whole available screen for the game play.

For example , consider a portrait game with fixed height , catching the coins falling from the sky. In this we need to give the player ability to move horizontally over the available screen width.

Hence we need the bounds of the camera in terms of world co-ordinates to know where exactly the left,right,top or bottom of the camera clips at the world position.
We can also use these bounds to anchor game elements or UI to a desired side of the camera.

Using Camera.ViewportToWorldPoint we can get the bounds.Viewport space is normalized and relative to the camera. The bottom-left of the camera is (0,0); the top-right is (1,1). The z position is in world units from the camera. For 2D/orthographic the z doesn't matter.

Vector3 leftBottom = camera.ViewportToWorldPoint(new Vector3(0, 0, camera.nearClipPlane));
Vector3 rightTop = camera.ViewportToWorldPoint(new Vector3(1, 1, camera.nearClipPlane));

float left = leftBottom.x;
float bottom = leftBottom.y;
float right = rightTop.x;
float top = rightTop.y;

UI

For UI we can apply the same concepts that we used for the game elements. After introduction of Unity5 UI and availability of plugins like NGUI this won't be much of a problem :)

\$\endgroup\$
0
\$\begingroup\$

I created the Unity Asset 'Resolution Magic 2D' to solve this very problem (ad: you can get it from the Unity Asset Store, or see more details at grogansoft.com).

The way I tackled the problem was as follows...

Define an area of the screen that must always be visible regardless of aspect ratio/resolution, and use a simple rectangle transform to 'stencil out' this region. This is your ideal screen shape. Using a simple algorithm I then zoom the camera until the region blocked out by the rectangle is as large as possible while still being 100% visible to the camera.

Then your main game area is always taking up as much screen as possible. And as long as there is sufficient 'extra' content (e.g. your background) outside of the rectangle area you defined before, players whose screen is not the same aspect ratio as your 'ideal' rectangle will see the extra content where black bars would otherwise go.

My asset also includes some logic for placing UI, but that is mostly obsolete due to Unity's new UI system.

My asset provides this out of the box with minimal setup, and it works really well on all platforms.

If you use this technique (or my asset), just make sure you design your game to have 'optional' space all around to accommodate screens that are wider or taller than your ideal (to avoid black bars).

I don't personally make 3D games, so I don't know if this would work in 3D (or if it is even necessary).

It's really hard to explain without pictures, so please visit my website (Resolution Magic 2D)

\$\endgroup\$
0
\$\begingroup\$

The best solution for me is to use the theorem of intersecting lines so that there is neither a cut off on the sides nor a distortion of the game view. That means that you have to step back or forward depending on the different aspect ratio.

\$\endgroup\$
-3
\$\begingroup\$

I created a AssetStore extension that allows for easier aspect switching called AspectSwitcher. It provides a system for allowing you to easily specify different properties for different aspects. There are generally two method that most people use to switch aspects. One is to provide different game objects for each aspect. The other is to create custom code that modifies the properties of a single game object based on the current aspect. That generally requires a lot of custom coding. My extension attempts to alleviate a lot of that pain.

\$\endgroup\$
1
  • 2
    \$\begingroup\$ This answer would be better, and seem less spammy, if you elaborated on the actual techniques one might use to address the OP's question. \$\endgroup\$
    – user1430
    Commented Oct 15, 2014 at 17:57

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .