Игровой цикл и отслеживание времени - PullRequest
2 голосов
/ 03 апреля 2010

Может быть, я просто идиот, но я целый день пытаюсь реализовать игровой цикл, и он просто не щелкает. Я прочитал буквально каждую статью, которую смог найти в Google, но проблема в том, что все они используют разные механизмы синхронизации, что затрудняет их применение в моей конкретной ситуации (некоторые используют миллисекунды, другие используют тики и т. Д.).

По сути, у меня есть объект Clock, который обновляется при каждом выполнении игрового цикла.

internal class Clock {
    public static long Timestamp {
        get { return Stopwatch.GetTimestamp(); }
    }

    public static long Frequency {
        get { return Stopwatch.Frequency; }
    }

    private long _startTime;
    private long _lastTime;
    private TimeSpan _totalTime;
    private TimeSpan _elapsedTime;

    /// <summary>
    /// The amount of time that has passed since the first step.
    /// </summary>
    public TimeSpan TotalTime {
        get { return _totalTime; }
    }

    /// <summary>
    /// The amount of time that has passed since the last step.
    /// </summary>
    public TimeSpan ElapsedTime {
        get { return _elapsedTime; }
    }

    public Clock() {
        Reset();
    }

    public void Reset() {
        _startTime = Timestamp;
        _lastTime = 0;
        _totalTime = TimeSpan.Zero;
        _elapsedTime = TimeSpan.Zero;
    }

    public void Tick() {
        long currentTime = Timestamp;

        if (_lastTime == 0)
            _lastTime = currentTime;

        _totalTime = TimestampToTimeSpan(currentTime - _startTime);
        _elapsedTime = TimestampToTimeSpan(currentTime - _lastTime);
        _lastTime = currentTime;
    }

    public static TimeSpan TimestampToTimeSpan(long timestamp) {
        return TimeSpan.FromTicks(
            (timestamp * TimeSpan.TicksPerSecond) / Frequency);
    }
}

Я основал большую часть этого на XNA GameClock, но он значительно упрощен. Затем у меня есть класс Time, который содержит различные времена, которые должны знать методы Update и Draw.

public class Time {
    public TimeSpan ElapsedVirtualTime { get; internal set; }
    public TimeSpan ElapsedRealTime { get; internal set; }
    public TimeSpan TotalVirtualTime { get; internal set; }
    public TimeSpan TotalRealTime { get; internal set; }

    internal Time() { }

    internal Time(TimeSpan elapsedVirtualTime, TimeSpan elapsedRealTime,
            TimeSpan totalVirutalTime, TimeSpan totalRealTime) {
        ElapsedVirtualTime = elapsedVirtualTime;
        ElapsedRealTime = elapsedRealTime;
        TotalVirtualTime = totalVirutalTime;
        TotalRealTime = totalRealTime;
    }
}

Мой основной класс содержит один экземпляр Time, который он должен постоянно обновлять в течение игрового цикла. Пока у меня есть это:

private static void Loop() {
    do {
        Clock.Tick();

        Time.TotalRealTime = Clock.TotalTime;
        Time.ElapsedRealTime = Clock.ElapsedTime;

        InternalUpdate(Time);
        InternalDraw(Time);
    } while (!_exitRequested);
}

Свойства временного класса в реальном времени получаются великолепными. Теперь я хотел бы получить правильный цикл обновления / отрисовки, чтобы состояние обновлялось переменное число раз за кадр, но с фиксированным временным шагом. В то же время, Time.TotalVirtualTime и Time.ElapsedVirtualTime должны быть обновлены соответственно. Кроме того, я намерен в будущем поддерживать многопользовательскую игру на случай, если это повлияет на дизайн игрового цикла.

Какие-нибудь советы или примеры того, как я мог бы реализовать это (кроме ссылок на статьи)?

1 Ответ

2 голосов
/ 04 апреля 2010

Подход, в основном используемый в XNA, заключается в том, чтобы вызывать Update() с определенной фиксированной скоростью и вызывать Draw() столько, сколько возможно, до некоторого предела. В методе Update() вы обновляете переменные только для отражения мира в новый момент времени, а когда вызывается Draw(), вы рисуете его только на экране.

Чтобы заставить Update() работать с фиксированной скоростью, вы можете использовать таймер или более точный тайминг, используя:

[DllImport("kernel32.dll")]
public static extern long GetTickCount();

Тогда вы можете использовать другой (более медленный) таймер, который вызывает Invalidate() для объекта, который вы используете для рисования вашей игры (холста). На холсте «Paint-event» вы звоните Draw(). Таким образом, ваш мир прорисовывается каждый раз, когда система считает это необходимым (когда она делает недействительным ваш холст) или как только у него появляется возможность сделать это после того, как вы позвонили Invalidate().

...