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.
  2. Dismiss Notice

How to sort Day from Night in a Lerp?

Discussion in 'Scripting' started by BronsonBrad, Apr 11, 2021.

  1. BronsonBrad

    BronsonBrad

    Joined:
    Jan 6, 2013
    Posts:
    8
    Heya! I am a newbie to scripting. Have been an artist for years without managing to write a line of code, but now I'm daring to! I am currently creating a Day/Night cycle using a MathF.Lerp to change all sorts of things (ambient light, colors for the sky etc all using gradients .evaluate) over a set duration in order to imitate a 24 hour period. This is working exactly as I intended. However, I am needing to somehow define what part of the Mathf.Lerp is Day and what is Night.

    I have created a slider/range so that I can manually change the time of day, and this goes from 0-24 (24 hour clock) with some InverseLerp black magic. I am essentially wanting to define 20 all the way through to 6 as Night, and the rest as Day.

    I am wanting to do this so that I can have certain things happen at night, such as enemies spawn and scary music play etc. And so I figured this might be a way to do it.

    Here is my script so far:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class VertexColorChange : MonoBehaviour
    6. {
    7.     // Scriptable Object reference for color palettes
    8.     public ColorLibrary colorPalette;
    9.  
    10.     // Gradients
    11.     public Gradient SkyTop;
    12.     public Gradient SkyMid;
    13.     public Gradient SkyBottom;
    14.     public Gradient AmbientColor;
    15.  
    16.     // Sky mesh
    17.     Color colorR;
    18.     Color colorG;
    19.     Color colorB;
    20.     GameObject sky;
    21.     Renderer skyRenderer;
    22.  
    23.     // Day Night cycle time
    24.     public float DayLength;
    25.     public bool UseSlider;
    26.     [Range (0.0f, 24.0f)]
    27.     public float TimeOfDay;
    28.     float t = 0f;
    29.  
    30.     // Start is called before the first frame update
    31.     void Start()
    32.     {
    33.         // Find Sky mesh and its renderer
    34.         sky = GameObject.Find("Sky");
    35.         if (sky) {
    36.             skyRenderer = sky.GetComponent<Renderer>();
    37.         }
    38.  
    39.         // Get colors from Color Palette object
    40.         SkyTop = colorPalette.SkyTop;
    41.         SkyMid = colorPalette.SkyMid;
    42.         SkyBottom = colorPalette.SkyLow;
    43.         AmbientColor = colorPalette.AmbientColor;
    44.  
    45.     }
    46.  
    47.     // Update is called once per frame
    48.     void Update()
    49.     {
    50.         // Day length lerp
    51.         float value = Mathf.Lerp(0f, 1f, t);
    52.         t += Time.deltaTime / DayLength;
    53.        
    54.         // Update colors in Sky material based on gradients  
    55.         colorR = SkyTop.Evaluate(value);
    56.         colorG = SkyMid.Evaluate(value);
    57.         colorB = SkyBottom.Evaluate(value);
    58.         skyRenderer.material.SetColor("_colorR", colorR);
    59.         skyRenderer.material.SetColor("_colorG", colorG);
    60.         skyRenderer.material.SetColor("_colorB", colorB);
    61.  
    62.         // Update ambient color
    63.         RenderSettings.ambientLight = AmbientColor.Evaluate(value);
    64.  
    65.         // Slider to change time of day
    66.         if (UseSlider) {
    67.             UseSlider = true;
    68.             t = Mathf.InverseLerp(0f, 24f, TimeOfDay);
    69.         }
    70.        
    71.         else {
    72.             UseSlider = false;
    73.             TimeOfDay = Mathf.Lerp(0f, 24f, t);
    74.         }
    75.        
    76.            
    77.     }
    78. }
    79.  
    An image of the sucker at work:
    upload_2021-4-11_21-58-53.png
     
  2. Stevens-R-Miller

    Stevens-R-Miller

    Joined:
    Oct 20, 2017
    Posts:
    664
    For someone doing their first program, I think you are doing pretty well!

    First off, kudos to you for using code tags. It is kind of a hallmark of the new programmer that they don't, so well done on that. Second, your comments show attention to professionalism, as do your choices for names of variables, methods, and classes. It shows that you've done some studying before running to the keyboard.

    Your indentation appears to be a mix of the two most popular styles. "K&R" style puts the opening curly bracket at the end of the line that controls the block, like you have done at Line 66:

    if (UseSlider) {


    Elsewhere, you have used "Allman" style, which puts the opening curly bracket even with the line that controls a block, but on the next line down, like you have at Lines 31 and 32:

    void Start()
    {


    As you have probably gleaned from your contacts with other programmers, there is some disagreement about which is better. K&R ruled for a long time. Allman has gained popularity in recent years. Both are common. I would suggest you pick one and use it consistently. Your code will be easier to read and understand if you do. (I use Allman, btw.)

    Now, for your question: You are pretty close to an answer already. By the time you get to Line 75, you have the time in TimeOfDay. If what you want is to have a way to define where day starts and where it ends, you could just add two more sliders to your class, maybe one for "sunrise" and one for "sunset," like this:
    Code (CSharp):
    1.     [Range (0.0f, 24.0f)]
    2.     public float Sunrise;
    3.  
    4.     [Range (0.0f, 24.0f)]
    5.     public float Sunset;
    Then all you need is a test to determine which phase of the day you are in:
    Code (CSharp):
    1. if (TimeOfDay > Sunrise && TimeOfDay < Sunset)
    2. {
    3.     // It is day.
    4. }
    5. else
    6. {
    7.     // It is night.
    8. }
    If you only want to do your day/night activities when the phase changes from day to night or from night to day, just add a second test:
    Code (CSharp):
    1. bool itIsDayNow;
    2.  
    3. if (TimeOfDay > Sunrise && TimeOfDay < Sunset)
    4. {
    5.     if(itIsDayNow == false)
    6.     {
    7.         itIsDayNow = true;
    8.         // It is day.
    9.     }
    10. }
    11. else
    12. {
    13.     if(itIsDayNow == true)
    14.     {
    15.         itIsDayNow = false;
    16.         // It is night.
    17.     }
    18. }
    There are more compact ways to do this, but what I've written here is self-explanatory and will run as well as more cryptic options.

    Note that you do not need Lines 67 or 72. Look at Line 66 to see why: you cannot reach Line 67 unless UseSlider is already true, and you cannot reach Line 72 unless UseSlider is already false. So, both of those lines just set UseSlider to the value it already has.

    Now, using sliders to set the times for Sunrise and Sunset will allow you to set a Sunset time that comes before Sunrise. That would make your code behave as though it were always night. Nothing wrong with that, but if you want to use a slightly more complicated slider that handles two values, and guarantees that one is greater than the other all the time, have a look at this video:


    Does that help?
     
    Kurt-Dekker and Vryken like this.
  3. Stevens-R-Miller

    Stevens-R-Miller

    Joined:
    Oct 20, 2017
    Posts:
    664
    By the way, the Unity documentation does not say so, but Mathf.InverseLerp "clamps" the value it returns to the range [0, 1]. This means that if, for some reason, TimeOfDay were above the range [0, 24] (say, 36), it would not return 1.5; it would return 1.0. Likewise, if TimeOfDay were below the range [0, 24] (say, -12), it would not return -0.5; it would return 0. This may or may not be what you want, but I thought I'd pass it along as the Unity documentation explicitly does say the interpolation parameter is clamped to [0,1] for Mathf.Lerp, but it is silent about clamping for Mathf.InverseLerp.

    If you want an unclamped version, use this line:

    Func<float, float, float, float> inverseLerp = (a, b, v) => a == b ? 0f : (v - a) / (b - a);


    For these calls:

        Console.WriteLine(inverseLerp(0, 24, -6));
    Console.WriteLine(inverseLerp(0, 24, 0));
    Console.WriteLine(inverseLerp(0, 24, 12));
    Console.WriteLine(inverseLerp(0, 24, 24));
    Console.WriteLine(inverseLerp(0, 24, 36));


    the output is:

    -0.25
    0
    0.5
    1
    1.5
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,760
    WELCOME! Jump right in, the water's fine. I see that @Stevens-R-Miller has already gotten you situated with a nice cold beverage, and let me point out that the snacks are right over there on that long table.

    For your actual problem, let me also add huge props for using
    Gradient
    objects, because you're making it so that tweaking and twiddling in the future is going to be soooo easy for you.

    MinMaxRange sliders are awesome, but let me add another handy-dandy tool: just like Gradient, the
    AnimationCurve
    object is very powerful for lookups.

    Make one of those babies and put it in alongside your colors above, and then you can just agree with yourself, "below 0.5f is night, above 0.6f is day, else twilight," and create your curve to match the colors. It would look like this:

    Code (csharp):
    1. public AnimationCurve DayOrNight;
    And in the inspector:

    Screen Shot 2021-04-11 at 10.52.37 AM.png
     
  5. Stevens-R-Miller

    Stevens-R-Miller

    Joined:
    Oct 20, 2017
    Posts:
    664
    Why does no one ever tell me where the snacks are?
    Hey, I like that approach! Using the actual light in the scene to decide day v. night makes great sense. In addition to maintaining the parallel curve, I'd also consider reading the current ambient color value and converting to the a gray shade:

    float brightness = RenderSettings.ambientLight.grayscale;


    Then base your decision about whether it is day or night on the value in brightness.

    Now, Unity uses a standard conversion from RGB color space to a single number. Here's the math:

    0.299 * red + 0.587 * green + 0.114 * blue


    You could use your own choice and you might have to if, for some reason, the ambient gradient you use has more than one local minimum or maximum (that is, as the green fades away but the red rises at the "twilight" part of the day, it's possible the brightness will actually fade, then briefly rise up again, before fading all the way to zero. There are, oh, about a billion ways to handle that. One would be to start evaluating the gradient at zero and then keep evaluating in small steps. 0.001 would be way small enough. You could evaluate at 0, 0.001, 0.002, 0.003, etc. Keep going until the gray value exceeds the value you define for "day." Whatever the evaluation parameter is at that point, that's the time of sunrise. Do the same thing from the other end, starting at 1, and working down (0.999, 0.998, 0.997, etc) and again stop when the gray value exceeds the day brightness you've picked. That's your time for sunset.

    With an approach like that, you never have to set any particular time for day or night. You base it on the light in the sky, which might even be different for a given time of day if you are simulating seasonal changes.

    Note: I'm not 100% sure, but I think Line 51 sets value to always be equal to t. You could probably drop value and just use t. Also, at some point you are going to have to reset t, when it exceeds one. When it does, just subtract one from it. Don't set it back to zero. that creates stutters in time. Just back it off by exactly one from wherever it is whenever it exceeds 1.

    This would do it:

     t = t > 1 ? t - 1 : t;


    Or, in case the compiler might miss t = t is a no-op:

    if (t > 1)
    {
    t = t - 1;
    }
     
    Last edited: Apr 11, 2021
  6. BronsonBrad

    BronsonBrad

    Joined:
    Jan 6, 2013
    Posts:
    8
    @Stevens-R-Miller thanks so much for that feedback and the kind words. I had wondered if that was a thing, and so I think I am slightly inclined towards "Allman" style in future.

    I will read and re-read your suggestion regarding the sliders, to make sure i understand it and to try test it. At the moment my brain moves pretty slow with this new realm of information.

    Essentially I was thinking of something like: Create bool Day, bool Night. Then in void update, If TimeOfDay = somewhere between 6 and 22, then Day = True, else Night = True. Or something along those lines. Then I can refer to the script elsewhere to check if Night is true and have things happen etc. But, I don't know how to write that out in a way that wouldn't appear embarrassingly wrong. ha.
     
  7. BronsonBrad

    BronsonBrad

    Joined:
    Jan 6, 2013
    Posts:
    8
    Appreciate that @Kurt-Dekker ! Ah yes I am about to embark into the world of Animation Curves for my sun and moon rotations...this is a very interesting idea for the day night cycle. It would mean I could change it on the fly which could be really handy. Thanks! Will play with it
     
  8. Stevens-R-Miller

    Stevens-R-Miller

    Joined:
    Oct 20, 2017
    Posts:
    664
    Not that I'm one of those people who obsesses over indentation rules, but...


    Just use one boolean:
    isDay
    . When it is false, that means it is night.
    You won't be charged by the keystroke. Try things out and see what happens. Never let the perfect be the enemy of the good, and never be the victim of analysis paralysis.
    That's the right attitude! These are games, after all. Have fun.
     
  9. BronsonBrad

    BronsonBrad

    Joined:
    Jan 6, 2013
    Posts:
    8
    One ought to frame this "Never let the perfect be the enemy of the good, and never be the victim of analysis paralysis." :D
     
  10. Stevens-R-Miller

    Stevens-R-Miller

    Joined:
    Oct 20, 2017
    Posts:
    664
    Not framed, but I have Post-It notes on my walls...
     
    BronsonBrad likes this.
  11. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,760
    Or as I like to say, "Does it work? Finish everything else now, and if you still have time, come back and polish."

    Or as I like to say, "let's roll some code and see how things start looking."
     
  12. Stevens-R-Miller

    Stevens-R-Miller

    Joined:
    Oct 20, 2017
    Posts:
    664
    Or, as we used to say back in the day:
    • Make it run.
    • Make it work.
    • Make it fast.