断断续续的在线 python 游戏

Choppy online python game

我使用 pygame 和套接字在 python 中编写了 Slither.io 的简单克隆,但我遇到了三个问题:

  1. 当我独自在笔记本电脑上玩游戏时,游戏时断时续。每隔十秒,我的游戏就会卡住一段时间(一毫秒),然后继续。这不是什么大问题,但很烦人。
  2. 当我在本地网络中的两台计算机上玩时,我看到另一台播放器(另一条蛇)也很不稳定。
  3. 最奇怪的问题是当我 运行 我的主笔记本电脑上的服务器然后 运行 我的第二台笔记本电脑上的游戏开始时,几秒钟后崩溃。客户端上的调试器说 pickle 数据在从服务器接收数据时被 t运行 处理。但是当我 运行 我的第二台笔记本电脑上的服务器程序和我的主笔记本电脑上的游戏时,一切都很好。为什么?

我试过了:
问题 1. 更改客户端上的 FPS 和服务器上的 time.sleep
问题 2. 在服务器
上更改 time.sleep 问题3.改变recv()方法的输入值


服务器代码:

import socket
import threading
import pickle
import random
import time
import math

ip = socket.gethostbyname(socket.gethostname())
port = 5555

address = (ip, port)
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

snakes = []
colors = [(80, 0, 0), (0, 80, 0), (0, 0, 140), (80, 80, 0), (80, 0, 80), (0, 80, 80)]
food = []

for i in range(0, 300, 1):
    food.append([random.randint(10, 3290), random.randint(10, 2090), (0, 0, 100), 10, 1])

number_of_players = 0

def start(server, address):
    server.bind(address)

def new_clients(server):
    global number_of_players
    server.listen()
    while True:
        conn, addr = server.accept()
        thread = threading.Thread(target=client, args=(conn, addr, number_of_players))
        thread.start()
        number_of_players += 1
        print(f"Active connections: {threading.activeCount() - 1}")

def client(conn, addr, player):
    print(f"New connection: {addr}\n")
    global snakes
    global colors
    global food
    try:
        snakes.append([])
        name = pickle.loads(conn.recv(1024))
        while True:
            verity = True
            x = random.randint(100, 3200)
            y = random.randint(100, 2000)
            for i in range(0, len(snakes), 1):
                if snakes[i] != []:
                    if math.sqrt((snakes[i][0][0] - x)**2 + (snakes[i][0][1] - y)**2) > snakes[i][0][4]*2:
                        for a in range(0, len(snakes[i][1])):
                            if math.sqrt((snakes[i][1][a][0] - x)**2 + (snakes[i][1][a][1] - y)**2) < snakes[i][0][4]*2:
                                verity = False
                            if verity == False:
                                break
                    else:
                        verity = False
                    if verity == False:
                        break
            if verity == True:
                break

        snakes[player] = [[x, y, random.choice(colors), 4, 30, name, 0, 3], []]
        conn.send(pickle.dumps(snakes[player]))
    except (ConnectionAbortedError, ConnectionResetError, EOFError):
        print("Connection lost")
        print(f"Active connections: {threading.activeCount() - 2}")
        conn.close()
        return
    while True:
        try:
            snakes_to_send = []
            for i in range(0, len(snakes), 1):
                if (i != player) and (len(snakes[i]) != 0):
                    snakes_to_send.append(snakes[i])
            conn.send(pickle.dumps([snakes_to_send, food]))

            received = pickle.loads(conn.recv(8192*4))
            snakes[player] = received[0]

            eaten = received[1]
            for i in range(0, len(eaten), 1):
                try:
                    food.remove(eaten[i])
                    if eaten[i][4] != 5:
                        food.append([random.randint(10, 3290), random.randint(10, 2090), (0, 0, 100), 10, 1])
                except ValueError:
                    continue

            dead_snake = received[2]
            if dead_snake != []:
                for i in range(0, len(dead_snake), 1):
                    food.append(dead_snake[i])

            time.sleep(1/1000)
        except (ConnectionAbortedError, ConnectionResetError, EOFError):
            snakes[player] = []
            print("Connection lost")
            print(f"Active connections: {threading.activeCount() - 2}")
            conn.close()
            break

print(f"Server is starting on IP: {ip} and PORT: {port}")
start(server, address)
new_clients(server)

游戏:

#import
import pygame
import sys
import math
import random
import pygame_textinput
from client import Client

#pygame initialize
pygame.init()
win = pygame.display.set_mode((1100, 700))
pygame.display.set_caption("Slither.io")

#connect to the server
ip = "10.0.0.3"
port = 5555
c = Client(ip, port)
c.server()

#fonts
font = pygame.font.SysFont("comicsansms", 20)
big_font = pygame.font.SysFont("comicsansms", 35)

#time
clock = pygame.time.Clock()

class Snake:
    def __init__(self, x, y, color, length, diameter, name, score, step):
        self.x = x
        self.y = y
        self.color = color
        self.length = length
        self.diameter = diameter
        self.name = name
        self.score = score
        self.step = step
        self.pos_list = []

    #move the snake
    def move(self):
        self.mousex, self.mousey = pygame.mouse.get_pos()
        self.difx = self.mousex - 550
        self.dify = self.mousey - 350
        try:
            self.xstep = math.sqrt((self.step**2)/((self.dify/self.difx)**2 + 1))
            self.ystep = (self.dify/self.difx)*self.xstep
        except ZeroDivisionError:
            self.xstep = 0
            self.ystep = self.step
        if math.sqrt(self.difx**2 + self.dify**2) > self.diameter/3:
            if(self.difx > 0):
                self.x += self.xstep
                self.y += self.ystep
            elif(self.difx < 0):
                self.x -= self.xstep
                self.y -= self.ystep
            else:
                if self.dify > 0:
                    self.y += self.ystep
                else:
                    self.y -= self.ystep
        self.pos_list.append([self.x, self.y])
        if len(self.pos_list) == 100:
            del self.pos_list[0]

    #check if the snake isn't out of the area or not crashed
    def collision(self):
        global run
        if (self.x + self.diameter/3 >= 3299) or (self.x - self.diameter/3 <= 1) or (self.y + self.diameter/3 >= 2099) or (self.y - self.diameter/3 <= 1):
            run = False
        for i in range(0, len(other_snakes), 1):
            if math.sqrt((other_snakes[i].x - snake.x)**2 + (other_snakes[i].y - snake.y)**2) < other_snakes[i].diameter*(2/3):
                run = False
        for i in range(0, len(other_segments), 1):
            if math.sqrt((other_segments[i].x - snake.x)**2 + (other_segments[i].y - snake.y)**2) < other_segments[i].diameter*(2/3):
                run = False

    #draw all snakes
    def draw(self):
        pygame.draw.rect(win, self.color, [self.x - snake.x + 550 - self.diameter/2, self.y - snake.y + 350 - self.diameter/2, self.diameter, self.diameter], border_radius=int(self.diameter/2))

class Segment(Snake):
    def __init__(self, x, y, diameter, step, circle):
        self.x = x
        self.y = y
        self.color = (80, 80, 80)
        self.diameter = diameter
        self.step = step
        self.circle = circle
        self.pos_list = []

    def move(self):
        self.diameter = snake.diameter
        try:
            self.x = self.circle.pos_list[-int(self.diameter/self.step)][0]
            self.y = self.circle.pos_list[-int(self.diameter/self.step)][1]
        except IndexError:
            self.x = self.circle.pos_list[0][0]
            self.y = self.circle.pos_list[0][1]
        self.pos_list.append([self.x, self.y])
        if len(self.pos_list) == 100:
            del self.pos_list[0]

class Food(Snake):
    def __init__(self, x, y, color, diameter, points):
        self.x = x
        self.y = y
        self.color = color
        self.diameter = diameter
        self.points = points

other_snakes = []
segments = []
other_segments = []
food = []
eaten = []
dead_snake = []

#get other snakes
def return_snakes():
    global other_snakes
    global other_segments
    global food
    other_snakes = []
    other_segments = []
    food = []
    message = c.receive_message()
    received = message[0]
    for i in range(0, len(received), 1):
        s = Snake(received[i][0][0], received[i][0][1], received[i][0][2], received[i][0][3], received[i][0][4], received[i][0][5], received[i][0][6], snake.step)
        other_snakes.append(s)
        for a in range(0, len(received[i][1]), 1):
            sg = Segment(received[i][1][a][0], received[i][1][a][1], s.diameter, snake.step, 0)
            other_segments.append(sg)
    rec_fd = message[1]
    for i in range(0, len(rec_fd), 1):
        f = Food(rec_fd[i][0], rec_fd[i][1], rec_fd[i][2], rec_fd[i][3], rec_fd[i][4])
        food.append(f)

#send the snake to server
def send_snake():
    global snake
    global segments
    global food
    to_send = [[[snake.x, snake.y, snake.color, snake.length, snake.diameter, snake.name, snake.score], []]]
    for i in range(0, len(segments), 1):
        to_send[0][1].append([segments[i].x, segments[i].y])
    to_send.append(eaten)
    to_send.append(dead_snake)
    c.send_message(to_send)

#function for ordering snakes by score
def order(o):
    return o["score"]

#draw screen
def screen():
    for i in range(0, 3301, 100):
        pygame.draw.line(win, (60, 60, 60), [i - snake.x + 550, 0 - snake.y + 350], [i - snake.x + 550, 2100 - snake.y + 350], 1)
    for i in range(0, 2101, 100):
        pygame.draw.line(win, (60, 60, 60), [0 - snake.x + 550, i - snake.y + 350], [3300 - snake.x + 550, i - snake.y + 350], 1)

#enter your nickname
textinput = pygame_textinput.TextInput(font_family="comicsansms", max_string_length=12)
while True:
    win.fill((100, 100, 100))
    events = pygame.event.get()
    for event in events:
        keys = pygame.key.get_pressed()
        if (event.type == pygame.QUIT) or keys[pygame.K_ESCAPE] or keys[pygame.K_F4] and pygame.key.get_mods() & pygame.KMOD_ALT:
            sys.exit()
    if keys[pygame.K_RETURN]:
        break
    name = big_font.render("Nickname:", True, (0, 0, 0))
    center = name.get_rect(center=(400, 350))
    win.blit(name, center)
    textinput.update(events)
    win.blit(textinput.get_surface(), (500, 325))
    pygame.display.update()

c.send_message(textinput.get_text())
received = c.receive_message()
snake = Snake(received[0][0], received[0][1], received[0][2], received[0][3], received[0][4], received[0][5], received[0][6], received[0][7])

segment = Segment(snake.x, snake.y, snake.diameter, snake.step, snake)
segments.append(segment)
for i in range(0, snake.length - 2, 1):
    segment = Segment(snake.x, snake.y, snake.diameter, snake.step, segment)
    segments.append(segment)

while True:
    run = True
    while run:
        eaten = []
        #quit game
        for event in pygame.event.get():
            keys = pygame.key.get_pressed()
            if (event.type == pygame.QUIT) or keys[pygame.K_ESCAPE] or keys[pygame.K_F4] and pygame.key.get_mods() & pygame.KMOD_ALT:
                sys.exit()

        return_snakes()

        snakes = [{"name": snake.name, "score": snake.score}]
        for i in range(0, len(other_snakes), 1):
            snakes.append({"name": other_snakes[i].name, "score": other_snakes[i].score})

        win.fill((100, 100, 100))
        screen()

        #draw all food
        for i in range(0, len(food), 1):
            food[i].draw()

        #draw the other snakes and describe
        for i in range(0, len(other_segments), 1):
            other_segments[i].draw()

        for i in range(0, len(other_snakes), 1):
            other_snakes[i].draw()

            name = font.render(other_snakes[i].name, True, (0, 0, 0))
            center = name.get_rect(center=(other_snakes[i].x - snake.x + 550, other_snakes[i].y - snake.y + 350))
            win.blit(name, center)

        #move, draw snake and check if the snake is alive
        snake.move()
        for i in range(0, len(segments), 1):
            segments[i].move()
        for i in range(0, len(segments), 1):
            segments[i].draw()
        snake.draw()
        snake.collision()

        #check if the snake ate food and add score
        for i in range(len(food) - 1, -1, -1):
            if math.sqrt((food[i].x - snake.x)**2 + (food[i].y - snake.y)**2) < (snake.diameter/2 + food[i].diameter/2):
                snake.score += food[i].points
                snake.length = int(snake.score/10) + 4
                if snake.length - 1 > len(segments):
                    segment = Segment(snake.x, snake.y, snake.diameter, snake.step, segment)
                    segments.append(segment)
                snake.diameter = int(snake.score/50)*0.5 + 30
                eaten.append([food[i].x, food[i].y, food[i].color, food[i].diameter, food[i].points])

        #describe the snake
        name = font.render(snake.name, True, (0, 0, 0))
        center = name.get_rect(center=(550, 350))
        win.blit(name, center)

        send_snake()

        #sort the snakes by score
        snakes.sort(key=order, reverse=True)

        #create the table
        for i in range(0, len(snakes), 1):
            table = font.render(str(snakes[i]["name"]), True, (0, 0, 0))
            win.blit(table, (900, 50 + i*30))
        for i in range(0, len(snakes), 1):
            table = font.render(str(snakes[i]["score"]), True, (0, 0, 0))
            win.blit(table, (1050, 50 + i*30))
        for i in range(0, len(snakes), 1):
            table = font.render(str(i + 1) + ".", True, (0, 0, 0))
            win.blit(table, (870, 50 + i*30))

        pygame.display.update()
        clock.tick(140)

    return_snakes()

    #create food from head of dead snake
    ds = [snake.x, snake.y, snake.color, 15, 5]
    dead_snake.append(ds)

    #create food from segments of dead snake
    for i in range(0, len(segments), 1):
        ds = [segments[i].x, segments[i].y, snake.color, 15, 5]
        dead_snake.append(ds)

    send_snake()
    dead_snake = []

    clock.tick(140)

    c.client.close()

    while True:
        #quit game
        for event in pygame.event.get():
            keys = pygame.key.get_pressed()
            if (event.type == pygame.QUIT) or keys[pygame.K_ESCAPE] or keys[pygame.K_F4] and pygame.key.get_mods() & pygame.KMOD_ALT:
                sys.exit()
        if keys[pygame.K_SPACE]:
            c = Client(ip, port)
            c.server()
            c.send_message(snake.name)
            received = c.receive_message()
            snake = Snake(received[0][0], received[0][1], received[0][2], received[0][3], received[0][4], received[0][5], received[0][6], received[0][7])
            segments = []
            segment = Segment(snake.x, snake.y, snake.diameter, snake.step, snake)
            segments.append(segment)
            for i in range(0, snake.length - 2, 1):
                segment = Segment(snake.x, snake.y, snake.diameter, snake.step, segment)
                segments.append(segment)
            break

        win.fill((100, 100, 100))
        screen()

        #press space bar to continue
        cont = big_font.render("Press space bar to continue", True, (0, 0, 0))
        center = cont.get_rect(center=(550, 350))
        win.blit(cont, center)

        pygame.display.update()
        clock.tick(140)

客户端class文件:

import socket
import pickle

ip = "10.0.0.3"
port = 5555

class Client:
    def __init__(self, ip, port):
        self.ip = ip
        self.port = port
        self.address = (self.ip, self.port)
        self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    def server(self):
        try:
            self.client.connect(self.address)
            print(f"Successfully connected to {self.address}")
        except:
            pass

    def send_message(self, message):
        self.client.sendall(pickle.dumps(message))

    def receive_message(self):
        return pickle.loads(self.client.recv(8192*4))

这是我在这里使用的 pygame_textinput 库。

您的第三个问题(被截断的 pickle 数据)是因为您使用的是 TCP,并且您正在对任何 recv returns 进行 unpickling。您可能会想,每当您 send 某事时,接收方调用 recv,return 就是完全相同的事情,但实际上并非如此。 TCP 将您的数据拆分成数据包,因此接收方可能不会同时接收到所有数据。

例如,如果您发送“abcdefgh”,然后单独发送“ijkl”,则允许第一个接收到 return“abcd”,第二个接收到 return“efghijkl”。或者第一个可以 return "ab",第二个可以 return "cde",第三个可以 return "fghijkl",等等。

你必须设计一种方法让接收者知道什么时候停止接收。例如,如果您先发送“8abcdefgh”,然后发送“4ijkl”,接收方可能会收到“8abcdefgh4ij”,然后它知道“8abcdefgh”是一个“发送”(因为它以 8 字节开始,然后是 8 个字节)并且它知道“4ij”是下一个“发送”的开始,但它不是全部(因为它以 4 开头,但没有 4 个字节)。

另一种方法是在每条消息后发送一个特殊字符,如换行符(回车键)。这可能不适用于泡菜,因为泡菜中可以有换行符。但是你可以选择 pickles 没有的另一个字节,比如 0xFF。然后接收方知道继续接收直到它看到字节 0xFF。