I'm learning Python and thought to make a simple platform game with PyQ5t. Currently I'm in trouble when I want to make a pixel level collision detection between shapes in my game. I have set the QPixMap to transparent and also tried to use QGraphicsPixmapItem.HeuristicMaskShape shape mode but collision detection does not work.
If I make shape (Mouse in this case) backgroun for example gray and remove the shape mode then there happens rectangular collision detection.
What I'm missing here? I have spent few hours digging around the Internet but no solution yet...
Here is my code to show the problem, please use arrow keys to move the the red "Mouse" around :) I hope to see collision detected text when the first pixel in the red circle touches the brown platform.
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_())
You're providing an empty pixmap (self.canvas
), so no matter what shape mode you select, it will always have a null shape, unless you use BoundingRectShape
, which uses the pixmap bounding rect.
The fact that you are manually painting a circle really doesn't matter, as painting is not considered for collision detection (nor it should).
You can override the shape()
method to provide your own shape (using a QPainterPath):
def shape(self):
path = QtGui.QPainterPath()
path.addEllipse(12, 12, 16, 16)
return path
But, since you're only drawing an ellipse, just use QGraphicsEllipseItem instead.
Some unrequested suggestions:
self.x
and self.y
, as they are QGraphicsItem existing properties;setFocus
is not enough, as it will lose focus as soon as the user clicks elsewhere; a possible solution is to use QGraphicsItem.grabKeyboard()
(which works only as soon as the item is added to a scene);from PyQt5.QtWidgets import QApplication, ...
) or the submodules (from PyQt5 import QtCore, ...
), otherwise it will make the code just confusing;