Search Unity

2D pixel perfect with smooth camera

Discussion in '2D' started by meganinja, Jun 5, 2020.

  1. meganinja

    meganinja

    Joined:
    Apr 2, 2015
    Posts:
    35
    I am having a lot of problems with my game. My objective is to do a top-down shooter with free movement(all directions) using 2D physics. But my obstacle is that I want to use pixel art, looking like "Enter the gungeon"(because it's the only way I can draw).
    It means that my sprites are jittering, shaking and kind of blurry. I've made a lot of research, and I think I'm doing everything right. My PPUs are all correct and the same for each sprite. The pixel-perfect component doesn't work and I tried all resolutions and different values. Compression is "none" and the filter is "point".
    Anyway, I think I tried everything on the internet(camera size, vsync, AA, changing pixel-perfect camera script, etc...) and it looks like it's impossible to do this type of game.
    As I can see, I could round the camera and make it less smooth or just make the player movement based on tiles. But I don't know exactly how to "round" the camera and make the camera itself move based on pixels.
    Does anyone have a solution or a recommendation? I don't know too much about pixels so I hope anyone here can help me.

    (here is a GIF of a random sprite with the jittering)
    https://imgur.com/a/g57eTzF
     
  2. jeremyseay

    jeremyseay

    Joined:
    Dec 2, 2014
    Posts:
    30
    I had the exact same issue as you and here's how I solved it!

    First question, are you wanting to allow sub-pixel movement? (Think of something like Shovel Knight or Mario Maker where the assets don't snap perfectly.) If you are, here's how to get super smooth rendering without swimming/jittery pixels. If you DON'T want sub-pixel movement, attach the following script to all of your Game Objects in your scene. THIS INCLUDES THE CAMERA!

    If you don't want pixel snapping, don't use this script.

    Code (CSharp):
    1. using System.Collections;
    2. using UnityEngine;
    3.  
    4. /// <summary>
    5. /// Snaps the attached GameObject to a Pixel Perfect X and Y position.
    6. /// </summary>
    7. [ExecuteInEditMode]
    8. public class PixelSnapScript : MonoBehaviour
    9. {
    10.     public int PixelsPerUnit = 16;
    11.  
    12.     private float _lastX;
    13.     private float _lastY;
    14.  
    15.     // Snap the attached GameObject to it's Pixel Perfect position.
    16.     public void Update()
    17.     {
    18.         if (gameObject.transform.position.x == _lastX && gameObject.transform.position.y == _lastY) return;
    19.  
    20.         gameObject.transform.position = new Vector3(((float)((int)(gameObject.transform.position.x * PixelsPerUnit)) / PixelsPerUnit),
    21.                                                     ((float)((int)(gameObject.transform.position.y * PixelsPerUnit)) / PixelsPerUnit),
    22.                                                     gameObject.transform.position.z);
    23.         _lastX = gameObject.transform.position.x;
    24.         _lastY = gameObject.transform.position.y;
    25.     }
    26.  
    27.     // Move the attached GameObject by a certain number of Pixels.
    28.     public void Move(int pixelX, int pixelY)
    29.     {
    30.         gameObject.transform.position = new Vector3(((float)((int)(gameObject.transform.position.x * PixelsPerUnit) + pixelX) / PixelsPerUnit),
    31.                                                     ((float)((int)(gameObject.transform.position.y * PixelsPerUnit) + pixelY) / PixelsPerUnit),
    32.                                                     gameObject.transform.position.z);
    33.         _lastX = gameObject.transform.position.x;
    34.         _lastY = gameObject.transform.position.y;
    35.     }
    36. }
    Unfortunately, I can't remember where I got this script so I can't credit the author.

    You'll need to do a few things.
    1. Get rid of the pixel-perfect camera. You won't need it. The only reason you'd want it is if you wanted to enforce an integer-based scale on your camera. (Stretching forces bilinear filtering. You can edit the code to change it to point, but it will still look like the Pixel Perfect camera isn't even running anyway.)
    2. For your camera size, use the following formula. (Screen Height in Pixels / PPU / 2) for example, in my game, I want 240 pixels high and my PPU is 16. Therefore, 240 / 16 / 2 = 7.5
    3. Turn off Auto Simulation of Physics. (You're going to call this every update frame instead.) Go to Build Settings > Player Settings > Physics2D > Auto Simulation
    4. Anywhere you're doing Physics / Camera Following in FixedUpdate(), change it to Update() instead.
    5. Create a new Material, set the shader to Sprite/Default, then turn on Pixel Snap. Add this material to all of your sprite renderers. This also fixes any issues with breaks in your TileMaps!
    6. Create a new Empty Game Object in your scene. I named mine Physics. ONLY HAVE ONE OF THESE PER SCENE!!!
    7. Create a new C# script. I called mine UpdatePhysics. This is going to simulate the physics engine once per frame instead of during FixedUpdate. I think this might be ill-advised, but it worked for me.
    8. Code (CSharp):
      1. using UnityEngine;
      2.  
      3. public class UpdatePhysics : MonoBehaviour
      4. {
      5.     // Update is called once per frame
      6.     void Update()
      7.     {
      8.         Physics2D.Simulate(Time.deltaTime);
      9.     }
      10. }
    9. Attach this script to your Empty Game Object
    Go ahead and try it out. You should now have smooth movement with your camera follow script, and no jittering / swimming pixels!

    You can even use Anti Aliasing with this method and it still looks good. The results should be really close to the smoothness of Shovel Knight or Mario Maker!
     
    Last edited: Jun 5, 2020
    PepperPaige likes this.
  3. jeremyseay

    jeremyseay

    Joined:
    Dec 2, 2014
    Posts:
    30
    I hope this isn't against the rules / code of conduct, but my Laptop cannot record at 60 FPS even though this runs at 60 FPS. (If it is I will remove this)

    Here is a small demo I'm working on that shows what the above does using the changes I outlined. If this is what you're looking for, then do what I showed above.

    https://drive.google.com/file/d/1_Muhegqq4ArzQOVEgvp5ygxDwTS30mBd/view?usp=sharing

    It's a Windows Build though, sorry if you're a Mac user. (I can't share the Unity source because the assets are not mine to distribute)

    EDIT: Oh, controls are the following.

    Keyboard:
    WASD for movement
    Space to Jump
    J to attack
    K to Dash
    L to Block
    I to Backflip

    GamepPad:
    Left Stick / DPAD for movement
    Button South for Jump
    Button West for Attack
    Button East for Dash
    Button North for Backflip
    R Button for Block

    Down, Forward, Attack to cast a Spell
     
    Last edited: Jun 5, 2020
  4. meganinja

    meganinja

    Joined:
    Apr 2, 2015
    Posts:
    35
    Wow, thank you so much. It's actually a great improvement but there is still something to be fixed.
    After testing and researching a lot, I found that this "bleeding" effect only occurs when the sprite have a black pixel outline. I re-did some of my sprites without a black outline and the result is a lot better.
    Do you know anything about this? Actually any black pixel on the sprite is enough to do the same.
    Also, nice work on your project, the art is just amazing. I'm aiming to do something like yours. I hope I can get this problem fixed.
     
  5. jeremyseay

    jeremyseay

    Joined:
    Dec 2, 2014
    Posts:
    30
    Are you by chance using an older LCD monitor? If the Grey to Grey time is high then the monitor will ghost.

    I think you might be experiencing LCD ghosting. I don't see the bleeding on my Laptop's LCD in the gif you shared. However, if you can afford the sacrifice in aesthetic it will help others with similar monitors as well.

    https://levvvel.com/monitor-ghosting-fix/

    Sounds like it's monitor ghosting.

    The art actually isn't mine, it's by Luis Zuno. (Ansimuz) I just did the programming. =P

    https://assetstore.unity.com/packages/2d/characters/gothicvania-bridge-101400

    You can find these specific assets there. I actually obtained them from his Patreon.

    And truth be told I found like 3 game-breaking bugs in my player controller that I need to somehow fix in a little over 1,000 lines of code. >.< I just wanted to demonstrate how you can render pixel art much more smoothly with a few tweaks. The Pixel-Perfect Camera is great if you're going for accuracy with how retro games actually worked, but it's not so great for doing a "modern" retro game in my experience with it.
     
  6. meganinja

    meganinja

    Joined:
    Apr 2, 2015
    Posts:
    35
    Oh no... you are probably right...
    I made a few tests with this crazy theory of the black outline and it is really weird. Gonna try to "fix" my monitor and see.
    Thanks for the help, I'm really grateful.
    Here is another GIF and at least I can see some "bleeding" on the wall on the right, in the green sprite with black outline and in the AK sprite. If you cannot see, it means that I'm crazy or you are right about the ghosting.
    https://imgur.com/a/rNZ4szd
    I will try to fix it and I'll update you with news tomorrow.
    Thanks again for all the help.
     
  7. jeremyseay

    jeremyseay

    Joined:
    Dec 2, 2014
    Posts:
    30
    I can't see the ghosting on the black outline, so I think it's your monitor, unfortunately. =( The 2nd image does look cleaner to me, though.

    Unfortunately, there's no real way to get rid of swimming / jittering pixels without having an exact integer ratio of pixels.

    For instance, with my project being 240 pixels high, at a resolution of 1920x1080 1 asset pixel is equivalent to 4.5 screen pixels. I'm cover this up with the tricks I gave above, but it's still going to have a slight swimming pixel effect.

    At 1280x720 1 asset pixel is equivalent to 3 screen pixels for me so there will be no swimming pixels at all.

    240-pixel height gives me the following ratios at these resolutions.

    1280x720 : 1:3
    1280x1024 : 1:4.266
    1920x1080 : 1:4.5
    2560x1440 : 1:6

    Anything that isn't a perfect ratio will need to be cropped in order to not have swimming pixels with point filtering. AA helps, though.

    What the pixel-perfect camera will do is it will always ensure that you always have perfect integer ratios and set your camera size to match. What this meant for me is that I can't enforce a strict 240-pixel height for my camera unless I either stretch the image or use the crop feature. I didn't want to do either, so I chose to solve this problem with AA.
     
  8. meganinja

    meganinja

    Joined:
    Apr 2, 2015
    Posts:
    35
    Hello again, Jeremy. First of all, thanks for all the help.
    Today I borrowed my Dad's computer monitor to try it out and the difference was big! There was no ghosting at all. All that I need to do now is find a way to fix my monitor(It's a Samsung one and I can't find the "overdrive" option).
    Thanks for the advice and tips, I'm a noob in anything with graphics, resolutions and pixels so that definitely gave me a better understanding.
    Anyway, I think the "problem" is over. I owe u one, dm me if you need some help or when you finish your project. Now I want to try the final version :).
     
    jeremyseay likes this.