[Share] GameTime class

This is simple, very simple, but it works. It need to be used internally by jme, for example, in simpleUpdate/controlUpdate/etc. the tpf should be 0 if GameTime is in pause state, but a special Node at the very top of the scene’s tree, which overrides updateLogicalState will do the trick. This way you can stop everything: animations, particles etc.

There are an example functions loadFromSave and getSaveData which shows that including the game time in savegame file is an easy task.

Having an access to current time (not only tpf) in object’s controllers allows you to calculate everything like x = f(time);
This is the way everything works in my Skullstone. Doors? I have _timeStartSwitchState and _timeEndSwitchState, which are serialized during savegame too, I can save/reload/pause game whilethe doors are opening and everything works like a charm.

GameTime class is thread safe.

@nehon if you find it useful add it to 3.1.

Here is the code:

import java.io.IOException;

import com.dungeongame.gameclient.util.SaveReader;
import com.dungeongame.gameclient.util.SaveWriter;

public class GameTimer
{
    public class GameTimeState
    {
        private long _time;
        private boolean _paused;
        
        public long getTime() 
        {
            return _time;
        }

        public boolean isPaused() 
        {
            return _paused;
        }
    }

    private static GameTimer _instance;
    
    public static GameTimer getInstance()
    {
        if (_instance == null) _instance = new GameTimer();
        
        return _instance;
    } 
    
    private boolean _pause;
    
    private long _beforePauseTime;
    private long _resumeTime;
    private long _internalTime;
    
    private long _gameTime;

    private GameTimer()
    {
        _pause = false;

        _beforePauseTime = 0;
        _internalTime = System.nanoTime() / 1000000;
        _resumeTime = _internalTime;

        _gameTime = 0;

        pause();
    }
    
    public GameTimeState createState()
    {
        return new GameTimeState();
    }    

    public synchronized boolean isPaused()
    {
        return _pause;
    }
    
    public synchronized long pause()
    {
        _internalTime = System.nanoTime() / 1000000;
        _gameTime = _beforePauseTime + (_internalTime - _resumeTime);
        
        if (_pause) return _gameTime; 
        
        _pause = true;
        
        _beforePauseTime = _gameTime;
        
        return _gameTime;
    }
    
    public synchronized void resume()
    {
        _pause = false;
        
        _resumeTime = System.nanoTime() / 1000000;
    }
    
    /**
     * 
     * @param newTime czas gry np. sprzed zrobienia save
     */
    public synchronized void resume(long newTime)
    {
        _beforePauseTime = newTime;
        
        _pause = false;
        
        _resumeTime = System.nanoTime() / 1000000;
    }
    
    public synchronized void fillState(GameTimeState st)
    {
        st._paused = _pause;
        
        if (_pause) st._time = _gameTime;
        else st._time = _beforePauseTime + ((System.nanoTime() / 1000000) - _resumeTime);
    }
    
    public synchronized long getGameTime()
    {
        if (_pause) return _gameTime;
        
        return _beforePauseTime + ((System.nanoTime() / 1000000) - _resumeTime);
    }

    public synchronized int loadFromSave(SaveReader sav)
    {
        try 
        {
            _gameTime = sav.readLong();
            _beforePauseTime = sav.readLong();
            _resumeTime = sav.readLong();            
        } 
        catch (IOException e) 
        {
            e.printStackTrace();
        }
        
        return 0;
    }
    
    public synchronized SaveWriter getSaveData()
    {
        SaveWriter sw = new SaveWriter();
        
        try 
        {
            sw.writeLong(_gameTime);
            sw.writeLong(_beforePauseTime);
            sw.writeLong(_resumeTime);
        } 
        catch (IOException e) 
        {
            e.printStackTrace();
        }

        return sw;
    }

    
}
2 Likes

An alternative is to just not use the automanaged viewport and create your own viewport and root node. Then you can call the update methods however and whenever you like. (To include not at all.)

As a general approach, passing 0 as tpf may cause issues for some controls that divide by tpf and it may be better to just not call updateLogicalState() in those cases.

A proper ‘to be included in core’ solution for game time is going to be a way more breaking and way more invasive change than this as the whole contract with controls will have to change.

1 Like

There is a “paused game logic feature” in my framework.
It’s really nice to fly around with the fly cam while particle systems and animations are stopped.
Also it is cool to have a main menu in front of a frozen game.
The camera can auto-rotate while the scene is frozen too - which adds a “Matrix”-style camera animation.

In order to make that feature I had to override the ‘update’ method of SimpleApplication.
I have four collections for things that need updates even when the game is paused:
List of Control that get 0f as tpf.
List of Control that get tpf as tpf.
List of AppState that get 0f as tpf.
List of AppState that get tpf as tpf.
(There is a timer running which still computes a good tpf value each frame).
(All Control and AppState not in these four collections don’t get any updates).
(All Node get a geometric update but no logic update).

:chimpanzee_cool:

2 Likes