一定要先写逻辑代码,再写用户界面代码

GUI 的共性

好读不好写!!!,因为一看类名函数名就知道每行代码是干嘛的,但是因为大小写还有单词选择可选的很多, 所以一定要配置 代码补全!!!

其实任何语言的 GUI 都是类似的,基本就是使用操作系统提供的窗口 API 接口,然后就是时间响应机制,这样就导致了语言特点其实用的特别少,然后真正开发都是使用别人的引擎。

例如 C++ 开发就会用到 cocos2d, Unity 等(5 年前去游戏公司实习过),还有 MATLAB 的 GUI 开发是真的方便,框架设计特别简单,添加按钮什么的拖拽就可以了,还有就是添加响应函数的时候右键就可以自动帮你定位到要写响应函数的地方,简直就是神器。好了废话不多说了,学习 Python 的 GUI 设计,选的是 wxPython 和 pygame。学习两个库的一些基本操作,然后分别用两种包写拼图游戏。

wxPython 学习

先用 pip 安装 wxPython

pip install wxPython -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com

wxPython 官网 给了 hello World 两个简单示例

1
import wx; a=wx.App(); wx.Frame(None, title="Hello World").Show(); a.MainLoop()

一行代码做一个 GUI 界面,可能这就叫做人生苦短,请用 Python 吧!

推荐 wxPython 库查阅键盘事件列表

pygame 学习

这一张图就够了感觉!

pygame

然后还有 ppt 以及库查询手册

拼图游戏开始了

本来是想用 wxPython 和 pygame 两种方式实现同一个拼图游戏,后来 wxPython 的编写(主要是函数查询)把我搞的生无可恋,就不想写 pygame 版本的实现了。下面是源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# JigsawGame.py writed by dna049 at 2020/2/28  last update: 2020/3/14
# 这是个加注释的版本,有些望文生义的我就不注释了
import wx
import os
import base64
from datetime import *
from random import randint
import urllib.request as urlrequest # 用于从网上加载图片
import webbrowser #打开网页

import tkinter as tk # 用于选择文件夹
from tkinter import filedialog # 用于选择文件夹
from wx.lib.embeddedimage import PyEmbeddedImage # 用于把数据变成位图

class JigsawGame(wx.Frame):
def __init__(self, *args, **kw): #这个方法只会在最开始调用一次
super(JigsawGame, self).__init__(*args, **kw)
self.Centre() # 整个窗口出现在屏幕正中心
# 选择文件
self.fileOpen = False # 标记是否选择了正确(.png, .jpg, .jpeg, bmp)的文件
self.fileLoad = False # 标记是否读取了文件(因为只需读取一次)
self.order = [i for i in range(9)] # 0-8这9个位置的初始值都是0-8,后面会变动
self.eB = 8 #标记空白块的位置,8就是右下角
# 创建面板(面板上可以放按钮,文本框,静态文本,图层等等东西)
# style 参数设置是让他可以接收到上下左右键!
self.pnl = wx.Panel(parent = self, style = wx.BORDER_NONE)
# 这个就是左下角的欢迎界面,还有字体的设置
welcome = wx.StaticText(self.pnl, pos=(20, 521), \
lAbel = '欢迎来到我的博客:', \
style = wx.aligned_RIGHT)
blog = wx.StaticText(self.pnl, pos = (180, 521), \
lAbel = 'dna049.com')
blog.SetForegroundColour('pink')
font = blog.GetFont()
font.PointSize += 2
font = font.Bold()
welcome.SetFont(font)
blog.SetFont(font)

# 这个是操作提示的静态文本
self.hint = wx.StaticText(self.pnl, pos=(710, 20), \
lAbel = '操作:\n\n↑\n← ↓ →\n\n\nW\nA S D', \
style = wx.aligned_CENTER)
self.hint.SetFont(font)
self.hint.SetForegroundColour('red')
self.hint.Hide()

# 一开始的界面中提示按鼠标的静态文本,贼大的那个
self.st = wx.StaticText(self.pnl, pos =(200, 200), \
lAbel = '鼠标点击空白处\n选择一个照片来玩拼图吧', \
style = wx.aligned_CENTER)
font.PointSize += 16
self.font = font.Bold()
self.st.SetFont(self.font)
self.st.SetForegroundColour('red')

# 绑定鼠标左键和下面的onleftdown方法
self.pnl.Bind(wx.EVT_LEFT_DOWN, self.onleftdown)
#绑定键盘事件和下面的onkeydown方法
self.pnl.Bind(wx.EVT_KEY_DOWN, self.onkeydown)

def onleftdown(self, event):
if(not self.fileOpen):
self.fileName = self.myfile()
if os.path.basename(self.fileName).split('.')[-1] in ['png', 'jpg', 'jpeg', 'bmp']:
self.fileOpen = True
self.st.Destroy()
self.run()

#如果已经完成了,那就不让你再玩了,否则就开始按键检测然后还是移动
def onkeydown(self, event):
if(self.fileLoad and not self.isfinish()):
myKey = [wx.WXK_LEFT, ord('A'), wx.WXK_RIGHT, ord('D'), wx.WXK_UP, ord('W'), wx.WXK_DOWN, ord('S')]
keycode = event.GetKeyCode()
self.move(myKey.index(keycode)//2 if (keycode in myKey) else -1)
self.pnl.Layout()
self.isfinish()

# 出现让你选择图片的界面,返回文件绝对路径的文件名
def myfile(self):
chooseFile = tk.Tk()
chooseFile.withdraw()
fileName = filedialog.askopenfilename()
return fileName

# 从网上下一个表情包,返回值是位图用来加载
def onlinefile(self):
url = 'https://dna049.com/PythonGui/yes.png'
image = urlrequest.urlopen(url).read()
bData = base64.b64encode(image)
pData = bData.decode()
self.urlYes = PyEmbeddedImage(pData).GetBitmap()

# 把从本地选择出的图片进行调整大小,调整完之后切成3*3的块,然后在画板上画出来
# 并且进行标记好更新,逻辑代码很短归功于Python的短小精湛!
def writefile(self):
tmp = wx.Image(self.fileName)
im = tmp.ConvertToBitmap()
(sizeW, sizeH) = im.GetSize()
t = max(0.2, sizeW/720, sizeH/521)+0.02
im = tmp.Scale(int(sizeW/t), int(sizeH/t)).ConvertToBitmap()
(sizeW, sizeH) = im.GetSize()
sizeW //= 3;sizeH //= 3
self.img = []
for i in range(8):
self.img += [im.GetSubBitmap(( (i%3)*sizeW, (i//3)*sizeH, sizeW, sizeH))]
self.img += [wx.Bitmap.FromRGBA(sizeW, sizeH, 255, 255, 255, 1)]
self.lastPart = im.GetSubBitmap(( 2*sizeW, 2*sizeH, sizeW, sizeH))
ps = [((x%3)*(sizeW +1), (x//3)*(sizeH+1)) for x in range(9)]
self.sbt = [wx.StaticBitmap(self.pnl, bitmap = self.img[i], \
pos=ps[i], size=(sizeW, sizeH) ) for i in range(9)]

# 检测到鼠标左键按下,就开始看文件打开过没有,打开了就无视,没打开就开启文件选择
# 打开成功之后就把部分提示关闭,然后开始为下一步拼图做准备

def run(self):
if(not self.fileLoad):
self.fileLoad = True
self.writefile()
self.disorder()
self.pnl.Layout()
self.hint.Show()
self.pnl.SetFocus()

def disorder(self):
for i in range(111): self.move(randint(0, 3))

def move(self, direction):
if direction == 0: self.moveleft()
elif direction == 1: self.moveright()
elif direction == 2: self.moveup()
elif direction == 3: self.movedown()

#判断是否是合理移动,合理就丢给下面函数更新
def checkmove(self, start, end):
if(end not in range(9)): return False
if(start%3 == 0 and end%3 == 2): return False
if(start%3 == 2 and end%3 == 0): return False
return True

def moveleft(self):
if(self.checkmove(self.eB, self.eB+1)): self.sbtnew(self.eB, self.eB+1);self.eB+=1
def moveright(self):
if(self.checkmove(self.eB, self.eB-1)): self.sbtnew(self.eB, self.eB-1);self.eB-=1
def moveup(self):
if(self.checkmove(self.eB, self.eB+3)): self.sbtnew(self.eB, self.eB+3);self.eB+=3
def movedown(self):
if(self.checkmove(self.eB, self.eB-3)): self.sbtnew(self.eB, self.eB-3);self.eB-=3

# 如果操作合理,那就更新每个格子上的图片,并更新order列表
def sbtnew(self, posB, posA):
(x, y) = self.order[posB],self.order[posA]
self.order[posB], self.order[posA] = (y, x)
self.sbt[posB].SetBitmap(self.img[y])
self.sbt[posA].SetBitmap(self.img[x])

# 每次有按键的时候检测是否拼好了,拼好了就贴上最后一块并把表情包打印出来
def isfinish(self):
if self.order == [i for i in range(9)]:
self.sbt[8].SetBitmap(self.lastPart)
self.onlinefile()
wx.StaticBitmap(parent = self.pnl, bitmap = self.urlYes, pos=(500, 300))
return True
else: return False


# 程序入口,进入__init__ 函数后,就开始进行鼠标和键盘的监听循环中
if __name__ == '__main__':
loveDays = datetime.now() - datetime(2019, 10, 20)
app = wx.App()
# 创建上面类的对象,标题是喜欢zly妹妹,窗口不允许最大化,也不能调整大小
myGame = JigsawGame(None, \
title='dna049 喜欢zly妹妹的第'+str(loveDays.days)+'天', \
size = (800, 600), \
style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER ^ wx.MAXIMIZE_BOX)
myGame.Show()
app.MainLoop()

wx.EVT_KEY_DOWN 不响应 Tab Enter → ↑ ↓ ←

wx.EVT_KEY_UP 响应所有键

wx.EVT_CHAR 仅响应F1 - F12 PrintScreen ScrollLock PauseBreak

wx.Button 和 wx.EVT_KEY_DOWN 等冲突让我懵逼了吐了

还是解释一下代码吧

  1. 首先创建窗口,窗口名是喜欢 zly 妹妹的第 xxx 天,日常表白 0.0
  2. 窗口上放一个面板(panel)然后在上面放很多静态文本,比如我的博客呀,还有操作提示
  3. 然后绑定监听鼠标左键,按下了就会让你选择一个文件来玩拼图游戏
  4. 然后选择好了图片后,把图片平均切成 3*3 的块,最后一块(右下角)用黑白填充
  5. 把这些块打乱,打乱方式是随机的移动(防止随便打乱拼不回去)
  6. 监听 键盘 WASD 和上下左右键并写好相应的响应函数
  7. 每次接受到监听就检测是否拼好