mirror of
https://github.com/panda3d/panda3d.git
synced 2025-09-28 07:48:37 -04:00
Add Distributed Network samples
1: Simple client-server connection 2: Client-server connection with timeManager 3: Create distributed objects, sending and receiving messages 4: Distributed model file 5: Simple text chat 6: Simple smooth moving actor
This commit is contained in:
parent
a4ea476cce
commit
3d8f824081
123
samples/networking/01-simple-connection/client.py
Normal file
123
samples/networking/01-simple-connection/client.py
Normal file
@ -0,0 +1,123 @@
|
||||
# all imports needed by the engine itself
|
||||
from direct.showbase.ShowBase import ShowBase
|
||||
|
||||
# initialize the engine
|
||||
base = ShowBase()
|
||||
|
||||
# initialize the client
|
||||
|
||||
from direct.distributed.ClientRepository import ClientRepository
|
||||
from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
|
||||
from direct.gui.OnscreenText import OnscreenText
|
||||
from panda3d.core import TextNode
|
||||
|
||||
|
||||
base.accept("escape", exit)
|
||||
|
||||
# Function to put instructions on the screen.
|
||||
def addInstructions(pos, msg):
|
||||
return OnscreenText(text=msg, style=1, fg=(0, 0, 0, 1), shadow=(1, 1, 1, 1),
|
||||
parent=base.a2dTopLeft, align=TextNode.ALeft,
|
||||
pos=(0.08, -pos - 0.04), scale=.06)
|
||||
|
||||
# Function to put title on the screen.
|
||||
def addTitle(text):
|
||||
return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08,
|
||||
parent=base.a2dBottomRight, align=TextNode.ARight,
|
||||
fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1))
|
||||
|
||||
title = addTitle("Panda3D: Tutorial - Distributed Network (NOT CONNECTED)")
|
||||
inst2 = addInstructions(0.06, "esc: Close the client")
|
||||
inst2 = addInstructions(0.12, "See console output")
|
||||
|
||||
def setConnectedMessage():
|
||||
title["text"] = "Panda3D: Tutorial - Distributed Network (CONNECTED)"
|
||||
|
||||
base.accept("client-ready", setConnectedMessage)
|
||||
|
||||
#
|
||||
# CLIENT
|
||||
#
|
||||
|
||||
class GameClientRepository(ClientRepository):
|
||||
|
||||
def __init__(self):
|
||||
dcFileNames = ['../direct.dc']
|
||||
|
||||
# a distributed object of our game.
|
||||
self.distributedObject = None
|
||||
self.aiDGameObect = None
|
||||
|
||||
ClientRepository.__init__(
|
||||
self,
|
||||
dcFileNames = dcFileNames,
|
||||
threadedNet = True)
|
||||
|
||||
# Set the same port as configured on the server to be able to connect
|
||||
# to it
|
||||
tcpPort = ConfigVariableInt('server-port', 4400).getValue()
|
||||
|
||||
# Set the IP or hostname of the server we want to connect to
|
||||
hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
|
||||
|
||||
# Build the URL from the server hostname and port. If your server
|
||||
# uses another protocol then http you should change it accordingly.
|
||||
# Make sure to pass the connectMethod to the ClientRepository.__init__
|
||||
# call too. Available connection methods are:
|
||||
# self.CM_HTTP, self.CM_NET and self.CM_NATIVE
|
||||
self.url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
|
||||
|
||||
# Attempt a connection to the server
|
||||
self.connect([self.url],
|
||||
successCallback = self.connectSuccess,
|
||||
failureCallback = self.connectFailure)
|
||||
|
||||
def lostConnection(self):
|
||||
""" This should be overridden by a derived class to handle an
|
||||
unexpectedly lost connection to the gameserver. """
|
||||
# Handle the disconnection from the server. This can be a reconnect,
|
||||
# simply exiting the application or anything else.
|
||||
exit()
|
||||
|
||||
def connectFailure(self, statusCode, statusString):
|
||||
""" Something went wrong """
|
||||
# here we could create a reconnect task to try and connect again.
|
||||
exit()
|
||||
|
||||
def connectSuccess(self):
|
||||
""" Successfully connected. But we still can't really do
|
||||
anything until we've got the doID range. """
|
||||
|
||||
# Mark interest for zone 1, 2 and 3. There won't be anything in them,
|
||||
# it's just to display how it works for this small example.
|
||||
self.setInterestZones([1, 2, 3])
|
||||
|
||||
# This method checks whether we actually have a valid doID range
|
||||
# to create distributed objects yet
|
||||
if self.haveCreateAuthority():
|
||||
# we already have one
|
||||
self.gotCreateReady()
|
||||
else:
|
||||
# Not yet, keep waiting a bit longer.
|
||||
self.accept(self.uniqueName('createReady'), self.gotCreateReady)
|
||||
|
||||
def gotCreateReady(self):
|
||||
""" Ready to enter the world. Expand our interest to include
|
||||
any other zones """
|
||||
|
||||
# This method checks whether we actually have a valid doID range
|
||||
# to create distributed objects yet
|
||||
if not self.haveCreateAuthority():
|
||||
# Not ready yet.
|
||||
return
|
||||
|
||||
# we are ready now, so ignore further createReady events
|
||||
self.ignore(self.uniqueName('createReady'))
|
||||
|
||||
print("Client Ready")
|
||||
base.messenger.send("client-ready")
|
||||
|
||||
# Start the client
|
||||
GameClientRepository()
|
||||
|
||||
base.run()
|
31
samples/networking/01-simple-connection/server.py
Normal file
31
samples/networking/01-simple-connection/server.py
Normal file
@ -0,0 +1,31 @@
|
||||
# all imports needed by the engine itself
|
||||
from direct.showbase.ShowBase import ShowBase
|
||||
|
||||
# all imports needed by the server
|
||||
from direct.distributed.ServerRepository import ServerRepository
|
||||
from panda3d.core import ConfigVariableInt
|
||||
|
||||
# initialize the engine
|
||||
base = ShowBase(windowType='none')
|
||||
|
||||
# the main server class
|
||||
class GameServerRepository(ServerRepository):
|
||||
"""The server repository class"""
|
||||
def __init__(self):
|
||||
"""initialise the server class"""
|
||||
|
||||
# get the port number from the configuration file
|
||||
# if it doesn't exist, we use 4400 as the default
|
||||
tcpPort = ConfigVariableInt('server-port', 4400).getValue()
|
||||
|
||||
# list of all needed .dc files
|
||||
dcFileNames = ['../direct.dc']
|
||||
|
||||
# initialise a threaded server on this machine with
|
||||
# the port number and the dc filenames
|
||||
ServerRepository.__init__(self, tcpPort, dcFileNames=dcFileNames, threadedNet=True)
|
||||
|
||||
# start the server
|
||||
GameServerRepository()
|
||||
|
||||
base.run()
|
81
samples/networking/02-time-synced-connection/AIRepository.py
Normal file
81
samples/networking/02-time-synced-connection/AIRepository.py
Normal file
@ -0,0 +1,81 @@
|
||||
from direct.distributed.ClientRepository import ClientRepository
|
||||
from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
|
||||
|
||||
class AIRepository(ClientRepository):
|
||||
def __init__(self):
|
||||
""" The AI Repository usually lives on a server and is responsible for
|
||||
server side logic that will handle game objects """
|
||||
|
||||
# List of all dc files that are of interest to this AI Repository
|
||||
dcFileNames = ['../direct.dc']
|
||||
|
||||
# Initialize the repository. We pass it the dc files and as this is an
|
||||
# AI repository the dcSuffix AI. This will make sure any later calls to
|
||||
# createDistributedObject will use the correct version.
|
||||
# The connectMethod
|
||||
ClientRepository.__init__(
|
||||
self,
|
||||
dcFileNames = dcFileNames,
|
||||
dcSuffix = 'AI',
|
||||
threadedNet = True)
|
||||
|
||||
# Set the same port as configured on the server to be able to connect
|
||||
# to it
|
||||
tcpPort = ConfigVariableInt('server-port', 4400).getValue()
|
||||
|
||||
# Set the IP or hostname of the server we want to connect to
|
||||
hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
|
||||
|
||||
# Build the URL from the server hostname and port. If your server
|
||||
# doesn't use http you should change it accordingly. Make sure to pass
|
||||
# the connectMethod to the ClientRepository.__init__ call too.
|
||||
# Available connection methods are:
|
||||
# self.CM_HTTP, self.CM_NET and self.CM_NATIVE
|
||||
url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
|
||||
|
||||
# Attempt a connection to the server
|
||||
self.connect([url],
|
||||
successCallback = self.connectSuccess,
|
||||
failureCallback = self.connectFailure)
|
||||
|
||||
def connectFailure(self, statusCode, statusString):
|
||||
""" something went wrong """
|
||||
print("Couldn't connect. Make sure to run server.py first!")
|
||||
raise(StandardError, statusString)
|
||||
|
||||
def connectSuccess(self):
|
||||
""" Successfully connected. But we still can't really do
|
||||
anything until we've got the doID range. """
|
||||
# The Client Repository will throw this event as soon as it has a doID
|
||||
# range and would be able to create distributed objects
|
||||
self.accept('createReady', self.gotCreateReady)
|
||||
|
||||
def lostConnection(self):
|
||||
""" This should be overridden by a derived class to handle an
|
||||
unexpectedly lost connection to the gameserver. """
|
||||
exit()
|
||||
|
||||
def gotCreateReady(self):
|
||||
""" Now we're ready to go! """
|
||||
|
||||
# This method checks whether we actually have a valid doID range
|
||||
# to create distributed objects yet
|
||||
if not self.haveCreateAuthority():
|
||||
# Not ready yet.
|
||||
return
|
||||
|
||||
# we are ready now, so ignore further createReady events
|
||||
self.ignore('createReady')
|
||||
|
||||
# Create a Distributed Object by name. This will look up the object in
|
||||
# the dc files passed to the repository earlier
|
||||
self.timeManager = self.createDistributedObject(
|
||||
className = 'TimeManagerAI', # The Name of the Class we want to initialize
|
||||
zoneId = 1) # The Zone this Object will live in
|
||||
|
||||
print("AI Repository Ready")
|
||||
|
||||
def deallocateChannel(self, doID):
|
||||
""" This method will be called whenever a client disconnects from the
|
||||
server. The given doID is the ID of the client who left us. """
|
||||
print("Client left us: ", doID)
|
136
samples/networking/02-time-synced-connection/client.py
Normal file
136
samples/networking/02-time-synced-connection/client.py
Normal file
@ -0,0 +1,136 @@
|
||||
# all imports needed by the engine itself
|
||||
from direct.showbase.ShowBase import ShowBase
|
||||
|
||||
# initialize the engine
|
||||
base = ShowBase()
|
||||
|
||||
# initialize the client
|
||||
|
||||
from direct.distributed.ClientRepository import ClientRepository
|
||||
from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
|
||||
from direct.gui.OnscreenText import OnscreenText
|
||||
from panda3d.core import TextNode
|
||||
|
||||
|
||||
base.accept("escape", exit)
|
||||
|
||||
# Function to put instructions on the screen.
|
||||
def addInstructions(pos, msg):
|
||||
return OnscreenText(text=msg, style=1, fg=(0, 0, 0, 1), shadow=(1, 1, 1, 1),
|
||||
parent=base.a2dTopLeft, align=TextNode.ALeft,
|
||||
pos=(0.08, -pos - 0.04), scale=.06)
|
||||
|
||||
# Function to put title on the screen.
|
||||
def addTitle(text):
|
||||
return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08,
|
||||
parent=base.a2dBottomRight, align=TextNode.ARight,
|
||||
fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1))
|
||||
|
||||
title = addTitle("Panda3D: Tutorial - Distributed Network (NOT CONNECTED)")
|
||||
inst1 = addInstructions(0.06, "esc: Close the client")
|
||||
inst2 = addInstructions(0.12, "See console output")
|
||||
|
||||
def setConnectedMessage():
|
||||
title["text"] = "Panda3D: Tutorial - Distributed Network (CONNECTED)"
|
||||
|
||||
base.accept("client-ready", setConnectedMessage)
|
||||
|
||||
#
|
||||
# CLIENT
|
||||
#
|
||||
|
||||
class GameClientRepository(ClientRepository):
|
||||
|
||||
def __init__(self):
|
||||
dcFileNames = ['../direct.dc']
|
||||
|
||||
# a distributed object of our game.
|
||||
self.distributedObject = None
|
||||
self.aiDGameObect = None
|
||||
|
||||
ClientRepository.__init__(
|
||||
self,
|
||||
dcFileNames = dcFileNames,
|
||||
threadedNet = True)
|
||||
|
||||
# Set the same port as configured on the server to be able to connect
|
||||
# to it
|
||||
tcpPort = ConfigVariableInt('server-port', 4400).getValue()
|
||||
|
||||
# Set the IP or hostname of the server we want to connect to
|
||||
hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
|
||||
|
||||
# Build the URL from the server hostname and port. If your server
|
||||
# uses another protocol then http you should change it accordingly.
|
||||
# Make sure to pass the connectMethod to the ClientRepository.__init__
|
||||
# call too. Available connection methods are:
|
||||
# self.CM_HTTP, self.CM_NET and self.CM_NATIVE
|
||||
self.url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
|
||||
|
||||
# Attempt a connection to the server
|
||||
self.connect([self.url],
|
||||
successCallback = self.connectSuccess,
|
||||
failureCallback = self.connectFailure)
|
||||
|
||||
def lostConnection(self):
|
||||
""" This should be overridden by a derived class to handle an
|
||||
unexpectedly lost connection to the gameserver. """
|
||||
# Handle the disconnection from the server. This can be a reconnect,
|
||||
# simply exiting the application or anything else.
|
||||
exit()
|
||||
|
||||
def connectFailure(self, statusCode, statusString):
|
||||
""" Something went wrong """
|
||||
# we could create a reconnect task to try and connect again.
|
||||
exit()
|
||||
|
||||
def connectSuccess(self):
|
||||
""" Successfully connected. But we still can't really do
|
||||
anything until we've got the doID range. """
|
||||
|
||||
# Make sure we have interest in the by the AIRepository defined
|
||||
# TimeManager zone, so we always see it even if we switch to
|
||||
# another zone.
|
||||
self.setInterestZones([1])
|
||||
|
||||
# We must wait for the TimeManager to be fully created and
|
||||
# synced before we can enter another zone and wait for the
|
||||
# game object. The uniqueName is important that we get the
|
||||
# correct, our sync message from the TimeManager and not
|
||||
# accidentaly a message from another client
|
||||
self.acceptOnce(self.uniqueName('gotTimeSync'), self.syncReady)
|
||||
|
||||
def syncReady(self):
|
||||
""" Now we've got the TimeManager manifested, and we're in
|
||||
sync with the server time. Now we can enter the world. Check
|
||||
to see if we've received our doIdBase yet. """
|
||||
|
||||
# This method checks whether we actually have a valid doID range
|
||||
# to create distributed objects yet
|
||||
if self.haveCreateAuthority():
|
||||
# we already have one
|
||||
self.gotCreateReady()
|
||||
else:
|
||||
# Not yet, keep waiting a bit longer.
|
||||
self.accept(self.uniqueName('createReady'), self.gotCreateReady)
|
||||
|
||||
def gotCreateReady(self):
|
||||
""" Ready to enter the world. Expand our interest to include
|
||||
any other zones """
|
||||
|
||||
# This method checks whether we actually have a valid doID range
|
||||
# to create distributed objects yet
|
||||
if not self.haveCreateAuthority():
|
||||
# Not ready yet.
|
||||
return
|
||||
|
||||
# we are ready now, so ignore further createReady events
|
||||
self.ignore(self.uniqueName('createReady'))
|
||||
|
||||
print("Client Ready")
|
||||
base.messenger.send("client-ready")
|
||||
|
||||
# Start the client
|
||||
client = GameClientRepository()
|
||||
|
||||
base.run()
|
34
samples/networking/02-time-synced-connection/server.py
Normal file
34
samples/networking/02-time-synced-connection/server.py
Normal file
@ -0,0 +1,34 @@
|
||||
# all imports needed by the engine itself
|
||||
from direct.showbase.ShowBase import ShowBase
|
||||
|
||||
# all imports needed by the server
|
||||
from direct.distributed.ServerRepository import ServerRepository
|
||||
from panda3d.core import ConfigVariableInt
|
||||
|
||||
from AIRepository import AIRepository
|
||||
|
||||
# initialize the engine
|
||||
base = ShowBase(windowType='none')
|
||||
|
||||
# the main server class
|
||||
class GameServerRepository(ServerRepository):
|
||||
"""The server repository class"""
|
||||
def __init__(self):
|
||||
"""initialise the server class"""
|
||||
|
||||
# get the port number from the configuration file
|
||||
# if it doesn't exist, we use 4400 as the default
|
||||
tcpPort = ConfigVariableInt('server-port', 4400).getValue()
|
||||
|
||||
# list of all needed .dc files
|
||||
dcFileNames = ['../direct.dc']
|
||||
|
||||
# initialise a threaded server on this machine with
|
||||
# the port number and the dc filenames
|
||||
ServerRepository.__init__(self, tcpPort, dcFileNames=dcFileNames, threadedNet=True)
|
||||
|
||||
# start the server
|
||||
GameServerRepository()
|
||||
AIRepository()
|
||||
|
||||
base.run()
|
29
samples/networking/03-distributed-node/AIDGameObject.py
Normal file
29
samples/networking/03-distributed-node/AIDGameObject.py
Normal file
@ -0,0 +1,29 @@
|
||||
from direct.distributed.DistributedObject import DistributedObject
|
||||
|
||||
class AIDGameObject(DistributedObject):
|
||||
""" This class is a DirectObject which will be created and managed by the
|
||||
AI Repository. """
|
||||
|
||||
def __init__(self, cr):
|
||||
DistributedObject.__init__(self, cr)
|
||||
|
||||
def announceGenerate(self):
|
||||
""" The AI has created this object, so we send it's distributed object ID
|
||||
over to the client. That way the client can actually grab the object
|
||||
and use it to communicate with the AI. Alternatively store it in the
|
||||
Client Repository in self.cr """
|
||||
base.messenger.send(self.cr.uniqueName('AIDGameObjectGenerated'), [self.doId])
|
||||
# call the base class method
|
||||
DistributedObject.announceGenerate(self)
|
||||
|
||||
def d_requestDataFromAI(self):
|
||||
""" Request some data from the AI and passing it some data from us. """
|
||||
data = ("Some Data", 1, -1.25)
|
||||
print("Sending game data:", data)
|
||||
self.sendUpdate('messageRoundtripToAI', [data])
|
||||
|
||||
def messageRoundtripToClient(self, data):
|
||||
""" Here we expect the answer from the AI from a previous
|
||||
messageRoundtripToAI call """
|
||||
print("Got Data:", data)
|
||||
print("Roundtrip message complete")
|
25
samples/networking/03-distributed-node/AIDGameObjectAI.py
Normal file
25
samples/networking/03-distributed-node/AIDGameObjectAI.py
Normal file
@ -0,0 +1,25 @@
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
|
||||
class AIDGameObjectAI(DistributedObjectAI):
|
||||
def __init__(self, aiRepository):
|
||||
DistributedObjectAI.__init__(self, aiRepository)
|
||||
|
||||
def messageRoundtripToAI(self, data):
|
||||
""" The client sent us some data to process. So work with it and send
|
||||
changed data back to the requesting client """
|
||||
requesterId = self.air.getAvatarIdFromSender()
|
||||
print("Got client data:", data, "from client with ID", requesterId)
|
||||
|
||||
# do something with the data
|
||||
aiChangedData = (
|
||||
data[0] + " from the AI",
|
||||
data[1] + 1,
|
||||
data[2])
|
||||
|
||||
print("Sending modified game data back:", aiChangedData)
|
||||
self.d_messageRoundtripToClient(aiChangedData, requesterId)
|
||||
|
||||
def d_messageRoundtripToClient(self, data, requesterId):
|
||||
""" Send the given data to the requesting client """
|
||||
print("Send message to back to:", requesterId)
|
||||
self.sendUpdateToAvatarId(requesterId, 'messageRoundtripToClient', [data])
|
86
samples/networking/03-distributed-node/AIRepository.py
Normal file
86
samples/networking/03-distributed-node/AIRepository.py
Normal file
@ -0,0 +1,86 @@
|
||||
from direct.distributed.ClientRepository import ClientRepository
|
||||
from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
|
||||
from AIDGameObjectAI import AIDGameObjectAI
|
||||
|
||||
class AIRepository(ClientRepository):
|
||||
def __init__(self):
|
||||
""" The AI Repository usually lives on a server and is responsible for
|
||||
server side logic that will handle game objects """
|
||||
|
||||
# List of all dc files that are of interest to this AI Repository
|
||||
dcFileNames = ['../direct.dc', 'sample.dc']
|
||||
|
||||
# Initialize the repository. We pass it the dc files and as this is an
|
||||
# AI repository the dcSuffix AI. This will make sure any later calls to
|
||||
# createDistributedObject will use the correct version.
|
||||
# The connectMethod
|
||||
ClientRepository.__init__(
|
||||
self,
|
||||
dcFileNames = dcFileNames,
|
||||
dcSuffix = 'AI',
|
||||
threadedNet = True)
|
||||
|
||||
# Set the same port as configured on the server to be able to connect
|
||||
# to it
|
||||
tcpPort = ConfigVariableInt('server-port', 4400).getValue()
|
||||
|
||||
# Set the IP or hostname of the server we want to connect to
|
||||
hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
|
||||
|
||||
# Build the URL from the server hostname and port. If your server
|
||||
# doesn't use http you should change it accordingly. Make sure to pass
|
||||
# the connectMethod to the ClientRepository.__init__ call too.
|
||||
# Available connection methods are:
|
||||
# self.CM_HTTP, self.CM_NET and self.CM_NATIVE
|
||||
url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
|
||||
|
||||
# Attempt a connection to the server
|
||||
self.connect([url],
|
||||
successCallback = self.connectSuccess,
|
||||
failureCallback = self.connectFailure)
|
||||
|
||||
def connectFailure(self, statusCode, statusString):
|
||||
""" something went wrong """
|
||||
print("Couldn't connect. Make sure to run server.py first!")
|
||||
raise(StandardError, statusString)
|
||||
|
||||
def connectSuccess(self):
|
||||
""" Successfully connected. But we still can't really do
|
||||
anything until we've got the doID range. """
|
||||
# The Client Repository will throw this event as soon as it has a doID
|
||||
# range and would be able to create distributed objects
|
||||
self.accept('createReady', self.gotCreateReady)
|
||||
|
||||
def lostConnection(self):
|
||||
""" This should be overridden by a derived class to handle an
|
||||
unexpectedly lost connection to the gameserver. """
|
||||
exit()
|
||||
|
||||
def gotCreateReady(self):
|
||||
""" Now we're ready to go! """
|
||||
|
||||
# This method checks whether we actually have a valid doID range
|
||||
# to create distributed objects yet
|
||||
if not self.haveCreateAuthority():
|
||||
# Not ready yet.
|
||||
return
|
||||
|
||||
# we are ready now, so ignore further createReady events
|
||||
self.ignore('createReady')
|
||||
|
||||
# Create a Distributed Object by name. This will look up the object in
|
||||
# the dc files passed to the repository earlier
|
||||
self.timeManager = self.createDistributedObject(
|
||||
className = 'TimeManagerAI', # The Name of the Class we want to initialize
|
||||
zoneId = 1) # The Zone this Object will live in
|
||||
|
||||
self.gameDistObject = self.createDistributedObject(
|
||||
className = 'AIDGameObjectAI',
|
||||
zoneId = 2)
|
||||
|
||||
print("AI Repository Ready")
|
||||
|
||||
def deallocateChannel(self, doID):
|
||||
""" This method will be called whenever a client disconnects from the
|
||||
server. The given doID is the ID of the client who left us. """
|
||||
print("Client left us: ", doID)
|
130
samples/networking/03-distributed-node/ClientRepository.py
Normal file
130
samples/networking/03-distributed-node/ClientRepository.py
Normal file
@ -0,0 +1,130 @@
|
||||
from direct.distributed.ClientRepository import ClientRepository
|
||||
from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
|
||||
from DGameObject import DGameObject
|
||||
|
||||
class GameClientRepository(ClientRepository):
|
||||
|
||||
def __init__(self):
|
||||
dcFileNames = ['../direct.dc', 'sample.dc']
|
||||
|
||||
# a distributed object of our game.
|
||||
self.distributedObject = None
|
||||
self.aiDGameObect = None
|
||||
|
||||
ClientRepository.__init__(
|
||||
self,
|
||||
dcFileNames = dcFileNames,
|
||||
threadedNet = True)
|
||||
|
||||
# Set the same port as configured on the server to be able to connect
|
||||
# to it
|
||||
tcpPort = ConfigVariableInt('server-port', 4400).getValue()
|
||||
|
||||
# Set the IP or hostname of the server we want to connect to
|
||||
hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
|
||||
|
||||
# Build the URL from the server hostname and port. If your server
|
||||
# uses another protocol then http you should change it accordingly.
|
||||
# Make sure to pass the connectMethod to the ClientRepository.__init__
|
||||
# call too. Available connection methods are:
|
||||
# self.CM_HTTP, self.CM_NET and self.CM_NATIVE
|
||||
self.url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
|
||||
|
||||
# Attempt a connection to the server
|
||||
self.connect([self.url],
|
||||
successCallback = self.connectSuccess,
|
||||
failureCallback = self.connectFailure)
|
||||
|
||||
def lostConnection(self):
|
||||
""" This should be overridden by a derived class to handle an
|
||||
unexpectedly lost connection to the gameserver. """
|
||||
# Handle the disconnection from the server. This can be a reconnect,
|
||||
# simply exiting the application or anything else.
|
||||
exit()
|
||||
|
||||
def connectFailure(self, statusCode, statusString):
|
||||
""" Something went wrong """
|
||||
exit()
|
||||
|
||||
def connectSuccess(self):
|
||||
""" Successfully connected. But we still can't really do
|
||||
anything until we've got the doID range. """
|
||||
|
||||
# Make sure we have interest in the by the AIRepository defined
|
||||
# TimeManager zone, so we always see it even if we switch to
|
||||
# another zone.
|
||||
self.setInterestZones([1])
|
||||
|
||||
# We must wait for the TimeManager to be fully created and
|
||||
# synced before we can enter another zone and wait for the
|
||||
# game object. The uniqueName is important that we get the
|
||||
# correct, our sync message from the TimeManager and not
|
||||
# accidentaly a message from another client
|
||||
self.acceptOnce(self.uniqueName('gotTimeSync'), self.syncReady)
|
||||
|
||||
def syncReady(self):
|
||||
""" Now we've got the TimeManager manifested, and we're in
|
||||
sync with the server time. Now we can enter the world. Check
|
||||
to see if we've received our doIdBase yet. """
|
||||
|
||||
# This method checks whether we actually have a valid doID range
|
||||
# to create distributed objects yet
|
||||
if self.haveCreateAuthority():
|
||||
# we already have one
|
||||
self.gotCreateReady()
|
||||
else:
|
||||
# Not yet, keep waiting a bit longer.
|
||||
self.accept(self.uniqueName('createReady'), self.gotCreateReady)
|
||||
|
||||
def gotCreateReady(self):
|
||||
""" Ready to enter the world. Expand our interest to include
|
||||
any other zones """
|
||||
|
||||
# This method checks whether we actually have a valid doID range
|
||||
# to create distributed objects yet
|
||||
if not self.haveCreateAuthority():
|
||||
# Not ready yet.
|
||||
return
|
||||
|
||||
# we are ready now, so ignore further createReady events
|
||||
self.ignore(self.uniqueName('createReady'))
|
||||
|
||||
self.join()
|
||||
|
||||
print("Client Ready")
|
||||
|
||||
def join(self):
|
||||
""" Join a game/room/whatever """
|
||||
self.accept(self.uniqueName('AIDGameObjectGenerated'), self.aiDGameObectGenerated)
|
||||
|
||||
# set our intersted zones to let the client see all distributed obects
|
||||
# in those zones
|
||||
self.setInterestZones([1, 2])
|
||||
|
||||
# Manifest a object on the server. The object will have our "base" doId.
|
||||
self.distributedObject = DGameObject(self)
|
||||
self.createDistributedObject(
|
||||
distObj = self.distributedObject,
|
||||
zoneId = 2)
|
||||
|
||||
base.messenger.send("client-joined")
|
||||
print("Joined")
|
||||
|
||||
def aiDGameObectGenerated(self, doId):
|
||||
print("AIDGameObect was generated")
|
||||
self.aiDGameObect = self.doId2do[doId]
|
||||
|
||||
def sendGameData(self):
|
||||
if not self.distributedObject: return
|
||||
|
||||
print("send game data")
|
||||
|
||||
# send a message to the server
|
||||
self.distributedObject.d_sendGameData()
|
||||
|
||||
def sendRoundtripToAI(self):
|
||||
if not self.aiDGameObect: return
|
||||
|
||||
print("Initiate roundtrip message to AI Server")
|
||||
|
||||
self.aiDGameObect.d_requestDataFromAI()
|
15
samples/networking/03-distributed-node/DGameObject.py
Normal file
15
samples/networking/03-distributed-node/DGameObject.py
Normal file
@ -0,0 +1,15 @@
|
||||
from direct.distributed.DistributedObject import DistributedObject
|
||||
|
||||
class DGameObject(DistributedObject):
|
||||
def __init__(self, cr):
|
||||
DistributedObject.__init__(self, cr)
|
||||
|
||||
def sendGameData(self, data):
|
||||
""" Method that can be called from the clients with an sendUpdate call """
|
||||
print(data)
|
||||
|
||||
def d_sendGameData(self):
|
||||
""" A method to send an update message to the server. The d_ stands
|
||||
for distributed """
|
||||
# send the message to the server
|
||||
self.sendUpdate('sendGameData', [('ValueA', 123, 1.25)])
|
20
samples/networking/03-distributed-node/ServerRepository.py
Normal file
20
samples/networking/03-distributed-node/ServerRepository.py
Normal file
@ -0,0 +1,20 @@
|
||||
# all imports needed by the server
|
||||
from direct.distributed.ServerRepository import ServerRepository
|
||||
from panda3d.core import ConfigVariableInt
|
||||
|
||||
# the main server class
|
||||
class GameServerRepository(ServerRepository):
|
||||
"""The server repository class"""
|
||||
def __init__(self):
|
||||
"""initialise the server class"""
|
||||
|
||||
# get the port number from the configuration file
|
||||
# if it doesn't exist, we use 4400 as the default
|
||||
tcpPort = ConfigVariableInt('server-port', 4400).getValue()
|
||||
|
||||
# list of all needed .dc files
|
||||
dcFileNames = ['../direct.dc', 'sample.dc']
|
||||
|
||||
# initialise a threaded server on this machine with
|
||||
# the port number and the dc filenames
|
||||
ServerRepository.__init__(self, tcpPort, dcFileNames=dcFileNames, threadedNet=True)
|
136
samples/networking/03-distributed-node/client.py
Normal file
136
samples/networking/03-distributed-node/client.py
Normal file
@ -0,0 +1,136 @@
|
||||
# all imports needed by the engine itself
|
||||
from direct.showbase.ShowBase import ShowBase
|
||||
|
||||
# initialize the engine
|
||||
base = ShowBase()
|
||||
|
||||
# initialize the client
|
||||
|
||||
from direct.distributed.ClientRepository import ClientRepository
|
||||
from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
|
||||
from direct.gui.OnscreenText import OnscreenText
|
||||
from panda3d.core import TextNode
|
||||
|
||||
|
||||
base.accept("escape", exit)
|
||||
|
||||
# Function to put instructions on the screen.
|
||||
def addInstructions(pos, msg):
|
||||
return OnscreenText(text=msg, style=1, fg=(0, 0, 0, 1), shadow=(1, 1, 1, 1),
|
||||
parent=base.a2dTopLeft, align=TextNode.ALeft,
|
||||
pos=(0.08, -pos - 0.04), scale=.06)
|
||||
|
||||
# Function to put title on the screen.
|
||||
def addTitle(text):
|
||||
return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08,
|
||||
parent=base.a2dBottomRight, align=TextNode.ARight,
|
||||
fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1))
|
||||
|
||||
title = addTitle("Panda3D: Tutorial - Distributed Network (NOT CONNECTED)")
|
||||
inst1 = addInstructions(0.06, "esc: Close the client")
|
||||
inst2 = addInstructions(0.12, "See console output")
|
||||
|
||||
def setConnectedMessage():
|
||||
title["text"] = "Panda3D: Tutorial - Distributed Network (CONNECTED)"
|
||||
|
||||
base.accept("client-ready", setConnectedMessage)
|
||||
|
||||
#
|
||||
# CLIENT
|
||||
#
|
||||
|
||||
class GameClientRepository(ClientRepository):
|
||||
|
||||
def __init__(self):
|
||||
dcFileNames = ['../direct.dc']
|
||||
|
||||
# a distributed object of our game.
|
||||
self.distributedObject = None
|
||||
self.aiDGameObect = None
|
||||
|
||||
ClientRepository.__init__(
|
||||
self,
|
||||
dcFileNames = dcFileNames,
|
||||
threadedNet = True)
|
||||
|
||||
# Set the same port as configured on the server to be able to connect
|
||||
# to it
|
||||
tcpPort = ConfigVariableInt('server-port', 4400).getValue()
|
||||
|
||||
# Set the IP or hostname of the server we want to connect to
|
||||
hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
|
||||
|
||||
# Build the URL from the server hostname and port. If your server
|
||||
# uses another protocol then http you should change it accordingly.
|
||||
# Make sure to pass the connectMethod to the ClientRepository.__init__
|
||||
# call too. Available connection methods are:
|
||||
# self.CM_HTTP, self.CM_NET and self.CM_NATIVE
|
||||
self.url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
|
||||
|
||||
# Attempt a connection to the server
|
||||
self.connect([self.url],
|
||||
successCallback = self.connectSuccess,
|
||||
failureCallback = self.connectFailure)
|
||||
|
||||
def lostConnection(self):
|
||||
""" This should be overridden by a derived class to handle an
|
||||
unexpectedly lost connection to the gameserver. """
|
||||
# Handle the disconnection from the server. This can be a reconnect,
|
||||
# simply exiting the application or anything else.
|
||||
exit()
|
||||
|
||||
def connectFailure(self, statusCode, statusString):
|
||||
""" Something went wrong """
|
||||
# we could create a reconnect task to try and connect again.
|
||||
exit()
|
||||
|
||||
def connectSuccess(self):
|
||||
""" Successfully connected. But we still can't really do
|
||||
anything until we've got the doID range. """
|
||||
|
||||
# Make sure we have interest in the by the AIRepository defined
|
||||
# TimeManager zone, so we always see it even if we switch to
|
||||
# another zone.
|
||||
self.setInterestZones([1])
|
||||
|
||||
# We must wait for the TimeManager to be fully created and
|
||||
# synced before we can enter another zone and wait for the
|
||||
# game object. The uniqueName is important that we get the
|
||||
# correct, our sync message from the TimeManager and not
|
||||
# accidentaly a message from another client
|
||||
self.acceptOnce(self.uniqueName('gotTimeSync'), self.syncReady)
|
||||
|
||||
def syncReady(self):
|
||||
""" Now we've got the TimeManager manifested, and we're in
|
||||
sync with the server time. Now we can enter the world. Check
|
||||
to see if we've received our doIdBase yet. """
|
||||
|
||||
# This method checks whether we actually have a valid doID range
|
||||
# to create distributed objects yet
|
||||
if self.haveCreateAuthority():
|
||||
# we already have one
|
||||
self.gotCreateReady()
|
||||
else:
|
||||
# Not yet, keep waiting a bit longer.
|
||||
self.accept(self.uniqueName('createReady'), self.gotCreateReady)
|
||||
|
||||
def gotCreateReady(self):
|
||||
""" Ready to enter the world. Expand our interest to include
|
||||
any other zones """
|
||||
|
||||
# This method checks whether we actually have a valid doID range
|
||||
# to create distributed objects yet
|
||||
if not self.haveCreateAuthority():
|
||||
# Not ready yet.
|
||||
return
|
||||
|
||||
# we are ready now, so ignore further createReady events
|
||||
self.ignore(self.uniqueName('createReady'))
|
||||
|
||||
print("Client Ready")
|
||||
base.messenger.send("client-ready")
|
||||
|
||||
# Start the client
|
||||
client = GameClientRepository()
|
||||
|
||||
base.run()
|
17
samples/networking/03-distributed-node/sample.dc
Executable file
17
samples/networking/03-distributed-node/sample.dc
Executable file
@ -0,0 +1,17 @@
|
||||
import DGameObject
|
||||
import AIDGameObject/AI
|
||||
|
||||
struct gameDataModel {
|
||||
string value_a;
|
||||
uint8 value_b;
|
||||
int8 value_c/100;
|
||||
}
|
||||
|
||||
dclass DGameObject: DistributedObject {
|
||||
sendGameData(gameDataModel data) broadcast;
|
||||
};
|
||||
|
||||
dclass AIDGameObject: DistributedObject {
|
||||
messageRoundtripToAI(gameDataModel data) p2p;
|
||||
messageRoundtripToClient(gameDataModel data) p2p;
|
||||
}
|
18
samples/networking/03-distributed-node/server.py
Normal file
18
samples/networking/03-distributed-node/server.py
Normal file
@ -0,0 +1,18 @@
|
||||
# all imports needed by the engine itself
|
||||
from direct.showbase.ShowBase import ShowBase
|
||||
|
||||
# import our own repositories
|
||||
from ServerRepository import GameServerRepository
|
||||
from AIRepository import AIRepository
|
||||
|
||||
# initialize the engine
|
||||
base = ShowBase(windowType='none')
|
||||
|
||||
# instantiate the server
|
||||
GameServerRepository()
|
||||
|
||||
# The AI Repository to manage server side (AI) clients
|
||||
AIRepository()
|
||||
|
||||
# start the server
|
||||
base.run()
|
81
samples/networking/04-distributed-model/AIRepository.py
Normal file
81
samples/networking/04-distributed-model/AIRepository.py
Normal file
@ -0,0 +1,81 @@
|
||||
from direct.distributed.ClientRepository import ClientRepository
|
||||
from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
|
||||
|
||||
class AIRepository(ClientRepository):
|
||||
def __init__(self):
|
||||
""" The AI Repository usually lives on a server and is responsible for
|
||||
server side logic that will handle game objects """
|
||||
|
||||
# List of all dc files that are of interest to this AI Repository
|
||||
dcFileNames = ['../direct.dc', 'sample.dc']
|
||||
|
||||
# Initialize the repository. We pass it the dc files and as this is an
|
||||
# AI repository the dcSuffix AI. This will make sure any later calls to
|
||||
# createDistributedObject will use the correct version.
|
||||
# The connectMethod
|
||||
ClientRepository.__init__(
|
||||
self,
|
||||
dcFileNames = dcFileNames,
|
||||
dcSuffix = 'AI',
|
||||
threadedNet = True)
|
||||
|
||||
# Set the same port as configured on the server to be able to connect
|
||||
# to it
|
||||
tcpPort = ConfigVariableInt('server-port', 4400).getValue()
|
||||
|
||||
# Set the IP or hostname of the server we want to connect to
|
||||
hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
|
||||
|
||||
# Build the URL from the server hostname and port. If your server
|
||||
# doesn't use http you should change it accordingly. Make sure to pass
|
||||
# the connectMethod to the ClientRepository.__init__ call too.
|
||||
# Available connection methods are:
|
||||
# self.CM_HTTP, self.CM_NET and self.CM_NATIVE
|
||||
url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
|
||||
|
||||
# Attempt a connection to the server
|
||||
self.connect([url],
|
||||
successCallback = self.connectSuccess,
|
||||
failureCallback = self.connectFailure)
|
||||
|
||||
def connectFailure(self, statusCode, statusString):
|
||||
""" something went wrong """
|
||||
print("Couldn't connect. Make sure to run server.py first!")
|
||||
raise(StandardError, statusString)
|
||||
|
||||
def connectSuccess(self):
|
||||
""" Successfully connected. But we still can't really do
|
||||
anything until we've got the doID range. """
|
||||
# The Client Repository will throw this event as soon as it has a doID
|
||||
# range and would be able to create distributed objects
|
||||
self.accept('createReady', self.gotCreateReady)
|
||||
|
||||
def lostConnection(self):
|
||||
""" This should be overridden by a derived class to handle an
|
||||
unexpectedly lost connection to the gameserver. """
|
||||
exit()
|
||||
|
||||
def gotCreateReady(self):
|
||||
""" Now we're ready to go! """
|
||||
|
||||
# This method checks whether we actually have a valid doID range
|
||||
# to create distributed objects yet
|
||||
if not self.haveCreateAuthority():
|
||||
# Not ready yet.
|
||||
return
|
||||
|
||||
# we are ready now, so ignore further createReady events
|
||||
self.ignore('createReady')
|
||||
|
||||
# Create a Distributed Object by name. This will look up the object in
|
||||
# the dc files passed to the repository earlier
|
||||
self.timeManager = self.createDistributedObject(
|
||||
className = 'TimeManagerAI', # The Name of the Class we want to initialize
|
||||
zoneId = 1) # The Zone this Object will live in
|
||||
|
||||
print("AI Repository Ready")
|
||||
|
||||
def deallocateChannel(self, doID):
|
||||
""" This method will be called whenever a client disconnects from the
|
||||
server. The given doID is the ID of the client who left us. """
|
||||
print("Client left us: ", doID)
|
116
samples/networking/04-distributed-model/ClientRepository.py
Normal file
116
samples/networking/04-distributed-model/ClientRepository.py
Normal file
@ -0,0 +1,116 @@
|
||||
from direct.distributed.ClientRepository import ClientRepository
|
||||
from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
|
||||
from random import random
|
||||
|
||||
class GameClientRepository(ClientRepository):
|
||||
|
||||
def __init__(self):
|
||||
dcFileNames = ['../direct.dc', 'sample.dc']
|
||||
|
||||
ClientRepository.__init__(
|
||||
self,
|
||||
dcFileNames = dcFileNames,
|
||||
threadedNet = True)
|
||||
|
||||
# Set the same port as configured on the server to be able to connect
|
||||
# to it
|
||||
tcpPort = ConfigVariableInt('server-port', 4400).getValue()
|
||||
|
||||
# Set the IP or hostname of the server we want to connect to
|
||||
hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
|
||||
|
||||
# Build the URL from the server hostname and port. If your server
|
||||
# uses another protocol then http you should change it accordingly.
|
||||
# Make sure to pass the connectMethod to the ClientRepository.__init__
|
||||
# call too. Available connection methods are:
|
||||
# self.CM_HTTP, self.CM_NET and self.CM_NATIVE
|
||||
self.url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
|
||||
|
||||
# Attempt a connection to the server
|
||||
self.connect([self.url],
|
||||
successCallback = self.connectSuccess,
|
||||
failureCallback = self.connectFailure)
|
||||
|
||||
def lostConnection(self):
|
||||
""" This should be overridden by a derived class to handle an
|
||||
unexpectedly lost connection to the gameserver. """
|
||||
# Handle the disconnection from the server. This can be a reconnect,
|
||||
# simply exiting the application or anything else.
|
||||
exit()
|
||||
|
||||
def connectFailure(self, statusCode, statusString):
|
||||
""" Something went wrong """
|
||||
exit()
|
||||
|
||||
def connectSuccess(self):
|
||||
""" Successfully connected. But we still can't really do
|
||||
anything until we've got the doID range. """
|
||||
|
||||
# Make sure we have interest in the by the AIRepository defined
|
||||
# TimeManager zone, so we always see it even if we switch to
|
||||
# another zone.
|
||||
self.setInterestZones([1])
|
||||
|
||||
# We must wait for the TimeManager to be fully created and
|
||||
# synced before we can enter another zone and wait for the
|
||||
# game object. The uniqueName is important that we get the
|
||||
# correct, our sync message from the TimeManager and not
|
||||
# accidentaly a message from another client
|
||||
self.acceptOnce(self.uniqueName('gotTimeSync'), self.syncReady)
|
||||
|
||||
def syncReady(self):
|
||||
""" Now we've got the TimeManager manifested, and we're in
|
||||
sync with the server time. Now we can enter the world. Check
|
||||
to see if we've received our doIdBase yet. """
|
||||
|
||||
# This method checks whether we actually have a valid doID range
|
||||
# to create distributed objects yet
|
||||
if self.haveCreateAuthority():
|
||||
# we already have one
|
||||
self.gotCreateReady()
|
||||
else:
|
||||
# Not yet, keep waiting a bit longer.
|
||||
self.accept(self.uniqueName('createReady'), self.gotCreateReady)
|
||||
|
||||
def gotCreateReady(self):
|
||||
""" Ready to enter the world. Expand our interest to include
|
||||
any other zones """
|
||||
|
||||
# This method checks whether we actually have a valid doID range
|
||||
# to create distributed objects yet
|
||||
if not self.haveCreateAuthority():
|
||||
# Not ready yet.
|
||||
return
|
||||
|
||||
# we are ready now, so ignore further createReady events
|
||||
self.ignore(self.uniqueName('createReady'))
|
||||
|
||||
self.join()
|
||||
|
||||
print("Client Ready")
|
||||
|
||||
def join(self):
|
||||
""" Join a game/room/whatever """
|
||||
# set our intersted zones to let the client see all distributed obects
|
||||
# in those zones
|
||||
self.setInterestZones([1, 2])
|
||||
|
||||
# Manifest a object on the server. The object will have our "base" doId.
|
||||
self.myDistributedModel = self.createDistributedObject(
|
||||
className = "DModel",
|
||||
zoneId = 2)
|
||||
|
||||
x = random()
|
||||
z = random()
|
||||
# set position for this local client
|
||||
self.myDistributedModel.setPos(x, 10, z)
|
||||
# make sure already connected clients will get notified of the
|
||||
# position by calling the distributred (d_*) version of the method
|
||||
self.myDistributedModel.d_setPos(x, 10, z)
|
||||
|
||||
base.messenger.send("client-joined")
|
||||
print("Joined")
|
||||
|
||||
def modelReady(self, doId):
|
||||
print("AIDGameObect was generated")
|
||||
self.aiDGameObect = self.doId2do[doId]
|
61
samples/networking/04-distributed-model/DModel.py
Normal file
61
samples/networking/04-distributed-model/DModel.py
Normal file
@ -0,0 +1,61 @@
|
||||
from direct.distributed.DistributedNode import DistributedNode
|
||||
|
||||
class DModel(DistributedNode):
|
||||
def __init__(self, cr):
|
||||
DistributedNode.__init__(self, cr)
|
||||
|
||||
# Load up the visible representation of this avatar.
|
||||
self.model = loader.loadModel('smiley.egg')
|
||||
self.model.reparentTo(self)
|
||||
|
||||
def announceGenerate(self):
|
||||
""" This method is called after generate(), after all of the
|
||||
required fields have been filled in. At the time of this call,
|
||||
the distributed object is ready for use. """
|
||||
|
||||
DistributedNode.announceGenerate(self)
|
||||
|
||||
# Now that the object has been fully manifested, we can parent
|
||||
# it into the scene.
|
||||
self.reparentTo(render)
|
||||
|
||||
def disable(self):
|
||||
""" This method is called when the object is removed from the
|
||||
scene, for instance because it left the zone. It is balanced
|
||||
against generate(): for each generate(), there will be a
|
||||
corresponding disable(). Everything that was done in
|
||||
generate() or announceGenerate() should be undone in disable().
|
||||
|
||||
After a disable(), the object might be cached in memory in case
|
||||
it will eventually reappear. The DistributedObject should be
|
||||
prepared to receive another generate() for an object that has
|
||||
already received disable().
|
||||
|
||||
Note that the above is only strictly true for *cacheable*
|
||||
objects. Most objects are, by default, non-cacheable; you
|
||||
have to call obj.setCacheable(True) (usually in the
|
||||
constructor) to make it cacheable. Until you do this, your
|
||||
non-cacheable object will always receive a delete() whenever
|
||||
it receives a disable(), and it will never be stored in a
|
||||
cache.
|
||||
"""
|
||||
|
||||
# Take it out of the scene graph.
|
||||
self.detachNode()
|
||||
|
||||
DistributedNode.disable(self)
|
||||
|
||||
def delete(self):
|
||||
""" This method is called after disable() when the object is to
|
||||
be completely removed, for instance because the other user
|
||||
logged off. We will not expect to see this object again; it
|
||||
will not be cached. This is stronger than disable(), and the
|
||||
object may remove any structures it needs to in order to allow
|
||||
it to be completely deleted from memory. This balances against
|
||||
__init__(): every DistributedObject that is created will
|
||||
eventually get delete() called for it exactly once. """
|
||||
|
||||
# Clean out self.model, so we don't have a circular reference.
|
||||
self.model = None
|
||||
|
||||
DistributedNode.delete(self)
|
20
samples/networking/04-distributed-model/ServerRepository.py
Normal file
20
samples/networking/04-distributed-model/ServerRepository.py
Normal file
@ -0,0 +1,20 @@
|
||||
# all imports needed by the server
|
||||
from direct.distributed.ServerRepository import ServerRepository
|
||||
from panda3d.core import ConfigVariableInt
|
||||
|
||||
# the main server class
|
||||
class GameServerRepository(ServerRepository):
|
||||
"""The server repository class"""
|
||||
def __init__(self):
|
||||
"""initialise the server class"""
|
||||
|
||||
# get the port number from the configuration file
|
||||
# if it doesn't exist, we use 4400 as the default
|
||||
tcpPort = ConfigVariableInt('server-port', 4400).getValue()
|
||||
|
||||
# list of all needed .dc files
|
||||
dcFileNames = ['../direct.dc', 'sample.dc']
|
||||
|
||||
# initialise a threaded server on this machine with
|
||||
# the port number and the dc filenames
|
||||
ServerRepository.__init__(self, tcpPort, dcFileNames=dcFileNames, threadedNet=True)
|
43
samples/networking/04-distributed-model/client.py
Normal file
43
samples/networking/04-distributed-model/client.py
Normal file
@ -0,0 +1,43 @@
|
||||
# all imports needed by the engine itself
|
||||
from direct.showbase.ShowBase import ShowBase
|
||||
|
||||
# import our own repositories
|
||||
from ClientRepository import GameClientRepository
|
||||
|
||||
# initialize the engine
|
||||
base = ShowBase()
|
||||
|
||||
# initialize the client
|
||||
client = GameClientRepository()
|
||||
|
||||
base.accept("escape", exit)
|
||||
|
||||
|
||||
|
||||
|
||||
from direct.gui.OnscreenText import OnscreenText
|
||||
from panda3d.core import TextNode
|
||||
|
||||
# Function to put instructions on the screen.
|
||||
def addInstructions(pos, msg):
|
||||
return OnscreenText(text=msg, style=1, fg=(0, 0, 0, 1), shadow=(1, 1, 1, 1),
|
||||
parent=base.a2dTopLeft, align=TextNode.ALeft,
|
||||
pos=(0.08, -pos - 0.04), scale=.06)
|
||||
|
||||
# Function to put title on the screen.
|
||||
def addTitle(text):
|
||||
return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08,
|
||||
parent=base.a2dBottomRight, align=TextNode.ARight,
|
||||
fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1))
|
||||
|
||||
title = addTitle("Panda3D: Tutorial - Distributed Network (NOT CONNECTED)")
|
||||
inst1 = addInstructions(0.06, "esc: Close the client")
|
||||
inst2 = addInstructions(0.12, "See console output")
|
||||
|
||||
def setConnectedMessage():
|
||||
title["text"] = "Panda3D: Tutorial - Distributed Network (CONNECTED)"
|
||||
|
||||
base.accept("client-joined", setConnectedMessage)
|
||||
|
||||
# start the client
|
||||
base.run()
|
3
samples/networking/04-distributed-model/sample.dc
Executable file
3
samples/networking/04-distributed-model/sample.dc
Executable file
@ -0,0 +1,3 @@
|
||||
import DModel
|
||||
|
||||
dclass DModel: DistributedNode { }
|
18
samples/networking/04-distributed-model/server.py
Normal file
18
samples/networking/04-distributed-model/server.py
Normal file
@ -0,0 +1,18 @@
|
||||
# all imports needed by the engine itself
|
||||
from direct.showbase.ShowBase import ShowBase
|
||||
|
||||
# import our own repositories
|
||||
from ServerRepository import GameServerRepository
|
||||
from AIRepository import AIRepository
|
||||
|
||||
# initialize the engine
|
||||
base = ShowBase(windowType='none')
|
||||
|
||||
# instantiate the server
|
||||
GameServerRepository()
|
||||
|
||||
# The AI Repository to manage server side (AI) clients
|
||||
AIRepository()
|
||||
|
||||
# start the server
|
||||
base.run()
|
81
samples/networking/05-small-chat/AIRepository.py
Normal file
81
samples/networking/05-small-chat/AIRepository.py
Normal file
@ -0,0 +1,81 @@
|
||||
from direct.distributed.ClientRepository import ClientRepository
|
||||
from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
|
||||
|
||||
class AIRepository(ClientRepository):
|
||||
def __init__(self):
|
||||
""" The AI Repository usually lives on a server and is responsible for
|
||||
server side logic that will handle game objects """
|
||||
|
||||
# List of all dc files that are of interest to this AI Repository
|
||||
dcFileNames = ['../direct.dc', 'sample.dc']
|
||||
|
||||
# Initialize the repository. We pass it the dc files and as this is an
|
||||
# AI repository the dcSuffix AI. This will make sure any later calls to
|
||||
# createDistributedObject will use the correct version.
|
||||
# The connectMethod
|
||||
ClientRepository.__init__(
|
||||
self,
|
||||
dcFileNames = dcFileNames,
|
||||
dcSuffix = 'AI',
|
||||
threadedNet = True)
|
||||
|
||||
# Set the same port as configured on the server to be able to connect
|
||||
# to it
|
||||
tcpPort = ConfigVariableInt('server-port', 4400).getValue()
|
||||
|
||||
# Set the IP or hostname of the server we want to connect to
|
||||
hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
|
||||
|
||||
# Build the URL from the server hostname and port. If your server
|
||||
# doesn't use http you should change it accordingly. Make sure to pass
|
||||
# the connectMethod to the ClientRepository.__init__ call too.
|
||||
# Available connection methods are:
|
||||
# self.CM_HTTP, self.CM_NET and self.CM_NATIVE
|
||||
url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
|
||||
|
||||
# Attempt a connection to the server
|
||||
self.connect([url],
|
||||
successCallback = self.connectSuccess,
|
||||
failureCallback = self.connectFailure)
|
||||
|
||||
def connectFailure(self, statusCode, statusString):
|
||||
""" something went wrong """
|
||||
print("Couldn't connect. Make sure to run server.py first!")
|
||||
raise(StandardError, statusString)
|
||||
|
||||
def connectSuccess(self):
|
||||
""" Successfully connected. But we still can't really do
|
||||
anything until we've got the doID range. """
|
||||
# The Client Repository will throw this event as soon as it has a doID
|
||||
# range and would be able to create distributed objects
|
||||
self.accept('createReady', self.gotCreateReady)
|
||||
|
||||
def lostConnection(self):
|
||||
""" This should be overridden by a derived class to handle an
|
||||
unexpectedly lost connection to the gameserver. """
|
||||
exit()
|
||||
|
||||
def gotCreateReady(self):
|
||||
""" Now we're ready to go! """
|
||||
|
||||
# This method checks whether we actually have a valid doID range
|
||||
# to create distributed objects yet
|
||||
if not self.haveCreateAuthority():
|
||||
# Not ready yet.
|
||||
return
|
||||
|
||||
# we are ready now, so ignore further createReady events
|
||||
self.ignore('createReady')
|
||||
|
||||
# Create a Distributed Object by name. This will look up the object in
|
||||
# the dc files passed to the repository earlier
|
||||
self.timeManager = self.createDistributedObject(
|
||||
className = 'TimeManagerAI', # The Name of the Class we want to initialize
|
||||
zoneId = 1) # The Zone this Object will live in
|
||||
|
||||
print("AI Repository Ready")
|
||||
|
||||
def deallocateChannel(self, doID):
|
||||
""" This method will be called whenever a client disconnects from the
|
||||
server. The given doID is the ID of the client who left us. """
|
||||
print("Client left us: ", doID)
|
104
samples/networking/05-small-chat/ClientRepository.py
Normal file
104
samples/networking/05-small-chat/ClientRepository.py
Normal file
@ -0,0 +1,104 @@
|
||||
from direct.distributed.ClientRepository import ClientRepository
|
||||
from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
|
||||
from message import Message
|
||||
|
||||
class GameClientRepository(ClientRepository):
|
||||
|
||||
def __init__(self):
|
||||
dcFileNames = ['../direct.dc', 'sample.dc']
|
||||
|
||||
ClientRepository.__init__(
|
||||
self,
|
||||
dcFileNames = dcFileNames,
|
||||
threadedNet = True)
|
||||
|
||||
# Set the same port as configured on the server to be able to connect
|
||||
# to it
|
||||
tcpPort = ConfigVariableInt('server-port', 4400).getValue()
|
||||
|
||||
# Set the IP or hostname of the server we want to connect to
|
||||
hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
|
||||
|
||||
# Build the URL from the server hostname and port. If your server
|
||||
# uses another protocol then http you should change it accordingly.
|
||||
# Make sure to pass the connectMethod to the ClientRepository.__init__
|
||||
# call too. Available connection methods are:
|
||||
# self.CM_HTTP, self.CM_NET and self.CM_NATIVE
|
||||
self.url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
|
||||
|
||||
# Attempt a connection to the server
|
||||
self.connect([self.url],
|
||||
successCallback = self.connectSuccess,
|
||||
failureCallback = self.connectFailure)
|
||||
|
||||
def lostConnection(self):
|
||||
''' This should be overridden by a derived class to handle an
|
||||
unexpectedly lost connection to the gameserver. '''
|
||||
# Handle the disconnection from the server. This can be a reconnect,
|
||||
# simply exiting the application or anything else.
|
||||
exit()
|
||||
|
||||
def connectFailure(self, statusCode, statusString):
|
||||
''' Something went wrong '''
|
||||
# we could create a reconnect task to try and connect again.
|
||||
exit()
|
||||
|
||||
def connectSuccess(self):
|
||||
''' Successfully connected. But we still can't really do
|
||||
anything until we've got the doID range. '''
|
||||
|
||||
# Make sure we have interest in the by the AIRepository defined
|
||||
# TimeManager zone, so we always see it even if we switch to
|
||||
# another zone.
|
||||
self.setInterestZones([1])
|
||||
|
||||
# We must wait for the TimeManager to be fully created and
|
||||
# synced before we can enter another zone and wait for the
|
||||
# game object. The uniqueName is important that we get the
|
||||
# correct, our sync message from the TimeManager and not
|
||||
# accidentaly a message from another client
|
||||
self.acceptOnce(self.uniqueName('gotTimeSync'), self.syncReady)
|
||||
|
||||
def syncReady(self):
|
||||
''' Now we've got the TimeManager manifested, and we're in
|
||||
sync with the server time. Now we can enter the world. Check
|
||||
to see if we've received our doIdBase yet. '''
|
||||
|
||||
# This method checks whether we actually have a valid doID range
|
||||
# to create distributed objects yet
|
||||
if self.haveCreateAuthority():
|
||||
# we already have one
|
||||
self.gotCreateReady()
|
||||
else:
|
||||
# Not yet, keep waiting a bit longer.
|
||||
self.accept(self.uniqueName('createReady'), self.gotCreateReady)
|
||||
|
||||
def gotCreateReady(self):
|
||||
''' Ready to enter the world. Expand our interest to include
|
||||
any other zones '''
|
||||
|
||||
# This method checks whether we actually have a valid doID range
|
||||
# to create distributed objects yet
|
||||
if not self.haveCreateAuthority():
|
||||
# Not ready yet.
|
||||
return
|
||||
|
||||
# we are ready now, so ignore further createReady events
|
||||
self.ignore(self.uniqueName('createReady'))
|
||||
|
||||
# create a instance of the message class
|
||||
msg = Message(self)
|
||||
# and create the distributed Object with it
|
||||
self.createDistributedObject(
|
||||
distObj = msg, zoneId = 1)
|
||||
# save the created Distributed Object
|
||||
# in self.msg for later usage
|
||||
self.msg = msg
|
||||
|
||||
def sendMessage(self, msgText):
|
||||
'''Function to call the send function of the message class,
|
||||
which sends the Message over the Network to the other users'''
|
||||
|
||||
sentText = "{}: {}".format(self.doIdBase, msgText)
|
||||
|
||||
self.msg.b_sendText(sentText)
|
85
samples/networking/05-small-chat/client.py
Executable file
85
samples/networking/05-small-chat/client.py
Executable file
@ -0,0 +1,85 @@
|
||||
from direct.showbase.ShowBase import ShowBase
|
||||
from direct.distributed.ClientRepository import ClientRepository
|
||||
from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
|
||||
from message import Message
|
||||
from ClientRepository import GameClientRepository
|
||||
from direct.gui.DirectGui import DirectButton, DirectEntry, DirectFrame
|
||||
from direct.gui import DirectGuiGlobals as DGG
|
||||
|
||||
# initialize the engine
|
||||
base = ShowBase()
|
||||
|
||||
client = GameClientRepository()
|
||||
|
||||
# Setup the GUI
|
||||
|
||||
# this frame will contain all our GUI elements
|
||||
frameMain = DirectFrame(frameColor = (0, 0, 0, 1))
|
||||
|
||||
def clearText():
|
||||
''' Write an empty string in the textbox '''
|
||||
txt_msg.enterText('')
|
||||
|
||||
def setDefaultText():
|
||||
''' Write the default message in the textbox '''
|
||||
txt_msg.enterText('Your Message')
|
||||
|
||||
def send(args=None):
|
||||
''' Send the text written in the message textbox to the clients '''
|
||||
client.sendMessage(txt_msg.get())
|
||||
txt_msg.enterText('')
|
||||
|
||||
# the Textbox where we write our Messages, which we
|
||||
# want to send over to the other users
|
||||
txt_msg = DirectEntry(
|
||||
initialText = 'Your Message',
|
||||
cursorKeys = True,
|
||||
pos = (-0.9,0,-0.9),
|
||||
scale = 0.1,
|
||||
width = 14,
|
||||
focusInCommand = clearText,
|
||||
focusOutCommand = setDefaultText,
|
||||
command = send,
|
||||
parent = frameMain)
|
||||
|
||||
# a button to initiate the sending of the message
|
||||
btn_send = DirectButton(
|
||||
text = 'Send',
|
||||
pos = (0.75,0,-0.9),
|
||||
scale = 0.15,
|
||||
command = send,
|
||||
parent = frameMain)
|
||||
|
||||
# This will show all sent messages
|
||||
txt_messages = DirectEntry(
|
||||
cursorKeys = False,
|
||||
pos = (-0.8,0,0.5),
|
||||
scale = 0.1,
|
||||
width = 14,
|
||||
numLines = 10,
|
||||
state = DGG.DISABLED,
|
||||
parent = frameMain)
|
||||
|
||||
# Function which will write the given text in the
|
||||
# textbox, where we store all send and recieved messages.
|
||||
# This Function will only write the last 10 messages and
|
||||
# cut of the erlier messages
|
||||
def setText(messageText):
|
||||
# get all messages from the textbox
|
||||
parts = txt_messages.get().split('\n')
|
||||
if len(parts) >= 10:
|
||||
cutParts = ''
|
||||
# as the textbox can only hold 10 lines cut out the first entry
|
||||
for i in range(1,len(parts)):
|
||||
cutParts += parts[i] + '\n'
|
||||
txt_messages.enterText(cutParts + messageText)
|
||||
else:
|
||||
txt_messages.enterText(txt_messages.get() + '\n' + messageText)
|
||||
|
||||
# create a DirectObject instance, which will then catch the events and
|
||||
# handle them with the given functions
|
||||
base.accept('setText', setText)
|
||||
base.accept('escape', exit)
|
||||
|
||||
# start the application
|
||||
base.run()
|
25
samples/networking/05-small-chat/message.py
Executable file
25
samples/networking/05-small-chat/message.py
Executable file
@ -0,0 +1,25 @@
|
||||
from direct.distributed.DistributedObject import DistributedObject
|
||||
from direct.showbase.MessengerGlobal import messenger
|
||||
|
||||
class Message(DistributedObject):
|
||||
def __init__(self, clientRepo):
|
||||
DistributedObject.__init__(self, clientRepo)
|
||||
|
||||
def sendText(self, messageText):
|
||||
"""Function which is caled for local changes only"""
|
||||
# send an event, which will set the text on the
|
||||
#print "got a message"
|
||||
messenger.send("setText", [messageText])
|
||||
|
||||
def d_sendText(self, messageText):
|
||||
"""Function which is caled to send the message over the network
|
||||
therfore the d_ suffix stands for distributed"""
|
||||
#print "send message %s" % messageText
|
||||
self.sendUpdate("sendText", [messageText])
|
||||
|
||||
def b_sendText(self, messageText):
|
||||
"""Function which combines the local and distributed functionality,
|
||||
so the sendText and d_sendText functions are called.
|
||||
The b_ suffix stands for both"""
|
||||
self.sendText(messageText)
|
||||
self.d_sendText(messageText)
|
5
samples/networking/05-small-chat/sample.dc
Executable file
5
samples/networking/05-small-chat/sample.dc
Executable file
@ -0,0 +1,5 @@
|
||||
from message import Message
|
||||
|
||||
dclass Message: DistributedObject {
|
||||
sendText(string messageText) broadcast;
|
||||
}
|
34
samples/networking/05-small-chat/server.py
Normal file
34
samples/networking/05-small-chat/server.py
Normal file
@ -0,0 +1,34 @@
|
||||
# all imports needed by the engine itself
|
||||
from direct.showbase.ShowBase import ShowBase
|
||||
|
||||
# all imports needed by the server
|
||||
from direct.distributed.ServerRepository import ServerRepository
|
||||
from panda3d.core import ConfigVariableInt
|
||||
|
||||
from AIRepository import AIRepository
|
||||
|
||||
# initialize the engine
|
||||
base = ShowBase(windowType='none')
|
||||
|
||||
# the main server class
|
||||
class GameServerRepository(ServerRepository):
|
||||
"""The server repository class"""
|
||||
def __init__(self):
|
||||
"""initialise the server class"""
|
||||
|
||||
# get the port number from the configuration file
|
||||
# if it doesn't exist, we use 4400 as the default
|
||||
tcpPort = ConfigVariableInt('server-port', 4400).getValue()
|
||||
|
||||
# list of all needed .dc files
|
||||
dcFileNames = ['../direct.dc', 'sample.dc']
|
||||
|
||||
# initialise a threaded server on this machine with
|
||||
# the port number and the dc filenames
|
||||
ServerRepository.__init__(self, tcpPort, dcFileNames=dcFileNames, threadedNet=True)
|
||||
|
||||
# start the server
|
||||
GameServerRepository()
|
||||
AIRepository()
|
||||
|
||||
base.run()
|
81
samples/networking/06-simple-avatar/AIRepository.py
Normal file
81
samples/networking/06-simple-avatar/AIRepository.py
Normal file
@ -0,0 +1,81 @@
|
||||
from direct.distributed.ClientRepository import ClientRepository
|
||||
from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
|
||||
|
||||
class AIRepository(ClientRepository):
|
||||
def __init__(self):
|
||||
""" The AI Repository usually lives on a server and is responsible for
|
||||
server side logic that will handle game objects """
|
||||
|
||||
# List of all dc files that are of interest to this AI Repository
|
||||
dcFileNames = ['../direct.dc', 'sample.dc']
|
||||
|
||||
# Initialize the repository. We pass it the dc files and as this is an
|
||||
# AI repository the dcSuffix AI. This will make sure any later calls to
|
||||
# createDistributedObject will use the correct version.
|
||||
# The connectMethod
|
||||
ClientRepository.__init__(
|
||||
self,
|
||||
dcFileNames = dcFileNames,
|
||||
dcSuffix = 'AI',
|
||||
threadedNet = True)
|
||||
|
||||
# Set the same port as configured on the server to be able to connect
|
||||
# to it
|
||||
tcpPort = ConfigVariableInt('server-port', 4400).getValue()
|
||||
|
||||
# Set the IP or hostname of the server we want to connect to
|
||||
hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
|
||||
|
||||
# Build the URL from the server hostname and port. If your server
|
||||
# doesn't use http you should change it accordingly. Make sure to pass
|
||||
# the connectMethod to the ClientRepository.__init__ call too.
|
||||
# Available connection methods are:
|
||||
# self.CM_HTTP, self.CM_NET and self.CM_NATIVE
|
||||
url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
|
||||
|
||||
# Attempt a connection to the server
|
||||
self.connect([url],
|
||||
successCallback = self.connectSuccess,
|
||||
failureCallback = self.connectFailure)
|
||||
|
||||
def connectFailure(self, statusCode, statusString):
|
||||
""" something went wrong """
|
||||
print("Couldn't connect. Make sure to run server.py first!")
|
||||
raise(StandardError, statusString)
|
||||
|
||||
def connectSuccess(self):
|
||||
""" Successfully connected. But we still can't really do
|
||||
anything until we've got the doID range. """
|
||||
# The Client Repository will throw this event as soon as it has a doID
|
||||
# range and would be able to create distributed objects
|
||||
self.accept('createReady', self.gotCreateReady)
|
||||
|
||||
def lostConnection(self):
|
||||
""" This should be overridden by a derived class to handle an
|
||||
unexpectedly lost connection to the gameserver. """
|
||||
exit()
|
||||
|
||||
def gotCreateReady(self):
|
||||
""" Now we're ready to go! """
|
||||
|
||||
# This method checks whether we actually have a valid doID range
|
||||
# to create distributed objects yet
|
||||
if not self.haveCreateAuthority():
|
||||
# Not ready yet.
|
||||
return
|
||||
|
||||
# we are ready now, so ignore further createReady events
|
||||
self.ignore('createReady')
|
||||
|
||||
# Create a Distributed Object by name. This will look up the object in
|
||||
# the dc files passed to the repository earlier
|
||||
self.timeManager = self.createDistributedObject(
|
||||
className = 'TimeManagerAI', # The Name of the Class we want to initialize
|
||||
zoneId = 1) # The Zone this Object will live in
|
||||
|
||||
print("AI Repository Ready")
|
||||
|
||||
def deallocateChannel(self, doID):
|
||||
""" This method will be called whenever a client disconnects from the
|
||||
server. The given doID is the ID of the client who left us. """
|
||||
print("Client left us: ", doID)
|
103
samples/networking/06-simple-avatar/ClientRepository.py
Normal file
103
samples/networking/06-simple-avatar/ClientRepository.py
Normal file
@ -0,0 +1,103 @@
|
||||
from direct.distributed.ClientRepository import ClientRepository
|
||||
from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
|
||||
from DistributedSmoothActor import DistributedSmoothActor
|
||||
|
||||
class GameClientRepository(ClientRepository):
|
||||
|
||||
def __init__(self):
|
||||
dcFileNames = ['../direct.dc', 'sample.dc']
|
||||
|
||||
# a distributed object of our game.
|
||||
self.distributedObject = None
|
||||
self.aiDGameObect = None
|
||||
|
||||
ClientRepository.__init__(
|
||||
self,
|
||||
dcFileNames = dcFileNames,
|
||||
threadedNet = True)
|
||||
|
||||
# Set the same port as configured on the server to be able to connect
|
||||
# to it
|
||||
tcpPort = ConfigVariableInt('server-port', 4400).getValue()
|
||||
|
||||
# Set the IP or hostname of the server we want to connect to
|
||||
hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
|
||||
|
||||
# Build the URL from the server hostname and port. If your server
|
||||
# uses another protocol then http you should change it accordingly.
|
||||
# Make sure to pass the connectMethod to the ClientRepository.__init__
|
||||
# call too. Available connection methods are:
|
||||
# self.CM_HTTP, self.CM_NET and self.CM_NATIVE
|
||||
self.url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
|
||||
|
||||
# Attempt a connection to the server
|
||||
self.connect([self.url],
|
||||
successCallback = self.connectSuccess,
|
||||
failureCallback = self.connectFailure)
|
||||
|
||||
def lostConnection(self):
|
||||
""" This should be overridden by a derived class to handle an
|
||||
unexpectedly lost connection to the gameserver. """
|
||||
# Handle the disconnection from the server. This can be a reconnect,
|
||||
# simply exiting the application or anything else.
|
||||
exit()
|
||||
|
||||
def connectFailure(self, statusCode, statusString):
|
||||
""" Something went wrong """
|
||||
exit()
|
||||
|
||||
def connectSuccess(self):
|
||||
""" Successfully connected. But we still can't really do
|
||||
anything until we've got the doID range. """
|
||||
|
||||
# Make sure we have interest in the by the AIRepository defined
|
||||
# TimeManager zone, so we always see it even if we switch to
|
||||
# another zone.
|
||||
self.setInterestZones([1])
|
||||
|
||||
# We must wait for the TimeManager to be fully created and
|
||||
# synced before we can enter another zone and wait for the
|
||||
# game object. The uniqueName is important that we get the
|
||||
# correct, our sync message from the TimeManager and not
|
||||
# accidentaly a message from another client
|
||||
self.acceptOnce(self.uniqueName('gotTimeSync'), self.syncReady)
|
||||
|
||||
def syncReady(self):
|
||||
""" Now we've got the TimeManager manifested, and we're in
|
||||
sync with the server time. Now we can enter the world. Check
|
||||
to see if we've received our doIdBase yet. """
|
||||
|
||||
# This method checks whether we actually have a valid doID range
|
||||
# to create distributed objects yet
|
||||
if self.haveCreateAuthority():
|
||||
# we already have one
|
||||
self.gotCreateReady()
|
||||
else:
|
||||
# Not yet, keep waiting a bit longer.
|
||||
self.accept(self.uniqueName('createReady'), self.gotCreateReady)
|
||||
|
||||
def gotCreateReady(self):
|
||||
""" Ready to enter the world. Expand our interest to include
|
||||
any other zones """
|
||||
|
||||
# This method checks whether we actually have a valid doID range
|
||||
# to create distributed objects yet
|
||||
if not self.haveCreateAuthority():
|
||||
# Not ready yet.
|
||||
return
|
||||
|
||||
# we are ready now, so ignore further createReady events
|
||||
self.ignore(self.uniqueName('createReady'))
|
||||
|
||||
self.join()
|
||||
|
||||
print("Client Ready")
|
||||
|
||||
def join(self):
|
||||
""" Join a game/room/whatever """
|
||||
# set our intersted zones to let the client see all distributed obects
|
||||
# in those zones
|
||||
self.setInterestZones([1, 2])
|
||||
|
||||
base.messenger.send('client-joined')
|
||||
print("Joined")
|
@ -0,0 +1,50 @@
|
||||
from direct.distributed.DistributedSmoothNode import DistributedSmoothNode
|
||||
from panda3d.core import NodePath
|
||||
|
||||
from direct.actor.Actor import Actor
|
||||
|
||||
class DistributedSmoothActor(DistributedSmoothNode, Actor):
|
||||
def __init__(self, cr):
|
||||
Actor.__init__(self, "models/ralph",
|
||||
{"run": "models/ralph-run",
|
||||
"walk": "models/ralph-walk"})
|
||||
DistributedSmoothNode.__init__(self, cr)
|
||||
self.setCacheable(1)
|
||||
self.setScale(.2)
|
||||
|
||||
def generate(self):
|
||||
DistributedSmoothNode.generate(self)
|
||||
self.activateSmoothing(True, False)
|
||||
self.startSmooth()
|
||||
|
||||
def announceGenerate(self):
|
||||
DistributedSmoothNode.announceGenerate(self)
|
||||
self.reparentTo(render)
|
||||
|
||||
def disable(self):
|
||||
# remove all anims, on all parts and all lods
|
||||
self.stopSmooth()
|
||||
if (not self.isEmpty()):
|
||||
Actor.unloadAnims(self, None, None, None)
|
||||
DistributedSmoothNode.disable(self)
|
||||
|
||||
def delete(self):
|
||||
try:
|
||||
self.DistributedActor_deleted
|
||||
except:
|
||||
self.DistributedActor_deleted = 1
|
||||
DistributedSmoothNode.delete(self)
|
||||
Actor.delete(self)
|
||||
|
||||
def start(self):
|
||||
# Let the DistributedSmoothNode take care of broadcasting the
|
||||
# position updates several times a second.
|
||||
self.startPosHprBroadcast()
|
||||
|
||||
def loop(self, animName):
|
||||
self.sendUpdate("loop", [animName])
|
||||
return Actor.loop(self, animName)
|
||||
|
||||
def pose(self, animName, frame):
|
||||
self.sendUpdate("pose", [animName, frame])
|
||||
return Actor.pose(self, animName, frame)
|
20
samples/networking/06-simple-avatar/ServerRepository.py
Normal file
20
samples/networking/06-simple-avatar/ServerRepository.py
Normal file
@ -0,0 +1,20 @@
|
||||
# all imports needed by the server
|
||||
from direct.distributed.ServerRepository import ServerRepository
|
||||
from panda3d.core import ConfigVariableInt
|
||||
|
||||
# the main server class
|
||||
class GameServerRepository(ServerRepository):
|
||||
"""The server repository class"""
|
||||
def __init__(self):
|
||||
"""initialise the server class"""
|
||||
|
||||
# get the port number from the configuration file
|
||||
# if it doesn't exist, we use 4400 as the default
|
||||
tcpPort = ConfigVariableInt('server-port', 4400).getValue()
|
||||
|
||||
# list of all needed .dc files
|
||||
dcFileNames = ['../direct.dc', 'sample.dc']
|
||||
|
||||
# initialise a threaded server on this machine with
|
||||
# the port number and the dc filenames
|
||||
ServerRepository.__init__(self, tcpPort, dcFileNames=dcFileNames, threadedNet=True)
|
151
samples/networking/06-simple-avatar/client.py
Normal file
151
samples/networking/06-simple-avatar/client.py
Normal file
@ -0,0 +1,151 @@
|
||||
# all imports needed by the engine itself
|
||||
from direct.showbase.ShowBase import ShowBase
|
||||
from panda3d.core import KeyboardButton, NodePath, PandaNode
|
||||
|
||||
# import our own repositories
|
||||
from ClientRepository import GameClientRepository
|
||||
from DistributedSmoothActor import DistributedSmoothActor
|
||||
|
||||
# initialize the engine
|
||||
base = ShowBase()
|
||||
|
||||
base.disableMouse()
|
||||
|
||||
class Avatar:
|
||||
def __init__(self, cr):
|
||||
self.cr = cr
|
||||
self.ralph = DistributedSmoothActor(self.cr)
|
||||
|
||||
self.cr.createDistributedObject(
|
||||
distObj = self.ralph,
|
||||
zoneId = 2)
|
||||
|
||||
# Create a floater object, which floats 2 units above ralph. We
|
||||
# use this as a target for the camera to look at.
|
||||
|
||||
self.floater = NodePath(PandaNode("floater"))
|
||||
self.floater.reparentTo(self.ralph)
|
||||
self.floater.setZ(2.0)
|
||||
|
||||
# We will use this for checking if keyboard keys are pressed
|
||||
self.isDown = base.mouseWatcherNode.isButtonDown
|
||||
|
||||
taskMgr.add(self.move, "moveTask")
|
||||
|
||||
# Set up the camera
|
||||
base.camera.setPos(self.ralph.getX(), self.ralph.getY() + 10, 2)
|
||||
|
||||
# start the avatar
|
||||
self.ralph.start()
|
||||
|
||||
# Accepts arrow keys to move either the player or the menu cursor,
|
||||
# Also deals with grid checking and collision detection
|
||||
def move(self, task):
|
||||
|
||||
# Get the time that elapsed since last frame. We multiply this with
|
||||
# the desired speed in order to find out with which distance to move
|
||||
# in order to achieve that desired speed.
|
||||
dt = globalClock.getDt()
|
||||
|
||||
# If the camera-left key is pressed, move camera left.
|
||||
# If the camera-right key is pressed, move camera right.
|
||||
|
||||
if self.isDown(KeyboardButton.asciiKey(b"j")):
|
||||
base.camera.setX(base.camera, -20 * dt)
|
||||
if self.isDown(KeyboardButton.asciiKey(b"k")):
|
||||
base.camera.setX(base.camera, +20 * dt)
|
||||
|
||||
# If a move-key is pressed, move ralph in the specified direction.
|
||||
|
||||
if self.isDown(KeyboardButton.asciiKey(b"a")):
|
||||
self.ralph.setH(self.ralph.getH() + 300 * dt)
|
||||
if self.isDown(KeyboardButton.asciiKey(b"d")):
|
||||
self.ralph.setH(self.ralph.getH() - 300 * dt)
|
||||
if self.isDown(KeyboardButton.asciiKey(b"w")):
|
||||
self.ralph.setY(self.ralph, -20 * dt)
|
||||
if self.isDown(KeyboardButton.asciiKey(b"s")):
|
||||
self.ralph.setY(self.ralph, +10 * dt)
|
||||
|
||||
# update distributed position and rotation
|
||||
#self.ralph.setDistPos(self.ralph.getX(), self.ralph.getY(), self.ralph.getZ())
|
||||
#self.ralph.setDistHpr(self.ralph.getH(), self.ralph.getP(), self.ralph.getR())
|
||||
|
||||
# If ralph is moving, loop the run animation.
|
||||
# If he is standing still, stop the animation.
|
||||
currentAnim = self.ralph.getCurrentAnim()
|
||||
|
||||
if self.isDown(KeyboardButton.asciiKey(b"w")):
|
||||
if currentAnim != "run":
|
||||
self.ralph.loop("run")
|
||||
elif self.isDown(KeyboardButton.asciiKey(b"s")):
|
||||
# Play the walk animation backwards.
|
||||
if currentAnim != "walk":
|
||||
self.ralph.loop("walk")
|
||||
self.ralph.setPlayRate(-1.0, "walk")
|
||||
elif self.isDown(KeyboardButton.asciiKey(b"a")) or self.isDown(KeyboardButton.asciiKey(b"d")):
|
||||
if currentAnim != "walk":
|
||||
self.ralph.loop("walk")
|
||||
self.ralph.setPlayRate(1.0, "walk")
|
||||
else:
|
||||
if currentAnim is not None:
|
||||
self.ralph.stop()
|
||||
self.ralph.pose("walk", 5)
|
||||
self.isMoving = False
|
||||
|
||||
# If the camera is too far from ralph, move it closer.
|
||||
# If the camera is too close to ralph, move it farther.
|
||||
|
||||
camvec = self.ralph.getPos() - base.camera.getPos()
|
||||
camvec.setZ(0)
|
||||
camdist = camvec.length()
|
||||
camvec.normalize()
|
||||
if camdist > 10.0:
|
||||
base.camera.setPos(base.camera.getPos() + camvec * (camdist - 10))
|
||||
camdist = 10.0
|
||||
if camdist < 5.0:
|
||||
base.camera.setPos(base.camera.getPos() - camvec * (5 - camdist))
|
||||
camdist = 5.0
|
||||
|
||||
# The camera should look in ralph's direction,
|
||||
# but it should also try to stay horizontal, so look at
|
||||
# a floater which hovers above ralph's head.
|
||||
base.camera.lookAt(self.floater)
|
||||
|
||||
return task.cont
|
||||
|
||||
base.accept("escape", exit)
|
||||
|
||||
# initialize the client
|
||||
client = GameClientRepository()
|
||||
|
||||
|
||||
from direct.gui.OnscreenText import OnscreenText
|
||||
from panda3d.core import TextNode
|
||||
|
||||
# Function to put instructions on the screen.
|
||||
def addInstructions(pos, msg):
|
||||
return OnscreenText(text=msg, style=1, fg=(0, 0, 0, 1), shadow=(1, 1, 1, 1),
|
||||
parent=base.a2dTopLeft, align=TextNode.ALeft,
|
||||
pos=(0.08, -pos - 0.04), scale=.06)
|
||||
|
||||
# Function to put title on the screen.
|
||||
def addTitle(text):
|
||||
return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08,
|
||||
parent=base.a2dBottomRight, align=TextNode.ARight,
|
||||
fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1))
|
||||
|
||||
title = addTitle("Panda3D: Tutorial - Distributed Network (NOT CONNECTED)")
|
||||
inst1 = addInstructions(0.06, "W|A|S|D: Move avatar)")
|
||||
inst2 = addInstructions(0.12, "esc: Close the client")
|
||||
inst3 = addInstructions(0.24, "See console output")
|
||||
|
||||
def clientJoined():
|
||||
title["text"] = "Panda3D: Tutorial - Distributed Network (CONNECTED)"
|
||||
|
||||
# Setup our avatar
|
||||
Avatar(client)
|
||||
|
||||
base.accept("client-joined", clientJoined)
|
||||
|
||||
# start the client
|
||||
base.run()
|
BIN
samples/networking/06-simple-avatar/models/ralph-run.egg.pz
Normal file
BIN
samples/networking/06-simple-avatar/models/ralph-run.egg.pz
Normal file
Binary file not shown.
BIN
samples/networking/06-simple-avatar/models/ralph-walk.egg.pz
Normal file
BIN
samples/networking/06-simple-avatar/models/ralph-walk.egg.pz
Normal file
Binary file not shown.
BIN
samples/networking/06-simple-avatar/models/ralph.egg.pz
Normal file
BIN
samples/networking/06-simple-avatar/models/ralph.egg.pz
Normal file
Binary file not shown.
BIN
samples/networking/06-simple-avatar/models/ralph.jpg
Normal file
BIN
samples/networking/06-simple-avatar/models/ralph.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 82 KiB |
6
samples/networking/06-simple-avatar/sample.dc
Executable file
6
samples/networking/06-simple-avatar/sample.dc
Executable file
@ -0,0 +1,6 @@
|
||||
import DistributedSmoothActor
|
||||
|
||||
dclass DistributedSmoothActor: DistributedSmoothNode {
|
||||
loop(string animName) broadcast;
|
||||
pose(string animName, int16 frame) broadcast;
|
||||
}
|
18
samples/networking/06-simple-avatar/server.py
Normal file
18
samples/networking/06-simple-avatar/server.py
Normal file
@ -0,0 +1,18 @@
|
||||
# all imports needed by the engine itself
|
||||
from direct.showbase.ShowBase import ShowBase
|
||||
|
||||
# import our own repositories
|
||||
from ServerRepository import GameServerRepository
|
||||
from AIRepository import AIRepository
|
||||
|
||||
# initialize the engine
|
||||
base = ShowBase(windowType='none')
|
||||
|
||||
# instantiate the server
|
||||
GameServerRepository()
|
||||
|
||||
# The AI Repository to manage server side (AI) clients
|
||||
AIRepository()
|
||||
|
||||
# start the server
|
||||
base.run()
|
91
samples/networking/direct.dc
Executable file
91
samples/networking/direct.dc
Executable file
@ -0,0 +1,91 @@
|
||||
// This is a sample dc file for some of the classes defined within the
|
||||
// direct source tree. It is suggested that you copy this file into
|
||||
// your own project (or load it from the direct source tree) and build
|
||||
// on it with your own dc file for your own classes.
|
||||
|
||||
keyword broadcast;
|
||||
keyword ram;
|
||||
keyword p2p;
|
||||
|
||||
from direct.distributed import DistributedObject/AI
|
||||
from direct.distributed import TimeManager/AI
|
||||
from direct.distributed import DistributedNode/AI
|
||||
from direct.distributed import DistributedSmoothNode/AI
|
||||
|
||||
struct BarrierData {
|
||||
uint16 context;
|
||||
string name;
|
||||
uint32 avIds[];
|
||||
};
|
||||
|
||||
// The most fundamental class
|
||||
dclass DistributedObject {
|
||||
// These are used to support DistributedObjectAI.beginBarrier() and
|
||||
// the matching DistributedObject.doneBarrier(). If you don't call
|
||||
// these functions, you don't care about these distributed methods.
|
||||
// (Actually, you probably don't care anyway.)
|
||||
setBarrierData(BarrierData data[]) broadcast ram;
|
||||
setBarrierReady(uint16 context);
|
||||
setLocation(uint32 parentId, uint32 zoneId) broadcast ram;
|
||||
};
|
||||
|
||||
dclass TimeManager: DistributedObject {
|
||||
requestServerTime(uint8 context) p2p;
|
||||
serverTime(uint8 context, int32 timestamp);
|
||||
};
|
||||
|
||||
dclass DistributedNode: DistributedObject {
|
||||
setX(int16 / 10) broadcast ram;
|
||||
setY(int16 / 10) broadcast ram;
|
||||
setZ(int16 / 10) broadcast ram;
|
||||
setH(int16 % 360 / 10) broadcast ram;
|
||||
setP(int16 % 360 / 10) broadcast ram;
|
||||
setR(int16 % 360 / 10) broadcast ram;
|
||||
|
||||
setPos: setX, setY, setZ;
|
||||
setHpr: setH, setP, setR;
|
||||
setPosHpr: setX, setY, setZ, setH, setP, setR;
|
||||
setXY: setX, setY;
|
||||
setXZ: setX, setZ;
|
||||
setXYH: setX, setY, setH;
|
||||
setXYZH: setX, setY, setZ, setH;
|
||||
};
|
||||
|
||||
dclass DistributedSmoothNode: DistributedNode {
|
||||
// Component set pos and hpr functions.
|
||||
|
||||
setComponentL(uint64) broadcast ram;
|
||||
setComponentX(int16 / 10) broadcast ram;
|
||||
setComponentY(int16 / 10) broadcast ram;
|
||||
setComponentZ(int16 / 10) broadcast ram;
|
||||
setComponentH(int16 % 360 / 10) broadcast ram;
|
||||
setComponentP(int16 % 360 / 10) broadcast ram;
|
||||
setComponentR(int16 % 360 / 10) broadcast ram;
|
||||
setComponentT(int16 timestamp) broadcast ram;
|
||||
|
||||
// Composite set pos and hpr functions. These map to combinations
|
||||
// of one or more of the above components. They all include
|
||||
// setComponentT(), which must be called last.
|
||||
setSmStop: setComponentT;
|
||||
setSmH: setComponentH, setComponentT;
|
||||
setSmZ: setComponentZ, setComponentT;
|
||||
setSmXY: setComponentX, setComponentY, setComponentT;
|
||||
setSmXZ: setComponentX, setComponentZ, setComponentT;
|
||||
setSmPos: setComponentX, setComponentY, setComponentZ, setComponentT;
|
||||
setSmHpr: setComponentH, setComponentP, setComponentR, setComponentT;
|
||||
setSmXYH: setComponentX, setComponentY, setComponentH, setComponentT;
|
||||
setSmXYZH: setComponentX, setComponentY, setComponentZ, setComponentH, setComponentT;
|
||||
setSmPosHpr: setComponentX, setComponentY, setComponentZ, setComponentH, setComponentP, setComponentR, setComponentT;
|
||||
// special update if L (being location, such as zoneId) changes, send everything, intended to
|
||||
// keep position and 'location' in sync
|
||||
setSmPosHprL: setComponentL, setComponentX, setComponentY, setComponentZ, setComponentH, setComponentP, setComponentR, setComponentT;
|
||||
|
||||
clearSmoothing(int8 bogus) broadcast;
|
||||
|
||||
suggestResync(uint32 avId, int16 timestampA, int16 timestampB,
|
||||
int32 serverTimeSec, uint16 serverTimeUSec,
|
||||
uint16 / 100 uncertainty);
|
||||
returnResync(uint32 avId, int16 timestampB,
|
||||
int32 serverTimeSec, uint16 serverTimeUSec,
|
||||
uint16 / 100 uncertainty);
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user