Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.

Discussion Free Script - Chronos - For Emulating the cycles of an old console!

Discussion in 'General Discussion' started by KevinConner, Aug 3, 2022.

  1. KevinConner

    KevinConner

    Joined:
    Jan 8, 2022
    Posts:
    7
    Hi Guys!

    I can't figure out how or where to upload this to the unity asset store, and not sure where to post this, but I wanted to give something back to the Unity Community.

    It's a script, a very simple script, which does a surface (non intensive) timing emulation of classic consoles of the late 70s, and early 90s.

    I call it Chronos but well it can be called anything you want.

    I'll just post my script in full here, and if anyone has any questions feel free to ask. Please remember this is a free script, and it is intended to be simple for a few reasons. I did try a few more complex variations that tended to present "issues" with Unity. As a result this is the least interfering version I could come up with for everyone to enjoy!

    How to use this code: Implement a condition statement that suspends the functions you want to suspend during the horizontal blank cycle ( don't be doing things when hertzCounter == 0, or maybe, do only certain things during that .001 second period ). Easiest place to suspend or enable functions is within FixedUpdate(). Keep that in mind - this code only specifically works with FixedUpdate() cycles. You can change the values of the hertzLimit to properly reflect the hertz cycle you're trying to emulate.

    hertzLimit = 30; for example, is for use when trying to emulate 90% of all the 8bit games, and a large number of 16b games out there running on a NTSC system. If you're doing pal, then you'd want 25 and 50. Don't forget to adjust the Time.fixedDeltaTime values as well. Have fun with this site to figure out what the Pal Video Timing really is: http://martin.hinner.info/vga/pal.html ;)


    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. // This code is emulating the hertz cycle frequencies of the retro games :D
    4.  
    5. // Description of the Emulation
    6.  
    7. // CRTs never operated at 60 frames per second.  They operated at 60 scan sweeps/cycles
    8. // plus 2 horizontal blank scan sweeps/cycles per second.
    9. // In the early age of Videogames, there were very few videogames which bothered to
    10. // prevent people from inputting commands during those two blank horizontal cycles.
    11. // See, what would happen is the actual graphics would stop updating, movement wouldn't
    12. // take place, but people could still input commands which could affect non-graphical gameplay,
    13. // as a result, some games glitched out, and people could - say - shift a car from 1st to 2nd.
    14. // Basically it's cheating to abuse this function.  I am emulating that experience here.
    15. // Most 8 bit systems operated on a 30 frame per second cycle +2 blank cycles (59.94 / 2),
    16. // that's because they had to process 2 scan sweeps per singular frame of animation.
    17. // The T.V. would shoot one sweep/cycle of even numbered lines, followed by another sweep/cycle
    18. // of odd numbered lines, plus 2 horizontal blank sweeps/cycles just to get 1 frame of animation.
    19. // This had to be done in order to make the image appear taller than it actually was, by
    20. // shooting the same line twice.  If they didn't, the image would look like a Venetian Blind.
    21. // So I have a 29.97 hertz cycle as an option, alongside a 59.94 hertz cycle.
    22. // Now, during the horizontal blanks, there was NO image information was transmitted to the CRTs.
    23. // they were called horizontal blanks specifically because the CRTs needed to re adjust and clear
    24. // whatever information they had in order to change the image they were shooting at the screen.
    25. // Well, I tried a few methods to give this feel and unique experience to those who use Unity,
    26. // but I ran into three problems.
    27. // First of all, Unity doesn't operate on a Rainbow Quartz Crystal.
    28. // Secondly, the main camera is the only thing that shuts visuals off.
    29. // So because Unity is a real bitch with timing, it's almost impossible to make it 100% accurate
    30. // for 2 blank cycles.  In addition, Unity doesn't handle processes within .0005 very well.  Why?
    31. // Unity is a big program, it runs a lot of things.  So unlike a Rainbow Quartz Crystal, it
    32. // just won't process things reliably during that time frame.  So I combined the two blank cycles
    33. // into a singular "faux" blank frame/cycle, which is the closest reliable emulation I can get.
    34. // Finally, the third problem - when a CRT shuts its lasers off, your eyes still have that image
    35. // burned into your retina for a little bit longer.  The effect on LCD screens, however.. just
    36. // isn't right in appearance.  So I just killed the idea of shutting off all visuals for 0.001s.
    37. // It feels clunky.  I mean you can enable main camera controls, but it just doesn't feel right.
    38. // The flicker is all off.  It's either too long or it's too short to be visibly present
    39. // (and if it's not visible, why bother?). (Camera.main.enable = true/false;)
    40. // The important aspect of this code, however, is the emulation of .001 seconds where game
    41. // logic functions require a real fast hand to implement, while visuals are suspended.
    42. // This code is as close as you can get to solid horizontal blank input in this particular engine.
    43.  
    44. // Much help and thanks to GameDev.TV's discord, where I found a ton of help from several
    45. // individuals in understanding how fixedDeltaTime and TimeSteps worked with FixedUpdate()
    46. // The most helpful individual goes by the handle Raistael.
    47. // Incidentally, they all called me crazy and insane for even wanting to do this.  WUAHAHHAHA
    48.  
    49. //*********************************************************
    50. // Table of Contents
    51. //
    52. // I.a. Variables for the Cycles
    53. //      contains:
    54. //      public static int hertzLimit, where you set the hertz emulation
    55. //      public static int hertzCounter, how you determine which cycle you're on
    56. //
    57. // I.a. Variables for the Game
    58. //
    59. // II.  Awake
    60. //
    61. // III. FixedUpdate
    62. //
    63. // IV.  HValuesForfixedDeltaTime()
    64. //      this is where the absolute values for hertz emulation
    65. //      are directly set to Time.fixedDeltaTime
    66. //*********************************************************
    67.  
    68.  
    69. public class Chronos : MonoBehaviour
    70. {
    71.  
    72. //*********************************************************
    73. // I.a. Variables for the Cycles
    74. //*********************************************************
    75.  
    76.     [Header("Emulating the Time Cycles")]
    77.     public static int hertzLimit = 30;
    78.     // if the option 30 herts is selected, then hertzLimit = 30.  This is a temporary value
    79.     // to be changed for the appropriate game.  As of now, almost all the games using this
    80.     // code are 8 bit games, which means 30 is a good default value.
    81.    
    82.     public static int hertzCounter; // if over 0 then it's ok to run processes.
    83.  
    84. //*********************************************************
    85. // I.a. Variables for the Game
    86. //*********************************************************
    87.  
    88.     public static int gameLives = 0;
    89.  
    90.     public static int gameContinues = 0;
    91.  
    92.     public static int gameHealth = 0;
    93.  
    94.     public static float speed = 3.0f;
    95.  
    96. //*********************************************************
    97. // II.  Awake
    98. //*********************************************************
    99.  
    100.     void Awake()
    101.     {
    102.        
    103.         int numGameSessions = FindObjectsOfType<Chronos>().Length;
    104.         // the above code counts the number of Chronos which exist
    105.  
    106.         if (numGameSessions > 1)
    107.             {
    108.                 Destroy(gameObject);
    109.             }
    110.             // the above code orders Zeus to kill the duplicate Chronos.
    111.         else
    112.             {
    113.                 DontDestroyOnLoad(gameObject);
    114.             }
    115.         // the above code makes sure the original Chronos survives between scenes.
    116.  
    117.         HValuesForfixedDeltaTime(); // section IV.
    118.     }
    119.  
    120.  
    121. //*********************************************************
    122. // III. FixedUpdate
    123. //*********************************************************
    124.  
    125.     void FixedUpdate()
    126.     {
    127.        
    128.         if (hertzCounter == 0)
    129.             {
    130.                 hertzCounter += 1;
    131.  
    132.                 HValuesForfixedDeltaTime(); // section IV. Resetting the proper emulated timing.
    133.             }
    134.         else if (hertzCounter != hertzLimit)
    135.             {
    136.                 hertzCounter +=1;
    137.             }
    138.         else if (hertzCounter == hertzLimit)
    139.             {
    140.                 Time.fixedDeltaTime = 0.001f; // emulating the 2 horizontal blank cycles.
    141.                 hertzCounter = 0;
    142.             }
    143.        
    144.  
    145.     }
    146.  
    147.  
    148. //*********************************************************
    149. // IV.  HValuesForfixedDeltaTime()
    150. //
    151. //      This is where the fixedDeltaTime values are set for
    152. //      the hertz cycles chosen to be emulated.
    153. //      This is how the FixedUpdate cycles are limited.
    154. //      Numerous tests have shown that any value below
    155. //      0.001 is unreliable.  That includes infinite values
    156. //      such as those generated by fractions.
    157. //*********************************************************
    158.  
    159.     public void HValuesForfixedDeltaTime()
    160.         {
    161.         if (hertzLimit == 30)
    162.             {
    163.             Time.fixedDeltaTime = 0.0333f;
    164.             }
    165.         else if (hertzLimit == 60)
    166.             {
    167.             Time.fixedDeltaTime = 0.01665f;
    168.             }
    169.         }
    170. }
    171.  
    172.  
    173.  
    174.  
    175.  
     
    CodeSmile and OCASM like this.
  2. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    2,065
    Errm, since when do CRTs have lasers? :D
    They're cathode rays (electron beams), hence the name cathode ray tube = CRT.

    I hope not! :p

    It's in fact a brain thing. Between the eyes and the brain there's a lot of filtering going on which means the brain processes just a couple bits of data and interpolates the rest. The eyes themselves process visuals at a higher frequency than the brain. We are, so to speak, CPU limited. :D

    My thoughts exactly. :p
    But I admire the dedication.

    Lastly: I suppose all of this breaks when a user tweaks the FixedUpdateTime in Project Settings, right?

    PS: would be nice to see a video with the effect (with and without for comparison). Perhaps using one of the free platformer templates on the store.
     
  3. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,196
    LOL, these comments made my day:D
     
  4. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    12,555
    FixedUpdate does not run at regular intervals and is not a precise timer. It plays catchup with real time, and can fire in bursts.

    TV refresh rates were 60 Hz for NTSC and 50 Hz for PAL. However, they were rendering half-frames.
     
    CodeSmile and Ryiah like this.