Related
My game is a top down shooter. When the player is stationary and is shooting in any direction, the bullet goes in the same direction as the mouse. However, when I move diagonally whilst shooting the bullet is no longer in the direction of the mouse position. So basically it works when the player is stationary, but not when I'm moving.
Edit: I will keep trying to fix this but here is a video to better understand the issue https://imgur.com/a/8QRr1PO
Here is the code:
import pygame
from sys import exit
import math
pygame.init()
# window and text
WIDTH = 1280
HEIGHT = 720
FPS = 60
screen = pygame.display.set_mode((WIDTH,HEIGHT))
pygame.display.set_caption('Shooting problem demo')
game_font = pygame.font.Font('freesansbold.ttf', 50)
clock = pygame.time.Clock()
# loads imgs
background = pygame.image.load("background/gamemap.png").convert()
plain_bg = pygame.image.load("background/plain_bg.png").convert()
bullet_img = pygame.image.load("bullets/bluebullet.png").convert_alpha()
class Player(pygame.sprite.Sprite):
def __init__(self, pos):
super().__init__()
self.image = pygame.image.load("handgun/move/survivor-move_handgun_0.png").convert_alpha()
self.image = pygame.transform.rotozoom(self.image, 0, 0.35)
self.base_player_image = self.image
self.pos = pos
self.base_player_rect = self.base_player_image.get_rect(center = pos)
self.rect = self.base_player_rect.copy()
self.player_speed = 10 # was 4
self.shoot = False
self.shoot_cooldown = 0
def player_turning(self):
self.mouse_coords = pygame.mouse.get_pos()
self.x_change_mouse_player = (self.mouse_coords[0] - (WIDTH // 2))
self.y_change_mouse_player = (self.mouse_coords[1] - (HEIGHT // 2))
self.angle = int(math.degrees(math.atan2(self.y_change_mouse_player, self.x_change_mouse_player)))
self.angle = (self.angle) % 360
self.image = pygame.transform.rotate(self.base_player_image, -self.angle)
self.rect = self.image.get_rect(center=self.base_player_rect.center)
def player_input(self):
self.velocity_x = 0
self.velocity_y = 0
keys = pygame.key.get_pressed()
if keys[pygame.K_w]:
self.velocity_y = -self.player_speed
if keys[pygame.K_s]:
self.velocity_y = self.player_speed
if keys[pygame.K_d]:
self.velocity_x = self.player_speed
if keys[pygame.K_a]:
self.velocity_x = -self.player_speed
if self.velocity_x != 0 and self.velocity_y != 0: # moving diagonally
self.velocity_x /= math.sqrt(2)
self.velocity_y /= math.sqrt(2)
if keys[pygame.K_SPACE]:
self.shoot = True
self.is_shooting()
else:
self.shoot = False
if event.type == pygame.KEYUP:
if event.key == pygame.K_SPACE:
self.shoot = False
def move(self):
self.base_player_rect.centerx += self.velocity_x
self.base_player_rect.centery += self.velocity_y
self.rect.center = self.base_player_rect.center
def is_shooting(self):
if self.shoot_cooldown == 0 and self.shoot:
self.bullet = Bullet(self.base_player_rect.centerx, self.base_player_rect.centery, self.angle)
self.shoot_cooldown = 20
bullet_group.add(self.bullet)
all_sprites_group.add(self.bullet)
def update(self):
self.player_turning()
self.player_input()
self.move()
if self.shoot_cooldown > 0:
self.shoot_cooldown -= 1
class Bullet(pygame.sprite.Sprite):
def __init__(self, x, y, angle):
super().__init__()
self.image = bullet_img
self.image = pygame.transform.rotozoom(self.image, 0, 0.1)
self.image.set_colorkey((0,0,0))
self.rect = self.image.get_rect()
self.rect.center = (x, y)
self.x = x
self.y = y
self.speed = 10
self.angle = angle
self.x_vel = math.cos(self.angle * (2*math.pi/360)) * self.speed
self.y_vel = math.sin(self.angle * (2*math.pi/360)) * self.speed
self.bullet_lifetime = 750
self.spawn_time = pygame.time.get_ticks()
def bullet_movement(self):
self.x += self.x_vel
self.y += self.y_vel
self.rect.x = int(self.x)
self.rect.y = int(self.y)
if pygame.time.get_ticks() - self.spawn_time > self.bullet_lifetime:
self.kill()
def update(self):
self.bullet_movement()
class Camera(pygame.sprite.Group):
def __init__(self):
super().__init__()
self.offset = pygame.math.Vector2()
self.floor_rect = background.get_rect(topleft = (0,0))
def custom_draw(self):
self.offset.x = player.rect.centerx - (WIDTH // 2)
self.offset.y = player.rect.centery - (HEIGHT // 2)
#draw the floor
floor_offset_pos = self.floor_rect.topleft - self.offset
screen.blit(background, floor_offset_pos)
for sprite in all_sprites_group:
offset_pos = sprite.rect.topleft - self.offset
screen.blit(sprite.image, offset_pos)
# Groups
all_sprites_group = pygame.sprite.Group()
player = Player((900,900))
all_sprites_group.add(player)
bullet_group = pygame.sprite.Group()
camera = Camera()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
screen.blit(plain_bg, (0,0))
camera.custom_draw()
all_sprites_group.update()
pygame.display.update()
clock.tick(FPS)
Actually, there is no problem at all. It is just an optical illusion. The projectile does not move relative to the player, but relative to the camera. The player is always in the center of the screen, because the player doesn't move, but the camera does. When the camera moves, all objects move with the camera.
For example, if you shoot a bullet to the right and move the player up, it will look like the bullet is moving diagonally to the right and down. To the right because it changes position, to the right and down because the player moves upwards.
To illustrate this, I reduced the speed of the player (self.player_speed = 2) and the speed of the bullet (self.speed = 4) and drew the scene on a checkered background:
if event.type == pygame.KEYUP: only makes sens in the event loop, but not in player_input. Shooting only one bullet at once just needs another condition (and not self.shoot):
if keys[pygame.K_SPACE] and not self.shoot:
self.shoot = True
self.is_shooting()
else:
self.shoot = False
About 500 lines of code, so to make it easier to read I believe the bug comes from _update_asteroids(self) function. Simply put, when the user is in the paused game state and must press play, if the player decides to wait lets say a minute. A minutes worth of asteroids will spawn at the exact same time. I believe this is because the pygame.time.get_ticks() function continues to gain ticks while game is paused. Is there any way to reset the ticks or make it so that 100s of asteroids do not spawn when the user decides to wait before pressing play button?
import pygame
import sys
from pygame.sprite import Sprite
import random
from time import sleep
import pygame.font
class Game(Sprite):
""" a class the creates a window with a blue screen """
def __init__(self):
pygame.init()
super().__init__()
#--------------------------------------------------------------------------------------------
#screen size, color, caption
self.screen = pygame.display.set_mode((1200,800)) #create attribute to hold display settings
self.bg_color = (0,0,255) #create attribute to hold RGB color (blue)
pygame.display.set_caption("Blue Screen")
#--------------------------------------------------------------------------------------------
#--------------------------------------------------------------------------------------------
#tank drawing
self.screen_rect = self.screen.get_rect() #get the screen rect dim
self.image = pygame.image.load('images/tank.gif') #load the image from directory
self.rect = self.image.get_rect() #get the image rect dim
self.rect.center = self.screen_rect.center #store the screens center x/y coord
self.x = float(self.rect.x)
self.y = float(self.rect.y)
#tank movement
self.tank_moving_left = False
self.tank_moving_right = False
self.tank_moving_up = False
self.tank_moving_down = False
self.tank_speed = 0.5 #tank pixels
self.direction_right = self.image #holds right image
self.direction_left = pygame.transform.flip(self.image, True, False) #holds left image
#--------------------------------------------------------------------------------------------
#--------------------------------------------------------------------------------------------
#UI
self.health = 100
self.visable = True
self.sb = Scoreboard(self)
#--------------------------------------------------------------------------------------------
#--------------------------------------------------------------------------------------------
#bullet
self.bullets = pygame.sprite.Group()
self.current_direction = self.direction_right
#--------------------------------------------------------------------------------------------
#--------------------------------------------------------------------------------------------
#asteroids
self.asteroids = pygame.sprite.Group()
self.next_object_time = 0
self.time_interval = 250
#self._create_asteroids()
#--------------------------------------------------------------------------------------------
#--------------------------------------------------------------------------------------------
#button
self.game_active = False
self.play_button = Button(self, "Play")
#--------------------------------------------------------------------------------------------
def _create_asteroids(self):
""" create the asteroid shower """
#create a asteroid
number_of_aliens = 1
for asteroid_num in range(number_of_aliens):
self._create_asteroid()
def _create_asteroid(self):
asteroid = Asteroid(self)
if asteroid.x <= 0:
asteroid.direction = 1
elif asteroid.x >= 1160:
asteroid.direction = -1
self.asteroids.add(asteroid)
def move(self):
""" move tnak tank_speed based on direction of movement (key pressed)
also detect collision """
if self.tank_moving_right and self.rect.right < self.screen_rect.right:
self.x += self.tank_speed
self.rect.x = self.x
if self.tank_moving_left and self.rect.left > self.screen_rect.left:
self.x -= self.tank_speed
self.rect.x = self.x
if self.tank_moving_down and self.rect.bottom < self.screen_rect.bottom:
self.y += self.tank_speed
self.rect.y = self.y
if self.tank_moving_up and self.rect.top > self.screen_rect.top:
self.y -= self.tank_speed
self.rect.y = self.y
def draw_healthbar(self):
pygame.draw.rect(self.screen, (255,0,0), (self.rect.x, self.rect.y - 20, 100, 10))
pygame.draw.rect(self.screen, (0,255,0), (self.rect.x, self.rect.y - 20, 100 - (100 - self.health), 10))
def blitme(self):
""" draw the image of the tank """
self.screen.blit(self.image, self.rect)
def tank_asteroid_collision(self):
#get rid of asteroids that collide with tank
for asteroid in self.asteroids.copy():
if pygame.Rect.colliderect(self.rect, asteroid.rect):
if self.health > 25:
self.health -= 25
self.asteroids.remove(asteroid)
print(self.health)
else:
self._tank_death()
self.sb.reset_score()
self.game_active = False
pygame.mouse.set_visible(True)
def _update_screen(self):
""" update screen """
self.screen.fill(self.bg_color)
self.blitme()
for bullet in self.bullets.sprites():
self.bullets.draw(self.screen)
collisions = pygame.sprite.groupcollide(self.bullets, self.asteroids, True, True)
if collisions:
for asteroids in collisions.values():
self.sb.score += 100 * len(asteroids)
self.sb.prep_score()
self.sb.check_high_score()
#draw healthbar if game active
if self.game_active:
self.draw_healthbar()
self.asteroids.draw(self.screen)
#draw the play button and other buttons if game is paused
if not self.game_active:
self.play_button.draw_button()
self.sb.show_score()
pygame.display.flip()
def _tank_death(self):
sleep(0.5)
self.bullets.empty()
self.asteroids.empty()
self.center_ship()
self.health = 100
def _check_KEYDOWN(self, event):
""" when key is press either quit, or move direction of arrow pressed and flip image """
if event.key == pygame.K_q:
sys.exit()
elif event.key == pygame.K_RIGHT:
self.tank_moving_right = True
self.image = self.direction_right
self.current_direction = self.direction_right
elif event.key == pygame.K_LEFT:
self.tank_moving_left = True
self.image = self.direction_left
self.current_direction = self.direction_left
elif event.key == pygame.K_UP:
self.tank_moving_up = True
elif event.key == pygame.K_DOWN:
self.tank_moving_down = True
elif event.key == pygame.K_SPACE:
self._fire_bullet()
def _check_KEYUP(self, event):
""" when key is let go stop moving """
if event.key == pygame.K_RIGHT:
self.tank_moving_right = False
elif event.key == pygame.K_LEFT:
self.tank_moving_left = False
elif event.key == pygame.K_UP:
self.tank_moving_up = False
elif event.key == pygame.K_DOWN:
self.tank_moving_down = False
def _fire_bullet(self):
""" create a bullet and add it to the bullets group """
if self.current_direction == self.direction_left:
#create new bullet and set bullet path
new_bullet = Bullet(self)
new_bullet.bullet_shootRight = False
new_bullet.bullet_shootLeft = True
#change direction of bullet starting point
new_bullet.x -= 100
#add bullet to sprite list
self.bullets.add(new_bullet)
elif self.current_direction == self.direction_right:
#create new bullet and set bullet path
new_bullet = Bullet(self)
new_bullet.bullet_shootRight = True
new_bullet.bullet_shootLeft = False
#add bullet to sprite list
self.bullets.add(new_bullet)
def center_ship(self):
""" centers the ship in middle of screen """
self.rect.center = self.screen_rect.center
self.x = float(self.rect.x)
self.y = float(self.rect.y)
def _update_tank(self):
""" move tank or quit game based on keypress """
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
self._check_KEYDOWN(event)
elif event.type == pygame.KEYUP:
self._check_KEYUP(event)
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_pos = pygame.mouse.get_pos()
self._check_play_button(mouse_pos)
def _check_play_button(self, mouse_pos):
button_clicked = self.play_button.rect.collidepoint(mouse_pos)
if button_clicked and self.play_button.rect.collidepoint(mouse_pos):
self.game_active = True
pygame.mouse.set_visible(False)
def _update_asteroids(self):
""" afer 'x' milliseconds create a new asteroid """
current_time = pygame.time.get_ticks()
if current_time > self.next_object_time:
self.next_object_time += self.time_interval
self._create_asteroids()
def run_game(self):
""" loops the game/ updates screen/ checks for key clicks"""
while True:
self._update_tank()
if self.game_active:
self.move()
self.bullets.update()
self.tank_asteroid_collision()
#after certain number of time create a asteroid
self._update_asteroids()
self.asteroids.update()
#get rid of asteroids that have disapeared
for asteroid in self.asteroids.copy():
if asteroid.x >= 1200 or asteroid.x <= 0:
self.asteroids.remove(asteroid)
#get rid of bullets that have disapeared
for bullet in self.bullets.copy():
if bullet.rect.left >= 1200 or bullet.rect.right <= 0:
self.bullets.remove(bullet)
self._update_screen()
class Bullet(Sprite):
""" A class to manage bullets fired from the ship """
def __init__(self, game):
""" create a bullet object at the ships current position """
super().__init__()
self.screen = game.screen
self.bullet_speed = 2.0
self.bullet_width = 20
self.bullet_height = 5
self.bullet_color = (0, 200, 200)
self.bullet_shootRight = False
self.bullet_shootLeft = False
self.image = pygame.Surface((self.bullet_width, self.bullet_height))
self.image.fill(self.bullet_color)
self.rect = self.image.get_rect()
self.rect.midright = game.rect.midright
self.rect.y -= 5 #the tanks barrel is 5 pixels above center
self.rect.x += 15
#store the bullets position as a decimal value
self.y = float(self.rect.y)
self.x = float(self.rect.x)
def update(self):
""" move the bullet up the screen """
#update the decimal position of the bullet.
if self.bullet_shootRight:
self.x += self.bullet_speed
self.rect.x = self.x
elif self.bullet_shootLeft:
self.x -= self.bullet_speed
self.rect.x = self.x
class Asteroid(Sprite):
""" a class that represents a single asteroid """
def __init__(self, game):
""" initialize the asteroid and set its starting position """
super().__init__()
self.screen = game.screen
self.speed = 0.1
self.direction = 1
#load the asteroid image onto the screen
self.image = pygame.image.load('images/asteroid.gif')
self.rect = self.image.get_rect()
#start each new asteroid in a random part just outside the map on either the left or right
self.rect.x = random.randint(*random.choice([(0, 0), (1160, 1160)]))
self.rect.y = random.randint(0, 760)
#store the asteroid's exact horizontal positon
self.x = float(self.rect.x)
self.y = float(self.rect.y)
def update(self):
""" move the asteroid to the right or left """
self.x += self.speed * self.direction
self.rect.x = self.x
self.y += (self.speed / 3) * self.direction
self.rect.y = self.y
class Button:
def __init__(self, game, msg):
""" Initialize button """
self.screen = game.screen
self.screen_rect = self.screen.get_rect()
#set the dimensions and properties of the button
self.width, self.height = 200, 50
self.button_color = (0, 255, 0)
self.text_color = (255, 255, 255)
self.font = pygame.font.SysFont(None, 48)
#Build the button's rect object and center it.
self.rect = pygame.Rect(0, 0, self.width, self.height)
self.rect.center = self.screen_rect.center
self._prep_msg(msg)
def _prep_msg(self, msg):
""" Turn msg into a rendered image and center text on the button """
self.msg_image = self.font.render(msg, True, self.text_color, self.button_color)
self.msg_image_rect = self.msg_image.get_rect()
self.msg_image_rect.center = self.rect.center
def draw_button(self):
""" draw blank button then draw message """
self.screen.fill(self.button_color, self.rect)
self.screen.blit(self.msg_image, self.msg_image_rect)
class Scoreboard:
""" A class to report scoring information """
def __init__(self, game):
""" initialize scorekeeping attributes """
self.screen = game.screen
self.screen_rect = self.screen.get_rect()
self.score = 0
self.highscore = 0
# Font settings for scoring information
self.text_color = (30, 30, 30)
self.font = pygame.font.SysFont(None, 48)
self.prep_score()
self.prep_highscore()
def reset_score(self):
self.score = 0
self.prep_score()
def prep_highscore(self):
""" Turn the high score into a rendered image. """
high_score = round(self.highscore, -1)
high_score_str = "{:,}".format(high_score)
self.high_score_image = self.font.render(high_score_str, True, self.text_color, (255, 255, 255))
#center the high score at the top of the screen
self.high_score_rect = self.high_score_image.get_rect()
self.high_score_rect.centerx = self.screen_rect.centerx
self.high_score_rect.top = self.score_rect.top
def check_high_score(self):
""" check to see if there's a new high score """
if self.score > self.highscore:
self.highscore = self.score
self.prep_highscore()
def prep_score(self):
""" Turn the score into a renered image """
rounded_score = round(self.score, -1)
score_str = "{:,}".format(rounded_score)
self.score_image = self.font.render(score_str, True, self.text_color, (255, 255, 255))
#Display the score at the top of the screen
self.score_rect = self.score_image.get_rect()
self.score_rect.right = self.screen_rect.right
self.score_rect.top = 0
def show_score(self):
""" draw the score to the screen """
self.screen.blit(self.score_image, self.score_rect)
self.screen.blit(self.high_score_image, self.high_score_rect)
if __name__ == '__main__':
a = Game()
a.run_game()
The spawning of the asteroids depends on the next_object_time. next_object_time is initialized with 0. You need to set self.next_object_time when the play button is pressed:
class Game(Sprite):
# [...]
def _check_play_button(self, mouse_pos):
if self.play_button.rect.collidepoint(mouse_pos):
self.game_active = True
pygame.mouse.set_visible(False)
self.next_object_time = pygame.time.get_ticks() + self.time_interval
To make the algorithm more robust you can set next_object_time depending on the current time when an asteroid spawns (this is optional):
class Game(Sprite):
# [...]
def _update_asteroids(self):
""" afer 'x' milliseconds create a new asteroid """
current_time = pygame.time.get_ticks()
if current_time > self.next_object_time:
# self.next_object_time += self.time_interval
self.next_object_time = current_time + self.time_interval
self._create_asteroids()
Creating a platformer in pygame and im using vectors for the gravity and movement. Right now im stuck on the collisions with the platforms because the plat will hit the platform and sink almost entirely in the platform like this:
Player sunk into platform block
(EDIT) People wanted more code info so...
Main Class:
import pygame as pg
import sys
from sprites import *
class Game:
def __init__(self):
pg.init()
pg.mixer.init()
pg.display.set_caption(TITLE)
self.screen = pg.display.set_mode((WIDTH, HEIGHT))
self.clock = pg.time.Clock()
self.running = True
self.load_data()
self.new_game()
def load_data(self):
# Eventually will load data
pass
def new_game(self):
# Creates new instances of all of the sprites
# groups and re-renders sprites at starting locations
self.all_sprites = pg.sprite.Group()
self.platform_sprites = pg.sprite.Group()
self.player = Player(self, 60, 60)
# Creates some test platforms
for x in range(10):
Platform(self, x, 6)
def run(self):
# Basic game loop
while self.running:
self.dt = self.clock.tick(FPS) # Get time passed since last update / Set FPS
self.events()
self.render()
self.update()
def events(self):
for event in pg.event.get():
if event.type == pg.QUIT:
self.running = False
if event.type == pg.KEYDOWN:
if event.key == pg.K_ESCAPE:
self.running = False
# Check for quit event and close window
def render(self):
self.screen.fill(WHITE)
self.all_sprites.draw(self.screen) # Draws all sprites to screen
pg.display.flip() # updates screen
def update(self):
self.all_sprites.update() # calls the update function of all of the sprites
if __name__ == '__main__':
g = Game()
while g.running:
g.run()
pg.quit()
sys.exit()
.... Player and platform sprite code:
import pygame as pg
from constants import *
vec = pg.math.Vector2
class Player(pg.sprite.Sprite):
def __init__(self, game, x, y):
self.groups = game.all_sprites
self.game = game
super().__init__(self.groups)
self.image = pg.Surface((40, 70))
self.image.fill(BLUE)
self.rect = self.image.get_rect()
pg.draw.rect(self.image, BLACK, (0, 0, self.rect.width, self.rect.height), 3)
self.pos = vec(x, y)
self.vel = vec(0, 0)
self.acc = vec(0, 0)
def move(self):
self.acc = vec(0, 0)
keypress = pg.key.get_pressed()
if keypress[pg.K_a]:
self.acc.x += -PLAYER_MOVE_SPEED
if keypress[pg.K_d]:
self.acc.x += PLAYER_MOVE_SPEED
self.acc += self.vel * PLAYER_FRICTION
self.vel += self.acc
self.pos += self.vel + 0.5 * self.acc
self.rect.midbottom = self.pos
def collisions(self, axis):
if axis == 'y':
for plat in self.game.platform_sprites:
if self.acc.y > 0:
if pg.Rect.colliderect(self.rect, plat.rect):
self.acc.y = 0
self.pos.y = plat.rect.top
def update(self):
self.move()
self.collisions('y')
class Platform(pg.sprite.Sprite):
def __init__(self, game, x, y):
self.groups = game.all_sprites, game.platform_sprites
self.game = game
super().__init__(self.groups)
self.image = pg.Surface((TILESIZE, TILESIZE))
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.x = x * TILESIZE
self.y = y * TILESIZE
def update(self):
self.rect.x = self.x
self.rect.y = self.y
The "Constants" file only has stuff like the screen size, colors, and player speed defined. IF any of that's needed I'll add it.
In the Player.collisions:
if self.acc.y > 0:
if pg.Rect.colliderect(self.rect, plat.rect):
self.acc.y = 0
self.pos.y = plat.rect.top
The main cause of your issue is the player's velocity:
You're resetting the vertical acceleration (acc.y) and position (pos.y), but it seems like you're forgetting about the vertical speed (vel.y). The player's sinking because it still has downward velocity, which you can remedy with self.vel.y = 0.
.
Another more minor issue might be, as #LarryMcMuffin correctly guessed, that there is an ordering issue between your moving/drawing:
You may be updating Player.pos.y correctly so the player stands perfectly on top of the platform, but you also need to update self.rect.midbottom if you want your character to be drawn at its new position -- which you do, but in Player.move, which is called before. You could reorder the move/collision functions, but my suggestion is to simply move self.rect.midbottom = self.pos from Player.move to the very end of Player.update.
End result:
def collisions(self, axis):
if axis == 'y':
for plat in self.game.platform_sprites:
if self.acc.y > 0:
if pg.Rect.colliderect(self.rect, plat.rect):
self.acc.y = 0
self.vel.y = 0
self.pos.y = plat.rect.top
def update(self):
self.move()
self.collisions('y')
self.rect.midbottom = self.pos
Another thing I'd like to point out, even though it's probably not related to the issue you described: Use the delta-time.
See, you have two values that influence your character's position:
velocity (vel): expresses a change of position over time
acceleration (acc): expresses a change of velocity over time
The over time is key here -- since the time elapsed between two calls of move is variable, this matters: as more/less time passed, the speed should act more/less upon the position. Fortunately, you're saving the time elapsed between two frames/updates in Game.dt. Pass it to your player and use it more or less like so:
self.acc += self.vel * PLAYER_FRICTION
self.vel += self.acc * dt
self.pos += (self.vel + 0.5 * self.acc) * dt
It won't fix it completely so that two calls with dt = .5 produce the same effect as one call with dt = 1, but it will "fill the gap". I don't think you can achieve it without either updating the physics on a fixed-interval basis, or resorting to expensive and complex calculations (using the power function).
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
I was working on a movement animations since I saw a youtuber explaining how to do it, but I'm getting this error:
TypeError: argument 1 must be pygame.Surface, not list
My code is about 500 lines.
# Pygame Template - skeleton for a new pygame project
import pygame
import random
import os
from os import path
vec = pygame.math.Vector2
width = 800
height = 600
FPS = 60
POWERUP_TIME = 5000
title = 'Parkourse'
# Player properties
player_acceleration = 0.5
player_friction = -0.12
player_gravity = 0.8
player_jump = 10
# Starting platforms
platforms_list = [(0,height-40,width,50), # Ground
(0,0,800,10), # Top
(0,0,10,600), # Left Border
(790,height-400,10,600),# Right Border
(250,height - 160,width-200,10), # Floor 1
(0,height - 280,width-200,10), # Floor 2
(250,height - 400,width-100,10)] # Floor 3
# Define Colors
white = (255,255,255)
black = (0,0,0)
red = (255,0,0)
green = (0,255,0)
blue = (0,0,255)
# set up assets folders
game_folder = os.path.dirname(__file__)
image_folder = os.path.join(game_folder, "Image")
sound_folder = os.path.join(game_folder, "Sound")
# Initialize pygame and create window
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((width,height))
pygame.display.set_caption(title)
clock = pygame.time.Clock()
# Load all game graphics
background = pygame.image.load(path.join(image_folder, "background.png")).convert()
background_rect = background.get_rect()
no_mvmt_0 = pygame.image.load(path.join(image_folder,"no_mvmt_0.png")).convert()
no_mvmt_1 = pygame.image.load(path.join(image_folder,"no_mvmt_1.png")).convert()
running_0 = pygame.image.load(path.join(image_folder,"running_0.png")).convert()
running_1 = pygame.image.load(path.join(image_folder,"running_1.png")).convert()
jumping_0 = pygame.image.load(path.join(image_folder,"jumping_0.png")).convert()
mini_no_mvmt = pygame.transform.scale(no_mvmt_0, (25,48))
mini_no_mvmt.set_colorkey(white)
scissors = pygame.image.load(path.join(image_folder,"scissors.png")).convert()
mob_left = pygame.image.load(path.join(image_folder,"mob_left.png")).convert()
power_upper_image = {}
power_upper_image['shield_0'] = pygame.image.load(path.join(image_folder,"shield_upper_0.png")).convert()
power_upper_image['shield_1'] = pygame.image.load(path.join(image_folder,"shield_upper_1.png")).convert()
power_upper_image['shield_2'] = pygame.image.load(path.join(image_folder,"shield_upper_2.png")).convert()
power_upper_image['life'] = pygame.image.load(path.join(image_folder,"life_upper.png")).convert()
power_upper_image['power'] = pygame.image.load(path.join(image_folder,"power.png")).convert()
explosion_animation = {}
explosion_animation['normal']=[]
explosion_animation['small']=[]
explosion_animation['player']=[]
for explosion in range(5):
explose = 'explosion_{}.png'.format(explosion)
image = pygame.image.load(path.join(image_folder, explose)).convert()
image.set_colorkey(white)
image.set_colorkey(black)
image_normal = pygame.transform.scale(image, (80,80))
explosion_animation['normal'].append(image_normal)
image_small = pygame.transform.scale(image, (30, 30))
explosion_animation['small'].append(image_small)
death = 'dying_{}.png'.format(explosion)
image = pygame.image.load(path.join(image_folder, death)).convert()
image.set_colorkey(white)
explosion_animation['player'].append(image)
#Load all game sounds
scream_sound = []
for scream in ["slightscream_0.wav", "slightscream_1.wav", "slightscream_2.wav",
"slightscream_3.wav", "slightscream_4.wav", "slightscream_5.wav",
"slightscream_6.wav", "slightscream_7.wav", "slightscream_8.wav",
"slightscream_9.wav", "slightscream_10.wav", "slightscream_11.wav",
"slightscream_12.wav", "slightscream_13.wav", "slightscream_14.wav"]:
scream_sound.append(pygame.mixer.Sound(path.join(sound_folder,scream)))
shoot_sound = pygame.mixer.Sound(path.join(sound_folder,"shoot.wav"))
shield = pygame.mixer.Sound(path.join(sound_folder,"shield.wav"))
life = pygame.mixer.Sound(path.join(sound_folder,"life.wav"))
special_power = pygame.mixer.Sound(path.join(sound_folder,"special_power.wav"))
death_sound = pygame.mixer.Sound(path.join(sound_folder,"death.ogg"))
explosion_sound = []
for sound in ["explosion.wav", "explosion_2.wav"]:
explosion_sound.append(pygame.mixer.Sound(path.join(sound_folder, sound)))
pygame.mixer.music.load(path.join(sound_folder,"gameplay.ogg"))
pygame.mixer.music.set_volume(0.6)
font_name = pygame.font.match_font('arial')
def draw_text (surf, text,color,size, x, y):
font = pygame.font.Font(font_name, size)
text_surface = font.render(text, True, color)
text_rect = text_surface.get_rect()
text_rect.midtop = (x, y)
surf.blit(text_surface, text_rect)
def newmob():
m = Mobs()
all_sprites.add(m)
mobs.add(m)
def draw_shield_bar(screen, x,y,percentage):
if percentage < 0:
percentage = 0
bar_lenght = 100
bar_height = 10
fill = (percentage / 100) * bar_lenght
outline_rect = pygame.Rect(x,y,bar_lenght,bar_height)
fill_rect = pygame.Rect(x,y,fill, bar_height)
pygame.draw.rect(screen, green, fill_rect)
pygame.draw.rect(screen, black, outline_rect, 2) # 2 is the the number of pixels
# of how wide you want the outline
# of the rectangle to be
def draw_lives (surface, x, y, lives, image):
for i in range(lives):
image_rect = image.get_rect()
image_rect.x = x + 30 * i
image_rect.y = y
surface.blit(image, image_rect)
score = 0
def show_game_over_screen():
screen.blit(background, background_rect)
draw_text(screen, "Dang..!",red,100, width/2, 200)
draw_text(screen, "Score: " + str(score),blue,30, width/2, 330)
draw_text(screen, "Press any key to retry",blue,30, width/2, 390)
pygame.display.flip()
waiting = True
while waiting:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
if event.type == pygame.KEYUP:
waiting = False
def show_start_screen():
screen.blit(background, background_rect)
draw_text(screen,"Parkourse!", green, 100, width/2, 200)
draw_text(screen, "Use the arrow keys to move, S to fire, and space to Jump",blue,30, width/2, 330)
draw_text(screen, "Press any key to begin",blue,30, width/2, 390)
pygame.display.flip()
waiting = True
while waiting:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
if event.type == pygame.KEYUP:
waiting = False
class Player (pygame.sprite.Sprite):
# Sprite for the player
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.load_movement_images()
self.image = self.standing_frame[0]
self.rect = self.image.get_rect()
self.pos = vec(50,500)
self.vel = vec(0,0)
self.acc = vec(0,0)
self.shield = 100
self.lives = 3
self.hidden = False
self.hide_timer = pygame.time.get_ticks()
self.power = 1
self.power_timer = pygame.time.get_ticks()
self.running = False
self.jumping = False
self.current_frame = 0
self.last_update = 0
def load_movement_images(self):
self.standing_frame = [no_mvmt_0, no_mvmt_1]
self.running_frame_right = [running_0,running_1]
self.running_frame_left = []
for frame in self.standing_frame:
frame.set_colorkey(white)
for frame in self.running_frame_right:
self.running_frame_left.append(pygame.transform.flip(frame,True,False)) # True is horizontaly, False is vertically
frame.set_colorkey(white)
self.jumping_frame = jumping_0
self.jumping_frame.set_colorkey(white)
def animate(self):
now = pygame.time.get_ticks()
if not self.jumping and not self.running:
if now - self.last_update > 350:
self.last_update = now
self.current_frame = (self.current_frame + 1) % len(self.standing_frame)
self.image = self.standing_frame
def jump(self):
# Jump only if standing on a Platform
self.rect.x +=1
hits = pygame.sprite.spritecollide(player,platforms, False)
self.rect.x -= 1
if hits:
self.vel.y = - player_jump
def update(self):
self.animate()
# timeout for powerups
if self.power >=2 and pygame.time.get_ticks() - self.power_time > POWERUP_TIME:
self.power -= 1
self.power_time = pygame.time.get_ticks()
# unhide if hidden
if self.hidden and pygame.time.get_ticks() - self.hide_timer > 1000:
self.hidden = False
self.pos = vec(30, 400)
self.acc = vec(0,player_gravity)
keystate = pygame.key.get_pressed()
if keystate[pygame.K_LEFT] or keystate[pygame.K_a]:
self.acc.x = -player_acceleration
if keystate[pygame.K_RIGHT] or keystate[pygame.K_d]:
self.acc.x = player_acceleration
if keystate[pygame.K_SPACE]:
player.jump()
# apply friction
self.acc.x += self.vel.x * player_friction
# equations of motions
self.vel += self.acc
self.pos += self.vel + 0.5 * self.acc
# wrap around the sides of the screen
if self.pos.x > 750:
self.pos.x = 750
if self.pos.x <= 0:
self.pos.x = 25
self.rect.midbottom = self.pos
def powerup(self):
self.power += 1
self.power_time = pygame.time.get_ticks()
def shoot(self):
if self.power == 1:
bullet = Bullet(self.pos.x + 5, self.pos.y - 20)
all_sprites.add(bullet)
bullets.add(bullet)
shoot_sound.play()
if self.power >= 2:
bullet1 = Bullet(self.pos.x + 5, self.pos.y - 20)
bullet2 = Bullet(self.pos.x + 35, self.pos.y -20)
all_sprites.add(bullet1)
all_sprites.add(bullet2)
bullets.add(bullet1)
bullets.add(bullet2)
shoot_sound.play()
def hide(self):
# hide the player temporarily
self.hidden = True
self.hide_timer = pygame.time.get_ticks()
self.pos = vec(0, 6000)
class Bullet(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = scissors
self.rect = self.image.get_rect()
self.image.set_colorkey(white)
self.image = pygame.transform.scale(scissors, (30,15))
self.rect.bottom = y
self.rect.centerx = x
self.speedx = 10
def update(self):
self.rect.x += self.speedx
# kill if it moves off the top of the screen
if self.rect.bottom < 0:
self.kill()
class Mobs(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = mob_left
self.rect = self.image.get_rect()
self.image.set_colorkey(white)
self.rect.x = random.randrange(0,800)
self.rect.y = 530
self.speedx = 2
def update(self):
self.rect.x -= self.speedx
if self.rect.right < 0:
self.rect.x = 800
class Explosion(pygame.sprite.Sprite):
def __init__(self, center, size, frame):
pygame.sprite.Sprite.__init__(self)
self.size = size
self.image = explosion_animation[self.size][0]
self.rect = self.image.get_rect()
self.rect.center = center
self.frame = 0
self.last_update = pygame.time.get_ticks()
self.frame_rate = frame
def update(self):
now = pygame.time.get_ticks()
if now - self.last_update > self.frame_rate:
self.last_update = now
self.frame += 1
if self.frame == len(explosion_animation[self.size]):
self.kill()
else:
center = self.rect.center
self.image = explosion_animation[self.size][self.frame]
self.rect = self.image.get_rect()
self.rect.center = center
class Normal_Power(pygame.sprite.Sprite):
def __init__(self, center):
pygame.sprite.Sprite.__init__(self)
self.type = random.choice(['shield_0','shield_1','shield_2'])
self.image = power_upper_image[self.type]
self.rect = self.image.get_rect()
self.image.set_colorkey(white)
self.rect.center = center
class Special_Power(pygame.sprite.Sprite):
def __init__(self, center):
pygame.sprite.Sprite.__init__(self)
self.type = random.choice(['life','power'])
self.image = power_upper_image[self.type]
self.rect = self.image.get_rect()
self.image.set_colorkey(white)
self.rect.center = center
class Platform(pygame.sprite.Sprite):
def __init__(self, x, y, w, h):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((w, h))
self.image.fill(black)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
pygame.mixer.music.play(loops=-1) # loops = -1 means that pygame will restart the song when it's finished
# Game loop
running = True
new_game = True
game_over = False
while running:
if new_game:
show_start_screen()
new_game = False
all_sprites = pygame.sprite.Group()
platforms = pygame.sprite.Group()
for plat in platforms_list:
p = Platform (*plat)
all_sprites.add(p)
platforms.add(p)
mobs = pygame.sprite.Group()
player = Player()
all_sprites.add(player)
bullets = pygame.sprite.Group()
powerups = pygame.sprite.Group()
for i in range(1):
newmob()
score = 0
if game_over:
show_game_over_screen()
game_over = False
all_sprites = pygame.sprite.Group()
platforms = pygame.sprite.Group()
for plat in platforms_list:
p = Platform (*plat)
all_sprites.add(p)
platforms.add(p)
mobs = pygame.sprite.Group()
player = Player()
all_sprites.add(player)
bullets = pygame.sprite.Group()
powerups = pygame.sprite.Group()
for i in range(1):
newmob()
score = 0
# Keep loop running at the right speed
clock.tick(FPS)
# Process input (events)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_s:
player.shoot()
# Updates
all_sprites.update()
# check if player hits a platform - only if falling
if player.vel.y > 0:
hits = pygame.sprite.spritecollide(player,platforms,False)
if hits:
player.pos.y = hits[0].rect.top
player.vel.y = 0
# check to see if a bullet hit a mob
hits = pygame.sprite.groupcollide(mobs,bullets,True,True)
for hit in hits:
score += 1
random.choice(explosion_sound).play()
expl = Explosion(hit.rect.center, 'normal', 50)
all_sprites.add(expl)
if random.random() > 0.75:
power = Normal_Power(hit.rect.center)
all_sprites.add(power)
powerups.add(power)
if random.random() > 0.90:
lives = Special_Power(hit.rect.center)
all_sprites.add(lives)
powerups.add(lives)
newmob()
# check to see if the mob hit the player
hits = pygame.sprite.spritecollide(player, mobs,True)
for hit in hits:
random.choice(explosion_sound).play()
player.shield -= 25
newmob()
expl = Explosion(hit.rect.center, 'small', 50)
all_sprites.add(expl)
if player.shield <= 0:
death_sound.play()
death_animation = Explosion(player.rect.center, 'player', 100)
all_sprites.add(death_animation)
player.hide()
player.lives -= 1
player.shield = 100
else:
random.choice(scream_sound).play()
# check if the player hit a powerup
hits = pygame.sprite.spritecollide(player, powerups, True)
for hit in hits:
if hit.type == 'shield_0':
player.shield += 5
if player.shield >= 100:
player.shield = 100
shield.play()
if hit.type == 'shield_1':
player.shield += 20
if player.shield >= 100:
player.shield = 100
shield.play()
if hit.type == 'shield_2':
player.shield += 20
if player.shield >= 100:
player.shield = 100
shield.play()
if hit.type == 'life':
player.lives += 1
if player.lives >= 3:
player.lives = 3
life.play()
if hit.type == 'power':
special_power.play()
player.powerup()
# if the player died and the explosion finished playing
if player.lives == 0 and not death_animation.alive():
game_over = True
# Draw / Render
screen.fill(white)
screen.blit(background, background_rect)
all_sprites.draw(screen)
draw_text(screen,str(score),red,30, width/ 2, 30)
draw_text(screen,"Score:",red,30, width / 2, 3)
draw_shield_bar(screen,90,20, player.shield)
draw_lives(screen,95,40,player.lives, mini_no_mvmt)
# *after* drawing everything, flip the display
pygame.display.flip()
pygame.quit()
quit()
The error is caused by a sprite in your all_sprites group that has a list as its self.image attribute. I've just printed the sprites before the all_sprites.draw(screen) line
for sprite in all_sprites:
print(sprite, sprite.image)
and it was the player sprite which had a list as its image.
<Player sprite(in 1 groups)> [<Surface(40x25x32 SW)>, <Surface(40x25x32 SW)>]
In the load_movement_images method you define self.standing_frame as a list of two images/pygame.Surfaces and in __init__ you set self.image to the first item of that list. But in the animate method you set self.image to the whole list instead of the active image self.image = self.standing_frame and that leads to the error.
class Player (pygame.sprite.Sprite):
# Sprite for the player
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.load_movement_images()
# self.image is the first image in the
# self.standing_frame list.
self.image = self.standing_frame[0]
# ...
def load_movement_images(self):
self.standing_frame = [no_mvmt_0, no_mvmt_1]
# ...
def animate(self):
now = pygame.time.get_ticks()
if not self.jumping and not self.running:
if now - self.last_update > 350:
self.last_update = now
self.current_frame = (self.current_frame + 1) % len(self.standing_frame)
# Here you set the self.image to the self.standing_fram list instead one image.
self.image = self.standing_frame
# It should be:
# self.image = self.standing_frame[self.current_frame]
I am building a top down shooter in the style of Raiden 2. I need to know how to get access to the enemy object when I detect collision using 'spritecollide'.
The reason I need the enemy object is so that I can lower their energy level for every bullet that hits them.
This is the dictionary that I have to work with:
enemyCollisions = pygame.sprite.groupcollide(shipBulletGroup, enemyGroup, True, False)
Here's my Bullet class:
class Bullet(Entity):
def __init__(self, ship, angle):
Entity.__init__(self)
self.speed = 8
self.level = 0
self.image = pygame.Surface((BULLET_DIMENSIONS, BULLET_DIMENSIONS)).convert()
self.rect = self.image.get_rect()
self.rect.x = ship.rect.centerx
self.rect.y = ship.rect.top
self.angle = angle
def update(self, enemyGroup):
if self.rect.bottom < 0:
self.kill()
else:
self.rect.y -= self.speed
Here's my unfinished Enemy class:
class Enemy_1(Entity):
def __init__(self):
Entity.__init__(self)
self.speed = (1, 1)
self.energy = 10
self.begin = False
self.run = False
self.image = pygame.Surface((WIN_W/5, WIN_H/15)).convert()
self.image.fill((70, 70, 70))
self.rect = self.image.get_rect()
self.rect.centerx = WIN_W/1.333
self.rect.y = -WIN_H/15
def shoot(self):
bullet_1 = Bullet(self, 270)
bullet_2 = Bullet(self, 225)
bullet_3 = Bullet(self, 315)
def update(self, enemyGroup, timer):
if timer == 300:
self.begin = True
elif timer == 900:
self.run = True
elif timer > 900 and self.rect.x > WIN_W:
self.remove(enemyGroup)
if self.run:
self.rect.x += self.speed[0]
self.rect.y += self.speed[1]
elif self.begin:
self.rect.y += self.speed[1]
if self.rect.y > WIN_H/4:
self.rect.y = WIN_H/4
if self.energy == 0:
self.kill()
Here's the complete program:
import sys, pygame, os, random, math
from ast import literal_eval
# Force static position of screen
os.environ['SDL_VIDEO_CENTERED'] = '1'
# Constants
LEFT = 'left'
RIGHT = 'right'
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
PILL_WIDTH = 5
PILL_HEIGHT = 20
WIN_W = 500
WIN_H = 800
SHIP_WIDTH = WIN_W/15
SHIP_HEIGHT = WIN_H/15
BULLET_DIMENSIONS = 5
TIMER = 0
class Entity(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
class Text(Entity):
def __init__(self, text, size, color, position, font=None):
Entity.__init__(self)
self.color = color
self.font = pygame.font.Font(font, size)
self.text = text
self.set(text, position)
def set(self, text, position):
self.image = self.font.render(str(text), 1, self.color)
self.rect = self.image.get_rect()
self.rect.move_ip(position[0]-self.rect.width/2, (position[1]-self.rect.height)/2)
class Ship(Entity):
def __init__(self, container):
Entity.__init__(self)
self.speed = 5
self.score = 0
self.image = pygame.Surface((SHIP_WIDTH, SHIP_HEIGHT)).convert()
self.rect = self.image.get_rect()
self.rect.centerx = container.centerx
self.rect.y = container.centery
def update(self, bulletGroup):
key = pygame.key.get_pressed()
if key[pygame.K_w]:
self.rect.centery -= self.speed
if key[pygame.K_s]:
self.rect.centery += self.speed
if key[pygame.K_d]:
self.rect.centerx += self.speed
if key[pygame.K_a]:
self.rect.centerx -= self.speed
if key[pygame.K_SPACE]:
if TIMER % 7 == 0:
bullet = Bullet(self, 90, 'friend')
bulletGroup.add(bullet)
# Ship Movement Boundaries
if self.rect.y < WIN_H/25:
self.rect.y = WIN_H/25
if self.rect.y > WIN_H - SHIP_HEIGHT:
self.rect.y = WIN_H - SHIP_HEIGHT
if self.rect.x < 0:
self.rect.x = 0
if self.rect.x > WIN_W - SHIP_WIDTH:
self.rect.x = WIN_W - SHIP_WIDTH
class Bullet(Entity):
def __init__(self, ship, angle, type):
Entity.__init__(self)
self.speed = 8
self.level = 0
self.image = pygame.Surface((BULLET_DIMENSIONS, BULLET_DIMENSIONS)).convert()
self.rect = self.image.get_rect()
self.dx = math.cos(math.radians(angle)) * self.speed
self.dy = math.sin(math.radians(angle)) * self.speed
self.setXY(ship, type)
def setXY(self, ship, type):
self.rect.x = ship.rect.centerx
if type == 'friend':
self.rect.y = ship.rect.top
elif type == 'enemy':
self.rect.y = ship.rect.bottom
def update(self):
if self.rect.bottom < 0:
self.kill()
else:
self.rect.x -= self.dx
self.rect.y -= self.dy
if type == 'friend':
self.rect.y = -self.rect.y
self.rect.x = -self.rect.x
class Powerup(Entity):
def __init__(self, xden, pillCount):
Entity.__init__(self)
self.speed = 3
self.density = xden
self.image = pygame.Surface((PILL_WIDTH, PILL_HEIGHT)).convert()
self.rect = self.image.get_rect()
self.rect = self.rect.move(100,100)
def restart(self):
pass
def update(self):
if self.rect.y > WIN_H:
del self
else:
self.rect = self.rect.move((0, self.speed))
class Enemy(Entity):
def __init__(self):
Entity.__init__(self)
self.begin = False
self.run = False
self.color = (153, 0, 76)
class Planes(Enemy):
def __init__(self, speed, energy, size, location):
Enemy.__init__(self)
self.speed = speed
self.energy = energy
self.image = pygame.Surface(size).convert()
self.image.fill(self.color)
self.rect = self.image.get_rect()
self.rect.centerx = location[0]
self.rect.y = location[1]
def shoot(self, enemyBulletGroup):
bullet_1 = Bullet(self, 240, 'enemy')
bullet_2 = Bullet(self, 270, 'enemy')
bullet_3 = Bullet(self, 300, 'enemy')
enemyBulletGroup.add(bullet_1, bullet_2, bullet_3)
def update(self, enemyGroup, enemyBulletGroup):
if TIMER == 300:
self.begin = True
elif TIMER == 900:
self.run = True
elif self.rect.x > WIN_W or self.energy == 0:
self.kill()
if self.run:
self.rect.x += self.speed[0]
self.rect.y += self.speed[1]
if TIMER % 100 == 0:
self.shoot(enemyBulletGroup)
elif self.begin:
self.rect.y += self.speed[1]
if self.rect.y > WIN_H/4:
self.rect.y = WIN_H/4
if TIMER % 100 == 0:
self.shoot(enemyBulletGroup)
def main():
# Initialize Everything
pygame.init()
global TIMER
fps = 60
lLeft = lRight = lUp = lDown = shoot = False
clock = pygame.time.Clock()
play = True
pygame.display.set_caption('Pong')
screen = pygame.display.set_mode((WIN_W, WIN_H), pygame.SRCALPHA)
# Create Game Objects
ship = Ship(pygame.rect.Rect(0, 0, WIN_W, WIN_H))
score1 = Text("Score: " + str(ship.score), 40, BLACK, (WIN_W/2, (WIN_H/25)))
enemy_1 = Planes((1, 1), 10, (WIN_W/5, WIN_H/15), (WIN_W/1.4, -WIN_H/15))
# Create Groups
powerupGroup = pygame.sprite.Group()
shipGroup = pygame.sprite.Group()
shipGroup.add(ship)
textGroup = pygame.sprite.Group()
textGroup.add(score1)
shipBulletGroup = pygame.sprite.Group()
enemyBulletGroup = pygame.sprite.Group()
enemyGroup = pygame.sprite.Group()
enemyGroup.add(enemy_1)
# Gameplay
while play:
# Checks if window exit button pressed
for event in pygame.event.get():
if event.type == pygame.QUIT: sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
pygame.quit()
sys.exit()
# Update
powerupGroup.update()
ship.update(shipBulletGroup)
shipBulletGroup.update()
textGroup.update()
enemyGroup.update(enemyGroup, enemyBulletGroup)
enemyBulletGroup.update()
enemyCollisions = pygame.sprite.groupcollide(shipBulletGroup, enemyGroup, True, False)
for key in enemyCollisions:
print enemyCollisions[key].energy
# Print Background/Sprites
screen.fill(WHITE)
powerupGroup.draw(screen)
shipGroup.draw(screen)
shipBulletGroup.draw(screen)
textGroup.draw(screen)
enemyGroup.draw(screen)
enemyBulletGroup.draw(screen)
# Print Score Bar
hori_partition = pygame.Surface((WIN_W, 1))
screen.blit(hori_partition, (0, WIN_H/25))
TIMER += 1
# Limits frames per iteration of while loop
clock.tick(fps)
# Writes to main surface
pygame.display.flip()
if __name__ == "__main__":
main()
The pygame.sprite.groupcollide() function returns a dictionary which values are list objects:
Every Sprite inside group1 is added to the return dictionary. The value for each item is the list of Sprites in group2 that intersect.
Thats the reason why calling print enemyCollisions[key].energy when iterating over the return dict fails, since a list object -- the value of enemyCollisions[key] -- does not have an energy() method.
To access all values of a dictionary in Python 2.X you could use the .itervalues() method of a dict instance. To get each colliding sprite, iterate again over these values:
enemyCollisions = pygame.sprite.groupcollide(shipBulletGroup, enemyGroup, True, False)
#iterate over all values of the enemyCollisions dict
for enemyCollisions in enemyCollisions.itervalues():
#access all enemies which collided with an shipBullet
for enemy in enemyCollisions:
print enemy.energy
The syntax for accessing the collided sprite's property is as follows:
collisions = pygame.sprite.spritecollide(self, pillGroup, True)
for key in collisions:
self.density += key.density