目录

1.1 设置游戏窗口

1.2 绘制一个方块

1.3 编写服务端代码

1.4 完善客户端代码

1.5 完整代码下载地址


在本节,我们将通过一个简单的方块移动程序进入多人联机游戏的大门。每个玩家打开游戏窗口后都可以控制一个方块,当某个玩家移动方块后,其余玩家的窗口上会自动更新该玩家的方块位置。运行示例如下:

本项目结构显示如下:

├── client.py         # 客户端代码└── server.py         # 服务端代码

在client.py中我们一共导入了以下几个模块或库:

import sys
import json
import pygame
import socket
from random import randint

在server.py中我们一共导入了以下几个模块或库:

import json
import socket
from threading import Thread

1.1 设置游戏窗口

我们首先要设置好游戏窗口的相关属性,比如窗口标题、宽高以及背景等等。

# client.py
class GameWindow:def __init__(self):self.width = 500self.height = 500self.window = self.init_window()def init_window(self):                      # 1pygame.init()pygame.display.set_caption('移动方块')return pygame.display.set_mode((self.width, self.height))def update_window(self):                    # 2self.window.fill((255, 255, 255))pygame.display.update()def start(self):                            # 3clock = pygame.time.Clock()while True:clock.tick(60)for event in pygame.event.get():if event.type == pygame.QUIT:pygame.quit()sys.exit()self.update_window()if __name__ == "__main__":game = GameWindow()game.start()

代码解释如下:

1. init_window()函数用来初始化pygame窗口的相关属性,我们在该函数中设置了窗口的标题和大小。

2. update_window()函数用来更新窗口,将窗口背景设置为白色,后续我们也会在该函数中不断更新玩家的状态。

3. start()函数是游戏入口,重点是要调用update_window()函数不断更新窗口。

运行结果如下:

1.2 绘制一个方块

游戏窗口设置好了之后,我们就可以往窗口上添加方块了。一个方块代表一个玩家,我们就用Player类来实现方块的相关功能。

class Player:def __init__(self, win, p_id, x, y, color):self.win = win              # 1self.id = p_id              # 2self.dis = 3                # 3self.x = xself.y = yself.width = 100self.height = 100self.color = colordef move(self):                 # 4keys = pygame.key.get_pressed()if keys[pygame.K_LEFT]:self.x -= self.diselif keys[pygame.K_RIGHT]:self.x += self.diselif keys[pygame.K_UP]:self.y -= self.diselif keys[pygame.K_DOWN]:self.y += self.disdef draw(self):                 # 5pygame.draw.rect(self.win, self.color, (self.x, self.y, self.width, self.height))

代码解释如下:

1. Player类接收一个游戏窗口实例,后面我们会在GameWindow类中实例化一个Player对象并传入游戏窗口实例的。

2. 每个玩家都会拥有一个id,该id会在服务端生成并从服务端获取过来。

3. dis变量为方块每次移动的距离。方块的宽高都为100,坐标和颜色是随机的。

4. 根据按键改变方块位置。

5. 将方块绘制到窗口上。

玩家类已经编写好了,接下来就是要在游戏窗口上添加玩家了。

# client.py
class GameWindow:def __init__(self):...self.player = Player(win=self.window,           # 1p_id=None,x=randint(0, self.width - 100),y=randint(0, self.height - 100),color=(randint(0, 200), randint(0, 200), randint(0, 200)))...def update_window(self):self.window.fill((255, 255, 255))self.player.move()                              # 2self.player.draw()pygame.display.update()...

代码解释如下:

1. 实例化一个Player对象并传入相关参数。因为还没有连接到服务端,所以p_id先设置为None。x和y坐标是随机的,减去100(也就是方块的宽高)是为了让方块显示在窗口内。之所以将颜色值设定在0-200之间,是为了防止方块和窗口背景颜色太接近。假如方块颜色特别接近白色,那和窗口背景就混在一起,很难辨别了。

2. 在update_window()函数中调用Player实例对象的move()和draw()方法,不断在游戏窗口中更新自身的状态。

运行结果如下:

1.3 编写服务端代码

服务端的代码逻辑很简单,就是创建套接字等待客户端连接,然后将各个客户端发送过来的玩家数据保存起来,整理之后再发送出去。代码编写如下:

# server.py
import json
import socket
from threading import Threadclass Server:def __init__(self):self.port = 5000                # 1self.host = "127.0.0.1"self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)self.players_data = {}          # 2def start(self):                    # 3self.get_socket_ready()self.handle_connection()def get_socket_ready(self):         # 4self.sock.bind((self.host, self.port))self.sock.listen()print("服务器已准备接收客户端连接")def handle_connection(self):        # 5while True:conn, addr = self.sock.accept()print(f"接收到来自{addr}的连接")conn.send(str(id(conn)).encode("utf-8"))Thread(target=self.handle_message, args=(conn, )).start()def handle_message(self, conn):     # 6while True:try:data = conn.recv(2048)if not data:print("未接收到数据,关闭连接")self.players_data.pop(str(id(conn)))conn.close()breakelse:data = json.loads(data.decode("utf-8"))self.update_one_player_data(data)conn.sendall(json.dumps(self.get_other_players_data(data["id"])).encode("utf-8"))except Exception as e:print(repr(e))breakdef update_one_player_data(self, data):key = data["id"]pos = data["pos"]color = data["color"]self.players_data[key] = {"pos": pos, "color": color}def get_other_players_data(self, current_player_id):data = {}for key, value in self.players_data.items():if key != current_player_id:data[key] = valuereturn dataif __name__ == '__main__':server = Server()server.start()

代码解释如下:

1. 服务端监听的地址为127.0.0.1:5000,所以后续再客户端中编写代码时要连接到这个地址。

2. players_data是一个字典变量,用来存储各个玩家的数据。该字典的各个键是玩家id,值包含玩家的位置和颜色。示例如下:

{"玩家id": {"pos" [x坐标值, y坐标值],"color": (r, g, b)}
}

3. start()函数是程序入口。

4.  在get_socket_ready()函数中,我们让套接字绑定监听了127.0.0.1:5000,准备就绪。

5. 如果服务端收到了来自客户端的连接,那就会将str(id(conn))作为玩家id值发送到客户端,而客户端也会将该值保存到Player对象的id属性中。为了不让连接堵塞,我们使用多线程技术来处理后续的消息通信。

6. 在handle_message()函数中,我们不断接收来自客户端的消息。如果客户端没有发送消息过来(data为空),说明客户端已经被关闭,玩家离开游戏了,那我们就要从players_data变量中删除对应玩家的数据并关闭套接字。如果有消息发送过来(data不为空),那么我们就将该玩家的数据保存或更新到player_data变量中,通过update_one_player_data()函数实现。最后将其他玩家的数据全部发送给该玩家,好让客户端窗口更新其他玩家的位置信息,其他玩家的数据通过get_other_players_data()函数获取。

简而言之:玩家A发送自身数据到服务端,服务端保存并返回除A之外的其他所有玩家的数据。玩家B发送自身数据到服务端,服务端保存并返回除B之外的其他所有玩家的数据。

注:或者也可以这样通信,玩家A发送自身数据到服务端,服务端将该数据发送给其他所有玩家。玩家B发送自身数据到服务端,服务端将该数据发送给其他所有玩家。这种通信方式有个缺点,就是服务端的发送的消息次数会很多。假如现在有10个玩家,当每个玩家发送1次数据到服务端,服务端还需要发送9次将数据同步给其他玩家。

运行结果如下:

1.4 完善客户端代码

最后一步就是要在客户端程序中添加和服务端通信的代码。客户端会把玩家数据发送到服务端,然后把服务端接收过来的其他玩家的数据更新到窗口上。代码编写如下:

class GameWindow:def __init__(self):...self.port = 5000                # 1self.host = "127.0.0.1"self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)self.connect()                  # 2self.other_players_dict = {}    # 3...def connect(self):self.sock.connect((self.host, self.port))self.player.id = self.sock.recv(2048).decode("utf-8")def send_player_data(self):         # 4data = {"id": self.player.id,"pos": [self.player.x, self.player.y],"color": self.player.color}self.sock.send(json.dumps(data).encode("utf-8"))return self.sock.recv(2048).decode("utf-8")def update_window(self):            # 5self.window.fill((255, 255, 255))self.player.move()self.player.draw()other_players_data = json.loads(self.send_player_data())self.update_other_players_data(other_players_data)self.delete_offline_players(other_players_data)pygame.display.update()def update_other_players_data(self, data):  # 6for key, value in data.items():if not self.other_players_dict.get(key):self.add_one_player(key, value)else:pos = value["pos"]self.other_players_dict[key].x = pos[0]self.other_players_dict[key].y = pos[1]self.other_players_dict[key].draw()def add_one_player(self, player_id, value): # 7pos = value["pos"]color = value["color"]self.other_players_dict[player_id] = Player(self.window, player_id, pos[0], pos[1], color)def delete_offline_players(self, data):     # 8new_dict = {}for key in self.other_players_dict.keys():if data.get(key):new_dict[key] = self.other_players_dict[key]self.other_players_dict = new_dict...

代码解释如下:

1. 客户端要连接到127.0.0.1:5000,即服务端监听的地址。

2. 在connect()函数中,我们让客户端和服务端进行了连接。连接后,服务端会发送str(id(conn))作为玩家id,我们将该值保存到Player对象的id属性中。

3. other_players_dict字典变量用来保存其他所有玩家的数据,字典的键是玩家id,值是代表该玩家的Player对象,格式如下所示。

{"玩家id": Player实例对象
}

4. send_player_data()函数用来将当前玩家的数据发送到服务端,并返回从服务端接收到的其他玩家的数据。

5. 在update_window()函数中,我们不仅要更新当前玩家的状态,还要更新其他所有玩家的状态。

6.&7. 在update_other_players_data()函数中,我们分析从服务端接收回来的数据,如果出现一个新的玩家id,那么就实例化一个Player对象并保存到other_players_dict字典变量中。如果该玩家id已存在,则更新该玩家的位置即可。

8. 如果某个玩家退出了游戏,那从服务端接收回来的数据中,肯定会少一个玩家id。但是该玩家id之前又已经保存到other_players_dict字典变量中,所以我们每次还要更新下other_players_dict,把不需要的玩家id给去掉。

现在先运行服务端程序,然后再运行任意数量的客户端程序,笔者这里就打开三个客户端。我们发现,每个游戏窗口上都会出现三个方块,在任何一个窗口上移动方块,其他两个窗口也会立即更新方块位置。运行结果如下:

1.5 完整代码下载地址

链接:https://pan.baidu.com/s/15uFqYp98R0LoH92LOsRzug

密码:18ik

《Python多人游戏项目实战》第一节 简单的方块移动相关推荐

  1. 电商项目实战第一节: CSS3+HTML5+JS 设计案例【考拉海购网站】之【顶部导航】

    文章目录 [考拉海购网站]之[顶部导航] 第一步,分析布局 第二步,建立基本的文本目录及文件 第三步,根据第一步对导航栏的分析,在html代码里面补全需要的标签 index.html文件代码 第四步, ...

  2. 《Python多人游戏项目实战》第三节 在窗口上显示玩家ID以及对话内容

    目录 3.1 显示不同的人物图片 3.2 显示玩家ID 3.3 显示玩家对话内容 3.4 完整代码下载地址 本节只是在上一节内容的基础上加一些小功能:显示不同的人物图片.在人物头顶上显示玩家ID以及人 ...

  3. 电商项目实战第二节: CSS3+HTML5+JS 设计案例【考拉海购网站】之【搜索框那一栏】

    上一节:电商项目实战第一节: CSS3+HTML5+JS 设计案例[考拉海购网站]之[顶部导航] 文章目录 [考拉海购网站]之[搜索框那一栏] 第一步,分析布局 第二步,在html代码里面补全需要的标 ...

  4. golang知识图谱NLP实战第一节——整体思路

    golang知识图谱NLP实战第一节--整体思路 golang知识图谱NLP实战第二节--解析依存句法分析结果 golang知识图谱NLP实战第三节--关系抽取 最大的愿望是给engineercms工 ...

  5. python爬虫项目-33个Python爬虫项目实战(推荐)

    今天为大家整理了32个Python爬虫项目. 整理的原因是,爬虫入门简单快速,也非常适合新入门的小伙伴培养信心.所有链接指向GitHub,祝大家玩的愉快~O(∩_∩)O WechatSogou [1] ...

  6. python爬虫知网实例-33个Python爬虫项目实战(推荐)

    今天为大家整理了32个Python爬虫项目. 整理的原因是,爬虫入门简单快速,也非常适合新入门的小伙伴培养信心.所有链接指向GitHub,祝大家玩的愉快~O(∩_∩)O WechatSogou [1] ...

  7. python爬虫项目实战教学视频_('[Python爬虫]---Python爬虫进阶项目实战视频',)

    爬虫]---Python 爬虫进阶项目实战 1- Python3+Pip环境配置 2- MongoDB环境配置 3- Redis环境配置 4- 4-MySQL的安装 5- 5-Python多版本共存配 ...

  8. python金融实战 源代码_穆棱市seo总代直销python金融量化营业实战课程 python量化项目实战源码+课件+视频...

    python金融量化生意实战课程 python量化项目实战源码+课件+视频 1. 自愿化生意综述 重要实质: 课程实质综述,自愿化/算法生意先容,python正在自愿生意中的使用简介 2. 量化生意体 ...

  9. 【HUST狼牙实验室梯队学习项目】第一节 底盘搭建

    [HUST狼牙实验室梯队学习项目]第一节 底盘搭建 主控选择 电机控制部分 PWM调波 定时器 A4950电机驱动 PID闭环控制 串口通信 串口通信配置: 串口接收 串口发送 主控选择 该项目选择的 ...

最新文章

  1. pandas.read_table API
  2. html5 网页游戏 开源,HTML5 网页游戏,基于 WebGL 打造
  3. cms建站系统有哪些,各自的特点是什么?
  4. ossim-agent代理和要监控的服务器的配置
  5. 5分钟内看懂机器学习和深度学习的区别
  6. Dapr集成之GRPC 接口
  7. 平衡小车卡尔曼滤波算法
  8. 小猴吃桃matlab,看图写话:小猴吃桃精彩选篇
  9. 让Vim更好用 for Mac OS X | Hessian's Blog
  10. c语言编程打印格式输出总结
  11. 取PE文件OriginalFilename-解析VERSION资源
  12. 工作室流量卡如何做才能不封号?
  13. python鸭制作类代码_python鸭子类型
  14. [Python从零到壹] 四十二.图像处理基础篇之图像金字塔向上取样和向下取样
  15. idea关闭html校验,怎么样关闭IntelliJ IDEA的javascript提示
  16. mysql数据库地址 名称_数据库地址和名称是什么?怎么知道自己地址和名称?
  17. 酵素果冻真的能减肥吗?
  18. php 获取扩展模块信息,查看PHP opcode扩展模块及Web服务
  19. 神经网络基础与顺序神经网络的构建
  20. 相对位置编码与绝对位置编码

热门文章

  1. 人在外省想在老家装监控,在手机上能看,要什么条件和材料?
  2. Goland自定义头部注释,增加author和data等(学习笔记,不作教程)
  3. MySQL银行绩效面试题
  4. Java生成并合并图片以及base64转图片
  5. 【文献阅读2020】 像素级自适应学习的超分辨率Pixel-Level Self-Paced Learning For Super-Resolution
  6. VTM10.0代码学习5:coding_unit()cu_pred_data()
  7. hotmail邮箱在Outlooknbsp;2010中…
  8. 安装onnx遇到error信息:Couldn‘t build proto file
  9. 写在汶川地震发生之后的思考
  10. Macbook pro苹果笔记本电脑安装双系统图文教程