Python boid 实现
Python boids implementation
我正在尝试在 python 中实现一个相当基本的类群模拟。我的目标是使用基本的捕食者猎物设置进行模拟。我发现了一些伪代码(不能 post 超过两个链接,但如果你 google boids pseudocode
它是第一个结果)和一些代码 here 并决定给它一个走。因为我想添加捕食者,所以我决定采用我发现的代码对其进行修改,使 boids(将成为猎物)成为 sprite,然后从那里开始。但是,我已经 运行 解决了这个问题。
在我修改代码以使用 pygame 精灵后,所有 boid 都移动到右下角(原始代码工作正常)。
我的代码(只需克隆存储库)是 here(github)。有没有人 运行 进入过第一期?有没有人有解决它的想法?关于问题2,请问有哪位大侠解释一下怎么做吗?
谢谢您,我们将不胜感激。
P.S.
boids 的行为(它们的移动)似乎工作正常,除了它们总是去右下角的事实。
P.P.S。
多亏了 furas,猎物现在可以正常行动了。
P.P.P.S.
由于调试问题已经解决,我的问题剩下的部分涉及解释,我觉得应该是正题。
您的代码必须有所不同
您在开始时使用了不同的速度 - 在 __init__
中,但这应该没有区别。
您在不同时刻更新对象。
您同时移动所有猎物(使用组更新)- 在所有计算之后。
原始代码在计算后移动每个 boid,因此下一个 boid 使用不同的数据来计算其移动。
我将 prey.update()
放入 for
循环并移除 all_sprites_list.update()
我组织代码的方式有点不同:
#!/usr/bin/env python
# Prey implementation in Python using PyGame
from __future__ import division # required in Python 2.7
import sys
import pygame
import random
import math
# === constants === (UPPER_CASE names)
SCREEN_SIZE = SCREEN_WIDTH, SCREEN_HEIGHT = 800, 600
BLACK = (0, 0, 0)
MAX_VELOCITY = 10
NUM_BOIDS = 50
BORDER = 25
# === classes === (CamelCase names for classes / lower_case names for method)
class Prey(pygame.sprite.Sprite):
def __init__(self, x, y):
super(Prey, self).__init__()
# Load image as sprite
self.image = pygame.image.load("ressources/img/prey.png").convert()
# Fetch the rectangle object that has the dimensions of the image
self.rect = self.image.get_rect()
# Coordinates
self.rect.x = x
self.rect.y = y
# self.velocityX = random.randint(-10, 10) / 10.0
# self.velocityY = random.randint(-10, 10) / 10.0
self.velocityX = random.randint(1, 10) / 10.0
self.velocityY = random.randint(1, 10) / 10.0
def distance(self, prey):
'''Return the distance from another prey'''
distX = self.rect.x - prey.rect.x
distY = self.rect.y - prey.rect.y
return math.sqrt(distX * distX + distY * distY)
def move_closer(self, prey_list):
'''Move closer to a set of prey_list'''
if len(prey_list) < 1:
return
# calculate the average distances from the other prey_list
avgX = 0
avgY = 0
for prey in prey_list:
if prey.rect.x == self.rect.x and prey.rect.y == self.rect.y:
continue
avgX += (self.rect.x - prey.rect.x)
avgY += (self.rect.y - prey.rect.y)
avgX /= len(prey_list)
avgY /= len(prey_list)
# set our velocity towards the others
distance = math.sqrt((avgX * avgX) + (avgY * avgY)) * -1.0
self.velocityX -= (avgX / 100)
self.velocityY -= (avgY / 100)
def move_with(self, prey_list):
'''Move with a set of prey_list'''
if len(prey_list) < 1:
return
# calculate the average velocities of the other prey_list
avgX = 0
avgY = 0
for prey in prey_list:
avgX += prey.velocityX
avgY += prey.velocityY
avgX /= len(prey_list)
avgY /= len(prey_list)
# set our velocity towards the others
self.velocityX += (avgX / 40)
self.velocityY += (avgY / 40)
def move_away(self, prey_list, minDistance):
'''Move away from a set of prey_list. This avoids crowding'''
if len(prey_list) < 1:
return
distanceX = 0
distanceY = 0
numClose = 0
for prey in prey_list:
distance = self.distance(prey)
if distance < minDistance:
numClose += 1
xdiff = (self.rect.x - prey.rect.x)
ydiff = (self.rect.y - prey.rect.y)
if xdiff >= 0:
xdiff = math.sqrt(minDistance) - xdiff
elif xdiff < 0:
xdiff = -math.sqrt(minDistance) - xdiff
if ydiff >= 0:
ydiff = math.sqrt(minDistance) - ydiff
elif ydiff < 0:
ydiff = -math.sqrt(minDistance) - ydiff
distanceX += xdiff
distanceY += ydiff
if numClose == 0:
return
self.velocityX -= distanceX / 5
self.velocityY -= distanceY / 5
def update(self):
'''Perform actual movement based on our velocity'''
if abs(self.velocityX) > MAX_VELOCITY or abs(self.velocityY) > MAX_VELOCITY:
scaleFactor = MAX_VELOCITY / max(abs(self.velocityX), abs(self.velocityY))
self.velocityX *= scaleFactor
self.velocityY *= scaleFactor
self.rect.x += self.velocityX
self.rect.y += self.velocityY
# === main === (lower_case names)
# --- init ---
pygame.init()
screen = pygame.display.set_mode(SCREEN_SIZE)
#screen_rect = screen.get_rect()
# --- objects ---
# lists
# This is a list of 'sprites.' Each block in the program is
# added to this list. The list is managed by a class called 'Group.'
prey_list = pygame.sprite.Group()
# This is a list of every sprite. All blocks and the player block as well.
all_sprites_list = pygame.sprite.Group()
# create prey_list at random positions
for i in range(NUM_BOIDS):
prey = Prey(random.randint(0, SCREEN_WIDTH), random.randint(0, SCREEN_HEIGHT))
# Add the prey to the list of objects
prey_list.add(prey)
all_sprites_list.add(prey)
# --- mainloop ---
clock = pygame.time.Clock()
running = True
while running:
# --- events ---
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
# --- updates ---
for prey in prey_list:
closeBoids = []
for otherBoid in prey_list:
if otherBoid == prey:
continue
distance = prey.distance(otherBoid)
if distance < 200:
closeBoids.append(otherBoid)
prey.move_closer(closeBoids)
prey.move_with(closeBoids)
prey.move_away(closeBoids, 20)
# ensure they stay within the screen space
# if we roubound we can lose some of our velocity
if prey.rect.x < BORDER and prey.velocityX < 0:
prey.velocityX = -prey.velocityX * random.random()
if prey.rect.x > SCREEN_WIDTH - BORDER and prey.velocityX > 0:
prey.velocityX = -prey.velocityX * random.random()
if prey.rect.y < BORDER and prey.velocityY < 0:
prey.velocityY = -prey.velocityY * random.random()
if prey.rect.y > SCREEN_HEIGHT - BORDER and prey.velocityY > 0:
prey.velocityY = -prey.velocityY * random.random()
prey.update()
# Calls update() method on every sprite in the list
#all_sprites_list.update()
# --- draws ---
screen.fill(BLACK)
# Draw all the spites
all_sprites_list.draw(screen)
# Go ahead and update the screen with what we've drawn.
pygame.display.flip()
# Limit to 60 frames per second
# Used to manage how fast the screen updates
clock.tick(120)
# --- the end ---
pygame.quit()
sys.exit()
编辑: 真正的问题是将 Python 2 中的两个整数相除,结果四舍五入为整数
解决方案:
from __future__ import division
在其他导入之前使用它。
我正在尝试在 python 中实现一个相当基本的类群模拟。我的目标是使用基本的捕食者猎物设置进行模拟。我发现了一些伪代码(不能 post 超过两个链接,但如果你 google boids pseudocode
它是第一个结果)和一些代码 here 并决定给它一个走。因为我想添加捕食者,所以我决定采用我发现的代码对其进行修改,使 boids(将成为猎物)成为 sprite,然后从那里开始。但是,我已经 运行 解决了这个问题。
在我修改代码以使用 pygame 精灵后,所有 boid 都移动到右下角(原始代码工作正常)。
我的代码(只需克隆存储库)是 here(github)。有没有人 运行 进入过第一期?有没有人有解决它的想法?关于问题2,请问有哪位大侠解释一下怎么做吗?
谢谢您,我们将不胜感激。
P.S.
boids 的行为(它们的移动)似乎工作正常,除了它们总是去右下角的事实。
P.P.S。
多亏了 furas,猎物现在可以正常行动了。
P.P.P.S.
由于调试问题已经解决,我的问题剩下的部分涉及解释,我觉得应该是正题。
您的代码必须有所不同
您在开始时使用了不同的速度 - 在
__init__
中,但这应该没有区别。您在不同时刻更新对象。
您同时移动所有猎物(使用组更新)- 在所有计算之后。
原始代码在计算后移动每个 boid,因此下一个 boid 使用不同的数据来计算其移动。
我将 prey.update()
放入 for
循环并移除 all_sprites_list.update()
我组织代码的方式有点不同:
#!/usr/bin/env python
# Prey implementation in Python using PyGame
from __future__ import division # required in Python 2.7
import sys
import pygame
import random
import math
# === constants === (UPPER_CASE names)
SCREEN_SIZE = SCREEN_WIDTH, SCREEN_HEIGHT = 800, 600
BLACK = (0, 0, 0)
MAX_VELOCITY = 10
NUM_BOIDS = 50
BORDER = 25
# === classes === (CamelCase names for classes / lower_case names for method)
class Prey(pygame.sprite.Sprite):
def __init__(self, x, y):
super(Prey, self).__init__()
# Load image as sprite
self.image = pygame.image.load("ressources/img/prey.png").convert()
# Fetch the rectangle object that has the dimensions of the image
self.rect = self.image.get_rect()
# Coordinates
self.rect.x = x
self.rect.y = y
# self.velocityX = random.randint(-10, 10) / 10.0
# self.velocityY = random.randint(-10, 10) / 10.0
self.velocityX = random.randint(1, 10) / 10.0
self.velocityY = random.randint(1, 10) / 10.0
def distance(self, prey):
'''Return the distance from another prey'''
distX = self.rect.x - prey.rect.x
distY = self.rect.y - prey.rect.y
return math.sqrt(distX * distX + distY * distY)
def move_closer(self, prey_list):
'''Move closer to a set of prey_list'''
if len(prey_list) < 1:
return
# calculate the average distances from the other prey_list
avgX = 0
avgY = 0
for prey in prey_list:
if prey.rect.x == self.rect.x and prey.rect.y == self.rect.y:
continue
avgX += (self.rect.x - prey.rect.x)
avgY += (self.rect.y - prey.rect.y)
avgX /= len(prey_list)
avgY /= len(prey_list)
# set our velocity towards the others
distance = math.sqrt((avgX * avgX) + (avgY * avgY)) * -1.0
self.velocityX -= (avgX / 100)
self.velocityY -= (avgY / 100)
def move_with(self, prey_list):
'''Move with a set of prey_list'''
if len(prey_list) < 1:
return
# calculate the average velocities of the other prey_list
avgX = 0
avgY = 0
for prey in prey_list:
avgX += prey.velocityX
avgY += prey.velocityY
avgX /= len(prey_list)
avgY /= len(prey_list)
# set our velocity towards the others
self.velocityX += (avgX / 40)
self.velocityY += (avgY / 40)
def move_away(self, prey_list, minDistance):
'''Move away from a set of prey_list. This avoids crowding'''
if len(prey_list) < 1:
return
distanceX = 0
distanceY = 0
numClose = 0
for prey in prey_list:
distance = self.distance(prey)
if distance < minDistance:
numClose += 1
xdiff = (self.rect.x - prey.rect.x)
ydiff = (self.rect.y - prey.rect.y)
if xdiff >= 0:
xdiff = math.sqrt(minDistance) - xdiff
elif xdiff < 0:
xdiff = -math.sqrt(minDistance) - xdiff
if ydiff >= 0:
ydiff = math.sqrt(minDistance) - ydiff
elif ydiff < 0:
ydiff = -math.sqrt(minDistance) - ydiff
distanceX += xdiff
distanceY += ydiff
if numClose == 0:
return
self.velocityX -= distanceX / 5
self.velocityY -= distanceY / 5
def update(self):
'''Perform actual movement based on our velocity'''
if abs(self.velocityX) > MAX_VELOCITY or abs(self.velocityY) > MAX_VELOCITY:
scaleFactor = MAX_VELOCITY / max(abs(self.velocityX), abs(self.velocityY))
self.velocityX *= scaleFactor
self.velocityY *= scaleFactor
self.rect.x += self.velocityX
self.rect.y += self.velocityY
# === main === (lower_case names)
# --- init ---
pygame.init()
screen = pygame.display.set_mode(SCREEN_SIZE)
#screen_rect = screen.get_rect()
# --- objects ---
# lists
# This is a list of 'sprites.' Each block in the program is
# added to this list. The list is managed by a class called 'Group.'
prey_list = pygame.sprite.Group()
# This is a list of every sprite. All blocks and the player block as well.
all_sprites_list = pygame.sprite.Group()
# create prey_list at random positions
for i in range(NUM_BOIDS):
prey = Prey(random.randint(0, SCREEN_WIDTH), random.randint(0, SCREEN_HEIGHT))
# Add the prey to the list of objects
prey_list.add(prey)
all_sprites_list.add(prey)
# --- mainloop ---
clock = pygame.time.Clock()
running = True
while running:
# --- events ---
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
# --- updates ---
for prey in prey_list:
closeBoids = []
for otherBoid in prey_list:
if otherBoid == prey:
continue
distance = prey.distance(otherBoid)
if distance < 200:
closeBoids.append(otherBoid)
prey.move_closer(closeBoids)
prey.move_with(closeBoids)
prey.move_away(closeBoids, 20)
# ensure they stay within the screen space
# if we roubound we can lose some of our velocity
if prey.rect.x < BORDER and prey.velocityX < 0:
prey.velocityX = -prey.velocityX * random.random()
if prey.rect.x > SCREEN_WIDTH - BORDER and prey.velocityX > 0:
prey.velocityX = -prey.velocityX * random.random()
if prey.rect.y < BORDER and prey.velocityY < 0:
prey.velocityY = -prey.velocityY * random.random()
if prey.rect.y > SCREEN_HEIGHT - BORDER and prey.velocityY > 0:
prey.velocityY = -prey.velocityY * random.random()
prey.update()
# Calls update() method on every sprite in the list
#all_sprites_list.update()
# --- draws ---
screen.fill(BLACK)
# Draw all the spites
all_sprites_list.draw(screen)
# Go ahead and update the screen with what we've drawn.
pygame.display.flip()
# Limit to 60 frames per second
# Used to manage how fast the screen updates
clock.tick(120)
# --- the end ---
pygame.quit()
sys.exit()
编辑: 真正的问题是将 Python 2 中的两个整数相除,结果四舍五入为整数
解决方案:
from __future__ import division
在其他导入之前使用它。