BGE: In-Game Time and Event Timing

Time plays an important part in games, from measuring performance to timing events. This tutorial will examine how to use the python time module. We’ll start by looking at formatting and using time and date information then move more specifically to timing events, ending with a mini-game to see it in action.

There are many ways we could keep track of time in the BGE. For instance, we could always use a delay sensor, or an always sensor set to pulse mode to update a property every so often. We could do this every 60 logic ticks, so it would increase roughly every second. But what if we wanted something with more precision? Such as measuring time between keystrokes or if we wanted to time a short section within a script, then only one logic tick would have elapsed irrespective of how long that tick took. This is where the python time module comes in. So lets import time and have a look:

Formatting Time

time.time() returns the number of seconds passed since time started, a point known as the epoch. This isn’t the very dawn of time in the literal sense, instead it is defined by the operating system. If you’re curious what the epoch is on your system try time.gmtime(0).

Anyway, the floating point number from time.time() isn’t very readable from a human standpoint. That’s where time.localtime() comes in. This takes a set of seconds as parameter, and if none is given then it uses the value returned by time.time(), it returns a named tuple (called struct_time) of 9 useful time/date information. By named tuple I mean you can refer to the value you want by name (refer to the docs for all the names). Assuming this script is hooked up to a text object, we can do this to display a clock:

import time
import bge

own = bge.logic.getCurrentController().owner

t = time.localtime()

own['Text'] = str(t.tm_hour) + ":" + str(t.tm_min) + ":" + str(t.tm_sec)

Ok, not really stretching the BGE to it’s limits, so lets say you want to keep track of time in an open world game? And for fun, lets be able to speed time up, slow it down and pause it.

Game Time

##################################################################
#                                                                #
# BGE World Time v1                                              #
#                                                                #
# By Jay (Battery)                                               #
#                                                                #
# https://whatjaysaid.wordpress.com/                              #
#                                                                #
# Feel free to use this as you wish, but please keep this header #
#                                                                #
##################################################################

import bge
import time

class WorldTime:
    def __init__(self):
        self.gameTime = 0.0
        self._timeElapsed = time.time()
        self._deltaTime = time.time()
        self._year = time.gmtime(self.gameTime).tm_year
        self.daysElapsed = 0
        self.gameYear = 1010
        self.pause = False
        #provided in seconds
        self.hourLength = 60
        #List must contain 7 strings
        self.weekdays = []
        #List must contain 11 strings
        self.yearMonths = [] 

        self.updateWorldTime()
        return

    def updateWorldTime(self):
        if self.pause:
            self.timeElapsed = time.time()
            return
        self._deltaTime = time.time() - self._timeElapsed
        self._timeElapsed = time.time()
        self.gameTime += self._deltaTime*(3600/self.hourLength)
        self.daysElapsed = int(self.gameTime/86400)
        t = time.gmtime(self.gameTime)
        self.hour = t.tm_hour
        self.minute = t.tm_min
        self.seconds = t.tm_sec
        self.mday = t.tm_mday
        self.gameYear += t.tm_year - self._year
        self._year += t.tm_year - self._year
        if self.weekdays:
            self.wday = self.weekdays[t.tm_wday]
        else:
            self.wday = time.strftime('%A', t)
        if self.yearMonths:
            self.month = self.yearMonths[t.tm_mon-1]
        else:
            self.month = time.strftime('%B', t)     

    def getWorldDisplayTime(self, secs = False):
        self.updateWorldTime()
        timeStr = str(self.hour) + ":" + str(self.minute)
        if secs:
            timeStr += ":" + str(self.seconds)
        return timeStr

    def skip(self, length, unit):
        if self.pause:
            return
        if unit == 's':
            self.gameTime += length
        elif unit == 'm':
            self.gameTime += length * 60
        elif unit == 'h':
            self.gameTime += length * 3600
        elif unit == 'd':
            self.gameTime += length * 86400
        self.updateWorldTime()

It looks like there’s a lot going on here, but really, it’s not much different to the previous example. Were keeping track of the change in time between each update (self._deltaTime), multiplying by how long we want each hour to last (self.hourLength) and adding the time that has passed to the total game time (self.gameTime). The rest of it is really just fancy formatting.

This has several advantages. Firstly, because were measuring the passage of time using the operating system we do not need to keep updating anything or do anything unless we need the game time (unlike timer variables). Secondly, pausing is simple because we just need to stop adding the time passed to the game time. Speeding and slowing time is easy as we just change the multiplier applied to the delta time. Thirdly, when it comes to saving we only need to save self.gameTime and then we can pass that back to the class on loading.

Instead of time.localtime() we’re using time.gmtime(), which is very similar in its operation, the real difference is in the returned tuple. time.gmtime() returns Greenwich Mean Time, which means time zones and day light savings are not included, unlike time.localtime(), which does. The result is a  module that will return the same time/date information regardless of the computer it’s run on. We pass the game time to time.gmtime() to use the seconds passed in the game to calculate what time, day, month and year the game is currently in.

The tuple returned by time.gmtime() is then parcelled out to the class variables so that the game can access them. We also make use of time.strftime() which allows us to use python’s string formatting and apply it to the struct_time tuple. This gives us the weekdays and month names. For added spice, you can override these in the class to provide your own day and month names. In the working example (see resources below) I’ve been lazy and used the names from the Elder Scrolls.

Pausing Python

You can use time.sleep(secs) to pause the current python thread. When working in pure python this function can be used to delay further execution. Interestingly, it works within the BGE, but not as you might expect. Because the BGE works through logic sequentially processing cannot move forward until time.sleep() has finished. This has the effect of suspending everything in the BGE. Although, I’m not sure why you would want to do this, since nothing else can happen until the sleep has finished. If anyone can think of a use post it below.

 Timing Within a Script

This is much more straight word then creating game time. We can make use of time.clock() to keep track of how long something takes. The return value of time.clock() varies depending on the operating system. For unix systems it gives us the CPU time, in windows this is the system time. Eitherway, it’s reasonably precise, which means we can use it for doing benchmarks of code:

import time

t1 = time.clock()

...
some code would go here
...

t2 = time.clock() - t1
print(t2)

There are more accurate ways to profile code (timeit for example) but for finding hotspots in your scripts (or slow scripts overall), this simple method will often do. We can also use this method to time events, read on.

Timing Key Events

Check out the canyon crossing example in the resources below to see this in action. The key difference here is that we need to store the value of initial time in a game object property (own[‘t’]) to prevent it from being lost with each logic tick.

import bge
import time

cont = bge.logic.getCurrentController()
own = cont.owner
scene = bge.logic.getCurrentScene()

targetTime = 0.3

keyPress = own.sensors['Keyboard']
move = own.actuators['Motion']

yPos = 0.0

if 'init' not in own:
    own['t'] = time.clock()
    own['init'] = 1
    own['move'] = own.worldPosition
    own['rot'] = [0,0,0]
    own['r'] = 0.0
    scene.objects['collisionBox'].suspendDynamics()

if keyPress.positive:
    own['r'] = (targetTime - (time.clock() - own['t']))*0.02
    own['t'] = time.clock()
    yPos = abs(0.5 - targetTime - (time.clock() - own['t']))

if own.worldOrientation[2][2] < 0.87:
    scene.objects['collisionBox'].restoreDynamics()

own['rot'][1] += own['r']
own.worldOrientation = own['rot']

own['move'][1] += yPos
own.worldPosition = own['move']

The time between each key press is compared against a target value (targetTime) and the difference is then applied as rotation and location (with a bit of tweaking to make the numbers work properly).

The 11th Hour

So, we’ve covered creating our own in-game time system, timing sections of script and timing keyboard events. One thing that’s missing from all this is being able to set the time and date or go backwards. Setting in-game time is perfectly doable, you just have to work out how far you want to nudge it forward in seconds (there’s even a python module to help with this). Going back in time is a bit trickier but can be done using the timedate module to work out the days before the epoch. The real challenge is figuring out how to ensure all the things in the game are in their correct positions for any given time. But it can be done (Stalker, for example, updates AI positions even when they’re not in play).

As always, any questions or comments, leave them below. Laters

Resources

Game time example .blend

Canyon Crossing example

Credit goes to TehJoran for the low poly character used in this tutorial

Advertisements

~ by Jay on May 26, 2014.

3 Responses to “BGE: In-Game Time and Event Timing”

  1. […] often need to be able to manipulate the game environment. In this tutorial we’ll build on the worldTime.py class from last time and use it to drive a simple day and night cycle. We’ll look at 2 parametric functions (sine […]

    Like

  2. thank you thank youuu its just what ive bein loojing for days (y)

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

 
%d bloggers like this: