为何Python Pygame中ball的initialvel属性始终与vel属性相等?
问题:Pygame物理引擎重置小球状态失效,初始值随实时值同步变化
我用Python Pygame开发物理引擎时,遇到了一个问题:按R键重置小球的速度和位置时完全无效。测试后发现,ball.initialvel在初始化赋值后始终和ball.vel相等,每次ball.vel变化时initialvel也会跟着变,但初始化代码明明只执行了一次。
问题根源
pygame.Vector2是可变对象,当执行i.initialvel = i.vel这种赋值操作时,并没有复制向量的值,只是让initialvel和vel指向了内存中的同一个对象。后续对vel的任何修改(比如碰撞时反转速度、加速度累加)都会直接修改这个共享对象的数据,导致initialvel同步变化,自然无法保留初始状态。
修复方案
核心是给初始值创建独立的Vector2拷贝,而不是引用原对象,有两种简单的实现方式:
方式一:修改初始化循环
在给每个小球设置初始值时,使用copy()方法复制向量:
for i in ballz: i.initialpos = i.pos.copy() # 复制位置向量,生成新对象 i.initialvel = i.vel.copy() # 复制速度向量,生成新对象
方式二:在类的初始化方法中直接保存拷贝
把初始值的保存逻辑放到ball类的__post_init__方法里,一步到位:
def __post_init__(self): if self.pos is None: self.pos = pygame.Vector2(screen.get_width() / 2, screen.get_height() / 2) if self.vel is None: self.vel = pygame.Vector2(40,30) if self.accel is None: self.accel = g # 直接保存初始状态的拷贝 self.initialpos = self.pos.copy() self.initialvel = self.vel.copy()
重置逻辑的优化(可选)
重置时如果希望完全隔离初始值和当前值,也可以再次拷贝初始值:
if event.type == pygame.KEYDOWN and event.key == pygame.K_r: for allballs in ballz: allballs.pos = allballs.initialpos.copy() allballs.vel = allballs.initialvel.copy()
完整修改后的代码
import numpy as np import pygame from dataclasses import dataclass import random import string # pygame setup pygame.init() screen = pygame.display.set_mode((1280, 720)) clock = pygame.time.Clock() running = True dt = 0 #Constants and Initialisations r = 40 M = 10 g = pygame.Vector2(0, 9.81) @dataclass class ball: rad: float = 40 colour: str = "red" vel: pygame.Vector2 = None pos: pygame.Vector2 = None accel: pygame.Vector2 = None mass: float = 1 initialvel: pygame.Vector2 = None initialpos: pygame.Vector2 = None def __post_init__(self): if self.pos is None: self.pos = pygame.Vector2(screen.get_width() / 2, screen.get_height() / 2) if self.vel is None: self.vel = pygame.Vector2(40,30) if self.accel is None: self.accel = g # 保存初始状态的拷贝 self.initialpos = self.pos.copy() self.initialvel = self.vel.copy() def BoxCollision(self): if self.pos.x - self.rad <= 50 or self.pos.x + self.rad >= 1230: self.vel.x = -self.vel.x if self.pos.y - self.rad <= 50 or self.pos.y + self.rad >= 670: self.vel.y = -self.vel.y def PosUpdate(self): pygame.draw.circle(screen, self.colour, self.pos, self.rad) self.vel += self.accel * dt self.BoxCollision() self.pos += self.vel * dt #Declarations of balls (objects) ballR = ball() ballB = ball(30,"blue" , pygame.Vector2(-35,40)) ballY = ball(30,"yellow" , pygame.Vector2(-55,100)) ballz = [ballR, ballB, ballY] #Main Loop while running: for event in pygame.event.get(): if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE): running = False if event.type == pygame.KEYDOWN and event.key == pygame.K_r: for allballs in ballz: print("Before reset - Initial Velocity:", allballs.initialvel) print("Before reset - Velocity:", allballs.vel) allballs.pos = allballs.initialpos.copy() allballs.vel = allballs.initialvel.copy() print("After reset - Initial Velocity:", allballs.initialvel) print("After reset - Velocity:", allballs.vel) if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE: pygame.time.wait(60000) screen.fill("black") box = pygame.draw.rect(screen, "blue", (50, 50, 1180, 620), width = 1) ballR.PosUpdate() ballB.PosUpdate() ballY.PosUpdate() pygame.display.flip() dt = clock.tick(144) / 100 pygame.quit()
内容的提问来源于stack exchange,提问作者rubin
相关产品推荐
相关产品推荐

