1
$\begingroup$

I want to use Blender gaming engine to display the position of objects that I get from UDP. The UDP contains the datas (X) for Object A, and (X) for object B I also need to use bge to compute some distances and send this over udp

I did some tests, and I succeed in using Python to read the data from UDP. for object A

enter image description here

Python Script

import bge
import math
import socket
import struct
#socket for receiving position
s = socket.socket(socket.AF_INET,       socket.SOCK_DGRAM)
s.bind((address, port))

#socket for sending information
s2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s2.bind((address, port2))


def setPosition(): 
    if not allSensorsArePositive():
        return
    data, addr = s.recvfrom(16)
    data = struct.unpack('dd', data)

    objA_x = data[0]

    owner = bge.logic.getCurrentController().owner

    owner.localPosition.x = objA_x
    owner.localPosition.y = 0

for object B

import bge
import math
import socket
import struct
#socket for receiving position
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind((address, port))


def setPosition(): 
    if not allSensorsArePositive():
        return
    data, addr = s.recvfrom(16)
    data = struct.unpack('dd', data)

    objB_x = data[1]

    owner = bge.logic.getCurrentController().owner

    owner.localPosition.x = objB_x
    owner.localPosition.y = 0

But i don't like the idea of "deserializing" the data twice. And I get an error at execution :

    OSError: [WinError 10048] Only one usage of each socket address (protocol/network address/port) is normally permitted
Python module can't be imported - object 'ObjB', controller 'Python':

Also, I would like to know if there a place, where I can execute once :

data, addr = s.recvfrom(16)
data = struct.unpack('dd', data)
# some stuff

objA_x = data[0]
objB_x = data[1]

and then store and objA_x, objB_x variables so that they can be used by scripts for each object.

Note : I know one can do it with BPY, but I need a real time environment and computation done by bge (get distances), so I prefer to stick with bge.

#Example with bpy    
import bge
import math
import socket
import struct

port = 25001
port2 = 25002
address = "127.0.0.1"
base = 20
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind((address, port))

s2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s2.bind((address, port2))


objA = bge.data.objects['objA']
objA.location = (500, 0, 0)

objB = bge.data.objects['objB']
objB.location = (0, 0, 0)

class ModalTimerOperator(bpy.types.Operator):
    """Operator which runs its self from a timer"""
    bl_idname = "wm.modal_timer_operator"
    bl_label = "Modal Timer Operator"

    _timer = None

    def modal(self, context, event):
        if event.type in {'ESC'}:
            self.cancel(context)
            s.close()
            objA.location = (0, 0, 0)
            objB.location = (0, 0, 0)
            return {'CANCELLED'}

        if event.type == 'TIMER':
            data, addr = s.recvfrom(16)
            data = struct.unpack('dd', data)
            objA_x = data[0]
            objB_x = data[1]

            objA.location = (objA_x, 0, 0) 
            objB.location = (objB_x, 0, 0)


        return {'PASS_THROUGH'}

    def execute(self, context):
        wm = context.window_manager
        self._timer = wm.event_timer_add(0.1, context.window)
        wm.modal_handler_add(self)
        return {'RUNNING_MODAL'}

    def cancel(self, context):
        wm = context.window_manager
        wm.event_timer_remove(self._timer)


def register():
    bpy.utils.register_class(ModalTimerOperator)


def unregister():
    bpy.utils.unregister_class(ModalTimerOperator)


if __name__ == "__main__":
    register()

    # test call
    bpy.ops.wm.modal_timer_operator()

EDIT : As suggested by @Strapicarus

I edited the 2 scripts :

Object A

import bge
import random
import socket
import struct
import math

port = 25001
port2 = 25002
address = "127.0.0.1"
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setblocking(0)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((address, port))

#s2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
#s2.bind((address, port2))


def setPosition(): 
    if not allSensorsArePositive():
        return
    data_bytes, addr = s.recvfrom(16)
    data = struct.unpack('dd', data_bytes)
    obj_x = data[0]
    owner = bge.logic.getCurrentController().owner
    owner.localPosition.x = obj_x
    owner.localPosition.y = 0

def allSensorsArePositive():
    for sensor in bge.logic.getCurrentController().sensors:
        if not sensor.positive:
            return False
    return True

Object B

import bge
import random
import socket
import struct
import math

port = 25001
address = "127.0.0.1"
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setblocking(0)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((address, port))


def setPosition(): 
    if not allSensorsArePositive():
        return
    data_bytes, addr = s.recvfrom(16)
    data = struct.unpack('dd', data_bytes)
    obj_x= data[1]
    owner = bge.logic.getCurrentController().owner
    owner.localPosition.x = obj_x
    owner.localPosition.y = 0

def allSensorsArePositive():
    for sensor in bge.logic.getCurrentController().sensors:
        if not sensor.positive:
            return False
    return True

but I still have an error at Execution:

Blender Game Engine Started
Python module can't be imported - object 'ObjA', controller 'Python':
Traceback (most recent call last):
  File "D:\~\blender\Env3D.blend\pos_obj_A.py", line 12, in <module>
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
OSError: [WinError 10048] Only one usage of each socket address (protocol/network address/port) is normally permitted
Python module can't be imported - object 'ObjB', controller 'Python':
Traceback (most recent call last):
  File "D:\~\blender\Env3D.blend\pos_obj_B.py", line 12, in <module>
    s.bind((address, port))
OSError: [WinError 10048] Only one usage of each socket address (protocol/network address/port) is normally permitted
Blender Game Engine Finished

Ressources:

Controlling drivers with data received via TCP/UDP

Mapping an algorithm to an animation

Controlling drivers with data received via TCP/UDP

$\endgroup$
8
  • $\begingroup$ I would recommend following pep-8 so that others can easier follow your code (e.g. keep capitalised names for class names and not instances). Also, you use data for multiple things (bytes from the socket, as well as the result from struct.unpack()). Furthermore, your question subject and tags indicates the BGE, but your code doesn't use the BGE at all. All in all it's rather confusing. $\endgroup$
    – dr. Sybren
    Commented Dec 18, 2017 at 15:22
  • $\begingroup$ self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.socket.setblocking(0) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind((host,port)) i think the problem is the socket blocking a left this here, sorry dont have time to elaborate a proper answer. $\endgroup$ Commented Dec 18, 2017 at 17:46
  • $\begingroup$ Thank to you 2. I tried your suggestion @Strapicarus, but I still have an issue. There definitely must be a solution to extract many informations once and use them for various object. I thinks this is how a network game would read position of all player once and consume it in each player script right ? $\endgroup$ Commented Dec 18, 2017 at 20:14
  • $\begingroup$ You'll want to have one "thing" reading from the socket. Then in the socket data itself, indicate for each position which object it should be applied to. Then apply it to the object. That's much easier to manage than to have each object trying to read its own network data. $\endgroup$
    – dr. Sybren
    Commented Dec 18, 2017 at 21:07
  • $\begingroup$ You'll want to have one "thing" reading from the socket. Then in the socket data itself, indicate for each position which object it should be applied to. Then apply it to the object. That's much easier to manage than to have each object trying to read its own network data. $\endgroup$
    – dr. Sybren
    Commented Dec 18, 2017 at 21:08

2 Answers 2

1
$\begingroup$

enter image description here

Following Blender Game Engine Tutorial: Networking/Sockets i made a little change it to make it work. You should be able to take what you need i think that is pickle.dumps what you are looking for

Server:

from  bge import logic, types, events
import socket
import pickle

class RemoteKeyboard:

    def __init__(self): 
        self.key_stat={} #client keystroke

    # only on press or relase events   
    def updateState(self, list_key_stat):
        for key, stat in list_key_stat:
            self.key_stat[key]=stat
    #funcion de remplazo        
    def keyDown(self, key_code, status=logic.KX_INPUT_ACTIVE):

        if key_code in self.key_stat:
            #si la tecla presionada esta en la lista
            if self.key_stat[key_code] == status:
                return True

        return False

class User:

    def __init__(self, name):
        #variables del usuario
        self.name=name
        self.keyboard=RemoteKeyboard()

class Server:
    def __init__(self, host="127.0.0.1", port=8989):
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.socket.setblocking(0)
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind((host,port))
        self.addr_user={}
        print('server created')

    def receive(self, scene = logic.getCurrentScene()):
        try:
            data, addr = self.socket.recvfrom(1024)
            print(data)

            if not addr in self.addr_user:

                user = User(data.decode())
                spawner = scene.objects["Spawner"]
                avatar = scene.addObject("avatar", spawner)
                avatar.children[0]["Text"] = user.name
                avatar["user"]=user
                print('user added')
                self.addr_user[addr]=user
            else:
                user=self.addr_user[addr]
                user.keyboard.updateState(pickle.loads(data))
        except Exception as e:
            return

    def send(self):
        scene = logic.getCurrentScene()
        state = {(gobj.name, gobj['user'].name): list(gobj.worldPosition) for gobj in scene.objects if 'avatar' in gobj.name}
        for addr in self.addr_user:
            self.socket.sendto(pickle.dumps(state), addr)

def send(cont):
    cont.owner['server'].send()

def update(cont):
    if not 'server' in cont.owner.getPropertyNames():
        cont.owner['server'] = Server()
    cont.owner['server'].receive()

enter image description here

Client:

from  bge import logic, types, events
from mathutils import Vector
import socket
import pickle

def keyDown(key_code, status=logic.KX_INPUT_ACTIVE):
    #
    if logic.keyboard.events[key_code] == status:
        return True
    return False      

def keyHit(key_code):
    return keyDown(key_code, logic.KX_INPUT_JUST_RELEASED)

class Client:
    def __init__(self, server_ip="127.0.0.1", server_port=8989):
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.socket.setblocking(0)
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.serv_addr=(server_ip, server_port)
        self.entities = {}
        self.main=self.state_sendName

    def state_sendName(self):
        scene = logic.getCurrentScene()
        text = scene.objects["Name"]
        if keyHit(events.ENTERKEY):       
            self.socket.sendto(bytes(text["Text"], "utf-8"), self.serv_addr)
            text.endObject()
            self.main = self.state_loop

    def state_loop(self):
        self.send()
        self.receive()

    def send(self):
        list_key_stat = []
        kevts = logic.keyboard.events
        for k in kevts:
            s = kevts[k]
            if s in (logic.KX_INPUT_ACTIVE, logic.KX_INPUT_JUST_RELEASED):
                list_key_stat.append((k, s))
        if len(list_key_stat):

            self.socket.sendto(pickle.dumps(list_key_stat),self.serv_addr)

    def receive(self, scene = logic.getCurrentScene()):
        try:
            data, addr = self.socket.recvfrom(1024)
            print('from',addr,'data',data)
            state = (pickle.loads(data))
            print(state)
            for k in state:
                if not k in self.entities:
                    spawner = scene.objects['Spawner']
                    print('entity ',k[0])
                    entity = scene.addObject(k[0],spawner)
                    entity.children[0]["Text"] = k[1]
                    self.entities[k] = entity
                else:
                    entity = self.entities[k]
                entity.worldPosition = Vector(state[k])
        except socket.error as e:
            return

def main(cont):
    if not 'client' in cont.owner.getPropertyNames():
        cont.owner['client'] = Client()      
    cont.owner['client'].main()

enter image description here

Disclaimer

This setup has an issue, while looks that work, the oldest connection has a increased lag, i dunno why yet. i have tested it making as far as with 10 clients and the last to connect to the server dont has any lag while the first client to connect to the server has a lag * n clients, and the second client has a lag * n -1.

If anyone have an idea why is happenig leave a comment :)

$\endgroup$
1
  • $\begingroup$ I cannot see the movie now, I will look into it later, and then read your proposal in depth. I my case the udp flow ids now generated by blender, but from a 3rd party tool. I know the detail of if (number and type of data), but I have no control of it. $\endgroup$ Commented Dec 19, 2017 at 8:09
0
$\begingroup$

I finally found a solution to my original problem. As @Monster comment it, I chose an inefficient architecture. Here because name of object are fixed, therefore I can use the following:

scene = bge.logic.getCurrentScene()

objA = scene.objects['objA']
objB = scene.objects['objB']

So now I am using only one script applied to one of the 2 object (ideally I shall create a dummy object, and apply the script on it). No longer 1 script per object. The 2 objects are identified by there name from scene (and not from the script).

Note: i am not able to read data from the udp stream that i am emitting. But this is another story, and no time to play on it.

Here is the full code:

import bge
import bpy
import random
import socket
import struct
import math


port = 25001
port2 = 25002
address = "127.0.0.1"
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setblocking(0)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((address, port))

s2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setblocking(0)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s2.bind((address, port2))

scene = bge.logic.getCurrentScene()

objA = scene.objects['objA']
objB = scene.objects['objB']

def setPosition(): 
    if not allSensorsArePositive():
        return
    data_bytes, addr = s.recvfrom(16)
    data = struct.unpack('dd', data_bytes)
    objA_x = data[0]
    objB_y = data[1]
    # print(objA_x, objB_y)
    objA.localPosition.x = objA_x
    objB.localPosition.y = objB_y

    length = objA.getDistanceTo(objB)
    # print("Distance: ", length)
    data_sent_bytes = struct.pack('d',length)
    s2.sendto(data_sent_bytes, (address, port2))


def allSensorsArePositive():
    for sensor in bge.logic.getCurrentController().sensors:
        if not sensor.positive:
            return False
    return True
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .