Recording and simulating user input

In this recipe you will learn how to record the stream of user inputs and replay it at a later point. This can be useful in several areas of game development, such as playtesting and AI.

While testing a game, you could capture all of the playtesters' actions, for example. If a bug is encountered, the data file containing the input that led to a crash or unintended behavior can then be attached to the bug report so that a programmer is able to easily reproduce the steps that led to the problem and fix it.

Apart from being able to save input streams for reproducing bugs, this data could also be used to automate playtesting. You have to realize that testing a game means playing the same section of it over and over again, just to make sure everything works properly. To relieve your testers from this repetitive kind of work, using a technique similar to the one shown here could enable them to record a stream of interactions once for every test case. This way, as long as there are no fundamental changes to the level or the gameplay, testers would just need to hit "play" and wait for unexpected things to happen.

Prerecorded input could also be used as a starting point for computer controlled opponents. You could, for example, record the input of several hundred players and let the AI toggle between these command streams or just parts of them. This will of course not work for every game and might not be convincing enough, but a fake AI is still better than no AI if you didn't implement that part of your game yet.

Getting ready

We will use the recipe Implementing an abstraction layer for supporting multiple input methods found in this chapter as the base for this sample (Handling input from an Xbox 360 controller works too). If you haven't read and implemented it yet, take a step back and complete that recipe first!

How to do it...

The following steps are necessary for recording and simulating user input:

  1. Create a new source file. Name it InputRecorder.py and insert the code found below:
    from direct.showbase.DirectObject import DirectObject
    from panda3d.core import *
    class InputRecorder(DirectObject):
    def __init__(self):
    DirectObject.__init__(self)
    self.events = []
    self.setupEvents()
    def setupEvents(self):
    self.startTime = globalClock.getFrameTime()
    del self.events[:]
    self.accept("walk-start", self.recordEvent, ["walk-start"])
    self.accept("walk-stop", self.recordEvent, ["walk-stop"])
    self.accept("reverse-start", self.recordEvent, ["reverse-start"])
    self.accept("reverse-stop", self.recordEvent, ["reverse-stop"])
    self.accept("walk", self.recordEvent, ["walk"])
    self.accept("reverse", self.recordEvent, ["reverse"])
    self.accept("turn", self.recordEvent, ["turn"])
    def replay(self):
    self.ignoreAll()
    self.acceptOnce("replay-done", self.setupEvents)
    last = 0
    for e in self.events:
    taskMgr.doMethodLater(e[0], self.createInput, "replay", extraArgs = [e[1], e[2]])
    last = e[0]
    taskMgr.doMethodLater(last + 1, messenger.send, "replay done", extraArgs = ["replay-done"])
    def recordEvent(self, name, rate = 0):
    self.events.append((globalClock.getFrameTime() - self.startTime, name, rate))
    def createInput(self, event, rate):
    if not event in ["walk", "reverse", "turn"]:
    messenger.send(event)
    else:
    messenger.send(event, [rate])
    
  2. Open Application.py and add these two calls to the constructor:
    self.rec = InputRecorder()
    self.accept("r", self.startReplay)
    
  3. Add these two methods to the Application class:
    def startReplay(self):
    self.acceptOnce("replay-done", self.replayDone)
    messenger.send("walk-stop")
    messenger.send("reverse-stop")
    self.panda.clearTransform()
    self.rec.replay()
    def replayDone(self):
    self.panda.clearTransform()
    messenger.send("walk-stop")
    messenger.send("reverse-stop")
    
  4. Start the sample. Walk around a bit, then press the R key to see a replay of your actions.

How it works...

Our record and replay implementation is really simple. The InputRecorder class just adds additional event listeners for the input commands. But instead of moving an actor around the scene, it appends the command and its argument to a list. We also store the time the event occurred relative to the start time of the recording. This is enough for accurately reproducing player actions in the right order and with the correct timing.

When replaying, we first reset all transformations and stop playing animations. Then we queue up calls to the createInput() method, which is used to send movement commands, just as a normal input device would do. In fact, what we created here is a virtual game controller.

Finally, when the playing of the recording has finished, we reset the scene again and get ready to save another stream of user commands.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset