2

I am creating a Guitar Hero Clone using C# and am having some trouble syncing animations and the sound.

A rundown of what I do now:

  • A read a file and get the note highway, note time and not length (This is all done correctly)
  • The notes are put into an array
  • Before a song starts, I generate a Storyboard, read in the entire array of notes, and then create DoubleAnimations to make the rectangle objects (notes) scroll down the screen.
  • I set the BeginTime on the animation to 1 second before the expected play time (the duration is spent approaching the end) , and the animation duration to 1 second. However, what I expect to happen (the notes playing in sync) inst happening.

Here is the code for creating the animation:

private void StartNote(MidiNoteEvent evnt)
    {
        const double length = 0;
        int[] highwayArray = evnt.Highway;

        for (int i = 1; i <= highwayArray.Length; i++ )
        {
            var highway = highwayArray[i-1];
            if (highway > 0)
            {
                string name = evnt.Time.ToString() + "|" + highway.ToString(); 
                var rect = new Rectangle {Tag=name, Height = length+5, Width = 50 };
                Canvas.SetTop(rect,-6);

                int offset;

                if (i == 1)
                {
                    offset = 20;
                    rect.Fill = new SolidColorBrush(Colors.Green);
                }
                else if (i == 2)
                {
                    offset = 120;
                    rect.Fill = new SolidColorBrush(Colors.Red);
                }
                else if (i == 3)
                {
                    offset = 220;
                    rect.Fill = new SolidColorBrush(Colors.Yellow);
                }
                else if (i == 4)
                {
                    offset = 320;
                    rect.Fill = new SolidColorBrush(Colors.Blue);
                }
                else
                {
                    offset = 420;
                    rect.Fill = new SolidColorBrush(Colors.Orange);
                }

                rect.Margin = new Thickness(offset, 0, 0, 0);

                guitarCanvas.Children.Add(rect);

                var duration = new Duration(TimeSpan.FromSeconds(1));

                var myDoubleAnimation2 = new DoubleAnimation
                                             {Duration = duration, BeginTime = TimeSpan.FromSeconds(evnt.Time-1)};

                _notes.Children.Add(myDoubleAnimation2);

                Storyboard.SetTarget(myDoubleAnimation2, rect);

                // Set the attached properties of Canvas.Left and Canvas.Top
                // to be the target properties of the two respective DoubleAnimations
                Storyboard.SetTargetProperty(myDoubleAnimation2, new PropertyPath("(Canvas.Top)"));

                myDoubleAnimation2.To = guitarCanvas.Height;

                myDoubleAnimation2.Completed += new EventHandler(myDoubleAnimation2_Completed);

                // Begin the animation.
                //sb.Begin();
            }
        }

    }

I am not sure of how to fix this problem, am I misunderstanding how the animation works? Using the wrong units? Or is it something else entirely? Any tips are very much appreciated, even if it is a complete rewrite of how I display notes.

Nate
  • 30,286
  • 23
  • 113
  • 184
Ty Rozak
  • 471
  • 6
  • 19

3 Answers3

2

I reccomend you use a UserControl and use the paint event to draw a FillRectangle and change the position of the control to make the rectangle animation using a Timer Tick event.

EDIT 1: To improve the performance of your user control and to avoid the flickering effect look here.

EDIT 2: The timer works in background and to enable and disable it you can write a public field like here:

private Timer _timer;

public bool TimerEnabled
{
    get { return _timer.Enabled; }
    set { _timer.Enabled = value; }
}

void InitializeTimer()
{
    _timer = new Timer();
    _timer.Interval = 1; //One millisecond
    _timer.Enabled = false;
    _timer.Tick += new EventHandler(_timer_Tick);
}

void _timer_Tick(object sender, EventArgs e)
{
    //Example
    Location = new Point(Location.X, Location.Y + 1);
}
Community
  • 1
  • 1
Omar
  • 16,329
  • 10
  • 48
  • 66
  • Can all this be done before the song starts? I dont think it is possible to dynamically calculate while the song is running because it would cause smalls amount of lag while generating the objects – Ty Rozak Mar 07 '12 at 16:42
  • You can initialize your game to avoid lag, calculating before the song start how many object do you need and generate and initialize an Array of UserControls. – Omar Mar 07 '12 at 16:46
  • How would the movement actually be done though? How could you smoothly move the user control while not sacrificing performance? – Ty Rozak Mar 07 '12 at 16:52
  • Also, what would trigger the start of movement? Would a timer be running in the background, ticking every millisecond to see whether or not a note should be played? Or is there another way? – Ty Rozak Mar 07 '12 at 17:00
2

How are you playing your sound? I did something similar with video. I started the video in the same storyboard as I put my other animations so they'd all be on the same timer. So I had my Storyboard with a MediaTimeline and then added my other animations to the same Storyboard.

Worked great. Just make sure you set SlipBehavior to "Slip" on the storyboard.

In the actions for a button click, I had:

<BeginStoryboard Name="playVideoSB">
    <Storyboard SlipBehavior="Slip" Completed="Storyboard_Completed" FillBehavior="Stop">
        <MediaTimeline Source="{Binding Filename}" Storyboard.TargetName="video" Name="mediaTime"/>
    </Storyboard>
</BeginStoryboard>

I added to that storyboard in the code something like this:

BooleanAnimationUsingKeyFrames bukf = new BooleanAnimationUsingKeyFrames();
Storyboard.SetTargetProperty(bukf, new PropertyPath(PropertyFoo));

Storyboard.SetTarget(bukf, myObject);

bukf.KeyFrames.Add(new DiscreteBooleanKeyFrame(true, KeyTime.FromTimeSpan(start)));
bukf.KeyFrames.Add(new DiscreteBooleanKeyFrame(false, KeyTime.FromTimeSpan(end)));
playVideoSB.Storyboard.Children.Add(bukf);

I happened to be using boolean animations, but it should work with doubles.

Matt Burland
  • 44,552
  • 18
  • 99
  • 171
1

My answer isn't specific to C# as such, but to general audio/visual syncing. It also depends on the sound system being used to play the sounds. To me, it sounds like the problem you are encountering is one of your animation clock not being in sync with your audio clock.

Usually when you write some code that has to keep a note stream in sync with an audio track, you tie your animation updates directly to the clock on the sound source, rather than relying on the renderer frame events.

With many animation systems when you say "play this animation", internally the update loop will most likely perform a variable time-step animation to gradually move your object from A to B over T. This is all fine until you want things to happen with as much precision as audio timing (a necessity for a rhythm action game). I won't go into the details behind it, but the crux of it is that the clock that times your frames and the clock that times your audio might not give the exact same time.

The solution is relatively simple (although my explanation might not be so good): update your note positions on a per-frame basis and don't rely on an animation system to get it right.

You know that since they are scrolling down the lanes that each note has a Y position p at time t. Using t = (the time value from the audio clock), you can calculate where you should be drawing p at any given time with something like: p = laneLength - t where laneLength is the top of your lane (assume 1.0). The notes would then scroll down past 0 exactly in time with the audio clock. What happens when they hit or pass 0 is up to the display.

I hope this makes sense, I'm not convinced it does :)

badgerr
  • 7,802
  • 2
  • 28
  • 43