Search Unity

Pixel Perfect movement

Discussion in '2D' started by piggybank1974, Sep 24, 2017.

  1. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    As I've been coding up more of 1980's loveliness, I wanted to do a sort of platformer but NOT.

    I came across this little gem, I've got the basic building block "Map" working so to speak.



    I was looking at the movement and though, the animator is out for this as it sort of moves every split second a single pixel at a time left or right or up down if your on a ladder.

    Code (CSharp):
    1. public class PlayerComponent : MonoBehaviour
    2. {
    3. private SpriteRenderer mRenderer;
    4. private Transform mTransform;
    5.  
    6. public Sprite[] Up;
    7. public Sprite[] Right;
    8. public Sprite[] Down;
    9. public Sprite[] Left;
    10.    
    11. public Sprite[] Salter;
    12. public Sprite[] Hit;
    13. public Sprite Front;
    14.  
    15. private Boolean mUsingLadder;
    16. private Int32 mAnimationIndex = 0;
    17.  
    18. private Vector3 mVectorZero;
    19.  
    20. private float mSpeed;
    21.  
    22. private Byte OldDirectionIndex;
    23. private Byte NewDirectionIndex;
    24.  
    25. private float mTimer;
    26. Vector3 mDirection;
    27.  
    28.   void Awake()
    29.   {
    30.    mVectorZero = Vector3.zero;
    31.    mRenderer = this.GetComponent<SpriteRenderer>();
    32.    mTransform = this.transform;
    33.  
    34.    mSpeed = 0.01F / 10F;
    35.    mTimer = 0.05F;
    36.    mDirection = mVectorZero;
    37.   }
    38.  
    39.   void Start()
    40.   {
    41.    OldDirectionIndex = 10;
    42.    NewDirectionIndex = 10;
    43.      }
    44.    
    45.   void Update()
    46.   {
    47.     mTimer -= Time.deltaTime;
    48.  
    49.   if (Input.GetKey(KeyCode.W) == true) // Up
    50.   {
    51.    mDirection.y = -0.01F;
    52.    OldDirectionIndex = NewDirectionIndex;
    53.    NewDirectionIndex = 0;
    54.   }
    55.  
    56.   if (Input.GetKey(KeyCode.D) == true) // Right
    57.   {
    58.    mDirection.x = 0.01F;
    59.    OldDirectionIndex = NewDirectionIndex;
    60.    NewDirectionIndex = 1;
    61.   }
    62.  
    63.   if (Input.GetKey(KeyCode.S) == true) // Down
    64.   {
    65.    mDirection.y = 0.01F;
    66.    OldDirectionIndex = NewDirectionIndex;
    67.    NewDirectionIndex = 2;
    68.   }
    69.  
    70.   if (Input.GetKey(KeyCode.A) == true) // Left
    71.   {
    72.    mDirection.x = -0.01F;
    73.    OldDirectionIndex = NewDirectionIndex;
    74.    NewDirectionIndex = 3;
    75.   }
    76.  
    77.   if (mTimer <= 0 )
    78.       {
    79.        if (mDirection != mVectorZero)
    80.          {
    81.           Vector3 mPosition = mTransform.position + mDirection;// * Time.fixedDeltaTime;
    82.           UpdateSprite(mPreviousKey, mKey);
    83.        
    84.           mTransform.position = mPosition;
    85.          }
    86.  
    87.        //Debug.Log("Date: " + DateTime.Now.ToString());
    88.        mTimer = 1;
    89.        mDirection = mVectorZero;
    90.       }
    91.      }
    92.  
    93.   private void UpdateSprite(KeyCode previouskey, KeyCode key)
    94.   {
    95.    if (OldDirectionIndex != 10)
    96.      {
    97.      }
    98.  
    99.    if (OldDirectionIndex != NewDirectionIndex)
    100.      {
    101.        mAnimationIndex = 0;
    102.      }
    103.    else
    104.        {
    105.         Sprite mSprite = null;
    106.  
    107.         switch (NewDirectionIndex)
    108.         {
    109.          case 0: // Up
    110.           mAnimationIndex = (mAnimationIndex + 1) % Up.Length;
    111.           mSprite = Up[mAnimationIndex];
    112.          break;
    113.  
    114.          case 1: // Right
    115.           mAnimationIndex = (mAnimationIndex + 1) % Right.Length;
    116.           mSprite = Right[mAnimationIndex];
    117.          break;
    118.  
    119.          case 2: // Down
    120.           mAnimationIndex = (mAnimationIndex + 1) % Down.Length;
    121.           mSprite = Down[mAnimationIndex];
    122.          break;
    123.  
    124.          case 3: // Left
    125.           mAnimationIndex = (mAnimationIndex + 1) % Left.Length;
    126.           mSprite = Left[mAnimationIndex];
    127.          break;
    128.         }
    129.  
    130.         if (mSprite != null)
    131.           {
    132.            mRenderer.sprite = mSprite;
    133.           }
    134.        }
    135.   }
    136. }
    now it sort of works but I don't feel as if this is correct, and not a true pixel movement.

    0.01F = should be a single pixel?

    the camera will not get resized as the level is on a single screen as you can see.

    I have seen this example

    http://nielson.io/2015/08/the-pixel-grid-better-2d-in-unity-part-1/

    Where he changes the pixel per unit from 100 to 16 to represent the size of this sprites.

    is this the way to go?

    so what I'm looking for is a single pixel movement with a single animation movement to this.

    an idea's would be great.

    Cheers.
     
  2. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    You could check out the asset store, there is a free pixel perfect asset in there. I have not used it but the author seems to know what he is doing.

    I did my pixel perfect setup without any asset but there are a lot of possible pitfalls, depending on what exactly you want to achieve.

    Based on the video, your setup should not be too complicated though. I would possibly even use a 1:1 ppu since it does not seem to use physics or use a large map and you can simply round to int. if you use a center pivot, pay attention to the Sprite dimensions (even vs odd)

    Edit: looked at your code, but I am in mobile and don't really enjoy looking ;) - Looks a bit complicated and it seems not to be frame rate independent
     
    Last edited: Sep 24, 2017
  3. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    @sngdan

    Do you know what is called the pixel perfect?

    later last night I did change a few things and got it working?, the video shows a sort of how to put it none smooth movement, I made it frame independent last night with a little modification, I did originally have it this way, as you can see the commented out code, but thought it was not working correctly which I worked out it was, just I had the movement wrong 0.01 should of been 1.

    The original map, I've re-created this as the map I have is for the NES which is a cut down version of the original, it's only about 200pxx170px so converting the movment to int's would result in the play moving most of the map in that direction, if I remember the camera size is only set to 1(I'm at working replying)

    So what you are saying for this I need to blow the map up or something as lets say my characters is at 0.23 x 0.45 at the moment I can only move a percentage of this e.g 0.01 as this at the time was a single pixel.

    I'll post my new code later, the animation you see is quite smooth, what I didn't want was to put all images into a single sprite array, them I would need the indexes to pick out the individual start and end point.

    again anymore advice would be great.

    thanks pigs
     
  4. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    I was referring to Pixel Perfect Camera, but I just saw it is not free. I thought it was in the past. So I take back my recommendation as I have not used it and it is not free...it might still be worth it for you but you have to do your own assessment.

    Not covering edge cases, my start setup would likely be something like this:
    • NES resolution 256x240 pixel
    • Design resolution 288x270 pixel (same aspect as NES, but scaled up a little to scale better to target resolution)
    • Target resolution 1920x1080 (design x 4 = 1152x1080, you will have lots of empty space on the side, covered with UI or background image)
    • Sprite design size 16x16 (unchanged)
    • Other sprite sizes (even x/y dimensions, good if pot but not required)
    Other settings:
    • Quality settings: Texture Quality = Full Res, Anisotropic Textures = Disabled, Anti Aliasing = Disabled (I would likely create a new quality setting based on the "Low" pre-defined setting, call it Retro and turn on V-Sync - every Vblank)
    • Sprite import: PPU = 1, Pivot = Center (0.5,0.5), MipMap = Off, Filter Mode = Point, Compression = none
    • Sprite transform: position = only integer positions, scale = 1,1,1
    • Camera: Projection = Orthographic, Size = 135 (270 / 2)
    • Game Window: Create new fixed resolution = 1920x1080
    That should be roughly it.

    I am not good at looking at other peoples code, with regards to frame rate independent, use Time.deltaTime. You should not only make the position but also the animation frame rate independent.
     
  5. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    @sngdan

    Well that's some really nice suggestion's, normally what I do, is a load of tiles, use my own tile editor to place them and hay presto, you have a map, but in this instance I changed my mind as they don't really form to a grid pattern, the original Arcade version you see "I've got the NES Maps" is a different size(ladders are not as large) to the NES version I have, so I redesigned the map using photoshop as a single png from looking at the youtube video, to match in with the original, although its tiny 200x170 I believe or not much bigger.

    so by changing the PPU (pixels per unit), this effectively should allow me to move 1,2,3,4.. and not 0.23.. I guess, I should of thought of that, mind you my other games and my released title( zoom's based on a 64px tile).

    I'll create a little test program for this tonight, and post back here if I need advice sngdan, as you have done a little more on pixel perfect(for this type of game) than I have so far.

    if this works I'll have to redesign my scene based on your idea and my findings, but this should help for future development(s) for pixel based games.

    Cheers
     
  6. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    Sounds good.
    - what I wrote above also applies to tilemaps
    - ppu can be se to anything but setting it to 1 is most intuitive for pixel games (if you for other reasons, I.e. Large worlds with physics want to keep it at another ratio, you just need a function that rounds the positions to said ratio)
    - as for the map size that should be an integer multiple of the target resolution (so for 1920x1080 you would be better off with say 200x180, I.e. Scales with factor 6 to 1200x1080)
     
  7. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Burger Time! That was a fun game.

    Yes, I would probably not use Animator for this, and certainly wouldn't use it to actually move sprites around. Check out my SimpleAnimator class here, which lets you define animations as just a frame rate and a series of frames; and there's also code there for moving around (though you'd use quite different code for Burger Time, which doesn't involve any jumping or sliding — you are always locked onto the level platforms & ladders).
     
  8. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    This is how I implemented a simple animation in a previous project. You can simplify further if you remove the duration (which allows to play for a certain time) and you could just remove the else (which plays all sprite frames in order, in case no sequence is passed on).

    This function would need to be called each frame, something like
    Code (CSharp):
    1.  
    2. void Update ()
    3. {
    4. timer += Time.deltaTime;
    5. PlayAnimation(myConfig.carSprites.drivingAnim, myConfig.carSprites.drivingSeq, speed * 0.05f, timer); // speed * 0.05f is frames per second
    6. }
    7.  


    Code (CSharp):
    1.         private void PlayAnimation (Sprite[] frames, int[] sequence, float framesPerSecond, float timer, float duration = 0f)
    2.         {
    3.             if (framesPerSecond < 0) return; // not implemented
    4.             if (duration <= 0 || timer < duration)
    5.             {
    6.                 if (sequence != null)
    7.                 {
    8.                     myRenderer.sprite = frames[sequence[Mathf.FloorToInt(timer * framesPerSecond) % sequence.Length]];
    9.                 } else
    10.                 {
    11.                     myRenderer.sprite = frames[(Mathf.FloorToInt(timer * framesPerSecond)) % frames.Length];
    12.                 }
    13.             }
    14.         }
    edit: it is not checking if the sprite referenced in the sequence exists (can easily be implemented, or you pay attention not to put something wrong :) )
     
  9. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    @sngdan

    @JoeStrout

    Joe I did see your article a while back, so I'll have a look again.

    Sng I like your idea for the frame/sequence I think I'll try this out. feel really clean to me.

    Now I have a little question, this has always stumped me out,. The map(208px X 148px) is now PPU 1, max size 2048, RGB 16 bit and the camera is size 100.

    For the player to appear correct for the scale (originally 16x16 pixels) I had to set the PPU to 1 as well not 100 or 16 does this sound correct to you guys?
     
  10. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    Yes, all sprites should be the same PPU.

    Use RGB32 bit (though 16 should be ok, too)

    Why did you choose a camera size of 100?
     
    JoeStrout likes this.
  11. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    @sngdan

    not quite sure on this, but I needed padding on the top for the panel ui score extra and the bottom for the burger falling down, any suggestions?

    Cheers
     
  12. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    It depends on your target display resolution. Check my example above, those numbers were not randomly picked.

    You want your design resolution to be an integer multiple of it, since unity will scale up to fill the whole screen and you want this to be in integers for the pixel art to scale properly.

    So if your target resolution is indeed 1980x1080, then a design resolution of width x 180 (camera size of 90) or width x 216 (camera size 108) would be better than width x 200 (camera size 100).

    1x1design pixel scales up to 6x6 screen pixels or 5x5 respectively
     
  13. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    @sngdan

    The game will on be for mobile only, so all sort of resolutions could happen we know how fragmented android has become.

    I've been trying to get the movement with your animation idea worked out put I could figure out how you got that to work with movement+animation would you be so kind in posting that so I could get a better look at it, I'm still

    I've recoded some parts

    Code (CSharp):
    1.  
    2. public class PlayerComponent : MonoBehaviour
    3. {
    4. private SpriteRenderer mRenderer;
    5. private Transform mTransform;
    6.  
    7. public Sprite[] Frames;
    8.  
    9. public Int32[] Up;
    10. public Int32[] Right;
    11. public Int32[] Down;
    12. public Int32[] Left;
    13.  
    14. public Int32[] SalterLeft;
    15. public Int32[] SalterRight;
    16. public Int32[] Hit;
    17. public Int32[] Front;
    18.  
    19. private Boolean mUsingLadder;
    20. private Int32 mAnimationIndex = 0;
    21.  
    22. private Vector3 mVectorZero;
    23.  
    24. private float mSpeed;
    25.  
    26. private float mTimer;
    27. private float mRestTimer;
    28. private Vector3 mDirection;
    29. private Int32[] mSequence;
    30.  
    31. private float mtim;
    32.  
    33. private Vector3[] mDirections;
    34. private Int32 mDirectionIndex;
    35.  
    36.   void Awake()
    37.   {
    38.    mDirections = new Vector3[] {Vector3.up, Vector3.right, Vector3.down, Vector3.left};
    39.    mVectorZero = Vector3.zero;
    40.    mRenderer = this.GetComponent<SpriteRenderer>();
    41.    mTransform = this.transform;
    42.    mSpeed = 60F;
    43.    mRestTimer = 0.1F;
    44.    mTimer = mRestTimer;
    45.    mDirection = mVectorZero;
    46.    mDirectionIndex = -1;
    47.   }
    48.  
    49.      void Update()
    50.   {
    51.     mtim += Time.deltaTime;
    52.     mTimer -= Time.deltaTime;
    53.  
    54.     if (Input.GetKeyDown(KeyCode.W) == true) // Up
    55.       {
    56.        mDirectionIndex = 0;
    57.        mSequence = Up;
    58.       }
    59.  
    60.     if (Input.GetKeyDown(KeyCode.D) == true) // Right
    61.       {
    62.        mDirectionIndex = 1;
    63.        mSequence = Right;
    64.       }
    65.  
    66.     if (Input.GetKeyDown(KeyCode.S) == true) // Down
    67.       {
    68.        mDirectionIndex = 2;
    69.        mSequence = Down;
    70.       }
    71.  
    72.     if (Input.GetKeyDown(KeyCode.A) == true) // Left
    73.       {
    74.        mDirectionIndex = 3;
    75.        mSequence = Left;
    76.       }
    77.  
    78.     if (mTimer <= 0)
    79.       {
    80.        if (mDirectionIndex != -1)
    81.          {
    82.           Vector3 mPosition = mTransform.position + mDirections[mDirectionIndex];//* Time.deltaTime;
    83.           UpdateSprite(mSequence, mSpeed * 0.05f, mtim);
    84.           mTransform.position = new Vector3((int)mPosition.x, (int)mPosition.y, 0);  
    85.          }
    86.  
    87.        //Debug.Log("Date: " + DateTime.Now.ToString());
    88.        mTimer = mRestTimer;
    89.        mDirectionIndex = -1;
    90.      }
    91.      }
    92.  
    93.   private void UpdateSprite(Int32[] sequence, float framesPerSecond, float timer, float duration = 0f)
    94.   {
    95.    mRenderer.sprite = Frames[sequence[Mathf.FloorToInt(timer * framesPerSecond) % sequence.Length]];
    96.   }
    97. }
    98.  
    The movement works fine, but the animation is off, as you work in a direction the animation should increase by a single frame, I'm not sure about the speed either here.

    I still don't think the mTimer is used correctly here all though it works but there must be a better way?
     
  14. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    Pixel perfect
    - Pixel perfect + many resolutions = difficult
    - there will only be so many combinations where you are able to scale your design resolution in full integers to a target resolution, unless you accept pillar/letterbox --- best is to consider what are the "dominant" resolutions you want to support
    - another approach is to give up on pixel perfect and try to make it look good, something like the Retro AA shader on the asset store


    Animation
    - the problem with my example code is that it is for a specific situation that might not work 100% for you. The one thing that comes to mind is that if you do not start the timer at 0 each time you switch direction, it might start on a random frame in the sequence (so the easy fix is to reset the mtim to 0 each time you change direction)
    - Call the function as follows UpdateSprite(mSequence, mFPS, mtim); // mFPS = frames per second, you can get rid of the mSpeed * 0.05f, i had a variable frame rate in my example, i.e. the faster you drove the faster the tire animation, you don't need that
    - What do you use the mTimer for? Is this meant to prevent you from changing direction immediately?
    - You want mPosition to use Time.deltaTime
    - you are not easing in / out on direction change on purpose, correct?
     
  15. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    @sngdan

    As far as I can see from the game on youtube, it works very basic, if I move left a single pixel the animation will progress a single frame to the left, as it's not really smooth, I did have this before I changed, I might after change your stuff to fit my needs but I did like it. the idea of the mTimer is to slow down the movement, so you cannot repeat it for a split second 0.1 in this case allowing for again a more realistic feel, I thought, no ease in or out of direction change as it does not this make it easier as no need to rotation lerping etc. In all my titles so far I'm going for as realistic as possible if I can work it out and it's possible to do.
     
  16. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    Sure :)

    Getting the controls right is quite important. I looked at the video and well the animations are so basic that it almost does not matter if they are done perfect or not, don't even see why my code would not work (even without resetting the timer on direction change)

    I assume you are using direct touch on the mobile (and not a simulated joystick) and hence want to delay the reaction time a bit.

    Good luck and it would be interesting to see what you finally settle for with regards to the controls (once it's adjusted for touchscreen) and of course play the game once it's done...
     
  17. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    @sngdan,

    Yeah I'm not using my own joystick ui/code on this but the original arcade machine does use a joystick, for some reason, it has two forms of pepper buttons maybe this is left and right when he throws the pepper, I'm not sure yet

    I've done the following Games for the retro games pack so far

    1) - Space Invaders
    2) - Missile Command
    3) - Asteroids
    4) - Nibbler (still doing maps for this as I ave 32 and I've done 11 so far).
    5) - BurgerTime

    These are as close the the originals as I can do them so far, so little updates will happen, my original title gauntlet conversion is doing ok, this is going to be on a single app so I've coded these with namespaces etc in mind.

    sure I'll let you know when its finished :).
     
  18. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    Cool, so how are you planning to control this burger game?

    - Is the character following the finger
    - Do you tap on a side of the character and he keeps walking in this direction until your next tap
    - you use an on screen controller?
     
  19. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    @sngdan,

    Sorry I didn't see your message I've been busy at work all day so didn't get chance to reply, ASP.NET is the devil's child, and javascript is it's cousin anyways.

    This time around I'm going to use buttons, originally joystick, but I find that it's over kill as only four directions, so I'm playing save this time around, I'm not a fan of tapping as on larger devices you tend to have to release a and to tap if the device is large and your hands are small, and as the game will have room on each side it should not obscure the game area :) why any thoughts?
     
  20. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    Not really, but I would test it out well, from the look of the video, getting this right is a key game element.

    Possibly would start trying with an on screen controller (mimic analog stick) and tap for fire (2nd finger)

    Edit: unless it's payed in landscape orientation, then maybe buttons on each side
     
  21. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621