Search Unity

Pixel Perfect Advice Needed

Discussion in '2D' started by ianmcelvain, Feb 29, 2016.

  1. ianmcelvain

    ianmcelvain

    Joined:
    Jan 28, 2014
    Posts:
    37
    Since I have started this platformer project I have used a pixel perfect script to help while I was developing it but now that the game has grown my pixel perfect script is getting in the way (illustrated with pictures)

    My problem is with the zoom factor of the Pixel Perfect Script; I want the field of view to be appropriate for the player (not too close or far away)

    This is zoom 1: too far away

    This is zoom 2: too close (want the player to have a good view of enemies coming)


    This is zoom 1.5 (I just added in the script:cool:) : This is the field of view im looking for but it disorients the pixels because 1.5 zoom doesn't fit with the pixel ratio calculations :(


    So I am just wondering if anybody has advice for PixelPerfect in Unity in regards to my zoom problem.
    Tuts, scripts, assets, etc... Any help will be AWESOME!


    Heres the script for a reference (Made by unity user Marrt):

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using UnityEngine.UI;
    4. namespace MoreMountains.CorgiEngine
    5. {
    6.  
    7.     //PixelPerfect Camera in Unity
    8.     //from Marrt
    9.  
    10.     //Spriteguy gathered from http://www.rpgmakervxace.net/topic/7926-8-direction-sprites/
    11.     public enum FollowMode { SnapToScreenPixel, SnapToArtPixel };
    12.  
    13.     public class PixelPerfect : MonoBehaviour
    14.     {
    15.  
    16.         public static PixelPerfect instance;    //make sure only one instance of this script exists
    17.  
    18.         //PIXELPERFECT VIEWPORT
    19.         public Camera cam;
    20.         public Transform camAnchor;
    21.  
    22.         private Transform followTarget;
    23.  
    24.         public FollowMode snapMode = FollowMode.SnapToArtPixel;
    25.  
    26.         private float orthoSize;
    27.  
    28.         protected Transform _target;
    29.  
    30.  
    31.         //pixel perfect & zoom
    32.         public bool readAngle = true;
    33.         private float camTilt = 53.13010235F;   //yields: zStretch 1.25F    yStretch= 1.6666666666F    for pixel perfect rendering
    34.         private float xStretch = 1F;        //factor of X stretching is 1F
    35.         private float zStretch;                 //factor of Z stretching applied to floor Tiles to make pixels square
    36.         private float yStretch;                 //factor of Y stretching applied to wall Tiles to make pixels square
    37.         public float pxPerUnit = 100F;       //one Unity MeterSpans pxPerUnit pixels
    38.         private float subPixelFactor = 2F;      //changes with zoom Level, is used to position actor pefectly
    39.                                                 //
    40.  
    41.         //rounding Values, width of a single SCREEN-pixel in world coordinates, if you need ART-pixel snapping use RoundToArtPixelGrid()
    42.         private bool pixelSnapOn = true;
    43.         private float xSnap = 0F;
    44.         private float ySnap = 0F;
    45.         private float zSnap = 0F;
    46.  
    47.  
    48.         void Start()
    49.         {
    50.             Zoom(1.5F);
    51.             followTarget = GameManager.Instance.Player.transform;
    52.         }
    53.  
    54.         void Awake()
    55.         {
    56.  
    57.             instance = this;
    58.  
    59.             InitViewPort(readAngle);
    60.             Zoom(1.5F);
    61.         }
    62.  
    63.         void Update()
    64.         {
    65.             if (Input.GetKeyDown("1")) { Zoom(1F); }
    66.             if (Input.GetKeyDown("2")) { Zoom(1.5F); }
    67.             if (Input.GetKeyDown("3")) { Zoom(2F); }
    68.             if (Input.GetKeyDown("4")) { Zoom(3F); }
    69.             if (Input.GetKeyDown("5")) { Zoom(4F); }
    70.             if (Input.GetKeyDown("6")) { Zoom(5F); }
    71.             if (Input.GetKeyDown("7")) { Zoom(6F); }
    72.         }
    73.  
    74.  
    75.         private void InitViewPort(bool read)
    76.         {
    77.  
    78.             if (!read)
    79.             {   //write tilt of cam
    80.                 camAnchor.transform.rotation = Quaternion.Euler(new Vector3(camTilt, 0F, 0F));
    81.             }
    82.  
    83.             //calculate stretch factors, they are 1 & infinity if environment is viewing from top
    84.             yStretch = 1F / Mathf.Cos(camAnchor.rotation.eulerAngles.x * Mathf.Deg2Rad);
    85.             zStretch = 1F / Mathf.Sin(camAnchor.rotation.eulerAngles.x * Mathf.Deg2Rad);
    86.  
    87.             yStretch = float.IsInfinity(yStretch) ? 1F : yStretch;
    88.             zStretch = float.IsInfinity(zStretch) ? 1F : zStretch;
    89.  
    90.             print("zStretch" + zStretch + "\tyStretch" + yStretch + "\nlenght of a pixel:" + 1 / pxPerUnit);
    91.         }
    92.  
    93.         private void Zoom(float subPxFactor)
    94.         {
    95.             print("Pixel 1:" + subPxFactor + "x" + subPxFactor);
    96.             orthoSize = GetPixelPerfectOrthoSize(subPxFactor);
    97.             //StartCoroutine(ZoomTransition());
    98.             cam.orthographicSize = orthoSize;
    99.         }
    100.  
    101.         private IEnumerator ZoomTransition()
    102.         { //during transition, we cannot be pixelperfect      
    103.             float timer = 1F;
    104.             while (timer > 0F)
    105.             {
    106.                 cam.orthographicSize = Mathf.Lerp(cam.orthographicSize, orthoSize, Time.deltaTime * 10F);
    107.                 timer -= Time.deltaTime;
    108.                 yield return null;
    109.             }
    110.             cam.orthographicSize = orthoSize;
    111.         }
    112.  
    113.         private float GetPixelPerfectOrthoSize(float screenPixelPerSpritePixelWidth)
    114.         {
    115.  
    116.             subPixelFactor = screenPixelPerSpritePixelWidth;    //1 means 1ArtPixel = 1 ScreenPixel, 2 means 1 Art = 2x2 Screen
    117.  
    118.             float s = pxPerUnit * subPixelFactor;
    119.  
    120.             xSnap = xStretch / pxPerUnit / subPixelFactor;
    121.             ySnap = yStretch / pxPerUnit / subPixelFactor;
    122.             zSnap = zStretch / pxPerUnit / subPixelFactor;
    123.             print("SceenPixelSnapingGrid:(" + xSnap + "," + ySnap + "," + zSnap);
    124.  
    125.             return cam.pixelHeight / s / 2F;
    126.         }
    127.  
    128.  
    129.  
    130.         public static Vector3 RoundToScreenPixelGrid(Vector3 worldPos)
    131.         {
    132.             float xSnapArt = PixelPerfect.instance.xSnap;
    133.             float ySnapArt = PixelPerfect.instance.ySnap;
    134.             float zSnapArt = PixelPerfect.instance.zSnap;
    135.             return new Vector3(Mathf.Round(worldPos.x / xSnapArt) * xSnapArt,
    136.                                 Mathf.Round(worldPos.y / ySnapArt) * ySnapArt,
    137.                                 Mathf.Round(worldPos.z / zSnapArt) * zSnapArt);
    138.         }
    139.  
    140.         public static Vector3 RoundToArtPixelGrid(Vector3 worldPos)
    141.         {
    142.             float xSnapArt = PixelPerfect.instance.xSnap * PixelPerfect.instance.subPixelFactor;
    143.             float ySnapArt = PixelPerfect.instance.ySnap * PixelPerfect.instance.subPixelFactor;
    144.             float zSnapArt = PixelPerfect.instance.zSnap * PixelPerfect.instance.subPixelFactor;
    145.             return new Vector3(Mathf.Round(worldPos.x / xSnapArt) * xSnapArt,
    146.                                 Mathf.Round(worldPos.y / ySnapArt) * ySnapArt,
    147.                                 Mathf.Round(worldPos.z / zSnapArt) * zSnapArt);
    148.         }
    149.  
    150.         //UI-Button Functions
    151.  
    152.         public void PixelSnapOn(bool on)
    153.         {
    154.             pixelSnapOn = on;
    155.         }
    156.  
    157.         public void ToggleSnappingMode(bool art)
    158.         {
    159.             if (art)
    160.             {
    161.                 snapMode = FollowMode.SnapToArtPixel;
    162.             }
    163.             else
    164.             {
    165.                 snapMode = FollowMode.SnapToScreenPixel;
    166.             }
    167.         }
    168.     }
    169.  
    170. }
     
  2. Jonathan-Westfall-8Bits

    Jonathan-Westfall-8Bits

    Joined:
    Sep 17, 2013
    Posts:
    259
    Atomiz2002 likes this.
  3. ianmcelvain

    ianmcelvain

    Joined:
    Jan 28, 2014
    Posts:
    37
    Thanks for the reply and I have seen that before but have decided to take a whack at it again and it didnt help with the problem. Unity just has a problem with 2D Pixel rendering. I cant get the exact FOV i would want. Considering going to Game Maker studio, awesome pixels and good games made from it such as risk of rain, splelunky, hotline: miami.
     
  4. Jonathan-Westfall-8Bits

    Jonathan-Westfall-8Bits

    Joined:
    Sep 17, 2013
    Posts:
    259
    Depending on if you want to spend money for a good camera system you might want to check this out.

    https://www.assetstore.unity3d.com/en/#!/content/42095

    It supports pixel perfect. It has a demo if you want to try it out.

    Other than that Pixel Perfect is a matter of trial and error between FOV and PPU. Either way, I wish you luck on your project.
     
  5. Douvantzis

    Douvantzis

    Joined:
    Mar 21, 2016
    Posts:
    79
    I have created a simple camera script that I think does exactly what you want and it is free.

    You can get the ideal orthographic camera height (or width) and the script will chose the closest pixel-perfect size. So, this may result is zooming-in or zooming-out. You can even set a maximum allowed width or height, so that you don't zoom-out too much.

    Get it for free:
    https://www.assetstore.unity3d.com/en/#!/content/64563

    You should keep in mind though that this problem exists in every engine. It's just there are specific pixel-perfect camera sizes, given a screen resolution.
     
  6. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    613
    I want to share some of my insights and mildly slap you on the butt for using Zoom(1.5F):

    There is no "Pixel-Perfect" if you use a Zoom() level of 1.5f as you have noticed.

    I don't know if Zoom() was the best name for that function because Zoom(1.5F) just means that 1x1-ArtworkPixel will be shown on 1.5x1.5 ScreenPixels. I refer to this factor as subPxFactor.
    That this non integer subPxFactor yields varying line-thicknesses is obvious, look at your picture for example:
    upload_2016-8-10_13-28-9.png
    I guess that all letters have a line width of 1px in your artwork. But on screen this line becomes sometimes two, and sometimes one. Because at this "zoom", each 2nd pixel on your screen samples its point filtered color-value at the very knife edge between two pixels of the Texture. Therefore floating point inaccuracies or some other voodoo decide which to take, you can try this in MS-Paint:
    upload_2016-8-10_13-27-14.png

    My approach:

    What you have to do is consider all possible resolutions that you might have to cater.
    upload_2016-8-10_13-43-10.png

    Create a function that will yield you a subPxFactor that creates the lowest deviation in orthographic viewport size across all your supported resolutions
    upload_2016-8-10_13-8-31.png

    Then visualize it. Consider this DebugScreen of our project for example. The outer frame shows exactly what a viewport at 1920x1080 resp. 960x540 would contain, the others are showing other resolutions in above pic (low budget samsung phones and iphones). The resolution varies, but the area viewed is nearly the same and each one is Pixel-Perfect. What varies between them is the subPxFactor. 1920x1080 has a subPxFactor of 6, while 960x540 has 3 - but those have the same border and viewport-size.

    upload_2016-8-10_12-59-24.png

    You might argue "Well, then your game offers advantages for players with specific resolution devices". Of course, but it could also be said for different aspect ratios (16:10 vs 16:9) in most cases. If you want pixel-perfection across multiple resolutions, your game has to be made in a way that it doesn't penalize or reward any of the possible resolutions. If your game is completely dependent on seeing the incoming fire ball from the edge of the screen at resolution-A 200ms prior to resolution-B... i guess your game mechanics aren't in sync with the actual representation of your game.

    We were very careful in that regard because our game features melee and ranged. We use a nominal subject-size of 24x24px and we target lower resolutions like 960x540(=1080p/2). At that resolution we get subPixelFactors ranging from 2 to 4 when matching the results with our compromise-comfort-zone between overview and detail at melee range.

    We arrived at 24x24 after asking the very question you asked in OP: How can i solve non integer subPxFactors to be pixel-perfect? You cannot, the answer is: You have to design your artwork from the beginning with resolution in mind to avoid needing non-integer subPixelFactors.

    24x24px was the best compromise for us and now we design for that size. We only arrived there after weeks of tinkering and checking what view-area would suit our game. You too should make some mockup characters with different sizes (like 16x16, 24x24, 32x32...) and THEN check which subPxFactor-subjectSize-combination is feasible for your gameplay.

    Edit: Improved wording
     
    Last edited: Aug 17, 2016
    Martin_H and BTStone like this.
  7. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    613
    Removed
     
    Last edited: Jun 14, 2018
  8. Undertaker-Infinity

    Undertaker-Infinity

    Joined:
    May 2, 2014
    Posts:
    112
    Upcoming asset that will help with this: