Edit: this is not doing any smoothing yet. I’m working on it
base.dc
from direct.distributed import DistributedObject
from direct.distributed import DistributedNode
from newServer import Movable, Avatar
typedef uint32 DoId;
import types;
dclass Movable {
set_xyz(int16 x, int16 y, int16 z) required broadcast ram;
set_hpr(int16 h, int16 p, int16 r) required broadcast ram;
set_xyzhpr(int16 x, int16 y, int16 z, int16 h, int16 p, int16 r) required broadcast ram;
};
dclass Avatar : Movable {
setName(string text) required broadcast;
tellPlayer(string message) broadcast;
};
newServer.py
#############################################################################
## This sample program shows how to use a DistributedSmoothNode that shows ##
## up on multiple clients and is updated across the network without ##
## dealing with low level sockets and datagrams hardly at all. It also ##
## does smoothing on remote clients as the name implies ##
## Feel free to make any suggestion/modifications ##
## Enjoy! ##
## ##
## -=Zanz=- May 2009 ##
#############################################################################
import direct.directbase.DirectStart
from direct.showbase.ShowBaseGlobal import *
from direct.showbase.DirectObject import DirectObject
from direct.distributed.DistributedObject import *
from direct.distributed.DistributedSmoothNode import DistributedSmoothNode
from direct.distributed.ClientRepository import *
from direct.distributed.ServerRepository import *
from direct.gui.OnscreenText import OnscreenText
import sys
font = loader.loadFont("cmss12")
def addTitle(text):
return OnscreenText(text=text, style=1, fg=(1,1,1,1), font = font,
pos=(1.3,-0.95), align=TextNode.ARight, scale = .07)
def addInstructions(pos, msg):
return OnscreenText(text=msg, style=1, fg=(1,1,1,1), font = font,
pos=(-1.3, pos), align=TextNode.ALeft, scale = .05)
# Our Movable class that can be inherited into subclasses such as the Avatar class
# to allow the subclass to be moved around as a DistributedSmoothNode
class Movable(DistributedSmoothNode):
def __init__(self,cr):
DistributedSmoothNode.__init__(self,cr)
# you have to initialize NodePath.__init__() here because it is not called
# in DistributedSmoothNode.__init__()
NodePath.__init__(self, 'avatar')
# all the required methods as defined in the base.dc file
# note: for each method defined in the dc file that begins with
# set needs a corresponding get method in the class definition
def set_xyz(self,x,y,z):
self.setSmPos(x,y,z)
def get_xyz(self):
return tuple((self.getX(),self.getY(),self.getZ()))
def set_hpr(self,h,p,r):
self.setSmHpr(h,p,r)
def get_hpr(self):
return tuple((self.getH(),self.getP(),self.getR()))
def set_xyzhpr(self,x,y,z,h,p,r):
self.setSmPosHpr(x,y,z,h,p,r)
def get_xyzhpr(self):
return tuple((self.getX(),self.getY(),self.getZ(),self.getH(),self.getP(),self.getR()))
class Avatar(Movable):
def __init__(self,cr):
Movable.__init__(self,cr)
self.cr = cr
self.name = "unknown"
self.zone = 1
s = loader.loadModel('smiley')
s.reparentTo(self)
self.reparentTo(render)
self.readyToMove = 0
# required fields as defined in the base.dc file
def setName(self, name):
self.name = name
def getName(self):
return self.name
def tellPlayer(self, message):
print "message from "+self.name+">"+message
class MyClientRepository(ClientRepository):
def __init__(self,dcFileNames):
ClientRepository.__init__(self, dcFileNames)
# we need to delete the DistributedNode when the server tells us to
def handleDelete(self,di):
doId = di.getUint32()
do = self.doId2do.get(doId)
do.delete()
class Server(DirectObject.DirectObject):
def __init__(self):
self.serverRepository = ServerRepository(tcpPort=4400,udpPort=4400,dcFileNames=["base.dc"])
print "Server Repository instantiated"
class Client(DirectObject.DirectObject):
def __init__(self):
self.clientRepository = MyClientRepository(dcFileNames=["base.dc"])
#self.clientRepository.setVerbose(1)
#self.clientRepository.notify.setDebug(0)
self.host = "127.0.0.1"
self.port = "4400"
self.avatar = 0
self.zone = 1
self.sampleInterval = 8 # this is the how many frames between sending network updates
self.sampleCounter = 0
def connectToServer(self, host, port):
if(self.clientRepository.isConnected()): return
self.clientRepository.connect([URLSpec("http://"+host+":"+port)],
self.connectSuccess,[],self.connectFail,[])
def connectSuccess(self):
print "Connected to server"
base.taskMgr.add(self.tskMonitorConnection,"tskMonitorConnection")
def connectFail(self):
print "Connection to host failed"
def move(self, task):
toggle = 0;
if(self.avatar == 0): return Task.cont
# only send a network update every so often to not flood the wire
# you can modify self.sampleInterval in __init__ above to change the sample rate
if(self.sampleCounter >= self.sampleInterval):
self.sampleCounter = 0
# if connection is lost to the server stop all movement
if(self.clientRepository.isConnected() == 0):
return Task.done
self.avatar.sendUpdate("set_xyzhpr",[self.avatar.getX(),self.avatar.getY(),self.avatar.getZ(),
self.avatar.getH(),self.avatar.getP(),self.avatar.getR()])
elapsed = globalClock.getDt()
if (self.keyMap["cam-left"]!=0):
self.avatar.setH(self.avatar, +(elapsed*120))
if (self.keyMap["cam-right"]!=0):
self.avatar.setH(self.avatar, -(elapsed*120))
if (self.keyMap["forward"] !=0):
self.avatar.setY(self.avatar, -(elapsed*25))
if (self.keyMap["backward"] !=0):
self.avatar.setY(self.avatar, +(elapsed*25))
if (self.keyMap["up"] !=0):
base.camera.setP(base.camera, +(elapsed*50))
if (self.keyMap["down"] !=0):
base.camera.setP(base.camera, -(elapsed*50))
if (self.keyMap["oobe"] !=0):
base.oobe()
self.keyMap["oobe"] = 0
if (self.keyMap["menu"] !=0):
self.toggleMenu()
self.keyMap["menu"] = 0;
self.sampleCounter += 1
return Task.cont
def tskMonitorConnection(self, task):
if(self.clientRepository.isConnected() == 0):
print "Connection to server lost"
return Task.done
if(self.avatar == 0):
if(self.clientRepository.haveCreateAuthority()):
# this is the local avatar
self.avatar = self.clientRepository.createWithRequired("Avatar",1)
# turn on smoothing, but not prediction
self.avatar.activateSmoothing(True,False)
# start the smoothing task
self.avatar.startSmooth()
base.camera.setPos(0,-0.5,0.4)
base.camera.reparentTo(self.avatar)
base.camera.setH(180)
taskMgr.add(self.move,"moveTask")
return Task.cont
def setZone(self, task):
self.clientRepository.sendSetZoneMsg(self.zone)
return Task.done
class World(DirectObject.DirectObject):
def __init__(self):
self.title = addTitle("DistributedSmoothNode Demo")
self.inst1 = addInstructions(0.95, "[ESC]: Quit")
self.inst2 = addInstructions(0.90, "[Left Arrow]: Rotate Left")
self.inst3 = addInstructions(0.85, "[Right Arrow]: Rotate Right")
self.inst4 = addInstructions(0.80, "[Up Arrow]: Move Forward")
self.inst5 = addInstructions(0.75, "[Down Arrow]: Move Backwards")
self.inst6 = addInstructions(0.70, "[a]: Look up")
self.inst7 = addInstructions(0.65, "[z]: Look down")
self.inst8 = addInstructions(0.60, "[o]: Toggle oobe")
self.inst9 = addInstructions(0.55, "[s]: Start server")
self.inst10= addInstructions(0.50, "[c]: Connect to server")
self.oobe = 0
self.myClient = Client()
self.myClient.keyMap = {"forward":0, "backward":0, "cam-left":0, "cam-right":0,
"up":0, "down":0, "oobe":0, "menu":0}
self.accept("escape", sys.exit)
self.acceptOnce("c", self.connectToServer)
self.acceptOnce("s", self.startServer)
self.accept("arrow_up", self.setKey, ["forward",1])
self.accept("arrow_up-up", self.setKey, ["forward",0])
self.accept("arrow_down", self.setKey, ["backward",1])
self.accept("arrow_down-up", self.setKey, ["backward",0])
self.accept("arrow_left", self.setKey, ["cam-left",1])
self.accept("arrow_left-up", self.setKey, ["cam-left",0])
self.accept("arrow_right", self.setKey, ["cam-right",1])
self.accept("arrow_right-up", self.setKey, ["cam-right",0])
self.accept("a", self.setKey, ["up",1])
self.accept("a-up", self.setKey, ["up",0])
self.accept("z", self.setKey, ["down",1])
self.accept("z-up", self.setKey, ["down",0])
self.accept("o",self.setOobe)
base.disableMouse()
def setKey(self, key, value):
self.myClient.keyMap[key] = value
def connectToServer(self):
self.myClient.connectToServer(self.myClient.host,self.myClient.port)
# can't send this too soon or it gets lost so send it in the next frame
base.taskMgr.doMethodLater(0,self.myClient.setZone,"setzone")
def startServer(self):
myServer = Server()
def setOobe(self):
base.oobe()
w = World()
run()
As you can see this uses the same steering method as the RR example. Enjoy!
-=Z=-