Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Execute action after specific amount of milliseconds

Discussion in 'Scripting' started by ILLUCIA_, Sep 16, 2019.

  1. ILLUCIA_

    ILLUCIA_

    Joined:
    Sep 16, 2019
    Posts:
    4
    Hey everyone,

    so I've been fiddling around with this for a while now and I'm not sure how to deal with this the right way and to get to most accurate results.

    What I'm trying to do is the following:
    • I have a GameObject called Selector and 2 GameObjects that are representing Lane 1 and Lane 2
    • There's an AudioSource (song) running during the gameplay.
    • I'm getting the current playback time by doing the following: song.timeSamples / song.clip.frequency
    • I have an array of integers containing values in milliseconds (example: [1000, 1250, 1500, 1750, 2000...]
    • At (exactly) the given playback time I want Unity to check if the Selector is either on Lane 1 and if it is on the first Lane, the score should be increased by 100.

    Note:
    I've tried using InvokeRepeat but after some testing I ended up getting different scores after every test I did.

    I'm new to Unity so any help would be appreciated :)
     
  2. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,773
    There are a number of ways to do timing checks in Unity, but most of them are dependent on the framerate. However, I'm puzzled how the setup you describe could lead to different scores for each test; it seems like, while it may not hit at the exact expected time (as a result of varying framerates), it should hit a consistent number of times, and thus result in a consistent score. This suggests that there's something wrong in the code itself, rather than in the concept.
     
    ILLUCIA_ likes this.
  3. Neriad

    Neriad

    Joined:
    Feb 12, 2016
    Posts:
    125
    You can achieve something like that with Coroutines :

    Code (CSharp):
    1. void Start ()
    2. {
    3.     StartCoroutine(Delay());
    4. }
    5.  
    6. IEnumerator Delay ()
    7. {
    8.     // Do something before
    9.     yield return new WaitForSeconds(1);
    10.     // Do something after
    11. }
    You will need to convert from milliseconds to seconds though.
    You can also use yield return null, yield return WaitForEndOfFrame, and a few more, you should check the documentation about coroutines.
     
  4. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,773
    Gotta be careful with doing this for something like musical timing, as errors will accumulate with every step.
    Example: WaitForSeconds(1)
    The next frame after 1 second will be 1.015252 (or whatever). That's when this code happens. Then you call WaitForSeconds(1) again. Now, it's waiting for 2.015252, but the next frame after that might be 2.018. Do it again, and now it's on 3.0321412. Every time you do it, it'll be a smidgeon further delayed after the true time. Specifically, on average, you'll be delayed by 0.5 * (1 / fps) seconds every cycle. If you're running at 60fps and have 200 "beats" to hit in your song, then by the end of the song you'll be about 1.6 seconds behind the song.

    To avoid this what you would need to do is start with the base time, and base every single delay off of that, not on what the current frame's time is.
     
    ILLUCIA_ likes this.
  5. ILLUCIA_

    ILLUCIA_

    Joined:
    Sep 16, 2019
    Posts:
    4
    Thank you, I'll look into my code again... I guess I messed something up then.

    InvokeRepeat isn't exactly what I want thought, in the example I gave the interval was always 250ms but the array could also look like this: [1000, 1250, 1500, 3000, 3500, 4000, 4100].
    The code below seems to work fine so far but I'm still concerned about it being framerate dependant.

    Code (CSharp):
    1.  
    2. int[] sliderticks = {1000, 1250, 1500, 3000, 3500, 4000, 4100};
    3. int slidertickIndex = 0;
    4.  
    5. void Update()
    6. {
    7.                //songPosition is in milliseconds
    8.  
    9.                if (sliderticks.Count > slidertickIndex && songPosition > sliderticks[slidertickIndex])
    10.                 {
    11.                     score += 100;
    12.                     slidertickIndex++;
    13.                 }
    14. }
     
  6. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,605
    Requiring a precise milliseconds check seems like a bad idea. From how i understand it, you are trying to create a music based game, where the user has to do some inputs based on the song? If so, then these inputs can probably happen over a span of more than 1ms. Or in other words, a beat or whatever is generally way longer than one millisecond.

    So you could save it as a range of milliseconds instead, and then use Update() to check if you are at the right location for the current range of milliseconds. If yes, reward score, if not dont.
    Or am i misunderstanding the problem?
     
    ILLUCIA_ likes this.
  7. ILLUCIA_

    ILLUCIA_

    Joined:
    Sep 16, 2019
    Posts:
    4

    I think I understand what you're explaining. I'm just afraid it might get too inaccurate if I check for ranges.
    Currently there are no user inputs required, right now it's just meant to work similar to metronome if that makes any sense. Maybe look at the code I posted above, that's how I'm dealing with it right now.

    Sorry for all the confusion.
     
  8. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,773
    If you need to play a different sound in time with something else with sub-frame accuracy (like a metronome), check out PlayDelayed. It will require you to "look ahead" and predict whether the prescribed time is likely to hit within the next frame.
     
  9. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,605
    A bit off-topic, but if there are no user inputs, and the thing is supposed to work like a metronome, what's the score for? According to what you are doing now, wouldnt the desired behavior be to never miss anything, while the "game" autoplays, and thus every time possible, generates 100 score?
     
  10. ILLUCIA_

    ILLUCIA_

    Joined:
    Sep 16, 2019
    Posts:
    4
    I'm sorry, it's a bit hard to explain. I'll start over again.

    So as I said before there's 2 lanes: lane 1 and lane 2 and one indicator. The indicator shows which lane you have currently selected. There's either a lane on the right or on the left.

    Looks like this:

    Lane 1 Lane 2
    |
    | <-- lane
    |
    ^ <-- indicator

    While the indicator is on one of these lanes the score should increase by 100 and a sound should play, something like a drum sound after each of the timing points in the array (example: [1000, 1250, 1500, 1750, 2000...]).
    Also each part of the lane contains a time where it starts and where it ends.
    The player has to press space bar to switch from lane to lane. User input is required but that's not the issue, it's the timing that's an issue, because it has to be precise as possible so the sound that is being played doesn't sound offbeat.

    I hope this makes a bit more sense now, sorry.
     
  11. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,773
    Yeah, for playing sounds in sync with each other, go for PlayDelayed. That's what it exists for.