用PyQt打造带动画、碰撞检测和键盘控制的小游戏

✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。

🍎个人主页:Java Fans的博客

🍊个人信条:不迁怒,不贰过。小知识,大智慧。

💞当前专栏:Python案例分享专栏

✨特色专栏:国学周更-心性养成之路

🥭本文内容:Qt Designer 和 PyQt 开发教程

文章目录

PyQt不仅适合做传统的桌面应用,也能用来开发简单的2D小游戏。本文将带你一步步实现一个带动画效果、碰撞检测和键盘控制的小游戏,帮助你掌握PyQt在游戏开发中的应用。


一、游戏需求与设计

我们设计一个简单的"躲避方块"游戏:

  • 玩家控制一个小方块(用键盘上下左右移动)。
  • 屏幕上会不断出现自动移动的敌方方块。
  • 玩家需要躲避敌方方块,避免碰撞。
  • 游戏带有动画效果,敌方方块自动移动。
  • 碰撞检测判断玩家是否被敌方方块碰到,碰撞则游戏结束。

二、用到的PyQt知识点(详细阐述)

1. QWidget绘图机制

  • 核心类:QWidget

    PyQt中所有窗口和控件的基类是QWidget。我们通过继承QWidget创建自定义窗口和游戏界面。

  • 绘图事件:paintEvent
    paintEventQWidget的一个事件处理函数,当窗口需要重绘时(比如窗口被遮挡后重新显示,或者调用update()时),系统会自动调用这个函数。

    我们重写paintEvent,使用QPainter对象在窗口上绘制游戏元素(玩家方块、敌人方块、背景等)。

  • 绘图工具:QPainter
    QPainter是PyQt中用于绘制图形的类,支持绘制点、线、矩形、圆形、文本等。

    通过设置画笔颜色、画刷填充颜色,可以绘制各种形状。

    在游戏中,我们用QPainter.drawRect()绘制矩形表示玩家和敌人。

  • 抗锯齿:setRenderHint(QPainter.Antialiasing)

    该设置让绘制的图形边缘更平滑,视觉效果更好。


2. 动画与定时器

  • 动画原理

    游戏动画本质是不断刷新画面,更新游戏元素的位置或状态,给人连续运动的感觉。

  • 定时器:QTimer

    PyQt提供QTimer类,可以定时触发信号。

    在游戏中,我们创建一个定时器,设置时间间隔(如30毫秒),每隔这个时间调用一次游戏循环函数game_loop

    这样就实现了游戏状态的周期性更新和界面重绘。

  • 游戏循环
    game_loop函数中,我们更新敌人位置、检测碰撞、移除出界敌人,然后调用update()请求重绘。

    这就是典型的游戏主循环结构。

  • 多定时器配合

    除了主循环定时器,我们还用另一个定时器spawn_timer定时生成新的敌人,实现游戏动态难度。


3. 键盘事件处理

  • 事件机制

    PyQt中,键盘按下会触发keyPressEvent事件。

    我们重写keyPressEvent方法,捕获用户按键。

  • 键值判断

    通过event.key()获取按键代码,判断是上下左右箭头键(Qt.Key_Up等)。

  • 玩家移动

    根据按键,调整玩家矩形的位置(QRect.translate()),并限制玩家不能移出窗口边界。

  • 实时响应

    每次按键后调用update(),触发重绘,玩家位置即时更新。


4. 碰撞检测

  • 矩形碰撞检测

    游戏中玩家和敌人都用QRect表示位置和大小。
    QRect类提供intersects()方法,判断两个矩形是否有重叠区域。

  • 碰撞判断逻辑

    在游戏循环中,遍历所有敌人,判断它们的矩形是否与玩家矩形相交。

    一旦检测到碰撞,触发游戏结束逻辑。

  • 效率考虑

    对于简单游戏,遍历检测足够快。复杂游戏可用空间划分等优化。


5. 游戏循环与状态管理

  • 游戏状态变量

    self.is_game_over标记游戏是否结束,控制游戏逻辑是否继续执行。

  • 定时器启动与停止

    游戏结束时停止定时器,防止游戏继续更新。

  • 界面刷新

    通过调用self.update()请求重绘,触发paintEvent,实现动画效果。


6. 其他辅助知识点

  • 窗口尺寸与坐标系

    PyQt窗口的坐标系以左上角为原点,x轴向右,y轴向下。

    位置和大小都用像素表示。

  • 消息框提示

    QMessageBox.information弹出游戏结束提示框,增强用户体验。

  • 随机数生成

    用Python的random.randint生成敌人出现的随机高度,增加游戏趣味性。


三、代码实现

python 复制代码
import sys
import random
from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox
from PyQt5.QtGui import QPainter, QColor, QBrush
from PyQt5.QtCore import Qt, QTimer, QRect

class Enemy:
    def __init__(self, x, y, size, speed):
        self.rect = QRect(x, y, size, size)
        self.speed = speed

    def move(self):
        # 敌人向左移动
        self.rect.translate(-self.speed, 0)

class PlayerGame(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("PyQt小游戏 - 躲避方块")
        self.setGeometry(100, 100, 800, 600)

        # 玩家属性
        self.player_size = 40
        self.player_pos = QRect(100, 280, self.player_size, self.player_size)
        self.player_speed = 20

        # 敌人列表
        self.enemies = []
        self.enemy_size = 40
        self.enemy_speed = 5

        # 游戏状态
        self.is_game_over = False

        # 定时器,控制游戏刷新和敌人生成
        self.timer = QTimer()
        self.timer.timeout.connect(self.game_loop)
        self.timer.start(30)  # 30ms刷新一次,大约33帧每秒

        self.spawn_timer = QTimer()
        self.spawn_timer.timeout.connect(self.spawn_enemy)
        self.spawn_timer.start(1500)  # 每1.5秒生成一个敌人

    def spawn_enemy(self):
        # 敌人从右侧随机高度出现
        y = random.randint(0, self.height() - self.enemy_size)
        x = self.width()
        enemy = Enemy(x, y, self.enemy_size, self.enemy_speed)
        self.enemies.append(enemy)

    def game_loop(self):
        if self.is_game_over:
            return

        # 移动敌人
        for enemy in self.enemies:
            enemy.move()

        # 移除已经移出屏幕的敌人
        self.enemies = [e for e in self.enemies if e.rect.right() > 0]

        # 碰撞检测
        for enemy in self.enemies:
            if enemy.rect.intersects(self.player_pos):
                self.game_over()
                break

        # 刷新界面
        self.update()

    def game_over(self):
        self.is_game_over = True
        self.timer.stop()
        self.spawn_timer.stop()
        QMessageBox.information(self, "游戏结束", "你被撞到了!游戏结束。")

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)

        # 画背景
        painter.fillRect(self.rect(), QColor(230, 230, 230))

        # 画玩家方块(蓝色)
        painter.setBrush(QBrush(QColor(0, 100, 255)))
        painter.drawRect(self.player_pos)

        # 画敌人方块(红色)
        painter.setBrush(QBrush(QColor(255, 50, 50)))
        for enemy in self.enemies:
            painter.drawRect(enemy.rect)

    def keyPressEvent(self, event):
        if self.is_game_over:
            return

        key = event.key()
        if key == Qt.Key_Up:
            if self.player_pos.top() - self.player_speed >= 0:
                self.player_pos.translate(0, -self.player_speed)
        elif key == Qt.Key_Down:
            if self.player_pos.bottom() + self.player_speed <= self.height():
                self.player_pos.translate(0, self.player_speed)
        elif key == Qt.Key_Left:
            if self.player_pos.left() - self.player_speed >= 0:
                self.player_pos.translate(-self.player_speed, 0)
        elif key == Qt.Key_Right:
            if self.player_pos.right() + self.player_speed <= self.width():
                self.player_pos.translate(self.player_speed, 0)

        self.update()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    game = PlayerGame()
    game.show()
    sys.exit(app.exec_())

四、代码解析

1. 游戏窗口和玩家

  • PlayerGame继承自QWidget,作为游戏主窗口。
  • 玩家用QRect表示位置和大小,初始在窗口左侧中间。
  • 玩家移动通过键盘事件keyPressEvent控制,限制移动范围不超出窗口。

2. 敌人设计

  • 敌人用Enemy类表示,包含矩形区域和移动速度。
  • 敌人从窗口右侧随机高度生成,向左移动。
  • 超出左侧边界的敌人被移除,避免内存泄漏。

3. 动画与定时器

  • 使用QTimer定时调用game_loop方法,实现游戏状态更新和动画。
  • 另一个定时器spawn_timer负责定时生成敌人。
  • 通过update()触发paintEvent重绘界面。

4. 碰撞检测

  • 利用QRect.intersects()判断玩家和敌人是否重叠。
  • 一旦检测到碰撞,游戏结束,停止定时器并弹出提示框。

五、扩展思路

  • 增加分数和计时:统计玩家存活时间或躲避敌人数。
  • 多种敌人类型:不同速度、大小的敌人增加难度。
  • 玩家攻击:添加发射子弹功能,消灭敌人。
  • 音效和背景音乐 :用PyQt的QSound模块丰富游戏体验。
  • 更复杂的动画:利用Qt的动画框架实现平滑移动和特效。

六、总结

通过这个小游戏,我们学习了:

  • PyQt的绘图机制和事件处理。
  • 使用定时器实现游戏循环和动画。
  • 键盘事件捕获实现玩家控制。
  • 矩形碰撞检测的简单实现。

码文不易,本篇文章就介绍到这里,如果想要学习更多Java系列知识点击关注博主,博主带你零基础学习Java知识。与此同时,对于日常生活有困扰的朋友,欢迎阅读我的第四栏目《国学周更---心性养成之路》,学习技术的同时,我们也注重了心性的养成。

相关推荐
深蓝海拓3 小时前
PySide6从0开始学习的笔记(十一) QSS 属性选择器
笔记·python·qt·学习·pyqt
AAA_bo13 小时前
liunx安装canda、python、nodejs、git,随后部署私有网页内容提取工具--JinaReader全攻略
linux·python·ubuntu·typescript·aigc·python3.11·jina
高洁013 小时前
DNN案例一步步构建深层神经网络(3)
python·深度学习·算法·机器学习·transformer
AI_56783 小时前
Jupyter交互式数据分析的效率革命
开发语言·python
superman超哥3 小时前
仓颉语言中并发集合的实现深度剖析与高性能实践
开发语言·后端·python·c#·仓颉
superman超哥3 小时前
仓颉语言中原子操作的封装深度剖析与无锁编程实践
c语言·开发语言·后端·python·仓颉
拾贰_C3 小时前
【Anaconda | Python | pytorch】sklearn scikit-learn 报错:
pytorch·python·sklearn
叶子丶苏3 小时前
第十八节_PySide6基本窗口控件深度补充_剪贴板与拖曳功能(QMimeData 类) 上篇
python·pyqt
酷酷的佳4 小时前
python--面向对象(3)
python