Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

2D Pixel Art Game with Smooth and Pixel Perfect Camera

Discussion in '2D' started by BusyRoots, Mar 17, 2018.

  1. BusyRoots

    BusyRoots

    Joined:
    Jul 25, 2017
    Posts:
    41
    Hello everybody,

    since the beginning of my 2D pixel art game development I researched from time to time about how to make the camera movement as smooth as possible without any stutters or ripple/warping effects on the sprites and still being pixel perfect.

    But up to now I'am not 100% satisfied with my camera movement. Since I found some very good sources regarding this topic over time I thought its maybe good to share this information. On one site I hope it can help somebody with similar problems and on the other side maybe I have a misconception or overlooked something.

    1. Basics - Orthographic Size
    When you draw your sprites or your canvas for a pixel art game you draw (unsurprisingly) single pixels ^^. But you need to choose a resolution (native resolution) for your drawings in Photoshop, Pyxel Edit, aseprite, Pixaki, Paint or whatever program you like to use. To have a pixel perfect image in your game at the end you need to scale your native resolution by an integer N (1, 2, 3, …) because there are no half pixels. Images look blurry or the drawing pixels aren't square if they aren't pixel perfect. That means

    N * drawing pixel = screen pixel​

    must always be fulfield to a have a pixel perfect image on the screen.

    The following image:
    Foto 16.03.18, 16 14 35.jpg
    is a close up pixel perfect „screenshot“ (with a smartphone camera) from the game FEZ on a screen with a resolution of 2560x1440. You basically see that 1 drawing pixel (e.g. one of the yellow cord pixel of the red hat) consists of 4 screen pixel. So the native resolution is 2560x1440 divided by 4 = 640x360.


    These sources have some good visual explanetaions:

    But which native resolution shell we use? This obviously deppends on the screen (platform) you designing for (Smartphones, Tablets, PC, Consoles, Consoles hooked on TV-Screens, …).I like to make a game for PCs. The (probably) most common resouloution is 1920x1080 (16:9 ratio). So I chose 640x360 as native resolution. Its a good trade off between the level of detail for drawing and having a pixel perfect and black bazle free images on common resolutions:
    • 640x360 *2 = 1280x720
    • ... *3 = 1920x1080 (HD)
    • ... *4 = 2560x1440 (WQHD)
    • … *6 = 3840x2160 (4k)
    • … *8 = 5120x2880 (5k)

    Here is an interesting blog post from D-Pad Studio the makers of Owlboy about the Hi-Bit Era:
    As side note: This websites shows you the wolrdwide most common screen resolutions:
    So my Unity settings are as follows:
    • PPU (pixels per unit) = 16px/unit (my tiles are also 16x16 that means → 1 tile = 1 unit)
    • orthographic size = 360px / (2* 16px/unit) = 11.25 unit
    For my Sprites I have the following settings:
    • PPU = 16
    • Filter Mode = Point (no filter)
    • Compression = None

    2. Smooth (floating point) Camera/Character Movements on a integer Screen
    This is the tricky part I think. Because whenever you like to move something smooth in Unity you have floating point operations (lerping, physics calculations, …) so thats a probleme on an integer screen (no half pixels) and I think that my stuttery camera and rippling/warping sprite have to do with this relation.

    To analyse this I created a Unity project as test bench where I tried 3 different movements techniques for the player:
    • Rigidbody2D.MovePosition(...)
    • Rigidbody2D.velocity(...)
    • Rigidbody2D.AddForce(...)
    And to 2 different „camera follow player“ functions, where each in its core consits out of one of the following functions:
    • Camera.transform.position = Vector3.MoveTowards(lastCameraPosition, aimPosition, speed*time
    • Camera.transform.position = new Vector3(Player.xposition, Player.yposition

    I switched in my test bench project between all of the above mentioned techniques and tried all of them in a built project (anti aliasing and anistropic textures turned off) but no success. Still a stuttery camera and ore warping effects on the sprites.


    Regarding this issue I also found another post that addresses this problem:
    Compared to games like Stardew Valley, Kingdom New Lands, Eitr or FEZ my camera is really worse. I know not all of these games are made with Unity but how can I achieve such a nice camera with Unity? What did these guys do different? How do you guys achieve a smooth camera? Do you have recommendations? I feel like I don't make any progress on this topic :(.


    I'm thankful for every answer and hint :).

    Best regards,
    Bredjo
     
    Last edited: Mar 17, 2018
  2. Fab4

    Fab4

    Joined:
    Sep 30, 2012
    Posts:
    114
    I do the following:
    Vector3 intendedPosition (where the camera is supposed to be)
    Vector3 theoreticalPosition (where it would be without pixel Perfect)
    Vector3 activePosition (where I really place my camera)

    All you need to do is to lerp between intended position and theoretical position. This makes the camera go smooth.
    Instead of setting the transform position of the camera to the theoretical, I calculate the closest whole pixel position of the camera into activePosition.
    If each pixel is e.g. 0.1 Unity Units a theoretical Position of 0.432 is set to 0.4

    The activePosition is then put into the Transform. Position.

    Theoretical position values must be kept the whole time and intended position is public Tobe set by other scripts.

    If you are interested into the script. Please write me an pm.

    Best regards
     
    BusyRoots likes this.
  3. BusyRoots

    BusyRoots

    Joined:
    Jul 25, 2017
    Posts:
    41
    Thank you very much for your answer :).
    It took me a while to understand but I think I got it now. Correct me if I'm wrong:

    The key is to move the camera in steps of whole pixel means discrete (integer) pixel by pixel steps. Since the camera moves in unity units we need to know how much units represent 1 pixel. This depends on the PPU settings. In my case PPU = 16 that means the inverse number UPP = 1/16 (Units Per Pixel) gives us the answer. That means 0.0625 units = 1 pixel.

    So the script for the camera movements needs to do the following steps:
    1. lerp camera from current position to the intendedPosition BUT instead of assigning the lerp result direct as new camera position we save it in a variable theoreticalPosition (Vector2, not pixel perfect)
    2. now we transfrom with a custom function the x and y value of the theoreticalPosition to their closest pixel perfect pandents
    3. we assign the new closest pixel perfect x (xPixelPerfectValue) and y (yPixelPerfectValue) value to the camera.transform.position.
    I implemented this with the script down below. The script is direct attached to the Camera GameObject.
    The good thing is now all the sprites in the scene are pixel perfect and I have no warping effects but the camera movement is really juddery (because of the discrete steps).
    What have I done wrong? Did I forgot something?

    Best regards,
    Bredjo

    Code (CSharp):
    1. // CameraMovementSpeed = 1000
    2. public float CameraMovementSpeed
    3. // PPU = 16
    4. public int PPU
    5.  
    6.  void Start ()
    7.     {
    8.         _invPPU = 1 / (float) PPU;
    9.         _zStartPositionCamera = transform.position.z;
    10.     }
    11.  
    12.  
    13. void LateUpdate()
    14.    {
    15.        transform.position = WholePixelPositionMovement(new Vector2(Player.transform.position.x, transform.position.y));
    16.    }
    17.  
    18. private Vector3 WholePixelPositionMovement(Vector2 intendPosition)
    19.    {
    20.        Vector2 theoreticalPosition;
    21.        float xPixelPerfectValue;
    22.        float yPixelPerfectValue;
    23.  
    24.        // lerping setup
    25.        journeyLength = Math.Abs(transform.position.x - intendPosition.x);
    26.        float distCovered = Time.deltaTime * CameraMovementSpeed;
    27.        float fracJourney = distCovered / journeyLength;
    28.        // therotical position <-- NOT Pixel Perfect or in Whole Pixel Position
    29.        theoreticalPosition = Vector2.Lerp(AsVector2(transform.position), intendPosition, fracJourney);
    30.        // exchange x and y values with thier closest whole pixel values
    31.        xPixelPerfectValue = CalculateWholePixelPerfectPosition(theoreticalPosition.x);
    32.        yPixelPerfectValue = CalculateWholePixelPerfectPosition(theoreticalPosition.y);
    33.        // return Vector with x and y closest pixel values
    34.        // z values stays untouchted
    35.        return new Vector3(xPixelPerfectValue, yPixelPerfectValue, _zStartPositionCamera);
    36.    }
    37.  
    38.  
    39. private float CalculateWholePixelPerfectPosition(float valueWithoutPixelPerfection)
    40.    {
    41.        // divide value without pixel perfection by the inversed pixel per unit value (unit per pixel)
    42.        // _invPPU = 1/PPU
    43.        float resDivision = valueWithoutPixelPerfection / _invPPU;
    44.        // cut off decimals
    45.        int resDivisionInt = (int)resDivision;
    46.        // get only decimals and multiply with factor 10 to make rounding decision
    47.        float resDivisionDezim = (resDivision - resDivisionInt) * 10;
    48.        // rounding up if decimals > 0.5 (0.5 = 5/10)
    49.        if (resDivisionDezim >= 5)
    50.        {
    51.            resDivisionInt++;
    52.        }
    53.        // resDivisionInt = integer amount number of _invPPU for closestWholePixelValue
    54.        float closestWholePixelValue = resDivisionInt * _invPPU;
    55.        // return closestWholePixelValeu
    56.        return closestWholePixelValue;
    57.    }
    58.  
    59.  
    60. private Vector2 AsVector2(Vector3 vector3)
    61.     {
    62.         return new Vector2(vector3.x, vector3.y);
    63.     }
     
    Last edited: Mar 19, 2018
    Good_Punk and rakkarage like this.
  4. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Maybe it's fine, but I notice that all 3 of your character movement approaches are involving 2D physics.

    Unless you're making some pixel-perfect version of Angry Birds, you probably don't need (and should not be using) physics to move your character around. Just move the Transform directly.

    Moreover, if you set your scale so that 1 pixel = 1 unit, you can even use integer math for all your unit positions, just like the video games of yore. Or you can continue to do floating-point calculations, and just round to the nearest whole pixel, just like you're now doing for the camera above.
     
    ridlr and BusyRoots like this.
  5. BusyRoots

    BusyRoots

    Joined:
    Jul 25, 2017
    Posts:
    41
    Thank you also JoeStrout very much for your answer :).

    I did a lot of testing and changes in my test bench project and figured out the core of the problem.
    The stutters a caused by the movement of the player. Since in previous states the camera followed the player without any delay the stutters transfered from the player straight to the camera movement.

    In the current status the camera is fixed and I only the player is moving so the stutters are visible at the player sprite. I tested player movements with and without physics but I have stutters in both cases. To show you what I mean I recorded some gameplay of the built project. But I had to record it external with a tablet since the stutters are even worse if I did some screen recording with OBS. I uploaded the video to youtube.

    Before you watch the video here some explanations:
    • pink text: movement typ of the player (active state is labelled with true or false):
      • physics based (in the video I only use the Rigidbody2D.velocity function as comparison)
      • none physics based movement (pixel perfect movement, see script below)
    • red text: player x and y position, screen resolution (of the recorded screen)
    • blue text on the top center: refresh rate (or FPS), to set the fps for the game I do:
    Code (CSharp):
    1.  void Awake()
    2.     {
    3.         Application.targetFrameRate = Screen.currentResolution.refreshRate;
    4.     }
    Time slots:
    • 00:00 - 00:37: using pixel perfect movement (no physics involved) but still some stutters if you follow careful the player movement (see also the player x position is always in 0.0625 steps (1/16))
      • 00:00 - 00:12. doing some stop and go to show the pixel perfect x position of the player
    • 00:37 - End: using Rigidbody2D.velocity movement and still regular stutters.

    I must admit you need to look close to see the stutters and you would definitively see it better if you run the project on your own pc. To see the stutters follow the player (yellow rectangle with 2 black stripes) during the longer movement phases. There are time periods where the 2 black stripes on the player jiggle more heavily (e.g. the whole first time slot 00:00 to 00:37 and from 00:59 to 1:06).

    Here the link to the video:



    Following the code for the pixel perfect movement function without physics:
    (source: https://gamedev.stackexchange.com/questions/104131/how-to-do-pixel-perfect-movement-with-unity3d)


    Code (CSharp):
    1. void Update()
    2.     {
    3.         // get input keys for transform based movment
    4.         InputForTransformBasedMovement();
    5.  
    6.         // pixel perfect movement
    7.         PixelPerfectMovementController();
    8.     }
    9.  
    10. // Input for Transform Based Movement
    11. private void InputForTransformBasedMovement()
    12.     {
    13.         // Move Left
    14.         if (Input.GetKey(KeyCode.A))
    15.         {
    16.             // move left
    17.             _movement.x -= MovementSpeed * Time.deltaTime;
    18.             _movingLeft = true;
    19.         }
    20.         // Move Right
    21.         if (Input.GetKey(KeyCode.D) && !_movingLeft)
    22.         {
    23.             // move right
    24.             _movement.x += MovementSpeed * Time.deltaTime;
    25.         }
    26.  
    27.         // reset value for next Update() call
    28.         _movingLeft = false;
    29.     }
    30.  
    31. // Pixel Perfect Movement Controller    
    32. private void PixelPerfectMovementController()
    33.     {
    34.         // Clamp the current movement
    35.         Vector2 clamped_movement = new Vector2(PPV(_movement.x), 0);
    36.  
    37.         // Check if a movement is needed (more than 1px move)
    38.         if (clamped_movement.magnitude >= _invPPU)
    39.         {
    40.             // Update velocity, removing the actual movement
    41.             _movement = _movement - clamped_movement;
    42.             //Debug.Log("asdfasdf");
    43.             if (clamped_movement != Vector2.zero)
    44.             {
    45.                 //Debug.Log("Set new position");
    46.                 // Move to the new position
    47.                 transform.position = new Vector2(PPV(transform.position.x), PPV(transform.position.y)) + clamped_movement;
    48.             }
    49.         }
    50.     }
    51.  
    52. // Find Nearest Pixel Perfect Position  
    53. // PPV = Pixel Perfect Value
    54. private float PPV(float valueWithoutPixelPerfection)
    55.     {
    56.         // divide value without pixel perfection by the inversed pixel per unit value (unit per pixel)
    57.         // _invPPU = 1/PPU
    58.         _divisionResult = valueWithoutPixelPerfection / _invPPU;
    59.  
    60.         // resDivisionInt = integer amount number of _invPPU for closestWholePixelValue
    61.         float closestWholePixelValue = Mathf.Round(_divisionResult) * _invPPU;
    62.  
    63.         return closestWholePixelValue;
    64.     }

    And here are my quality settings (I run the project in Very Low state):

    Quality_Settings_CameraTestbench_Stutters.JPG



    Best regards,
    Bredjo
     
    Last edited: Jun 1, 2020
  6. BusyRoots

    BusyRoots

    Joined:
    Jul 25, 2017
    Posts:
    41
    If I don't set a value
    Application.targetFrameRate
    and therefor don't limit the FPS my game/test bench project runs with about 1600 FPS smoother of course but I even then have occasionally stutters round about every 3 to 6 seconds in the player movement. Very strange :( ...
     
  7. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Well, your display can't actually show 1600 FPS. So that's a bit silly. If you really want to pixel-perfect, old-school movement, you should set your project to sync to the monitor referesh (in Player settings IIRC).
     
  8. BusyRoots

    BusyRoots

    Joined:
    Jul 25, 2017
    Posts:
    41
    Your're right that's silly ^^.
    I activated the V Sync Count (Edit > Project Settings > Quality) but still no improvements.
     
  9. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    I don't know what to say at this point. Perhaps print out (via Debug.Log or writing to a file) the position of your object on each frame (i.e. Update), along with the Time.deltaTime value to make sure your frame rate isn't stuttering. Make sure it's exactly what you think it should be, and if not, then work backwards to figure out why.
     
  10. BusyCat

    BusyCat

    Joined:
    Jan 9, 2015
    Posts:
    37
    I don't mean to take this off-topic but, @JoeStrout , you seem to imply physics are inappropriate in a 2D game and, while I'm inclined to disagree, I wanted to better understand your meaning and rationale. I am probably misunderstanding.
     
  11. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Not inappropriate, but in most cases not necessary, and the harder way to do things. There was no physics engine in Mario, Metroid, or Castlevania. Nor in Lemmings, or Warcraft, or <insert great 2D game of yore here>. They didn't need them, and if you're making a game similar to any of these, then neither do you.

    Using the physics engine means at least partially giving up control of your character (and the enemies, projectiles, etc.). The whole point of it is to calculate how things should move according to physics forces and collisions, which means your code doesn't determine how things move. So, getting the really tight, natural-feeling, perfect motion characteristic of (say) Mario or Smash Bros is really difficult in this case. You're constantly fighting the physics engine to try and get the motion you want.

    On the other hand, if you're making the next Angry Birds, where collision response and tumbling objects is a major part of the game, then by all means, let the physics engine do the work for you. My only complaint is that because the first tutorial most Unity newbies do is Roll-a-Ball, they think the physics engine is the only way to move things around in Unity, when that's far from true.

    This article of mine is mostly about 2D animation, but the demo project also shows a character able to run, skid to a stop, jump, and double-jump, with very tight motion controls, and all without Unity physics.
     
    ExpiditionVortex and BusyCat like this.
  12. BusyCat

    BusyCat

    Joined:
    Jan 9, 2015
    Posts:
    37
    Excellent answer and totally agreed. Thanks.
     
    JoeStrout likes this.
  13. BusyRoots

    BusyRoots

    Joined:
    Jul 25, 2017
    Posts:
    41
    For my 2D game I honestly don't really need physics but for starters it made things easier for me. But I will definitively study your article @JoeStrout. Thank you for sharing :).
     
    Last edited: Apr 13, 2018
  14. BusyRoots

    BusyRoots

    Joined:
    Jul 25, 2017
    Posts:
    41
    And I'm happy to tell you that I found my mistake :):):).

    Following some steps for a smooth pixel perfect 2D camera:

    1. Scale PPU
    The completely stutter free camera I aimed for is not possible in pixel art. To maintain a pixel perfect image (no sprite jitter) you can minimum move the camera in 1 pixel steps (correct me if I'm wrong). Dependent on your monitor resolution (also the refresh rate) and your screen pixel to drawing pixel ratio (called "N" in the first thread post) you have more or less noticeable stutters.
    The core aspect I noticed is:
    The bigger N the higher is the amount of screen pixel displaying 1 drawing pixel and the more steps you have to move 1 drawing pixel on your screen, which results in a more smooth movement. (correct me if I'am wrong)

    The reason why I had relative strong noticeable stutters is because I forgot to consider the scale factor of the screen resolution to the native resolution.
    Earlier I wrote, thanks to the answer of @Fab4,:

    This is right if you only play your game in your native resolution (in my case: 640x360, PPU = 16). But if you play in 1920x1080 you need to add a factor of 4 (1920/640 = 4, or 1080/360 = 4) which gives us ScreenPPU = 4*PPU = 64 (4 times more steps).
    That means:
    Screen PPU = PPU * (Screen Resolution Width / Native Resolution Width)

    (heights can also be used)

    A lot of these things are already mentioned here:

    But I guess needed to go the hard way to really understand whats going on ^^. Again thank you all for your support :D.​

    2. Built Project!
    Always test the build of your game, because I noticed in the editor game mode you always get some stutters (I guess because of the edit possibility and other things Unity provides in that mode).​

    3. Turn Vsync On
    After applying the methods described in 1., I noticed that Vsync does improve the smoothness (thanks to @JoeStrout for the hint). In Unity (Version 2017.3.1f1) go to: Edit > Project Settings > Quality > Other > V Sync Count​

    4. Smooth/Sluggish Camera
    I noticed that a smaller camera speed which lets the camera act more sluggish/lagging behind helps retouching the stuttering mentioned in section 1. as well.
    Conclusion:
    When I play my built game with a native resolution of 640x360 (PPU = 16, camera orthographic size = 11.25) on a Full HD screen @60Hz (N = 3) I do notice some stutters but its much better as before and more important: I know what causes these stutters (see section 1.).
    I also made a best case test with a native resolution of 320x180 (PPU = 16, camera orthographic size = 5.625) on a 2560x1440 (@144Hz) screen which gives me N = 8, which results in very smooth movement without any noticeable stutters.

    !!!!!!!!! NOTE: If anyone has other ideas, techniques, tricks or experience how to make 2D pixel art games appeal stutter free or reduce stutters I'm always interested :) !!!!!!!!!.

    I also added some functions to automatically calculate the right Screen PPU value dependent on the orthographic size of the camera and your screen resolution. Here a minimal example script with all the core functionalities I'm currently using:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class SmoothPixelPerfectCamera : MonoBehaviour {
    4.  
    5.     // ---------------------------------------- public ----------------------------------------
    6.     public GameObject Player;
    7.     public GameObject MainCamera;
    8.     // values between 1 - 3 make camera sluggish, 10 seems instant
    9.     public float SmoothSpeed = 3;
    10.  
    11.     public static float PPUScale;
    12.     public static float ScreenPPU;
    13.  
    14.     public int NativePPU;
    15.  
    16.  
    17.  
    18.     // ---------------------------------------- private ----------------------------------------
    19.     private Camera _mainCamera;
    20.  
    21.     private float _zStartPositionCamera;
    22.     private float _yOffsetCameraToPlayer;
    23.     private float _orthographicCameraSize;
    24.  
    25.     private int _screenResolutionWidth;
    26.     private int _nativeResolutionWidth;
    27.  
    28.  
    29.  
    30.     // Use this for initialization
    31.     void Start ()
    32.     {
    33.         _mainCamera = MainCamera.GetComponent<Camera>();
    34.         _yOffsetCameraToPlayer = MainCamera.transform.position.y - Player.transform.position.y;
    35.         _zStartPositionCamera = MainCamera.transform.position.z;
    36.         _screenResolutionWidth = 0;
    37.     }
    38.  
    39.  
    40.     // Update is called once per frame
    41.     void Update()
    42.     {
    43.  
    44.         // if screen resolution changes or orthographic size of the camera --> adapt values to maintain pixel perfection
    45.         if (_screenResolutionWidth != Screen.currentResolution.width || !Mathf.Approximately(_orthographicCameraSize, _mainCamera.orthographicSize))
    46.         {
    47.             // update values for pixel perfection
    48.             UpdatePixelPerfectScaleValues();
    49.         }
    50.  
    51.     }
    52.  
    53.  
    54.     void LateUpdate () {
    55.  
    56.         // only move camera if distance between player position and camera is bigger then 10* 1/ScreenPPU
    57.         // reason: single latecomer stutters when player is standing still (single adjustings of the script)
    58.         if (Mathf.Abs(Player.transform.position.x - GrafikAndGuiSettings.PPV(transform.position.x)) > 10 * 1 / GrafikAndGuiSettings.ScreenPPU)
    59.         {
    60.             SmoothPixelPerfectCamera();
    61.         }
    62.  
    63.     }
    64.  
    65.  
    66.     //Smooth pixel perfect camera
    67.     private void SmoothPixelPerfectCamera()
    68.     {
    69.         Vector2 desiredPosition = new Vector2(Player.transform.position.x, Player.transform.position.y + _yOffsetCameraToPlayer);
    70.         Vector2 pixelPerfectDesiredPosition = new Vector2(PPV(desiredPosition.x), PPV(desiredPosition.y));
    71.         Vector2 smoothPosition = Vector2.Lerp(transform.position, pixelPerfectDesiredPosition, Time.deltaTime * SmoothSpeed);
    72.         Vector2 smoothPixelPerfectPosition = new Vector2(PPV(smoothPosition.x), PPV(smoothPosition.y));
    73.  
    74.         // add z-start-position of camera
    75.         MainCamera.transform.position = (Vector3)smoothPixelPerfectPosition + Vector3.forward * PPV(_zStartPositionCamera);
    76.     }
    77.  
    78.  
    79.     // PPV = Pixel Perfect Value
    80.     public static float PPV(float valueWithoutPixelPerfection)
    81.     {
    82.         // divide value without pixel perfection by the inversed pixel per unit value (unit per pixel)
    83.         // _screenPPU = 1/PPU
    84.         float screenPixelPosition = valueWithoutPixelPerfection * ScreenPPU;
    85.  
    86.         // resDivisionInt = integer amount number of _screenPPU for closestWholePixelValue
    87.         float pixelPerfectScreenUnitPosition = Mathf.Round(screenPixelPosition) / ScreenPPU;
    88.  
    89.         return pixelPerfectScreenUnitPosition;
    90.  
    91.     }
    92.  
    93.  
    94.     private void UpdatePixelPerfectScaleValues()
    95.     {
    96.         float aspectRatio = (float)16 / 9;
    97.         // calculate native resolution width, float auxiliary variable for a full float calculation
    98.         float auxiliaryVar = aspectRatio * _mainCamera.orthographicSize * NativePPU * 2f;
    99.         _nativeResolutionWidth = (int)auxiliaryVar;
    100.  
    101.         // calculate PPUScale
    102.         PPUScale = (float)Screen.currentResolution.width / _nativeResolutionWidth;
    103.         // translate Native Pixel Per Unit (PPU) to actually ScreenPPU
    104.         ScreenPPU = PPUScale * NativePPU;
    105.  
    106.         // save new resolution
    107.         _orthographicCameraSize = _mainCamera.orthographicSize;
    108.         _screenResolutionWidth = Screen.currentResolution.width;
    109.     }
    110. }
     
    Last edited: Oct 10, 2021
    JoeStrout likes this.
  15. BusyRoots

    BusyRoots

    Joined:
    Jul 25, 2017
    Posts:
    41
    Update:
    In the previous approach I set direct the camera.transform.position from one pixel to the next and that causes noticeable small stutters, particular in that cases where the screen pixel to drawing pixel ratio is small.
    Instead of setting direct the transform position of the camera to the next pixel perfect position I use now a coroutine, that contains the Vector2.MoveTowards method to move the camera (fast) from one pixel to the next (see the code below). In between the pixel positions, the camera is strictly speaking not pixel perfect but since the movement speed is high, you do not notice this and there is no sprite wiggle.
    Overall this makes things also bit smoother.

    A friend of mine gave me also some other keywords: Lanczos Resampling and subpixel accurate movement to look into. We also so talked about my problem with the discrete pixel steps and it might also be solvable with making a FFT (Fast Fourier Transformation) (I actually have an electrical engineering background). But I assume Unity is already doing something like that internally since I can move objects not only on the pixel grid but also in between, am I right? Anyway I'll deal with these topics next to see if i can improve the situation.

    If anyone knows more about these things or has some experience in this areas, pleas get in touch. I would be very grateful :).

    Best regards,
    Bredjo

    Code (CSharp):
    1.  IEnumerator MoveTowardsNextPixel(Vector2 endPosition, float zStartPosition, float speed)
    2.     {
    3.         float sqrRemainingDistance = ((Vector2)transform.position - endPosition).sqrMagnitude;
    4.  
    5.         while (sqrRemainingDistance > float.Epsilon)
    6.         {
    7.             Vector2 newPosition = Vector2.MoveTowards(transform.position, endPosition, speed * Time.fixedDeltaTime);
    8.  
    9.             //Vector3 newPixelPerfectPosition = GrafikAndGuiSettings.PPVVector3(newPosition) + Vector3.forward*_zStartPositionCamera;
    10.             transform.position = (Vector3)newPosition + Vector3.forward * _zStartPositionCamera;
    11.  
    12.             sqrRemainingDistance = ((Vector2)transform.position - endPosition).sqrMagnitude;
    13.  
    14.             yield return null;
    15.         }
    16.  
    17.         transform.position = (Vector3)endPosition + Vector3.forward * zStartPosition;
    18.     }
    My variable settings:
    speed = 60
    Smooth Speed = 8 (see code previous to this post)
     
    JoeStrout likes this.
  16. Stevepunk

    Stevepunk

    Joined:
    Oct 20, 2013
    Posts:
    205
    Don't use a pixel perfect camera. Instead write a shader that samples the texture for each pixel drawn on screen. If the sample is 100% one colour then draw that colour. If it is somewhere between 2+ colours then draw the average. So you will still get fairly crisp pixel art without blurring (unlike bilinear filtering which burs based on sampled image resolution, not screen resolution) but you will also get an antialiased effect that is only 1 screen pixel wide at most regardless of zoom level.

    example: https://www.shadertoy.com/view/ltBGWc
     
  17. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    @Cereal_Killa, that is the coolest thing I've heard all week. Including last week since this week's just begun.

    Unfortunately, I don't think my shader skills are up to converting that Shadertoy sample into a Unity shader. Any guidance on how to do that?
     
  18. RichardKain

    RichardKain

    Joined:
    Oct 1, 2012
    Posts:
    1,261
    Do what I do, and use Tilengine to render actual 2D sprites using a legacy pixel approach. That way you can get a texture object with your scene rendered to true pixel-perfect 2D, no matter what you are doing with camera movements or sprites. It's a bit extreme, but I've been getting some good results, and I don't have to worry about the camera in the slightest.

    Also, also, the movement of the camera isn't nearly as important as the elements within the camera, and their relation to the current pixel display. The size of the display likely doesn't change. The orthographic size of your camera probably doesn't change. If you store your game elements as a root and parented game object, you could perform your position and physics calculations on the root object, and have the child object contain your rendering elements. Then cook up a script that slightly adjusts the local position of the child elements to correct themselves for pixel-perfect rendering.

    Also, also, also, why would you ever render at anything other than your target resolution? If you need to scale for different displays, just render at your lower target resolution, point it at a renderTexture, slap that crap on a quad, and scale it up to the desired display resolution. That's the fastest way to keep things perfectly sized.
     
    Last edited: Apr 2, 2019
  19. Stevepunk

    Stevepunk

    Joined:
    Oct 20, 2013
    Posts:
    205
    People with ultrawide screens, ultra HD and other configs constantly run into problems with games so complaining is their default response to any game/app that wants to display with black borders in order to enforce a standard resolution.

    I can agree that black borders are annoying but I would personally take a different approach away from 2D pixel art and then not have to worry about viewport size.
     
  20. RichardKain

    RichardKain

    Joined:
    Oct 1, 2012
    Posts:
    1,261
    (Shrug) It's always an option. In fact, in an engine like Unity it is technically the default option. Unity's core rendering is built around a resolution-independent 3D pipeline. Even it's 2D tools are built around the same pipeline. If you just do things the standard "Unity Way", you'll get resolution-independent scaling graphics.

    But you also won't get low-resolution pixel art graphics. And while that style isn't for everybody, it is for some people. And for those people who do want to go down that road, you have discussions like this one where you try to figure out efficient ways of creating that look within an engine like Unity. It is a testament to Unity's flexibility that it can provide such a presentation, even when it isn't built around such an approach.

    Another possible option, and a potentially interesting stylistic experiment. Use relatively high-polycount models, cook up some relatively simple shaders, and then render a 3D scene specifically to look like some old-school low-resolution games. Unity's render-target tools make it very easy to play around with alternative forms of rendering. Adjusting some basic shaders to display basic ramps as blocks of colors instead of gradients of shading would allow you to create a old-school PC game look from 3D models. Switch off all anti-aliasing and mip-mapping to get those nice, hard pixel edges. And adjust your render camera to a low resolution. You could produce fairly vibrant and complex scenes this way, while still maintaining a retro aesthetic. And because this rendering approach wouldn't be very performance intensive, you could also afford to throw more polygons at the scene, allowing your models to have smoother edges and more complex shapes.
     
    Last edited: Apr 11, 2019
  21. Stevepunk

    Stevepunk

    Joined:
    Oct 20, 2013
    Posts:
    205
    There are many methods indeed.
    Another World (1991) actually used this method to create flat 2d pixel art from polygons.
    It was ahead of it's time. In the "sequel" Flashback they actually drew each frame by tracing live action video footage.
    Some more recent games have used polygon models to create accurate frames then hand drawn over them to create physically accurate hand drawn graphics.
     
  22. Juandapp

    Juandapp

    Joined:
    May 11, 2016
    Posts:
    53
    wow @Bredjo
    I was about to throw in the towel and go to Gamer Maker for 2d Pixel Art video games. And then I find this post of yours. I am trying to understand, but my low English and my low knowledge of C # complicates my life.

    I don't know if today you have found the total solution, and good practices for this problem. I find myself too lost. I've seen videos like this one
    but I still can't stop stuttering and other problems. I've tried to follow everything you've left on the road, but I'm still with the problem.

    You can give me a love, and help me understand a little more, how do you currently do for pixel art?

    You can see my problem here https://forum.unity.com/threads/cinemachine-with-pixel-art-2d-problem.777587/#post-5186057

    very very thank you any help for me :)
     
  23. BusyRoots

    BusyRoots

    Joined:
    Jul 25, 2017
    Posts:
    41
    @Juandapp sorry for the late reply.
    I saw that you got some advice from @ZuBsPaCe:
    https://forum.unity.com/threads/cinemachine-with-pixel-art-2d-problem.777587/#post-5206826

    I think he is right. For starters just keep it as simple as possible and maybe try out an already existing solution.

    Recently somebody showed me another Unity 2D Pixel Art project called "Sonic Realms" (older Unity Version required, I could run it using version 5.6.0f3):
    https://github.com/mdechatech/Sonic-Realms

    But you could just use the CameraController.cs logic for your project:
    https://github.com/mdechatech/Sonic-Realms/tree/master/Assets/Scripts/SonicRealms/Level

    Hope it helps.
     
    Last edited: Oct 10, 2021
  24. declanmills07

    declanmills07

    Joined:
    Jun 15, 2020
    Posts:
    2
  25. Dunkelheit

    Dunkelheit

    Joined:
    Sep 3, 2013
    Posts:
    81