一个完整的python webSockets游戏服务器,每100ms接收并广播玩家位置

Python WebSockets 游戏服务器

下面是一个完整的 Python WebSockets 游戏服务器实现,它每 100ms 接收并广播玩家位置信息。这个服务器使用 websocketsasyncio 库来处理 WebSocket 连接和异步操作。

完整代码

#!/usr/bin/env python3
"""
实时游戏位置广播服务器
每100ms接收玩家位置并广播给所有连接的客户端
"""

import asyncio
import json
import logging
import uuid
import websockets
from websockets.exceptions import ConnectionClosed

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(message)s',
    handlers=[
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

# 存储所有连接的玩家
PLAYERS = {}

# 最后一次广播的时间
last_broadcast_time = 0

# 广播间隔 (毫秒)
BROADCAST_INTERVAL = 100

async def register_player(websocket, player_id=None):
    """
    注册新玩家
    :param websocket: WebSocket连接
    :param player_id: 玩家ID (如果未提供则生成)
    :return: 玩家ID
    """
    if player_id is None:
        player_id = str(uuid.uuid4())
    
    PLAYERS[player_id] = {
        "websocket": websocket,
        "position": {"x": 0, "y": 0, "z": 0},
        "rotation": {"x": 0, "y": 0, "z": 0},
        "last_update": asyncio.get_event_loop().time(),
        "color": f"#{uuid.uuid4().hex[:6]}"  # 随机颜色
    }
    
    logger.info(f"玩家 {player_id} 已连接. 当前玩家数: {len(PLAYERS)}")
    return player_id

async def unregister_player(player_id):
    """
    取消注册玩家
    :param player_id: 玩家ID
    """
    if player_id in PLAYERS:
        del PLAYERS[player_id]
        logger.info(f"玩家 {player_id} 已断开连接. 当前玩家数: {len(PLAYERS)}")

async def broadcast_positions():
    """
    向所有玩家广播位置信息
    """
    if not PLAYERS:
        return
    
    # 准备广播数据
    positions = {
        player_id: {
            "position": player["position"],
            "rotation": player["rotation"],
            "color": player["color"]
        }
        for player_id, player in PLAYERS.items()
    }
    
    message = json.dumps({
        "type": "positions_update",
        "players": positions
    })
    
    # 广播到所有客户端
    disconnected_players = []
    for player_id, player in PLAYERS.items():
        try:
            await player["websocket"].send(message)
        except websockets.exceptions.ConnectionClosed:
            disconnected_players.append(player_id)
    
    # 移除断开连接的玩家
    for player_id in disconnected_players:
        await unregister_player(player_id)

async def broadcast_loop():
    """
    定期广播玩家位置的循环
    """
    while True:
        await broadcast_positions()
        await asyncio.sleep(BROADCAST_INTERVAL / 1000)  # 转换为秒

async def handle_join(websocket, message):
    """
    处理玩家加入请求
    """
    player_id = message.get("player_id")
    
    # 如果玩家已存在且提供了ID,使用现有ID
    # 否则生成新ID
    if player_id in PLAYERS:
        logger.info(f"玩家 {player_id} 重新连接")
    else:
        player_id = await register_player(websocket, player_id)
    
    # 发送加入确认
    await websocket.send(json.dumps({
        "type": "join_ack",
        "player_id": player_id,
        "color": PLAYERS[player_id]["color"]
    }))
    
    # 告诉新玩家关于现有玩家的信息
    await websocket.send(json.dumps({
        "type": "positions_update",
        "players": {
            pid: {
                "position": player["position"],
                "rotation": player["rotation"],
                "color": player["color"]
            }
            for pid, player in PLAYERS.items()
        }
    }))
    
    return player_id

async def handle_position_update(player_id, message):
    """
    处理位置更新消息
    """
    if player_id not in PLAYERS:
        return
    
    # 更新玩家位置
    position = message.get("position", {})
    rotation = message.get("rotation", {})
    
    PLAYERS[player_id]["position"] = {
        "x": position.get("x", PLAYERS[player_id]["position"]["x"]),
        "y": position.get("y", PLAYERS[player_id]["position"]["y"]),
        "z": position.get("z", PLAYERS[player_id]["position"]["z"])
    }
    
    PLAYERS[player_id]["rotation"] = {
        "x": rotation.get("x", PLAYERS[player_id]["rotation"]["x"]),
        "y": rotation.get("y", PLAYERS[player_id]["rotation"]["y"]),
        "z": rotation.get("z", PLAYERS[player_id]["rotation"]["z"])
    }
    
    PLAYERS[player_id]["last_update"] = asyncio.get_event_loop().time()

async def game_server(websocket, path):
    """
    处理WebSocket连接的主函数
    """
    player_id = None
    
    try:
        async for message_str in websocket:
            try:
                message = json.loads(message_str)
                message_type = message.get("type", "")
                
                if message_type == "join":
                    player_id = await handle_join(websocket, message)
                
                elif message_type == "position_update":
                    if player_id is None:
                        player_id = message.get("player_id")
                        if player_id not in PLAYERS:
                            player_id = await register_player(websocket, player_id)
                    
                    await handle_position_update(player_id, message)
                
                elif message_type == "chat":
                    # 处理聊天消息
                    if player_id and "text" in message:
                        chat_message = {
                            "type": "chat",
                            "player_id": player_id,
                            "text": message["text"],
                            "color": PLAYERS[player_id]["color"] if player_id in PLAYERS else "#ffffff"
                        }
                        
                        # 广播聊天消息给所有玩家
                        for pid, player in PLAYERS.items():
                            try:
                                await player["websocket"].send(json.dumps(chat_message))
                            except:
                                pass
            
            except json.JSONDecodeError:
                logger.warning(f"收到无效的JSON消息: {message_str}")
    
    except ConnectionClosed:
        logger.info(f"连接已关闭")
    finally:
        # 确保玩家取消注册
        if player_id:
            await unregister_player(player_id)

async def main():
    """
    主函数 - 启动WebSocket服务器和广播循环
    """
    # 启动广播循环
    broadcast_task = asyncio.create_task(broadcast_loop())
    
    # 启动WebSocket服务器
    host = "0.0.0.0"
    port = 8765
    
    logger.info(f"游戏服务器正在启动于 ws://{host}:{port}")
    
    async with websockets.serve(game_server, host, port):
        await asyncio.Future()  # 保持服务器运行

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        logger.info("服务器正在关闭...")

HTML 客户端示例

以下是一个简单的 HTML 客户端示例,可以连接到上面的 WebSocket 服务器并显示玩家位置:

html




    
    
    WebSocket 游戏客户端
    


    

WebSocket 游戏客户端

使用方向键或WASD移动你的角色

状态: 未连接

玩家ID: -

在线玩家: 0

聊天

html 4

在画布上打开

如何使用

  1. 将 Python 服务器代码保存为 game_server.py
  2. 将 HTML 客户端代码保存为 game_client.html
  3. 安装所需的 Python 依赖:

    pip install websockets
    
  4. 运行服务器:

    python game_server.py
    
  5. 在浏览器中打开 game_client.html 文件
  6. 可以打开多个浏览器窗口,模拟多个玩家

功能特点

  1. 玩家管理

    • 自动生成唯一玩家 ID
    • 支持断开重连(使用本地存储记住玩家 ID)
    • 每个玩家有唯一颜色标识
  2. 位置同步

    • 每 100ms 广播更新所有玩家位置
    • 使用 WASD 或方向键移动角色
    • 支持位置和旋转数据
  3. 聊天系统

    • 玩家可以发送和接收聊天消息
    • 消息显示发送者 ID 和颜色
  4. 可靠性

    • 自动处理连接中断和重连
    • 优雅处理异常和关闭连接

扩展建议

  1. 添加游戏物理

    • 添加碰撞检测
    • 实现简单的物理效果
  2. 游戏进程

    • 添加游戏目标或游戏规则
    • 实现计分系统
  3. 优化

    • 只发送变化的数据以减少带宽使用
    • 添加数据压缩
    • 使用更高效的位置预测算法
  4. 安全性

    • 添加简单的认证系统
    • 服务器端验证以防止作弊

这个实现提供了一个功能完整的实时多人游戏的基础框架,您可以根据需要进一步扩展和改进。

你可能感兴趣的:(Python基础及AI开发,python,游戏,服务器)