Using viznet we can recreate the Network communication tutorial to easily support multiple users by using the client server approach.
The first thing to do is to add the required imports, start the Vizard engine, set the background, and position the display so that the full map will be visible as the server is used to watch other interactions.
Next we create a list of objects which must be identical to the list of objects on the client. These are the allowable character displays for each client. We then add the map, and finally an empty dictionary to store client information in.
objectList = ['duck.wrl','logo.wrl','vcc_male.cfg','vcc_female.cfg']
maze = viz.add('tankmaze.wrl')
objects = {}
Here we define specific events which will be used in the interaction between the client and the server. These events must be defined in the same way on each client.
UPDATE = viznet.id('update')
CLIENT_DISCONNECTED = viznet.id('client_disconnected')
CHAT = viznet.id('chat')
Now we start the server and handle client joining and client leaving events. The onStopClient function preforms some cleanup actions as described in the comments.
viznet.server.start(port_in=14950,port_out=14951)
def onStartClient(e):
print
'** Client joined:',e.sender
viz.callback(viznet.CLIENT_CONNECT_EVENT,onStartClient)
def onStopClient(e):
print
'** Client left:',e.sender
viznet.server.send(CLIENT_DISCONNECTED,client=e.sender)
# Alert other clients
objects[e.sender].remove() #
Remove object from our map
del
objects[e.sender] # Delete user from array
viz.callback(viznet.CLIENT_DISCONNECT_EVENT,onStopClient)
The update function essentially updates a client's position, however if this is the first time the client's position is updated, it also adds the client to the map. Adding the user this way makes it so we don't have to have another event to handle when the client has picked a character. Further more this way we don't have to send a list of already connected clients to all new clients.
def update(e):
try:
objects[e.sender].setPosition(e.pos)
objects[e.sender].setEuler(e.ori)
except
KeyError: # If key doesn't exist add the user's
object
objects[e.sender] = viz.add(objectList[e.object],scale=[2,2,2],pos=e.pos,euler=e.ori)
objects[e.sender].tooltip =
e.sender # Sets the tool tip for this object
viz.callback(UPDATE,update)
Here we import and define the tool tip for the server.
# Tool Tip
import viztip
tth = viztip.ToolTip()
tth.start()
tth.setDelay(0)
The following portion of the server handles the chat features. The updateChat function updates the string to be displayed. The variable text is the on screen messages which is limited at 5, and the variable input displays the messages input by the server as they are input. The onChat function is called whenever the CHAT event is called which was defined previously.
# Chat Stuff
def updateChat(msg): # Function to update the chat display
text.message('\n'.join(text.getMessage().split('\n')[1:])+'\n'+msg)
text = viz.addText('\n\n\n\n',viz.SCREEN) # Add to screen
text.alignment(viz.TEXT_LEFT_BOTTOM)
text.fontSize(25)
text.setPosition(.05,.08)
input = viz.addText('> _',viz.SCREEN) # Add to screen
input.fontSize(25);
input.setPosition(.032,.05)
input.visible(0)
# We only want visible when in type mode
def onChat(e): # Update the message display
updateChat(e.client+':
'+e.chat)
viz.callback(CHAT,onChat)
This portion handles the input of messages. To start typing a message one must first press 'y' thus enabling talking mode. Once in talking mode one can exit by either sending the message with the enter key, or canceling the message by pressing escape.
Note that this example only handles the basic keyboard characters.
talking = False
def onKeyDown(key):
global
talking
if
key == 'y' and not talking: # Start talking mode
talking = True
input.message('>
_')
input.visible(1)
elif
key == viz.KEY_ESCAPE and
talking: # Exit talking mode
talking = False
input.visible(0)
return True
elif
talking and key == viz.KEY_RETURN:
# Send message
talking = False
viznet.server.send(CHAT,client='SERVER',chat=input.getMessage()[2:-1])
input.visible(0)
# Update
display with our message
updateChat('SERVER'+': '+input.getMessage()[2:-1])
elif
talking and key == viz.KEY_BACKSPACE:
# Delete a character
if len(input.getMessage()) > 3:
input.message(input.getMessage()[:-2]+'_')
elif
len(key) > 1: # Handle special keys
return False
elif
talking and 32 <= ord(key) <= 126:
# Input message
input.message(input.getMessage()[:-1]+key+'_')
viz.callback(viz.KEYDOWN_EVENT,onKeyDown,priority=-6)
The client starts the same by setting up the background, adding the map and creating an empty dictionary of objects.
Then a timer is defined which is used to send updated position and orientation data, followed by the user defined events, which are identical to those on the server.
UPDATE = viznet.id('update')
CLIENT_DISCONNECTED = viznet.id('client_disconnected')
CHAT = viznet.id('chat')
Here is a clean way to connect to a server. This continues to probe the user for a server to connect to until it is connected, or the user no longer wishes to try. If the user does not connect the application is closed.
SERVER_NAME=vizinput.input('Enter Server Name')
while not viznet.client.connect(SERVER_NAME,port_in=14951,port_out=14950):
if
vizinput.ask('Could not connect to ' + SERVER_NAME + ' would you like to try
another?'):
SERVER_NAME=vizinput.input('Enter Server Name')
else:
viz.quit()
break
Here we create the object list, which is identical to that on the server, and ask the user which object they would like to be.
objectList = ['duck.wrl','logo.wrl','vcc_male.cfg','vcc_female.cfg']
obj_choice=vizinput.choose('Connected.
Select your character',objectList)
The timer portion sends updated information to all clients at the specified interval. Notice that we additionally add the client parameter so that other clients know where the message originated. We also send which index in the object array we selected so that the server and other clients know which object we are.
Here the client handles updates. Since the client uses a sendAll the client receives updates about itself. Thus we must first ignore messages that originated from itself. Then like we did with the server update function we update the position and orientation if the object already exists otherwise, we add the new client's object.
def update(e):
if
e.client != viz.getComputerName():
try:
objects[e.client].setPosition(e.pos)
objects[e.client].setEuler(e.ori)
except KeyError:
objects[e.client]
= viz.add(objectList[e.object],scale=[2,2,2],pos=e.pos,euler=e.ori)
objects[e.client].tooltip
= e.client
viz.callback(UPDATE,update)
The client_disconnected function performs some simple cleanup when another
client disconnects, and the onStopServer function closes the client.
def client_disconnected(e):
objects[e.client].remove()
del
objects[e.client]
viz.callback(CLIENT_DISCONNECTED,client_disconnected)
def onStopServer(e):
print
'Disconnected from server'
viz.quit()
viz.callback(viznet.SERVER_DISCONNECT_EVENT,onStopServer)
The tool tip functionality is then added.
import viztip
tth = viztip.ToolTip()
tth.start()
tth.setDelay(0)
Finally the chat is defined. This is nearly identical to the server's chat except that we use the client's sendAll function, rather than the server's send function, and because of that the client does not need to handle its own messages.
def updateChat(msg):
text.message('\n'.join(text.getMessage().split('\n')[1:])+'\n'+msg)
text = viz.addText('\n\n\n\n',viz.SCREEN)
text.alignment(viz.TEXT_LEFT_BOTTOM)
text.fontSize(25)
text.setPosition(.05,.08)
input = viz.addText('> _',viz.SCREEN)
input.fontSize(25)
input.setPosition(.032,.05)
input.visible(0)
def onChat(e):
updateChat(e.client+':
'+e.chat)
viz.callback(CHAT,onChat)
talking = False
def onKeyDown(key):
global
talking
if
key == 'y' and not talking:
talking = True
input.message('>
_')
input.visible(1)
elif
key == viz.KEY_ESCAPE and
talking:
talking = False
input.visible(0)
return True
elif
talking and key == viz.KEY_RETURN:
talking = False
viznet.client.sendAll(CHAT,client=viz.getComputerName(),chat=input.getMessage()[2:-1])
input.visible(0)
elif
talking and key == viz.KEY_BACKSPACE:
if len(input.getMessage()) > 3:
input.message(input.getMessage()[:-2]+'_')
elif
len(key) > 1:
return False
elif
talking and 32 <= ord(key) <= 126:
input.message(input.getMessage()[:-1]+key+'_')
viz.callback(viz.KEYDOWN_EVENT,onKeyDown,priority=-6)
Primer on networking