How Can I Shoot Projectiles with my mouse at any position? [duplicate] - python

This question already has answers here:
Shooting a bullet in pygame in the direction of mouse
(2 answers)
calculating direction of the player to shoot pygame
(1 answer)
How can you rotate the sprite and shoot the bullets towards the mouse position?
(1 answer)
Closed 2 years ago.
I made my projectile shoot left and right by buttons but that would be boring clicking alot of buttons
how do make it shoot with with my mouse? at any position x,y
# projectile class
if keys[pygame.K_f]:
for bullet in bullets:
if bullet.x < 500 and bullet.x > 0:
bullet.x += bullet.speed
else:
bullets.pop(bullets.index(bullet))
if len(bullets) < 2:
bullets.append(projectile(round(playerman.x+playerman.width//2),round(playerman.y + playerman.height-54),(0,0,0)))
if keys[pygame.K_g]:
for bullet in bullets:
if bullet.x < 500 and bullet.x > 0:
bullet.x -= bullet.speed
else:
bullets.pop(bullets.index(bullet))
if len(bullets) < 2:
bullets.append(projectile(round(playerman.x+playerman.width//2),round(playerman.y + playerman.height-54),(0,0,0)))
# Jump and Collisions
and this is my projectile class
class projectile(object):
def __init__(self, x, y,color):
self.x = x
self.y = y
self.slash = pygame.image.load("heart.png")
self.rect = self.slash.get_rect()
self.rect.topleft = ( self.x, self.y )
self.speed = 10
self.color = color
def draw(self, window):
self.rect.topleft = ( self.x,self.y )
window.blit(slash, self.rect)

You have to add the moving direction (dirx, diry) to the class projectile. Further add a method which moves the bullet:
class projectile(object):
def __init__(self, x, y, dirx, diry, color):
self.x = x
self.y = y
self.dirx = dirx
self.diry = diry
self.slash = pygame.image.load("heart.png")
self.rect = self.slash.get_rect()
self.rect.topleft = ( self.x, self.y )
self.speed = 10
self.color = color
def move(self):
self.x += self.dirx * self.speed
self.y += self.diry * self.speed
def draw(self, window):
self.rect.topleft = (round(self.x), round(self.y))
window.blit(slash, self.rect)
Compute the direction form the player to the mouse when the mouse button is pressed and spawn a new bullet.
The direction is given by the vector form the player to the muse position (mouse_x - start_x, mouse_y - start_y).
The vector has to be normalized (Unit vector) by dividing the vector components by the Euclidean distance:
for event in pygame.event.get():
# [...]
if event.type == pygame.MOUSEBUTTONDOWN:
if len(bullets) < 2:
start_x, start_y = playerman.x+playerman.width//2, playerman.y + playerman.height-54
mouse_x, mouse_y = event.pos
dir_x, dir_y = mouse_x - start_x, mouse_y - start_y
distance = math.sqrt(dir_x**2 + dir_y**2)
if distance > 0:
new_bullet = projectile(start_x, start_y, dir_x/distance, dir_y/distance, (0,0,0))
bullets.append(new_bullet)
Move the bullets in a loop in te main application loop and remove the bullet if it is out of the window
run = True
while run:
# [...]
for event in pygame.event.get():
# [...]
for bullet in bullets[:]:
bullet.move()
if bullet.x < 0 or bullet.x > 500 or bullet.y < 0 or bullet.y > 500:
bullets.pop(bullets.index(bullet))
# [...]
Example code
runninggame = True
while runninggame:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
runninggame = False
if event.type == pygame.MOUSEBUTTONDOWN:
if len(bullets) < 2:
start_x, start_y = playerman.x+playerman.width//2, playerman.y + playerman.height-54
mouse_x, mouse_y = event.pos
dir_x, dir_y = mouse_x - start_x, mouse_y - start_y
distance = math.sqrt(dir_x**2 + dir_y**2)
if distance > 0:
new_bullet = projectile(start_x, start_y, dir_x/distance, dir_y/distance, (0,0,0))
bullets.append(new_bullet)
for bullet in bullets[:]:
bullet.move()
if bullet.x < 0 or bullet.x > 800 or bullet.y < 0 or bullet.y > 800:
bullets.pop(bullets.index(bullet))
# [...]

Related

How do I make a wall in pygame? [duplicate]

This question already has answers here:
How do I detect collision in pygame?
(5 answers)
How do I prevent the player from moving through the walls in a maze?
(3 answers)
Closed 9 months ago.
I am trying to learn the basics of pygame by making a simple pacman game with a friend, but I have been having trouble figuring out how to make walls and check collision with the pacman and the wall, and also stopping the movement through a wall.
(this is a 2 player pacman, ghost is arrow keys, and pacman is wasd)
This is main.py
import pygame
import random
import time
from Pacman import pacman
from Ghost import ghost
#colors
Yellow = (255,255,0)
Blackish_Blue = (20,0,70)
Red = (255,0,0)
P_B = (40,60,100)
clock = pygame.time.Clock()
#display
pygame.init()
pygame.display.set_caption(' Scuffed Pacman')
width, height = 640, 480
screen = pygame.display.set_mode((width, height))
pacman = pacman(screen)
ghost = ghost(screen)
font = pygame.font.Font('freesansbold.ttf', 32)
text = font.render('Game Over', True, Yellow)
textRect = text.get_rect()
textRect.center = (width / 2, height / 2)
run = True
while run == True:
pygame.time.delay(10)
clock.tick(60)
screen.fill(Blackish_Blue)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
color = (182,163,192)
#attempt to make wall and check collision
ghostspawn = pygame.Rect(0,0,60,40)
ghostspawn.center = (width / 2, (height / 2)-50)
pygame.draw.rect(screen, color, ghostspawn)
if ghostspawn.collidepoint(pacman.x, pacman.y):
print("collision detected")
pacman.draw(screen)
ghost.draw(screen)
ghost.update()
pacman.update()
distance = (((pacman.x - ghost.x)**2) + ((pacman.y - ghost.y)**2))**(1/2)
sumrad = pacman.radius + ghost.radius
if distance < sumrad:
while True:
screen.fill(P_B)
screen.blit(text, textRect)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
pygame.display.update()
pygame.display.update()
pygame.quit()
This is the code for pacman:
import pygame
Yellow = (255,255,0)
class pacman(pygame.sprite.Sprite):
def __init__(self, mainscreen):
super().__init__()
self.x = 320
self.y = 240
self.radius = 15
self.velocity = 2
self.origin = (self.x, self.y)
def draw(self, mainscreen):
pygame.draw.circle(mainscreen, Yellow, (self.x, self.y), self.radius)
def update(self):
self.movement()
self.origin = (self.x, self.y)
def movement(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_d] and self.velocity > 0 and self.x < 625:
self.x += self.velocity
if keys[pygame.K_a] and self.velocity > 0 and self.x > 15:
self.x -= self.velocity
if keys[pygame.K_w] and self.velocity > 0 and self.y > 15:
self.y -= self.velocity
if keys[pygame.K_s] and self.velocity > 0 and self.y < 465:
self.y += self.velocity
and this is the code for ghost:
import pygame
Red = (255,0,0)
class ghost(pygame.sprite.Sprite):
def __init__(self, mainscreen):
super().__init__()
self.x = 213
self.y = 240
self.radius = 15
self.velocity = 2
self.origin = (self.x, self.y)
def draw(self, mainscreen):
pygame.draw.circle(mainscreen, Red, (self.x, self.y), self.radius)
def update(self):
self.movement()
self.origin = (self.x, self.y)
def movement(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_RIGHT] and self.velocity > 0 and self.x < 625:
self.x += self.velocity
if keys[pygame.K_LEFT] and self.velocity > 0 and self.x > 15:
self.x -= self.velocity
if keys[pygame.K_UP] and self.velocity > 0 and self.y > 15:
self.y -= self.velocity
if keys[pygame.K_DOWN] and self.velocity > 0 and self.y < 465:
self.y += self.velocity
this is my attempt at checking for wall collision, but I am unaware of how to stop pacman and the ghost from going through. the collision also only checks the middle of the circle.
ghostspawn = pygame.Rect(0,0,60,40)
ghostspawn.center = (width / 2, (height / 2)-50)
pygame.draw.rect(screen, color, ghostspawn)
if ghostspawn.collidepoint(pacman.x, pacman.y):
print("allowed")
here is how i detect collision between pacman and ghost, as it may be helpful for doing it with walls, but i don't know how.
distance = (((pacman.x - ghost.x)**2) + ((pacman.y - ghost.y)**2))**(1/2)
sumrad = pacman.radius + ghost.radius
ive looked at a few similar questions on here, but I can't quite grasp how it works.
So, if anyone could help, I'm having trouble with checking collision between a circle and rectangle, and also preventing the circle/pacman from moving through.
To check if a circle is colliding with a wall you just need to do 2 things
Get the distance between the circle and the wall
Then check if the distance is smaller or equal to the circles distance
Pretty much like this:
distance = abs(circle.x - wall.x) + abs(circle.y - wall.y)
if distance <= circle.radius: # abs() Makes the value in the brackets positive
circle.x = oldX
circle.y = oldY
To implement this I with your game would first add a self.oldX and a self.oldY in the self.__init__() in both of the pacman and ghost classes. And I would set them to 0 for now.
Then I would update the oldX and oldY in the movement function before I move the object, like this:
def movement(self):
keys = pygame.key.get_pressed()
self.oldX = self.x
self.oldY = self.y
if keys[pygame.K_d] and self.velocity > 0 and self.x < 625:
self.x += self.velocity
if keys[pygame.K_a] and self.velocity > 0 and self.x > 15:
self.x -= self.velocity
if keys[pygame.K_w] and self.velocity > 0 and self.y > 15:
self.y -= self.velocity
if keys[pygame.K_s] and self.velocity > 0 and self.y < 465:
self.y += self.velocity
Then I would have a list that containes every wall in the game and a list that containes every ghost in the game (That's if you want to have more than one ghost).
Then I would go in the main loop (The while loop that you have) and I would add this After calling the movement function of the ghosts and the pacman:
for wall in walls:
distance = abs(pacman.x - wall.x) + abs(pacman.y - wall.y)
if distance <= pacman.radius:
pacman.x = pacman.oldX
pacman.y = pacman.oldY
for ghost in ghosts:
distance = abs(ghost.x - wall.x) + abs(ghost.y - wall.y)
if distance <= ghost.radius:
ghost.x = ghost.oldX
ghost.y = ghost.oldY
and that should work, Thanks.

Make bullets fire off in the direction the player is facing

I was just getting some help to figure out how to get my player fire bullets when I realized that they only go (kinda expected this but however as only had y value for movement). I don't know how I'll make the bullets fire off in the direction the player is facing.
I have some idea of what to but I just don't know how to do it... I thought I could somehow use the cursor and player tracking that's in this game for the visuals but I don't know how to make that a one-time thing instead of a constant. For diagonal movement of the bullet, I have no clue.
Code below (split into two parts/file Main.py and PlayerSprite.py):
Main:
py.init()
py.mixer.init()
screen = py.display.set_mode((WIDTH, HEIGHT))
py.display.set_caption("Dimensional Drifter")
clock = py.time.Clock()
all_sprites = py.sprite.Group()
NPCs = py.sprite.Group()
bullets = py.sprite.Group()
player = Player()
all_sprites.add(player)
for i in range(14):
n = NPC(player)
all_sprites.add(n)
NPCs.add(n)
# Game loop
running = True
while running:
# keep loop running at the right speed
clock.tick(FPS)
for event in py.event.get():
# check for closing window
if event.type == py.QUIT:
running = False
elif event.type == py.KEYDOWN:
if event.key == py.K_SPACE:
New_bullet = player.Shoot()
all_sprites.add(New_bullet)
bullets.add(New_bullet)
# Update
all_sprites.update()
# # check if there a collision between the bullet and NPC
hits = py.sprite.groupcollide(NPCs, bullets, True, True)
# check if there a collision between the player and NPC
hits = py.sprite.spritecollide(player, NPCs, True)
if hits:
running = False
# updates the position of of mouse and rotates it towards the mouse position
mouse_x, mouse_y = py.mouse.get_pos()
player.rotate(mouse_x, mouse_y)
# render
screen.fill(BLACK)
all_sprites.draw(screen)
# flip the display
py.display.flip()
py.quit()
PlayerSprite
import pygame as py
import math
import random
WIDTH = 800
HEIGHT = 600
FPS = 60
# define colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
class Player(py.sprite.Sprite):
def __init__(self):
py.sprite.Sprite.__init__(self)
self.image = py.Surface((40, 40), py.SRCALPHA)
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect.centerx = WIDTH / 2
self.rect.bottom = HEIGHT / 2
self.Yspeed = 0
self.rotatableimage = self.image
def update(self):
self.Xspeed = 0
self.Yspeed = 0
# line below allow for key press to equate to move of sprite
keypreesed = py.key.get_pressed()
if keypreesed[py.K_a]:
self.Xspeed = - 11
if keypreesed[py.K_d]:
self.Xspeed = 11
if keypreesed[py.K_w]:
self.Yspeed = - 11
if keypreesed[py.K_s]:
self.Yspeed = 11
self.rect.x += self.Xspeed
self.rect.y += self.Yspeed
# line below allow the sprite to wrap around the screen
if self.rect.left > WIDTH:
self.rect.right = 0
if self.rect.right < 0:
self.rect.left = WIDTH
if self.rect.top > HEIGHT:
self.rect.top = 0
if self.rect.bottom < 0:
self.rect.bottom = HEIGHT
def rotate(self, mouse_x, mouse_y):
rel_x = mouse_x - self.rect.x
rel_y = mouse_y - self.rect.y
angle = (180 / math.pi) * -math.atan2(rel_y, rel_x)
self.image = py.transform.rotate(self.rotatableimage, int(angle))
self.rect = self.image.get_rect(center=(self.rect.centerx, self.rect.centery))
return
def Shoot(self):
return Bullet(self.rect.centerx, self.rect.top)
class NPC(py.sprite.Sprite):
def __init__(self, player):
py.sprite.Sprite.__init__(self)
self.player = player
self.image = py.Surface((30, 30)).convert_alpha()
self.image.fill(RED)
self.originalimage = self.image
self.rect = self.image.get_rect()
self.spawn()
# allows of spawning from all four side of the screen and set the x, y speed and spawn position
def spawn(self):
self.direction = random.randrange(4)
if self.direction == 0:
self.rect.x = random.randrange(WIDTH - self.rect.width)
self.rect.y = random.randrange(-100, -40)
self.Xspeed = random.randrange(-2, 2)
self.Yspeed = random.randrange(4, 8)
elif self.direction == 1:
self.rect.x = random.randrange(WIDTH - self.rect.width)
self.rect.y = random.randrange(HEIGHT, HEIGHT + 60)
self.Xspeed = random.randrange(-2, 2)
self.Yspeed = -random.randrange(4, 8)
elif self.direction == 2:
self.rect.x = random.randrange(-100, -40)
self.rect.y = random.randrange(HEIGHT - self.rect.height)
self.Xspeed = random.randrange(4, 8)
self.Yspeed = random.randrange(-2, 2)
elif self.direction == 3:
self.rect.x = random.randrange(WIDTH, WIDTH + 60)
self.rect.y = random.randrange(HEIGHT - self.rect.height)
self.Xspeed = -random.randrange(4, 8)
self.Yspeed = random.randrange(-2, 2)
def update(self):
self.rect.x += self.Xspeed
self.rect.y += self.Yspeed
# makes it so that NPC point to wards the player as it passes from side to side
dir_x, dir_y = self.player.rect.x - self.rect.x, self.player.rect.y - self.rect.y
self.rot = (180 / math.pi) * math.atan2(-dir_x, -dir_y)
self.image = py.transform.rotate(self.originalimage, self.rot)
# Respawns the NPC when they hit an side
if self.direction == 0:
if self.rect.top > HEIGHT + 10:
self.spawn()
elif self.direction == 1:
if self.rect.bottom < -10:
self.spawn()
elif self.direction == 2:
if self.rect.left > WIDTH + 10:
self.spawn()
elif self.direction == 3:
if self.rect.right < -10:
self.spawn()
class Bullet(py.sprite.Sprite):
def __init__(self, x, y):
py.sprite.Sprite.__init__(self)
self.image = py.Surface((5, 5))
self.image.fill(YELLOW)
self.rect = self.image.get_rect()
self.rect.bottom = y
self.rect.centerx = x
self.Yspeed = -10
def update(self):
self.rect.y += self.Yspeed
# kill if moved of screen
if self.rect.bottom > HEIGHT or self.rect.top < 0:
self.kill()
if self.rect.right > WIDTH or self.rect.left < 0:
self.kill()
Add 2 attributes self.lastX and self.lastY to the class Player and change the attributes when the player changes the direction:
class Player(py.sprite.Sprite):
def __init__(self):
# [...]
self.lastX = 0
self.lastY = -10
def update(self):
# [...]
self.rect.x += self.Xspeed
self.rect.y += self.Yspeed
if self.Xspeed != 0 or self.Yspeed != 0:
self.lastX = self.Xspeed
self.lastY = self.Yspeed
Add an argument Xspeed ans Yspeed to the class Bullet
class Bullet(py.sprite.Sprite):
def __init__(self, x, y, Xspeed, Yspeed):
py.sprite.Sprite.__init__(self)
self.image = py.Surface((5, 5))
self.image.fill(YELLOW)
self.rect = self.image.get_rect()
self.rect.bottom = y
self.rect.centerx = x
self.Xspeed = Xspeed
self.Yspeed = Yspeed
def update(self):
self.rect.x += self.Xspeed
self.rect.y += self.Yspeed
# [...]
Set the attributes when the bullet spawns
class Player(py.sprite.Sprite):
# [...]
def Shoot(self):
return Bullet(self.rect.centerx, self.rect.centery, self.lastX, self.lastY)
Alternatively it is also possible to set the speed dependent on the direction to the mouse cursor.
Get the position of the player and the mouse cursor and compute the x and y distance (Vector ):
pos = self.rect.center
mpos = py.mouse.get_pos()
vx = mpos[0] - pos[0]
vy = mpos[1] - pos[1]
If the mouse position and the bullet position are equal, that does not make any sense, thus the bullet is skipped
if vx == 0 and vy == 0:
return None
Of course this vector is far to long, if you would use it for the direction (Xspeed, Yspeed) directly, then the bullet would step to the mouse cursor in one turn.
In the following I use pygame.math.Vector2, because it provides the handy method scale_to_length, that scales a vector to a specified Euclidean length:
direction = py.math.Vector2(vx, vy)
direction.scale_to_length(10)
Now the x and y component of the vector contain the x and y component of the speed. Since the components are floating point values, they are round to integral values:
return Bullet(pos[0], pos[1], round(direction.x), round(direction.y))
Method Shoot:
class Player(py.sprite.Sprite):
# [...]
def Shoot(self):
pos = self.rect.center
mpos = py.mouse.get_pos()
vx, vy = mpos[0] - pos[0], mpos[1] - pos[1]
if vx == 0 and vy == 0:
return None
direction = py.math.Vector2(vx, vy)
direction.scale_to_length(10)
return Bullet(pos[0], pos[1], round(direction.x), round(direction.y))
Note, if you set the bullet dependent on the direction to the mouse cursor, then it may be useful to spawn the bullet by a mouse click:
while running:
# [...]
for event in py.event.get():
if event.type == py.QUIT:
# [...]
elif event.type == py.MOUSEBUTTONDOWN:
if event.button == 1:
New_bullet = player.Shoot()
if New_bullet:
all_sprites.add(New_bullet)
bullets.add(New_bullet)
You can use pygames Vector2 to move in any direction. You calculate the angle of the player you can use that.
class Player(py.sprite.Sprite):
def __init__(self):
...
self.angle = 0
def rotate(self, mouse_x, mouse_y):
...
self.angle = -angle #make negative otherwise it will be going away from mouse
def Shoot(self):
return Bullet(self.rect.centerx, self.rect.top, py.math.Vector2(1,0).rotate(self.angle))
then in your bullet class, get the direction and add to its position
class Bullet(py.sprite.Sprite):
def __init__(self, x, y, Dir):
...
self.Dir = Dir
def update(self):
self.rect.y += self.Dir[1] * self.speed
self.rect.x += self.Dir[0] * self.speed
...

Trouble Shooting Projectiles Upward in Pygame

I'm in the process of writing a duel-player tank game, similar to one known as "Tank Trouble". Of course, I'm only in the baby steps of it right now and I'm a bit stuck on a particular little bug. At the moment, the code below enables me to display my sprite, able to move around in all the directions, and also able to shoot projectiles. The only issue, however, is that it can only shoot projectiles left or right. Does anyone have any suggestions as to how to change this so that the tank can shoot upward, when facing upward and downward, when facing downward? It can only shoot left and right, even when it is facing up or down, which is kind of odd. I am having trouble accessing the y-axis and having the projectile travel along it.
If anyone can find out how to do that and tell me what the new code is and where to place it, that would help a ton. Here is my code:
import pygame
pygame.init()
screen = pygame.display.set_mode((500,500))
pygame.display.set_caption("Game")
tankImg = pygame.image.load("tank1.png")
downTank = tankImg
leftTank = pygame.image.load("left1.png")
rightTank = pygame.image.load("right1.png")
upTank = pygame.image.load("up1.png")
bg = pygame.image.load("background.png")
screenWidth = 500
screenHeight = 500
clock = pygame.time.Clock()
class player(object):
def __init__(self, x, y, width, height):
self.x = x
self.y = y
self.width = width
self.height = height
self.vel = 5
self.left = False
self.right = False
self.up = False
self.down = False
def draw(self,screen):
screen.blit(tankImg,(self.x,self.y))
class projectile(object):
def __init__(self,x, y, radius, color, facing):
self.x = x
self.y = y
self.radius = radius
self.color = color
self.facing = facing
self.vel = 8 * facing
def draw(self,screen):
pygame.draw.circle(screen, self.color, (self.x,self.y), self.radius)
def redraw():
screen.blit(bg, (0,0))
tank.draw(screen)
for bullet in bullets:
bullet.draw(screen)
pygame.display.update()
run = True
tank = player(300, 410, 16, 16)
bullets = []
while run:
clock.tick(30)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
for bullet in bullets:
if bullet.x < screenWidth and bullet.x > 0:
bullet.x += bullet.vel
elif bullet.y > screenHeight and bullet.y > 0:
bullet.y += bullet.vel
else:
bullets.pop(bullets.index(bullet))
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and tank.x > tank.vel:
tank.left = True
tankImg = leftTank
tank.x -= tank.vel
facing = -1
if keys[pygame.K_RIGHT] and tank.x < 500 - tank.width:
tank.right = True
tankImg = rightTank
tank.x += tank.vel
facing = 1
if keys[pygame.K_UP] and tank.y > tank.vel:
tank.up = True
tankImg = upTank
tank.y -= tank.vel
facing = 1
if keys[pygame.K_DOWN] and tank.y < 500 - tank.height:
down = True
tankImg = downTank
tank.y += tank.vel
facing = -1
if keys[pygame.K_SPACE]:
if len(bullets) < 1:
bullets.append(projectile(round(tank.x + tank.width // 2), round(tank.y + tank.height // 2), 4, (0,0,0),facing))
redraw()
pygame.quit()
The issue is caused in that part of the code:
if bullet.x < screenWidth and bullet.x > 0:
bullet.x += bullet.vel
elif bullet.y > screenHeight and bullet.y > 0:
bullet.y += bullet.vel
else:
bullets.pop(bullets.index(bullet))
As long the bullet is in bounds of the window, the 1st condition evaluates True. That causes that the bullet can move in x-direction only.
Instead of the facing attribute add a direction attribute to the class projectile. Furthermore add a method (move) which moves the projectile. This method ignores the bounds of the window:
class projectile(object):
def __init__(self,x, y, radius, color, direction):
self.x = x
self.y = y
self.radius = radius
self.color = color
self.direction = direction
self.vel = 8
def move(self):
self.x += self.direction[0] * self.vel
self.y += self.direction[1] * self.vel
def draw(self,screen):
pygame.draw.circle(screen, self.color, (self.x,self.y), self.radius)
Move the bullets in a loop end evaluate if a bullet is in bounds of the window. This can be done with ease, by using pygame.Rect and collidepoint():
while run:
# [...]
for bullet in bullets[:]:
bullet.move()
window_rect = pygame.Rect(0, 0, screenWidth, screenHeight)
if not window_rect.collidepoint((bullet.x, bullet.y)):
bullets.pop(bullets.index(bullet))
Set the direction of the bullet dependent on the moving direction of the tank:
direction = (-1, 0)
while run:
# [...]
if keys[pygame.K_LEFT] and tank.x > tank.vel:
tank.left = True
tankImg = leftTank
tank.x -= tank.vel
direction = (-1, 0)
if keys[pygame.K_RIGHT] and tank.x < 500 - tank.width:
tank.right = True
tankImg = rightTank
tank.x += tank.vel
direction = (1, 0)
if keys[pygame.K_UP] and tank.y > tank.vel:
tank.up = True
tankImg = upTank
tank.y -= tank.vel
direction = (0, -1)
if keys[pygame.K_DOWN] and tank.y < 500 - tank.height:
down = True
tankImg = downTank
tank.y += tank.vel
direction = (0, 1)
if keys[pygame.K_SPACE]:
if len(bullets) < 1:
px, py = round(tank.x + tank.width // 2), round(tank.y + tank.height // 2)
bullets.append(projectile(px, py, 4, (0,0,0), direction))
I recommend to spawn the bullets in the pygame.KEYDOWN event instead of the evaluating the pressed keys (keys[pygame.K_SPACE]). The event occurs once when a button is pressed. So every time when SPACE is pressed a single bullet is generated (Of course that is up to you):
direction = (-1, 0)
while run:
clock.tick(30)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
if event. key == pygame.K_SPACE:
px, py = round(tank.x + tank.width // 2), round(tank.y + tank.height // 2)
bullets.append(projectile(px, py, 4, (0,0,0), direction))
for bullet in bullets[:]:
bullet.move()
window_rect = pygame.Rect(0, 0, screenWidth, screenHeight)
if not window_rect.collidepoint((bullet.x, bullet.y)):
bullets.pop(bullets.index(bullet))
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and tank.x > tank.vel:
tank.left = True
tankImg = leftTank
tank.x -= tank.vel
direction = (-1, 0)
if keys[pygame.K_RIGHT] and tank.x < 500 - tank.width:
tank.right = True
tankImg = rightTank
tank.x += tank.vel
direction = (1, 0)
if keys[pygame.K_UP] and tank.y > tank.vel:
tank.up = True
tankImg = upTank
tank.y -= tank.vel
direction = (0, -1)
if keys[pygame.K_DOWN] and tank.y < 500 - tank.height:
down = True
tankImg = downTank
tank.y += tank.vel
direction = (0, 1)
redraw()
Currently, if bullet.x is between 0 and the screen width the first condition is met and the second is not checked
for bullet in bullets:
if bullet.x < screenWidth and bullet.x > 0:
# When this is True then the "elif" is not run
bullet.x += bullet.vel
elif bullet.y > screenHeight and bullet.y > 0:
bullet.y += bullet.vel
else:
bullets.pop(bullets.index(bullet))
You should just have one condition that makes sure the bullet is in both the x and y bound so that you update both x and y
for bullet in bullets:
if 0 < bullet.x < screenWidth and 0 < bullet.y < screenHeight:
bullet.x += bullet.vel
bullet.y += bullet.vel
else:
bullets.pop(bullets.index(bullet))
You can combine conditions like this that check if a value is between 2 values like so min < value < max

Pygame - Getting enemy sprites to move towards player

As of currently the enemy sprites just spawn on the axis, and "move" with the player which is actually a scrolling background, and I'd like for the enemies to move only along the X axis so it doesn't destroy the immersion.
I'd also like for the sprites to spawn "Off the map" and whenever the map scrolls towards them they move towards the players set X axis? I think that would keep things simple, but isn't really the question at hand right now.
The current code I was trying to get working for the movement was :
def move_towards_player(self, player):
# Find direction vector (dx, dy) between enemy and player.
dx, dy = player.rect.x - self.rect.x, player.rect.y - self.rect.y
dist = math.hypot (dx, dy)
dx, dy = dx / dist, dy / dist # Normalize
# Move along this normalized vector towards the player
self.rect.x += dx * self.speed
self.rect.y += dy * self.speed
But it wouldn't work, this is with importing the math module.
(I know I don't need the y movements just wanted to get it working first)
Here is the rest of the code --
Zombie.py:
import pygame
from pygame.locals import *
import random
import math
class ZombieEnemy(pygame.sprite.Sprite):
def __init__(self, x=300, y=360):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('images/zombie.png')
self.rect = self.image.get_rect()
self.rect.center = (x, y)
all_zombies = pygame.sprite.Group()
for i in range( 50 ):
new_x = random.randrange( 0, 10000) # random x-position
# new_y = random.randrange( 0, ) # random y-position
all_zombies.add(ZombieEnemy(new_x)) # create, and add to group
I wasn't sure if I should create a whole new class for enemy movement, or what my partner is currently working on the Player, and getting the sprite animations and movement working right.
But here is the main code I have as of now:
import pygame
from Zombie import *
import math
from pygame.locals import *
pygame.init()
​
win = pygame.display.set_mode((900,567))
​
pygame.display.set_caption("Power Rangers ZOMBIES")
​
walkRight = [pygame.image.load('images/walk1.png'), pygame.image.load('images/walk2.png'), pygame.image.load('images/walk3.png'), pygame.image.load('images/walk4.png'), pygame.image.load('images/walk5.png'), pygame.image.load('images/walk6.png')]
walkLeft = [pygame.image.load('images/leftwalk2.png'), pygame.image.load('images/leftwalk3.png'), pygame.image.load('images/leftwalk4.png'), pygame.image.load('images/leftwalk5.png'), pygame.image.load('images/leftwalk6.png'), pygame.image.load('images/leftwalk7.png')]
bg = pygame.image.load('images/background.png')
char = pygame.image.load('images/standingstill.png')
clock = pygame.time.Clock()
​
class Player(pygame.sprite.Sprite):
def __init__(self,x,y,width,height):
self.x = x
self.y = y
self.width = width
self.height = height
self.vel = 0
self.isJump = False
self.left = False
self.right = False
self.walkCount = 0
self.jumpCount = 10
​
def draw(self, win):
if self.walkCount + 1 >= 18:
self.walkCount = 0
​
if self.left:
win.blit(walkLeft[self.walkCount//3], (self.x,self.y))
self.walkCount += 1
elif self.right:
win.blit(walkRight[self.walkCount//3], (self.x,self.y))
self.walkCount +=1
else:
win.blit(char, (self.x,self.y))
​
class Background(pygame.sprite.Sprite):
def __init__(self, image_file, location):
pygame.sprite.Sprite.__init__(self) #call Sprite initializer
self.image = pygame.image.load('images/background.png')
self.rect = self.image.get_rect()
self.rect.left, self.rect.top = location
​
BackGround = Background('images/background.png', [0,0])
​
# def redrawGameWindow():
# win.blit(bg, (0,0))
# man.draw(win)
# pygame.display.update()
​
#mainloop
man = Player(100, 340, 40, 60)
run = True
while run:
clock.tick(27)
​
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
​
keys = pygame.key.get_pressed()
​
if keys[pygame.K_LEFT] and man.x > man.vel:
BackGround.rect.left = BackGround.rect.left + int(10)
man.x -= man.vel
man.left = True
man.right = False
elif keys[pygame.K_RIGHT]: #and man.x < 500 - man.width - man.vel:
BackGround.rect.left = BackGround.rect.left - int(10)
man.x += man.vel
man.right = True
man.left = False
else:
man.right = False
man.left = False
man.walkCount = 0
if not(man.isJump):
if keys[pygame.K_SPACE]:
man.isJump = True
man.right = False
man.left = False
man.walkCount = 0
else:
if man.jumpCount >= -10:
neg = 1
if man.jumpCount < 0:
neg = -1
man.y -= (man.jumpCount ** 2) * 0.5 * neg
man.jumpCount -= 1
else:
man.isJump = False
man.jumpCount = 10
# redrawGameWindow()
for zombie in all_zombies:
zombie.move_towards_player(Player)
all_zombie.update()
win.blit(BackGround.image, BackGround.rect)
man.draw(win)
pygame.display.flip()
all_zombies.draw(screen)
​
pygame.quit()
I can't run code but I see two problems
1 - move_towards_player has to be inside class ZombieEnemy
# --- classes ---
class ZombieEnemy(pygame.sprite.Sprite):
def __init__(self, x=300, y=360):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('images/zombie.png')
self.rect = self.image.get_rect()
self.rect.center = (x, y)
def move_towards_player(self, player):
# Find direction vector (dx, dy) between enemy and player.
dx, dy = player.rect.x - self.rect.x, player.rect.y - self.rect.y
dist = math.hypot (dx, dy)
dx, dy = dx / dist, dy / dist # Normalize
# Move along this normalized vector towards the player
self.rect.x += dx * self.speed
self.rect.y += dy * self.speed
# --- other ---
all_zombies = pygame.sprite.Group()
2 - you have to use it in main loop
for zombie in all_zombies:
zombie.move_towards_player(player)
in main loop
while running:
# --- events ---
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# If keystroke is pressed check right, left.
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
#playerX_change = -2.0
BackGround.rect.left = BackGround.rect.left + 2.5
if event.key == pygame.K_RIGHT:
#playerX_change = 2.0
BackGround.rect.left = BackGround.rect.left - 2.5
# if event.type == pygame.KEYUP:
# if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:
# BackGround.rect.left = 0
# --- updates/moves ---
playerX += playerX_change
for zombie in all_zombies:
zombie.move_towards_player(player)
all_zombies.update()
# --- draws ---
screen.blit(BackGround.image, BackGround.rect)
player(playerX,playerY)
all_zombies.draw(screen)
pygame.display.flip()
But here I see other problem - your function expects player as class instance with self.rect inside (similar to ZombieEnemy) but you keep player as separated variables playerImg, playerX, playerY, playerX_change
So you have to create class Player or you have to use playerx, playery in move_towards_player instead of player.rect.x, player.rect.y

How do I create a hitbox for all shapes in my program and a way to detect collision? [duplicate]

This question already has an answer here:
How to detect collisions between two rectangular objects or images in pygame
(1 answer)
Closed 2 years ago.
I am fairly new to python and the module pygame. I also recently started work on a small project where the purpose of the game is to if a circle touches the "player" (the square) then the game will end or present a message saying you loose (still trying to solve the hitbox issue so still thinking of what to do after). The problem I am having is I am having trouble creating a way to detect the collision of the hitboxes and every time I try to make a way to test if the hitboxes I have collided it just won't work, so if someone can tell me how to make the classes and hitbox, another method, or even how to fix my code so it won't crash with a player class. Thank you, for any help, I may receive.
Here is the code (apologies if it is horrible on the eyes I kept on deleting and adding stuff while trying to find a solution and also sorry if I did something improper this is one of my first questions).
import pygame
import random
pygame.init()
width, height = 800, 800
hbox, vbox = 15, 15
rect = pygame.Rect(500, 600, hbox, vbox)
velocity = (0, 0)
frames = 40
ball_size = 12
white = (255, 255, 255)
black = (0, 0, 0)
hp = 100
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Space Invaders")
icon = pygame.image.load('ufo.png')
pygame.display.set_icon(icon)
is_blue = True
clock = pygame.time.Clock()
class Ball:
def __init__(self):
self.x = 0
self.y = 0
self.change_x = 0
self.change_y = 0
self.hitbox = (self.x + 1, self.y + 2, 31, 57)
def make_ball():
ball = Ball()
Ball.hitbox = ball.hitbox
ball.x = random.randrange(ball_size, width - ball_size)
ball.y = random.randrange(ball_size, height - ball_size)
ball.change_x = random.randrange(-2, 3)
ball.change_y = random.randrange(-2, 3)
return ball
ball_list = []
ball = make_ball()
ball_list.append(ball)
class player(object):
def __init__(self, ):
self.x = rect.x
self.y = rect.y
self.box_width = hbox
self.box_hieght = vbox
self.speed = move
player.hitbox = (self.x + 1, self.y + 11, 29, 52)
def draw(self, is_blue):
if is_blue:
color = (0, 128, 255)
else:
color = (255, 100, 0)
if event.type == pygame.KEYDOWN and event.key == pygame.K_c:
is_blue = not is_blue
Player = player
Player = pygame.draw.rect(screen, color, rect)
def move(self):
surface = pygame.Surface((100, 100))
keys = pygame.key.get_pressed()
if keys[pygame.K_LSHIFT] or keys[pygame.K_RSHIFT]:
move = 8
else:
move = 4
if keys[pygame.K_w]:
rect.y -= move
if rect.y < 0:
rect.y = 0
if keys[pygame.K_s]:
rect.y += move
if rect.y > height - hbox:
rect.y = height - vbox
if keys[pygame.K_a]:
rect.x -= move
if rect.x < 0:
rect.x = 0
if keys[pygame.K_d]:
rect.x += move
if rect.x > width - hbox:
rect.x = width - hbox
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
# Space bar! Spawn a new ball.
if event.key == pygame.K_SPACE:
ball = make_ball()
ball_list.append(ball)
if rect.x == ball.x and rect.y == ball.y:
hp -100
if hp == 0:
pygame.quit()
if event.type == pygame.KEYDOWN and event.key == pygame.K_c:
is_blue = not is_blue
surface = pygame.Surface((100, 100))
keys = pygame.key.get_pressed()
if keys[pygame.K_LSHIFT] or keys[pygame.K_RSHIFT]:
move = 8
else:
move = 4
if keys[pygame.K_w]:
rect.y -= move
if rect.y < 0:
rect.y = 0
if keys[pygame.K_s]:
rect.y += move
if rect.y > height - hbox:
rect.y = height - vbox
if keys[pygame.K_a]:
rect.x -= move
if rect.x < 0:
rect.x = 0
if keys[pygame.K_d]:
rect.x += move
if rect.x > width - hbox:
rect.x = width - hbox
screen.fill((0, 0, 0))
if is_blue:
color = (0, 128, 255)
else:
color = (255, 100, 0)
color_2 = (255, 0, 0)
for ball in ball_list:
ball.x += ball.change_x
ball.y += ball.change_y
if ball.y > height - ball_size or ball.y < ball_size:
ball.change_y *= -1
if ball.x > width - ball_size or ball.x < ball_size:
ball.change_x *= -1
screen.fill(black)
for ball in ball_list:
pygame.draw.circle(screen, white, [ball.x, ball.y], ball_size)
Rectangle = pygame.draw.rect(screen, color, rect)
pygame.display.flip()
clock.tick(30)
`
In general I recommend to use pygame.sprite.Sprite and pygame.sprite.Group, but I'll show you a solution, which is closer to your current code.
Create a Ball class, which can move and draw the ball object. The class has a .rect attribute of type pygame.Rect instead of the attributes .x and .y and can be used for the "hitbox", too.
The rectangle is updated in the instance method move():
class Ball:
def __init__(self):
x = random.randrange(ball_size, width - ball_size)
y = random.randrange(ball_size, height - ball_size)
self.change_x, self.change_y = 0, 0
while self.change_x == 0 and self.change_y == 0:
self.change_x = random.randrange(-2, 3)
self.change_y = random.randrange(-2, 3)
self.rect = pygame.Rect(x-ball_size, y-ball_size, ball_size*2, ball_size*2)
def move(self):
self.rect = self.rect.move(self.change_x, self.change_y)
if self.rect.right >= height or self.rect.left < 0:
self.change_x *= -1
if self.rect.bottom >= width or self.rect.top <= 0:
self.change_y *= -1
def draw(self, surface):
pygame.draw.circle(surface, white, self.rect.center, ball_size)
def make_ball():
ball = Ball()
return ball
In the main application loop, the balls can be moved and drawn in a for-loop and the collision test can be done by .colliderect():
hits = 0
running = True
while running:
# [...]
for ball in ball_list:
if ball.rect.colliderect(rect):
hits += 1
print("hit " + str(hits))
# [...]
for ball in ball_list:
ball.move()
screen.fill(black)
for ball in ball_list:
ball.draw(screen)
Rectangle = pygame.draw.rect(screen, color, rect)
pygame.display.flip()
clock.tick(30)

Categories

Resources