I am working on a school project with pygame and have unfortunately run into trouble with animating my sprites, or more specifically, changing the sprite from one to another, and I'm unsure on how to do it. Currently, I have a class of my spaceship sprite and background sprite:
game_folder = os.path.dirname(__file__)
img_folder = os.path.join(game_folder, 'img')
space_background = pygame.image.load('background.png').convert()
starship_1 = pygame.image.load('starship2.png').convert()
starship_2 = pygame.image.load('starship3.png').convert()
starship_3 = pygame.image.load('starship4.png').convert()
class starship(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
pygame.sprite.Sprite.__init__(self)
self.image = pygame.transform.scale(starship_1, (150,150))
self.image.set_colorkey(black)
self.rect = self.image.get_rect()
self.rect.center = (400, 400)
all_sprites = pygame.sprite.Group()
BackGround = background()
StarShip = starship()
all_sprites.add(BackGround)
all_sprites.add(StarShip)
My while loop looks like this:
run = True
while run:
pygame.time.delay(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and StarShip.rect.x > 25:
StarShip.rect.x -= vel
if keys[pygame.K_RIGHT] and StarShip.rect.x < 600:
StarShip.rect.x += vel
if keys[pygame.K_UP] and StarShip.rect.y > 25:
StarShip.rect.y -= vel
if keys[pygame.K_DOWN] and StarShip.rect.y < 600:
StarShip.rect.y += vel
win.fill((0,0,0))
all_sprites.update()
all_sprites.draw(win)
pygame.display.update()
pygame.quit()
This has basic movement of left/right/up/down. What I want to do is have my StarShip object constantly change between the variables starship_1, starship_2, starship_3 (which contain my 3 sprites of my starship), so it looks like the starship is moving.
My sprites look like this:
As you can see, it is the engine fire that is the difference between those sprites. How would I change between those 3 sprites every 1 second?
FYI: When the program is launched, this is what appears:
Thank you!
There are 2 parts to achieve this effect.
Create a function to change image of the sprite. ( Easy Task)
Call the above function periodically every x seconds. ( Intermediate Task)
Step 1.
You can achieve this by just setting/loading the self.image variable with the next image.
Step 2.
clock = pygame.time.Clock()
time_counter = 0
images = ['starship_1', 'starship_2', 'starship_3']
current_img_index = 0
while run:
# Your Code
time_counter = clock.tick()
# 1000 is milliseconds == 1 second. Change this as desired
if time_counter > 1000:
# Incrementing index to next image but reseting back to zero when it hits 3
current_img_index = (current_img_index + 1) % 3
set_img_function(current_img_index) # Function you make in step 1
# Reset the timer
time_counter = 0
A good approach will be to complete step 1 and then bind it to a button. Test if it works and then move on to step 2.
Some good read about the functions used in this code to fully understand them is here
Related
I'm building a pong game trying to get better at programming but Im having trouble moving the ball. When the move_right method is called the ellipse stretches to the right instead of moving to the right. I've tried putting the ball variable in the init method but that just makes it not move at all even though the variables should be changing on account of the move_right method. I have also tried setting the x and y positions as parameters in the Ball class,but that just stretches it also.
I don't understand why when I run the following code the ball I'm trying to move stretches to the right instead of moves to the right. Can someone explain why this is happening? I have tried everything I can think of but i can't get it to do what I want.
import pygame,sys
import random
class Ball:
def __init__(self):
self.size = 30
self.color = light_grey
self.x_pos = width/2 -15
self.y_pos = height/2 -15
self.speed = 1
#self.ball = pygame.Rect(self.x_pos, self.y_pos,self.size,self.size)
def draw_ball(self):
ball = pygame.Rect(self.x_pos, self.y_pos,self.size,self.size)
pygame.draw.ellipse(screen,self.color,ball)
def move_right(self):
self.x_pos += self.speed
class Player:
def __init__(self,x_pos,y_pos,width,height):
self.x_pos = x_pos
self.y_pos = y_pos
self.width = width
self.height = height
self.color = light_grey
def draw_player(self):
player = pygame.Rect(self.x_pos,self.y_pos,self.width,self.height)
pygame.draw.rect(screen,self.color,player)
class Main:
def __init__(self):
self.ball=Ball()
self.player=Player(width-20,height/2 -70,10,140)
self.opponent= Player(10,height/2-70,10,140)
def draw_elements(self):
self.ball.draw_ball()
self.player.draw_player()
self.opponent.draw_player()
def move_ball(self):
self.ball.move_right()
pygame.init()
size = 30
clock = pygame.time.Clock()
pygame.display.set_caption("Pong")
width = 1000
height = 600
screen = pygame.display.set_mode((width,height))
bg_color = pygame.Color('grey12')
light_grey = (200,200,200)
main = Main()
#ball = pygame.Rect(main.ball.x_pos, main.ball.y_pos,main.ball.size,main.ball.size)
#player = pygame.Rect(width-20,height/2 -70,10,140)
#opponent = pygame.Rect(10,height/2-70,10,140)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
#ball = pygame.Rect(main.ball.x_pos, main.ball.y_pos,main.ball.size,main.ball.size)
#pygame.draw.rect(screen,light_grey,player)
#pygame.draw.rect(screen,light_grey,opponent)
#pygame.draw.ellipse(screen,light_grey,ball)
main.draw_elements()
main.move_ball()
main.ball.x_pos += main.ball.speed
pygame.display.flip()
clock.tick(60)
You have to clear the display in every frame with pygame.Surface.fill:
while True:
# [...]
screen.fill(0) # <---
main.draw_elements()
main.move_ball()
main.ball.x_pos += main.ball.speed
pygame.display.flip()
# [...]
Everything that is drawn is drawn on the target surface. The entire scene is redraw in each frame. Therefore the display needs to be cleared at the begin of every frame in the application loop. The typical PyGame application loop has to:
handle the events by either pygame.event.pump() or pygame.event.get().
update the game states and positions of objects dependent on the input events and time (respectively frames)
clear the entire display or draw the background
draw the entire scene (blit all the objects)
update the display by either pygame.display.update() or pygame.display.flip()
This question already has an answer here:
How can I move the ball instead of leaving a trail all over the screen in pygame?
(1 answer)
Closed 1 year ago.
I am trying to make blocks fall to fall to the ground, but sadly the old sprites won't disappear. I have tried to move sprites with .move_ip, but then I get an error message, saying that my rect doesn't have attribute 'move_ip'. Also a little bit non-related, but how do I make the blocks stack on top of eachother? I suppose it's something to do with worldy?
import pygame
import random
pygame.init()
WHITE = (255, 255, 255)
worldx = 590
worldy = 770
screen = pygame.display.set_mode([worldx, worldy])
screen.fill([0,0,0])
gravity = 1
class Block1(pygame.sprite.Sprite):
def __init__(self,x,y):
super().__init__()
self.image = pygame.image.load("img_12.png")
self.rect = self.image.get_rect(topleft = (x,y))
self.x = x
self.y = y
self.speed_y = 0
def update(self):
self.speed_y += gravity
self.y += self.speed_y
self.rect.y = self.y
if self.rect.y >= worldy:
self.speed_y = 0
x = 0
y = 1
dt = 0
timer = 1
all_sprites_list = pygame.sprite.Group()
stopped_blocks = pygame.sprite.Group()
clock = pygame.time.Clock()
score = 0
block = Block1
mäng_töötab = True
while mäng_töötab:
for event in pygame.event.get():
if event.type == pygame.QUIT:
mäng_töötab = False
timer -= dt
if timer <= 0:
x = random.randrange(0, 18) * 32
all_sprites_list.add(block(x,y))
timer = 1
all_sprites_list.draw(screen)
all_sprites_list.update()
pygame.display.flip()
dt = clock.tick(60) / 1000
pygame.quit()
You have to clear the display in every frame:
while mäng_töötab:
for event in pygame.event.get():
if event.type == pygame.QUIT:
mäng_töötab = False
timer -= dt
if timer <= 0:
x = random.randrange(0, 18) * 32
all_sprites_list.add(block(x,y))
timer = 1
screen.fill(0) # <---
all_sprites_list.draw(screen)
all_sprites_list.update()
pygame.display.flip()
dt = clock.tick(60) / 1000
The typical PyGame application loop has to:
limit the frames per second to limit CPU usage with pygame.time.Clock.tick
handle the events by calling either pygame.event.pump() or pygame.event.get().
update the game states and positions of objects dependent on the input events and time (respectively frames)
clear the entire display or draw the background
draw the entire scene (blit all the objects)
update the display by calling either pygame.display.update() or pygame.display.flip()
I'm trying to create a simple game where u have to dodge enemies(asteroids), with pygame, but now I'm having trouble spawning them and I don't know if i should use lists or other things, or the enemy class(asteroidClass) is enough. The interval between spawning them is pretty simple I just don't how to deal with the spawn part (dealing with this for 3 days).
import pygame
import random
pygame.init()
#images
background = pygame.image.load('#path')
asteroid = pygame.image.load('#path')
display = pygame.display.set_mode((300,500))
FPS = 50
display.blit(background,(0,0))
#player everything is fine
#asteroid
class asteroidClass:
def __init__(self,asteroidX,asteroidY,asteroidVel):
self.x = asteroidX
self.y = asteroidY
self.vel = asteroidVel
def asteroid_advancing(self):
self.y += self.vel
display.blit(asteroid, (self.x, self.y))
def update():
pygame.display.update()
pygame.time.Clock().tick(FPS)
#variables
asteroidX = random.randint(0,250)
asteroidY, asteroidVel = 0, 2
asteroidOutClass = asteroidClass(asteroidX,asteroidY,asteroidVel)
#main loop
run = True
while run:
#should spawn multiple I don't know with what logic
#spawning in the same x
#saw in web they're using lists, maybe i should too?
#when i print i it just do 0123456 like it should, then restart to 0123456, is it wrong? kinda 100%
for i in range(7):
asteroidOutClass.asteroid_advancing() #everytime it's called should spawn and asteroid in random x?
update()
display.blit(background, (0, 0))
A few things:
Create the asteroids before the main loop
Use the init method to randomly set asteroid position and speed
Be sure to call an event method in the main loop to prevent freezing
Here's the updated code. I removed the background calls and used a circle for the asteroid.
import pygame
import random
pygame.init()
#images pygame.load() not in here
display = pygame.display.set_mode((300,500))
FPS = 50
#display.blit(background,(0,0))
#player everything is fine
#asteroid
class asteroidClass:
def __init__(self):
self.x = random.randrange(0, 300)
self.y = 0 # always top
self.velx = random.randrange(1,15)/3
self.vely = random.randrange(1,15)/3
def asteroid_advancing(self):
self.y += self.vely
self.x += self.velx
# wrap around screen
if self.x < 0: self.x=300
if self.x > 300: self.x=0
if self.y < 0: self.y=500
if self.y > 500: self.y=0
pygame.draw.circle(display, (200, 0, 0), (int(self.x), int(self.y)), 20) # draw asteroid
def update():
pygame.display.update()
pygame.time.Clock().tick(FPS)
#variables
# create list of 5 asteroids
roidlist = [asteroidClass() for x in range(7)]
#main loop
run = True
while run:
for event in pygame.event.get(): # required for OS events
if event.type == pygame.QUIT:
pygame.quit()
display.fill((0,0,0))
#should spawn multiple I don't know with what logic
#spawning in the same x
#saw in web they're using lists, maybe i should too?
#when i print i it just do 0123456 like it should, then restart to 0123456, is it wrong? kinda 100%
for r in roidlist:
r.asteroid_advancing() #everytime it's called should spawn and asteroid in random x?
update()
# display.blit(background, (0, 0))
This is my code so far, I can move and it places out a blip to pick up, I just need to know how to register that and move the blip to a new random spot!
I am very new to pygame and not 100% fluent in python either, but I'm decent. If there are pdf:s good for intermediate coders when it comes to python that would be wonderful!
import sys, pygame, os, math
from random import randint
pygame.init()
size = width, height = 800, 600
speed = [2, 2]
black = 1, 1, 1
screen = pygame.display.set_mode(size)
pygame.display.set_caption('Pick up the squares!')
UP='up'
DOWN='down'
LEFT='left'
RIGHT='right'
ball = pygame.image.load("ball.png")
ballrect = ball.get_rect()
ballx = 400
bally = 300
blip = pygame.image.load("blip.png")
bliprect = blip.get_rect()
blipx = randint(1,800)
blipy = randint(1,600)
background = pygame.Surface(screen.get_size())
background = background.convert()
background.fill((250, 250, 250))
clock = pygame.time.Clock()
while 1:
for event in pygame.event.get():
if event.type == pygame.QUIT: sys.exit()
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
ballx -= 5
if keys[pygame.K_RIGHT]:
ballx += 5
if keys[pygame.K_UP]:
bally -= 5
if keys[pygame.K_DOWN]:
bally +=5
screen.fill(black)
screen.blit(ball, (ballx,bally))
screen.blit(blip, (blipx, blipy))
pygame.display.update()
clock.tick(40)
Use the colliderect method of rectangles:
if ballrect.colliderect(bliprect):
print 'Do something here'
A basic collision detection works like this (assuming you are working with two rectangles):
def does_collide(rect1, rect2):
if rect1.x < rect2.x + rect2.width and rect1.x + rect1.width > rect2.x \
and rect1.y < rect2.y + rect2.height and rect1.height + rect1.y > rect2.y:
return True
return False
Fortunately Pygame is packed with such methods, so you should go with #MalikBrahimi's answer - using colliderect function call, which will do the math for you.
You can also try using pygame.sprite.spritecollide():
if pygame.sprite.spritecollide(a, b, False):
pass
#Do something
a here is your variable name for the class for one of the sprites in the collision. b here is the group that your second sprite will be. You can set the last one to True, which will remove the sprite from the group. Setting to False will keep it the way it is. Here is an example:
if pygame.sprite.spritecollide(my_ball, ballGroup, False):
Ball.speed[0] = -Ball.speed[0]
hit.play()
Ball.move()
The variable name for my sprite is my_ball. The group containing the other sprite(s) in this collision is ballGroup. I set the last one to False to keep all the sprites on the surface. If I set to True, the sprite from ballGroup will be removed from the group and screen. I hope this helps you!
I'm a newb when it comes to programming, but I'm learned a lot so far and I'm trying to make a very basic RPG.
I want to move my image object oPC with a mouse click. I've been able to accomplish this with the code I'm sharing below, however, no matter where I click on the screen the image takes the same amount of steps/time to get there. For instance, if I click a few inches away from the object it will gradually shift across the screen towards the target location just as fast as if I'd click right off the image.
I've been stuck trying to figure out a way to solve this issue for the last few days. Is there someway to use time for movement as opposed to the steps I've used?
Thanks!
import pygame, sys
import oPC
pygame.init()
WINDOWSIZE = (1000, 800)
BLACK = (0, 0, 0)
screen = pygame.display.set_mode((WINDOWSIZE))
pygame.display.set_caption("Game")
screen.fill(BLACK)
terrain = pygame.image.load("terrain.jpg").convert()
terrainRect = terrain.get_rect()
terrain = pygame.transform.scale(terrain, ((WINDOWSIZE)))
screen.blit(terrain, terrainRect)
oPC = oPC.Player()
oPC.draw(screen)
pygame.display.flip()
running = True
n_steps = 80
while running == True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
mlocX, mlocY = pygame.mouse.get_pos()
while mlocX != oPC.rect.x and mlocY != oPC.rect.y:
clock.tick(60)
oPC.update(mlocX, mlocY, n_steps)
if n_steps > 1:
screen.fill(BLACK)
screen.blit(terrain, terrainRect)
n_steps -= 1
oPC.draw(screen)
pygame.display.flip()
n_steps = 80
pygame.quit()
#sys.exit()
import pygame, sys
class Player(object):
def __init__(self):
self.image = pygame.image.load("tipping over s0000.bmp").convert()
self.rect = self.image.get_rect()
self.name = " "
self.stats = [0, 0, 0, 0, 0] #str, int, dex, health, mana
self.admin = False # False = Member / True = Administrator
def draw(self, screen):
self.image = pygame.transform.scale(self.image, (75, 75))
screen.blit(self.image, (self.rect.x, self.rect.y))
def update(self, mlocX, mlocY, n_steps):
self.rect.x += (mlocX - self.rect.x) / n_steps
self.rect.y += (mlocY - self.rect.y) / n_steps
Your design has a few flaws.
You are calling clock.tick() only on the MOUSEBUTTONDOWN event. It should be called on every frame.
Your bliting and display.update should also be done in the loop, not in the event queue.
The player update should also be called in the loop.
You scale your image each time you call draw. I think you may want to do that in the init method only. Since draw should be called repeatedly in the loop.
As for the step counter, I suggest to have a player state, and a step counter there. Something like this:
# for easier state access
def enum(*sequential, **named):
enums = dict(zip(sequential, range(len(sequential))), **named)
return type('Enum', (), enums)
player_states = enum('STATIONARY', 'MOVING')
STEPS = 30
def __init__(self):
self.state = player_states.STATIONARY
self.steps = 0
self.dest = None
def move(self,mlocX,mlocY):
if self.state != player_states.MOVING:
self.state = player_state.MOVING
self.steps = STEPS
self.dest = (mlocX,mlocY)
def update(self):
if self.steps != 0:
self.rect.x += (self.dest[0] - self.rect.x) / STEPS
self.rect.y += (self.dest[1] - self.rect.y) / STEPS
self.steps -= 1
else:
self.state = player_states.STATIONARY
As for you question with steps, you can use physics and the famous distance = velocity * time equation.
The clock.tick() method returns the number of ms passed since the last call to tick().
If you pass this to the update method, you can than change the moving equations to:
def update(self,delta):
self.rect.x += PLAYER_SPEED * direction * delta
Then pick PLAYER_SPEED to something that will suit you.