Moving a sprite while it is moving pygame - python

The problem I am facing is that I have an enemy sprite that is walking on a predetermined path and he needs to keep doing that, but when I press a button he needs to move to the right for example but he still needs to move on the predetermined path. So it's like the predetermined path has to move to the right on a keypress.
This is my code:
import pygame
import random
import os
import time
from random import choices
from random import randint
from pygame.math import Vector2
import itertools
pygame.init()
a = 0
b = 0
width = 1280
height = 720
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Game")
done = False
n = 0
x = 0
y = 0
x_wall = 0
y_wall = 0
clock = pygame.time.Clock()
WHITE = (255,255,255)
RED = (255,0,0)
change_x = 0
change_y = 0
HW = width / 2
HH = height / 2
background = pygame.image.load('mountains.png')
x1 = 200
y1 = 100
x2 = 500
y2 = 400
x3 = 100
y3 = 300
#player class
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("character.png")
self.rect = self.image.get_rect()
self.rect.x = width / 2
self.rect.y = height / 2
#enemy class
class Enemy(pygame.sprite.Sprite):
def __init__(self, pos, waypoints):
super().__init__()
self.image = pygame.image.load("enemy.png")
self.image = pygame.transform.scale(self.image, (int(50), int(50)))
self.rect = self.image.get_rect(center=pos)
self.vel = Vector2(0, 0)
self.max_speed = 5
self.pos = Vector2(pos)
self.waypoints = waypoints
self.waypoint_index = 0
self.target = self.waypoints[self.waypoint_index]
self.target_radius = 50
self.rect.x = width / 2
self.rect.y = height / 2
def update(self):
# A vector pointing from self to the target.
heading = self.target - self.pos
distance = heading.length() # Distance to the target.
heading.normalize_ip()
if distance <= 2: # We're closer than 2 pixels.
# Increment the waypoint index to swtich the target.
# The modulo sets the index back to 0 if it's equal to the length.
self.waypoint_index = (self.waypoint_index + 1) % len(self.waypoints)
self.target = self.waypoints[self.waypoint_index]
if distance <= self.target_radius:
# If we're approaching the target, we slow down.
self.vel = heading * (distance / self.target_radius * self.max_speed)
else: # Otherwise move with max_speed.
self.vel = heading * self.max_speed
self.pos += self.vel
self.rect.center = self.pos
#Enemy waypoints
waypoints = [(x1, y1), (x2, y2), (x3, y3)]
enemy = Enemy((100, 300), waypoints)
all_sprites = pygame.sprite.Group(enemy)
#wall class
class Wall(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("wall.png")
self.image = pygame.transform.scale(self.image, (int(50), int(50)))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
#wall movement
def update(self):
self.vx = 0
self.vy = 0
key = pygame.key.get_pressed()
if key[pygame.K_LEFT]:
self.vx = 5
self.vy = 0
elif key[pygame.K_RIGHT]:
self.vx = -5
self.vy = 0
if key[pygame.K_UP]:
self.vy = 5
self.vx = 0
elif key[pygame.K_DOWN]:
self.vy = -5
self.vx = 0
self.rect.x = self.rect.x + self.vx
self.rect.y = self.rect.y + self.vy
#player sprite group
sprites = pygame.sprite.Group()
player = Player()
sprites.add(player)
#all the wall sprites
wall_list = pygame.sprite.Group()
wall = Wall(x_wall, y_wall)
wall2 = Wall((x_wall + 50), y_wall)
wall3 = Wall((x_wall + 100), y_wall)
wall4 = Wall((x_wall + 150), y_wall)
wall5 = Wall((x_wall + 200), y_wall)
wall6 = Wall((x_wall + 250), y_wall)
#add all the walls to the list to draw them later
wall_list.add(wall, wall2, wall3, wall4, wall5, wall6)
#add all the walls here to fix the collision
all_walls = (wall, wall2, wall3, wall4, wall5, wall6)
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
#fill the screen
screen.fill((0, 0, 0))
#update sprites
sprites.update()
wall_list.update()
all_sprites.update()
##collision between player and and walls
#if player.rect.collidelist(all_walls) >= 0:
# print("Collision !!")
# player.rect.x = player.rect.x - player.vx
# player.rect.y = player.rect.y - player.vx
#draw the sprites
sprites.draw(screen)
wall_list.draw(screen)
all_sprites.draw(screen)
pygame.display.flip()
clock.tick(30)
pygame.quit()
Here is the download link with the images if you want to run it: https://geordyd.stackstorage.com/s/hZZ1RWcjal6ecZM

Inside your update method of the Enemy class add in some input handling to move the waypoints in the event of a keypress.
key = pygame.get_key_pressed()
if key[pygame.K_RIGHT]:
for i in range(len(waypoints)):
waypoints[i][0] += 2 # Increment the x value of every waypoint
# Ect
I hope this helped you and if you have any further questions please feel free to comment below.

Related

Rotate a rectangle over waves

I am trying to move a rectangle over the waves,trying to simulate a boat sailing.
For that, I rotate the rectangle using the height of the drawn lines and calculating the angle they form with the rectangle.
However, for some reason, in some points of the waves the rectangle is flickering.
The code shows the problem.
import sys
import math
import pygame
from pygame.locals import *
pygame.init()
SCREEN_WIDTH = 900
SCREEN_HEIGHT = 900
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
clock = pygame.time.Clock()
BLUE = (0, 103, 247)
RED = (255,0,0)
watter_levels = [0 for _ in range(SCREEN_WIDTH)]
def draw_water(surface, dy):
amplitude = 35
global watter_levels
for x in range(SCREEN_WIDTH):
y = int(math.sin((x)*(0.01)) * amplitude + dy)
watter_levels[x] = y
pygame.draw.aaline(surface,BLUE,(x,y),(x,y))
def get_water_level(index):
if index <= 0:
return watter_levels[0]
if index >= SCREEN_WIDTH:
return watter_levels[-1]
return watter_levels[index]
font = pygame.font.Font(None,30)
def debug(info,x=10,y=10):
display_surf = pygame.display.get_surface()
debug_surface = font.render(str(info),True,'White')
debug_rect = debug_surface.get_rect(topleft=(x,y))
pygame.draw.rect(display_surf,'Black',debug_rect)
display_surf.blit(debug_surface,debug_rect)
class Ship(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((80,80),pygame.SRCALPHA)
self.image.fill('red')
self.rect = self.image.get_rect(topleft=(0,0))
self.copy_img = self.image.copy()
self.move = pygame.math.Vector2(0,0)
self.copy_img = self.image.copy()
self.velocity = 8
def rotate(self, angle):
self.image = pygame.transform.rotate(self.copy_img, int(angle))
self.rect = self.image.get_rect(center=(self.rect.center))
def update(self):
self.get_input()
self.rect.bottom = get_water_level(self.rect.centerx)
left_y = get_water_level(self.rect.left)
right_y = get_water_level(self.rect.right)
angle = 180*math.atan2(left_y-right_y,self.image.get_width())/math.pi
debug("angle: "+str(int(angle)))
print(self.rect.left,self.rect.right)
self.rotate(angle)
def replicate(self):
if self.rect.left == 363:
return
self.rect.x += 1
def get_input(self):
self.replicate()
# keys = pygame.key.get_pressed()
# if keys[K_LEFT]:
# self.move.x = -self.velocity
# elif keys[K_RIGHT]:
# self.move.x = self.velocity
# else:
# self.move.x = 0
# if keys[K_UP]:
# self.move.y = -self.velocity
# elif keys[K_DOWN]:
# self.move.y = self.velocity
# else:
# self.move.y = 0
# self.rect.x += self.move.x
# self.rect.y += self.move.y
# if self.rect.left <= 0:
# self.rect.left = 0
# if self.rect.right >= SCREEN_WIDTH:
# self.rect.right = SCREEN_WIDTH
ship_sprite = pygame.sprite.GroupSingle()
ship_sprite.add(Ship())
while True:
screen.fill((200,210,255,0))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
ship_sprite.update()
ship_sprite.draw(screen)
draw_water(screen,SCREEN_HEIGHT-300)
clock.tick(60)
pygame.display.update()
It's about accuracy. You can improve the result by storing the water level with floating point precision:
def draw_water(surface, dy):
amplitude = 35
global watter_levels
for x in range(SCREEN_WIDTH):
y = math.sin(x * 0.01) * amplitude + dy
watter_levels[x] = y
pygame.draw.aaline(surface, BLUE, (x, round(y)), (x, round(y)))
Furthermore you have to get the left and right from the "unrotated" rectangle:
class Ship(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((80,80),pygame.SRCALPHA)
self.image.fill('red')
self.rect = self.image.get_rect(topleft = (0, 0))
self.copy_img = self.image.copy()
self.copy_rect = pygame.Rect(self.rect)
self.move = pygame.math.Vector2(0, 0)
self.velocity = 8
def rotate(self, angle):
self.image = pygame.transform.rotate(self.copy_img, round(angle))
self.rect = self.image.get_rect(center = (self.copy_rect.center))
def update(self):
self.get_input()
self.copy_rect.bottom = round(get_water_level(self.copy_rect.centerx))
left_y = get_water_level(self.copy_rect.left)
right_y = get_water_level(self.copy_rect.right)
angle = math.degrees(math.atan2(left_y-right_y, self.copy_img.get_width()))
debug("angle: " + str(round(angle)))
print(self.copy_rect.left, self.copy_rect.right)
self.rotate(angle)
def replicate(self):
if self.copy_rect.left == 363:
return
self.copy_rect.x += 1

Hitboxes only scaling from one side pygame

Im making a plat former
game with pygame
So im creating sprites using
self.rect = self.image.get_rect()
then to get it to scale the hotbox im using
self.rect.inflate(-40,-20)
but this only seems to alter the hotbox from the right side and I think the bottom (Because the x,y starts in the top left of the sprite)
so now my hotbox for my enemy sprite is fine on the left side but still unproportionate on the left side
I hear theres a way to make the hitbox start in the centre of the sprite, How would I do this?
If not how should I go about fixing hotboxes
Thanks in advance,
edit: this is my code for my sprite
#ENEMY SPRITE class
class Enemy(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('img/blob.png')
self.image = pygame.transform.scale(self.image, (65,35))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.move_direction = 1
self.move_counter = 0
def update(self): #update enemy (movement)
self.rect.x += self.move_direction
self.move_counter += 1
if abs(self.move_counter) > 50:
self.move_direction *= -1
self.move_counter *= -1
Full code
#import modules
import pygame
from pygame.locals import *
from pygame import mixer
import pickle
from os import path
#initiliaze pygamee
pygame.mixer.pre_init(44100,-16,2,512) #volume control
mixer.init()
pygame.init()
#fps
clock = pygame.time.Clock()
font = pygame.font.SysFont('Bauhaus 93', 70)
font_score = pygame.font.SysFont('Bauhaus 93',30)
#screen creation/global variables
screen_width = 800
screen_height = 800
tile_size = 40
fps = 60
game_over = 0
main_menu = True
level = 1
max_levels = 7
score = 0
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption('Crashlandingv6')
#color
white = (255,255,255)
red = (255,15,15)
blue = (0,0,200)
#load images
bg_img = pygame.image.load('img/background.jpg')
bg_img = pygame.transform.scale(bg_img, (1000,1000))
earth_img = pygame.image.load('img/earth.png')
earth_img = pygame.transform.scale(earth_img, (100,100))
rect = bg_img.get_rect()
restart_img = pygame.image.load('img/restart_btn.png')
start_img = pygame.image.load('img/start_btn.png')
exit_img = pygame.image.load('img/exit_btn.png')
#Load sounds
pygame.mixer.music.load('img/music.wav')
pygame.mixer.music.play(-1,0.0,15000)
coin_fx = pygame.mixer.Sound('img/coin.wav')
coin_fx.set_volume(0.4)
jump_fx = pygame.mixer.Sound('img/jump.wav')
jump_fx.set_volume(0.4)
game_over_fx = pygame.mixer.Sound('img/gameover.wav')
game_over_fx.set_volume(0.5)
def draw_text(text,font,text_col,x,y):
img = font.render(text,True, text_col)
screen.blit(img,(x,y))
#function to reset level
def reset_level(level):
player.reset(100, screen_height - 130)
blob_group.empty()
lava_group.empty()
exit_group.empty()
if path.exists(f'level{level}_data'):
pickle_in = open(f'level{level}_data', 'rb')
world_data = pickle.load(pickle_in)
world = World(world_data)
return world
#create button class
class Button():
def __init__(self,x,y,image):
self.image = image
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.clicked = False
def draw(self):
action = False
pos = pygame.mouse.get_pos()
#check for button collision (if button was clicked {action}
if self.rect.collidepoint(pos):
if pygame.mouse.get_pressed()[0] == 1 and self.clicked == False:
action = True
self.clicked = True
if pygame.mouse.get_pressed()[0] == 0:
self.clicked = False
#draw button to screen
screen.blit(self.image,self.rect)
return action
#class for player
class Player():
def __init__(self, x, y):
self.reset(x,y)
def update(self,game_over):
dx = 0 #delta x
dy = 0 #delta y
walk_cooldown = 4 #speed
if game_over == 0: #if game is running gameover = 0 if game is over gameover = -1
#get keypresses (controls)
key = pygame.key.get_pressed()
if key[pygame.K_SPACE] and self.jumped == False:
jump_fx.play()
self.vel_y = -15
self.jumped = True
if key[pygame.K_LEFT]:
dx -= 5
self.counter += 1
self.direction = -1
if key[pygame.K_RIGHT]:
dx += 5
self.counter += 1
self.direction = 1
if key[pygame.K_LEFT] == False and key[pygame.K_RIGHT] == False:
self.counter = 0
self.index = 0
if self.direction == 1:
self.image = self.images_right[self.index]
if self.direction == -1:
self.image = self.images_left[self.index]
#TO DO < insert here !!
# add idle player animation if all buttons are false set player to idle
# players animation
if self.counter > walk_cooldown:
self.counter = 0
self.index += 1
if self.index >= len(self.images_right):
self.index = 0
if self.direction == 1:
self.image = self.images_right[self.index]
if self.direction == -1:
self.image = self.images_left[self.index]
#add gravity
self.vel_y += 1
if self.vel_y > 10:
self.vel_y = 10
dy += self.vel_y
#check for collision
for tile in world.tile_list:
#x direction collision
if tile[1].colliderect(self.rect.x + dx, self.rect.y, self.width, self.height):
dx=0
#y direction collision
if tile[1].colliderect(self.rect.x, self.rect.y + dy, self.width, self.height):
#check if below ground (jumping)
if self.vel_y <0:
dy = tile[1].bottom - self.rect.top
self.vel_y = 0
#check if above ground(falling)
elif self.vel_y >= 0:
dy = tile[1].top - self.rect.bottom
self.jumped = False
#check for collision with enemies
if pygame.sprite.spritecollide(self, blob_group, False):
game_over = -1
game_over_fx.play()
#check for collision with lava
if pygame.sprite.spritecollide(self,lava_group,False):
game_over = -1
# check for collision with lava
if pygame.sprite.spritecollide(self, exit_group, False):
game_over = 1
#if gameover (recall gameover = -1 gamerunning = 0)
elif game_over == -1:
self.image = self.dead_image
draw_text('GAME OVER!', font, red, (screen_width //2) - 200, screen_height //2)
if self.rect.y > 200:
self.rect.y -= 5
#update player coordinates
self.rect.x += dx
self.rect.y += dy
#draw player onto screen
screen.blit(self.image, self.rect)
#for rect outlines uncomment #pygame.draw.rect(screen,(255,255,255), self.rect, 2)
return game_over
def reset(self,x,y): #Player class under reset button , when player class is created info gets called from reset for efficiency purposes (instead of typing out twice)
self.images_right = []
self.images_left = []
self.index = 0
self.counter = 0
for num in range(1, 7):
img_right = pygame.image.load(f'img/guy{num}.png')
img_right = pygame.transform.scale(img_right, (40, 80))
img_left = pygame.transform.flip(img_right, True, False) # flips right image on the x axis {true} and not y axis {false}
self.images_right.append(img_right)
self.images_left.append(img_left)
self.dead_image = pygame.image.load('img/ghost.png')
self.image = self.images_right[self.index]
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.width = self.image.get_width()
self.height = self.image.get_height()
self.vel_y = 0
self.jumped = False
self.direction = 0
#class for tiles
class World():
def __init__(self,data):
self.tile_list = []
#load images
dirt_img = pygame.image.load('img/dirt.png')
moonrock_img = pygame.image.load('img/moonrock.png')
#game map
row_count = 0
for row in data:
col_count = 0
for tile in row:
if tile == 1: #replace with dirt
img = pygame.transform.scale(dirt_img,(tile_size,tile_size))
img_rect = img.get_rect()
img_rect.x = col_count * tile_size
img_rect.y = row_count * tile_size
tile = (img,img_rect)
self.tile_list.append(tile)
if tile == 2: #replace with moonrock
img = pygame.transform.scale(moonrock_img, (tile_size, tile_size))
img_rect = img.get_rect()
img_rect.x = col_count * tile_size
img_rect.y = row_count * tile_size
tile = (img, img_rect)
self.tile_list.append(tile)
if tile == 3: #replace with alien
blob = Enemy(col_count * tile_size, row_count * tile_size + 10)
blob_group.add(blob)
if tile == 6: #replace with acid
lava = Lava(col_count * tile_size, row_count * tile_size+(tile_size //2))
lava_group.add(lava)
if tile == 7:
coin = Coin(col_count * tile_size + (tile_size //2), row_count * tile_size + (tile_size // 2))
coin_group.add(coin)
if tile == 8:
exit = Exit(col_count * tile_size, row_count * tile_size - (tile_size//2))
exit_group.add(exit)
col_count += 1
row_count += 1
def draw(self): #draws tiles to screen
for tile in self.tile_list:
screen.blit(tile[0],tile[1])
#for rectangle outlines uncomment #pygame.draw.rect(screen,(255,255,255), tile[1], 1)
def hitbox_from_image(surf):
image_mask = pygame.mask.from_surface(surf)
rect_list = image_mask.get_bounding_rects()
return rect_list[0].unionall(rect_list)
#ENEMY SPRITE class
class Enemy(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('img/blob.png')
self.image = pygame.transform.scale(self.image, (65,35))
self.rect = hitbox_from_image(self.image)
self.rect.x = x
self.rect.y = y
self.move_direction = 1
self.move_counter = 0
def update(self): #update enemy (movement)
self.rect.x += self.move_direction
self.move_counter += 1
if abs(self.move_counter) > 50:
self.move_direction *= -1
self.move_counter *= -1
#LIQUID SPRITE (acid)
class Lava(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
img = pygame.image.load('img/lava2.jpg')
self.image = pygame.transform.scale(img, (tile_size, tile_size//2))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
class Coin(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
img = pygame.image.load('img/coin.png')
self.image = pygame.transform.scale(img, (tile_size//2, tile_size//2))
self.rect = self.image.get_rect()
self.rect.center = (x,y)
class Exit(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
img = pygame.image.load('img/exit.png')
self.image = pygame.transform.scale(img, (tile_size, int(tile_size * 1.5)))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
player = Player(100,screen_height - 130)
blob_group = pygame.sprite.Group()
lava_group = pygame.sprite.Group()
coin_group = pygame.sprite.Group()
exit_group = pygame.sprite.Group()
#score coin dumby coin
score_coin = Coin(tile_size//2, tile_size//2)
coin_group.add(score_coin)
#load in level data and create world
if path.exists(f'level{level}_data'):
pickle_in = open(f'level{level}_data', 'rb')
world_data = pickle.load(pickle_in)
world = World(world_data)
#create buttons
restart_button = Button(screen_width // 2 - 50, screen_height // 2 + 100, restart_img)
start_button = Button(screen_width// 2 - 350, screen_height // 2, start_img)
exit_button = Button(screen_width// 2 + 100, screen_height // 2, exit_img)
#main loop/ WHILE GAME IS RUNNING DO THIS
run = True
while run:
clock.tick(fps) #run the fps timers
screen.blit(bg_img,rect) #add bg img
screen.blit(earth_img,(100,100))
if main_menu == True:
if exit_button.draw():
run = False
if start_button.draw():
main_menu = False
else:
world.draw() #draw the world tiles
if game_over == 0: # while alive / not dead
blob_group.update()
#update score and checking for coin collection
if pygame.sprite.spritecollide(player,coin_group,True):
score += 1
coin_fx.play()
draw_text("X " + str(score), font_score ,white, tile_size - 10, 10)
blob_group.draw(screen)
lava_group.draw(screen)
coin_group.draw(screen)
exit_group.draw(screen)
game_over = player.update(game_over)
#if player is dead
if game_over == -1:
if restart_button.draw():
world_data = []
world = reset_level(level)
game_over = 0
score = 0
#If level complete reset and next level
if game_over == 1:
level += 1
if level <= max_levels:
#reset level
world_date = []
world = reset_level(level)
game_over = 0
else:
draw_text('WINNER WINNER!', font, blue, (screen_width //2) - 140, screen_height // 2)
#restart game
if restart_button.draw():
level = 1
# reset level
world_date = []
world = reset_level(level)
game_over = 0
score = 0
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.display.update() #update display
pygame.quit() #quit game
See rect.inflate:
Returns a new rectangle with the size changed by the given offset. The rectangle remains centered around its current center.
However, while rect.inflate_ip changes the pygame.Rect object itself,
rect.inflate does not change the object, but it returns a new object with a different size
Note, the return value of rect.inflate_ip is None, but the return value of rect.inflate is a new pygame.Rect object.
Either call inflate_ip:
self.rect.inflate_ip(-40,-20)
or assign the return value of inflate to self.rect
self.rect = self.rect.inflate(-40,-20)
"how should I go about fixing hotboxes"
This depends on the area covered by the sprite in the bitmap. The covered area can be computed by using a pygame.mask.Mask object and get_bounding_rects.
Use pygame.mask.from_surface to create a pygame.mask.Mask from a pygame.Surface:
image_mask = pygame.mask.from_surface(self.image)
Get a list containing a bounding rectangles (sequence of pygame.Rect objects) for each connected component with get_bounding_rects:
rect_list = image_mask.get_bounding_rects()
Create the union rectangle of the sequence of rectangles with unionall:
self.rect = rect_list[0].unionall(rect_list)
See also How to get the correct dimensions for a pygame rectangle created from an image
Write a function that gets the job done:
def hitbox_from_image(surf):
image_mask = pygame.mask.from_surface(surf)
rect_list = image_mask.get_bounding_rects()
return rect_list[0].unionall(rect_list)
self.rect = hitbox_from_image(self.image)

Can I get the rect.x from one class to use in another class?

So basically I want to my Road class to always know where my Car (Player1) class is so road knows whether to slow down as Car has left road (or be able to speed back up when Car gets back within road area.
As Car only moves left / right and it is the road that speeds up coming down screen, I figured a way to mimic the car using a "self.where_player" at same start position and then add / subtract x position and follow it with key's pressed (a, d), but after awhile it can lose Car placement as Car can either have x values added or subtracted while slipping left or right on ice or have it keys (a, d) temporarily disabled if spinning on oil, while the road does not know these things...
If I could figure out how the Road class could "always" know where the Car class rect.x was, well that would be fantastic!
import pygame
W = 1000
H = 800
pygame.init()
pygame.display.set_mode((W, H))
class Road1(pygame.sprite.Sprite):
def __init__(self, top, player):
pygame.sprite.Sprite.__init__(self)
bkgrnd = pygame.image.load("Images/Road.png").convert_alpha()
self.image = pygame.transform.scale(bkgrnd, (300, 100))
self.rect = self.image.get_rect(topleft = (120, top))
self.top = top
self.speedy = 0
self.player = player
self.out_of_bounds = False
def update(self):
self.top += self.speedy
if self.top >= 800:
self.top = self.top - 900
self.rect.top = self.top
keys = pygame.key.get_pressed()
if keys[pygame.K_w]:
self.speedy = min(20, self.speedy + 0.25)
if keys[pygame.K_s]:
self.speedy = max(0, self.speedy - 0.25)
if keys[pygame.K_a]:
self.player -= 5
if self.player <= 120:
self.player = 120
self.out_of_bounds = True
if self.out_of_bounds and self.speedy > 5:
self.speedy -= 0.35
else:
self.out_of_bounds = False
if keys[pygame.K_d]:
self.player += 5
if self.player >= 420:
self.player = 420
self.out_of_bounds = True
if self.out_of_bounds and self.speedy > 5:
self.speedy -= 0.35
else:
self.out_of_bounds = False
The following is a portion from the Main page enough to make Road work:
import pygame
import random
import sys
# from Ice_Patch import Ice1, Ice2
# from Oil_Slick import Oil1, Oil2
from Player1 import Player1, car1_image
# from Player2 import Player2, car2_image
from Roads import Road1, # Road2
W, H = 1000, 800
HW, HH = W / 2, H / 2
AREA = W * H
FPS = 30
GREEN = (0, 200, 0)
pygame.init()
CLOCK = pygame.time.Clock()
DS = pygame.display.set_mode((W, H))
pygame.display.set_caption("Racing Fury")
def events():
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
player = Player1()
all_sprites = pygame.sprite.Group()
road = pygame.sprite.Group()
for i in range(9):
rd = Road1((i - 1) * 100, player)
all_sprites.add(rd)
road.add(rd)
while True:
CLOCK.tick(FPS)
events()
all_sprites.update()
DS.fill(GREEN)
all_sprites.draw(DS)
pygame.display.flip()
Player1 Class:
import pygame
W = 1000
H = 800
pygame.init()
pygame.display.set_mode((W, H))
car1_image = pygame.image.load("Images/GalardB.png").convert_alpha()
car1_image = pygame.transform.scale(car1_image, (40, 70))
GREY = (211, 211, 211)
ANIM_DELAY = 50
POWERUP_TIME = 5000
SPIN_TIME = 1950
class Player1(pygame.sprite.Sprite):
def __init__(self, car1_image, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = car1_image
self.image.set_colorkey(GREY)
self.rect = self.image.get_rect()
self.rect.center = (x, y)
self.radius = 25
self.speedx = 0
self.rotations = [self.image]
self.spinning_since = 0
self.rotate()
self.damage = 100
self.shoot_delay = 250
self.last_shot = pygame.time.get_ticks()
self.lives = 3
self.hidden = False
self.hide_timer = pygame.time.get_ticks()
self.score = 0
self.crashx = 0
self.crashy = 0
self.power = 0
self.power_time = pygame.time.get_ticks()
self.not_spinning = True
def update(self):
if self.power >= 1 and pygame.time.get_ticks() - self.power_time > POWERUP_TIME:
self.power -= 1
self.power_time = pygame.time.get_ticks()
if self.hidden and pygame.time.get_ticks() - self.hide_timer > 1500:
self.hidden = False
self.rect.center = (300, 700)
self.speedx = 0
keystate1 = pygame.key.get_pressed()
if keystate1[pygame.K_SPACE]:
self.shoot()
if self.not_spinning:
if keystate1[pygame.K_a]:
self.speedx = -5
if keystate1[pygame.K_d]:
self.speedx = 5
self.rect.x += self.speedx
if self.rect.right > 420:
self.rect.right = 420
if self.rect.left < 120:
self.rect.left = 120
if self.spinning_since > 0:
time_spinning = pygame.time.get_ticks() - self.spinning_since
if time_spinning >= SPIN_TIME:
self.spinning_since = 0
index = 0
self.not_spinning = True
else:
index = int(time_spinning / ANIM_DELAY)
index %= len(self.rotations)
self.not_spinning = False
cx, cy = self.rect.center
self.image = self.rotations[index]
self.rect = self.image.get_rect()
self.rect.centerx = cx
self.rect.centery = cy
def powerup(self):
self.power += 1
self.power_time = pygame.time.get_ticks()
def shoot(self):
now = pygame.time.get_ticks()
if now - self.last_shot > self.shoot_delay:
self.last_shot = now
if self.power == 0:
pass
if self.power >= 1:
bullet1 = Bullet(self.rect.centerx, self.rect.top)
all_sprites.add(bullet1)
bullets1.add(bullet1)
def rotate(self):
ROT_FRAMES = 36
for i in range(ROT_FRAMES):
angle = i * (720 / ROT_FRAMES)
rotated_image = pygame.transform.rotozoom(self.image, angle, 1)
self.rotations.append(rotated_image)
def spin(self):
if self.spinning_since == 0:
self.spinning_since = pygame.time.get_ticks()
else:
pass
def slide(self):
keystate1 = pygame.key.get_pressed()
if keystate1[pygame.K_a]:
self.rect.x -= 2
self.rect.x -= 1.5
self.rect.x -= 1
self.rect.x -= 0.5
self.rect.x -= 0.25
if keystate1[pygame.K_d]:
self.rect.x += 2
self.rect.x += 1.5
self.rect.x += 1
self.rect.x += 0.5
self.rect.x += 0.25
def hide(self):
self.crashx = self.rect.x
self.crashy = self.rect.y
self.hidden = True
self.hide_timer = pygame.time.get_ticks()
self.rect.center = (W * 2, H * 2)
The easiest solution is to make the "road" knowing the player. Add an attribute player the class Road1:
class Road1(pygame.sprite.Sprite):
def __init__(self, top, player):
pygame.sprite.Sprite.__init__(self)
self.player = player
# [...]
Instead of self.where_player you can directly ask self.player for its position.
Pass the player to the road objects when they are constructed. e.g.:
player = Player1(?????)
all_sprites = pygame.sprite.Group()
road = pygame.sprite.Group()
for i in range(9):
rd = Road1((i - 1) * 100, player)
all_sprites.add(rd)
road.add(rd)
An other option would be to add an argument player to the method update:
class Road1(pygame.sprite.Sprite):
# [...]
def update(self, player):
# [...]
Of course you have to separate the update of road and player. e.g.:
player.update()
road.update(player)

TypeError: argument 1 must be pygame.Surface, not list [closed]

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]

Drawing images onto Pygame's background or screen

I am writing a game with pygame which involves two player-controlled tanks which go around a brick map with their own health bars and shoot each other with one kind of bullet. From what I can tell, everything else is working, but I am having trouble determining which surface to draw the bricks, tanks, and bullets on: the "background" or the "screen" (the bricks do not change during the game, only the tanks and bullets). I tried drawing everything but the health bars on the background, but that just resulted in a black screen which only displayed the health bars. I then drew everything directly onto the screen and that displays everything correctly at first, but when the tanks move around, the screen doesn't refresh (I get many many tank images overlapping each other as the tank moves) and the bullets do not fire properly as a result. The code is below but the main() method is at the very bottom and that is what is causing errors. The "effects.py" file that is imported at the very top contains just the bullets and "booms" and "bombs" classes which are just special effects but aren't used so far in the main() method so they can be ignored.
from __future__ import print_function, division
import pygame, os, sys
from pygame.locals import *
import random
import effects
from effects import *
import math
GRAD = math.pi / 180
class Brick(pygame.sprite.Sprite):
def __init__(self, pos, image, top, bottom, right, left, bricks):
pygame.sprite.Sprite.__init__(self)
self.rect = image.get_rect(topleft = pos)
self.image = image
self.pos = pos
self.top = top
self.bottom = bottom
self.right = right
self.left = left
self.health = 30
bricks.add(self)
class City(object):
def __init__(self, bricks, level):
self.level = level
self.city = self.level.convert_alpha()
self.brick = pygame.image.load("brick.png").convert_alpha()
self.bricks = bricks
self.x = self.y = 0
collidable = (255, 0, 0, 255)
self.height = self.city.get_height()
self.width = self.city.get_width()
self.vehicle_pos = (0,0)
while self.y < self.height:
color = self.city.get_at((self.x, self.y))
collidable = (255, 0, 0, 255), (0,0,0,255)
top = False
bottom = False
right = False
left = False
if color in collidable:
self.bricks.add(Brick((self.x*30, self.y*30), self.brick, top, bottom, right, left, self.bricks))
print ("brick added!")
print (self.x, self.y)
self.x += 1
if self.x >= self.width:
self.x = 0
self.y += 1
def get_size(self):
return [self.city.get_size()[0]*30, self.city.get_size()[1]*30]
class Tank(pygame.sprite.Sprite):
book = {} # a book of tanks to store all tanks
number = 0 # each tank gets his own number
firekey = (pygame.K_SPACE, pygame.K_RETURN)
forwardkey = (pygame.K_w, pygame.K_i)
backwardkey = (pygame.K_s, pygame.K_k)
tankLeftkey = (pygame.K_a, pygame.K_j)
tankRightkey = (pygame.K_d, pygame.K_l)
color = ((200,200,0), (0,0,200))
def __init__(self, pos, angle, health):
self.number = Tank.number
Tank.number += 1
Tank.book[self.number] = self
pygame.sprite.Sprite.__init__(self)
self.tank_pic = pygame.image.load("tank.png").convert_alpha()
self.image = self.tank_pic
self.image_type = self.tank_pic
self.tank1_pic = pygame.image.load("tank1.png").convert_alpha()
self._image = self.image
self.rect = self.image.get_rect()
self.rect = self.rect.move(pos)
self.tankAngle = angle # tank facing
#---handles controls---#
self.firekey = Tank.firekey[self.number] # main gun
self.forwardkey = Tank.forwardkey[self.number] # move tank
self.backwardkey = Tank.backwardkey[self.number] # reverse tank
self.tankLeftkey = Tank.tankLeftkey[self.number] # rotate tank
self.tankRightkey = Tank.tankRightkey[self.number] # rotat tank
self.health = health
self.alive = True
self.speed = 5
self.angle = angle
self.timer = 3
self.timerstart = 0
self.x, self.y = self.rect.center
self.bullet_s = pygame.mixer.Sound("bullet.wav")
self.bullet_s.set_volume(.25)
def rotate(self):
center = self.rect.center
self.image = pygame.transform.rotozoom(self._image, self.angle, 1.0)
self.rect = self.image.get_rect(center = center)
def update(self, keys, bricks, bullets, booms, bombs):
self.bricks = bricks
self.t = True
self._rect = Rect(self.rect)
self._rect.center = self.x, self.y
self.rotate()
turn_speed = 3
pressedkeys = pygame.key.get_pressed()
if pressedkeys[self.forwardkey]:
self.x += sin(radians(self.angle))*-self.speed
self.y += cos(radians(self.angle))*-self.speed
if pressedkeys[self.backwardkey]:
self.x += sin(radians(self.angle))*self.speed
self.y += cos(radians(self.angle))*self.speed
if pressedkeys[self.tankLeftkey]:
self.angle += turn_speed
if pressedkeys[self.tankRightkey]:
self.angle -= turn_speed
if keys[self.firekey]:
if self.timer >= 3:
self.timer = self.timerstart
self.b_size = "small"
bullets.add(Bullet(self.rect.center, self.angle, self.b_size, "vehicle"))
self.bullet_s.play()
if self.timer < 3:
self.timer += 1
if self.angle > 360:
self.angle = self.angle-360
if self.angle <0:
self.angle = self.angle+360
self.rect.center = self.x, self.y
x = self.rect.centerx
y = self.rect.centery
_x = self._rect.centerx
_y = self._rect.centery
for b in bricks:
if self.rect.colliderect(b.rect):
if _x+21 <= b.rect.left and x+21 > b.rect.left:
if b.left == True:
self.x = b.rect.left-21
if _x-21 >= b.rect.right and x-21 < b.rect.right:
if b.right == True:
self.x = b.rect.right+21
if _y+21 <= b.rect.top and y+21 > b.rect.top:
if b.top == True:
self.y = b.rect.top-21
if _y-21 >= b.rect.bottom and y-21 < b.rect.bottom:
if b.bottom == True:
self.y = b.rect.bottom+21
for b in bullets:
if self.rect.colliderect(b.rect):
b_size = b.get_size()
pygame.sprite.Sprite.kill(b)
if b_size == "small":
booms.add(Boom(b.rect.center, "small"))
self.health -= 1
if b_size == "big":
booms.add(Boom(b.rect.center, "big"))
self.health -=5
for b in bombs:
if self.rect.colliderect(b.rect) and b.timer == 20:
self.health -=5
if self.health <= 0:
booms.add(Boom(self.rect.center, "huge"))
self.alive = False
self.health = 0
'''if self.image_type == self.tank_pic:
self.image = self.tank1_pic
self.image_type = self.tank1_pic
print "switch 1"
if self.image_type == self.tank1_pic:
self.image = self.tank_pic
self.image_type = self.tank_pic
print "switch 2"'''
class Turret(pygame.sprite.Sprite):
def __init__(self, pos, angle, follow):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("turret.png").convert_alpha()
self.timer = 40
self.timer_start = self.timer
self.size = "big"
self.bang_s = pygame.mixer.Sound("bang.wav")
self.speed = 3
self.bang_s.set_volume(1.0)
self._image = self.image
self.rect = self.image.get_rect()
self.rect = self.rect.move(pos)
self.angle = angle
self.timer = 40
self.wait_timer = 5
self.timer_restart = 0
self.x, self.y = self.rect.center
self.follow = follow
def rotate(self):
center = self._rect.center
self.image = pygame.transform.rotozoom(self._image, self.angle, 1.0)
self.rect = self.image.get_rect(center = center)
def update(self, pos, mx, my, keys, booms, tank_angle, bricks, background, city):
self.background = background
self.city_size = city
self.t = True
self.bricks = bricks
self.end = None
self._rect = Rect(self.rect)
self._rect.center = pos
self.tank_angle = tank_angle
t_x, t_y = self.rect.center
if keys[K_m]:
if self.wait_timer >= 5:
self.follow = not self.follow
self.wait_timer = self.timer_restart
if self.follow:
self.mouse_angle = math.atan2(xd, yd)*(180/math.pi)+180 #used atan2(x,y) instead of atan2(y,x). Sprite was origanly drawn along the y axis, gave better results
if self.angle < self.mouse_angle:
if math.fabs(self.angle - self.mouse_angle) < 180:
self.angle +=self.speed
else:
self.angle -=self.speed
else:
if math.fabs(self.angle - self.mouse_angle) < 180:
self.angle -=self.speed
else:
self.angle +=self.speed
if math.fabs(self.angle - self.mouse_angle) < self.speed+.5:
self.angle = self.mouse_angle
if not self.follow:
if self.angle != self.tank_angle:
if self.angle < self.tank_angle:
if math.fabs(self.angle - self.tank_angle) < 180:
self.angle +=self.speed
else:
self.angle -=self.speed
else:
if math.fabs(self.angle - self.tank_angle) < 180:
self.angle -=self.speed
else:
self.angle +=self.speed
if math.fabs(self.angle - self.tank_angle) < self.speed+.5:
self.angle = self.tank_angle
else:
self.angle = self.tank_angle
self.rotate()
if self.angle > 360:
self.angle = self.angle-360
if self.angle <0:
self.angle = self.angle+360
if self.wait_timer < 5:
self.wait_timer += 1
# ---------- END OF CLASSES ---------- #
def main():
pygame.init()
version = "Tank Wars 2.0"
screen = pygame.display.set_mode((1170, 510),0,32)
n = 1
size = screen.get_size()
pygame.mouse.set_visible(False)
map_ = pygame.image.load("c2.png")
health = 40
health_full = health
bricks= pygame.sprite.Group()
bricks_des = pygame.sprite.Group()
bricks_non = pygame.sprite.Group()
bullets = pygame.sprite.Group()
booms = pygame.sprite.Group()
bombers = pygame.sprite.Group()
bombs = pygame.sprite.Group()
tanks = pygame.sprite.Group()
allgroup = pygame.sprite.LayeredUpdates()
#assign default groups to each sprite class
#Tank.groups = tanks, allgroup
#Turret.groups = allgroup
#Bullet.groups = bullets, allgroup
city = City(bricks, map_)
city_size = city.get_size()
clock = pygame.time.Clock()
timer = 0
chance = None
score = 0
player1 = Tank((150, 250), 360, 40)
tanks.add(player1)
player2 = Tank((1100, 250), 360, 40)
tanks.add(player2)
player1_turret = Turret((150, 250), 360, False)
player2_turret = Turret((1100, 250), 360, False)
background = pygame.Surface((city_size), 0, 32)
city.bricks.draw(screen)
font4 = pygame.font.Font("7theb.ttf", 13)
font5 = pygame.font.SysFont("Courier New", 16, bold=True)
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
return
clock.tick(24)
time_passed = clock.tick()
keys = pygame.key.get_pressed()
m_x, m_y = pygame.mouse.get_pos()
background.fill((87, 87, 87))
if player1.alive == True:
player1.update(keys, bricks, bullets, booms, bombs)
player1_turret.update(player1.rect.center, m_x, m_y, keys, booms, 360, bricks, background, city_size)
screen.blit(player1.image, player1.rect)
screen.blit(player1_turret.image, player1_turret.rect)
if player2.alive == True:
player2.update(keys, bricks, bullets, booms, bombs)
player2_turret.update(player2.rect.center, m_x, m_y, keys, booms, 360, bricks, background, city_size)
screen.blit(player2.image, player2.rect)
screen.blit(player2_turret.image, player2_turret.rect)
bullets.update(bricks, booms)
bombs.update(booms, player1)
bombs.update(booms, player2)
booms.update(background)
bombs.draw(screen)
bullets.draw(screen)
bombers.draw(screen)
healthshow = font4.render('Health ', False, (255,255,255))
#---Player 1 Healthbar---#
pygame.draw.ellipse(screen, (255, ((player1.health*255)/health_full),0), (90, 20, 10, 13))
pygame.draw.ellipse(screen, (255, ((player1.health*255)/health_full),0), (92+(100*(float(player1.health)/float(health_full))), 20, 10, 13))
screen.fill((255,((player1.health*255)/health_full),0),(96,20,(100*(float(player1.health)/float(health_full))), 13))
screen.blit(healthshow, (5, 20))
#---Player 2 Healthbar---#
pygame.draw.ellipse(screen, (255, ((player2.health*255)/health_full),0), (90, 20, 10, 13))
pygame.draw.ellipse(screen, (255, ((player2.health*255)/health_full),0), (92+(100*(float(player2.health)/float(health_full))), 20, 10, 13))
screen.fill((255,((player2.health*255)/health_full),0),(96,20,(100*(float(player2.health)/float(health_full))), 13))
screen.blit(healthshow, (500, 20))
allgroup.clear(screen, background)
pygame.display.flip()
if __name__ == "__main__":
main()
My suggestion is to make a global screen variable (instead of being wrapped inside a function, and generate a single city image, with the bricks added to it. Since those are all the components that do not change, this can be blitted to the screen every time the frame is refreshed.
After that, on each frame, the tank (with health bar and other stuff) and bullets can be blitted directly to the screen variable.
It is probably simplest to just do pygame.display.flip() after all of the blitting is done, but keeping an array of pygame.Rects representing blit positions and then keeping the old blit Rects and then doing something like:
old_rects = current_rects
current_rects = []
#make blits, collect Rects in current_rects
pygame.display.update(current_rects + old_rects)
This way, pygame.display.update() can be used (since it is more efficient), and there won't be problems with no refreshing.
I hope this works and that you can implement something like this.

Categories

Resources