目录
本节只是在上一节内容的基础上加一些小功能:显示不同的人物图片、在人物头顶上显示玩家ID以及人物头顶上显示一个聊天对话框。大家可以把这一节内容当做一个过渡,用来巩固下多人游戏程序中pickle的用法。程序完成后的运行结果如下:
本项目结构显示如下:
├── SimHei.ttf # 字体文件
├── client.py # 客户端代码
├── pics # 图片文件夹
│ ├── 1.png
│ ├── 2.png
│ ├── 3.png
│ ├── 4.png
│ ├── 5.png
│ └── 6.png
├── player.py # 包含Player类
└── server.py # 服务端代码
在client.py中我们一共导入了以下模块或库:
import sys
import pygame
import pickle
import socket
from player import Player
from random import randint
在player.py中我们一共导入了以下模块或库:
import pygame
import random
在server.py中我们一共导入了以下模块或库:
import socket
import pickle
from player import Player
from threading import Thread
3.1 显示不同的人物图片
pics文件夹下有6个人物方位图,玩家打开游戏窗口时,程序都会从这6个方位图中随机选择一张进行显示。因为是随机的,所以其他玩家的人物图片可能跟当前玩家的一样,也可能不一样。
首先修改Player类:
# player.py
class Player:
def __init__(self, p_id, x, y, pic_num, frame_width, frame_height):
self.id = p_id
self.dis = 3
self.x = x
self.y = y
self.pic_num = pic_num # 1
self.frame_width = frame_width
self.frame_height = frame_height
self.frame_num = 0
self.frame_rect = (self.frame_num * self.frame_width, 0 * self.frame_height,
self.frame_width, self.frame_height)
self.current_dir = "下"
self.last_dir = self.current_dir
...
代码解释如下:
1. 在初始化函数中加一个pic_num变量用来保存玩家显示的人物图片编号,这样就能从服务端传送过来的数据中知道某个玩家用的什么人物图了。
接着修改GameWindow类:
# client.py
class GameWindow:
def __init__(self):
...
self.pic_dict = { # 1
1: pygame.image.load("./pics/1.png"),
2: pygame.image.load("./pics/2.png"),
3: pygame.image.load("./pics/3.png"),
4: pygame.image.load("./pics/4.png"),
5: pygame.image.load("./pics/5.png"),
6: pygame.image.load("./pics/6.png")
}
frame_width = self.pic_dict[1].get_width() // 4 # 2
frame_height = self.pic_dict[1].get_height() // 4
self.player = Player(p_id=None,
x=randint(0, self.width-frame_width),
y=randint(0, self.height-frame_height),
pic_num=randint(1, 6), # 3
frame_width=frame_width,
frame_height=frame_height)
...
...
def update_window(self):
self.window.fill((255, 255, 255))
self.player.move()
self.player.draw(self.window, self.pic_dict[self.player.pic_num]) # 4
other_players_data = pickle.loads(self.send_player_data())
self.update_other_players_data(other_players_data)
pygame.display.update()
def update_other_players_data(self, data):
for player in data.values():
player.draw(self.window, self.pic_dict[player.pic_num]) # 5
...
代码解释如下:
1. 创建一个pic_dict字典变量,用来保存各个人物方位图对象,字典的键就是图片编号。
2. 因为所有人物方位图的大小都是一样的,所以这里就直接通过第一张方位图获取帧的宽高值。
3. 随机获取1-6之间的整数,这样就可以随机选择一张人物方位图了。
4. & 5. 根据pic_num值从pic_dict字典中选择对应的人物方位图,然后绘制到窗口上。
运行结果如下:
3.2 显示玩家ID
很多游戏会把玩家的id或名称显示在人物图片上方,并跟随人物移动,如下图所示。
这点其实很好实现,因为我们已经将玩家id值保存在Player对象的id属性中,所以只需要将这个id值绘制到窗口上就可以了。
将Player类的draw()函数修改如下:
# player.py
def draw(self, win, pic):
win.blit(pic, (self.x, self.y), self.frame_rect)
font = pygame.font.SysFont("Arial", 10) # 1
id_text = font.render(self.id, True, (150, 150, 150))
win.blit(id_text, (self.x + round(self.frame_width/2) - round(id_text.get_width()/2), self.y - id_text.get_height()))
代码解释如下:
1. 设置文字字体并将文本绘制到人物图片的正上方。
运行结果如下:
3.3 显示玩家对话内容
如果要聊天的话,玩家就会在文本输入框中输入消息,然后按下回车键发送,其他玩家随后也会收到该玩家发送的消息。Pygame没有提供一个文本输入框控件,如果要自己实现的话会增加很多跟本教程主题无关的代码。所以为了保持代码简洁,笔者就预先设置好一些聊天内容,然后随机发送一条。
修改Player类:
# player.py
class Player:
def __init__(self, p_id, x, y, pic_num, frame_width, frame_height):
...
self.message = "" # 1
...
def speak(self): # 2
messages_list = ["你好", "最近咋样", "我在打怪呢", "你说的有道理", "你哪里人", "不玩了,去吃饭了", "待会再玩", "今天很开心"]
self.message = random.choice(messages_list)
代码解释如下:
1. message变量用来存储玩家要发送消息。
2. speak()函数会随机生成一条聊天消息,并保存在message变量中。
修改GameWindow类:
# client.py
class GameWindow:
def __init__(self):
...
self.all_players_messages = [] # 1
self.max_messages_shown = 6 # 2
self.port = 5000
self.host = "127.0.0.1"
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect()
...
def send_player_data(self):
data = {
"id": self.player.id,
"player": self.player
}
self.sock.send(pickle.dumps(data))
self.player.message = "" # 3
return self.sock.recv(2048)
def update_window(self):
self.window.fill((255, 255, 255))
self.player.move()
self.player.draw(self.window, self.pic_dict[self.player.pic_num])
if randint(0, 1000) < 50: # 4
self.player.speak()
self.update_messages(self.player) # 5
other_players_data = pickle.loads(self.send_player_data())
self.update_other_players_data(other_players_data)
pygame.display.update()
def update_other_players_data(self, data):
for player in data.values():
player.draw(self.window, self.pic_dict[player.pic_num])
self.update_messages(player) # 6
def update_messages(self, player):
if player.message:
self.all_players_messages.insert(0, f"{player.id}: {player.message}")
if len(self.all_players_messages) > self.max_messages_shown:
self.all_players_messages.pop()
font_size = 12
font = pygame.font.Font("SimHei.ttf", font_size)
for i, msg in enumerate(self.all_players_messages):
msg_text = font.render(msg, True, (150, 150, 150))
self.window.blit(msg_text, (0, self.height-(font_size*(i+1))))
代码解释如下:
1. 所有玩家的消息都会存储在all_players_messages列表变量中。
2. max_messages_shown变量用来控制在窗口上显示的聊天消息数。
3. 当前玩家的数据被发送到服务器后,就要清空Player对象的message属性,否则其他玩家会重复收到一条不变的消息。
4. 使用随机数来模拟玩家聊天。
5. update_messages()函数是重点,在该函数中,我们首先判断玩家数据中的message变量是否有值,有的话说明有发送消息,那就将这个值插到all_players_messages列表开头。如果消息总数大于max_messages_shown变量的值,那就把列表中最后一条聊天消息删除掉,不再显示。最后将所有聊天内容都显示到窗口左下角。为了解决Pygame中文显示的问题,笔者这里使用了另外的SimHei.ttf字体,已放入到项目文件夹中。
6. 接收到其他玩家的数据后,要记得调用update_messages()函数更新聊天内容。
运行结果如下:
3.4 完整代码下载地址
链接:https://pan.baidu.com/s/1chQ5c94X07Oi3Iv_eX3xDg
密码:9qbz