Blender Multiplayer Networking

This is my own attempt of making online game creation easier for those who are only logic brick savvy. There are two parts to this: a python module and a collections of python scripts for easing the burden of developing online games in blender.

Python Module

The first part of what I’m releasing is a python module that I wrote a while ago; its purpose is to decrease the development time necessary for creating a server and client. Its name is bi_net and it contains two classes.

The first class is Server. It’s purpose it to act as a server. The class is meant to be extended for full use. The on_input, on_output, on_connect, and on_disconnect methods are what should be overridden.

The second class is Client. It acts as a client. Just like the Server class, it needs to be extended for full use. The on_input and on_disconnect methods are what should be overridden.

I did do some rather odd, maybe even unpytohnic things in the module, but fortunately you’ll find examples in the next section.

import socket, time 
 
"""A module for performing socket input and output with less work. 
 
This module offers a simple way of using a UDP socket. There 
is only support for IP, and the classes are best used as 
superclasses. 
""" 
 
class Server: 
     
    """Class for receiving input and output from multiple clients. 
     
    The constructor takes no arguments. Instead is checks if IP, 
    PORT, BUFF_SIZE, or TIMEOUT have been defined in self, if not, they are 
    given default values. 
    """ 
     
    def __init__(self): 
        """Constructor. See class doc string.""" 
        if not ('IP' in self.__dict__): 
            self.IP = socket.gethostbyname(socket.gethostname()) 
        if not ('PORT' in self.__dict__): 
            self.PORT = 21500 
        if not ('BUFF_SIZE' in self.__dict__): 
            self.BUFF_SIZE = 1024 
        if not ('TIMEOUT' in self.__dict__): 
            self.TIMEOUT = 0.005 
        if not ('DISCONNECT' in self.__dict__): 
            self.DISCONNECT = 30 
         
        self.server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 
        self.server.bind((self.IP, self.PORT)) 
        self.server.settimeout(self.TIMEOUT) 
         
        self.clients = [] 
        self.client_times = [] 
     
    def recv(self): 
        """Returns all packets that have been received. 
         
        Each time a packet is succesfully received, the on_input 
        method is called. 
         
        If an error has occured while receiving a packet, it is 
        assumed that a client has disconnected. 
         
        Once a client has disconnected, they are removed from the 
        self.clients list, and the on_disconnect method is called. 
         
        Whenever a new client connects, their address is added to 
        the self.clients list, and the on_connect method is called. 
        """ 
        concatenated = '' 
         
        data = bytes() 
        addr = tuple() 
         
        while 1: 
 
            for client in self.clients: 
                index = self.clients.index(client) 
                difference = time.time() - self.client_times[index] 
                if (difference > self.DISCONNECT): 
                    self.on_disconnect(client) 
                    del self.clients[index] 
                    del self.client_times[index] 
             
            try: 
                data, addr = self.server.recvfrom(self.BUFF_SIZE) 
            except socket.timeout: 
                return concatenated 
            except socket.error: 
                continue 
             
            data = data.decode('utf-8') 
             
            if not (addr in self.clients): 
                self.clients.append(addr) 
                self.client_times.append(time.time()) 
                self.on_connect(addr) 
 
            self.client_times[self.clients.index(addr)] = time.time() 
             
            data = self.on_input(data, addr) 
            concatenated += data 
     
    def send(self, data): 
        """Sends data to each client. 
         
        Every time, before data is sent to a client, the on_output 
        method is called. 
        """ 
        if hasattr(data, 'encode'): 
            data = data.encode('utf-8') 
         
        for client in self.clients: 
            output = data 
            output = self.on_output(output, client) 
            self.server.sendto(output, client) 
     
    def on_input(self, data, address): 
        """Runs when a new packet of data is received.""" 
        return data 
     
    def on_output(self, data, address): 
        """Runs each time a new packet of data is sent.""" 
        return data 
     
    def on_connect(self, address): 
        """Runs each time a new client has connected.""" 
        pass 
     
    def on_disconnect(self, address): 
        """Runs when a client has disconnected.""" 
        pass 
 
 
class Client: 
     
    """Class for receiving input and output from a server. 
     
    The constructor takes no arguments. Instead is checks if IP, 
    PORT, BUFF_SIZE, or TIMEOUT have been defined in self, if not, they are 
    given default values. 
    """ 
     
    def __init__(self): 
        """Constructor. See class doc string.""" 
         
        if not ('IP' in self.__dict__): 
            self.IP = socket.gethostbyname(socket.gethostname()) 
        if not ('PORT' in self.__dict__): 
            self.PORT = 21500 
        if not ('BUFF_SIZE' in self.__dict__): 
            self.BUFF_SIZE = 1024 
        if not ('TIMEOUT' in self.__dict__): 
            self.TIMEOUT = 0.005 
         
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 
        self.socket.connect((self.IP, self.PORT)) 
        self.socket.settimeout(self.TIMEOUT) 
     
    def recv(self): 
        """Returns all packets that have been received. 
         
        Each time a packet is succesfully received, the on_input 
        method is called. 
         
        If an error has occured while receiving a packet, it is 
        assumed that a server has disconnected. 
         
        Once the server is no longer connected the on_disconnect 
        method is called. 
        """ 
         
        while 1: 
            input = '' 
            data = bytes() 
             
            try: 
                data = self.socket.recv(self.BUFF_SIZE) 
            except socket.timeout: 
                return input 
            except socket.error: 
                self.socket.close() 
                self.on_disconnect() 
                break 
             
            data = data.decode('utf-8') 
            data = self.on_input(data) 
            input += data 
     
    def send(self, data): 
        """Sends data to the server.""" 
        if hasattr(data, 'encode'): 
            data = data.encode('utf-8') 
         
        self.socket.send(data) 
     
    def on_disconnect(self): 
        """Runs once the server is not detected.""" 
        pass 
     
    def on_input(self, data): 
        """Runs each time a packet of data is received.""" 
        return data

Python Scripts

This was an attempt of mine to make online game creation easier for those who are only able to use logic bricks. It may seem daunting, but as long as you follow the directions bellow you’ll be fine.

default.blend

  • Make a new blend file and empty the scene.
  • Add one object, name it _resource, and move it to layer two.
  • Add an empty, name it _spawn, and position it where you want your clients to appear when they connect.
  • Add any other objects that you wish to, such as lights or cameras, but be sure that their names begin with an underscore.
  • Add the following python scripts into your blend file: client_client, client_input, and server_server.
  • Save what you have done so far and name it default.blend.

server.blend

  • Reopen blender and append all of the objects and python scripts from default.blend into the current scene.
  • Select an object, I recommend a camera, and attach an always sensor with true level triggering selected.
  • Add a new controller and set the controller type to python.
  • Set server_server as the python script to be ran in the controller.
  • Go to layer two, you should see that object named _resource.
  • Add a property sensor.
  • To test if the client is pressing the w key, you would enter w as the property and 1 as the value to be tested.
  • Add an and controller.
  • Attach the property sensor and the and controller.
  • Add any actuators that you want to the controller.
  • Continue steps 6 - 12 until you have configured your clients’ controls.
  • Save what you have done as server.blend.

client.blend

  • Open a new instance of blender and empty the scene.
  • Append all of the objects from default.blend into your current scene.
  • Find an object, I recommend a camera, and add an always sensor with true level triggering.
  • Add a new controller and set the controller type to python.
  • Set the script for the controller to client_client.
  • Attach the always sensor to the python controller.
  • Add a keyboard sensor with true level triggering and all keys selected.
  • Name the controller input.
  • Add another controller and set the controller type for it to python.
  • Set the script for the new controller to client_input.
  • Connect the keyboard sensor to the other controller.
  • Save what you have done as client.blend.

If everything was done correctly, then start server.blend on one computer, and you can connect to it on as many alternate computers with client.blend as you wish.

I know that those are some pretty heft instructions and I may not have been clear at times, but hopefully you’ll end up with the right result. Any necessary python scripts that I mentions above can be found in the post below.

There is more information in the next post.

Could you post a tutorial on how to use this? It looks very neat, but how do you set up the server and client in the first place?

I’m working on that right now, I just ran out of time getting that in the post earlier. :yes:

I am very interested,hope you will release a demo/blend file with it running!
Cya!

You must have a copy of the bi_net module in the same folder as the server and client blend files. If you don’t it will not run correctly.

Here are all of the python scripts mentions above. Remember to update line 11 of client_client to have the server’s IP address.

client_client

import bi_net, bge

cont = bge.logic.getCurrentController()
own = cont.owner

scene =  bge.logic.getCurrentScene()
objs = scene.objects

class Client(bi_net.Client):
    def __init__(self):
        self.IP = "*********"
        bi_net.Client.__init__(self)
    
    def on_input(self, input):
        data = input.split('
')
        
        if (data[0] == 'r'):
            own['objects'][data[1]].endObject()
            del own['objects'][data[1]]
            return input
        
        # adds new objects when necesary
        if not (data[0] in own['objects']):
            scene.addObject('_resource', '_spawn', 0)
            own['objects'][data[0]] = objs[-1]
            
            if (str(self.socket.getsockname()) == data[0]):
                own['self'] = objs[-1]
        
        object = own['objects'][data[0]]
        
        # sets new position and orientation
        object.orientation = eval(data[1])
        object.position = eval(data[2])
        
        return input
    
    def on_disconnect(self):
        print('disconnected')
        bge.logic.endGame()

if not ('init' in own):
    bge.logic.client = Client()
    bge.logic.client.send('*')
    
    own['objects'] = {}
    own['init'] = True

bge.logic.client.recv()
bge.logic.client.send('\1')


client_input

import bge

cont = bge.logic.getCurrentController()
own = cont.owner

keys = own.sensors['input']
MAX = 2

if keys.positive:
    events = keys.events[0][:MAX]
    for event in events:
        key = chr(event)
        bge.logic.client.send(key)

server_server

import bi_net, bge

cont = bge.logic.getCurrentController()
own = cont.owner

scene =  bge.logic.getCurrentScene()
objs = scene.objects

def matrix_to_list(matrix, rows, columns):
    list = []
    for row in range(rows):
        tmp = []
        for column in range(columns):
            if columns == 1:
                tmp = matrix[row][column]
            else:
                if rows == 1:
                    tmp.append(matrix[column])
                else:
                    tmp.append(matrix[row][column])
        if rows == 1:
            list = tmp
        else:
            list.append(tmp)
    
    return list

class Server(bi_net.Server):
    def __init__(self):
        self.DISCONNECT = 1
        bi_net.Server.__init__(self)
    
    def on_input(self, data, address):
        own['objects'][address][data[0]] = True
        if not (data[0] in own['objects'][address]['pressed']):
            own['objects'][address]['pressed'].append(data[0])
        return data
    
    def on_connect(self, address):
        print(address, "has connected")
        scene.addObject('_resource', '_spawn', 0)
        own['objects'][address] = objs[-1]
        own['objects'][address]['pressed'] = []
    
    def on_disconnect(self, address):
        print(address, "has disconnected")
        own['objects'][address].endObject()
        bge.logic.server.send('r
' + str(address))
        del own['objects'][address]

if not ('init' in own):
    bge.logic.server = Server()
    own['objects'] = {}
    own['init'] = True
    
    for obj in objs:
        if obj.name[0] == '_':
            continue
        own['objects'][obj.name] = obj

for addr in own['objects']:
    for key in own['objects'][addr]['pressed']:
        own['objects'][addr][key] = 0

bge.logic.server.recv()

for obj in own['objects']:
    object = own['objects'][obj]
    
    name = str(obj)
    orientation = str(matrix_to_list(object.orientation, 3, 3))
    position = str(matrix_to_list(object.position, 1, 3))
    
    bge.logic.server.send(name + '
' + orientation + '
' + position)

Demo and Video

Here’s an example made using everything above. Download

The video was recorded using the demo. There were three computers used in it. The first was running both a server and client and The second and third were only running clients. The server and one of the clients was running Windows. The other client, which is what is seen in the video, was running Linux.

Server Information

The server that I wrote is able to connect to multiple clients. How many clients a server is able to handle depends on the awesomeness of the computer. Using the bi_net module there are no limitations with what you can do. However, using the collection of python scripts and following the directions above, you will not be able to include some things in your game, such as ipos.

I did some more testing and found a few bugs with what I posted. Luckily, they didn’t take so long to fix and now everything has been updated. I also set the camera to use the camera actuator in the demo and did a quick cross platform test, which was successful.

As far as I can tell the directions above seem to be alright, if anyone finds any issues tell me and I’ll fix it.

I am excited for a demo blend or two from what I understand?or atleast video. I will try this a little layer but I would appreciate a vid or demo? THANK YOU SO MUCH XD

I just dont get how we place them in to the engine? and that bi net module or whatever =/ Please demo or vid

… And here’s an example made using everything above. Download
Sorry if the link got hidden above, but the demo’s up there.
I’ll try to get a video posted as soon as I can. :yes:

might want to put it in its own line space out so can spot the download and vid link I still cant find it up there XD

I’ve added a video and moved things around a bit in that last post. Hopefully now the link will be easier to find. :wink:

I’ll try to have another video uploaded soon that goes through the instructions.

I cant figure out how make anything spawn or anything.

Enter in the client_client script line 11 (self.IP = “xxxxxxxxx”) in the default.blend the IP address of your router.

It seems pretty attractive. Thanks for the posted information!

Hm you are saying ipo animations won’t work? Or armature animations…what does this script actually do?(might sound like a stupid question)

As of now, the script shares object data such as location and orientation between the clients through the server. I’ll try to incorporate ipo animations into it today. It shouldn’t be but so hard.

Thanks now i can make multiplayer Games :slight_smile:

Without any animations?

Do you have a 2.49 version of the scripts?

Hey JBC! This is great! Thank you so much for sharing! I learned a lot with your files, thank you so much for making something well documented, efective and simple to learn. :slight_smile:

is there a way to have it so you type in a ip address when you playing the game and it connects rather then typing it into the script?