QGraphicsTextItem 没有更新分数
QGraphicsTextItem is not updating the score
我想在 pyqt 中构建一个简单的游戏,我想当敌人与子弹碰撞时增加分数,我创建了一个从 QGraphicsTextItem 扩展的分数 class,我还创建了分数文本我添加了一个增加方法。之后我在我的 Bullet.py 文件中添加了这个 class,因为在 Bullet.py 中发生了碰撞,我也在我的 Window.py 文件中添加了这个 class,因为我希望文本应该在场景中,但是当我 运行 游戏得分为 0 并且碰撞后没有任何反应,这些是我的文件
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")
这是我的 Score.py 文件
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))
问题是每次检测到碰撞时您都在创建一个 score
项目,但该项目未添加到场景中,因此它在 move
结束时被删除, 使其无用;但是,即使您 确实 将该项目添加到场景中,这也毫无意义,因为场景中已经存在 score
项目,但您没有保持持久性引用它,所以你不能调用它的 increase
方法。
因此,需要进行三处更改:
- 想办法跟现场沟通,发生碰撞,分数要增加;正确的方法是通过信号,但由于基本的 QGraphicsItems 不继承自 QObject,因此您无法为它们创建信号,因此您可以使用 class 作为“信号代理”;
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")
- 连接子弹信号;这需要对逻辑进行微小但重要的更改,这是因为更好更正确的 OOP 实现将使场景(或更好的“主控制器”)负责添加子弹;所以,我们也会给播放器添加一个信号代理;
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
和 player
实例成员,并在需要时连接信号:
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)
注意:
- 我改变了一些小东西(最值得注意的是,如果你只需要在一个轴上移动,只需使用
setX()
或 setY()
)
- 添加项目应该是 scene/controller 的责任,删除项目也应该是责任;我没有更改它以避免使事情复杂化,但请记住,一个项目只能由场景或其祖先之一删除;另外,您不应多次尝试删除一个项目;
- 应谨慎使用可能会删除项目的计时器,因为有时会在项目从场景中删除后立即调用
move
,这可能会引发异常(因为 self.scene()
会 return None
);以下更改将解决此问题:
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)
MyBullet 显然也需要同样的东西,您应该从 classes;
的 __init__
中删除 self.timer.start(50)
最后,虽然视图的场景在程序的生命周期内通常总是相同的,但这并不总是正确的; scene()
是 QGraphicsView 的 existing 动态函数,所以你不应该用 self.scene
.
覆盖它
我想在 pyqt 中构建一个简单的游戏,我想当敌人与子弹碰撞时增加分数,我创建了一个从 QGraphicsTextItem 扩展的分数 class,我还创建了分数文本我添加了一个增加方法。之后我在我的 Bullet.py 文件中添加了这个 class,因为在 Bullet.py 中发生了碰撞,我也在我的 Window.py 文件中添加了这个 class,因为我希望文本应该在场景中,但是当我 运行 游戏得分为 0 并且碰撞后没有任何反应,这些是我的文件
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")
这是我的 Score.py 文件
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))
问题是每次检测到碰撞时您都在创建一个 score
项目,但该项目未添加到场景中,因此它在 move
结束时被删除, 使其无用;但是,即使您 确实 将该项目添加到场景中,这也毫无意义,因为场景中已经存在 score
项目,但您没有保持持久性引用它,所以你不能调用它的 increase
方法。
因此,需要进行三处更改:
- 想办法跟现场沟通,发生碰撞,分数要增加;正确的方法是通过信号,但由于基本的 QGraphicsItems 不继承自 QObject,因此您无法为它们创建信号,因此您可以使用 class 作为“信号代理”;
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")
- 连接子弹信号;这需要对逻辑进行微小但重要的更改,这是因为更好更正确的 OOP 实现将使场景(或更好的“主控制器”)负责添加子弹;所以,我们也会给播放器添加一个信号代理;
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
和player
实例成员,并在需要时连接信号:
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)
注意:
- 我改变了一些小东西(最值得注意的是,如果你只需要在一个轴上移动,只需使用
setX()
或setY()
) - 添加项目应该是 scene/controller 的责任,删除项目也应该是责任;我没有更改它以避免使事情复杂化,但请记住,一个项目只能由场景或其祖先之一删除;另外,您不应多次尝试删除一个项目;
- 应谨慎使用可能会删除项目的计时器,因为有时会在项目从场景中删除后立即调用
move
,这可能会引发异常(因为self.scene()
会 returnNone
);以下更改将解决此问题:
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)
MyBullet 显然也需要同样的东西,您应该从 classes;
的__init__
中删除 self.timer.start(50)
最后,虽然视图的场景在程序的生命周期内通常总是相同的,但这并不总是正确的; scene()
是 QGraphicsView 的 existing 动态函数,所以你不应该用 self.scene
.