I want to build a simple game in pyqt, i want when the enemy collides with the bullet increase the score, I have created a Score class that extends from QGraphicsTextItem, I have created the score text also I have added an increase method. after that I have added this class in my Bullet.py file, because in the Bullet.py collision occurs, also I have added this class in my Window.py file, because I want that text should be in the scene, but when I run the game the score is 0 and after colliding nothing happens, these are my files
Window.py
from PyQt6.QtWidgets import QGraphicsScene, QApplication, QGraphicsView, QGraphicsItem
from PyQt6.QtCore import Qt, QTimer
import sys
from Player import Player
from Enemy import Enemy
from Score import Score
class Window(QGraphicsView):
def __init__(self):
super().__init__()
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.setFixedSize(800, 600)
self.create_scene()
self.show()
def create_scene(self):
self.scene = QGraphicsScene()
# create an item to put in the scene
player = Player()
# make rect focusable
player.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsFocusable)
player.setFocus()
# by default QGraphicsRectItem has 0 length and width
player.setRect(0, 0, 100, 100)
# add item to the scene
self.scene.addItem(player)
# set size of the scene
self.scene.setSceneRect(0, 0, 800, 600)
# set the player at the botoom
player.setPos(self.width() / 2, self.height() - player.rect().height())
#adding the score to the scene
score = Score()
self.scene.addItem(score)
self.setScene(self.scene)
self.timer = QTimer()
self.timer.timeout.connect(self.spawn)
self.timer.start(2000)
def spawn(self):
enemy = Enemy()
self.scene.addItem(enemy)
App = QApplication(sys.argv)
window = Window()
sys.exit(App.exec())
Player.py
from PyQt6.QtWidgets import QGraphicsRectItem
from PyQt6.QtGui import QKeyEvent
from PyQt6.QtCore import Qt
from Bullet import MyBullet
class Player(QGraphicsRectItem):
def __init__(self):
super().__init__()
def keyPressEvent(self, event: QKeyEvent):
if (event.key() == Qt.Key.Key_Left):
if self.pos().x() > 0:
self.setPos(self.x() - 10, self.y())
elif (event.key() == Qt.Key.Key_Right):
if (self.pos().x() + 100 < 800):
self.setPos(self.x() + 10, self.y())
elif (event.key() == Qt.Key.Key_Space):
mybullet = MyBullet()
mybullet.setPos(self.x(), self.y())
self.scene().addItem(mybullet)
Enemy.py
from PyQt6.QtWidgets import QGraphicsRectItem
from random import randint
from PyQt6.QtCore import QTimer
class Enemy(QGraphicsRectItem):
def __init__(self):
super().__init__()
random_number = randint(10,1000) % 700
self.setPos(random_number , 0)
self.setRect(0,0,100,100)
self.timer = QTimer()
self.timer.timeout.connect(self.move)
self.timer.start(50)
def move(self):
#move enemy to down
self.setPos(self.x(), self.y()+5)
if self.pos().y() + self.rect().height() < 0:
self.scene().removeItem(self)
print("Bullet deleted")
Bullet.py
from PyQt6.QtWidgets import QGraphicsRectItem, QGraphicsItem
from PyQt6.QtCore import QTimer
from Enemy import Enemy
from Score import Score
class MyBullet(QGraphicsRectItem):
def __init__(self):
super().__init__()
self.setRect(0, 0, 10, 50)
self.timer = QTimer()
self.timer.timeout.connect(self.move)
self.timer.start(50)
def move(self):
# This is the place for the collision
colliding = self.collidingItems()
for item in colliding:
if isinstance(item, Enemy):
# increase the score
score = Score()
score.increase()
self.scene().removeItem(item)
self.scene().removeItem(self)
self.setPos(self.x(), self.y() - 10)
if self.pos().y() + self.rect().height() < 0:
self.scene().removeItem(self)
print("Bullet deleted")
And this is my Score.py file
from PyQt6.QtWidgets import QGraphicsTextItem
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QFont
class Score(QGraphicsTextItem):
def __init__(self):
super().__init__()
self.score = 0
# draw the text
self.setPlainText("Score : " + str(self.score))
self.setDefaultTextColor(Qt.GlobalColor.red)
self.setFont(QFont("Sanserif", 18))
def increase(self):
self.score += 1
self.setPlainText(str(self.score))
The problem is that you're creating a score
item every time the collision is detected, but that item is not added to the scene, so it gets deleted at the end of move
, making it useless; but, even if you did add that item to the scene, it would be pointless, since a score
item already exists on the scene, but you didn't keep a persistent reference to it, so you cannot call it's increase
method.
So, three changes are required:
class BulletSignals(QObject):
enemyHit = pyqtSignal()
class MyBullet(QGraphicsRectItem):
def __init__(self):
super().__init__()
self.proxy = BulletSignals()
self.enemyHit = self.proxy.enemyHit
# ...
def move(self):
colliding = self.collidingItems()
isHit = False
for item in colliding:
if isinstance(item, Enemy):
self.scene().removeItem(item)
isHit = True
if isHit:
self.scene().removeItem(self)
self.enemyHit.emit()
else:
self.setY(self.y() - 10)
if self.pos().y() + self.rect().height() < 0:
self.scene().removeItem(self)
print("Bullet deleted")
class PlayerSignals(QObject):
fire = pyqtSignal(QPointF)
class Player(QGraphicsRectItem):
def __init__(self):
super().__init__()
self.proxy = PlayerSignal()
self.fire = self.proxy.fire
def keyPressEvent(self, event: QKeyEvent):
if (event.key() == Qt.Key.Key_Left):
if self.pos().x() > 0:
self.setX(max(0, self.x() - 10))
elif (event.key() == Qt.Key.Key_Right):
if (self.pos().x() + 10 < 800):
self.setX(min(800, self.x() + 10))
elif (event.key() == Qt.Key.Key_Space):
self.fire.emit(self.pos()
score
and player
instance members, and connect the signals when required:class Window(QGraphicsView):
# ...
def create_scene(self):
self.scene = QGraphicsScene()
# create an item to put in the scene
self.player = Player()
self.player.fire.connect(self.fire)
# ...
self.score = Score()
self.scene.addItem(self.score)
# ...
def fire(self, pos):
bullet = MyBullet()
bullet.setPos(pos)
self.scene.addItem(bullet)
bullet.enemyHit.connect(self.score.increase)
Note that:
setX()
or setY()
)move
will be called right after the item has been removed from the scene, which could raise an exception (since self.scene()
would return None
); the following change would fix this:class Enemy(QGraphicsRectItem):
# ...
def itemChange(self, change, value):
if change == self.ItemSceneChange:
if value:
self.timer.start(50)
else:
self.timer.stop()
return super().itemChange(change, value)
the same obviously is required for MyBullet too, and you should remove self.timer.start(50)
from both the __init__
of those classes;
Finally, while the scene of a view is generally always the same during the lifespan of the program, that's not always true; scene()
is an existing dynamic function of QGraphicsView, so you should not overwrite it with self.scene
.