Reading input data from a network

In this recipe you will learn how to remotely control an actor by sending commands over a network. This is a very common usage scenario in AI simulations, for example. In many of these simulations one central server program is used for visualizing the current simulation state. Each of the simulated entities is processed on a dedicated computer that connects to the server to send commands for altering the state of the simulation.

The following instructions will teach you how to build such a setup using Panda3D. The client part of this application will send text strings to the server side. The server will then interpret these strings as movement commands for an actor placed in a simple scene.

Getting ready

To be able to complete this recipe, you need to set up a new project according to the steps presented in the Chapter 1 article Setting up the game structure. Additionally, you need the FollowCam class from the recipe Making the camera smoothly follow an object found in Chapter 2 as well as the InputHandler class described in Implementing an abstraction layer for supporting multiple input methods, which can be found in this chapter. Copy the files FollowCam.py and InputHandler.py to the src subdirectory of your project.

This recipe also assumes that you have worked your way through Sending and receiving custom datagrams in Chapter 9, Networking. Some of the code presented in the article will be reused and altered slightly in the following steps. If you haven't yet read said recipe, this might be the right time to do so.

How to do it...

This recipe consists of the following tasks:

  1. Implement the classes responsible for sending and receiving in a new source file called NetClasses.py:
    from panda3d.core import *
    from direct.distributed.PyDatagram import PyDatagram
    from direct.distributed.PyDatagramIterator import PyDatagramIterator
    from random import choice
    class NetCommon:
    def __init__(self, protocol):
    self.manager = ConnectionManager()
    self.reader = QueuedConnectionReader(self.manager, 0)
    self.writer = ConnectionWriter(self.manager, 0)
    self.protocol = protocol
    taskMgr.add(self.updateReader, "updateReader")
    def updateReader(self, task):
    if self.reader.dataAvailable():
    data = NetDatagram()
    self.reader.getData(data)
    reply = self.protocol.process(data)
    if reply != None:
    self.writer.send(reply, data.getConnection())
    return task.cont
    class Server(NetCommon):
    def __init__(self, protocol, port):
    NetCommon.__init__(self, protocol)
    self.listener = QueuedConnectionListener(self.manager, 0)
    socket = self.manager.openTCPServerRendezvous(port, 100)
    self.listener.addConnection(socket)
    self.connections = []
    taskMgr.add(self.updateListener, "updateListener")
    def updateListener(self, task):
    input handling, Panda3Dinput data, reading from networkif self.listener.newConnectionAvailable():
    connection = PointerToConnection()
    if self.listener.getNewConnection(connection):
    connection = connection.p()
    self.connections.append(connection)
    self.reader.addConnection(connection)
    print "Server: New connection established."
    return task.cont
    class Client(NetCommon):
    def __init__(self, protocol):
    NetCommon.__init__(self, protocol)
    def connect(self, host, port, timeout):
    self.connection = self.manager.openTCPClientConnection(host, port, timeout)
    if self.connection:
    self.reader.addConnection(self.connection)
    print "Client: Connected to server."
    def send(self, datagram):
    if self.connection:
    self.writer.send(datagram, self.connection)
    def start(self):
    data = PyDatagram()
    data.addUint8(0)
    data.addString("hi")
    self.send(data)
    class Protocol:
    def printMessage(self, title, msg):
    print "%s %s" % (title, msg)
    def buildReply(self, msgid, data):
    reply = PyDatagram()
    reply.addUint8(msgid)
    reply.addString(data)
    return reply
    def process(self, data):
    input handling, Panda3Dinput data, reading from networkreturn None
    class ServerProtocol(Protocol):
    def process(self, data):
    it = PyDatagramIterator(data)
    msgid = it.getUint8()
    if msgid == 0:
    pass
    elif msgid == 1:
    command = it.getString()
    self.printMessage("new command:", command)
    messenger.send(command)
    return self.buildReply(0, "ok")
    class ClientProtocol(Protocol):
    def __init__(self):
    self.lastCommand = globalClock.getFrameTime()
    self.commands = ["net-walk-start",
    "net-walk-stop",
    "net-left-start",
    "net-left-stop",
    "net-right-start",
    "net-right-stop"]
    def process(self, data):
    time = globalClock.getFrameTime()
    if time - self.lastCommand > 0.5:
    self.lastCommand = time
    return self.buildReply(1, choice(self.commands))
    else:
    return self.buildReply(0, "nop")
    
  2. Next, create another new source file called NetworkHandler.py. Implement the network input handler in the newly created file:
    from InputHandler import InputHandler
    from panda3d.core import *
    class NetworkHandler(InputHandler):
    def __init__(self):
    InputHandler.__init__(self)
    self.accept("net-walk-start", self.beginWalk)
    self.accept("net-walk-stop", self.endWalk)
    self.accept("net-left-start", self.beginTurnLeft)
    self.accept("net-left-stop", self.endTurnLeft)
    self.accept("net-right-start", self.beginTurnRight)
    self.accept("net-right-stop", self.endTurnRight)
    taskMgr.add(self.updateInput, "update network input")
    def updateInput(self, task):
    self.dispatchMessages()
    return task.cont
    
  3. Open Application.py and extend the Application class implementation:
    from direct.showbase.ShowBase import ShowBase
    from direct.actor.Actor import Actor
    from panda3d.core import *
    from FollowCam import FollowCam
    from NetworkHandler import NetworkHandler
    from NetClasses import Server, Client, ServerProtocol, ClientProtocol
    class Application(ShowBase):
    def __init__(self):
    ShowBase.__init__(self)
    self.setupScene()
    self.setupInput()
    self.setupNetwork()
    def setupScene(self):
    self.world = loader.loadModel("environment")
    self.world.reparentTo(render)
    self.world.setScale(0.5)
    self.world.setPos(-8, 80, 0)
    self.panda = Actor("panda", {"walk": "panda-walk"})
    self.panda.reparentTo(render)
    self.followCam = FollowCam(self.cam, self.panda)
    def setupInput(self):
    self.netInput = NetworkHandler()
    self.accept("walk-start", self.beginWalk)
    self.accept("walk-stop", self.endWalk)
    self.accept("reverse-start", self.beginReverse)
    self.accept("reverse-stop", self.endReverse)
    self.accept("walk", self.walk)
    self.accept("reverse", self.reverse)
    self.accept("turn", self.turn)
    def setupNetwork(self):
    server = Server(ServerProtocol(), 9999)
    client = Client(ClientProtocol())
    client.connect("localhost", 9999, 3000)
    client.start()
    def beginWalk(self):
    self.panda.setPlayRate(1.0, "walk")
    self.panda.loop("walk")
    def endWalk(self):
    input handling, Panda3Dinput data, reading from networkself.panda.stop()
    def beginReverse(self):
    self.panda.setPlayRate(-1.0, "walk")
    self.panda.loop("walk")
    def endReverse(self):
    self.panda.stop()
    def walk(self, rate):
    self.panda.setY(self.panda, rate)
    def reverse(self, rate):
    self.panda.setY(self.panda, rate)
    def turn(self, rate):
    self.panda.setH(self.panda, rate)
    
  4. Finally, press F6 to launch the program. You should be able to see a panda wander around randomly in a simple background scene:
How to do it...

How it works...

We start this recipe by implementing our networking layer. This is mostly taken from Chapter 9, but not without a few notable alterations to the communication protocol.

In our custom network protocol, we distinguish between two general message types, indicated by a numerical ID sent along with every command string. A message ID of 0 indicates an internal command, while an ID of 1 stands for a movement command.

After the client establishes a connection, it sends the internal command"hi" to start the conversation between the two hosts. The server then sends the reply"hi" to signal it has successfully received a command. In fact, the server acknowledges every command it receives by sending this reply to request further data.

Every 0.5 seconds, the client sends a random command string out of the possible movement commands stored in self.commands. When the server receives such a command with message ID 1, it uses Panda3D's messaging system to create a new event named after the command. This is where the NetworkHandler class we implemented in step 2 comes into play.

NetworkHandler is derived from the InputHandler class to create a new input handling implementation for network commands. We implement this class to listen for the messages the server side protocol dispatches when it receives a new command from the client. Whenever a new movement command arrives, the NetworkHandler class translates it to the common input message format implemented previously in the recipe Implementing an abstraction layer for supporting multiple input methods.

This leaves us with the Application class. Here we set up the scene and the networking layer. Additionally, we implement methods for handling incoming

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

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