PyGame rect.move movement not functioning properly - python

I am developing my first pygame application in the form of a brick breaker clone. For the player paddle, I am checking for key holds in the main game loop and then redrawing the player object's sprite every frame, as show in the code snippet below:
class Player():
def __init__(self):
self.sprite = pg.transform.scale(pg.image.load('49-Breakout-Tiles.png'), (61,16))
self.rect = self.sprite.get_rect()
self.rect.right += displayRect.center[0]-25
self.rect.top += displayRect.center[1]+450
self.draw(0)
def draw(self, x):
pg.draw.rect(display, black, (self.rect.x, self.rect.y, 61, 16))
self.rect.right += x
if not displayRect.contains(self.rect):
self.rect.right -= x
display.blit(self.sprite, self.rect)
#from gameloop
moveNeg, movePos = False, False
while True:
for event in pg.event.get():
if event.type == pg.KEYDOWN:
if event.key == pg.K_LEFT or event.key == pg.K_a:
moveNeg = True
if event.key == pg.K_RIGHT or event.key == pg.K_d:
movePos = True
if event.type == pg.KEYUP:
if event.key == pg.K_LEFT or event.key == pg.K_a:
moveNeg = False
if event.key == pg.K_RIGHT or event.key == pg.K_d:
movePos = False
if moveNeg:
player.draw(-1)
if movePos:
player.draw(1)
This code works fine, and also ensures that the player paddle stays within the display bounds.
However, for the ball object, I am trying to use rect.move(x,y) to move it. I have already tried with rect.right and rect.top but the ball object is still unresponsive. Here is the code I have so far:
class Ball():
def __init__(self):
self.sprite = pg.transform.scale(pg.image.load('58-Breakout-Tiles.png'), (16, 16))
self.rect = self.sprite.get_rect()
self.rect.x += displayRect.center[0]
self.rect.y += displayRect.center[1]
self.dir = [random.uniform(-1.0,1.0), random.uniform(-1.0,1.0)]
print(self.dir)
def draw(self, xy):
pg.draw.rect(display, black, (self.rect.x, self.rect.y, 16, 16))
self.rect = self.rect.move(xy[0], xy[1])
display.blit(self.sprite, self.rect)
The ball is centered when the object is initialised (as shown on lines 3/4 of the __init__ function, but I don't see why this would affect the movement of the ball's rect. Also for clarity, self.dir is used to give the ball a random starting direction, and I am aware that it is able to move upward with this current configuration.
Since I am new to pygame it completely baffles me as to why my logic is flawed so any help is much appreciated.
Thanks

pygame.Rect.move doesn't change the Rect object itself, but it returns an new Rect object (Probably the name of the method is misleading).
This means, that
self.rect.move(xy[0], xy[1])
doesn't change anything. You've to assign the rectangle which is returned by .move to "itself":
self.rect = self.rect.move(xy)
Alternatively you can use pygame.Rect.move_ip(), moves the rectangle "in place" and changes the Rect object:
self.rect.move_ip(xy[0], xy[1])

Related

How can I fix my Pygame collision system? [duplicate]

Is there a way in pygame to look for a collision between the a particular side of a sprite and a particular side of another sprite in pygame? For example, if the top of sprite A collides with the bottom of Sprite B, return True.
I am certain there is a way to do this, but I can't find any particular method in the documentation.
Thanks!
There is no function to get sides collision in PyGame.
But you could try to use pygame.Rect.collidepoint to test if A.rect.midleft, A.rect.midright, A.rect.midtop, A.rect.midbottom, A.rect.topleft, A.rect.bottomleft , A.rect.topright, A.rect.bottomright are inside B.rect (pygame.Rect).
EDIT:
Example code. Use arrows to move player and touch enemy.
(probably it is not optimal solution)
import pygame
WHITE = (255,255,255)
BLACK = (0 ,0 ,0 )
RED = (255,0 ,0 )
GREEN = (0 ,255,0 )
BLUE = (0 ,0 ,255)
class Player():
def __init__(self, x=0, y=0, width=150, height=150):
self.rect = pygame.Rect(x, y, width, height)
self.speed_x = 5
self.speed_y = 5
self.move_x = 0
self.move_y = 0
self.collision = [False] * 9
self.font = pygame.font.SysFont("", 32)
self.text = "";
def set_center(self, screen):
self.rect.center = screen.get_rect().center
def event_handler(self, event):
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
self.move_x -= self.speed_x
elif event.key == pygame.K_RIGHT:
self.move_x += self.speed_x
elif event.key == pygame.K_UP:
self.move_y -= self.speed_y
elif event.key == pygame.K_DOWN:
self.move_y += self.speed_y
elif event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT:
self.move_x += self.speed_x
elif event.key == pygame.K_RIGHT:
self.move_x -= self.speed_x
elif event.key == pygame.K_UP:
self.move_y += self.speed_y
elif event.key == pygame.K_DOWN:
self.move_y -= self.speed_y
def update(self):
self.rect.x += self.move_x
self.rect.y += self.move_y
def draw(self, screen):
pygame.draw.rect(screen, WHITE, self.rect, 2)
self.draw_point(screen, self.rect.topleft, self.collision[0])
self.draw_point(screen, self.rect.topright, self.collision[1])
self.draw_point(screen, self.rect.bottomleft, self.collision[2])
self.draw_point(screen, self.rect.bottomright, self.collision[3])
self.draw_point(screen, self.rect.midleft, self.collision[4])
self.draw_point(screen, self.rect.midright, self.collision[5])
self.draw_point(screen, self.rect.midtop, self.collision[6])
self.draw_point(screen, self.rect.midbottom, self.collision[7])
self.draw_point(screen, self.rect.center, self.collision[8])
def draw_point(self, screen, pos, collision):
if not collision:
pygame.draw.circle(screen, GREEN, pos, 5)
else:
pygame.draw.circle(screen, RED, pos, 5)
def check_collision(self, rect):
self.collision[0] = rect.collidepoint(self.rect.topleft)
self.collision[1] = rect.collidepoint(self.rect.topright)
self.collision[2] = rect.collidepoint(self.rect.bottomleft)
self.collision[3] = rect.collidepoint(self.rect.bottomright)
self.collision[4] = rect.collidepoint(self.rect.midleft)
self.collision[5] = rect.collidepoint(self.rect.midright)
self.collision[6] = rect.collidepoint(self.rect.midtop)
self.collision[7] = rect.collidepoint(self.rect.midbottom)
self.collision[8] = rect.collidepoint(self.rect.center)
def render_collision_info(self):
text = "collision: "
print "collision:",
if self.collision[0] or self.collision[2] or self.collision[4]:
text += "left "
print "left",
if self.collision[1] or self.collision[3] or self.collision[5]:
text += "right "
print "right",
if self.collision[0] or self.collision[1] or self.collision[6]:
text += "top "
print "top",
if self.collision[2] or self.collision[3] or self.collision[7]:
text += "bottom "
print "bottom",
if self.collision[8]:
text += "center "
print "center",
print
self.text = self.font.render(text, 1, WHITE)
def draw_collision_info(self, screen, pos):
screen.blit(self.text, pos)
#----------------------------------------------------------------------
class Game():
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode( (800,600) )
pygame.display.set_caption("Side Collision")
self.player = Player()
self.enemy = Player()
self.enemy.set_center(self.screen)
def run(self):
clock = pygame.time.Clock()
RUNNING = True
while RUNNING:
# --- events ----
for event in pygame.event.get():
if event.type == pygame.QUIT:
RUNNING = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
RUNNING = False
self.player.event_handler(event)
# --- updates ---
self.player.update()
self.enemy.update()
self.player.check_collision(self.enemy.rect)
self.enemy.check_collision(self.player.rect)
self.player.render_collision_info()
self.enemy.render_collision_info()
# --- draws ----
self.screen.fill(BLACK)
self.player.draw(self.screen)
self.enemy.draw(self.screen)
self.player.draw_collision_info(self.screen, (0,0))
self.enemy.draw_collision_info(self.screen, (0,32))
pygame.display.update()
# --- FPS ---
clock.tick(30)
pygame.quit()
#----------------------------------------------------------------------
Game().run()
EDIT (08.2016): version with colllisions rect, rect_ratio, circle
GitHub: furas/python-examples/pygame/collisions
The logic behind collision is like that:
#Assume two surfaces
objectA
objectB
if objectA.right > objectB.left and objectA.left < objectB.right and objectA.top < objectB.bottom and objectA.bottom > objectB.top
#Collision happens
if you want to detect side collision (as if objectA hitted objectB on the side) you can do the following:
#Here is the code where objectA moves
objectA.X += 10
#Here check Collision, if collision happens objectA hitted objectB from the side
objectA.Y += 10
#Here check Collision again, if collision is true then objectA hitted object B from top/bottom
I solved this by creating multiple collide boxes. This should help a lot of people.
https://youtu.be/W-Vz6le1YUg
Code:
if tanner.axe.colliderect(oak.red) and.
tanner.playerHitBox.colliderect(oak.leftHit) and.
keys_pressed[pygame.K_SPACE]:
Number_of_Hits_Left += 1
print(Number_of_Hits_Left)
if tanner.axe.colliderect(oak.red) and.
tanner.playerHitBox.colliderect(oak.rightHit) and.
keys_pressed[pygame.K_SPACE]:
Number_of_Hits_Right += 1
print(Number_of_Hits_Right)
So I have a total of 5 hit boxes to accomplish said mission. And really all you would have to do is create your main hit box, then create 2 side boxes on the left and right side of main hit box, so that they barely overlap. So let's say you shoot a bullet your code would be something like above. "When bullet collides with side box AND when bullet collides with main box, do something."
Like Furas has said, no, there is not way to get side collisions in Pygame past the point system he set up. And even that one wont give you what you want, because you can never be sure which direction the collision happened when dealing with rows, columns or corners of Rectangles.
This is why most tutorials recommend saving your sprites initial direction. then moving in the opposite direction in case of a collision.
For objectA give the object this method:
def is_collided_with(self, sprite):
return self.rect.colliderect(sprite.rect)
This return statement returns either True or False
then in the main loop for collisions just do:
if objectA.is_collided_with(ObjectB):
Collision happened!

Bullets wont show when fired... Attributes seems to show bullet is moving but wont show [duplicate]

This question already has answers here:
Why is nothing drawn in PyGame at all?
(2 answers)
Closed 1 year ago.
Below is the code... Bullet class is defined first, class is called in _fire_bullet, _update_screen, and _check_KEYDOWN. When spacebar is pressed the event handler should take in the event key, call _fire_bullet function where a bullet object is created and added to sprite list. Then fired and moves across screen. As of now its going to move the wrong direction but I will correct that issue easily when I can actually see the bullet.
#make a pygame window with a blue background
import pygame
import sys
from pygame.sprite import Sprite
class Bullet(Sprite):
""" A class to manage bullets fired from the ship """
def __init__(self, game):
""" create a bullet object at the ships current position """
super().__init__()
self.screen = game.screen
self.bullet_speed = 1.0
self.bullet_width = 300
self.bullet_height = 150
self.bullet_color = (0, 200, 200)
#create a bullet at rect (0,0) and the set the correct position
self.rect = pygame.Rect(0, 0, self.bullet_width, self.bullet_height)
self.rect.midright = game.rect.midright
#store the bullets position as a decimal value
self.y = float(self.rect.y)
self.x = float(self.rect.x)
def update(self):
""" move the bullet up the screen """
#update the decimal position of the bullet.
self.y -= self.bullet_speed
self.rect.x = self.x
#uipdate the rect position
self.rect.y = self.y
def draw_bullet(self):
""" draw the bullet to the screen """
pygame.draw.rect(self.screen, self.bullet_color, self.rect)
class Game:
""" a class the creates a window with a blue screen """
def __init__(self):
pygame.init()
#--------------------------------------------------------------------------------------------
#screen size, color, caption
self.screen = pygame.display.set_mode((1200,800)) #create attribute to hold display settings
self.bg_color = (250,250,250) #create attribute to hold RGB color (blue)
pygame.display.set_caption("Blue Screen")
#--------------------------------------------------------------------------------------------
#--------------------------------------------------------------------------------------------
#tank drawing
self.screen_rect = self.screen.get_rect() #get the screen rect dim
self.image = pygame.image.load('images/tank.bmp') #load the image from directory
self.rect = self.image.get_rect() #get the image rect dim
self.rect.center = self.screen_rect.center #store the screens center x/y coord
#tank movement
self.tank_moving_left = False
self.tank_moving_right = False
self.tank_moving_up = False
self.tank_moving_down = False
self.tank_speed = 1 #tank pixels
self.direction_right = self.image #holds right image
self.direction_left = pygame.transform.flip(self.image, True, False) #holds left image
#--------------------------------------------------------------------------------------------
self.bullets = pygame.sprite.Group()
def move(self):
""" move tnak tank_speed based on direction of movement (key pressed)
also detect collision """
if self.tank_moving_right and self.rect.right < self.screen_rect.right:
self.rect.x += self.tank_speed
if self.tank_moving_left and self.rect.left > self.screen_rect.left:
self.rect.x -= self.tank_speed
if self.tank_moving_down and self.rect.bottom < self.screen_rect.bottom:
self.rect.y += self.tank_speed
if self.tank_moving_up and self.rect.top > self.screen_rect.top:
self.rect.y -= self.tank_speed
def blitme(self):
""" draw the image of the tank """
self.screen.blit(self.image, self.rect)
def _update_screen(self):
""" update screen """
self.screen.fill(self.bg_color)
self.blitme()
pygame.display.flip()
for bullet in self.bullets.sprites():
bullet.draw_bullet()
print(bullet.rect.midright)
def _check_KEYDOWN(self, event):
""" when key is press either quit, or move direction of arrow pressed and flip image """
if event.key == pygame.K_q:
sys.exit()
elif event.key == pygame.K_RIGHT:
self.tank_moving_right = True
self.image = self.direction_right
elif event.key == pygame.K_LEFT:
self.tank_moving_left = True
self.image = self.direction_left
elif event.key == pygame.K_UP:
self.tank_moving_up = True
elif event.key == pygame.K_DOWN:
self.tank_moving_down = True
elif event.key == pygame.K_SPACE:
self._fire_bullet()
print(1)
def _check_KEYUP(self, event):
""" when key is let go stop moving """
if event.key == pygame.K_RIGHT:
self.tank_moving_right = False
elif event.key == pygame.K_LEFT:
self.tank_moving_left = False
elif event.key == pygame.K_UP:
self.tank_moving_up = False
elif event.key == pygame.K_DOWN:
self.tank_moving_down = False
def _fire_bullet(self):
""" create a bullet and add it to the bullets group """
new_bullet = Bullet(self)
self.bullets.add(new_bullet)
def run_game(self):
""" loops the game/ updates screen/ checks for key clicks"""
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
self._check_KEYDOWN(event)
elif event.type == pygame.KEYUP:
self._check_KEYUP(event)
self.move()
self.bullets.update()
self._update_screen()
if __name__ == '__main__':
a = Game()
a.run_game()
Your _update_screen() function is drawing your bullets after updating the display. The bullets then get overwritten next time _update_screen() is called when you fill the screen with the background color.
If you reorder your screen update function as follows you should be able to see your bullets:
def _update_screen(self):
""" update screen """
self.screen.fill(self.bg_color)
self.blitme()
for bullet in self.bullets.sprites():
bullet.draw_bullet()
print(bullet.rect.midright)
pygame.display.flip()
Also, you can create an image for your bullet by changing your initialization function:
…
#create a bullet at rect (0,0) and the set the correct position
self.image = pygame.Surface((self.bullet_width, self.bullet_height))
self.image.fill(self.bullet_color)
self.rect = self.image.get_rect()
Then you won't need a draw_bullet() function, you can replace the drawing of individual bullets in _update_screen() with self.bullets.draw(self.screen)

init takes 1 argument but 2 were given? (Positional)

I am making a game in python using pygmae. And I am having trouble with Init, it says that It has 2 arguments. but I only gave, one I found other solutions online but those did not quite work, I also got this error multiple times in the past and normally when I would fix it, it would be in very unusual ways such as places where the code is all the way down on the other side of the screen. I get that is how python works, but i'm pretty sure even in the self method I gave, I only gave it one positional argument> Here is the error code if anyone is wondering (I was doing this to make the enemy appear on the screen, so all of that is for the enemy, this problem originated from the fact that I was trying to make the enemy appear on the screen)
enemy = Enemy(enemy_image)
TypeError: init() takes 1 positional argument but 2 were given
Here is my code:
import pygame, sys
# classes
# Player class
class Player(pygame.sprite.Sprite):
def __init__(self, image):
super().__init__()
self.image = image
self.rect = self.image.get_rect(center = (screen_width//2, screen_height//2))
def update(self):
self.rect.center = pygame.mouse.get_pos()
def create_bullet(self):
return Bullet(*pygame.mouse.get_pos())
# bullet class
class Bullet (pygame.sprite.Sprite):
def __init__(self,pos_x,pos_y):
super().__init__()
self.image = pygame.Surface((50,10))
self.image.fill((255,255,0))
self.rect = self.image.get_rect(center = (pos_x,pos_y))
def update(self):
self.rect.x += 5
#Making the enemy work
enemy_speed_factor = 1.5
class Enemy:
def __init__(self):
"""Initialize the enemy and set its starting position"""
self.screen = screen
#Load the enemy image and get its rect
self.image = pygame.image.load("Enemy4.png")
self.rect = self.image.get_rect()
self.scree_rect = screen.get_rect()
#start each new enemy at the bottom of the screen
self.rect.centerx = self.scree_rect.centerx
self.rect.bottom = self.scree_rect.bottom
#store a decimal value for the ships x and y center
self.centerx = float(self.rect.centerx)
self.centery = float(self.rect.centery)
#Movement flag
self.moving_right = False
self.moving_left = False
self.moving_down = False
self.moving_up = False
def update(self):
"""Update the enemys position based on the movement flag"""
#Upade the enemy's center value, not the rect.
if self.moving_right and self.rect.right < self.screen_rect.right:
self.centerx += self.ai_settings.ship_speed_factor
if self.mobing_left and self.rect.left >0:
self.centerx -= self.ai_settings.ship_speed_factor
if self.moving_down and self.rect.bottom < self.screen_rect.bottom:
self.centery =+ self.ai_settings.ship_speed_factor
if self.moving_down and self.rect.top > self.screen_rect.top:
self.centery -= self.ai_settings.ship_speed_factor
#Update rect object from self.center
if self.moving_up or self.moving_down:
self.rect.centery = self.centery
if self.moving_left or self.moving_right:
self.rect.centerx = self.centerx
def blitme(selfself, self):
"""draw the enemy at its current location"""
self.screen.blit(self.image, self.rect)
#making the movements for the enemy
def check_keydown_events(events, ship):
"""responds to keypresses"""
if event.key == pygame.K_RIGHT:
ship.moving_right = True
elif event.key == pygame.K_LEFT:
ship.moving_left = True
elif event.key == pygame.K_DOWN:
ship.moving_down = True
elif event.key == pygame.K_UP:
ship.moving_up = True
def check_keyup_evets(event, ship):
"""responds to key releases"""
if event.key == pygame.K_RIGHT:
ship.moving_right = False
elif event.key == pygame.K_LEFT:
ship.moving_left = False
elif event.key == pygame.K_DOWN:
ship.moving_down = False
elif event.key == pygame.K_UP:
ship.moving_up = False
def check_keyup_events(event, ship):
pass
def check_events(ship):
"""Respond to keypresses and mouse events"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
elif event.type == pygame.KEYDOWN:
check_keydown_events(event, enemy)
elif event.type == pygame.KEYUP:
check_keyup_events(event, ship)
def update_screen(ai_settings, screen, ship):
"""Update images on the screen and flip to the new screen"""
#Redeaw the screen during each pass through the loop
screen.fill(ai_settings.bg_color)
enemy.blitme()
#Make the most recently drawn screeen visible
pygame.display.flip()
def blitme(self):
self.screen.blit(self)
# general setup
pygame.init()
clock = pygame.time.Clock()
# game screen
screen_width = 1920
screen_height = 1080
screen = pygame .display.set_mode((screen_width, screen_height))
background = pygame.image.load("BackGround.png")
# player
player_image = pygame.image.load("Charachter2.png")
player = Player(player_image)
player_group = pygame.sprite.Group()
player_group.add(player)
# enemy
enemy_image = pygame.image.load("Enemy4.png")
enemy = Enemy(enemy_image)
enemy_group = pygame.sprite.Group()
enemy_group.add(enemy)
# Bullet
bullet_group = pygame.sprite.Group()
# caption
pygame.display.set_caption("Wild-West Shooter")
# makes game quit
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame:quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
bullet_group.add(player.create_bullet())
player_group.update()
bullet_group.update()
# draw
screen.blit(background,(0,0)) # background
player_group.draw(screen) # player
bullet_group.draw(screen) # bullets
enemy.draw(screen)
#thief.draw(screen) # enemy
pygame.display.flip()
clock.tick(120)
# makes game run
if __name__ == "__main__" :
theApp = App()
theApp.on_execute()
The problem is that your __init__ method for the Enemy class takes only 1 argument, which is the self argument. Python passes this automatically when an object is created for a class.
For example, if you have a class Foo, and its __init__ method is defined as:
def __init__ (self):
# Do stuff
and you create an object as such:
bar = Foo()
Python automatically converts the object call and adds a self argument. Think of it as self argument always being there.
Now, if you want to pass two arguments, you need to edit the __init__ method as such:
def __init__(self, image):
# Do stuff
This way, the __init__ method accepts 2 arguments, the self and the image. Then you can proceed to create an object as follows:
enemy = Enemy(enemy_image)
And Python will add the self argument to it automatically so that you final object is enemy = Enemy(self, enemy_image)

Pygame: my bullet class does nothing apart from being drawn

So im trying to make a local multiplayer game, i have created the player class which works fine so far.
Now im trying to do a bullet class to create different types of shots for the player's spaceships, however upon calling my bullet, it only gets drawn to the screen where the player was at the start of the game and it doesnt move (so far just trying to program it to move)
Here is the code:
import pygame
pygame.init()
#Display screen
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Space Arena")
background = pygame.image.load(r'C:\Users\Bruno\Documents\PythonProjects\Pygame\Multiplayer\Images\background.png').convert_alpha()
playerImg = pygame.image.load(r'C:\Users\Bruno\Documents\PythonProjects\Pygame\Multiplayer\Images\Player.png').convert_alpha()
player2Img = pygame.image.load(r'C:\Users\Bruno\Documents\PythonProjects\Pygame\Multiplayer\Images\Player2.png').convert_alpha()
regular_bullet = pygame.image.load(r'C:\Users\Bruno\Documents\PythonProjects\Pygame\Multiplayer\Images\bullet.png').convert_alpha()
class Player:
def __init__(self, image, x, y, isPlayer1, isPlayer2):
self.image = image
self.x = x
self.y = y
self.isPlayer1 = isPlayer1
self.isPlayer2 = isPlayer2
def Move(self):
key_states = pygame.key.get_pressed()
x_change = 0.2
y_change = 0.2
if self.isPlayer1:
if key_states[pygame.K_a]:
self.x += -x_change
if key_states[pygame.K_d]:
self.x += x_change
if key_states[pygame.K_w]:
self.y += -y_change
if key_states[pygame.K_s]:
self.y += y_change
elif self.isPlayer2:
if key_states[pygame.K_LEFT]:
self.x += -x_change
if key_states[pygame.K_RIGHT]:
self.x += x_change
if key_states[pygame.K_UP]:
self.y += -y_change
if key_states[pygame.K_DOWN]:
self.y += y_change
def draw(self, screen):
screen.blit(self.image,(self.x,self.y))
def ColorPlayer(self ,image):
if self.isPlayer1:
self.image.fill((190,0,0,100), special_flags = pygame.BLEND_ADD)
if self.isPlayer2:
self.image.fill((0,0,190,100), special_flags = pygame.BLEND_ADD)
class Bullet():
def __init__(self, image, bulletx, bullety):
self.image = image
self.bulletx = bulletx
self.bullety = bullety
self.bullet_state = "ready"
def draw(self, screen):
if self.bullet_state == "fire":
screen.blit(self.image,(self.bulletx,self.bullety))
def shoot(self):
change_x = 0.9
if self.bullet_state == "fire":
self.bulletx += change_x
if self.bulletx > 800:
self.bullet_state = "ready"
def redrawWindow(screen, player, player2, background, player_bullet):
screen.fill((255,255,255))
screen.blit(background,(0,0))
player.draw(screen)
player2.draw(screen)
player_bullet.draw(screen)
pygame.display.update()
def Main():
done = False
player = Player(playerImg,200,200,True,False)
player2 = Player(player2Img,600,200,False,True)
player1_bullet = Bullet(regular_bullet, player.x, player.y)
while not done:
player.Move()
player2.Move()
player.ColorPlayer(playerImg)
player2.ColorPlayer(player2Img)
redrawWindow(screen, player, player2, background, player1_bullet)
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
if player1_bullet.bullet_state is "ready":
player1_bullet.bullet_state = "fire"
player1_bullet.shoot()
Main()
It looks like shoot is effectively the "update" function for your bullet, and the code inside would need to be called every frame (like with player.Move()) for the bullet to keep moving after it's fired.
The only code you have that changes the bullet position is this bit inside the shoot method.
def shoot(self):
change_x = 0.9
if self.bullet_state == "fire":
self.bulletx += change_x
Nothing else moves it. You need to have a move function that adjusts it position and call that each frame. In fact your shoot function is a bit odd it looks more like what the move function would look like. Perhaps rename it to move() and call it each frame?
That function is a bit unusual for a shoot() function, since it moves an existing bullet instead of firing a new one. Normally a shoot method would be expected to be a method in the player and to create a new bullet. It does not really make sense for a bullet instance function to shoot and to create a new bullet. Also you seem to have coded it so that there is a single bullet that goes to 800 and then can be re-fired, but you do not have anything that resets the bullets position back to where the player is.
It might make more sense for the 'ready' and 'fire' state to be a state of the player since the player is the one that can or cannot fire. Also the shoot() method is more commonly in the player class and when it fires (the player is in state 'ready') it creates a new Bullet instance at the players position.
The bullet move() method would be called each frame just like the player move() functions are. Normally there would be more than one bullet allowed at a time and so you would keep a list of the and have to iterate through the list of bullets moving them along (and doing collision checking to see if they hit anything). If a bullet goes off the screen or hits something then you remove the bullet from the bullet list.
You have to change 2 things.
The method Bullet.shoot() has to be continuously invoked in the main application loop. Note, this method updates the bullet dependent on its state. If the state of the bullet is "fire" then the bullet moves. Else it stands still.
Update the position of the bullet when it is fired. The bullet always has to start at the position of the player:
def Main():
# [...]
while not done:
# [...]
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
if player1_bullet.bullet_state is "ready":
player1_bullet.bullet_state = "fire"
player1_bullet.bulletx = player.x
player1_bullet.bullety = player.y
player1_bullet.shoot()

Projectiles only move if i hold the fire key down

Previously in my projectiles module, I had a class that handled each direction of fire seperately (a class for firing up, down, left and right) and this did it's job. However, now that I'm trying to incorporate shot speed and other things into the class, having 4 seperate classes is just too messy and so I tried to trim it down so that I only have one class for all projectiles fired.
However, now that I have done this, when I fire a projectile, it will only move so long as I am holding the fire button ('a' key if firing left) down. Also, if I fire left, then fire right, the projectile that was previously travelling left will begin to travel right instead.
My question is; How do I handle the projectiles so that when I fire one, it no longer accepts updates and travels in a straight line?
This is my working code;
Main game module
import pygame
from constants import *
from player import Player
from Projectile import Projectiles
pygame.init()
screen = pygame.display.set_mode([500, 500])
pygame.display.set_caption('Labyrinth')
# Spawn player
player = Player(50, 50)
all_sprites_list = pygame.sprite.Group()
all_sprites_list.add(player)
projectile_list = pygame.sprite.Group()
clock = pygame.time.Clock()
done = False
# ----- Event Loop
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.KEYDOWN:
if event.key == ord('a'):
player.changespeed(-3, 0)
elif event.key == ord('d'):
player.changespeed(3, 0)
elif event.key == ord('w'):
player.changespeed(0, -3)
elif event.key == ord('s'):
player.changespeed(0, 3)
elif event.key == pygame.K_LEFT:
projectile = Projectiles(0, 0)
projectile.rect.x = player.rect.x
projectile.rect.y = player.rect.y
elif event.key == pygame.K_RIGHT:
projectile = Projectiles(0, 0)
projectile.rect.x = player.rect.x
projectile.rect.y = player.rect.y
elif event.key == pygame.K_UP:
projectile = Projectiles(0, 0)
projectile.rect.x = player.rect.x
projectile.rect.y = player.rect.y
elif event.key == pygame.K_DOWN:
projectile = Projectiles(0, 0)
projectile.rect.x = player.rect.x
projectile.rect.y = player.rect.y
try:
if projectile:
projectile_list.add(projectile)
except:
pass
elif event.type == pygame.KEYUP:
if event.key == ord('a'):
player.changespeed(3, 0)
elif event.key == ord('d'):
player.changespeed(-3, 0)
elif event.key == ord('w'):
player.changespeed(0, 3)
elif event.key == ord('s'):
player.changespeed(0, -3)
# ----- Game Logic
all_sprites_list.update()
projectile_list.update()
screen.fill(GREEN)
all_sprites_list.draw(screen)
projectile_list.draw(screen)
pygame.display.flip()
clock.tick(60)
pygame.quit()
Player module
from constants import *
import pygame
import time
from datetime import datetime, timedelta
class Player(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = pygame.Surface([15, 15])
self.image.fill(BLACK)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.change_x = 0
self.change_y = 0
def changespeed(self, x, y):
self.change_x += x
self.change_y += y
def update(self):
self.rect.x += self.change_x
self.rect.y += self.change_y
Projectile module
import pygame
from constants import *
class Projectiles(object):
def __init__(self, x, y):
super().__init__()
self.image = pygame.Surface([4, 4])
self.image.fill(RED)
self.rect = self.image.get_rect()
def update(self):
key = pygame.key.get_pressed()
if key[pygame.K_UP]:
self.rect.y -= 5
if key[pygame.K_DOWN]:
self.rect.y += 5
if key[pygame.K_LEFT]:
self.rect.x -= 5
if key[pygame.K_RIGHT]:
self.rect.x += 5
As always any help would be much appreciated!
The first problem is that you only create ONE projectile. Your class is called Projectiles which is misleading because it's only one object not multiple. This causes the projectile to be controlled even after firing.
Also, the reason why the projectile only moves while you press a key is that in the update() method, you only add to the projectiles x or y coordinate when e.g. key[pygame.K_UP]: is true.
So, to fix this issues you will have to change the way your game handles projectiles.
If I understand your question right, you want to have multiple projectiles at once.
The way to implement this is to use a collection of projectiles.
Game
//nothing changed till here
elif event.key == pygame.K_LEFT:
p = Projectile(player.rect.x, player.rect.y, -5, 0)
projectile_list.add(p)
elif event.key == pygame.K_RIGHT:
p = Projectile(player.rect.x, player.rect.y, 5, 0)
projectile_list.add(p)
elif event.key == pygame.K_UP:
p = Projectile(player.rect.x, player.rect.y, 0, -5)
projectile_list.add(p))
elif event.key == pygame.K_DOWN:
p = Projectile(player.rect.x, player.rect.y, 0, 5)
projectile_list.add(p)
// moved the part where you append the projectile to in the if statement
# ----- Game Logic
all_sprites_list.update()
projectile_list.update()
screen.fill(GREEN)
all_sprites_list.draw(screen)
projectile_list.draw(screen)
pygame.display.flip()
clock.tick(60)
pygame.quit()
Projectile:
import pygame
from constants import *
// projectile needs to extend Sprite
class Projectile(pygame.sprite.Sprite):
def __init__(self, x, y, x_speed, y_speed):
super().__init__()
self.image = pygame.Surface([4, 4])
self.image.fill(RED)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.x_speed = x_speed
self.y_speed = y_speed
def update(self):
self.rect.x += self.x_speed
self.rect.y += self.y_speed
Im not so familiar with Pygame but I hope this will help you at least somehow.
I have never used PyGame but one can see that your update() method in the Projectiles class depends on key press while it should depend on elapsed time instead.
Your projectile moves 5 units per key press, not 5 units per game tick once launched.

Categories