Search code examples
pythonlistclassphysics-engineprojectile

Referencing an appended list with user generated class objects from definition within a class?


I want to create a class Ball, and make a list of objects generated by user for that class. I will then reference this list in run() to draw user generated objects in pygame. My problems are as follows:

When I try to reference Ball(a, v, r...) it says Ball is referenced before my definition. Any help is appreciated!

HERE IS FULL ERROR:

File "/Users/.../Desktop/.../python/promot.py", line 66, in run
    balls.append(Ball(a, v, r, c, x_pos, y_pos)) #here i get error
                 ^^^^
UnboundLocalError: cannot access local variable 'Ball' where it is not associated with a value
import pygame
import math
 
pygame.init()
 
#screen
framespd = 30 #what is frame speed for gravity = 9.81
gravity = 1
t = 0.01
clock = pygame.time.Clock()
width, height = 1000, 1000
pygame.display.set_caption("Orbit Take 1")
screen = pygame.display.set_mode([width, height])
 
class Ball:
    def __init__(self, angle, velocity, radius, color, x_pos, y_pos):
        self.angle = angle
        self.velocity = velocity
        self.radius = radius
        self.color = color
        self.x_pos = x_pos
        self.y_pos = y_pos
 
    def draw(self):
        self.circle = pygame.draw.circle(screen, self.color, (self.x_pos, self.y_pos), self.radius)
 
    def true_velocity_x(self):
        true_velocity_x = self.velocity * math.cos(self.angle)
 
    def true_velocity_y(self):
        true_velocity_y = self.velocity * math.sin(self.angle)
        new_velocity_y = true_velocity_y + (gravity * t)
 
    #def update position(self):
        #x pos = x velocity
        #y pos += y velocity
 
balls = [] 
 
def run():
    value = True
    while value:
        clock.tick(framespd)
 
        # Balls in sim
 
        number_balls = int(input("How many balls: "))
 
        while len(balls) < number_balls:
            # Variables for Ball
            a = input("Angle: ")
            v = input("Velocity: ")
            r = input("Radius: ")
            c = input("Color: ")
            x_pos = '0'
            y_pos = '500'
            balls.append(Ball(a, v, r, c, x_pos, y_pos)) #here i get error "local variable "Ball" defined in enclosing scope on line 24 referenced before assignment"
 
        screen.fill('black')    
 
        #draw ball
        for Ball in balls:
            Ball.draw(pygame.display.set_mode((width, height)))
        #update position
 
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                value = False
            pygame.display.flip()
    pygame.quit()       
 
run()

My first attempt is referenced in bold as "something else I tried", but I am having trouble using classes outside of their initialization with out errors. My next attempt was "current attempt" which does not show anything when I run.


Solution

  • For the first ball this was working correctly but then it encountered the

    for Ball in balls:
    

    statement and then you had a Ball in your namespace so the global variable is overwritten, instead we can call the object ball and run .draw on the ball object.

    There was a way around this (if you insist on calling the local variable Ball) by passing the class into the function that I demonstrated as well.

    def run(cls = Ball):
    
    
    import pygame
    import math
    
    pygame.init()
    
    # screen
    framespd = 30  # what is frame speed for gravity = 9.81
    gravity = 1
    t = 0.01
    clock = pygame.time.Clock()
    width, height = 1000, 1000
    pygame.display.set_caption("Orbit Take 1")
    screen = pygame.display.set_mode([width, height])
    
    
    class Ball:
        def __init__(self, angle, velocity, radius, color, x_pos, y_pos):
            self.angle = angle
            self.velocity = velocity
            self.radius = radius
            self.color = color
            self.x_pos = x_pos
            self.y_pos = y_pos
            self.balls = []
    
        def draw(self, width, height):
            # We need the inputs of width and height to pass to set_mode
            pygame.display.set_mode((width, height))
            #self.circle = pygame.draw.circle(screen, (0, 0, 255), (250, 250), (self.x_pos, self.y_pos), self.radius)
            # below will always draw a blue circle - tweak color, center and radius to be variables so that you can
            # generate multiple balls with different properties
            self.circle = pygame.draw.circle(screen, color=(0, 0, 255), center=(250, 250), radius=75)
    
        def true_velocity_x(self):
            true_velocity_x = self.velocity * math.cos(self.angle)
    
        def true_velocity_y(self):
            true_velocity_y = self.velocity * math.sin(self.angle)
            new_velocity_y = true_velocity_y + (gravity * t)
    
        # def update position(self):
        # x pos = x velocity
        # y pos += y velocity
    
    
    balls = []
    
    
    def run():
        value = True
        while value:
            clock.tick(framespd)
    
            # Balls in sim
    
            number_balls = int(input("How many balls: "))
    
            while len(balls) < number_balls:
                # Variables for Ball
                a = input("Angle: ")
                v = input("Velocity: ")
                r = input("Radius: ")
                c = input("Color: ")
                x_pos = '0'
                y_pos = '500'
                balls.append(Ball(a, v, r, c, x_pos,
                                  y_pos))  # here i get error "local variable "Ball" defined in enclosing scope on line 24 referenced before assignment"
    
            screen.fill('black')
    
            # draw ball
            # call our ball 'ball' so it doesn't overwrite out global class
            for ball in balls:
                ball.draw(width, height)
            # update position
    
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    value = False
                pygame.display.flip()
        pygame.quit()
    
    
    run()