下面是一个完整的 Python WebSockets 游戏服务器实现,它每 100ms 接收并广播玩家位置信息。这个服务器使用 websockets
和 asyncio
库来处理 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 客户端示例,可以连接到上面的 WebSocket 服务器并显示玩家位置:
html
WebSocket 游戏客户端
WebSocket 游戏客户端
使用方向键或WASD移动你的角色
状态: 未连接
玩家ID: -
在线玩家: 0
聊天
html 4
在画布上打开
game_server.py
game_client.html
pip install websockets
python game_server.py
game_client.html
文件玩家管理
位置同步
聊天系统
可靠性
添加游戏物理
游戏进程
优化
安全性
这个实现提供了一个功能完整的实时多人游戏的基础框架,您可以根据需要进一步扩展和改进。