You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何未被引用的default_rect变量值会随rect改变?

问题分析与解决方案

问题描述

我为缩放和滚动功能创建了基准矩形default_rect与绘制用矩形rect,试图单独修改rect的y值,但default_rect的数值也随之改变。编写最小复现示例后发现,调用update_rect()方法时仅读取default_rect.centery,它的值却也被修改,且矩形y值会在数秒内持续变为-1000000。初始化时"Test"仅打印一次,排除__init__多次执行的可能。

根本原因

在Python中,对象赋值是引用传递,原代码中self.default_rect = self.rect这行代码并没有创建新的矩形对象,而是让default_rect和rect指向同一个pygame.Rect实例。所以当你修改rect的属性时,default_rect作为同一个对象的引用,其属性自然会同步变化。这就导致update_rect()每次计算时,都是基于已经被修改过的default_rect.centery累加偏移量,最终y值会无限减小。

修复方案

需要为default_rect创建一个独立的Rect副本,使用pygame.Rect.copy()方法即可实现:

修改Element类的__init__方法:

class Element(pg.sprite.Sprite):
    def __init__(self, img, x, y, placement="center"):
        super().__init__()
        self.image = img
        if placement == "topright":
            self.rect = self.image.get_rect(topright=(x, y))
        if placement == "center":
            self.rect = self.image.get_rect(center=(x, y))
        # 创建Rect副本,避免引用同一对象
        self.default_rect = self.rect.copy()
        print("Test")

    def update_rect(self, y_offset):
        self.rect.centery = self.default_rect.centery + y_offset

完整修复示例代码

import pygame as pg

pg.init()

screen_width, screen_height = 800, 600
screen = pg.display.set_mode((screen_width, screen_height))

grey = (150, 150, 150)
dark_grey = (60, 60, 60)


class Element(pg.sprite.Sprite):
    def __init__(self, img, x, y, placement="center"):
        super().__init__()
        self.image = img
        if placement == "topright":
            self.rect = self.image.get_rect(topright=(x, y))
        if placement == "center":
            self.rect = self.image.get_rect(center=(x, y))
        # 创建Rect副本,避免引用同一对象
        self.default_rect = self.rect.copy()
        print("Test")

    def update_rect(self, y_offset):
        self.rect.centery = self.default_rect.centery + y_offset


class ScrollBar(Element):
    def __init__(self, x, y, total_bar_w, total_bar_h, total_h):
        img = pg.Surface((total_bar_w, total_bar_h))
        img.fill(dark_grey)
        super().__init__(img, x, y, "topright")

        bar_w = 0.98*total_bar_w
        bar_h = (total_bar_h / total_h) * total_bar_h
        bar_img = pg.Surface((bar_w, bar_h))
        bar_img.fill(grey)
        self.bar = Element(bar_img, x, y, "topright")

        self.total_h = total_h
        self.ratio = self.total_h / self.rect.h
        self.offset = 0
        self.y_offset = 0

        self.pressed = False
        self.active = False

    def update(self):
        pos = pg.mouse.get_pos()
        click = pg.mouse.get_pressed()[0]

        # Check if the slider bar was pressed
        if click and self.rect.collidepoint(pos):
            self.active = True
        # Checking if mouse was released or mouse left the allowed zone
        if not click or abs(pos[0] - self.rect.midleft[0]) > self.rect.w*3:
            self.active = False

        if self.active:
            self.bar.rect.centery = pos[1]
            if self.bar.rect.top < 0:
                self.bar.rect.top = 0
            if self.bar.rect.bottom > self.rect.top + self.rect.h:
                self.bar.rect.bottom = self.rect.top + self.rect.h

            # Calculate y offset for elements. Multiply by negative
            # to allow us to add the offset rather than subtract
            self.y_offset = (self.bar.rect.centery - self.bar.rect.h / 2) * -self.ratio

    def draw(self):
        screen.blit(self.image, (self.rect.x, self.rect.y))
        screen.blit(self.bar.image, (self.bar.rect.x, self.bar.rect.y))


class Button(Element):
    def __init__(self, x, y, w, h):
        super().__init__(pg.Surface((w, h)), x, y, "center")
        self.clicked = False

    def update(self):
        self.update_rect(scroll_bar.y_offset)
        action = False
        if self.rect.collidepoint(pg.mouse.get_pos()):
            if pg.mouse.get_pressed()[0] and not self.clicked:
                self.clicked = True
                action = True
            if not pg.mouse.get_pressed()[0]:
                self.clicked = False
        return action

    def draw(self):
        pg.draw.rect(screen, "blue", self.rect)


button1 = Button(400, 100, 200, 150)
button2 = Button(400, 300, 200, 150)
button3 = Button(400, 500, 200, 150)
buttons = [button1, button2, button3]

total_height = screen_height * 2
scroll_bar = ScrollBar(screen_width, 0, 0.05 * screen_width, screen_height, total_height)


run = True
while run:
    screen.fill("white")

    for button in buttons:
        button.draw()
        if button.update():
            print("Button's y value is: " + str(button.rect.y))

    scroll_bar.update()
    scroll_bar.draw()

    for e in pg.event.get():
        if e.type == pg.QUIT:
            run = False
        if e.type == pg.KEYDOWN:
            if e.key == pg.K_SPACE:
                print(f"{button1.rect.y} / {button2.rect.y} / {button3.rect.y}")
            if e.key == pg.K_r:
                print(scroll_bar.y_offset)

    pg.display.update()

内容的提问来源于stack exchange,提问作者user19706742

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 08:45:35