how to make an animation in pygame [duplicate] - python

This question already has answers here:
Animated sprite from few images
(4 answers)
Closed 1 year ago.
i am trying to make an animation of when my player gets shot to make it seem like he is falling to the ground.
i have tried the code below but that doesn't seem to work. it slows down the frames and only shows the last image of my animation is there a simpler way of animation in pygame?
if player2_hit_sequence == True:
stage1 = True
if stage1 == True:
game_display.blit(dying1_p2, (player2X, player2Y))
time.sleep(0.2)
stage1 = False
stage2 = True
if stage2 == True:
game_display.blit(dying2_p2, (player2X, player2Y))
time.sleep(0.2)
stage2 = False
stage3 = True
if stage3 == True:
game_display.blit(dying3_p2, (player2X, player2Y))
time.sleep(0.2)
is there a function to make a sequence of images or something like that?

Ok so for animation you need a bunch of images, and a timer.
I'm presenting some code snippets based around a pygame sprite. Maybe this doesn't exactly fit the question, but it seems like a better solution then painting/blitting images manually.
So first the code starts with a sprite class:
class AlienSprite(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.base_image = pygame.image.load('alien.png').convert_alpha()
self.image = self.base_image
self.rect = self.image.get_rect()
self.rect.center = ( WINDOW_WIDTH//2, WINDOW_HEIGHT//2 )
# Load warp animation
self.warp_at_time = 0
self.warp_images = []
for filename in [ "warp1.png", "warp2.png", "warp3.png" ]:
self.warp_images.append( pygame.image.load(filename).convert_alpha() )
So the idea is the Alien Sprite has a "normal" image, but then when it "warps" (teleports) an animation plays. The way this is implemented is to have a list of animation images. When the animation starts, the sprite's image is changed from base_image to the first of the warp_images[]. As time elapses, the sprites image is changed to the next frame, and then the next, before finally reverting back to the base image. By embedding all this into the sprite update() function, the normal updating mechanism for sprites handles the current "state" of the alien sprite, normal or "warp". Once the "warp"-state is triggered, it runs without any extra involvement of the pygame main loop.
def update(self):
# Get the current time in milliseconds (normally I keep this in a global)
NOW_MS = int(time.time() * 1000.0)
# Did the alien warp? (and at what time)
if (self.warp_at_time > 0):
# 3 Frames of warp animation, show each for 200m
ms_since_warp_start = NOW_MS - self.warp_at_time
if ( ms_since_warp > 600 ):
# Warp complete
self.warp_at_time = 0
self.image = self.base_image # return to original bitmap
# Move to random location
self.rect.center = ( random.randrange( 0, WINDOW_WIDTH ), random.randrange( 0, WINDOW_HEIGHT ) )
else:
image_number = ms_since_warp // 200 # select the frame for this 200ms period
self.image = self.warp_images[image_number] # show that image
def startWarp(self):
# Get the current time in milliseconds (normally I keep this in a global)
NOW_MS = int(time.time() * 1000.0)
# if not warping already ...
if (self.warp_at_time == 0):
self.warp_at_time = NOW_MS
So the first thing to notice, is that the update() uses the clock to know the number of elapsed milliseconds since the animation started. To keep track of the time I typically set a global NOW_MS in the game loop.
In the sprite, we have 3 frames of animation, with 200 milliseconds between each frame. To start a sprite animating, simply call startWarp() which obviously just kicks-off the timer.
SPRITES = pygame.sprite.Group()
alien_sprite = AlienSprite()
SPRITES.add(alien_sprite)
...
# Game Loop
done = False
while not done:
SPRITES.update()
# redraw window
screen.fill(BLACK)
SPRITES.draw(screen)
pygame.display.update()
pygame.display.flip()
if (<some condition>):
alien_sprite.startWarp() # do it
Obviously all those frame timings & what-not should be member variables of the sprite class, but I didn't do that to keep the example simple.

Related

Explosions Sprite Despears Way To Fast When Enemy is Killed How Do Fix This?

on my draw I did this but the explosion doesnt event last a sec and just dispears
is there a better way I could do this instead of saying if the enemy health is greater then this load this?
for enemys in enemying:
if enemys.health < -4
for explode in explodes:
explode.draw((enemys.hitbox, enemys.hitbox))
my explosion class
class explode:
def __init__(self,x,y,height,width,color):
self.x = x
self.y = y
self.height = height
self.width = width
self.explode = [
pygame.image.load("spark_01.png"),
pygame.image.load("spark_02.png"),
pygame.image.load("spark_03.png"),
pygame.image.load("spark_04.png"),
pygame.image.load("spark_05.png"),
pygame.image.load("spark_06.png"),
pygame.image.load("spark_07.png")]
self.explode = [pygame.transform.scale(image,(image.get_width()//5,image.get_height()//5)) for image in self.explode]
self.rect = pygame.Rect(x,y,height,width)
self.direction = "blobright"
self.anim_index = 0
def draw(self,x):
self.rect.topleft = (self.x,self.y)
if self.direction == "blobright":
window.blit(self.explode[self.anim_index], self.rect)
self.anim_index += 1
if self.anim_index == len(self.explode):
self.anim_index = 0
black = (0,0,0)
explode1 = explode(400,450,50,50,black)
explodes = [explode1]
this is where I delete the enemys
for enemyshoot in enemyshooting:
for bullet in bullets:
if bullet.rect.colliderect(enemyshoot.hitbox):
if enemyshoot.health > -8:
enemyshoot.health -= 1
bullets.pop(bullets.index(bullet))
else:
del enemyshooting[one]
Here is the example I promised. Notice how the system clock and a flag
are used to control animation speed so that different actions can happen on the screen at different speeds. This program is written quick-and-dirty. There are MUCH more efficient ways to handle the animation so that the screen is only drawn to when it is time for something to move, and ONLY those parts of the screen that have changed are drawn and refreshed. But don't worry about that. The lesson here is how to use the clock to slow down your animation speed. What's the old saying? Code now, refactor later? Words to live by.
import pygame
from pygame.locals import * # Pygame Constants such as FULLSCREEN and KEYDOWN.
from pygame import Color
pygame.init()
import time
# ==============================================================
# Disable Windows Auto-scaling
# The windows auto-scaling feature can mess with pygame graphics.
# Windows-Specific: Delete if you are not on a Windows machine
# If you are on Windows, comment section to see the effect.
import ctypes
awareness = ctypes.c_int()
errorCode = ctypes.windll.shcore.GetProcessDpiAwareness(0, ctypes.byref(awareness))
errorCode = ctypes.windll.shcore.SetProcessDpiAwareness(2) # Awareness levels can be 0, 1 or 2:
# ===============================================================
# I will base the example on your explode class. If I were writing the
# game, I would use a sprite class along with a sprite group to simplify
# the code. However, these topics are an entirely different branch of study
# so I won't use them in the example. Forgive my quick-and-dirty code.
# I don't have your image files, but it looks as though
# you have an actual animation to use for your explosion that is
# made up of several different frames. We will emulate this for the
# sake of our demonstration.
class EXPLODE:
def create_spark(self):
TRANSPARENT = (254,255,255) # You can chose your own transparent color.
# Lacking the spark files, let's draw our own.
spark_img = pygame.Surface((31,31))
spark_img.fill(TRANSPARENT)
spark_img.set_colorkey(TRANSPARENT)
spark_rec = spark_img.get_rect()
left_center = (spark_rec.left,spark_rec.centery)
right_center = (spark_rec.right,spark_rec.centery)
top_center = (spark_rec.centerx,spark_rec.top)
bottom_center = (spark_rec.centerx,spark_rec.bottom)
top_left = spark_rec.topleft
top_right = spark_rec.topright
bottom_left = spark_rec.bottomleft
bottom_right = spark_rec.bottomright
pygame.draw.circle(spark_img,Color("yellow"),spark_rec.center,spark_rec.width//2,0)
pygame.draw.line(spark_img,Color("indianred"),left_center,right_center,2)
pygame.draw.line(spark_img,Color("red"),top_center,bottom_center,2)
pygame.draw.line(spark_img,Color("burlywood3"),top_left,bottom_right,3)
pygame.draw.line(spark_img,Color("orange"),top_right,bottom_left,3)
# Now crop line segments that fall outside the spark circle..
pygame.draw.circle(spark_img,TRANSPARENT,spark_rec.center,spark_rec.width//2+8,8)
return spark_img
def __init__(self,window,x,y,height,width,color = (255,0,0)):
self.window = window # Needed so class can draw to the screen.
self.T0 = time.time() # Holds the starting time for the timer.
self.x = x
self.y = y
self.height = height
self.width = width
self.anim_index = 0
image = self.create_spark()
# Standing in for actual animation pages, we scale up the spark image.
self.explode = [
image,
pygame.transform.scale(image,(image.get_width()*2,image.get_height()*2)),
pygame.transform.scale(image,(image.get_width()*4,image.get_height()*4)),
pygame.transform.scale(image,(image.get_width()*6,image.get_height()*6)),
pygame.transform.scale(image,(image.get_width()*8,image.get_height()*8))]
'''
# Or you can load your spark frames as before.
self.explode = [
pygame.image.load("spark_01.png"),
pygame.image.load("spark_02.png"),
pygame.image.load("spark_03.png"),
pygame.image.load("spark_04.png"),
pygame.image.load("spark_05.png"),
pygame.image.load("spark_06.png"),
pygame.image.load("spark_07.png")]
# All of the loaded images are scaled down to 1/5 their size.
self.explode = [pygame.transform.scale(image,(image.get_width()//5,image.get_height()//5)) for image in self.explode]
'''
self.rect = image.get_rect()
self.direction = "blobright" # <-- I don't know what this is, so I've ignored it.
self.anim_index = 0
def draw(self,enemy_rec,anin_speed): # Create an animation method to handle the draw routine.
clock = time.time()
elapsed_time = clock - self.T0
finish_flg = False
if elapsed_time > anin_speed: # Animation Speed Controlled Here!!
self.anim_index +=1
self.T0 = time.time() # Reset the start time.
if self.anim_index == len(self.explode)-1:
finish_flg = True
frame = self.explode[self.anim_index]
rec = frame.get_rect()
rec.center = enemy_rec.center
self.window.blit(frame,rec)
return finish_flg # The finish flag lets the main program know it can delete the enemy.
# ================== MAIN() ===================
# ----------------------------------------------
def main(): # By using a 'main()' function, your code will run faster!
screen = pygame.display.set_mode((3000,2000),pygame.FULLSCREEN,32)
screen_rec = screen.get_rect()
screen.fill(Color("steelblue1"))
# Declare and initialie an instance of the EXPLODE class.
# Only one enemy can blow up at the same time for any give instance
# of the class, so you may want each ship to have its own instance
# if there is a chance of simultanious explosions.
# I wouldn't normally use this aproach, but I wanted to stay
# as close as possible to your existing code.
explode = EXPLODE(screen,screen_rec.centerx,screen_rec.centery,30,20,Color('blue'))
# Let's create some "enemy" units.
# You would use an enemy class for this (and for drawing them)
# but this example is qick and dirty, so.. Two enemies coming up!
# One enemy to blow up when it hits the wall.
enemy1_img = pygame.Surface((30,30))
enemy1_rec = enemy1_img.get_rect()
enemy1_img.fill(Color("Green"))
pygame.draw.rect(enemy1_img,Color("Red"),enemy1_rec,5)
# And one 'enemy' to move while the other is blowing up.
enemy2_img = enemy1_img.copy()
enemy2_rec = enemy2_img.get_rect()
# Give enemies screen positions.
enemy1_rec.center = (screen_rec.centerx-300, screen_rec.centery-300)
enemy2_rec.center = (screen_rec.centerx-800,screen_rec.centery-300)
# Create a wall for a ship to crash into.
wall_img = pygame.Surface((100,60))
wall_img.fill(Color("Indianred"))
wall_rec = wall_img.get_rect()
wall_rec.center = screen_rec.center
wall_rec = wall_rec.move((400,0))
# Oh, this list is ugly. Forgive me! Used instead of a list of Enemy-Class objects.
enemy_list = [[10,enemy1_img,enemy1_rec,(2,1)],[10,enemy2_img,enemy2_rec,(3,0)]] # [Life, Image, Rectangle, Speed]
# Ok, the setup is finished. Time for some action!
# =============== BODY ===================
# ------------------------------------------
anin_speed = 0.3 # You can control explosion speed here!
pygame.mouse.set_visible(False)
run_cycle = True
while run_cycle == True:
# There are much better ways to erase images, but this will do for now.
screen.fill(Color("steelblue1")) # Erase old sprites.
screen.blit(wall_img,wall_rec) # Put the wall back in place.
# Because it is bad idea to modify an object being looped through,
# we will construct a new list, called 'hold', and copy it back at the end.
hold = []
for enmy in enemy_list:
life,enemy_img,enemy_rec,speed = enmy
if life > 4:
screen.blit(enemy_img,enemy_rec) # If enemy is healthy, put it on the screen.
enemy_rec = enemy_rec.move(speed)
if enemy_rec.colliderect(wall_rec) == True:
life = 0
if enemy_rec.left > screen_rec.right+10: # End the program after top ship leaves the screen.
run_cycle = False
hold.append([life,enemy_img,enemy_rec,speed])
else: # Otherwise draw the explosion.
finish_flg = explode.draw(enemy_rec,anin_speed)
if finish_flg == False: # If TRUE the enemy is ommitted from the hold list!
hold.append(enmy)
enemy_list = hold.copy() # And now the possibly modified list is copied back to the enemy_list.
pygame.display.flip()
# ================
# Main
# ----------------
main() # Hint! For technical reasons related to the compiler being able
# to control scope and predict variable sizes, keeping your
# main body encapsulated in a function like this will improve
# efficiency and run speeds.
You have a great start. If nothing needs to happen while the explosion is going on, you could use a sleep command in your loop. >> time.sleep(0.01)
If the action has to continue on the screen during the explosion, then you will need to use a timer and keep returning to that function after each duration to draw the next frame. Just initialize using >> T0 = time.time() before the explosion, visit the function when time.time()-T0 > 0.01 seconds (for example) and reset T0 = time.time() after each frame is drawn. Return a 'finished' value when the animation is over, so you can remove it from your enemy list.
In the __init__() for explode note the time when it is called and save it.
In the explodes draw() only increment self.anim_index when enough time has passed since the last time it was incremented (based on the time value you saved in the __init__()). This will let you go more slowly through the frames of the exploding animation.
There is really no difference between this and any other object animation, other than once the cycle completes the object (the explosion) goes a way.

Working with classes (creating a class efficiently, drawing and updating all instances of the class every frame)

I've been working on a "bullet hell" game of my own, but I am struggling very much with working with classes (im new to python), below i've enclosed my code(I wouldnt include so much but i dont know howo to go about the problem). At the moment what i've been attempting to
1. Create different instances of the class
2. Draw them every frame
3. Update their position every frame according to the x and y speed
import pygame
# Define colors needed
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
bulletArray = []
BulletSprite = ("Bullet4.png")
pygame.init()
class Bullet:
def __init__(self, sprite, x, y, xSpeed, ySpeed):
pygame.sprite.Sprite.__init__(self)
self.x = x
self.y = y
self.xSpeed = xSpeed
self.ySpeed = ySpeed
bulletArray.append(self)
def update(self):
pass
def draw(screen):
self.screen = screen
screen.blit(screen, (x, y))
#Set the width and height of the game screen
size = (700, 500)
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Bullet stuff")
# Loop until the user clicks the close button.
done = False
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
testBullet = Bullet(BulletSprite, 200, 200, 2, 2)
#Main Program Loop
while not done:
# --- Main event loop
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
# --- Game logic should go here
print(bulletArray)
# --- background image.
screen.fill(WHITE)
# --- Drawing code should go here
# --- Updates the screen every frame with whats been drawn
pygame.display.flip()
# --- Limit to 60 frames per second
clock.tick(60)
# Close the window and quit.
pygame.quit()
Much of the this mechanism is already handled automatically if you use the PyGame Sprite Class.
A PyGame sprite is essentially made of:
An Image to draw to represent the object
A rectangle around the object to remember the location and check for collisions
An update() function to handle any movement of the sprite
It's handy to work with the existing PyGame Sprite layout because a lot of functionality is already provided by the library code.
So let's make a BulletSprite:
class BulletSprite( pygame.sprite.Sprite ):
def __init__( self, bitmap, x, y ):
pygame.sprite.Sprite.__init__( self )
self.image = bitmap # How the sprite looks
self.rect = bitmap.get_rect() # Bounding box the size of the image
self.rect.centerx = x # position the bullet
self.rect.centery = y # about its centre
And that's it. This will give you a stationary sprite, painted as the given bitmap at (x,y) on-screen.
So let's now use it:
# Create a group of 100 bullets
sprite_image = pygame.image.load( "bullet.png" )
all_bullets = pygame.sprite.Group() # Group to hold the sprites
for i in range( 100 ):
random_x = 50 + random.randrange( 950 ) # TODO: use proper screen size
random_y = 50 + random.randrange( 950 )
new_sprite = BulletSprite( sprite_image, random_x, random_y )
all_bullets.add( new_sprite )
# Main Window loop
while True:
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
break
# Paint the window
window.fill( ( 0, 0, 0 ) ) # Paint it Black
all_bullets.update() # move all the bullets
all_bullets.draw( window ) # Paint all the bullets
pygame.display.flip()
pygame.quit()
And that's it. After the sprites are created, it takes two lines of code to move them all, and draw them all.
But they wont move yet. For them to move, the BulletSprite needs to have an update() function. This function does whatever is necessary to move the sprite - apply velocity, gravity, whatever. The net effect is the sprite's rectangle's (x,y) is changed.
So adding some changes to the sprite - first an x and y velocity, and then the movement code.
class BulletSprite( pygame.sprite.Sprite ):
def __init__( self, bitmap, x, y ):
pygame.sprite.Sprite.__init__( self )
self.image = bitmap # How the sprite looks
self.rect = bitmap.get_rect() # Bounding box the size of the image
self.rect.centerx = x # position the bullet
self.rect.centery = y # about its centre
self.x_move = random.randrange( -3, 3 ) # simple random velocity
self.y_move = random.randrange( -3, 3 )
def update( self ):
x = self.rect.centerx + self.x_move # calculate the new position
y = self.rect.centerx + self.y_move # by applying an offset
# has the sprite gone off-screen
if ( x < 0 or x > 1000 or y < 0 or y > 1000 )
self.kill() # remove from the group
else:
self.rect.centerx = x # RE-position the bullet
self.rect.centery = y
Every time the update() is called on your sprite group (all_bullets), the sprite library will go and call the update() function on every sprite in the group. The code checks to see if the bullet is off-screen (I lazily used 1000 as a placeholder for screen width & height), and if-so, the sprite.kill() function handles the removing from the group and cleaning up. It's very easy.

How to implement a Pygame timed game loop?

I am looking to write a better game loop in Python using pygame.time.Clock(), and I understand the concept of keeping time and de-coupling rendering from the main game loop in order to better utilise the ticks. I also understand about passing time lag into the rendering so that it renders the correct amount of movement, but the only examples I've found are written in C# and although I first thought it fairly simple to convert to Python, it's not behaving.
I've figured out that Pygame's Clock() already works out the milliseconds between the last 2 calls to .tick, and I've tried to adapt the sample code I found, but I really need to see a working example written in Python. Here's what I've come up with so far:
FPS = 60
MS_PER_UPDATE = 1000 / FPS
lag = 0.0
clock = pygame.time.Clock()
running = True
# Do an initial tick so the loop has 2 previous ticks.
clock.tick(FPS)
while running:
clock.tick(FPS)
lag += clock.get_time()
user_input()
while lag >= MS_PER_UPDATE:
update()
lag -= MS_PER_UPDATE
render(lag / MS_PER_UPDATE)
I'm not sure if this is all worth it in Pygame, or if it's already taken care of in some of it's time functions already? My game runs slower on the laptop (expected) but I thought doing this might even out the FPS a bit between my main PC and laptop by de-coupling the rendering. Does anyone have experience doing these advanced game loops in Pygame? I just want it to be as good as it can be...
Just take the time it took to render the last frame (called delta time) and pass it to your game objects so they can decide what to do (e.g. move more or less).
Here's a super simple example:
import pygame
class Actor(pygame.sprite.Sprite):
def __init__(self, *args):
super().__init__(*args)
self.image = pygame.Surface((32, 32))
self.rect = pygame.display.get_surface().get_rect()
self.image.fill(pygame.Color('dodgerblue'))
def update(self, events, dt):
self.rect.move_ip((1 * dt / 5, 2 * dt / 5))
if self.rect.x > 500: self.rect.x = 0
if self.rect.y > 500: self.rect.y = 0
def main():
pygame.init()
screen = pygame.display.set_mode((500, 500))
sprites = pygame.sprite.Group()
Actor(sprites)
clock = pygame.time.Clock()
dt = 0
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
sprites.update(events, dt)
screen.fill((30, 30, 30))
sprites.draw(screen)
pygame.display.update()
dt = clock.tick(60)
if __name__ == '__main__':
main()
If your game slows down below 60 (or whatever) FPS, dt gets bigger, and Actor moves more to make up for the lost time.

drawing the moving objects all at once instead of drawing one then moving onto the next one

this code draws the basics of my orbit simulator but as soon as it has done one orbit with a planet it moves onto the next one in the list, how do i make it so it does all of the different orbits at the same time.
#import the library
import pygame,math
#classes
class planet():
def __init__(self,name,colour,distance,eccentricity,radius,x,y,angle):
self.screen = screen
self.name = name
self.colour = colour
self.distance = distance
self.eccentricity = eccentricity
self.radius = radius
self.x = x
self.y = y
self.angle = angle
def draw(self):
pygame.draw.circle(self.screen,self.colour,(self.x,self.y),self.radius,)
def draw_orbit(screen,colour,x,y,r):
screen.fill(Black)
int(r)
pygame.draw.circle(screen,colour,[x,y],r)
pygame.display.flip
orbit()
also i can make it so there is only one item in the list but it never does more than one rotation so, if i add planet.angle+360 instead of just the 360 it does the first one but never moves of it.
def orbit(planet):
while planet.angle <= 360:
a = planet.distance*(1.496*10**8)
e = planet.eccentricity
angle_radians = math.radians(planet.angle)
r = a*(1-(e*math.cos(angle_radians)))
planet.x = int(math.cos(angle_radians)*r/10**6)
planet.y = int(math.sin(angle_radians)*r/10**6)
planet.angle +=1
## print(planet.angle)
## print(planet.x,planet.y)
planet.x +=800
planet.y +=400
screen.fill(Black)
pygame.draw.circle(screen,Red,center,10)
## pygame.draw.circle(screen,White,center,r,1)
pygame.draw.circle(screen,planet.colour,[planet.x,planet.y],planet.radius)
pygame.display.flip()
#define colours
Black = (0,0,0)
White = (255,255,255)
Green = (0,255,0)
Red = (255,0,0)
Blue = (0,0,255)
#initialise the engine
pygame.init()
#Opening a window
size = (1600,800)
screen = pygame.display.set_mode(size)
center = [800,400]
planets =[]
planet_draw =[]
# screen,name,colour,distance,eccentricity,radius,x,y
planet_list = [
['Mercury',White,0.387,0.2056,5,0,0,120],
['Venus',Green,0.723,0.0068,10,0,0,60],
['Earth',Blue,1,0.0167,10,0,0,0],
['Mars',White,1.524,0.0934,10,0,0,150],
['Jupiter',Green,5.203,0.0484,30,0,0,330],
## [screen,'Saturn',Red,9.537,0.0542,10,0,0],
## [screen,'Uranus',Red,19.191,0.0472,10,0,0],
## [screen,'Neptune',Green,30.069,0.0086,10,0,0]
]
for i in planet_list:
planet_draw.append(planet(i[0],i[1],i[2],i[3],i[4],i[5],i[6],i[7]))
#set window title
pygame.display.set_caption("Orbit Simulator")
#loop unti the user clicks the close button
done = False
#used to manage how fast the screen updates
clock = pygame.time.Clock()
#------ Main program Loop ------
while not done:
#--- Main event loop
for event in pygame.event.get(): #user did something
if event.type == pygame.QUIT: #if user clicked close
done = True #flag that we are done and exit the loop
#------ Game logic should go here ------
for planet in planet_draw:
orbit(planet)
#------ Drawing code should go here -------
#first, clear the screen to white. Don't put other drawing commands above this or they will be erased with this command.
#update the screen
pygame.display.flip()
#------ Limit to 60 frames per second ------
clock.tick(60)
#------ When the loop ends, quit ------
pygame.quit()
The problem is your orbit() function. It's clearing and repainting the entire screen, each time a new planet is drawn.
The function only needs to draw the planet, the clearing of the screen and flipping should be done elsewhere.
Some pseudocode ~
Update Orbits (just the positions)
Clear screen
Draw N planets
Page flip
Wait for timer tick
Giving Code:
def orbit(planet):
while planet.angle <= 360:
a = planet.distance*(1.496*10**8)
e = planet.eccentricity
angle_radians = math.radians(planet.angle)
r = a*(1-(e*math.cos(angle_radians)))
planet.x = int(math.cos(angle_radians)*r/10**6)
planet.y = int(math.sin(angle_radians)*r/10**6)
planet.angle +=1
# Note: Don't paint, Don't offset for screen size
while not done:
#--- Main event loop
for event in pygame.event.get(): #user did something
if event.type == pygame.QUIT: #if user clicked close
done = True #flag that we are done and exit the loop
#------ Game logic should go here ------
# ---- move the planets -----
for planet in planet_draw:
orbit(planet)
# ------ Re-paint the current state of the screen ------
screen.fill(Black)
for planet in planet_draw:
planet.draw()
pygame.display.flip()
#------ Limit to 60 frames per second ------
clock.tick(60)
It might work out easier to modify your planet object such that given the time-tick (or some counter), it re-calculates the orbit co-ordinates at this time. This could give you code like:
while ( True ):
frame_counter = pygame.get_ticks()
screen.fill(Black)
for planet in planet_draw:
planet.calcuateOrbitAt( frame_counter )
planet.draw()
pygame.display.flip()
And it handles jumping orbits at really slow frame rates.

Can't click on image again, what's wrong with my pygame code?

Okay, I'am trying to create a Tom and Jerry game with the pygame library.
The game focuses on catching mice by clicking on them as they appear in their holes. The problem
is that sometimes a cat appears instead of a mouse and should the player erroneously click on the
cat (s)he looses all earned points, but the game continues.
The mouse is an image of a mouse and the cat is an image of an cat.
If you click on the mouse, you get mouse, otherwise the cat gets the points.
The code is a mess, that's because I don't know what I'am doing and just set an another event loop because then it works, because it runs after I create the mouse. It works to click on the mouse but then you click somewhere else and after that it's like you did not clicked on the mouse.
The mouse is created in a loop and is supposed to wait for 5 seconds and if you click on the mouse within these seconds then an appropriate message prints out in the console ,,Jerry clicked!" else "1 click". If you don't click on the mouse within 5 seconds a image covers the mouse so she disappears.
Now, what I'am trying to do right now is to print the message 1 click when the player does not click on anything but print 1 click jerry clicked when the player clicks on the mouse. I have a image of the mousehole and then I put the mouse on the mousehole, that is, on an another image.
This code works with one image at least:
pygame.init()
width=350;
height=400
screen = pygame.display.set_mode( (width, height ) )
pygame.display.set_caption('clicked on image')
redSquare = pygame.image.load("images/red-square.png").convert()
x = 20; # x coordnate of image
y = 30; # y coordinate of image
screen.blit(redSquare , ( x,y)) # paint to screen
pygame.display.flip() # paint screen one time
running = True
while (running):
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
# Set the x, y postions of the mouse click
x, y = event.pos
if redSquare.get_rect().collidepoint(x, y):
print('clicked on image')
#loop over, quite pygame
pygame.quit()
My problem is that, when I click on the mouse and then I don't click on the mouse I can't click on the mouse again at another position.
So what's wrong? What I'am doing wrong here?
Here is my code:
import pygame
from pygame import *
from random import *
init()
run = True
screen = (800,800)
screen = display.set_mode(screen)
xpos = 0
ypos = 0
mouseorcatxpos = 5
mouseorcatypos = 0
mousehole = image.load("mousehole.png").convert()
cat = image.load("tom.png")
jerry = image.load("jerry.png")
def makeholes():
global ypos
global xpos
for holey in range(1,9):
for holex in range(1,9):
screen.blit(mousehole,(xpos,ypos))
display.flip()
xpos += 100
ypos += 100
xpos = 0
def mouseorcat():
global xpos
mouseorcatxpos = 5
ypos = 0
for mousecaty in range(1,9):
pygame.event.pump()
for mousecatx in range(1,9):
randommouse = randint(1, 3)
randomcat = randint(1, 10)
if(randommouse == 2):
screen.blit(jerry, (mouseorcatxpos, ypos))
display.flip()
for event in pygame.event.get():
if (event.type == MOUSEBUTTONDOWN):
if jerry.get_rect().collidepoint(xpos, ypos) == False:
print("l clicked!")
x, y = event.pos
if jerry.get_rect().collidepoint(xpos, y):
print("JERRY CLICKED!!")
x, y = event.pos
print(x, y)
time.wait(5000)
#screen.blit(mousehole, (mouseorcatxpos - 5, ypos))
display.flip()
elif(randomcat == 2):
screen.blit(cat, (mouseorcatxpos, ypos))
display.flip()
time.wait(1500)
screen.blit(mousehole, (mouseorcatxpos-5, ypos))
display.flip()
mouseorcatxpos += 100
mouseorcatxpos = 0
ypos += 100
makeholes()
while run == True:
for event in pygame.event.get():
mouseorcat()
if event.type == QUIT:
run = False
I rewrote your game to show you how I would do it.
To keep track of the time and to limit the framerate I used a pygame.time.Clock and a timer variable. The clock returns the time in milliseconds since clock.tick was called the last time, which is used to increase the timer variable. The cat just replaces the mouse after two seconds and the mouse is set to a new position. I use pygame.Rects to store the positions, but you could also use lists or tuples.
import sys
import random
import pygame
pygame.init()
size = (800, 800)
screen = pygame.display.set_mode(size)
# Images replaced by pygame.Surface. Do that too
# in the future before you post your code.
mousehole = pygame.Surface((40, 40)).convert()
mousehole.fill(pygame.Color(30, 30, 30))
cat = pygame.Surface((40, 40)).convert()
cat.fill(pygame.Color(110, 110, 130))
jerry = pygame.Surface((40, 40)).convert()
jerry.fill(pygame.Color(190, 130, 0))
# Create the background image and blit the holes.
background = pygame.Surface(size).convert()
for holey in range(8):
for holex in range(8):
background.blit(mousehole, (holex*100, holey*100))
def new_position():
"""Return a random position between 0-700 in steps of 100."""
return (random.randrange(0, 701, 100), random.randrange(0, 701, 100))
def main():
fps = 30
clock = pygame.time.Clock()
jerry_rect = jerry.get_rect() # Stores jerry's position and size.
jerry_rect.topleft = new_position() # New random position.
# The cat is outside of the screen first.
cat_rect = cat.get_rect(topleft=(-100, -100))
points = 0
timer = 0
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
if jerry_rect.collidepoint(event.pos):
points += 1
print('Jerry caught! Points:', points)
timer = 0
jerry_rect.topleft = new_position()
else:
print('Missed. Points:', points)
# Run logic.
timer += clock.tick(fps) / 1000 # timer + seconds since last tick.
if timer > 2: # Cat catches mouse after 2 seconds.
cat_rect.topleft = jerry_rect.topleft
jerry_rect.topleft = new_position()
timer = 0
points = 0
print('Tom caught Jerry.')
# Draw.
# Clear the screen by blitting the bg.
screen.blit(background, (0, 0))
screen.blit(jerry, jerry_rect)
screen.blit(cat, cat_rect)
pygame.display.flip()
if __name__ == '__main__':
main()
pygame.quit()
sys.exit()
Side notes:
Don't use star imports (from module import *), because that can make code harder to read. If you want you can use from pygame.locals import *, if it's the only star import.
Don't use global variables, because they can make code harder to read, understand and maintain. Pass variables to functions as arguments and then return the result.
Update: Some notes about your program:
The first big problem is that your game has two event loops and the important one is deeply nested inside of two other for loops and a if. The event loop should be directly under the main while loop (one indentation level (when you have more experience you can put it into a function or class method)).
The two for loops seem to have the purpose to let the code run until randommouse or randomcat are 2. To run code until a condition is met is the purpose of a while loop. But in this case you should better just pick a random number and write the if/elif conditions so that they always apply. For example, you want a 2/3 chance for mouse and 1/3 for a cat,
random_number = random.randint(1, 3)
if random_number < 3:
print("2/3 probability. It's a mouse")
else:
print("1/3 probability. It's a cat")
Or use random.choice with a list:
>>> random.choice(['mouse', 'mouse', 'cat'])
'mouse'
time.wait(5000) shouldn't be used because the game just hangs in this time. You can't even close the window. Limit the framerate and get the time since the last tick with a pygame.time.Clock.
pygame.event.pump() is not needed.
If you call get_rect() without an argument, the rect is positioned at (0, 0).
if jerry.get_rect().collidepoint(xpos, y):
That's the reason why clicking on jerry only works in the top row, and because you use the global xpos here. Since xpos is 0, the whole top row counts as Jerry.
You can pass coordinates to get_rect like so (you can also use center or other args instead of topleft):
jerry_rect = jerry.get_rect(topleft=(50, 100))
I'm sorry but I don't think I can simply fix your code. I've tried it several times, but I always end up re-writing it completely.
I begin by extracting the event loop out of the two nested for loops, then remove these loops, create rects for the mouse and cat, fix the collision detection, add a timer and so on. Take a close look at my example and try to rewrite your game in a similar way, and keep asking questions if you don't understand something.

Categories