How can I drive individual sprites - python

I would like individual cars to drive across the screen from left to right with a certain time interval.It should be an endless loop. It is ended by program end
With my code, they all start at the same time. In addition, rect.y changes from the default value 300 to 0.
import pygame
import random
WIDTH = 800
HEIGHT = 600
FPS = 30
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("My Game")
clock = pygame.time.Clock()
class Car(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.images = []
for i in range(5):
img = pygame.image.load(f"Bilder/Gegenstaende/quer_auto_{i}.png")
self.images.append(img)
self.image = self.images[random.randrange(0, 5)]
self.rect = self.image.get_rect()
self.rect.x = 100
self.rect.y = 300
self.last = pygame.time.get_ticks()
self.delay = 5000
def update(self):
self.rect.x += 5
current = pygame.time.get_ticks()
if current - self.last > self.delay:
self.last = current
self.image = self.images[random.randrange(0, 5)]
self.rect = self.image.get_rect()
self.rect.x = 100
background = pygame.image.load("Bilder/starfield.png")
background_rect = background.get_rect()
cars = pygame.sprite.Group()
for i in range(3):
m = Car()
cars.add(m)
running = True
while running:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
cars.update()
screen.fill((250,250,250))
screen.blit(background,background_rect)
cars.draw(screen)
pygame.display.flip()
pygame.quit()

(Assuming you want these cars to be robots) First create variable and lets name them car_move_right and car_move_left . If you want the car to move towards the right at the beginning of the game, set the car_move_right to True and the other variable to False, and vice versa. Then in the main loop, add an if statement like this:
(Taking carX to be X-coordinate of car)
if car_move_right:
carX += ai_move_speed
if carX == WINDOWWIDTH or carX > WINDOWWIDTH:
car_move_right = False
car_move_left = True
if car_move_left:
carX -= ai_move_speed
if carX == 0 or carX < 0:
car_move_left = False
car_move_right = True
Please note: I had written this code for robot movement which also included going up and down. Not the cleanest and best, but I think it would work for you.

rect.y changes to 0 because you reset the rect, and default rect's x and y values are 0.
if current - self.last > self.delay:
self.last = current
self.image = self.images[random.randrange(0, 5)]
self.rect = self.image.get_rect() ##<--- here
self.rect.x = 100
Also you should consider storing your car's position in a python variable, since self.rect.x can only store integers.
For the timer thing, you should move your update function outside of your car class since it does not have any information about other cars and it needs it.
Here is an example implementation.
import pygame
WIDTH = 800
HEIGHT = 600
FPS = 30
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("My Game")
clock = pygame.time.Clock()
class Car(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((50, 50)).convert()
self.rect = self.image.get_rect()
self.rect.x = 100
self.rect.y = 300
def finished(self):
return self.rect.x >= WIDTH - self.rect.width
class Driver:
def __init__(self, cars):
self.cars = cars
self.currentCar = 0
self.delay = 1000
self.current = pygame.time.get_ticks()
self.last = pygame.time.get_ticks()
self.timePassed = False
def updateCar(self, car):
if self.timePassed:
car.rect.x += 1 #5
def runTimer(self):
self.current = pygame.time.get_ticks()
self.timePassed = False
if self.current - self.last > self.delay:
self.timePassed = True
def Run(self):
self.runTimer()
car = self.cars.sprites()[self.currentCar]
if not car.finished():
self.updateCar(car)
else:
self.current = pygame.time.get_ticks()
self.last = pygame.time.get_ticks()
self.currentCar += 1
def allFinished(self):
return self.currentCar >= len(self.cars)
cars = pygame.sprite.Group()
for i in range(3):
m = Car()
cars.add(m)
driver = Driver(cars)
while True:
if not driver.allFinished():
driver.Run()
#btw, no need to fill if you have background image the size of window
screen.fill((250,250,250))
#screen.blit(background,background_rect)
cars.draw(screen)
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()

Create 10 car classes. Give them a different name, like car1, car2, etc.
Let's focus on car1 for now. Let car1X represent the x-coordinate of car1.
Now for the speed of movement. If you want each car to have the same speed, just create a variable named ai_move_speed. Now, if you want the speed to differ, then instead of ai_move_speed, make 10 variables and name these variables as ai1_move_speed, ai2_move_speed, etc. For now, set these variables to an integer value of 2.
For the movement of left to right, create a variable called car1_move_right and set it to True.
As each car will go from left to right, then die, We can simply add this code to our main loop :
if car1_move_right:
car1X += ai_move_speed
Now once the game is run, car1 will go from its x-coordinate towards the left side. Repeat this process for the other cars.
But if you want the cars to go back to their original position if they go out of the boundary or die, then just add this line of code at the end of the above code:
if carX == 800:
carX = 100
Now so far we fixed movement. Now if you want the cars to start 1 after the other, delete the entire code from before. Now you can customize when a car starts. For car1 let's simply create a variable and call this variable c1_time. Set it to 5000 for now. Now I want to introduce you to a pygame function called pygame.time.get_ticks(). It gets the number of milliseconds since pygame.init() has been called in milliseconds. Remember the c1_time? We set it to an integer value of 5000 because 1000 milliseconds is 1 second, so car1 will start moving after 5 seconds after the interpreter reads pygame.init().
Now create this variable : time_init and store this code inside it : pygame.time.get_ticks(). After that create an if statement as so :
if car1_move_right:
if time_init >= c1_time:
car1X += ai_move_speed
if car1X == 800:
car1X = 100
Now what this above code does is that 5 seconds after the game starts, it will start moving car1. If car1 goes out of boundary it gets it back again. Repeat these steps for the other cars. This is the simplest and bug-free way I could do it.
PS - Feel free to change any variable name. Just make sure to change it everywhere in the code.
PPS - Please read the entire thing. Also upvote if you found this helpful cuz I spent a lot of time on this.

Related

Pygame Animated background freezing

I was working on making a animated background for my game. So I have the frames for it there are 174 ans store them in a list in a class. when I run the animate function in the class it only displays one image then dosnt do anything. I figured it is because of the for loop that is putting the images on the screen. How can I improve this so it can work properly?
basePath = os.path.dirname(sys.argv[0])+"\\"
pygame.init()
#setting fps lock for game and startign clock (Both are needed for fps)
fps = 30
fClock = pygame.time.Clock()
#Setting up pygame display size and window
res = pygame.display.Info()
tv = pygame.display.set_mode((res.current_w, res.current_h), pygame.SCALED)
class Background:
def __init__(self):
self.imFrames = os.listdir(basePath+"Background Frames/")
self.nTime = 0
def animate(self):
index = 0
tNow = pygame.time.get_ticks()
if (tNow > self.nTime):
fDelay = 0.1
self.nTime = tNow + fDelay
index += 1
if index >= len(self.imFrames):
index = 0
for item in self.imFrames:
tv.blit(pygame.image.load(basePath+"Background Frames/"+item).convert_alpha(), (0,0))
back = Background()
go = True
while go:
#event checks
for event in pygame.event.get():
if event.type == QUIT:
go = False
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
go = False
back.animate()
pygame.display.update()
fClock.tick(fps)
#Closing pygame and closing all functions
pygame.quit()
sys.exit()
Never implement a loop that tires to animate something in the application loop. This stops the application loop and the game becomes unresponsive. You need to load all the image int the constructor and loop through the images while the application loop is running.
Beside that, pygame.image.load is a very expensive operation. It needs to read this image file from the device and decode the image file. Never call pygame.image.load when the application loop is running, always load the images before the application for good performance.
Further note that pygame.time.get_ticks() returns the time in milliseconds, not seconds. 0.1 seconds is 100 milliseconds.
class Background:
def __init__(self):
self.imFrames = os.listdir(basePath+"Background Frames/")
self.images = []
for item in self.imFrames:
image = pygame.image.load(basePath+"Background Frames/"+item).convert_alpha()
self.images.appned(image)
self.image = self.images[0]
self.nTime = pygame.time.get_ticks()
self.index = 0
def animate(self):
tNow = pygame.time.get_ticks()
if tNow > self.nTime:
fDelay = 100
self.nTime = tNow + fDelay
self.index += 1
if self.index >= len(self.images):
self.index = 0
self.image = self.images[self.index]
tv.blit(self.image, (0,0))
You need to make a python list of all the images (a for-loop that appends pygame.image.load(basePath +"Background Frames"/item).convert_alpha() to the list would suffice), which is what I had mistakenly thought self.ImFrames was and instead of making a for-loop to run the animations, utilize the game-loop to your advantage and have the function which draws, increment the value every iteration of the game-loop (or you could do it externally which is how I did it):
def animate(self, index): # new param to avoid using global
tNow = pygame.time.get_ticks()
if (tNow > self.nTime):
fDelay = 0.1
self.nTime = tNow + fDelay
if index >= len(self.images):
index = 0
tv.blit(self.images[index])
index = 0
while go:
# code
# if animating, do this
index += 1
# code

trying to use clamp_ip to keep sprites onscreen - pygame

I'm working on a game for class and this is what we've learned to do so far. I'm trying to get the sprites to stay onscreen with clamp_ip, but it keeps giving me an error message that "Butterfly instance has no attribute clamp_ip." Is there a different way I should be keeping the butterflies onscreen?
This first bit is just setting up pygame and the butterfly class (where I included a line to define a rectangle for the butterflies), I've highlighted where I'm guessing the potential errors are below.
This should be ok.
pygame.init()
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
playground = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption('Butterflies')
screenRect = playground.get_rect()
steps = 1
class Butterfly:
def __init__(self):
self.x = random.randint(0, SCREEN_WIDTH)
self.y = random.randint(0, SCREEN_HEIGHT)
self.image_Butterfly = pygame.image.load('butterflya.png')
self.image_ButterflyB = pygame.image.load('butterflyb.png')
self.height = self.image_Butterfly.get_height()
self.width = self.image_Butterfly.get_width()
self.rect = (self.width, self.height)
clock = pygame.time.Clock()
fps = 10
net = []
for i in range (15):
butterflyInstance = Butterfly()
net.append(butterflyInstance)
playground.fill(cyan)
Game_Over = False
while not Game_Over:
for event in pygame.event.get():
if event.type == pygame.QUIT:
Game_Over = True
This is where I'm guessing I messed up. I tried to use the clamp_ip function at the end of this loop
for butterflyInstance in net:
butterflyInstance.x += random.randint(-10, 10)
butterflyInstance.y += random.randint(-10,10)
if steps % 2 == 0:
playground.blit(butterflyInstance.image_Butterfly, (butterflyInstance.x, butterflyInstance.y))
steps += 1
else:
playground.blit(butterflyInstance.image_ButterflyB, (butterflyInstance.x, butterflyInstance.y))
steps +=1
butterflyInstance.clamp_ip(screenRect)
Thank you so much!
See PyGame doc pygame.Rect()
clamp_ip is pygame.Rect method so you have to use butterflyInstance.rect.clamp_ip()
But you have to use self.rect.x, self.rect.y, self.rect.width, self.rect.height instead of self.x, self.y, self.width, self.height to keep position and size of object.
And use butterflyInstance.rect.x, butterflyInstance.rect.y, butterflyInstance.rect.width, butterflyInstance.rect.height
instead of butterflyInstance.x, butterflyInstance.y, butterflyInstance.width, butterflyInstance.height
PyGame use pygame.Rect in many places - it is usefull. For example: you can use rect.right instead of rect.x + rect.width, or rect.center = screenRect.center to center object on screen.

Collision detection in pygame not working properly

I have a piece of code that is supposed to place 1000 squares on screen, none overlapping.
But when I run it, I just get a bunch of squares where some of them ARE overlapping, any idea where I went wrong?
import time, pygame, ctypes, random
class Block(pygame.sprite.Sprite):
def __init__(self, color, width, height):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface([width, height])
self.image.fill(color)
self.rect = self.image.get_rect()
white = (255,255,255)
black = (0, 0, 0)
user32 = ctypes.windll.user32
sw = user32.GetSystemMetrics(0)
sh = user32.GetSystemMetrics(1)
Bloqs = {}
#Bloq atributes: Position (upper left), Size, Color, Genetics
pygame.init()
all_sprites_list = pygame.sprite.Group()
def Collision(bloq):
global all_sprites_list
hit = pygame.sprite.spritecollide(bloq, all_sprites_list, True)
if len(hit) == 0:
return False
if len(hit) > 0:
return True
def InitializeSim():
global Bloqs
global white
global black
global sw
global sh
global all_sprites_list
size = sw, sh
screen = pygame.display.set_mode(size, pygame.FULLSCREEN )
screen.fill(white)
count = 0
while count < 1000:
size = 10
pos = random.seed()
posx = random.randint(0, sw-size)
posy = random.randint(0, sh-size)
#pygame.draw.rect(screen, color, (x,y,width,height), thickness)
CurrentBloq = "bloq" + str(count)
Bloqs[CurrentBloq]={}
Bloqs[CurrentBloq]["position"] = (sw, sh)
bloq = Block(black, size, size)
bloq.rect.x = posx
bloq.rect.y = posy
Bloqs[CurrentBloq]["sprite"] = bloq
all_sprites_list.add(bloq)
all_sprites_list.draw(screen)
pygame.display.update()
while Collision(bloq) == True:
all_sprites_list.remove(bloq)
posx = random.randint(0, sw-size)
posy = random.randint(0, sh-size)
bloq.rect.x = posx
bloq.rect.y = posy
all_sprites_list.add(bloq)
all_sprites_list.draw(screen)
pygame.display.update()
count += 1
InitializeSim()
time.sleep(5)
pygame.quit()
Your code seems pretty complex for what you are trying to do...
approach it as a whole however feels natural for you, but when generating the blocks, I would follow something more along the lines of:
for i in range(numberofblocksyouwant):
while true:
create a block at a random x,y pos
if block collides with any existing blocks:
remove the block you just created
else:
break
For every block you want, this approach would keep recreating each block until it picks a random x,y that does not end up colliding with any other blocks.
However, if your blocks fill up the screen and make doing this impossible, the nested while loop will never end.
Hope that helps, let me know if that doesn't make sense.
To build upon Max's answer:
You would probably want a system to abort if there are to many iterations without success.
I would write it:
for i in range(numberofblocksyouwant):
while true:
numberofattempts = 0
create a block at a random x,y pos
if block collides with any existing blocks:
remove the block you just created
numberofattempts += 1
if numberofattempts > 1000:
return "took too many attempts"
else:
break

I can't blit the same object multiple times

I am currently making a Guitar Hero like game in python and I am trying to blit a note multiple times, but I can't seem to get it to work (the note is called Red)!
#Sprite Class
class Sprite(pygame.sprite.Sprite):
# What has to be passed when you create a sprite.
# image_file: The filename of the image.
# lacation: The initial location of where to draw the image.
def __init__(self, image_file, location):
pygame.sprite.Sprite.__init__(self) # Call Sprite initializer
self.image = pygame.image.load(image_file)
self.rect = self.image.get_rect()
self.rect.left, self.rect.top = location
self.xSpeed = 15 # The default xSpeed
self.ySpeed = 15 # The default ySpeed
self.name = "Not Assigned"
self.speed = 10
self.direction = 0
def setSpeed(self, speed):
self.speed = speed
self.calcXYSpeeds()
def setDirection(self, direction):
self.direction = direction
self.calcXYSpeeds()
def calcXYSpeeds(self):
angleRadians = math.radians(self.direction) # Convert the direction to radians.
self.xSpeed= self.speed*math.cos(angleRadians)
self.ySpeed = self.speed*math.sin(angleRadians)
def move(self):
self.rect = self.rect.move(self.xSpeed,self.ySpeed)
def setDirectionTowards(self, (x,y)):
self.direction = math.degrees(math.atan2((self.rect.x - x), (self.rect.y - y)))
self.direction = 270 - self.direction
self.calcXYSpeeds()
#Object Variables
Red = Sprite("Red.png", (100,25))
note1 = Sprite("note1.jpg", (50,650))
#add sprite to group
notes = pygame.sprite.Group()
notes.add(Red)
# Create an clock to keep track time
clock = pygame.time.Clock()
#Scoring Variables
font=pygame.font.Font(None,50)
score=0
score_text=font.render(str(score),1,(225,225,225))
#other Variables
Red1=0
ySpeed = 10
running = 1
while (running==1):
# Sets the frame rate to 30 frames/second.
clock.tick(30)
# Gets any events like keyboard or mouse.
event = pygame.event.poll()
key=pygame.key.get_pressed()
#object random spawn
time = pygame.time.get_ticks()
if (time==30*(random.randint(0,1000))):
screen.blit(Red.image, Red.rect)
pygame.display.update()
#Object Movement
Red.rect.y = Red.rect.y + ySpeed
#Keys to complete notes
if key[pygame.K_a]and Red1==0 and pygame.sprite.collide_rect(Red, note1) == True:
font=pygame.font.Font(None,50)
score = score+1
score_text=font.render(str(score),1,(225,225,225))
Red1=1
#where i currently working to fix the problem
notes.Group.clear(screen, background)
notes.Group.update()
notes.draw(screen)
# Sets the exit flag if the X was clicked on the run window.
if event.type == pygame.QUIT:
running = 0
# Color the whole screen with a solid color.
screen.fill((0, 255, 255))
#prints objects
screen.blit(note1.image, note1.rect)
screen.blit(Red.image, Red.rect)
screen.blit(score_text,(500,50))
# Update the window.
pygame.display.update()
You are cleaning and updating the screen multiple times in the same cycle.
Clean the screen in every cycle is a correct approach, but you need to clean only once otherwise you are erasing everything you blitted before.
Also, for notes to persist between cycles you need to keep them in a collection, like notes, that way you blit every note in every cycle.
Keep in mind this order in your main loop:
Control FPS (clock.tick())
Check events
Update state (ie: add/remove notes collection, etc)
Clear screen
Draw current state (notes, static elements, etc)
Update display
Another important thing, don't use the same Sprite() nor the same Rectangle in different locations. If you want to draw another copy of the same image in a new location make a copy of the rectangle, then create a new Sprite() object.

Updating Class Variables While Using Sprite Groups

Ill show you my code first(Outside of main loop):
START_BAT_COUNT = 10
BAT_IMAGE_PATH = os.path.join( 'Sprites', 'Bat_enemy', 'Bat-1.png' )
bat_image = pygame.image.load(BAT_IMAGE_PATH).convert_alpha()
bat_image = pygame.transform.scale(bat_image, (80, 70))
class Bat(pygame.sprite.Sprite):
def __init__(self, bat_x, bat_y, bat_image, bat_health, bat_immune):
pygame.sprite.Sprite.__init__(self)
self.bat_health = bat_health
self.bat_immune = bat_immune
self.image = bat_image
self.rect = self.image.get_rect()
self.mask = pygame.mask.from_surface(self.image)
self.rect.topleft = (bat_x, bat_y)
self.bat_x = bat_x
self.bat_y = bat_y
def update(self):
self.bat_x += 500
all_bats = pygame.sprite.Group()
for i in range(START_BAT_COUNT):
bat_x = (random.randint(0, 500))
bat_y = (random.randint(0, 500))
bat_health = 5
bat_immune = False
new_bat = Bat(bat_x, bat_y, bat_image, bat_health, bat_immune)
all_bats.add(new_bat)
Inside main loop:
all_bats.update()
all_bats.draw(display)
In update() I increase the value of bat_x by 500 every time the code is read, and I know the value of bat_x increases as I have tested this by printing the values of bat_x and watching them increase. My question is, is there a way to increase bat_x, and have that actually move my bat? As of now, the variable increases but the bat doesn't move. Thanks
What are the dimensions of your display? If you are moving the bat 500 pixels in the x direction then it will immediately fly off the screen once it starts moving. Also, your bat might not be moving because you didn't update the position of its rectangle.
In the lines
def update(self):
self.bat_x += 500
try
def update(self):
self.rect.move_ip(500, 0)
where move_ip moves the rectangle in-place and will increase the bat's x coordinate by 500 each time it updates.

Categories

Resources