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 方法。

因此,需要进行三处更改:

  1. 想办法跟现场沟通,发生碰撞,分数要增加;正确的方法是通过信号,但由于基本的 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")
  1. 连接子弹信号;这需要对逻辑进行微小但重要的更改,这是因为更好更正确的 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()
  1. 创建 scoreplayer 实例成员,并在需要时连接信号:
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)

注意:

  1. 我改变了一些小东西(最值得注意的是,如果你只需要在一个轴上移动,只需使用 setX()setY()
  2. 添加项目应该是 scene/controller 的责任,删除项目也应该是责任;我没有更改它以避免使事情复杂化,但请记住,一个项目只能由场景或其祖先之一删除;另外,您不应多次尝试删除一个项目;
  3. 应谨慎使用可能会删除项目的计时器,因为有时会在项目从场景中删除后立即调用 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.

覆盖它