基于tkinter模块的GUI
GUI是图形用户界面的缩写。 使用过计算机的人都应该熟悉图形用户界面像素游戏素材,因此这里无需赘述。 Python默认的GUI开发模块是tkinter(在Python 3之前的版本中称为Tkinter)。 从这个名字就可以看出它是基于Tk的。 Tk是最初为Tcl设计的工具包,后来被移植。 它为许多其他脚本语言提供了跨平台的 GUI 控件。 当然,Tk 并不是最新最好的选择,它也没有特别强大的 GUI 控件。 事实上,开发GUI应用程序并不是Python最擅长的。 如果你确实需要使用Python来开发GUI应用程序,wxPython、PyQt、PyGTK等模块是不错的选择。
基本上使用 tkinter 开发 GUI 应用程序需要以下 5 个步骤:
在 tkinter 模块中导入我们需要的内容。 创建一个顶级窗口对象并使用它来托管整个 GUI 应用程序。 在顶级窗口对象上添加 GUI 组件。 这些GUI组件的功能是通过代码来组织的。 进入主事件循环(mainloop)。
以下代码演示了如何使用 tkinter 制作一个简单的 GUI 应用程序。
import tkinter import tkinter.messagebox def main(): flag = True # 修改标签上的文字 def change_label_text(): nonlocal flag flag = not flag color, msg = ('red', 'Hello, world!')\ if flag else ('blue', 'Goodbye, world!') label.config(text=msg, fg=color) # 确认退出 def confirm_to_quit(): if tkinter.messagebox.askokcancel('温馨提示', '确定要退出吗?'): top.quit() # 创建顶层窗口 top = tkinter.Tk() # 设置窗口大小 top.geometry('240x160') # 设置窗口标题 top.title('小游戏') # 创建标签对象并添加到顶层窗口 label = tkinter.Label(top, text='Hello, world!', font='Arial -32', fg='red') label.pack(expand=1) # 创建一个装按钮的容器 panel = tkinter.Frame(top) # 创建按钮对象 指定添加到哪个容器中 通过command参数绑定事件回调函数 button1 = tkinter.Button(panel, text='修改', command=change_label_text) button1.pack(side='left') button2 = tkinter.Button(panel, text='退出', command=confirm_to_quit) button2.pack(side='right') panel.pack(side='bottom') # 开启主事件循环 tkinter.mainloop() if __name__ == '__main__': main()
应该注意的是,GUI 应用程序通常是事件驱动的。 之所以进入主事件循环是为了监听鼠标、键盘等各种事件的发生并执行相应的代码来处理事件从c语言程序设计到sdl游戏开发,因为事件会不断发生。 所以这样的循环需要一直运行,等待下一个事件发生。 另一方面,Tk 提供了三个布局管理器来放置控件。 可以通过布局管理器来定位控件。 这三个布局管理器分别是:Placer(开发者提供控件的大小和放置位置)、Packer(自动将控件填充到合适的位置)和Grid(根据网格坐标放置控件),这里不再赘述。
使用 Pygame 进行游戏开发
Pygame是一个专门用于开发多媒体应用程序(例如视频游戏)的开源Python模块,它包括对图像、声音、视频、事件、碰撞等的支持。Pygame构建在SDL之上,SDL是一套交叉-C语言实现的平台多媒体开发库,广泛应用于游戏、模拟器、播放器等的开发。Pygame让游戏开发者不再受底层语言的束缚,可以更专注于游戏的功能和逻辑。
现在让我们完成一个简单的游戏。 游戏的名字叫“大球吃小球”。 当然,完成这个游戏并不是重点。 学习使用Pygame也不是重点。 最重要的是我们必须了解如何使用流程中前面的步骤。 解释面向对象编程并学习使用这种编程思想来解决现实生活中的问题。
制作游戏窗口
import pygame def main(): # 初始化导入的pygame中的模块 pygame.init() # 初始化用于显示的窗口并设置窗口尺寸 screen = pygame.display.set_mode((800, 600)) # 设置当前窗口的标题 pygame.display.set_caption('大球吃小球') running = True # 开启一个事件循环处理发生的事件 while running: # 从消息队列中获取事件并对事件进行处理 for event in pygame.event.get(): if event.type == pygame.QUIT: running = False if __name__ == '__main__': main()
在窗口中绘制
可以通过pygame中的draw模块的功能在窗口上进行绘图。 可以绘制的图形包括:直线、矩形、多边形、圆形、椭圆形、圆弧等。需要注意的是,屏幕坐标系以屏幕左上角为坐标原点(0, 0)。 向右为x轴正方向,向下为y轴正方向。 当表达位置或设置大小时,我们的默认单位是像素。 所谓像素,就是屏幕上的一个点。 您可以使用图像浏览软件尝试将图片放大几倍来查看这些要点。 pygame中的颜色表示采用三色原色表示方法,即通过元组或列表指定颜色的RGB值。 每个值都在 0 到 255 之间,因为每种基色都使用 8 位(bit)来表示值,三种颜色总共相当于 24 位,通常称为“24 位颜色表示”。
import pygame def main(): # 初始化导入的pygame中的模块 pygame.init() # 初始化用于显示的窗口并设置窗口尺寸 screen = pygame.display.set_mode((800, 600)) # 设置当前窗口的标题 pygame.display.set_caption('大球吃小球') # 设置窗口的背景色(颜色是由红绿蓝三原色构成的元组) screen.fill((242, 242, 242)) # 绘制一个圆(参数分别是: 屏幕, 颜色, 圆心位置, 半径, 0表示填充圆) pygame.draw.circle(screen, (255, 0, 0,), (100, 100), 30, 0) # 刷新当前窗口(渲染窗口将绘制的图像呈现出来) pygame.display.flip() running = True # 开启一个事件循环处理发生的事件 while running: # 从消息队列中获取事件并对事件进行处理 for event in pygame.event.get(): if event.type == pygame.QUIT: running = False if __name__ == '__main__': main()
加载图片
如果需要直接加载图像到窗口中,可以使用pygame中image模块的功能来加载图像硬件设备,然后通过之前获得的window对象的blit方法来渲染图像。 代码如下。
import pygame def main(): # 初始化导入的pygame中的模块 pygame.init() # 初始化用于显示的窗口并设置窗口尺寸 screen = pygame.display.set_mode((800, 600)) # 设置当前窗口的标题 pygame.display.set_caption('大球吃小球') # 设置窗口的背景色(颜色是由红绿蓝三原色构成的元组) screen.fill((255, 255, 255)) # 通过指定的文件名加载图像 ball_image = pygame.image.load('./res/ball.png') # 在窗口上渲染图像 screen.blit(ball_image, (50, 50)) # 刷新当前窗口(渲染窗口将绘制的图像呈现出来) pygame.display.flip() running = True # 开启一个事件循环处理发生的事件 while running: # 从消息队列中获取事件并对事件进行处理 for event in pygame.event.get(): if event.type == pygame.QUIT: running = False if __name__ == '__main__': main()
实现动画效果
说到动画这个词,大家都不陌生。 其实实现动画效果的原理很简单。 就是连续播放不连续的画面。 只要达到每秒一定的帧数,那么就可以产生更流畅的动画效果。 如果想让上面代码中的小球移动,可以用变量来表示小球的位置,在循环中修改小球的位置,然后刷新整个窗口。
import pygame def main(): # 初始化导入的pygame中的模块 pygame.init() # 初始化用于显示的窗口并设置窗口尺寸 screen = pygame.display.set_mode((800, 600)) # 设置当前窗口的标题 pygame.display.set_caption('大球吃小球') # 定义变量来表示小球在屏幕上的位置 x, y = 50, 50 running = True # 开启一个事件循环处理发生的事件 while running: # 从消息队列中获取事件并对事件进行处理 for event in pygame.event.get(): if event.type == pygame.QUIT: running = False screen.fill((255, 255, 255)) pygame.draw.circle(screen, (255, 0, 0,), (x, y), 30, 0) pygame.display.flip() # 每隔50毫秒就改变小球的位置再刷新窗口 pygame.time.delay(50) x, y = x + 5, y + 5 if __name__ == '__main__': main()
影响检查
通常游戏中会有很多物体,这些物体之间的“碰撞”是不可避免的,比如炮弹击中飞机、盒子击中地面等。碰撞检测是大多数游戏中必须处理的关键问题。 pygame的sprite(动画精灵)模块提供了对碰撞检测的支持。 这里我们不介绍sprite模块提供的功能。 因为检测两个小球是否碰撞其实很简单。 您只需检查球中心之间的距离是否小于两个球的半径之和即可。 为了创建更多的小球,我们可以处理鼠标事件,在鼠标点击的位置创建随机颜色、大小和移动速度的小球。 当然,要做到这一点,我们可以利用之前所学的面向对象的知识来应用。
from enum import Enum, unique from math import sqrt from random import randint import pygame @unique class Color(Enum): """颜色""" RED = (255, 0, 0) GREEN = (0, 255, 0) BLUE = (0, 0, 255) BLACK = (0, 0, 0) WHITE = (255, 255, 255) GRAY = (242, 242, 242) @staticmethod def random_color(): """获得随机颜色""" r = randint(0, 255) g = randint(0, 255) b = randint(0, 255) return (r, g, b) class Ball(object): """球""" def __init__(self, x, y, radius, sx, sy, color=Color.RED): """初始化方法""" self.x = x self.y = y self.radius = radius self.sx = sx self.sy = sy self.color = color self.alive = True def move(self, screen): """移动""" self.x += self.sx self.y += self.sy if self.x - self.radius <= 0 or \ self.x + self.radius >= screen.get_width(): self.sx = -self.sx if self.y - self.radius <= 0 or \ self.y + self.radius >= screen.get_height(): self.sy = -self.sy def eat(self, other): """吃其他球""" if self.alive and other.alive and self != other: dx, dy = self.x - other.x, self.y - other.y distance = sqrt(dx ** 2 + dy ** 2) if distance < self.radius + other.radius \ and self.radius > other.radius: other.alive = False a self.radius = self.radius + int(other.radius * 0.146) def draw(self, screen): """在窗口上绘制球""" pygame.draw.circle(screen, self.color, (self.x, self.y), self.radius, 0)
事件处理
鼠标事件可以在事件循环中处理。 通过事件对象的type属性可以确定事件类型,通过pos属性可以获取鼠标点击的位置。 如果你想处理键盘事件,这也是地方。 该方法与处理鼠标事件类似。
def main(): # 定义用来装所有球的容器 balls = [] # 初始化导入的pygame中的模块 pygame.init() # 初始化用于显示的窗口并设置窗口尺寸 screen = pygame.display.set_mode((800, 600)) # 设置当前窗口的标题 pygame.display.set_caption('大球吃小球') running = True # 开启一个事件循环处理发生的事件 while running: # 从消息队列中获取事件并对事件进行处理 for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # 处理鼠标事件的代码 if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: # 获得点击鼠标的位置 x, y = event.pos radius = randint(10, 100) sx, sy = randint(-10, 10), randint(-10, 10) color = Color.random_color() # 在点击鼠标的位置创建一个球(大小、速度和颜色随机) ball = Ball(x, y, radius, sx, sy, color) # 将球添加到列表容器中 balls.append(ball) screen.fill((255, 255, 255)) # 取出容器中的球 如果没被吃掉就绘制 被吃掉了就移除 for ball in balls: if ball.alive: ball.draw(screen) else: balls.remove(ball) pygame.display.flip() # 每隔50毫秒就改变球的位置再刷新窗口 pygame.time.delay(50) for ball in balls: ball.move(screen) # 检查球有没有吃到其他的球 for other in balls: ball.eat(other) if __name__ == '__main__': main()
将以上两段代码放在一起,我们就完成了“大球吃小球”的游戏(如下图所示)。 准确的来说,它不是一个游戏,但是我们已经通过了制作小游戏的基础知识。 这个例子告诉大家,有了这些知识你就可以开始你的小游戏开发之旅了。 其实上面的代码还有很多值得改进的地方。 例如,刷新窗口和移动球的代码不应该放在事件循环中。 学习了多线程的知识后,就可以使用后台线程来处理这些事情了。 是一个更好的选择。 如果我们想要获得更好的用户体验从c语言程序设计到sdl游戏开发,我们还可以在游戏中添加背景音乐,在小球与小球碰撞时播放音效。 使用pygame的混音器和音乐模块,我们可以轻松做到这一点。 你可以了解一下这个。 事实上,如果你想更多地了解pygame,最好的教程是pygame的官方网站。 如果你英文没问题的话可以去看看。 如果你想开发3D游戏,pygame似乎还不够。 对3D游戏开发感兴趣的读者不妨看一下Panda3D。