Мультиплеер PyGame. Пишем сервер и клиент. Часть 1

от автора

Это моя первая статья и серия из статьей о написании мультиплеерной игры на Python с использованием библиотеки Pygame. В ходе этих «серий» мы напишем небольшую 2Д игру-стрелялку с мультиплеерным режимом.

Как работает мультиплеер? Введение

В мультиплеерных играх клиенты игры взаимодействуют с сервером, получая данные от него и отправляя новые(свои). Request — отправка своих данных, response — полученные данные от сервера. Клиент может отправить данные о своем передвижении, сервер проверяет это и меняет позицию игрока. А сервер постоянно делится данными с клиентами, отправляя им общие данные о позиции, полете и прочей игровой информацией.

Пишем базовую часть игры

Для начала напишем код для игры, чтобы создавалось окно, и локально выводился спрайт игрока. Использовать для этого как говорил ранее мы будем библиотеку Pygame. Она предназначена для создания легких игр в 2Д пространстве, и используется в частности программистами начального и среднего уровней, которые заинтересованы в создании игр и улучшении собственных навыков кодирования на Python.

import pygame from player import Player   pygame.init() # Инициализируем pygame   sсreen = pygame.display.set_mode((800, 600)) # Создаем окно с разрешением 800x600   clock = pygame.time.Clock() # Создаем объект для работы со временем внутри игры  player = Player()   while True:     for event in pygame.event.get(): # Перебираем все события которые произошли с программой         if event.type == pygame.QUIT: # Проверяем на выход из игры             exit()      sсreen.fill((0, 0, 0)) # Заполняем экран черным     sсreen.blit(player.image, player.rect) # Рисуем игрока     pygame.display.update() # Обновляем дисплей       clock.tick(60) # Ограничиваем частоту кадров игры до 60

Переходим к созданию класса игрока. В нем мы пропишем только отрисовку, и загрузим изображение спрайта, пока что мы сделаем ему координаты по центру экрана, чтобы мы увидели этого человечка.

import pygame  # Создаем класс, который взаимствован от класса Sprite внутри pygame class Player(pygame.sprite.Sprite):      # Инициализация     def __init__(self):         pygame.sprite.Sprite.__init__(self)          # Загружаем спрайт игрока         self.image = pygame.image.load("player.png").convert_alpha()         # (400, 300) размеры экрана / 2, не стал делать в переменной         self.rect = self.image.get_rect(center=(400, 300))

В итоге у нас получается окно с человечком по центру. Пока что он один, но скоро мы найдем ему друзей, и напишем сервер для обработки его желаний.

Спрайт игрока взял у своего давнего друга fisash, ссылка на его ВК - vk.com/fisash

Спрайт игрока взял у своего давнего друга fisash, ссылка на его ВК — vk.com/fisash

Сервер

Напишем сервер для того чтобы игроки (клиенты) могли взаимодействовать друг с другом через него, данные будут приниматься и отправлятся в формате JSON.

Весь код будет полностью, строчки почти все расписаны мной комментариями.

import socket from threading import Thread import json  HOST, PORT = 'localhost', 8080 # Адрес сервера MAX_PLAYERS = 2 # Максимальное кол-во подключений  class Server:      def __init__(self, addr, max_conn):         self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)         self.sock.bind(addr) # запускаем сервер от заданного адреса          self.max_players = max_conn         self.players = [] # создаем массив из игроков на сервере                  self.sock.listen(self.max_players) # устанавливаем максимальное кол-во прослушиваний на сервере         self.listen() # вызываем цикл, который отслеживает подключения к серверу      def listen(self):         while True:             if not len(self.players) >= self.max_players: # проверяем не превышен ли лимит                  # одобряем подключение, получаем взамен адрес и другую информацию о клиенте                 conn, addr = self.sock.accept()                  print("New connection", addr)                  Thread(target=self.handle_client, args=(conn,)).start() # Запускаем в новом потоке проверку действий игрока      def handle_client(self, conn):          # Настраиваем стандартные данные для игрока         self.player = {             "id": len(self.players),             "x": 400,             "y": 300         }         self.players.append(self.player) # добавляем его в массив игроков          while True:             try:                 data = conn.recv(1024) # ждем запросов от клиента                  if not data: # если запросы перестали поступать, то отключаем игрока от сервера                     print("Disconnect")                     break                  # загружаем данные в json формате                 data = json.loads(data.decode('utf-8'))                  # запрос на получение игроков на сервере                 if data["request"] == "get_players":                     conn.sendall(bytes(json.dumps({                         "response": self.players                     }), 'UTF-8'))                  # движение                 if data["request"] == "move":                      if data["move"] == "left":                         self.player["x"] -= 1                     if data["move"] == "right":                         self.player["x"] += 1                     if data["move"] == "up":                         self.player["y"] -= 1                     if data["move"] == "down":                         self.player["y"] += 1             except Exception as e:                 print(e)                 break          self.players.remove(self.player) # если вышел или выкинуло с сервера - удалить персонажа  if __name__ == "__main__":     server = Server((HOST, PORT), MAX_PLAYERS)

В данном коде мы прослушиваем заданный айпи адрес, и ищем новых подключений. Если найден игрок для подключения, то запускаем функцию в новом потоке которая проверяет запросы игрока и отвечает на них.

Клиент. Отображение всех игроков

Так, мы написали сервер. Но с одним сервером игрок не сможет ничего сделать, для сервера нужно написать клиент. Чтобы подключиться к нему и получать результаты наших запросов, надо написать клиент, который будет получать информацию о других игроках, и перемещаться в мире.

import socket import json from threading import Thread  class Client:      def __init__(self, addr):         self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)         self.sock.connect(addr) # подключаемся к айпи адресу сервера          self.players = [] # Создаем массив для хранения данных об игроках         Thread(target=self.get_players).start() # Делаем новый поток с циклом, в которым берем данные об игроках      def get_players(self):         while True:             self.sock.sendall(bytes(json.dumps({                 "request": "get_players"             }), 'UTF-8')) # Отправляем серверу запрос для получения игроков              received = json.loads(self.sock.recv(1024).decode('UTF-8'))              self.players = received["response"] # сохраняем результат запроса в переменную      def move(self, side):         self.sock.sendall(bytes(json.dumps({             "request": "move",             "move": side         }), 'UTF-8')) # Отправляем серверу запрос для получения игроков

В этом коде мы прописали подключение к серверу и функции для обращения к нему. В нашем случае это get_players, который отвечает за получение всех игроков в мире, и move, отвечающая за перемещение в этом же мире.

Добавляем в класс Player возможность задать координаты.

# Создаем класс, который взаимствован от класса Sprite внутри pygame class Player(pygame.sprite.Sprite):      # Инициализация     def __init__(self, pos):         pygame.sprite.Sprite.__init__(self)          # Загружаем спрайт игрока         self.image = pygame.image.load("player.png").convert_alpha()         self.rect = self.image.get_rect(center=pos)

Немного изменим главный файл отображения мира, добавив в него возможность инициализировать клиент, отображать других игроков, и заставлять себя двигаться, используя функции класса Client. Для отображения мы создаем новый спрайт класса Player и перемещаем его в нужную нам позицию.

import pygame from player import Player from client import Client   pygame.init() # Инициализируем pygame  HOST, PORT = "localhost", 8080 # Адрес сервера  client = Client((HOST, PORT)) # Создаем объект клиента   sсreen = pygame.display.set_mode((800, 600)) # Создаем окно с разрешением 800x600   clock = pygame.time.Clock() # Создаем объект для работы со временем внутри игры  while True:     for event in pygame.event.get(): # Перебираем все события которые произошли с программой         if event.type == pygame.KEYDOWN:             if event.key == ord('a'):                 client.move("left")             if event.key == ord('d'):                 client.move("right")             if event.key == ord('w'):                 client.move("up")             if event.key == ord('s'):                 client.move("down")          if event.type == pygame.QUIT: # Проверяем на выход из игры             client.sock.close()             exit()      sсreen.fill((0, 0, 0)) # Заполняем экран черным      for i in client.players:         print(i)         player = Player((i["x"], i["y"]))         sсreen.blit(player.image, player.rect) # Рисуем игрока      pygame.display.update() # Обновляем дисплей       clock.tick(60) # Ограничиваем частоту кадров игры до 60

Вот и все, в итоге мы получаем мультиплеерную игру с возможностью двигать персонажем, и наблюдать за действиями других.

Знаю коммьюнити Хабра, надеюсь, первая в жизни статья обойдется без хейта в мою сторону. Благодарю тех, кто укажет на мои ошибки, в следующий раз попытаюсь их избежать.


ссылка на оригинал статьи https://habr.com/ru/articles/756252/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *