mirror of
https://github.com/panda3d/panda3d.git
synced 2025-09-27 23:34:57 -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