I'm making a game in pygame and I'm trying to move the self.jetRect, which is the rectangular area of the Surface (jetSurface), but when I try updating the coordinates of the rectangle, I get a TypeError: 'tuple' object is not callable. I think this is related to the fact that a tuple is immutable, but I don't know how to fix this.
import pygame
pygame.init()
clock = pygame.time.Clock()
# Screen setup
screen_width = 600
screen_height = 600
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Jet Fighter Game")
# Colors
black = (0, 0, 0)
white = (255, 255, 255)
grey = (100, 100, 100)
# Classes
class PLAYER1:
def __init__(self):
self.jetSurface = pygame.image.load("player1 - blackJet.png")
self.x = 100
self.y = 100
self.speed = 5
self.jetRect = self.jetSurface.get_rect(center = (self.x, self.y))
self.angle = 0
self.rotatedJet = self.jetSurface
def rotateJet(self, surface, angle):
key = pygame.key.get_pressed()
if key[pygame.K_a]:
self.angle += 10
self.rotatedJet = pygame.transform.rotozoom(surface, angle, 1)
self.jetRect = self.rotatedJet.get_rect(center=(self.x, self.y))
if key[pygame.K_d]:
self.angle -= 10
self.rotatedJet = pygame.transform.rotozoom(surface, angle, 1)
self.jetRect = self.rotatedJet.get_rect(center=(self.x, self.y))
if self.angle >= 360:
self.angle = 0
screen.blit(self.rotatedJet, self.jetRect)
def moveJet(self):
key = pygame.key.get_pressed()
if key[pygame.K_w]:
self.y -= self.speed
self.jetRect.center(self.x, self.y)
class PLAYER2:
pass
class BULLET:
pass
class MAIN:
def __init__(self):
self.player1 = PLAYER1()
def rotateJets(self):
self.player1.rotateJet(self.player1.jetSurface, self.player1.angle)
def moveJets(self):
self.player1.moveJet()
main = MAIN()
# Main loop
run = True
while run:
# Checking for events
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
# Drawing on screen
screen.fill(grey)
# Movement and Collisions
main.rotateJets()
main.moveJets()
pygame.display.flip()
clock.tick(60)
pygame.quit()
Its a typo. self.jetRect.center is a tuple of (self.x, self.y), so when you do
self.jetRect.center(self.x, self.y), you are doing (self.x, self.y)(self.x, self.y), hence the error message of calling a tuple.
I think you meant to assign the tuple like self.jetRect.center = (self.x, self.y)
Related
I'm new to Pygame and trying to learn about sprites and collision detection. I found some code and tried to add some functionality to it.
The basic idea is that I have a polygon which can be rotated in place. When the user presses SPACEBAR, the polygon fires a projectile which can bounce around the frame. If the projectile hits the polygon again, I would like to quite the program
First of all, here is the code
import pygame
class Player(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((32, 32))
self.image.fill((0, 0, 0))
self.image.set_colorkey((0, 0, 0))
self.fire_from = (36, 20)
pygame.draw.polygon(self.image, pygame.Color('dodgerblue'), ((0, 0), (32, 16), (0, 32)))
self.org_image = self.image.copy()
self.angle = 0
self.direction = pygame.Vector2(1, 0)
self.rect = self.image.get_rect(center=(200, 200))
self.pos = pygame.Vector2(self.rect.center)
def update(self, events, dt):
for e in events:
if e.type == pygame.KEYDOWN:
if e.key == pygame.K_SPACE:
bullet = Projectile(self.fire_from, self.direction.normalize())
self.groups()[0].add(bullet)
pressed = pygame.key.get_pressed()
if pressed[pygame.K_LEFT]:
self.angle += 3
if pressed[pygame.K_RIGHT]:
self.angle -= 3
self.direction = pygame.Vector2(1, 0).rotate(-self.angle)
self.image = pygame.transform.rotate(self.org_image, self.angle)
self.rect = self.image.get_rect(center=self.rect.center)
class Projectile(pygame.sprite.Sprite):
def __init__(self, pos, direction):
super().__init__()
self.image = pygame.Surface((8, 8))
self.image.fill((0, 0, 0))
self.image.set_colorkey((0, 0, 0))
pygame.draw.circle(self.image, pygame.Color('orange'), (4, 4), 4)
self.rect = self.image.get_rect(center=pos)
self.direction = direction
self.pos = pygame.Vector2(self.rect.center)
self.max_bounce = 10
def update(self, events, dt):
#print(self.pos)
# Bounding box of the screen
screen_r = pygame.display.get_surface().get_rect()
# where we would move next
next_pos = self.pos + self.direction * dt
# we hit a wall
if not screen_r.contains(self.rect):
# after 10 hits, destroy self
self.max_bounce -= 1
if self.max_bounce == 0:
return self.kill()
# horizontal reflection
if next_pos.x > screen_r.right or next_pos.x < screen_r.left:
self.direction.x *= -1
# vertical reflection
if next_pos.y > screen_r.bottom or next_pos.y < screen_r.top:
self.direction.y *= -1
# move after applying reflection
next_pos = self.pos + self.direction * dt
# set the new position
self.pos = next_pos
self.rect.center = self.pos
def main():
pygame.init()
screen = pygame.display.set_mode((500, 500))
tank = Player()
sprites = pygame.sprite.Group()
sprites.add(tank)
#print(tank.groups()[0])
clock = pygame.time.Clock()
dt = 0
running = True
while running:
#print(tank.groups()[0])
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
sprites.update(events, dt)
screen.fill((30, 30, 30))
sprites.draw(screen)
pygame.display.update()
## ALWAYS DETECTS A COLLISION, WHY?
if len(tank.groups()[0])>1:
if pygame.sprite.spritecollideany(tank, tank.groups()[0]):
running = False
dt = clock.tick(60)
if __name__ == '__main__':
main()
I gather here that the Player class (i.e. my tank) itself has a sprite groups, to which it adds each projectile as its fired. Furthermore, the projectile originates from within the rectangle for the polygon (hence it always initially collides)
I would like to only add the projectile to the sprite group if it has made at least 1 bounce, is that possible? Or... is there a better way to do this?
Any help or pointers please?
Thanks
Null
The tank collides with itself, because it is a member of tank.groups()[0]:
if pygame.sprite.spritecollideany(tank, tank.groups()[0]):
Add a Group that contains just the bullets:
class Player(pygame.sprite.Sprite):
def __init__(self):
# [...]
self.bullets = pygame.sprite.Group()
def update(self, events, dt):
for e in events:
if e.type == pygame.KEYDOWN:
if e.key == pygame.K_SPACE:
bullet = Projectile(self.fire_from, self.direction.normalize())
self.groups()[0].add(bullet)
self.bullets.add(bullet)
# [...]
Use this Group for the collision test:
def main():
# [...]
while running:
# [...]
pygame.display.update()
if pygame.sprite.spritecollideany(tank, tank.bullets):
running = False
dt = clock.tick(60)
I am creating a space type game for a school competition. Im going to use stars (white dots) moving outwards to give an effect of moving in space. Next I would check to see if a star's x cords are less than the centre or the screen (move the star to the left) and if the x coordinate is larger (Move to the right). However, I can't seem to make more than one instance of the stars class.Here is an image of the kind of thing I want . Help is very appreciated.
import pygame
import random
pygame.init()
screen = pygame.display.set_mode((1200, 800))
caption = pygame.display.set_caption("sapce game")
screen.fill((56, 56, 56))
white = (255, 255, 255)
class Stars:
def __init__(self):
self.x = random.randint(0, 600)
self.y = random.randint(0, 400)
self.pos = (self.x, self.y)
def move(self):
pygame.draw.circle(screen, white, (self.x, self.y), 4)
self.y -= 1
self.x -= 2
screen.fill((56, 56, 56))
pygame.draw.circle(screen, white, (self.x, self.y), 4)
pygame.display.update()
s = Stars()
run = True
while run:
s.move()
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
quit()
First of all you have to put scrre.fill and pygame.display.update() in the main application loop. Remove this calls from Stars.move:
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
quit()
screen.fill((56, 56, 56))
s.move()
pygame.display.update()
Note, you want to clear the display once, then draw all the stars and finally update the display.
Create a list of stars:
star_list = []
for i in range(10):
star_list.append(Stars())
Move and draw the stars in a loop:
run = True
while run:
# [...]
screen.fill((56, 56, 56))
for s in star_list:
s.move()
pygame.display.update()
Example code:
import pygame
import random
pygame.init()
screen = pygame.display.set_mode((1200, 800))
caption = pygame.display.set_caption("sapce game")
screen.fill((56, 56, 56))
white = (255, 255, 255)
class Stars:
def __init__(self):
self.x = random.randint(0, 600)
self.y = random.randint(0, 400)
self.dx, self.dy = 0, 0
while self.dx == 0:
self.dx = random.randint(-2, 2)
while self.dy == 0:
self.dy = random.randint(-2, 2)
self.pos = (self.x, self.y)
def move(self):
self.x += self.dx
self.y += self.dy
if self.x <= 0 or self.x >= 1200:
self.dx = -self.dx
if self.y <= 0 or self.y >= 800:
self.dy = -self.dy
pygame.draw.circle(screen, white, (self.x, self.y), 4)
star_list = []
for i in range(10):
star_list.append(Stars())
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
quit()
screen.fill((56, 56, 56))
for s in star_list:
s.move()
pygame.display.update()
I'm new to python, and can't fix this. When trying to run my game, I get this error:
Traceback (most recent call last):
File "C:\Users\soni801\Documents\GitHub\volleyball\Player.py", line 15, in draw
window.blit(pygame.image.load("player.png"), 100, 100)
TypeError: invalid destination position for blit
I know other people that have done very similar code without getting this error, and I have no idea why i am getting this.
How do I fix this? I've tried changing literally every single variable, with no luck. Any help appreciated. Here is my code:
In Game.py:
import pygame
from Player import Player
running = True
clock = pygame.time.Clock()
# Images
background = pygame.image.load("bg.png")
# Global variables
WIDTH = 800
HEIGHT = 600
# Player variables
player_size = 70
player_pos = [(WIDTH / 4) - (player_size / 2), HEIGHT - player_size]
player_speed = 5
jump_height = 22
jumping = False
jump_time = -jump_height
window = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Volleyball")
player = Player(player_pos, player_size, player_speed, jump_height, pygame.image.load("player.png"))
def tick():
player.movement()
def render():
window.blit(background, (0, 0))
player.draw(window)
pygame.display.update()
if __name__ == '__main__':
while running:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
tick()
render()
pygame.quit()
In Player.py:
import pygame
class Player(object):
def __init__(self, pos, size, speed, jump_height, image):
self.jump_height = jump_height
self.speed = speed
self.pos = pos
self.image = image
self.jumping = False
self.jump_time = -jump_height
def draw(self, window):
window.blit(self.image, self.pos[0], self.pos[1])
def movement(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_a] and self.pos[0] > 0:
self.pos[0] -= self.speed
if keys[pygame.K_d] and self.pos[0] < 800 - self.pos:
self.pos[0] += self.speed
if not self.jumping:
if keys[pygame.K_SPACE]:
self.jumping = True
else:
if self.jump_time <= self.jump_height:
self.pos[1] += self.jump_time
self.jump_time += 1
else:
self.jumping = False
self.jump_time = -self.jump_height
I haven't tested yet but I think you just need to tuple the pos in your draw function:
not this:
def draw(self, window):
window.blit(self.image, self.pos[0], self.pos[1])
this:
def draw(self, window):
window.blit(self.image, (self.pos[0], self.pos[1]))
I'm trying to make a circle move from the middle of the screen to the top, and then back to the middle, and so on, using Pygame.
import pygame
import sys
pygame.init()
gameOver = False
speed = 5
clock = pygame.time.Clock()
fps = 20
class Screen:
largeur = 600
hauteur = 600
demiLargeur = int(largeur/2)
demiHauteur = int(hauteur/2)
screen = pygame.display.set_mode((Screen.largeur, Screen.hauteur))
class Couleurs:
bleu = (000,000,255)
noir = (000,000,000)
class Cercle:
diametre = 50
epaisseur = 5
posTop = [Screen.demiLargeur, Screen.demiHauteur-2*diametre]
class CirclePos:
top = [Cercle.posTop[0],Cercle.posTop[1]]
circleListTop = [CirclePos.top]
class DrawCircles:
def top (circleListTop):
for CirclePos.top in circleListTop:
pygame.draw.circle(screen, Couleurs.bleu, (CirclePos.top[0],CirclePos.top[1]),Cercle.diametre,Cercle.epaisseur)
class UpdateCirclesPositions:
def top(circleListTop):
for idx,CirclePos.top in enumerate(circleListTop):
if CirclePos.top[1] > Cercle.diametre :
CirclePos.top[1] -= speed
else:
circleListTop.pop(idx)
while not gameOver:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
screen.fill(Couleurs.noir)
clock.tick(fps)
DrawCircles.top(circleListTop)
UpdateCirclesPositions.top(circleListTop)
pygame.display.update()
I have this code so far, the idea being to make it move up, make it desappear, and then create another list to move circles from top to middle. I feel it's a bad idea.
Any idea ?
Thanks
As for me you have too much classes. In class Circle you should have its properties and methods update and draw.
When circle is at the top then it should change speed - speed = -speed. The same when it is in the middle. This way it may move all time. But it may need variable direction to check if it already changed direction because it can be in place where it may change speed all time (in every move).
import pygame
# --- constans ---
SCREEN_WIDTH = 600
SCREEN_HEIGHT = 600
FPS = 20
SPEED = 5
# --- classes ---
class Colors:
blue = (000, 000, 255)
black = (000, 000, 000)
red = (255, 000, 000)
class Circle:
def __init__(self, x, y, size=50, thick=5, color=Colors.blue, speed=SPEED):
self.size = size
self.thick = thick
self.color = color
self.rect = pygame.Rect(0, 0, 2*size, 2*size)
self.rect.centerx = x
self.rect.centery = y
if speed >= 0:
self.direction = 'up'
else:
self.direction = 'down'
self.speed = speed
def draw(self, screen):
pygame.draw.circle(screen, self.color, self.rect.center, self.size, self.thick)
def update(self):
self.rect.y -= self.speed
if self.rect.top <= 0 and self.direction == 'up':
self.direction = 'down'
self.speed = -self.speed
if self.rect.bottom >= screen_rect.centery and self.direction == 'down':
self.direction = 'up'
self.speed = -self.speed
# --- main ---
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
screen_rect = screen.get_rect()
circles = [
Circle(screen_rect.centerx, screen_rect.centery),
Circle(screen_rect.centerx, 0, speed=-SPEED, color=Colors.red)
]
clock = pygame.time.Clock()
game_over = False
while not game_over:
# --- events ----
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_over = True
# --- updates --- (without draws)
for item in circles:
item.update()
# --- draws --- (without updates)
screen.fill(Colors.black)
for item in circles:
item.draw(screen)
pygame.display.update()
clock.tick(FPS)
pygame.quit() # close window
I'm quite new to pygame and came across a bug that i just can't fix on my own. I'm trying to program a Flappy Bird game. The Problem is that the collision detection works, but it also messes with my sprites. If i manage to get past the first obstacle while playing, then the gap resets itself randomly. But the gap should always be the same, just on another position. If i remove the collision detection, it works perfectly fine. Any ideas?
import pygame
import random
randomy = random.randint(-150, 150)
class Bird(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((25,25))
self.image.fill((255,255,255))
self.rect = self.image.get_rect()
self.rect.center = (100, 200)
self.velocity = 0.05
self.acceleration =0.4
def update(self):
self.rect.y += self.velocity
self.velocity += self.acceleration
if self.rect.bottom > 590:
self.velocity = 0
self.acceleration = 0
class Pipe1(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((85, 500))
self.image.fill((255, 255, 255))
self.rect = self.image.get_rect()
self.rect.center = (500, randomy)
self.randomyupdate = random.randint(-150, 150)
def update(self):
self.rect.x -= 2
if self.rect.x < -90:
self.randomyupdate = random.randint(-150, 150)
self.rect.center = (450, self.randomyupdate)
class Pipe2(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((85, 500))
self.image.fill((255, 255, 255))
self.rect = self.image.get_rect()
self.rect.center = (500, (randomy +640))
def update(self):
self.rect.x -= 2
self.randomyupdate = Pipe1.randomyupdate
if self.rect.x < -90:
self.rect.center = (450, (self.randomyupdate + 640))
pygame.init()
pygame.mouse.set_visible(1)
pygame.key.set_repeat(1, 30)
pygame.display.set_caption('Crappy Bird')
clock = pygame.time.Clock()
Bird_sprite = pygame.sprite.Group()
Pipe_sprite = pygame.sprite.Group()
Bird = Bird()
Pipe1 = Pipe1()
Pipe2 = Pipe2 ()
Bird_sprite.add(Bird)
Pipe_sprite.add(Pipe1)
Pipe_sprite.add(Pipe2)
def main():
running = True
while running:
clock.tick(60)
screen = pygame.display.set_mode((400,600))
screen.fill((0,0,0))
Bird_sprite.update()
Pipe_sprite.update()
Bird_sprite.draw(screen)
Pipe_sprite.draw(screen)
The line im talking about:
collide = pygame.sprite.spritecollideany(Bird, Pipe_sprite)
if collide:
running = False
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
pygame.event.post(pygame.event.Event(pygame.QUIT))
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
Bird.rect.y -= 85
Bird.velocity = 0.05
Bird.acceleration = 0.4
Bird.rect.y += Bird.velocity
Bird.velocity += Bird.acceleration
pygame.display.flip()
if __name__ == '__main__':
main()
This has nothing to do with the collision detection, it has to do with the order of the sprites in the sprite group which can vary because sprite groups use dictionaries internally which are unordered (in Python versions < 3.6). So if the Pipe1 sprite comes first in the group, the game will work correctly, but if the Pipe2 sprite comes first, then its update method is also called first and the previous randomyupdate of Pipe1 is used to set the new centery coordinate of the sprite.
To fix this you could either turn the sprite group into an ordered group, e.g.
Pipe_sprite = pygame.sprite.OrderedUpdates()
or update the rect of Pipe2 each frame,
def update(self):
self.rect.x -= 2
self.rect.centery = Pipe1.randomyupdate + 640
if self.rect.x < -90:
self.rect.center = (450, Pipe1.randomyupdate + 640)
Also, remove the global randomy variable and always use the randomyupdate attribute of Pipe1.