How to make it so that all elements of an array are drawn - python

I'm making a simple parody of the game Chippy. In general, the monster(alien) must shoot and we must dodge and shoot back. I made it so that the picture of the ball is added to the array with all the other balls and removed when leaving the screen, but for some reason only one ball is drawn, and the rest are added to the array but do not appear on the screen. I am using module tkinter to get screen dimensions:
P.S. the ball is the bullets that the monster(alien) shoots, it's just that the bullets are already taken
# === ball ===
import pygame
from tkinter import *
root = Tk()
class Ball():
def __init__(self, screen, alien):
self.screen = screen
self.ball_image = pygame.image.load('images/pixil-frame-0 (1).png')
self.rect = self.ball_image.get_rect()
self.rect.centerx = alien.x
self.rect.centery = alien.y
self.all_ball = []
self.test = True
self.timer = 0
def draw_ball(self, alien):
self.screen.blit(self.ball_image, (self.rect.centerx, self.rect.centery))
def move(self):
self.rect.centery += 15
def create_ball(self, screen, alien):
for ball in self.all_ball.copy():
if ball.rect.top <= 0 or ball.rect.left <= 0 or ball.rect.right >= root.winfo_screenwidth() or ball.rect.bottom >= root.winfo_screenheight():
self.all_ball.remove(ball)
if self.test == True:
self.all_ball.append(Ball(screen, alien))
For now, I want to get the monster(alien) to shoot these balls in different directions.

all_ball should not be an attribute of Ball, because Ball is a class for one single Ball object. all_ball should be a variable in global namespace.
balls = []
def update_balls(screen, alien, create_new):
screen_rect = screen.get_rect()
for ball in balls[:]:
if not screen_rect.colliderect(ball.rect):
balls.remove(ball)
if create_new:
balls.append(Ball(screen, alien))
Draw the balls in a loop:
for ball in balls:
ball.draw_ball(screen)
However the Pygame solution would be to use pygame.sprite.Sprite and pygame.sprite.Group. See How can I add objects to a "pygame.sprite.Group()"? and What does pygame.sprite.Group() do.

Related

PyGame rendering issue -- edges are fuzzy as they move [duplicate]

I am struggling to move the sprite correctly. Instead of smooth move I can see blur move and I do not know how to solve it.
Is there any chance you can point what I do incorrectly ?
My target with it to drop the pizza so it hits the bottom and bounce back and bounc back if it hits the top and again the bottom -> bounce -> top -> bounce etc. etc.
import pygame
gravity = 0.5
class PizzaSprite:
def __init__(self, image, spritepos):
self.image = image
self.spos = spritepos
(x, y) = spritepos
self.posx = x
self.posy = y
self.xvel = 1
self.yvel = 1
print "x ", x
print "y ", y
def draw(self, target_surface):
target_surface.blit(self.image, self.spos)
def update(self):
self.posy -= self.yvel
self.spos = (self.posx, self.posy)
return
def main():
pygame.init()
screen_width = 800
screen_height = 600
x = screen_width
y = screen_height
screen = pygame.display.set_mode((screen_width, screen_height))
wall_image = pygame.image.load("wall.png")
sky_image = pygame.image.load("sky.png")
pizza_image = pygame.image.load("pizza.png")
screen.blit(wall_image,(0,200))
screen.blit(sky_image,(0,0))
all_sprites = []
pizza1 = PizzaSprite(pizza_image, (x/2, y/2))
all_sprites.append(pizza1)
while True:
ev = pygame.event.poll()
if ev.type == pygame.QUIT:
break
for sprite in all_sprites:
sprite.update()
for sprite in all_sprites:
sprite.draw(screen)
pygame.display.flip()
pygame.quit()
main()
in the beginning of your main game while loop add
white = (255,255,255)
screen.fill(white)
let me give you a small analogy of what is happening now,
you have paper and a lot of pizza stickers with the intent to make a flip book. To make the illusion of movement on each piece of paper you place a sticker a little bit lower. The screen.fill command essentially clears the screen with the rgb color value you give it. When you dont fill the screen essentially what you are doing is trying to make that flipbook on one piece of paper. You just keep placing more and more stickers a little bit lower making a blur when what you want is one on each page.
and place
pygame.init()
screen_width = 800
screen_height = 600
x = screen_width
y = screen_height
screen = pygame.display.set_mode((screen_width, screen_height))
wall_image = pygame.image.load("wall.png")
sky_image = pygame.image.load("sky.png")
all outside of your main game loop assuming you wont be making changes to these variables ever in your program it is tedious and inefficient to redefine screen,,x,y,and your two images over and over again if they dont change.
so to sum it all up:
use the screen.fill(white) command to reset the color of your screen
You only need to import pngs and define variables once if they are never going to change and don't need them in your main loop
hope this helps clear things up.

Having problems creating an animation from a sprite sheet in pygame

I am relatively new to Python and started messing around with pygame a few days ago. I made a sprite sheet in photoshop and I am trying to use it to animate the player in the game. The sprite sheet has 8 images, 4 for walking left and 4 for walking right. I want to show these animations at the correct time. I have tried many different methods I've found online but I haven't managed to get any working and I am still quite confused about it.
This is the Char class that controls the character's movement and sprite. I tried indexing the sprite and changing the index in the update method but I understand this isn't possible but I've left it in to show what I was trying to do. Any help would be appreciated!
import pygame, sys
from pygame.sprite import Sprite
class Char(Sprite):
def __init__(self, pf_game):
"""initialise char and set its starting location"""
self.screen = pf_game.screen
self.settings = pf_game.settings
self.screen_rect = pf_game.screen.get_rect()
self.dog_sprite = pygame.image.load('images/dog_sprite_sheet.bmp').convert()
self.current_sprite = 0
self.dog_image = self.dog_sprite[self.current_sprite]
self.rect = self.dog_image.get_rect()
# start new char at the bottom of centre of the screen
self.rect.midbottom = self.screen_rect.midbottom
#store a decimal value for the ships horizontal position
self.x = float(self.rect.x)
self.y = float(self.rect.y)
#movement flags
self.moving_right = False
self.moving_left = False
self.is_jump = False
def update(self):
"""Update the chars position based on movement flags"""
#update the chars x value and create animation for moving right
if self.moving_right and self.rect.right<self.screen_rect.right:
if self.current_sprite == 7:
self.current_sprite = 4
self.dog_image = self.dog_sprite[self.current_sprite]
self.current_sprite+=1
self.x+= self.settings.char_speed
#update the chars x value and create animation for moving left
if self.moving_left and self.rect.left>0:
if self.current_sprite == 3:
self.current_sprite = 0
self.dog_image = self.dog_sprite[self.current_sprite]
self.current_sprite+=1
self.x-= self.settings.char_speed
#update rect object from self.x
self.rect.x = self.x
self.rect.y = self.y
def blitme(self):
"""Draw the char at its curretn loaction"""
self.screen.blit(self.dog_image, self.rect)
edit:
I found a spritesheet class that seems to do what I want on http://www.pygame.org/wiki/Spritesheet. I am running into an error though when I try and input the coordinates. I get the error images_at() got multiple values for argument 'colorkey'. When i remove the colorkey I get the error images_at() takes from 2 to 3 positional arguments but 5 were given.
This is the code I am using.
self.dog_sprite=spritesheet.spritesheet('dog_sprite_sheet.bmp')
self.left_images=[]
self.right_images=[]
self.left_images=self.dog_sprite.images_at((0,0,19,19),(20,0,19,19),(39,0,19,19),(58,0,19,19), colorkey=(255, 255, 255))
self.right_images=self.dog_sprite.images_at((77,0,19,19),(96,0,19,19),(115,0,19,19),(134,0,19,19), colorkey=(255, 255, 255))
this may help:
def anim(anim_count):
if anim_count < 6:
screen.blit('anim1.png', player_position)
if anim_count > 6 and anim_count < 12:
screen.blit('anim2.png', player_position)
you should call anim at every frame, and then apply 1 to anim_count

Problems with Sprites Appearing and Collision with Rotated Objects; Pygame object is not iterable

I'm currently trying to make pixel perfect collisions between my pong ball and my player's paddle using the mask and collide_rect functions. I made my own checkCollision function in the pong class which would check for pixel perfect collision. However, right now, I can't even get the Sprites to work or appear on the screen because my "Pong object is not iterable.
Here is my pong class with the important features: (I will post additional code if needed)
class Pong(pygame.sprite.Sprite):
def __init__(self, screensize):
pygame.sprite.Sprite.__init__(self)
self.screensize = screensize
self.centerx = screensize[0] // 2
self.centery = screensize[1] // 2
self.radius = 25
self.rect = pygame.Rect(self.centerx-self.radius,
self.centery-self.radius,
self.radius*2, self.radius*2)
self.pokeimage = pygame.image.load("pokeball.png")
self.pokeimage = pygame.transform.scale(self.pokeimage, (self.radius, self.radius))
#Create the mask
self.mask = pygame.mask.from_surface(self.pokeimage)
def checkCollision(self, player_paddle, ai_paddle):
col = pygame.sprite.collide_rect(self, player_paddle)
return col
def collisionFormula(self, player_paddle, ai_paddle):
if self.checkCollision(self, player_paddle):
def collision_checks(self, player_paddle, ai_paddle):
#if the ball hits the top or bottom of the screen, change the y direction
if self.rect.top <= 0 or self.rect.bottom >= self.screensize[1] - 1:
self.direction[1] *= -1
#if the pong hits the paddles, change how the pong ball moves
if self.rect.colliderect(player_paddle.rect) or self.rect.colliderect(ai_paddle.rect):
self.collisionFormula(player_paddle, ai_paddle)
def update(self, player_paddle, ai_paddle):
self.update_ball_position()
self.reset_ball()
self.collision_checks(player_paddle, ai_paddle)
In my PlayerPaddle class, I do the same mask initialization.
class PlayerPaddle(pygame.sprite.Sprite):
def __init__(self, screensize):
pygame.sprite.Sprite.__init__(self)
self.screensize = screensize
self.centerx = 50
self.centery = screensize[1]//2
self.height = 100
self.width = 20
self.imageMaster = pygame.image.load("naruto.png").convert_alpha()
self.imageMaster = pygame.transform.scale(self.imageMaster, (self.width, self.height))
self.image = self.imageMaster
#mask
self.mask = pygame.mask.from_surface(self.image)
def turnLeft(self):
self.dir += 45
if self.dir > 360:
self.dir = 45
def turnRight(self):
self.dir -= 45
if self.dir < 0:
self.dir = 315
def update(self):
#Rotate functions
oldCenter = self.rect.center
self.image = pygame.transform.rotate(self.imageMaster, self.dir)
self.rect = self.image.get_rect()
self.rect.center = oldCenter
And here is my main function:
def main():
pygame.init()
screensize = (640,700)
screen = pygame.display.set_mode(screensize)
background = pygame.Surface(screen.get_size())
background.fill((0, 255, 0))
clock = pygame.time.Clock()
pong = Pong(screensize)
player_paddle = PlayerPaddle(screensize)
ai_paddle = AIPaddle(screensize)
paddleSprite = pygame.sprite.Group(player_paddle)
pongSprite = pygame.sprite.Group(pong)
while running:
running = True
#object updating phase
ai_paddle.update(pong, player_paddle)
player_paddle.update()
pong.update(player_paddle, ai_paddle)
if pygame.sprite.spritecollide(player_paddle, pong, False, pygame.sprite.collide_mask):
print("Collided")
#rendering phase
ai_paddle.render(screen)
player_paddle.render(screen)
pong.render(screen)
paddleSprite.clear(screen, background)
paddleSprite.update()
paddleSprite.draw(screen)
pongSprite.clear(screen,background)
pongSprite.update()
pongSprite.draw(screen)
pygame.display.flip()
pygame.quit()
main()
I made two "group" objects (the pong and the player_paddle) but I'm not sure why I'm even failing to run the program. Additionally, I know the collision will not work because the pong ball will hit the rectangle of the original image, but not the rotated image, but I'm not sure why that will happen if I use the built in sprite function. Thanks.
Read documentation for spritecollide
Find sprites in a group that intersect another sprite.
spritecollide(sprite, group, dokill, collided = None) -> Sprite_list
Second argument has to be group (pygame.sprite.Group), not single Sprite.
It can be group event with one sprite. But you use pong which is single sprite, not group.
See documentation for collide_mask
Collision detection between two sprites, using masks.
collide_mask(SpriteLeft, SpriteRight) -> point
It checks collision between two sprites using mask.
EDIT: in your code you have problem with
spritecollide(player_paddle, pong,...)
because pong is single Sprite, not Group.
With pong you should use collide_mask
collide_mask(player_paddle, pong)
You can use spritecollidewith pongSprite which is Group
spritecollide(player_paddle, pongSprite,...)`
BTW: you could use better names ie. pong_group instead of pongSprite.
And eventually pong_sprite instead of pong (but pong is OK, too).

why I get different results

Why I get different results when I use method update of group sprite objects during drawing to the screen. When thru calling method I get only one row of sprites and when I run for loop and move y axis of every sprite by one I get the whole swarm (several rows) moving down.
Here is the code
drop.py
import pygame
from pygame.sprite import Sprite
class Drop(Sprite):
"""Class that represent single drop in rain"""
def __init__(self, settings, screen):
"""Initialize drop and sets its starting position"""
super().__init__()
self.screen = screen
self.settings = settings
#Load drop image and sets its rect attribute
self.image = pygame.image.load('images/drop.bmp')
self.rect = self.image.get_rect()
#Start each drop on top-left on the screen
self.rect.x = self.rect.width
self.rect.y = self.rect.height
#store drop exact position
self.y = float(self.rect.y)
def update(self):
self.y += 1
self.rect.y = self.y
I have all functions stored in separate file. Just to keep it less cluttered. Where in function update_screen() is in comments described in code the problem I do not understand why makes difference looping thru sprites and that way changing y axis and getting whole group of them moving, but when I use method of sprite object .update() they are in group I get only partial group moving on screen.
func_rain.py
import pygame
import sys
from drop import Drop
from time import sleep
def create_rain(settings, screen, drops):
"""Create a fleet of drops."""
#Create an drop and find the number of drops in a row and number rows
drop = Drop(settings, screen)
number_drops_x = get_number_drops_x(settings, drop.rect.width)
number_rows = get_number_rows(settings, drop.rect.height)
for row_number in range(number_rows):
for drop_number in range (number_drops_x):
create_drop(settings, screen, drops, drop_number, row_number)
def get_number_drops_x(settings, drop_width):
"""Determine the number of drops that fit in a row."""
available_space_x = settings.screen_width - 2 * drop_width
number_drops_x = int(available_space_x / (2 * drop_width))
return number_drops_x
def get_number_rows(settings, drop_height):
"""Determine the number of rows of drops that fit on the screen."""
available_space_y = (settings.screen_height - drop_height)
number_rows = int(available_space_y / (2 * drop_height))
return number_rows
def create_drop(settings, screen, drops, drop_number, row_number):
"""Create drop and place it in the row"""
drop = Drop(settings, screen)
drop_width = drop.rect.width
drop.x = drop_width + 2 * drop_width * drop_number
drop.rect.x = drop.x
drop.rect.y = drop.rect.height + 2 * drop.rect.height * row_number
drops.add(drop)
def check_keydown_events(event):
"""Respond to key presses"""
if event.key == pygame.K_q:
sys.exit()
def check_events():
"""Respond to keypress and mouse events"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
check_keydown_events(event)
def update_screen(settings, screen, drops):
"""Update images on the screen and flip to the new screen."""
# Redraw the screen during each pass through the loop.
screen.fill(settings.bg_color)
#This loop
for drop in drops:
drop.rect.y += 1
"""Here is the problem!!!!!! if I use for loop just above I get whole group
consisting from 5 rows of drops (image has resolution 50*62 pix). But when
I use drops.update() commented below (no for loop) I get on screen only
one row of drops."""
#drops.update()
drops.draw(screen)
pygame.display.flip()
rain_settings.py
class Settings():
"""A class to store all ran settings"""
def __init__(self):
#Screen settings
self.screen_width = 1200
self.screen_height = 800
self.bg_color = (100, 100, 100)
And last main file.
rain.py
import pygame
from rain_settings import Settings
import func_rain as fr
from pygame.sprite import Group
def run_rain():
"""Initialize pygame and screen object"""
pygame.init()
settings = Settings()
screen = pygame.display.set_mode(
(settings.screen_width, settings.screen_height))
drops = Group()
fr.create_rain(settings, screen, drops)
while True:
fr.check_events()
fr.update_screen(settings, screen, drops)
run_rain()
I would really appreciate If someone can explain me what I'm doing wrong. I can't figure it out. please
So I've been playing around and I get the movement of sprites thru class method update to work.
def update(self):
self.rect.y += 1
Altho it still remains to me a mystery why directly changing the self.rect.y works and indirect change on y attribute don't.

Pygame spritecollide hitboxes

I am working on a simple 2D platformer, but I am having some trouble with the hitboxes of my sprites. I use the pygame.sprite.spritecollide function to generate a list of blocks(platforms) touching my player sprite.
Here is my player class:
class Player( pygame.sprite.Sprite ):
def __init__(self,x,y,image):
super( Player, self ).__init__()
self.vel_x = 0
self.vel_y = 0
self.moveSpeed = 3
self.jumpPower = 7
self.direction = idle
self.grounded = False
self.falling = True
self.jumping = False
self.climbing = False
self.image = pygame.Surface((50,50))#pygame.transform.scale( image, (50,50))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.width = 50
self.height = 50
self.rect.bottom = self.rect.y + self.height
def set_position( self,x,y ):
self.rect.x = x
self.rect.y = y
def experience_gravity(self, gravity = 0.3):
if not self.grounded:
self.vel_y += gravity
self.falling = True
else:
self.vel_y = 0
self.grounded = True
self.falling = False
def jump(self):
self.grounded = False
self.vel_y -= self.jumpPower
def update(self, collidable = pygame.sprite.Group() ):
global collision
self.experience_gravity()
self.rect.x += self.vel_x
self.rect.y += self.vel_y
collision_list = pygame.sprite.spritecollide( self, collidable, False )
for p in collision_list:
if ( self.vel_y > 0 ):
self.vel_y = 0
self.grounded = True
In my the update method, I check for collisions between a sprite group holding all of my platforms (parameter collidable) and the player.
Here's my block class:
class Block( pygame.sprite.Sprite ):
def __init__( self, x, y, width, height):
super( Block, self ).__init__()
self.image = pygame.Surface( ( width, height ) )
self.image.fill( black )
self.rect = self.image.get_rect()
self.type = platform
self.rect.x = x
self.rect.y = y
self.width = width
self.height = height
Pretty self-explanatory block class. The rest of my code is to update and blit everything onto a white background. The problem I run into is that when the player lands on a platform, it only stops falling (becomes grounded) when it is already in the platform. Even stranger, the depth the block sinks intot he platform is not consistent. Sometimes it will fall in 10 pixels, other ties 20 pixels. Here's a screenshot of the player getting stuck:
The player block is stuck inside the platform block
So this is really baffling me, especially since the amount the block falls in is inconsistent. If anyone could give me an idea on how to fix this, I'll really appreciate it.
With kindest regards,
Derek
It's hard to diagnose without any debugging trace, but I have an idea or two.
First of all, dump a debugging trace: print the salient values at each time step: position of the player block, positions of colliding blocks, and applicable velocities. This should give you a table-like snapshot at each time step of the involved objects.
Now, look at the values in the last time step before the collision. I suspect that you might have something like a player velocity of -20, but landing on a platform only 10 pixels below. Your update logic needs to handle the landing between the two time steps, and stop the player at the platform height. It looks to me as if the player is allowed to fall for the entire time step, which will embed his tender lower extremities 10 pixels into the platform.
Second, check that you're comparing the nearer edges: in this case, the player's lower edge with the platform's upper edge. You will later need to handle the case where a player's lower-right corner contacts the upper-left corner of a block, and you have to determine whether the player first hits the block's top (landing) or side (change horizontal velocity and drop).
I wouldn't alter the computed velocity; I would think it's easier just to adjust the final position. The last block of code you posted is where you already deal with a collision as a special case. When you detect a collision, get the top surface (y-value) of the object. If that's above the current position of the bottom of the player, assign that top surface as the correct position. This will leave your player sitting on top of whatever is the highest object on the collision list.
Note that this works only as long as your entire collision list is from falling; for lateral collisions as well, you'll need to separate the lists and adjust each boundary as needed. This can get complicated if there are collisions from several sides on the same time step -- for instance, a goombah slams into the player from the left just as the player hits both a wall and a platform.

Categories