Multiplayer (LAN) explanations?

Hi,

I wanna set up some “basic” game to be played over an LAN connection.

I have some ideas how it can work but I look for the best way:

example 1:
Server:
handles all position, location, properties over remote
Client:
send only keys to server

example 2:
Server:
send update messages to Clients to get object , Posi, Orie, Props
Client:
send/recieve as requested

at both examples the server wanna update to each client.

How to setup ping to client?
Best ways to create Lobby?
Update by update_objList from client useful?
Possible to get played Animation without Property?
Which socket() constructor is useful for games (SOCK_STREAM, AF_INET)? TCP/IP4
Which Buff_size should I use max 4kib or more? <- by knowing that depended on that what I send.
Threading for receiving useful?

and of course I am not use the UPBGE or the network addon, I have some errors to start the engine. I use normal Blender 2.78

All of this depends on the type of game. Probably the server should receive informations about the another players (position, damages dealt, etc.) and send informations about the world (enemies position, changes etc.).
I prefer UDP (AF_INET, SOCK_DGRAM) socket, because it doesn’t need extra connections and is simpler and much faster than TCP (SOCK_STREAM) but it can loose some packets.
Threading is useful because it not blocking graphics and physics frames.

Here is the good old 2.49b client-server example. It works great with 2.49b. If you convert it to bge python 3.0 it should work just fine with 2.78 too. I’ve tried to convert and it starts but I had some problems with dumps data and then sending it to client. Probably it is just a new python syntax but I was too lazy to go and fix it. So take you time and examine the server.blend and client.blend.
I’m giving you the converted to 2.78 blends…try and fix the problem I had.
You need to start the server and then start 2 clients in order to try it.
Me and some guys made a long ago a multiplayer game with this as base. And yes use UDP to send position/rotation…everything. TCP only for login or anything that needs to be exactly as is(like chat or when you choose character in the lobby ). Threading is really useful.

Example:


class ServerInitThread(threading.Thread):
   def __init__(self):
    threading.Thread.__init__(self)
    self.runThread = 1
    self._stop = threading.Event()
   def stop(self):
    self.runThread = 0
    self._stop.set()
   def stopped(self):
    return self._stop.isSet()
   
   def run(self):
    g = __import__('GameLogic', globals(), locals(), [], -1)
    cd = __import__('ManageClientData', globals(), locals(), [], -1)
    #import GameLogic as g
    #import ManageClientData as cd
    import socket
    
    # Get globals
    cont = g.getCurrentController()
    scene = g.getCurrentScene()
    own = cont.owner
    
    while self.runThread:
     try:
     #some game code....

class ServerTCPThread(threading.Thread):
   def __init__(self):
    threading.Thread.__init__(self)
    self.runThread = 1
    self._stop = threading.Event()
   def stop(self):
    self.runThread = 0
    self._stop.set()
   def stopped(self):
    return self._stop.isSet()
   
   def run(self):
    g = __import__('GameLogic', globals(), locals(), [], -1)
    cd = __import__('ManageClientData', globals(), locals(), [], -1)
    #import GameLogic as g
    #import ManageClientData as cd
    import socket
    
    # Get globals
    cont = g.getCurrentController()
    scene = g.getCurrentScene()
    own = cont.owner
    
    while self.runThread:
     try:
      # Some game code

class ServerUDPThread(threading.Thread):
   def __init__(self):
    threading.Thread.__init__(self)
    self.runThread = 1
    self._stop = threading.Event()
   def stop(self):
    self.runThread = 0
    self._stop.set()
   def stopped(self):
    return self._stop.isSet()
   
   def run(self):
    g = __import__('GameLogic', globals(), locals(), [], -1)
    cd = __import__('ManageClientData', globals(), locals(), [], -1)
    #import GameLogic as g
    #import ManageClientData as cd
    import socket
    # Get globals
    
    cont = g.getCurrentController()
    scene = g.getCurrentScene()
    own = cont.owner
    
    ServerID='Server'
    
    while self.runThread:
     try: 
       #Some game code



I do not think you need a thread. Polling the socket (which should run non-blocking) should be enough. It already runs parallel.

This way communication is in-synch with the game processing (it runs when the controller is executed).

I know what are you talking about, but without a thread the next graphic and physic frame won’t be executed when the script is working. Sometimes it can block whole game for a moment, especially if there is more players and if you want to print some informations in the console (to check the connections).

I do not understand that. When the socket is non-blocking, it should never hold the engine.

The idea is that the custom code just processes the data that already is in the cache of the socket, rather than to wait that data arrives.

Could it be the custom code is too heavy and causes the lag (printing to console, etc.?).

The issue with threads is that you can get in conflict with the processing order of the bge code. E.g. you should not modify the scene while the BGE is rendering the scene. You should do that when the controllers are executed. Remember the BGE is not thread-safe.

How to setup ping to client?
Put an ID on your packet and count the time it takes for the server to reply back with the same ID.

Best ways to create Lobby?
Depends on the game, may have to be more specific.

Update by update_objList from client useful?
Not quite sure what you mean so I took a stab in the dark:
Maintain your own list of networked objects so you aren’t wasting cpu/bandwidth on static objects.

Possible to get played Animation without Property?
Couple different interpretations:
-Send keys to server & propagate that to all clients. Determine which animation to play based on keys.
-owner.isPlayingAction(layer=0), owner.getActionName(layer=0), owner.getActionFrame(layer=0)

Which socket() constructor is useful for games (SOCK_STREAM, AF_INET)? TCP/IP4
Protocol: AF_INET. Add support for AF_INET6 if you’re feeling edgy, but at least have IPV4.
Socket type: Pretty much what haidme said - unless you have a protocol for reliable UDP, in which case I’d use that for (almost) everything.

Which Buff_size should I use max 4kib or more? <- by knowing that depended on that what I send.
4k is fine. Since you’re on LAN you could get away with a lot more, but you probably* won’t need more than 4k.

Threading for receiving useful?

Best to avoid threading for the meat of gameplay b/c what monster said.
But it’s still useful for:
-Keeping the connection alive during long load times (I’d just assume move back to the main thread when that’s done)
-Convenient way to keep a TCP data stream going between scenes (like if you’re downloading something)

If you don’t wanna mess with sockets and whatnot, something like enet will make life a lot easier. https://github.com/aresch/pyenet

The socket can be non-blocking, but reading the data is. With threading you can recive one hudge packet, read it and update the scene on the next logic frame.

Without thread:
[Frame 01 | time: 0:00] —> reciveing data / reading / updating —> [Frame 02 | time: 0:03]

With thread:
[Frame 01 | time: 0:00] —> [Frame 02 | time: 0:01] —> [Frame 03 | time: 0:02] —> [Frame 04 | time: 0:03]
---------------------------------------------> reciveing data / reading / updating --------------------->

So you mean the socket is providing newly received data while the application is polling (which results in a long lasting read operation as long as there are new data)?

@el_pawcik
Not entirely true, recv(1024) will block until 1024 bytes are recevied, but recv() simply returns whatever is there without blocking. Threading is only simpler in that you save a few lines of code dealing with custom message buffers.

EDIT
To elaborate further: With non-blocking reads you can have variable packet sizes, thus saving bandwidth (which is everything) and removing the need for threads. As a side bonus, you get a little less latency by acting as soon as the data is available vs waiting for the next frame. But for LAN apps I guess it doesn’t really matter.

I mean the socket won’t block the frame when receiving but you can receive only one packet per logic frame. So if you have more than 2 players will be problem, because the packets will be block each other and wait in queue. Of course you can use TCP protocol to avoid the problem (TCP can check every single connection - not whole socket like UDP), but in my opinion UDP is faster and easier to use.

Shouldn’t two players have two connections (and one socket for each connection)?

WOW, thanks all for the fast response.

How to setup ping to client?
@pqftgs I was thinking there is an in build method or class I can use for that.
but the ping should work parallel to the package I send for each client (posi, etc…)

Best ways to create Lobby?
of course depends on the game but the connection between Server and Client are same. (?)
By creating lobby the “host” will open ports. By starting as client/fellow player the client will search for each open port in network.
Now both can agree and play together or not.

Permissions are:
Host:
can delete follow players
can invite fellow players
game settings for level
start game

client:
can leave lobby

Update by update_objList from client useful?
only objects in this list will be updated dynamically (players, some dynamic props, etc…) others maybe at specific time.

Possible to get played Animation without Property?
more technically but less intensive? or should I do this as always over properties (maybe less accurate?)

Which socket() constructor is useful for games (SOCK_STREAM, AF_INET)? TCP/IP4
not that easy I see:

TCP/IP is a stream-based protocol, not a message-based protocol. There’s no guarantee that every send() call by one peer results in a single recv() call by the other peer receiving the exact data sent. It might receive the data piece-meal

What Ineed to define is an own message-based protocol on top of TCP in order to differentiate message boundaries. Then, to read a message, it should continue to call recv() until it has read an entire message or an error occurs.

@haidme TCP for login and UDP for game-played: so you close the first socket an recreate the same as other constructor or with complete new ports?
@el_pawcik I am not sure if the packets that will send to one server from 2 clients will be blocking each are there some tests?
but UDP is not every time faster. But I will give it a try
@Monster non-blocking is a way to deal with. and of course 2 Players need two connections

Threading for receiving useful?

if as Monster say the operation is most running parallel I didn’t need Threading. And if this cause problems with the render before threading finished there will be more errors on low-end pc’s

@haidme
your demo is a god start point to create my own network-frame

I would be interested in evaluating something like asyncio for a multiplayer game. I’ve been using it for a webserver and some other projects, and it’s been very useful.

Requires aiohttp & Python 3.5+

import asyncio
import aiohttp
from bge import logic, events
from time import monotonic


loop = asyncio.get_event_loop()


async def step():
    last_time = monotonic()
    accumulator = 0.0
    
    while True:
        now = monotonic()
        accumulator += now - last_time
        last_time = now
        
        # Step frames available
        dt = 1 / logic.getLogicTicRate()
        while accumulator &gt; dt:
            accumulator -= dt
            
            # Step gameloop
            logic.NextFrame()


            if logic.getExitKey() in logic.keyboard.active_events:
                return
            
            await asyncio.sleep(0.0)
    
    
async def read_google():
    url = "http://google.com"
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            response = await response.read()
            print(response)
        
    
asyncio.ensure_future(read_google())
loop.run_until_complete(step())

Concerning UDP / TCP, it’s not that difficult a decision:

TCP runs on the same network later as UDP, but it hides the reality of the network from the user. In guaranteeing order and reliability, it must perform resending of data that gets dropped and wait until it arrives.

Using UDP enables you to recognise that your data will be dropped, but design around that. TCP doesn’t let you do that.

I came up with some data catching system for objects that should update over the server:


def objListServer(sceneObjs):
    updateObjList = []

    for obj in sceneObjs:
        if "update_object" in obj:
            updateObjList.append(obj)
    return updateObjList

def positionVector(obj):
    if "position" in obj["update_object"]:
        posi = obj.worldPosition
        return posi
    else:
        return None

def orientationMatrix(obj):
    if "orientation" in obj["update_object"]:
        ori = obj.worldOrientation
        return ori
    else:
        return None

def propertiesList(obj):
    propDict = {}

    if "update_properties" in obj:
        if "properties" in obj["update_object"]:
            props = obj.getPropertyNames()
            for name in props:
                propDict[name] = obj[name]
            return propDict
        else:
            return None
    else:
        return None

def animationList(obj):
    #owner.isPlayingAction(layer=0), owner.getActionName(layer=0), owner.getActionFrame(layer=0)
    updateAnimationList = []
    if "animation" in obj["update_object"]:
        return updateAnimationList
    else:
        return None

def singleObjDict(updateObjList):
    singleObjDict = {}
    objUpdateDict = {}
    for obj in updateObjList:
        position = positionVector(obj)
        orientation = orientationMatrix(obj)
        properties = propertiesList(obj)
        animations = animationList(obj)
        singleObjDict = {"position": position,
                        "orientation": orientation,
                        "properties": properties,
                        "animations": animations}
        objUpdateDict[obj] = singleObjDict
    return objUpdateDict

def upDateServerObjects(cont):
    sceneObjs = cont.owner.scene.objects
    updateObjsDict = {}
    updateObjList = objListServer(sceneObjs)
    objDict = singleObjDict(updateObjList)

    updateObjsDict.update(objDict)
    print(updateObjsDict)



the output looks like


Blender Game Engine Started
{Ground: {'position': Vector((0.0, -28.547155380249023, -17.314699172973633)), '
orientation': Matrix(((1.0, 0.0, 0.0),
        (0.0, 1.0, 0.0),
        (-0.0, 0.0, 1.0))), 'animations': None, 'properties': {'ground': 0.09999
999403953552, 'block_camera_object': -0.09999999403953552, 'update_properties':
True, 'update_object': True}}, Car: {'position': Vector((-2.2785580711115472e-07
, 0.0, 2.6721572875976562)), 'orientation': Matrix(((1.0, 0.0, 0.0),
        (0.0, 1.0, 0.0),
        (-0.0, 0.0, 1.0))), 'animations': None, 'properties': {'update_animation
': False, 'update_properties': True, 'update_object': True}}}
{Ground: {'position': Vector((0.0, -28.547155380249023, -17.314699172973633)), '
orientation': Matrix(((1.0, 0.0, 0.0),
        (0.0, 1.0, 0.0),
        (-0.0, 0.0, 1.0))), 'animations': None, 'properties': {'ground': 0.09999
999403953552, 'block_camera_object': -0.09999999403953552, 'update_properties':
True, 'update_object': True}}, Car: {'position': Vector((-0.009760634042322636,
0.004111429676413536, 2.6693928241729736)), 'orientation': Matrix(((0.9171085953
712463, -0.3985776901245117, -0.006901925429701805),
        (0.3986039161682129, 0.9171185493469238, 0.002907336689531803),
        (0.005171084776520729, -0.005417478270828724, 0.9999719262123108))), 'an
imations': None, 'properties': {'update_animation': False, 'update_properties':
True, 'update_object': True}}}
Blender Game Engine Finished


is that to much information flow over udp?
meaning that this just two objects without animations and only basic props (just to test it)

Can do multiple packets with a loop sorta like this

while True:
    try:
        data, addr = socket.recvfrom(4096)
    except:
        break

    do stuff with data

It’s been a few years since I’ve messed with python sockets so my words might not be 100% accurate. I may have got my TCPs and UDPs flipped around at some point in the thread. But anyhow something along those lines does work (used in a game with 12+ clients @ 30 packets per second)

Now for another enet plug:
-Built-in ping function
-Messaging goodies
-Much easier than fiddling with sockets
-Works good and stuff

The main problem IMO is that it has to be compiled on each platform. There was talk of making it part of upbge, so there’s that to look forward to.

I don’t recommend downloading binaries from strangers, but if you’re feeling lucky and want to give it a shot:
https://pqftgs.net/downloads/netplay/enet.cp35-win32.pyd (Windows, requires 32 bit blender ~2.8)
https://pqftgs.net/downloads/netplay/enet.cpython-35m-x86_64-linux-gnu.so (Linux, 64 bit ~2.8)
Example code is here: https://github.com/aresch/pyenetAll the docs are for the C api but it’s pretty darn close.

I think I will stay only to the socket, treading maybe asyncio moduls to do many things by my self.

but back to the main question:

Better to let handle the server all movements/updates via only key input from client,

or

syncing with server by all props from objects from client (posi, orie, props, animations, etc…)

??

other question:

for getActionName(layer) is it possible to get all action names from all layers parallel? (maybe with an range function?)

@pqftgs I was thinking that the “while” statement is much to slow for bge coding?? (just an illusion from me?)

“While” is not slow … it is deadly.

Each and every processing step will increase the processing time of the frame. A loop makes the processing time multiplied by the number of iterations. Especially an endless loop (while True) is no good idea. Within a thread the loop will be killed from outside (by killing the thread).

Edit: This does not mean you should never use loops (for, while …). It means you should be carefull how many iterations the loop is doing and how long each iteration is doing.

ok “while i < 20: i += 1” but you get the point
But if you run into an infinite loop reading the socket then you’re having other problems