问题
I am making a Covid simulator and need my balls to move around randomly. However, I want them to stay moving in the first random direction that the chose, and only chnage direction if it hits another cell or hits the wall.
My current code is as follow:
import random
import pygame
# --- constants --- (UPPER_CASE_NAMES)
GREEN1 = (0, 255, 0) # Healthy cells
RED = (255, 0, 0) # Infected cells
GREEN2 = (0, 100, 0) # Healthy cells not susecptible
BLACK = (0, 0, 0) # Dead cells
WHITE = (255, 255, 255)
BACKGROUND_COLOR = (225, 198, 153)
SCREEN_SIZE = (800, 800)
# --- classes --- (CamelCaseNames)
# class keeep only one cell so it should has name `Cell` instead of `Cells`
class Cell(pygame.sprite.Sprite):
def __init__(self, color, speed, width, height):
super().__init__()
self.color = color
self.speed = speed
self.image = pygame.Surface([width, height])
self.image.fill(WHITE)
self.image.set_colorkey(WHITE)
self.radius = width // 2 # 25
center = [width // 2, height // 2]
pygame.draw.circle(self.image, self.color, center, self.radius, width=0)
self.rect = self.image.get_rect()
self.rect.x = random.randint(0, 400)
self.rect.y = random.randint(50, 700)
self.pos = pygame.math.Vector2(self.rect.center)
self.dir = pygame.math.Vector2(1, 0).rotate(random.randrange(360))
def update(self):
self.pos += self.dir * self.speed
if self.pos.x - self.radius < 0 or self.pos.x + self.radius > SCREEN_SIZE[0]:
self.dir.x *= -1
if self.pos.y - self.radius < 0 or self.pos.y + self.radius > SCREEN_SIZE[1]:
self.dir.y *= -1
for other_cell in all_cells:
if all_cells != self:
distance_vec = self.pos - other_cell.pos
if 0 < distance_vec.length_squared() < (self.radius * 2) ** 2:
self.dir.reflect_ip(distance_vec)
other_cell.dir.reflect_ip(distance_vec)
self.rect.centerx = round(self.pos.x)
self.rect.centery = round(self.pos.y)
# --- functions --- (lower_case_names)
# empty
# --- main --- (lower_case_names)
pygame.init()
screen = pygame.display.set_mode(SCREEN_SIZE)
pygame.display.set_caption("Covid-19 Simualtion")
speed = [0.5, -0.5]
# - objects -
all_cells = pygame.sprite.Group() # PEP8: lower_case_name
for _ in range(5):
cell = Cell(GREEN1, 5, 10, 10) # PEP8: lower_case_name
all_cells.add(cell)
# - loop -
clock = pygame.time.Clock()
end = False
while not end:
# - events -
for event in pygame.event.get():
if event.type == pygame.QUIT:
end = True
# - upadates (without draws) -
all_cells.update()
# - draws (without updates) -
screen.fill(BACKGROUND_COLOR)
pygame.draw.rect(screen, BLACK, (0, 50, 400, 700), 3)
all_cells.draw(screen)
pygame.display.flip()
clock.tick(30) # to use less CPU
# - end
pygame.quit() # some system may need it to close window
I was wondering if there was a way to do this, really grateful for any help. Thanks in advance. I tried copy the first answer below into my pycharm but it didnt work, so now i have put my full code up as opposed to one section in case it is needed. Sorry for not putting it all before
回答1:
I recommend to use pygame.math.Vector2. Add the attributes pos
and dir
(position and direction) to the class Cell
. Set the position with the center of the rect
attribute and generate a vector with a random direction:
class Cell(pygame.sprite.Sprite):
def __init__(self, color, speed, width, height):
# [...]
self.pos = pygame.math.Vector2(self.rect.center)
self.dir = pygame.math.Vector2(1, 0).rotate(random.randrange(360))
Change the position in t he method update
. Add the product of the direction vector dir
and the speed
to the position (pos
). Update the position of the rectangle by rounding (round
) the position vector:
class Cell(pygame.sprite.Sprite):
# [...]
def update(self):
self.pos += self.dir * self.speed
self.rect.centerx = round(self.pos.x)
self.rect.centery = round(self.pos.y)
See How to make ball bounce off wall with Pygame? and apply the suggestions to your code:
class Cell(pygame.sprite.Sprite):
# [...]
def update(self):
self.pos += self.dir * self.speed
if self.pos.x - self.radius < 0:
self.pos.x = self.radius
self.dir.x = abs(self.dir.x)
elif self.pos.x + self.radius > 400:
self.pos.x = 400 - self.radius
self.dir.x = -abs(self.dir.x)
if self.pos.y - self.radius < 50:
self.pos.y = 50 + self.radius
self.dir.y = abs(self.dir.y)
elif self.pos.y + self.radius > 700:
self.pos.y = 700 - self.radius
self.dir.y = -abs(self.dir.y)
self.rect.centerx = round(self.pos.x)
self.rect.centery = round(self.pos.y)
See Pygame how to let balls collide. Test to see if the cells collide and reflect the direction vectors when a collision is detected:
class Cell(pygame.sprite.Sprite):
# [...]
def update(self):
self.pos += self.dir * self.speed
# [...]
for other_cell in all_cells:
if all_cells != self:
distance_vec = self.pos - other_cell.pos
if 0 < distance_vec.length_squared() < (self.radius*2) ** 2:
self.dir.reflect_ip(distance_vec)
other_cell.dir.reflect_ip(distance_vec)
self.rect.centerx = round(self.pos.x)
self.rect.centery = round(self.pos.y)
See also Collision and Intersection - Circle and circle
Complete example:
import random
import pygame
GREEN1 = (0, 255, 0) # Healthy cells
RED = (255, 0, 0) # Infected cells
GREEN2 = (0, 100, 0) # Healthy cells not susecptible
BLACK = (0, 0, 0) # Dead cells
WHITE = (255, 255, 255)
BACKGROUND_COLOR = (225, 198, 153)
SCREEN_SIZE = (800, 800)
speed = [0.5, -0.5]
class Cell(pygame.sprite.Sprite):
def __init__(self, color, speed, width, height):
super().__init__()
self.color = color
self.speed = speed
self.image = pygame.Surface([width, height])
self.image.fill(WHITE)
self.image.set_colorkey(WHITE)
self.radius = width // 2 # 5
center = [width // 2, height // 2]
pygame.draw.circle(self.image, self.color, center, self.radius, width=0)
self.rect = self.image.get_rect()
self.rect.x = random.randint(0, 400)
self.rect.y = random.randint(50, 700)
self.pos = pygame.math.Vector2(self.rect.center)
self.dir = pygame.math.Vector2(1, 0).rotate(random.randrange(360))
def update(self):
self.pos += self.dir * self.speed
border_rect = pygame.Rect(0, 50, 400, 700)
if self.pos.x - self.radius < border_rect.left:
self.pos.x = border_rect.left + self.radius
self.dir.x = abs(self.dir.x)
elif self.pos.x + self.radius > border_rect.right:
self.pos.x = border_rect.right - self.radius
self.dir.x = -abs(self.dir.x)
if self.pos.y - self.radius < border_rect.top:
self.pos.y = border_rect.top + self.radius
self.dir.y = abs(self.dir.y)
elif self.pos.y + self.radius > border_rect.bottom:
self.pos.y = border_rect.bottom - self.radius
self.dir.y = -abs(self.dir.y)
for other_cell in all_cells:
if all_cells != self:
distance_vec = self.pos - other_cell.pos
if 0 < distance_vec.length_squared() < (self.radius*2) ** 2:
self.dir.reflect_ip(distance_vec)
other_cell.dir.reflect_ip(distance_vec)
self.rect.centerx = round(self.pos.x)
self.rect.centery = round(self.pos.y)
pygame.init()
screen = pygame.display.set_mode(SCREEN_SIZE)
pygame.display.set_caption("Covid-19 Simualtion")
all_cells = pygame.sprite.Group() # PEP8: lower_case_name
for i in range(100):
cell = Cell(GREEN1, 5, 10, 10) # PEP8: lower_case_name
all_cells.add(cell)
clock = pygame.time.Clock()
end = False
while not end:
for event in pygame.event.get():
if event.type == pygame.QUIT:
end = True
all_cells.update()
screen.fill(BACKGROUND_COLOR)
pygame.draw.rect(screen, BLACK, (0, 50, 400, 700), 3)
all_cells.draw(screen)
pygame.display.flip()
clock.tick(30) # to use less CPU
来源:https://stackoverflow.com/questions/65468240/random-systematic-movement-in-pygame