PyQt5像素级碰撞检测

PyQt5 pixel level collision detection

我正在学习 Python 并想用 PyQ5t 制作一个简单的平台游戏。 目前,当我想在游戏中的形状之间进行像素级碰撞检测时,我遇到了麻烦。 我已将 QPixMap 设置为透明并尝试使用 QGraphicsPixmapItem.HeuristicMaskShape 形状模式,但碰撞检测不起作用。

如果我将形状(在本例中为鼠标)背景设为灰色并移除形状模式,则会发生矩形碰撞检测。

我在这里缺少什么?我花了几个小时在 Internet 上搜索,但还没有解决方案...

这是我的代码来显示问题,请使用箭头键移动红色 "Mouse" :) 我希望在红色圆圈中的第一个像素接触棕色平台时看到碰撞检测文本。

import sys
from PyQt5.QtGui import QPen, QBrush
from PyQt5 import QtCore, QtGui
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtWidgets import QApplication, QWidget, QGraphicsView, QGraphicsScene, QLabel, QGraphicsPixmapItem, QFrame

class Mouse(QGraphicsPixmapItem):

    def __init__(self, parent):
        super().__init__()
        self.canvas = QtGui.QPixmap(40,40)
        self.canvas.fill(Qt.transparent)
        self.setPixmap(self.canvas)
        self.x = 100
        self.y = 100
        self.setPos(self.x, self.y)
        self.setFlag(QGraphicsPixmapItem.ItemIsMovable)
        self.setFlag(QGraphicsPixmapItem.ItemIsFocusable)
        self.setShapeMode(QGraphicsPixmapItem.HeuristicMaskShape)
        self.setFocus()
        parent.addItem(self)

    def paint(self, painter, option, widget=None):
        super().paint(painter, option, widget)
        pen = QPen(Qt.black, 4, Qt.SolidLine)
        brush = QBrush(Qt.red, Qt.SolidPattern)
        painter.save()
        painter.setPen(pen)
        painter.setBrush(brush)
        painter.drawEllipse(QPoint(20,20),16,16)
        painter.restore()

    def keyPressEvent(self, e):
        if e.key() == Qt.Key_Right:
            self.x += 5
        if e.key() == Qt.Key_Left:
            self.x -= 5
        if e.key() == Qt.Key_Up:
            self.y -= 5
        if e.key() == Qt.Key_Down:
            self.y += 5
        self.setPos(self.x, self.y)
        collides_with_items = self.collidingItems(mode=Qt.IntersectsItemShape)
        if collides_with_items:
            print("Collision detected!")
            for item in collides_with_items:
                print(item)

class Platform(QFrame):

    PLATFORM_STYLE = "QFrame { color: rgb(153, 0, 0); \
                               background: rgba(0,0,0,0%); }"

    def __init__(self, parent, x, y, width, height):
        super().__init__()
        self.setGeometry(QtCore.QRect(x, y, width, height))
        self.setFrameShadow(QFrame.Plain)
        self.setLineWidth(10)
        self.setFrameShape(QFrame.HLine)
        self.setStyleSheet(Platform.PLATFORM_STYLE)
        parent.addWidget(self)

class GameScreen(QGraphicsScene):
    def __init__(self):
        super().__init__()

        # Draw background
        background = QLabel()
        background.setEnabled(True)
        background.setScaledContents(True)
        background.setGeometry(0, 0, 1280, 720)
        background.setPixmap(QtGui.QPixmap("StartScreen.png"))

        background.setText("")
        background.setTextFormat(QtCore.Qt.RichText)
        self.addWidget(background)
        self.line_5 = Platform(self, 0, 80, 431, 16)
        self.mouse = Mouse(self)

class Game(QGraphicsView):
    def __init__(self):  
        super().__init__()
        self.setWindowTitle("Running Mouse")
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.gamescreen = GameScreen()
        self.setScene(self.gamescreen)
        self.show()

if __name__ == '__main__':
    app = QApplication([])
    game = Game()
    sys.exit(app.exec_())

您提供的是一个空像素图 (self.canvas),因此无论您 select 是什么形状模式,它总是具有 null 形状,除非你使用 BoundingRectShape,它使用像素图边界矩形。

您手动绘制圆并不重要,因为碰撞检测不考虑绘制(也不应该)。

您可以覆盖 shape() 方法以提供您自己的形状(使用 QPainterPath):

def shape(self):
    path = QtGui.QPainterPath()
    path.addEllipse(12, 12, 16, 16)
    return path

但是,由于您只绘制了一个椭圆,因此只需使用 QGraphicsEllipseItem 即可。


一些未请求的建议:

  • 如果您打算使用场景(QGraphicsItem 的父项目只能是另一个 QGraphicsItem),我会避免使用名称 "parent",并且项目通常不应该 "add itself"到一个场景;
  • 不要覆盖 self.xself.y,因为它们是 QGraphicsItem existing properties;
  • 如果你在一个场景中有多个项目并且只想使用键盘控制一个,通常最好覆盖场景或视图的键事件方法(特别是如果你使用箭头键:即使滚动条是隐藏的,它们仍然可以拦截那些移动);
  • 如果您仍想在该项目上使用键盘事件,请确保该项目保持键盘焦点:使用 setFocus 是不够的,因为一旦用户点击其他地方它就会失去焦点;一个可能的解决方案是使用 QGraphicsItem.grabKeyboard()(只有在将项目添加到场景后才有效);
  • 如果您在 QFrame 上设置样式表,它很可能至少会部分忽略任何框架选项集(形状、阴影等),因为它们是 style/platform 相关的;通常最好不要将样式表与与可视化相关的 Qt 小部件设置混合使用,因此在您的情况下,您可能只需要添加一个带有正确 stylesheet parameters set;
  • 的简单 QFrame
  • 虽然从技术上讲这不是问题,但通常最好与导入 "styles" 保持一致,尤其是在使用像 Qt 这样的复杂模块时:您要么导入单个 类 (from PyQt5.QtWidgets import QApplication, ...) 或子模块 (from PyQt5 import QtCore, ...),否则会使代码混乱;