简体   繁体   English

在 PyGame 中实现 WebSocket Server 以通过 HTML WebSocket Client 控制对象

[英]Implement a WebSocket Server in PyGame to control objects via a HTML WebSocket Client

General idea大概的概念

I successfully configured a raspberry pi as an access point such that I can connect via WIFI with my mobile phone or laptop.我成功地将树莓派配置为接入点,这样我就可以通过 WIFI 与我的手机或笔记本电脑进行连接。 Now, I would like to run PyGame on the raspberry pi, which is connected to a screen, and control objects in a game via a mobile phone or laptop that is connected to the raspberry pi.现在,我想在连接到屏幕的树莓派上运行 PyGame,并通过连接到树莓派的手机或笔记本电脑控制游戏中的对象。

In the following, I provide simple working examples first and then show what did not work.在下面,我首先提供简单的工作示例,然后展示哪些不起作用。

Testing the WebSocket Server worked测试 WebSocket 服务器是否有效

To provide some content to the clients, I installed an nginx server on the raspberry pi.为了向客户端提供一些内容,我在树莓派上安装了一个 nginx 服务器。 When I open a browser with the IP address of the raspberry pi (192.168.4.1), the index page appears.当我用树莓派的IP地址(192.168.4.1)打开浏览器时,出现索引页。

Then, to test a WebSocket Server, I wrote a simple python script based on the websockets package :然后,为了测试 WebSocket 服务器,我基于websockets 包编写了一个简单的 python 脚本:

import websockets
import asyncio

# handler processes the message and sends "Success" back to the client
async def handler(websocket, path):
    async for message in websocket:
        await processMsg(message)
        await websocket.send("Success")

async def processMsg(message):
    print(f"[Received]: {message}")

async def main():
    async with websockets.serve(handler, "192.168.4.1", 6677):
        await asyncio.Future() # run forever

if __name__ == "__main__":
    asyncio.run(main())

I tested the server by setting up an HTML page, which connects to the WebSocket Server via a Javascript file and implements a button to send a string to the server:我通过设置一个 HTML 页面来测试服务器,该页面通过一个 Javascript 文件连接到 WebSocket 服务器并实现一个按钮来向服务器发送一个字符串:

<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSocket Test</title>
    <script type="text/javascript" src="static/client.js"></script>
  </head>
  <body>
    <div>
      <h2>WebSocket Test</h2>
      <input type="button" name="send" value="Send Hello!" onClick="sendHello()">
    </div>
  </body>
</html>

and the client.js file:和 client.js 文件:

let socket = new WebSocket("ws://192.168.4.1:6677/");

socket.onopen = function(e) {
  console.log("[open] Connection established");
  console.log("[send] Sending to server");
  socket.send("Web connection established")
};

socket.onmessage = function(event) {
  console.log(`[message] Data received from server: ${event.data}`);
};

socket.onclose = function(event) {
  if (event.wasClean) {
    console.log(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`);
  } else {
    console.log('[close] Connection died!')
  }
};

socket.onerror = function(error) {
  console.log(`[error] ${error.message}`);
};

function sendHello() {
  console.log("[send] Sending to server");
  socket.send("Hello!");
};

With these simple example files I could successfully establish a persistent connection and exchange data between server and client.通过这些简单的示例文件,我可以成功地在服务器和客户端之间建立持久连接并交换数据。

Adding the WebSocket Server to PyGame did not work将 WebSocket 服务器添加到 PyGame 不起作用

To test the WebSocket Server with PyGame, I set up a simple game only displaying a blue circle:为了使用 PyGame 测试 WebSocket 服务器,我设置了一个仅显示蓝色圆圈的简单游戏:

# import and init pygame library
import pygame
pygame.init()

# screen dimensions
HEIGHT = 320
WIDTH = 480

# set up the drawing window
screen = pygame.display.set_mode([WIDTH,HEIGHT])

# run until the user asks to quit
running = True
while running:
    # did the user close the window
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # fill the background with white
    screen.fill((255,255,255))

    # draw a solid blue circle in the center
    pygame.draw.circle(screen, (0,0,255), (int(WIDTH/2),int(HEIGHT/2)), 30)

    # flip the display
    pygame.display.flip()

pygame.quit()

The game is running as it should, displaying a blue circle on a white background.游戏正常运行,在白色背景上显示一个蓝色圆圈。

The problem is that when I want to add the WebSocket Server to the game, either one is blocking the execution of the other part, ie, I can only run the game without an active server or I can run the WebSocket server but the game is not showing up.问题是,当我想将 WebSocket 服务器添加到游戏中时,其中一个阻止了另一部分的执行,即我只能在没有活动服务器的情况下运行游戏,或者我可以运行 WebSocket 服务器但游戏是没有出现。 For example, putting the WebSocket server in front of the game loop like this例如,像这样将 WebSocket 服务器放在游戏循环的前面

# WS server
async def echo(websocket, path):
    async for message in websocket:
        msg = message
        print(f"[Received] {message}")
        await websocket.send(msg)

async def server():
    async with websockets.serve(echo, "192.168.4.1", 6677):
        await asyncio.Future()

asyncio.ensure_future(server())

or including the game-loop inside the server:或在服务器内包含游戏循环:

async def server():
    async with websockets.serve(echo, "192.168.4.1", 6677):
        #await asyncio.Future()

        # set up the drawing window
        screen = pygame.display.set_mode([WIDTH,HEIGHT])

        # run until the user asks to quit
        running = True
        while running:
            #pygame code goes here

By an extensive Google search I figured out that PyGame and the asyncio package (websockets is based on asyncio) cannot simply work together as I have to somehow take care of, both, the asyncio-loop and the game-loop manually.通过广泛的谷歌搜索,我发现 PyGame 和 asyncio 包(websockets 基于 asyncio)不能简单地协同工作,因为我必须以某种方式手动处理异步循环和游戏循环。

I hope someone can help me dealing with this problem ...我希望有人能帮我解决这个问题......

Thanks to a colleague of mine I figured out the solution to get Pygame running including a Websockets server and responding to buttons pressed on mobile clients.感谢我的一位同事,我找到了让 Pygame 运行的解决方案,包括 Websockets 服务器和响应移动客户端上按下的按钮。 The important point is to run the Websockets server in a different thread and properly handle events.重要的一点是在不同的线程中运行 Websockets 服务器并正确处理事件。

Here is the code of server.py .这是server.py的代码。 I changed the IP address to local host (127.0.0.1) in order to test the code on my laptop.我将 IP 地址更改为本地主机 (127.0.0.1),以便在我的笔记本电脑上测试代码。

import websockets
import asyncio
import pygame

IPADDRESS = "127.0.0.1"
PORT = 6677

EVENTTYPE = pygame.event.custom_type()

# handler processes the message and sends "Success" back to the client
async def handler(websocket, path):
    async for message in websocket:
        await processMsg(message)
        await websocket.send("Success")

async def processMsg(message):
    print(f"[Received]: {message}")
    pygame.fastevent.post(pygame.event.Event(EVENTTYPE, message=message))

async def main(future):
    async with websockets.serve(handler, IPADDRESS, PORT):
        await future  # run forever

if __name__ == "__main__":
    asyncio.run(main())

The important part here is the method pygame.fastevent.post to trigger a Pygame event.这里的重要部分是pygame.fastevent.post方法来触发 Pygame 事件。

Then, in game.py the server is initiated in a different thread:然后,在game.py 中,服务器在不同的线程中启动:

# import and init pygame library
import threading
import asyncio
import pygame
import server


def start_server(loop, future):
    loop.run_until_complete(server.main(future))

def stop_server(loop, future):
    loop.call_soon_threadsafe(future.set_result, None)


loop = asyncio.get_event_loop()
future = loop.create_future()
thread = threading.Thread(target=start_server, args=(loop, future))
thread.start()

pygame.init()
pygame.fastevent.init()

# screen dimensions
HEIGHT = 320
WIDTH = 480

# set up the drawing window
screen = pygame.display.set_mode([WIDTH, HEIGHT])

color = pygame.Color('blue')
radius = 30
x = int(WIDTH/2)

# run until the user asks to quit
running = True
while running:
    # did the user close the window
    for event in pygame.fastevent.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == server.EVENTTYPE:
            print(event.message)
            color = pygame.Color('red')
            x = (x + radius / 3) % (WIDTH - radius * 2) + radius

    # fill the background with white
    screen.fill((255,255,255))

    # draw a solid blue circle in the center
    pygame.draw.circle(screen, color, (x, int(HEIGHT/2)), radius)

    # flip the display
    pygame.display.flip()

print("Stoping event loop")
stop_server(loop, future)
print("Waiting for termination")
thread.join()
print("Shutdown pygame")
pygame.quit()

Be careful to properly close the thread running the Websockets Server when you want to exit Pygame!当您想退出 Pygame 时,请注意正确关闭运行 Websockets Server 的线程!

Now, when you use the HTML file including the client.js script I posted in the question, the blue circle should change its color to red and move to the right every time you press the "Send hello" button.现在,当您使用包含我在问题中发布的 client.js 脚本的 HTML 文件时,每次按下“发送您好”按钮时,蓝色圆圈的颜色应更改为红色并向右移动。

I hope this helps everyone who is trying to setup an online multiplayer game using Pygame.我希望这可以帮助所有尝试使用 Pygame 设置在线多人游戏的人。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM