更新时间:2023-01-23 18:24:43
你的代码很乱,不过别担心,让我们一步一步地创建一个简单的 pygame 游戏.尝试了解每个步骤的作用.
首先,让我们从游戏的基本骨架开始,如下所示:
导入pygame定义主():屏幕 = pygame.display.set_mode((640, 480))时钟 = pygame.time.Clock()为真:事件 = pygame.event.get()对于事件中的 e:如果 e.type == pygame.QUIT:返回screen.fill('灰色')pygame.display.flip()时钟滴答(60)如果 __name__ == '__main__':主要的()
在这里,我们有一个简单的 main
函数和一个简单的游戏循环,它只监听 QUIT
事件,将所有内容设为灰色并将帧率限制为 60.你没有必须有一个 main
函数和 __name__
检查,但这样做是一个很好的做法,因为它允许你导入文件而不运行游戏.此外,我有助于不污染全局命名空间.
好的,让我们创建一些Sprites
:
导入pygame从随机导入randint,选择类动物(pygame.sprite.Sprite):颜色 = ['lightblue', 'blue', 'darkblue', 'dodgerblue']def __init__(self, pos=None, color=None):super().__init__()self.image = pygame.Surface((32, 32))self.image.fill(color if color else selection(Animal.colors))self.rect = self.image.get_rect(center = pos if pos else (randint(100, 540), randint(100, 380)))定义主():屏幕 = pygame.display.set_mode((640, 480))时钟 = pygame.time.Clock()动物 = pygame.sprite.Group()动物.添加(动物())动物.添加(动物())动物.添加(动物())为真:事件 = pygame.event.get()对于事件中的 e:如果 e.type == pygame.QUIT:返回screen.fill('灰色')动物绘制(屏幕)pygame.display.flip()时钟滴答(60)如果 __name__ == '__main__':主要的()
使用
由于到目前为止这很无聊,让我们为我们的精灵添加一些行为:
导入pygame从随机导入randint,选择类动物(pygame.sprite.Sprite):颜色 = ['lightblue', 'blue', 'darkblue', 'dodgerblue']def __init__(self, pos=None, color=None):super().__init__()self.image = pygame.Surface((32, 32))self.image.fill(color if color else selection(Animal.colors))self.rect = self.image.get_rect(center = pos if pos else (randint(100, 540), randint(100, 380)))self.pos = pygame.Vector2(*self.rect.center)self.speed = 3self.direction = pygame.Vector2(1, 0).rotate(randint(0, 360))定义更新(自我,DT):v = self.direction * self.speed而不是 pygame.display.get_surface().get_rect().contains(self.rect.move(v)):self.direction = pygame.Vector2(1, 0).rotate(randint(0, 360))v = self.direction * self.speedself.pos += vself.rect.center = self.pos定义主():屏幕 = pygame.display.set_mode((640, 480))时钟,dt = pygame.time.Clock(), 0动物 = pygame.sprite.Group()动物.添加(动物())动物.添加(动物())动物.添加(动物())为真:事件 = pygame.event.get()对于事件中的 e:如果 e.type == pygame.QUIT:返回动物.更新(dt/1000)screen.fill('灰色')动物绘制(屏幕)pygame.display.flip()dt = 时钟.tick(60)如果 __name__ == '__main__':主要的()
如您所见,我们将所有移动逻辑保留在 Animal
类中.游戏循环中唯一改变的是我们跟踪 delta time dt
以确保恒定的帧速率(老实说,这在一个小例子中并不重要像这样,但同样是一个好习惯),并将其传递给组 update
函数,后者将调用它包含的每个精灵的 update
函数.
在Animal类中,我们使用一些简单的向量数学来移动精灵:我们有一个speed
和一个direction
(这是一个向量);还有一个额外的 pos
向量.用向量改变位置很容易,因为我们可以做一些类似pos = pos + direction * speed
的事情,如果我们随机旋转方向向量,改变方向也很简单.
如果您想创建一个带有移动部件的 2D 游戏,我建议您学习一点矢量数学(如果您在学校还没有学过).除了可以轻松地将它们相加或相乘等这一事实之外,您不需要知道更多.
记住精灵是在其rect
的坐标处绘制的,所以我们也需要更新rect的位置.
Pygame 的 Rect
类也有一些方便的功能.看看我们如何检查精灵是否会离开屏幕.我们可以简单地抓取显示表面的 Rect
,移动 Sprite 的 Rect
并检查它是否仍在屏幕矩形内.如果不是,我们随机旋转我们的 direction
向量(好吧,它不是 100% 完美,而是 KISS).
那么其他精灵呢?让我们继承我们的 Animal
类,通过覆盖更新函数来改变速度、颜色和行为:
导入pygame导入 pygame.freetype从随机导入randint,选择从数学导入假设类动物(pygame.sprite.Sprite):颜色 = ['lightblue', 'blue', 'darkblue', 'dodgerblue']def __init__(self, pos=None, color=None, *grps):super().__init__(*grps)self.image = pygame.Surface((32, 32))self.color = color if color else selection(Animal.colors)self.image.fill(self.color)self.rect = self.image.get_rect(center = pos if pos else (randint(100, 540), randint(100, 380)))self.pos = pygame.Vector2(*self.rect.center)self.speed = 3self.direction = pygame.Vector2(1, 0).rotate(randint(0, 360))定义更新(自我,DT):v = self.direction * self.speed而不是 pygame.display.get_surface().get_rect().contains(self.rect.move(v)):self.direction = pygame.Vector2(1, 0).rotate(randint(0, 360))v = self.direction * self.speedself.pos += vself.rect.center = self.pos类捕食者(动物):def __init__(self,animals, pos=None, color=None, *grps):super().__init__(pos, color or 'red', *grps)self.speed = 4self.target = 无self.animals = 动物自食 = 0self.font = pygame.freetype.SysFont(None, 16)self.font.render_to(self.image, (10, 10), str(self.eaten), 'white')def get_nearest_animal(self):目标 = 无距离 = 无对于 self.animals 中的动物:pygame.draw.line(pygame.display.get_surface(),'darkgrey',self.pos,animal.pos)如果不是目标:目标 = 动物距离 = hypot(animal.pos.x - self.pos.x,animal.pos.y - self.pos.y)别的:new_distance = hypot(animal.pos.x - self.pos.x,animal.pos.y - self.pos.y)如果 new_distance
精灵可以是多个组的一部分,我们在这里使用它来为 Predator
提供它可以猎杀和吃掉的所有受害者的列表.再次,看看所有的精灵行为是如何在精灵类中的.主循环所做的唯一一件事就是创建初始状态,告诉精灵组更新和绘制它们的所有精灵(并通过按键添加新的动物,因为为什么不呢).还要注意我们如何使用 kill
将其从所有 it 组中删除,基本上将其从游戏中删除.
希望对您有所帮助,并让您了解如何组织您的 pygame 游戏.
随便玩玩吧.需要进食以维持生命和繁殖的动物怎么样?捕食者只需要触手可及的速度较慢的动物,如果没有,就吃其他捕食者甚至植物?
导入pygame导入 pygame.freetype从随机导入randint,选择从数学导入假设从数据类导入数据类类植物(pygame.sprite.Sprite):颜色 = ['绿色','浅绿色','深绿色']def __init__(self, pos=None, color=None, *grps):self._layer = -10super().__init__(*grps)self.image = pygame.Surface((24, 24))self.color = color if color else selection(Plant.colors)self.image.fill(self.color)self.rect = self.image.get_rect(center = pos if pos else (randint(10, 630), randint(10, 470)))self.pos = pygame.Vector2(*self.rect.center)类动物(pygame.sprite.Sprite):字体 = 无颜色 = ['lightblue', 'blue', 'darkblue', 'dodgerblue']def __init__(self, system, pos=None, color=None, *grps):super().__init__(*grps)self.image = pygame.Surface((24, 24))self.color = color if color else selection(Animal.colors)self.image.fill(self.color)self.rect = self.image.get_rect(center = pos if pos else (randint(100, 540), randint(100, 380)))self.pos = pygame.Vector2(*self.rect.center)self.speed = randint(20, 50)/10self.direction = pygame.Vector2(1, 0).rotate(randint(0, 360))self.reproduce = randint(1, 5)self.sleep = 0自食 = 0自我.能量 = 10self.system = 系统如果不是 self.font:Animal.font = pygame.freetype.SysFont(None, 16)def base_update(self, dt):self.energy -= dt * (self.speed/2)如果 self.energy = 0:self.sleep -= dt返回错误self.reproduce -= dt如果 self.reproduce = 4:self.reproduce = randint(1, 7)self.__class__(self.system, self.pos, None, *self.groups())self.sleep = 0.5自我能量 -= 1.5返回真def update_image(self):self.image.fill(self.color)self.image.set_alpha(122 if self.sleep > 0 else 255)pygame.draw.rect(self.image, 'green', (0, 0, self.rect.width * self.energy/10, 3))self.font.render_to(self.image, (7, 7), str(self.eaten), 'white')def get_nearest_target(self,targets,max_distance=250):目标 = 无距离 = 无对于目标中的可能目标:if possible_target == self 或 hasattr(possible_target, 'speed') and possible_target.speed >self.speed:继续new_distance = hypot(possible_target.pos.x - self.pos.x, possible_target.pos.y - self.pos.y)pygame.draw.line(pygame.display.get_surface(), 'darkgrey' if new_distance > max_distance else 'white', self.pos, possible_target.pos)如果 new_distance 10:自我.能量 = 10self.update_image()@数据类类系统:植物:对象动物:物体预兆:对象定义主():pygame.init()屏幕 = pygame.display.set_mode((640, 480))时钟,dt = pygame.time.Clock(), 0增长 = pygame.USEREVENT + 1pygame.time.set_timer(增长,1000)动物 = pygame.sprite.Group()植物 = pygame.sprite.Group()捕食者 = pygame.sprite.Group()all_sprites = pygame.sprite.LayeredUpdates()系统 = 系统(植物、动物、捕食者)对于范围内的 _(4):动物(系统,无,无,动物,all_sprites)对于 _ 范围(5):植物(无,无,植物,all_sprites)捕食者(系统,无,无,捕食者,all_sprites)为真:事件 = pygame.event.get()对于事件中的 e:如果 e.type == pygame.QUIT:返回如果 e.type == pygame.KEYDOWN:动物(系统,无,无,动物,all_sprites)如果 e.type == 增长:对于 _ 范围(5):植物(无,无,植物,all_sprites)screen.fill('灰色')all_sprites.update(dt/1000)all_sprites.draw(屏幕)pygame.display.flip()dt = 时钟.tick(30)如果 __name__ == '__main__':主要的()
Hello I am relatively new to creating games/coding so sorry before hand for the multitude of issues I am going to be bringing and the god awful organisation of my code, I'm attempting to have my class' have a function to render themselves into the game instead of using an outside source
`class Animal(pygame.sprite.Sprite):
def __init__(Self,):
super().__init__()
Self.Image=pygame.image.load('Blank.png').convert_alpha()
Self.rect=Self.image.get_rect()
Self.x=x
Self.y=y
Self.Screen= screen
#Self.Width=Width
#Self.Height=Height
#Self.Energy=0
def BoundryX(entityX):
if entityX<=0:
entityX=0
elif entityX>=600:
entityX=600
def BoundryY(entityY):
if entityY<=0:
entityY=0
elif entityY>=800:
entityY=800
class Predator(Animal):
def __init__(Self):
#super().__init__()
Self.Img=pygame.image.load('Icon.png')
Self.PredatorX=0
Self.PredatorY=0
Self.Screen= screen
def Render(Self,Img,X,Y):
Self.screen.blit(Img,(X,Y))
`
I'm having issues as it says the class doesn't have the attribute "screen" and I do not know what that means, on a side note what would be the best way to create a function to create more of a set class after they have eaten enough of their food chain as well as a function to remove them as well as organise all of the different sprites(I understand this isn't apart of my main issue so if it doesn't get answer that's fine)
here is the full code: (sorry for any pain caused by my terrible formatting
#imports
import math
import random
import pygame,sys
import random
import pdb
from pygame.locals import *
timmer=1
class Animal(pygame.sprite.Sprite):
def __init__(Self,):
super().__init__()
Self.Image=pygame.image.load('Blank.png').convert_alpha()
Self.rect=Self.image.get_rect()
Self.x=x
Self.y=y
Self.Screen= screen
#Self.Width=Width
#Self.Height=Height
#Self.Energy=0
def BoundryX(entityX):
if entityX<=0:
entityX=0
elif entityX>=600:
entityX=600
def BoundryY(entityY):
if entityY<=0:
entityY=0
elif entityY>=800:
entityY=800
class Predator(Animal):
def __init__(Self):
#super().__init__()
Self.Img=pygame.image.load('Icon.png')
Self.PredatorX=0
Self.PredatorY=0
Self.Screen= screen
def Render(Self,Img,X,Y):
Self.screen.blit(Img,(X,Y))
class prey(pygame.sprite.Sprite):
def __init__():
Self.preyImg=pygame.image.load('Prey.png')
Self.preyX=300
Self.preyY=700
Self.PreyX_change=0
def Render(Self):
Self.screen.blit(preyImg,(preyX,preyY))
def delete(Self):
i.delete()
CarrotImg=pygame.image.load('carrot.png')
CarrotX=100
CarrotY=300
foodamount=7
def food():
#CarrotX=random.randint(10,950)
#CarrotY=random.randint(10,750)
screen.blit(CarrotImg,(CarrotX,CarrotY))
#setup pygame
pygame.init()
#caption and Icons
pygame.display.set_caption("Game Of Life")
#predator icon
predatorImg=pygame.image.load('Icon.png')
predatorX=900
predatorY=100
predatorX_change=0
#Prey Icon
preyImg=pygame.image.load('Prey.png')
preyX=300
preyY=700
PreyX_change=0
#def delete():
#prey.delete()
preyImg=pygame.image.load('Prey.png')
preyX=300
preyY=700
PreyX_change=0
#def Prey():
#screen.blit(preyImg,(preyX,preyY))
class setup():
def __init__():
x=1
def Predator1(Self):
screen.blit(predatorImg,(predatorX,predatorY))
#Finding closest prey
def FindClosestItem(AgressorX,DefenderX,AgressorY,DefenderY):
dist = math.sqrt((AgressorX-DefenderX)**2 + (AgressorY-DefenderY)**2)#finds distance in pixels
#create pop out for game
screen=pygame.display.set_mode((1000,800))
def Tracking(AgressorX,DefenderX,AgressorY,DefenderY):
global XMovement#make variables global so it actually works
global YMovement
if AgressorX > DefenderX:#finds whether its position then moves left/righ,up/down depending on its location
XMovement=-0.25
elif AgressorX< DefenderX:
XMovement=0.25
else:
XMovement=0
if AgressorY > DefenderY:
YMovement=-0.25
elif AgressorY < DefenderY:
YMovement=0.25
else:
YMovement=0
def EatPrey(predatorX,PreyX,predatorY,preyY):
dist = math.sqrt((predatorX-preyX)**2 + (predatorY-preyY)**2)
if dist < 20:
return True
else:
return False
#setup test
predator=Predator()
#Sprite groups
all_sprites_Wolves=pygame.sprite.Group()
all_sprites_Rabbits=pygame.sprite.Group()
all_sprites_Carrots=pygame.sprite.Group()
#game loop
running=True
while running:
#Back ground colour
screen.fill((0,128,0))
for event in pygame.event.get():
if event.type==pygame.QUIT:
running=False
predator.Render(pygame.image.load('Icon.png'),600,700)
#Prey.Render()
ClosestPrey=FindClosestItem(predatorX,preyX,predatorY,preyY)
food()
Track(predatorX,preyX,predatorY,preyY)
predatorX+=XMovement
predatorY+=YMovement
#predatorX=BoundryX(predatorX)
#predatorY=BoundryY(predatorY)
Track(preyX,CarrotX,preyY,CarrotY)
preyX+=XMovement
preyY+=YMovement
#preyX=BoundryX(preyX)
#preyY=BoundryY(preyY)
#Eat=EatPrey(preyX,preyY,predatorX,predatorY)
#if Eat==True:
#delete()
#T=1
#Boundry(prey)
if preyX<=0:
preyX=0
elif preyX>=950:
preyX=950
if preyY<=0:
preyY=0
elif preyY>=750:
preyY=750
#preyY-=1
#Boundry(predator)
if predatorX<=0:
predatorX=0
elif predatorX>=950:
predatorX=950
elif predatorY<=0:
predatorY=0
elif predatorY>=750:
predatorY=750
pygame.display.update()
timmer=timmer+1
Your code is quite a mess, but don't worry, let's create a simple pygame game step for step. Try to understand what each step does.
First, let's start with a basic skeleton for a game, something like this:
import pygame
def main():
screen = pygame.display.set_mode((640, 480))
clock = pygame.time.Clock()
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
screen.fill('grey')
pygame.display.flip()
clock.tick(60)
if __name__ == '__main__':
main()
Here, we have a simple main
function and a simple game loop that does nothing but listen for the QUIT
event, make everything grey and limit the framerate to 60. You don't have to have a main
function and the __name__
check but doing this is good practice as it allows you to import file without running the game. Also, I helps to not pollute the global namespace.
OK, let's create some Sprites
:
import pygame
from random import randint, choice
class Animal(pygame.sprite.Sprite):
colors = ['lightblue', 'blue', 'darkblue', 'dodgerblue']
def __init__(self, pos=None, color=None):
super().__init__()
self.image = pygame.Surface((32, 32))
self.image.fill(color if color else choice(Animal.colors))
self.rect = self.image.get_rect(center = pos if pos else (randint(100, 540), randint(100, 380)))
def main():
screen = pygame.display.set_mode((640, 480))
clock = pygame.time.Clock()
animals = pygame.sprite.Group()
animals.add(Animal())
animals.add(Animal())
animals.add(Animal())
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
screen.fill('grey')
animals.draw(screen)
pygame.display.flip()
clock.tick(60)
if __name__ == '__main__':
main()
To use the Sprite
class in pygame, the class needs an image
attribute, which is a Surface
, and a rect
attribute, which is a Rect
and contains the position and the size of the Surface
. If you create a Sprite
like this, you can make use of the Group
class (or its subclasses) to draw and update your sprites. To draw somethin, I added three Animals to a group animals
and call the draw
function, passing the screen
Surface as an argument so the Group
knows where to blit the Animals' images.
Since this is quite boring so far, let's add some behaviour to our sprites:
import pygame
from random import randint, choice
class Animal(pygame.sprite.Sprite):
colors = ['lightblue', 'blue', 'darkblue', 'dodgerblue']
def __init__(self, pos=None, color=None):
super().__init__()
self.image = pygame.Surface((32, 32))
self.image.fill(color if color else choice(Animal.colors))
self.rect = self.image.get_rect(center = pos if pos else (randint(100, 540), randint(100, 380)))
self.pos = pygame.Vector2(*self.rect.center)
self.speed = 3
self.direction = pygame.Vector2(1, 0).rotate(randint(0, 360))
def update(self, dt):
v = self.direction * self.speed
while not pygame.display.get_surface().get_rect().contains(self.rect.move(v)):
self.direction = pygame.Vector2(1, 0).rotate(randint(0, 360))
v = self.direction * self.speed
self.pos += v
self.rect.center = self.pos
def main():
screen = pygame.display.set_mode((640, 480))
clock, dt = pygame.time.Clock(), 0
animals = pygame.sprite.Group()
animals.add(Animal())
animals.add(Animal())
animals.add(Animal())
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
animals.update(dt/1000)
screen.fill('grey')
animals.draw(screen)
pygame.display.flip()
dt = clock.tick(60)
if __name__ == '__main__':
main()
As you can see, we keep all the moving logic in the Animal
class. The only thing that changed in the game loop is that we keep track of the delta time dt
to ensure a constant frame rate (honestly, that's not that important in a small example like this, but again, good practice), and pass it to the groups update
function, which in turn will call the update
function of each sprite it contains.
In the Animal class, we use some simple vector math to move the sprite: we have a speed
and a direction
(which is a vector); and also an additional pos
vector. Changing the position is easy with vectors because we can just do something like pos = pos + direction * speed
, and changing the direction is simple too if we just randomly rotate the direction vector.
If you want to create a 2D game with moving parts I recommend that you learn a litte bit of vector math if you didn't already learn it in school. You don't need to know much more than the fact that you can easily add them up or multiply them etc.
Remember that the sprite is drawn at the coordinate of its rect
, so we need to update the rect's position, too.
Pygame's Rect
class has some handy functions, too. Just look how we check if the sprite would go out of screen. We can simply grab the Rect
of the display surface, move the Sprite's Rect
and check if it's still inside the screen rect. If not, we randomly rotate our direction
vector (well, it's not 100% perfect, but KISS).
So what about other sprites? Let's just subclass our Animal
class, change the speed and color and the behaviour by overwriting the update function:
import pygame
import pygame.freetype
from random import randint, choice
from math import hypot
class Animal(pygame.sprite.Sprite):
colors = ['lightblue', 'blue', 'darkblue', 'dodgerblue']
def __init__(self, pos=None, color=None, *grps):
super().__init__(*grps)
self.image = pygame.Surface((32, 32))
self.color = color if color else choice(Animal.colors)
self.image.fill(self.color)
self.rect = self.image.get_rect(center = pos if pos else (randint(100, 540), randint(100, 380)))
self.pos = pygame.Vector2(*self.rect.center)
self.speed = 3
self.direction = pygame.Vector2(1, 0).rotate(randint(0, 360))
def update(self, dt):
v = self.direction * self.speed
while not pygame.display.get_surface().get_rect().contains(self.rect.move(v)):
self.direction = pygame.Vector2(1, 0).rotate(randint(0, 360))
v = self.direction * self.speed
self.pos += v
self.rect.center = self.pos
class Preditor(Animal):
def __init__(self, animals, pos=None, color=None, *grps):
super().__init__(pos, color or 'red', *grps)
self.speed = 4
self.target = None
self.animals = animals
self.eaten = 0
self.font = pygame.freetype.SysFont(None, 16)
self.font.render_to(self.image, (10, 10), str(self.eaten), 'white')
def get_nearest_animal(self):
target = None
distance = None
for animal in self.animals:
pygame.draw.line(pygame.display.get_surface(), 'darkgrey', self.pos, animal.pos)
if not target:
target = animal
distance = hypot(animal.pos.x - self.pos.x, animal.pos.y - self.pos.y)
else:
new_distance = hypot(animal.pos.x - self.pos.x, animal.pos.y - self.pos.y)
if new_distance < distance:
target = animal
distance = new_distance
if target:
pygame.draw.line(pygame.display.get_surface(), 'green', self.pos, target.pos)
return target
def update(self, dt):
self.target = self.get_nearest_animal()
if self.target:
self.direction = (self.target.pos - self.pos).normalize()
else:
self.direction = pygame.Vector2(0, 0)
self.pos += self.direction * self.speed
self.rect.center = self.pos
if self.target and self.rect.colliderect(self.target.rect):
self.target.kill()
self.image.fill(self.color)
self.eaten += 1
self.font.render_to(self.image, (10, 10), str(self.eaten), 'white')
def main():
pygame.init()
screen = pygame.display.set_mode((640, 480))
clock, dt = pygame.time.Clock(), 0
animals = pygame.sprite.Group()
all_sprites = pygame.sprite.Group()
for _ in range(5):
Animal(None, None, animals, all_sprites)
Preditor(animals, None, None, all_sprites)
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
if e.type == pygame.KEYDOWN:
Animal(None, None, animals, all_sprites)
screen.fill('grey')
all_sprites.update(dt/1000)
all_sprites.draw(screen)
pygame.display.flip()
dt = clock.tick(60)
if __name__ == '__main__':
main()
Sprites can be part of multiple groups, and we use that here to give the Predator
a list of all its victims it can hunt and eat. Again, see how all sprite behaviour is inside the sprite classes. The only thing the main loop does is creating the initial state telling the sprite groups to update and draw all their sprites (and adding new Animals by pressing a key, because why not). Also note how we can use kill
to remove it from all it groups, basically removing it from the game.
Hope that helps and gives you an idea on how to organize your pygame game.
Just play around a little. How about Animals that need to eat to stay alive and reproduce? And predators that will only need slower animals within reach, and if there are none, eat other predators or even plants?
import pygame
import pygame.freetype
from random import randint, choice
from math import hypot
from dataclasses import dataclass
class Plant(pygame.sprite.Sprite):
colors = ['green', 'lightgreen', 'darkgreen']
def __init__(self, pos=None, color=None, *grps):
self._layer = -10
super().__init__(*grps)
self.image = pygame.Surface((24, 24))
self.color = color if color else choice(Plant.colors)
self.image.fill(self.color)
self.rect = self.image.get_rect(center = pos if pos else (randint(10, 630), randint(10, 470)))
self.pos = pygame.Vector2(*self.rect.center)
class Animal(pygame.sprite.Sprite):
font = None
colors = ['lightblue', 'blue', 'darkblue', 'dodgerblue']
def __init__(self, system, pos=None, color=None, *grps):
super().__init__(*grps)
self.image = pygame.Surface((24, 24))
self.color = color if color else choice(Animal.colors)
self.image.fill(self.color)
self.rect = self.image.get_rect(center = pos if pos else (randint(100, 540), randint(100, 380)))
self.pos = pygame.Vector2(*self.rect.center)
self.speed = randint(20, 50) / 10
self.direction = pygame.Vector2(1, 0).rotate(randint(0, 360))
self.reproduce = randint(1, 5)
self.sleep = 0
self.eaten = 0
self.energy = 10
self.system = system
if not self.font:
Animal.font = pygame.freetype.SysFont(None, 16)
def base_update(self, dt):
self.energy -= dt * (self.speed / 2)
if self.energy <= 0:
self.kill()
return False
if self.sleep >= 0:
self.sleep -= dt
return False
self.reproduce -= dt
if self.reproduce <= 0 and self.energy >= 4:
self.reproduce = randint(1, 7)
self.__class__(self.system, self.pos, None, *self.groups())
self.sleep = 0.5
self.energy -= 1.5
return True
def update_image(self):
self.image.fill(self.color)
self.image.set_alpha(122 if self.sleep > 0 else 255)
pygame.draw.rect(self.image, 'green', (0, 0, self.rect.width * self.energy/10, 3))
self.font.render_to(self.image, (7, 7), str(self.eaten), 'white')
def get_nearest_target(self, targets, max_distance=250):
target = None
distance = None
for possible_target in targets:
if possible_target == self or hasattr(possible_target, 'speed') and possible_target.speed > self.speed:
continue
new_distance = hypot(possible_target.pos.x - self.pos.x, possible_target.pos.y - self.pos.y)
pygame.draw.line(pygame.display.get_surface(), 'darkgrey' if new_distance > max_distance else 'white', self.pos, possible_target.pos)
if new_distance <= max_distance:
if not target or new_distance < distance:
target = possible_target
distance = new_distance
if target:
pygame.draw.line(pygame.display.get_surface(), 'green', self.pos, target.pos)
return target
def update(self, dt):
if not self.base_update(dt) or len(self.groups()) == 0:
return
v = self.direction * self.speed
while not pygame.display.get_surface().get_rect().contains(self.rect.move(v)):
self.direction = pygame.Vector2(1, 0).rotate(randint(0, 360))
v = self.direction * self.speed
for plant in self.system.plants:
if plant.rect.colliderect(self.rect) and self.energy < 8:
plant.kill()
self.eaten += 1
self.energy = 10
continue
self.pos += v
self.rect.center = self.pos
self.update_image()
class Preditor(Animal):
def __init__(self, system, pos=None, color=None, *grps):
super().__init__(system, pos, color or 'red', *grps)
self.speed = randint(20, 40) / 10
self.target = None
self.update_image()
def update(self, dt):
if not self.base_update(dt) or len(self.groups()) == 0:
return
self.target = self.get_nearest_target(self.system.animals)
if not self.target:
self.target = self.get_nearest_target(self.system.preditors)
if not self.target:
self.target = self.get_nearest_target(self.system.plants)
if self.target:
self.direction = (self.target.pos - self.pos).normalize()
else:
self.direction = pygame.Vector2(0, 0)
self.pos += self.direction * self.speed
self.rect.center = self.pos
if self.target and self.rect.colliderect(self.target.rect):
self.target.kill()
self.eaten += 1
self.sleep = 0.5
self.energy += 3
if self.energy > 10:
self.energy = 10
self.update_image()
@dataclass
class System:
plants: object
animals: object
preditors: object
def main():
pygame.init()
screen = pygame.display.set_mode((640, 480))
clock, dt = pygame.time.Clock(), 0
GROW = pygame.USEREVENT + 1
pygame.time.set_timer(GROW, 1000)
animals = pygame.sprite.Group()
plants = pygame.sprite.Group()
preditors = pygame.sprite.Group()
all_sprites = pygame.sprite.LayeredUpdates()
system = System(plants, animals, preditors)
for _ in range(4):
Animal(system, None, None, animals, all_sprites)
for _ in range(5):
Plant(None, None, plants, all_sprites)
Preditor(system, None, None, preditors, all_sprites)
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
if e.type == pygame.KEYDOWN:
Animal(system, None, None, animals, all_sprites)
if e.type == GROW:
for _ in range(5):
Plant(None, None, plants, all_sprites)
screen.fill('grey')
all_sprites.update(dt/1000)
all_sprites.draw(screen)
pygame.display.flip()
dt = clock.tick(30)
if __name__ == '__main__':
main()