Basic multiplayer game best practive

Hi, i create a small example of a multiplayer game, a player is a cube and it’s can be move.

i use the python socket lib, I would like to know if it was the right way to do what I want or not

the client code:

from direct.showbase.ShowBase import ShowBase
from panda3d.core import TextureStage, TexGenAttrib
import socket
import threading

# A class to handle the client side of the network communication
class Client:

    def __init__(self):
        # The IP address and port of the server
        self.host = "127.0.0.1"
        self.port = 9999

        # The socket object to communicate with the server
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        # A flag to indicate if the connection is established
        self.connected = False

        # A thread to receive messages from the server
        self.recv_thread = None

    # A method to connect to the server
    def connect(self):
        try:
            # Connect to the server
            self.sock.connect((self.host, self.port))
            print("Connected to server")

            # Set the connected flag to True
            self.connected = True

            # Start the receive thread
            self.recv_thread = threading.Thread(target=self.receive)
            self.recv_thread.start()
        
        except Exception as e:
            # Print any error message and close the socket
            print(e)
            self.sock.close()

    # A method to send a message to the server
    def send(self, msg):
        if self.connected:
            try:
                # Encode the message as bytes and send it through the socket
                data = msg.encode()
                self.sock.send(data)
            
            except Exception as e:
                # Print any error message and close the socket and connection
                print(e)
                self.sock.close()
                self.connected = False
    
    # A method to receive messages from the server
    def receive(self):
        while self.connected:
            try:
                # Receive data from the server as bytes and decode it as a string
                data = self.sock.recv(1024)
                msg = data.decode()

                # Split the message by commas and convert it to floats
                x, y, z = map(float, msg.split(","))

                # Set the position of the cube according to the message
                app.cube.setPos(x, y, z)
            
            except Exception as e:
                # Print any error message and close the socket and connection
                print(e)
                self.sock.close()
                self.connected = False

# A class to handle keyboard input and move an object
class KeyboardMovement:

    def __init__(self, obj):
        # The object to move
        self.obj = obj

        # The speed of movement and rotation
        self.move_speed = 0.1
        self.rotate_speed = 5

        # The keys to press for each direction
        self.keys = {
            "forward": "w",
            "backward": "s",
            "left": "a",
            "right": "d",
            "up": "q",
            "down": "e"
        }

        # A dictionary to store the state of each key
        self.key_state = {}
        for key in self.keys.values():
            self.key_state[key] = False

        # Accept the events for key press and release
        for key in self.keys.values():
            base.accept(key, self.setKeyState, [key, True])
            base.accept(key + "-up", self.setKeyState, [key, False])

    # A method to set the state of a key
    def setKeyState(self, key, state):
        # Set the state of the key in the dictionary
        self.key_state[key] = state

        # Send a message to the server with the key and state
        msg = f"{key},{state}"
        app.client.send(msg)

    # A method to update the movement of the object
    def update(self):
        # Get the current position and orientation of the object
        x, y, z = self.obj.getPos()
        h, p, r = self.obj.getHpr()

        # Move and rotate the object according to the key state
        if self.key_state[self.keys["forward"]]:
            y += self.move_speed
        if self.key_state[self.keys["backward"]]:
            y -= self.move_speed
        if self.key_state[self.keys["left"]]:
            x -= self.move_speed
        if self.key_state[self.keys["right"]]:
            x += self.move_speed
        if self.key_state[self.keys["up"]]:
            z += self.move_speed
        if self.key_state[self.keys["down"]]:
            z -= self.move_speed
        
        # Set the new position and orientation of the object
        self.obj.setPos(x, y, z)
        

# A class to handle the main application logic
class MyApp(ShowBase):

    def __init__(self):
        ShowBase.__init__(self)

        # Create a cube and load a cube map texture on it
        self.cube = loader.loadModel("models/box")
        tex = loader.loadCubeMap("models/skybox_#.png")
        self.cube.setTexture(tex)
        self.cube.reparentTo(render)

        # Enable texture coordinate generation for the cube
        ts = TextureStage.getDefault()
        self.cube.setTexGen(ts, TexGenAttrib.MWorldCubeMap)

        # Create a keyboard movement object for the cube
        self.keyboard = KeyboardMovement(self.cube)

        # Create a client object and connect to the server
        self.client = Client()
        self.client.connect()

        # Add a task to update the keyboard movement every frame
        self.taskMgr.add(self.keyboard.update, "keyboard_update")

# Create an instance of the application and run it
app = MyApp()
app.run()

the server code:

import socket
import select

# The IP address and port of the server
host = "127.0.0.1"
port = 9999

# The socket object to listen for incoming connections
server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Bind the socket to the address and port and start listening
server_sock.bind((host, port))
server_sock.listen()
print("Server listening on", host, port)

# A list of sockets to monitor for events
sockets_list = [server_sock]

# A dictionary to store the position of each client
clients_pos = {}

# A loop to handle network events
while True:
    # Use select to get the list of sockets that are ready for read, write or error
    read_sockets, write_sockets, error_sockets = select.select(sockets_list, [], sockets_list)

    # Iterate over the read sockets
    for sock in read_sockets:
        # If the socket is the server socket, it means a new connection is requested
        if sock == server_sock:
            # Accept the connection and get the client socket and address
            client_sock, client_addr = server_sock.accept()

            # Add the client socket to the list of sockets to monitor
            sockets_list.append(client_sock)

            # Print a message to indicate a new connection
            print("New connection from", client_addr)

            # Initialize the position of the client to (0, 0, 0)
            clients_pos[client_sock] = (0.0, 0.0, 0.0)
        
        # Otherwise, it means an existing client has sent some data
        else:
            try:
                # Receive data from the client as bytes and decode it as a string
                data = sock.recv(1024)
                msg = data.decode()

                # Split the message by commas and convert it to floats or booleans
                key, state = msg.split(",")
                state = state == "True"

                # Get the current position of the client from the dictionary
                x, y ,z = clients_pos[sock]

                # Update the position of the client according to
                # the key and state
                if key == "w" and state:
                    y += 0.1
                if key == "s" and state:
                    y -= 0.1
                if key == "a" and state:
                    x -= 0.1
                if key == "d" and state:
                    x += 0.1
                if key == "q" and state:
                    z += 0.1
                if key == "e" and state:
                    z -= 0.1
                
                # Set the new position of the client in the dictionary
                clients_pos[sock] = (x, y, z)

                # Format the position as a string with commas
                pos_str = f"{x},{y},{z}"

                # Encode the position as bytes and send it to the client
                data = pos_str.encode()
                sock.send(data)
            
            except Exception as e:
                # Print any error message and close the socket and connection
                print(e)
                sock.close()

                # Remove the socket from the list of sockets to monitor
                sockets_list.remove(sock)

                # Remove the position of the client from the dictionary
                clients_pos.pop(sock)

    # Iterate over the error sockets
    for sock in error_sockets:
        # Close the socket and connection
        sock.close()

        # Remove the socket from the list of sockets to monitor
        sockets_list.remove(sock)

        # Remove the position of the client from the dictionary
        clients_pos.pop(sock)
2 Likes