问题
Preamble: I feel I probably have wasted so much time for a simple situation...
Now, I am making a game with pygame, and at some point, I wanted to split files into two, namely main.py
and configurations.py
in order to make it more readable.
Everything was going good, until I ran into this problem.
I will share whole code at the bottom, but I want to summarize first:
Now first of all, in main.py
, I am importing by,
from configurations import *
now, the game loop on the main.py
depends on the variable running
by
while running:
.......
.......
.......
And the variable running
is initialized in configurations.py
by,
# initialize some variables
running = True
So, the main.py
has to be getting variable running
because it does not give any error and uses it in while running
statement.
In the main loop, there is a section where I check for events as follows,
for event in pygame.event.get():
# check for closing window
if event.type == pygame.QUIT:
running = False
This part works just as expected, it alters variable running and program gets out of the while loop.
Now, here comes the problematic part.
In one of the classes(Player class) there is a method as decrease_HP
,
def decrease_HP(self):
self.HP -= 1
print("-1 HP", "Current HP:", self.HP)
if self.HP <= 0:
running = False
However, the point I could not figure out is that, it is not changing running variable properly, and game never stops(gets out of while loop). Here is one example output which shows the problem.
pygame 1.9.6
Hello from the pygame community. https://www.pygame.org/contribute.html
-1 HP Current HP: 2
-1 HP Current HP: 1
-1 HP Current HP: 0
-1 HP Current HP: -1
-1 HP Current HP: -2
-1 HP Current HP: -3
-1 HP Current HP: -4
-1 HP Current HP: -5
-1 HP Current HP: -6
So, I hope I could make it clear. I probably have a misunderstanding about importing variables or variable scopes.
By the way, I have tried adding global running
above the running = False
statement in Player.decrease_HP
function.
Thanks in advance.
Exact codes in the files
main.py
# Pygame template - skeleton for a new pygame project
from configurations import *
# initiate some variables
max_bullet = 10
# initialize pygame and create window
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("CORONA RACE")
clock = pygame.time.Clock()
player = Player()
all_sprites.add(player)
# initialize some variables
running = True
# have to use this because, otherwise, for the first SPACE key pressing, the newest_bullet is not defined yet.
newest_bullet = Bullet(0, 0)
# Game loop
while running:
# keep loop running at the right speed
clock.tick(FPS)
# Process input (events)
for event in pygame.event.get():
# check for closing window
if event.type == pygame.QUIT:
running = False
else:
pass
while len(mobs) != 5:
m = Mob()
all_sprites.add(m)
mobs.add(m)
keystate = pygame.key.get_pressed()
player.speedx = 0
if keystate[pygame.K_RIGHT]:
player.speedx += player.SPEED
if keystate[pygame.K_LEFT]:
player.speedx -= player.SPEED
if keystate[pygame.K_SPACE] and player.rect.top - newest_bullet.rect.bottom > BULLET_H + MARGIN and not len(bullets) >= max_bullet:
newest_bullet = player.shoot()
# BULLET_H refers to height of the bullet and margin refers to the minimum allowable margin between two consequent b
# If there are more than 10 bullets at a time on the screen, then no more new bullets can be fired.
if keystate[pygame.K_ESCAPE]:
running = False
if random.randint(0, 14530) > 14470:
power_up = PowerUp()
all_sprites.add(power_up)
powerups.add(power_up)
hits = pygame.sprite.spritecollide(player, powerups, True)
for pu in hits:
power_up_funcs[pu.type](player)
hits = pygame.sprite.groupcollide(mobs, bullets, True, True)
for m in hits:
pass
hits = pygame.sprite.spritecollide(player, mobs, True)
if hits:
player.decrease_HP()
# print(player.HP)
# Update
all_sprites.update()
# Draw / render
screen.fill(WHITE)
all_sprites.draw(screen)
# *after* drawing everything, flip the display
pygame.display.flip()
pygame.quit()
raise SystemExit # to exit python
configurations.py
import pygame
import random
# define constants
WIDTH = 600
HEIGHT = 960
FPS = 30
BULLET_H = 24
BULLET_W = 8
POWERUP_H = 30
POWERUP_W = 30
MOB_W = 50
MOB_H = 80
MARGIN = 10
# define colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
MAGENTA = (255, 0, 255)
CYAN = (0, 255, 255)
# create sprite groups
all_sprites = pygame.sprite.Group()
bullets = pygame.sprite.Group()
powerups = pygame.sprite.Group()
mobs = pygame.sprite.Group()
# initialize some variables
running = True
# player sprite
class Player(pygame.sprite.Sprite):
SPEED = 15
def __init__(self):
super().__init__()
# pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((100, 150))
self.image.fill(CYAN)
pygame.draw.circle(self.image, RED, (50, 75), 15, 5)
self.rect = self.image.get_rect()
self.rect.centerx = WIDTH / 2
self.rect.bottom = HEIGHT - 5
self.speedx = 0
self.HP = 3
def update(self):
self.rect.x += self.speedx
if self.rect.right > WIDTH:
self.rect.right = WIDTH
if self.rect.left < 0:
self.rect.left = 0
def shoot(self):
bullet = Bullet(self.rect.centerx, self.rect.top)
all_sprites.add(bullet)
bullets.add(bullet)
return bullet # I need this to set the margin in continious fire.
def change_color(self):
pass
def increase_HP(self):
if self.HP <= 2:
self.HP += 1
print("+1 HP", "Current HP:", self.HP)
else:
print("HP IS ALREADY FULL", "Current HP:", self.HP)
def decrease_HP(self):
self.HP -= 1
print("-1 HP", "Current HP:", self.HP)
if self.HP <= 0:
running = False
class Mob(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((MOB_W, MOB_H))
self.image.fill(MAGENTA)
self.rect = self.image.get_rect()
self.rect.left = random.randint(0, WIDTH - POWERUP_W)
self.rect.bottom = random.randint(-2 * MOB_H, 0)
def update(self):
self.rect.y += 6
if self.rect.top > HEIGHT:
self.kill()
# Bullet sprite
class Bullet(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = pygame.Surface((BULLET_W, BULLET_H))
self.image.fill(RED)
self.rect = self.image.get_rect()
self.rect.centerx = x
self.rect.bottom = y
self.speedx = 0
self.speedy = -20
def update(self):
self.rect.y += self.speedy
# kill it if it moves away from the screen
if self.rect.bottom < 0:
self.kill() # built in method of pygame.sprite
# powerup sprite
power_up_funcs = [Player.increase_HP, print] # container for to-do functs.
class PowerUp(pygame.sprite.Sprite):
SPEEDY = 8
def __init__(self):
super().__init__()
self.type = random.randint(0, 1) # [0,1] integer
if self.type == 0: # HP power up
self.image = pygame.Surface((POWERUP_W, POWERUP_H))
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect.left = random.randint(0, WIDTH - POWERUP_W)
# self.rect.centerx = player.rect.centerx #debug
self.rect.bottom = 0
elif self.type == 1: # shield
self.image = pygame.Surface((POWERUP_W, POWERUP_H))
self.image.fill(BLUE)
self.rect = self.image.get_rect()
self.rect.left = random.randint(0, WIDTH - POWERUP_W)
# self.rect.centerx = player.rect.centerx # debug
self.rect.bottom = 0
else:
pass
def update(self):
self.rect.y += self.SPEEDY
if self.rect.top > HEIGHT:
self.kill()
回答1:
TLDR: Use import configuration
and fully qualified names, e.g. configuration.running
.
If a function inside configuration
needs to modify a top-level value, it must use global
.
def decrease_HP(self):
global running
self.HP -= 1
print("-1 HP", "Current HP:", self.HP)
if self.HP <= 0:
running = False
Using from configurations import running
(or equivalent via ... import *
) in main
binds the value of configurations.running
to a new name main.running
. While these names initially share the same value, re-assigning either breaks this equivalency. This is exactly the same as rebinding other names.
>>> a = 1
>>> b = a # a and b point to same value
>>> a == b
True
>>> b = 2 # rebind only b
>>> a == b
False
To make changes visible across the application, one should use an object an modify its value. A common example are containers, such as lists.
>>> a = [1]
>>> b = a # a and b point to same value
>>> a == b
True
>>> b[0] = 2 # modify content of value of b
>>> a == b
True
>>> a[0] == b[0] # content is the same
True
Since modules are objects, it is possible to use them directly to store state.
>>> import configuration
>>> b = configuration # configuration and b point to same value
>>> configuration == b
True
>>> b.running = False # modify content of value of b
>>> configuration == b
True
>>> configuration.running == b.running # content is the same
True
Functions have local scope. Any assignment to a name inside a function implicitly declares the target as local to the function.
>>> running = True
>>> def stop():
... running = False
...
>>> stop() # changes only running inside the function
>>> running
True
This can be made visible by accessing a local name before it has a value.
>>> running = True
>>> def stop():
... print(running)
... running = False
...
>>> stop()
UnboundLocalError: local variable 'running' referenced before assignment
By declaring the name global
, no local name is created. The global value can be directly read and written.
>>> running = True
>>> def stop():
... global running
... print(running)
... running = False
...
>>> stop() # will print the global value before the change
True
>>> running # global value was changed
False
来源:https://stackoverflow.com/questions/60838018/python-variable-importing-between-files