PyQt5 像素级碰撞检测

Posted

技术标签:

【中文标题】PyQt5 像素级碰撞检测【英文标题】:PyQt5 pixel level collision detection 【发布时间】:2020-04-11 15:54:05 【问题描述】:

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

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

我在这里缺少什么?我花了几个小时在互联网上挖掘,但还没有解决方案......

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

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_())

【问题讨论】:

哦,我忘了说 pygame 在这种特殊情况下不适合答案:) 【参考方案1】:

您提供的是一个空像素图 (self.canvas),因此无论您选择哪种形状模式,它都将始终具有 null 形状,除非您使用 BoundingRectShape,它使用像素图边界矩形。

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

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

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

但是,由于您只是在绘制一个椭圆,因此请改用 QGraphicsEllipseItem。


一些未经请求的建议:

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

【讨论】:

非常感谢您提供这些说明!高度赞赏并帮助我解决了我的问题! :)

以上是关于PyQt5 像素级碰撞检测的主要内容,如果未能解决你的问题,请参考以下文章

android 游戏 碰撞检测

SDL 中的每像素 2D 圆形碰撞检测

碰撞检测 (矩形圆形点旋转矩形框像素)

ActionScript 3 像素完美碰撞检测AS3

C++ 2D 像素完美碰撞检测库?

AEJoy —— 表达式之碰撞检测JS