Please, My Pygame image doesn't move but when I change the code to print and I press the arrow keys, it does work. Any help would be greatly appreciated. I have imported OS, random and pygame in my original code as well as set width and height to 800, 600
class Alien(pygame.sprite.Sprite):
def __init__(self,speed, size):
self.x = random.randrange(0,width)
self.y = random.randrange(-200, -20)
self.size = random.randint(3, 20)
self.speed = speed
def move_down(self):
self.y += self.speed
def draw(self, screen, colour):
pygame.draw.circle(screen, colour, (self.x, self.y), self.size)
def check_landed(self):
if self.y > height:
self.x = random.randrange(0,width)
self.y = random.randrange(-400, -20)
self.speed = random.randrange(1,4)
class Actor(Alien):
def __init__(self, filename):
self.score = 0
self.image = pygame.image.load(os.path.join("../images", filename)).convert_alpha()
self.rect = self.image.get_rect()
def move(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] or keys[pygame.K_a]:
self.rect.centerx -= 10
print("let")
elif keys[pygame.K_RIGHT] or keys[pygame.K_d]:
self.rect.centerx += 10
def draw(self, screen):
screen.blit(self.image, (width/2, height - 40))
In the main game implementation, i have
enemies = []
actor = Actor("ship.jpg")
for i in range (20):
aliens = Alien(2, 4)
enemies.append(aliens)
done = False
while done == False:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
screen.fill(white)
actor.x = 500
actor.y = 500
actor.draw(screen)
actor.move()
for i in range(len(enemies)):
enemies[i].draw(screen, red)
enemies[i].move_down()
enemies[i].check_landed()
pygame.display.flip()
clock.tick(30)
You've overriden the Sprite.draw method.
The normal method, as the docs say, "uses the Sprite.image attribute for the source surface, and Sprite.rect for the position".
In your Alien subclass, you aren't using that, but you're instead using the x and y attributes, which might also work, because that's what you're modifying everywhere:
def draw(self, screen, colour):
pygame.draw.circle(screen, colour, (self.x, self.y), self.size)
But your Actor subclass doesn't use either, it just blits to screen at a fixed location of (width/2, height - 40):
def draw(self, screen):
screen.blit(self.image, (width/2, height - 40))
So, no matter what attributes you change, you're always going to draw at the same position.
I'm not sure why you're overriding draw for either of these classes. I'm also not sure why Alien inherits from Sprite but then uses x and y attributes while ignoring rect, while Actor inherits from Alien but then ignores x and y while using rect again. This is almost certainly going to confuse you. But if you want the smallest possible change:
screen.blit(self.image, self.rect)
Of course you'll also need to set up self.rect to the initial starting position at startup, and prevent the user from going all the way off the edge of the screen, and so on. And, again, it would be better to clean this up to not be confusing in the first place. But this should get you started.
Related
This question already has answers here:
How do I rotate an image around its center using Pygame?
(6 answers)
How to rotate an image(player) to the mouse direction?
(2 answers)
How to turn the sprite in pygame while moving with the keys
(1 answer)
Closed 1 year ago.
I am creating a racecar game and I have decided to rewrite it because I realised I was initially writing it in a very inefficient way. I initially used image an just changed their location around the screen but I am now trying to learn how to create an object and I am running into a problem which I had initially but I don't know how to fix within a class.
What happens is when I turn the image it distorts a lot and ends up slowing down because I think pygame can't handle it maybe. In my old code, I created a copy of the original image and rotated the copy of the image which fixed the distortion problem. I now don't know how to do the same within a class.
main.py
import pygame, random
#Let's import the Car Class
from Car import Car
pygame.init()
SCREENWIDTH=800
SCREENHEIGHT=600
size = (SCREENWIDTH, SCREENHEIGHT)
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Car Racing")
#This will be a list that will contain all the sprites we intend to use in our game.
all_sprites_list = pygame.sprite.Group()
playerCar = Car(60, 80, 70)
playerCar.rect.x = 160
playerCar.rect.y = 100
# Add the car to the list of objects
all_sprites_list.add(playerCar)
#Allowing the user to close the window...
carryOn = True
clock=pygame.time.Clock()
while carryOn:
for event in pygame.event.get():
if event.type==pygame.QUIT:
carryOn=False
elif event.type==pygame.KEYDOWN:
if event.key==pygame.K_x:
playerCar.moveRight(10)
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
#playerCar.moveLeft(5)
playerCar.rot_center(2)
if keys[pygame.K_RIGHT]:
#playerCar.moveRight(5)
playerCar.rot_center(-2)
if keys[pygame.K_UP]:
playerCar.moveUp(5)
if keys[pygame.K_DOWN]:
playerCar.moveDown(5)
all_sprites_list.update()
#Drawing on Screen
screen.fill("white")
#Now let's draw all the sprites in one go. (For now we only have 1 sprite!)
all_sprites_list.draw(screen)
#Refresh Screen
pygame.display.flip()
#Number of frames per secong e.g. 60
clock.tick(25)
pygame.quit()
Car.py
import pygame
WHITE = (255, 255, 255)
class Car(pygame.sprite.Sprite):
#This class represents a car. It derives from the "Sprite" class in Pygame.
def __init__(self, width, height, speed):
# Call the parent class (Sprite) constructor
super().__init__()
# Instead we could load a proper picture of a car...
self.image = pygame.image.load("car.png").convert_alpha()
self.image = pygame.transform.rotate(self.image, 90)
#Initialise attributes of the car.
self.width=width
self.height=height
rect_surf = pygame.Surface((width, height), pygame.SRCALPHA)
rect_surf.fill((0, 0, 0, 0))
self.rect_surf = rect_surf
# Draw the car (a rectangle!)
pygame.draw.rect(self.image, (0, 0, 0, 0), [0, 0, self.width, self.height])
# Fetch the rectangle object that has the dimensions of the image.
self.rect = self.image.get_rect()
def moveRight(self, pixels):
self.rect.x += pixels
def moveLeft(self, pixels):
self.rect.x -= pixels
def moveUp(self, pixels):
self.rect.y -= pixels
def moveDown(self, pixels):
self.rect.y += pixels
def changeSpeed(self, speed):
self.speed = speed
def rot_center(self, angle):
rotated_image = pygame.transform.rotate(self.image, angle)
new_rect = rotated_image.get_rect(center = self.image.get_rect(center = (self.rect.x + (self.rect.width / 2), self.rect.y + (self.rect.height / 2))).center)
self.image = rotated_image
self.rect = new_rect
Any help on how to slvoe this would be much appreciated. I had questions about how to initially turn the image on another question, How do I make an object in pygame rotate.
I also don't know how to apply the rect correctly so that it is the size of the image and doesn't create a transparent spot on the image.
Any help would be much appreciated thanks. :):)
You should keep original image in separated variable with angle
self.original_image = pygame.image.load("car.png").convert_alpha()
self.angle = 90
and use it to generate rotated image at start
self.image = pygame.transform.rotate(self.original_image, self.angle)
and later you should increase self.angle and again create rotated image using original image
def rot_center(self, angle):
self.angle += angle
rotated_image = pygame.transform.rotate(self.original_image, self.angle)
new_rect = rotated_image.get_rect(center=self.rect.center)
self.image = rotated_image
self.rect = new_rect
or even shorter
def rot_center(self, angle):
self.angle += angle
self.image = pygame.transform.rotate(self.original_image, self.angle)
self.rect = self.image.get_rect(center=self.rect.center)
Full working code
I used red rectangle instead image so everyone can run it and test it.
I used pygame.math.Vector2(pixels, 0).rotate(self.angle) to move up/down using current angle - car direction - instead of moving to top/bottom of the screen.
import pygame
import random
# --- constants ---
WHITE = (255, 255, 255)
RED = (255, 0, 0)
SCREENWIDTH = 800
SCREENHEIGHT = 600
size = (SCREENWIDTH, SCREENHEIGHT)
# --- classes ---
class Car(pygame.sprite.Sprite):
#This class represents a car. It derives from the "Sprite" class in Pygame.
def __init__(self, width, height, speed):
# Call the parent class (Sprite) constructor
super().__init__()
#Initialise attributes of the car.
self.width = width
self.height = height
# Instead we could load a proper picture of a car...
#self.original_image = pygame.image.load("car.png").convert_alpha()
self.original_image = pygame.surface.Surface((width, height)).convert_alpha()
self.original_image.fill(RED)
self.angle = 90
self.image = pygame.transform.rotate(self.original_image, self.angle)
# Fetch the rectangle object that has the dimensions of the image.
self.rect = self.image.get_rect()
def moveRight(self, pixels):
self.rect.x += pixels
def moveLeft(self, pixels):
self.rect.x -= pixels
def moveUp(self, pixels):
#self.rect.y += pixels
x, y = pygame.math.Vector2(pixels, 0).rotate(self.angle)
self.rect.x += x
self.rect.y -= y
def moveDown(self, pixels):
#self.rect.y += pixels
x, y = pygame.math.Vector2(pixels, 0).rotate(self.angle)
self.rect.x -= x
self.rect.y += y
def changeSpeed(self, speed):
self.speed = speed
def rot_center(self, angle):
self.angle += angle
self.image = pygame.transform.rotate(self.original_image, self.angle)
self.rect = self.image.get_rect(center=self.rect.center)
# --- main ---
pygame.init()
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Car Racing")
#This will be a list that will contain all the sprites we intend to use in our game.
all_sprites_list = pygame.sprite.Group()
playerCar = Car(60, 80, 70)
playerCar.rect.x = 160
playerCar.rect.y = 100
# Add the car to the list of objects
all_sprites_list.add(playerCar)
#Allowing the user to close the window...
carryOn = True
clock = pygame.time.Clock()
while carryOn:
for event in pygame.event.get():
if event.type == pygame.QUIT:
carryOn=False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_x:
playerCar.moveRight(10)
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
#playerCar.moveLeft(5)
playerCar.rot_center(2)
if keys[pygame.K_RIGHT]:
#playerCar.moveRight(5)
playerCar.rot_center(-2)
if keys[pygame.K_UP]:
playerCar.moveUp(5)
if keys[pygame.K_DOWN]:
playerCar.moveDown(5)
all_sprites_list.update()
#Drawing on Screen
screen.fill("white")
#Now let's draw all the sprites in one go. (For now we only have 1 sprite!)
all_sprites_list.draw(screen)
#Refresh Screen
pygame.display.flip()
#Number of frames per secong e.g. 60
clock.tick(25)
pygame.quit()
I am trying to make a spaceship game, however, when I added the part which allows you to fire bullets, the game crashed immediately crashed when I run the game.
Here is my current code (Some parts omitted or replaced by --snip--)
class Spaceship(pygame.sprite.Sprite):
def __init__(self, s, x, y):
pygame.sprite.Sprite.__init__(self)
self.screen = s
self.x, self.y = x, y
self.image = pygame.image.load("C:/eqodqfe/spaceship.png")
self.image = pygame.transform.scale(self.image, (175, 175))
self.rect = self.image.get_rect()
self.rect.center = (self.x, self.y)
def update(self):
self.rect.center = (self.x, self.y)
class Bullet(pygame.sprite.Sprite):
def __init__(self, s, x, y):
pygame.sprite.Sprite.__init__(self)
self.screen = s
self.x, self.y = x, y
self.image = pygame.image.load("C:/eqodqfe/bullet.png")
self.image = pygame.transform.scale(self.image, (100, 100))
self.rect = self.image.get_rect()
self.rect.center = (self.x, self.y)
def update(self):
self.rect.center = (self.x, self.y)
def fire(self):
while self.y <= 490:
self.y -= 5
spaceship = Spaceship(screen, 400, 400)
bullet = Bullet(screen, 0, 0)
running = True
while running:
--snip--
key = pygame.key.get_pressed()
if key[pygame.K_a]:
spaceship.x -= 0.5
elif key[pygame.K_d]:
spaceship.x += 0.5
--snip--
spaceship.update()
bullet.update()
screen.fill((255, 255, 255))
screen.blit(spaceship.image, spaceship.rect)
screen.blit(bullet.image, bullet.rect)
bullet.x = spaceship.x
bullet.y = spaceship.y-20
if pygame.mouse.get_pressed():
bullet.fire()
pygame.display.update()
Does anybody know what went wrong?
What do you expect by
while self.y <= 490:
self.y -= 5
This is an infinite loop.
To make a bullet you have to create an instance of Bullet when mouse button is pressed. However you have to use the MOSUEBUTTONDOWN event. pygame.mouse.get_pressed() returns a list of Boolean values that represent the state (True or False) of all mouse buttons. The state of a button is True as long as a button is held down. The MOUSEBUTTONDOWN event occurs once when you click the mouse button and the MOUSEBUTTONUP event occurs once when the mouse button is released.
Move an kill the bullet in update. update is continuously invoked in the application loop:
class Bullet(pygame.sprite.Sprite):
# [...]
def update(self):
self.y -= 5
self.rect.center = (self.x, self.y)
if self.y < 0:
self.kill()
Create a Bullet when the MOSUEBUTTONDOWN occurs:
bullets = pygame.sprite.Group()
running = True
while running:
# [...]
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == MOSUEBUTTONDOWN
bullet = Bullet(screen, spaceship.x, spaceship.y-20)
bullets.add(bullet)
bullets.update()
# [...]
bullets.draw()
pygame.display.update()
See also:
How can i shoot a bullet with space bar?
How do I stop more than 1 bullet firing at once?
Pygame: problems with shooting in Space Invaders
I have an ice object my question is how can I make it shoot projectiles or make projectiles come out of it 1 by 1
for example: how could I make it shoot projectiles from its tip and it keeps falling tell the end of the screen
this is my ice object class
smallice = pygame.image.load("fals.png")
class smallice:
def __init__(self,x,y,height,width,color):
self.x = x
self.y = y
self.height = height
self.width = width
self.color = color
self.smallice = pygame.image.load("fals.png")
self.smallice = pygame.transform.scale(self.smallice,(self.smallice.get_width()-2,self.smallice.get_height()-2))
self.rect = pygame.Rect(x,y,height,width)
# the hitbox our projectiles will be colliding with
self.hitbox = (self.x + 0, self.y + 1, 10, 72)
def draw(self):
self.rect.topleft = (self.x,self.y)
player_rect = self.smallice.get_rect(center = self.rect.center)
player_rect.centerx += 0 # 10 is just an example
player_rect.centery += 70 # 15 is just an example
window.blit(self.smallice, player_rect)
# define the small ices
black = (0,0,0)
smallice1 = smallice(550,215,20,20,black)
small = [smallice1]
You can start by making a Bullet class, for example, something like this:
class Bullet():
def __init__(self, x, y):
self.x = x
self.y = y
self.v = 5
def fire_bullet(self,window):
pg.draw.circle(window, (255,0,0), (self.x,self.y), 5)
ice.fire = False
def collision_box(self):
collide = pg.Rect(self.x-offset_x, self.y-offset_y, size_x, size_y)
return collide
Bullets will be simple circles, self.v stands for velocity. And collision_box will be use to detect the possible collision.
Now in your main_loop you need to detect when the ice object "wants to fire", simple example:
if keys[pg.K_SPACE]:
if len(bullets) < 2:
ice.fire = True
And:
if ice.fire:
bullet = Bullet(round(y.x+offset), round(player.y+offset))
bullets.append(bullet)
Now len(bullets) appeared, bullets is a list in which you will add a bullet object when the bullet is fired and remove it when the collision is detected or bullet goes outside of the chosen area. With this you can control the number of the bullets on the screen and also loop through it and call collision() method to see if one (or more) of them had collided, and keep track if it is still on the screen.
And if you want to shoot randomly then here is one basic idea:
if round(pg.time.get_ticks()/1000) % 3 == 0:
ice.fire = True
The last part, going through the list, and some simple logic:
if bullets != []: #Firing bullets
for bullet in bullets:
bullet.fire_bullet(window)
if bullet.y < screen_y: #Bullet movement
bullet.x += bullet.v
else:
bullets.pop(bullets.index(bullet)) #Poping bullets from list
if bullet.collision_box().colliderect(other_rect_object): #Bullet collision with other object
bullets.pop(bullets.index(bullet))
I am assuming (from the screenshot) that you want to shoot bullets down and only (vertically) hence the bullet.y += bullet.v and no direction flag.
This is simple code and idea, and ofcourse there is a lot room for improvement, but the approach works.
I hope my answer will help you.
so I have loaded the images into pygame:
Explosion = [pygame.image.load('Explosion1.png'), pygame.image.load('Explosion2.png'), pygame.image.load('Explosion3.png'), pygame.image.load('Explosion4.png'), pygame.image.load('Explosion5.png'), pygame.image.load('Explosion6.png'), pygame.image.load('Explosion7.png'), pygame.image.load('Explosion8.png'), pygame.image.load('Explosion9.png'), pygame.image.load('Explosion10.png')]
and I would like, when the bullet, which is a separate class, makes a collision with the missile, it plays this animation at the position where both the bullet and the enemy collide, I'm not sure how I go around doing this?
Collision Script (In the main loop):
hits = pygame.sprite.groupcollide(enemies, bullets, True, True)
Bullet Class:
class Bullet (pygame.sprite.Sprite):
def __init__ (self, x, y):
super (Bullet, self).__init__()
self.surf = pygame.image.load("Bullet.png").convert()
self.surf.set_colorkey((255,255,255), RLEACCEL)
self.rect = self.surf.get_rect()
self.rect.bottom = y
self.rect.centerx = x
self.speedx = bullet_speed
def update(self):
self.rect.x += self.speedx
if self.rect.left > SCREEN_WIDTH:
self.kill()
Enemy Class:
class Enemy(pygame.sprite.Sprite):
def __init__(self):
super(Enemy, self).__init__()
self.surf = pygame.image.load("Missiles.png").convert()
self.surf.set_colorkey((255,255,255), RLEACCEL)
self.rect = self.surf.get_rect(
center=(
random.randint(SCREEN_WIDTH + 20, SCREEN_WIDTH + 100),
random.randint(0, SCREEN_HEIGHT),
)
)
self.speed = random.randint(Enemy_SPEED_Min, Enemy_SPEED_Max)
def update(self):
self.rect.move_ip(-self.speed, 0)
if self.rect.right < 0:
self.kill()
all of the code is here https://pastebin.com/CG2C6Bkc if you need it!
thank you in advance!
Do not destroy the enemies, when it collides with an bullet. Iterate through the enemies and which are returned in hits and start the explosion animation instead of killing the enemies:
hits = pygame.sprite.groupcollide(enemies, bullets, True, True)
hits = pygame.sprite.groupcollide(enemies, bullets, False, True)
for enemy in hits:
enemy.start_animation()
Add the attributes animation_count, animation_frames and animation to th class Enemy. When the explosion animation is started then animation is set True. animation_frames controls the speed of the animation. If the animation is started, then the enemy stops to move and the Explosion image is shown instead of the enemy. After the last image of the animation is shown, the enemy is killed:
class Enemy(pygame.sprite.Sprite):
def __init__(self):
super(Enemy, self).__init__()
self.surf = pygame.image.load("Missiles.png").convert()
self.surf.set_colorkey((255,255,255), RLEACCEL)
self.rect = self.surf.get_rect(
center=(
random.randint(SCREEN_WIDTH + 20, SCREEN_WIDTH + 100),
random.randint(0, SCREEN_HEIGHT),
)
)
self.speed = random.randint(Enemy_SPEED_Min, Enemy_SPEED_Max)
self.animation_count = 0
self.animation_frames = 10
self.animation = False
def start_animation(self):
self.animation = True
def update(self):
if self.animation:
image_index = self.animation_count // self.animation_frames
self.animation_count += 1
if image_index < len(Explosion):
self.surf = Explosion[image_index]
self.rect = self.surf.get_rect(center = self.rect.center)
else:
self.kill()
self.rect.move_ip(-self.speed, 0)
if self.rect.right < 0:
self.kill()
Well i dont want to do it for you, But i will say one way you could do it, then you can go and try to do it, and if you run into a problem/error, you can update the question and we can help you.
I would create another class called Explosion and and give it the images, when when you draw it, draw the first image, then change to the second, then draw that one, then change...
Then after it finishes, destroy the object.
So, create the class, create a spritegroup, when the bullet collides, create a new instance of the class, giving it the x and y co ordinates of the collision, update it every frame, and it will draw all the images then destroy itself
also, an easier way to get all of your images instead of typing them all out is
Explosion_imgs = [pygame.image.load("explosion" + str(x) + ".png") for x in range(1,11,1)]
For a while now I've been trying to teach myself how to use sprites in pygame and right now I am stuck on collision detection.
The specific place I am having trouble with in my code is the commented section labeled "error here" and that's the code that keeps giving me the "TypeError: Argument must be rect style object" error and the goal of that specific code is to detect collision.
The goal of this code is to print a message in the shell whenever the player block enters the non-player block and as I said earlier I have been having trouble getting that to happen.
from pygame.locals import *
import pygame
pygame.init()
SIZE = WIDTH, HEIGHT = 500, 700
screen = pygame.display.set_mode(SIZE)
plr_g = pygame.sprite.Group()
h_box = pygame.sprite.Group()
BLUE = (0, 206, 209)
GREEN = (0, 255, 0)
class Player(pygame.sprite.Sprite):
def __init__(self, width, height):
pygame.sprite.Sprite.__init__(self, plr_g)
self.image = pygame.Surface([width, height])
self.image.fill(BLUE)
self.rect = self.image.get_rect()
self.rect.center = (WIDTH / 2, HEIGHT / 2)
#self.rect = (400, 200)
def x_pos(self):
self.rect.x
def y_pos(self):
self.rect.y
def move_l(self, pixels):
self.rect.x -= pixels
def move_r(self, pixels):
self.rect.x += pixels
def move_u(self, pixels):
self.rect.y -= pixels
def move_d(self, pixels):
self.rect.y += pixels
class Hitbox(pygame.sprite.Sprite):
def __init__(self, bx, by):
pygame.sprite.Sprite.__init__(self, h_box)
self.image = pygame.Surface([100, 100])
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect = (bx, by)
hitbox = Hitbox(300, 300)
hitbox = Hitbox(100, 500)
player = Player(50, 50)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type==pygame.KEYDOWN:
if event.key==pygame.K_LEFT:
player.move_l(10)
if event.key==pygame.K_RIGHT:
player.move_r(10)
if event.key==pygame.K_UP:
player.move_u(10)
if event.key==pygame.K_DOWN:
player.move_d(10)
#error here
if plr_g.colliderect(h_box):
print("collide")
#----------------
plr_g.update()
h_box.update()
screen.fill((50, 50, 50))
h_box.draw(screen)
plr_g.draw(screen)
pygame.display.flip()
h_box is a sprite group, not a sprite and definitely not a rect. The collide_rect function of sprites must be called on individual sprites. A possible solution is something like the following, iterating over all the sprites in h_box:
if any([plr_g.colliderect(sp) for sp in h_box]):
print("collide")
block_hit_list = plr_g.colliderect(h_box)
for block in block_hit_list:
print ("collide")
I basically loop through the list to see if there are any collisions between the sprites in plr_g and the sprites in hitbox.