How can I get better timed events for my games?

2019-08-27 11:10发布

问题:

I have written an ordinary dispatcher timer method for creating a gameloop in WPF. I notice though that if it is set to a shorter interval than say 200 ms, it doesn't catch up correctly.

I printed the seconds to screen (see code below) and compared it to my wrist watch. With a setting of 500 ms, it's okay, but if I set it much lower there is a huge discrepancy. I tried a setting of 10 ms, and that meant that the time onscreen passed only 38 seconds in a (real) minute! (Note that all my game engine code was removed during testing, so it's just the timer loop that is called. Also, it doesn't matter if I run the code from VS or the exe file in the Debug folder.)

Note that the games I create run smoothly, it's just that my (standard) Each_Tick method doesn't get called at the correct times. This in turn means that my games will run faster on a faster computer.

So how do I keep correct track of time and make sure that the Each_Tick method fires at the same time, independently of the computer (or cellphone) used? That is, I would rather have a limit on the number of game objects, collision detection precision etc, but on time, rather than just going as fast as possible. Put differently, if I set the timer increment value to 50ms (which I think is reasonable as that would mean 20 times per second), I really want the game to be updated 20 times per second.

I really don't want to get into threading etc if it's possible to avoid it, as the games themselves run fine now. I just want them to play back at the same speed.

I looked up some great replies at How to control frame rate in WPF by using dispatcher timer accurately? but this still doesn't answer my question: how do I get my games to run at the same speed, regardless of (modern) computer/cell phone and whether it's WPF/UWP or perhaps something else?

Or is this impossible to do in a rather easy manner, and I should just accept that game speed depends on the computer used?

Thanks!

Code:

    public void StartTimer()
    {
        //This variable is used to get to the controls (labels etc) of the MainWindow (WPF)
        MainWindow mainWin = System.Windows.Application.Current.Windows.Cast<System.Windows.Window>().FirstOrDefault(window => window is MainWindow) as MainWindow;

        time = TimeSpan.FromSeconds(0);

        //What the code below does is this:
        //For each 10 ms, call the different methods. Then add 10 ms to the current time.

        timer = new DispatcherTimer(new TimeSpan(0, 0, 0, 0, 10), DispatcherPriority.Normal, delegate
        {
            if (runGame == false) return; //only go on if not in pause mode
            mainWin.txtInfo.Text = time.ToString("mm\\:ss");//Shows the timer in a textbox, only showing minutes and seconds.


            //Collision code etc removed during the test

            time = time.Add(TimeSpan.FromMilliseconds(10)); //adds a specified time to the current time
        }, System.Windows.Application.Current.Dispatcher);
    }

Note that the code above was added for testing purposes. The original code (facing the same problems) looks like this:

    public void StartTimer()
    {
        //A note on the dispatcherTimer http://www.wpf-tutorial.com/misc/dispatchertimer/
        var timer = new DispatcherTimer();
        timer.Interval = new TimeSpan(0, 0, 0, 0, 10); // Each every n milliseconds (set low to avoid flicker)
        timer.Tick += EachTick;
        timer.Start();
    }

    // A testing counter
    int counter = 0;

    // Raised every tick while the DispatcherTimer is active.
     private void EachTick(object sender, object e)
    { etc

回答1:

Build in timers are not very accurate. Use this code snippet. Ive used this timer many times. This timer is really accurate. Kudos to John

public class AccurateTimer
    {
        private delegate void TimerEventDel(int id, int msg, IntPtr user, int dw1, int dw2);
        private const int TIME_PERIODIC = 1;
        private const int EVENT_TYPE = TIME_PERIODIC;
        [DllImport("winmm.dll")]
        private static extern int timeBeginPeriod(int msec);
        [DllImport("winmm.dll")]
        private static extern int timeEndPeriod(int msec);
        [DllImport("winmm.dll")]
        private static extern int timeSetEvent(int delay, int resolution, TimerEventDel handler, IntPtr user, int eventType);

        private readonly int _mTimerId;

        public AccurateTimer(int delay)
        {
            timeBeginPeriod(1);
            _mTimerId = timeSetEvent(delay, 0, TimerTick, IntPtr.Zero, EVENT_TYPE);
        }

        public void Stop()
        {
            timeEndPeriod(1);
            System.Threading.Thread.Sleep(100);// Ensure callbacks are drained
        }

        private void TimerTick(int id, int msg, IntPtr user, int dw1, int dw2)
        {
            Console.WriteLine("Tick " + DateTime.Now.TimeOfDay.TotalMilliseconds);
        }
    }