Shooting a bullet in pygame in the direction of mouse - python

I just cant figure out why my bullet is not working. I made a bullet class and here it is:
class Bullet:
def __init__(self):
self.x = player.x
self.y = player.y
self.height = 7
self.width = 2
self.bullet = pygame.Surface((self.width, self.height))
self.bullet.fill((255, 255, 255))
Now I added several functions in my game class and here is the new code:
class Game:
def __init__(self):
self.bullets = []
def shoot_bullet(self):
if self.bullets:
for bullet in self.bullets:
rise = mouse.y - player.y
run = mouse.x - player.x
angle = math.atan2(rise, run)
bullet.x += math.cos(angle)
bullet.y += math.sin(angle)
pygame.transform.rotate(bullet.bullet, -math.degrees(angle))
D.blit(bullet.bullet, (bullet.x, bullet.y))
def generate_bullet(self):
if mouse.is_pressed():
self.bullets.append(Bullet())
What I was expecting the code to do was a Bullet() would get added to game.bullets every time I pressed the mouse button, then game.shoot_bullet would calculate the angle between the player and the mouse and shoot the bullet accordingly in the direction of the mouse. However, the result is a complete mess and the bullets actually don't rotate and don't move. They get generated and move weirdly to the left of the screen. I am not sure if I have messed up something or the method I have used is completely wrong.

First of all pygame.transform.rotate does not transform the object itself, but creates a new rotated surface and returns it.
If you want to fire a bullet in a certain direction, the direction is defined the moment the bullet is fired, but it does not change continuously.
When the bullet is fired, set the starting position of the bullet and calculate the direction vector to the mouse position:
self.pos = (x, y)
mx, my = pygame.mouse.get_pos()
self.dir = (mx - x, my - y)
The direction vector should not depend on the distance to the mouse, but it has to be a Unit vector.
Normalize the vector by dividing by the Euclidean distance
length = math.hypot(*self.dir)
if length == 0.0:
self.dir = (0, -1)
else:
self.dir = (self.dir[0]/length, self.dir[1]/length)
Compute the angle of the vector and rotate the bullet. In general, the angle of a vector can be computed by atan2(y, x). The y-axis needs to be reversed (atan2(-y, x)) as the y-axis generally points up, but in the PyGame coordinate system the y-axis points down (see How to know the angle between two points?):
angle = math.degrees(math.atan2(-self.dir[1], self.dir[0]))
self.bullet = pygame.Surface((7, 2)).convert_alpha()
self.bullet.fill((255, 255, 255))
self.bullet = pygame.transform.rotate(self.bullet, angle)
To update the position of the bullet, it is sufficient to scale the direction (by a velocity) and add it to the position of the bullet:
self.pos = (self.pos[0]+self.dir[0]*self.speed,
self.pos[1]+self.dir[1]*self.speed)
To draw the rotated bullet in the correct position, take the bounding rectangle of the rotated bullet and set the center point with self.pos (see How do I rotate an image around its center using PyGame?):
bullet_rect = self.bullet.get_rect(center = self.pos)
surf.blit(self.bullet, bullet_rect)
See also Shoot bullets towards target or mouse
Minimal example: repl.it/#Rabbid76/PyGame-FireBulletInDirectionOfMouse
import pygame
import math
pygame.init()
window = pygame.display.set_mode((500, 500))
clock = pygame.time.Clock()
class Bullet:
def __init__(self, x, y):
self.pos = (x, y)
mx, my = pygame.mouse.get_pos()
self.dir = (mx - x, my - y)
length = math.hypot(*self.dir)
if length == 0.0:
self.dir = (0, -1)
else:
self.dir = (self.dir[0]/length, self.dir[1]/length)
angle = math.degrees(math.atan2(-self.dir[1], self.dir[0]))
self.bullet = pygame.Surface((7, 2)).convert_alpha()
self.bullet.fill((255, 255, 255))
self.bullet = pygame.transform.rotate(self.bullet, angle)
self.speed = 2
def update(self):
self.pos = (self.pos[0]+self.dir[0]*self.speed,
self.pos[1]+self.dir[1]*self.speed)
def draw(self, surf):
bullet_rect = self.bullet.get_rect(center = self.pos)
surf.blit(self.bullet, bullet_rect)
bullets = []
pos = (250, 250)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.MOUSEBUTTONDOWN:
bullets.append(Bullet(*pos))
for bullet in bullets[:]:
bullet.update()
if not window.get_rect().collidepoint(bullet.pos):
bullets.remove(bullet)
window.fill(0)
pygame.draw.circle(window, (0, 255, 0), pos, 10)
for bullet in bullets:
bullet.draw(window)
pygame.display.flip()

I noticed that the cycle where you checking the condition if
lenghts is equal to 0 it doesn't really make any sense.
Of-course the value of sqrt(0) is always 0, but how is this even possible in reality to get? 0% (i guess)
So, while its looking for a path by calculate a hypotenuse
if length == 0.0:
self.dir = (0, -1)
In which scenario where it returns 0 is possible ?
P.S. if i am missing any thing feel free to suggest

Related

How do I make a image move towards another? [duplicate]

I'm having trouble with moving sprites. I can move them in the x and y axis with no problem at all. What I can't figure out is how can I move them according to a certain angle. What I mean is, I'm trying to create a function which includes the object I'm trying to move, its speed, and its direction(which should be measured in degrees). Something like:
MovingObject(obj,speed,direction) #this is the function I'm trying to define
It's more like a "spawning function" rather than just movement... Take for example that I would like to create an object of the class "Bullet" and want it to follow certain direction (different from the x and y axis, of course)
Actually I have no clear idea of how to do such thing and I would like some advice in order to achieve so.
Thanks for reading this!
EDIT:
#Joran Beasley I tried to do what you told me...but I guess I did it wrong...
import pygame, math, time
screen=pygame.display.set_mode((320,240))
clock=pygame.time.Clock()
pygame.init()
def calculate_new_xy(old_xy,speed,angle_in_radians):
new_x = old_xy.x + (speed*math.cos(angle_in_radians))
new_y = old_xy.y + (speed*math.sin(angle_in_radians))
return new_x, new_y
class Bullet(pygame.sprite.Sprite):
def __init__(self,x,y,direction,speed):
pygame.sprite.Sprite.__init__(self)
self.image=pygame.Surface((16, 16))
self.image.fill((255,0,0))
self.rect=self.image.get_rect()
self.rect.center=(x,y)
self.direction=math.radians(direction)
self.speed=speed
def update(self):
self.rect.center=calculate_new_xy(self.rect,self.speed,self.direction)
spr=pygame.sprite.Group()
bullet=Bullet(160,120,45,1); spr.add(bullet)
play=True
while play:
clock.tick(60)
for ev in pygame.event.get():
if ev.type == pygame.QUIT:
play=False
screen.fill((0,0,0))
spr.update()
spr.draw(screen)
pygame.display.flip()
pygame.quit()
The object moves but... not in the specified direction...
you just need a little basic trig
def calculat_new_xy(old_xy,speed,angle_in_radians):
new_x = old_xy.X + (speed*math.cos(angle_in_radians))
new_y = old_xy.Y + (speed*math.sin(angle_in_radians))
return new_x, new_y
--- edit ---
Here is your code from above edited to work
import pygame, math, time
screen=pygame.display.set_mode((320,240))
clock=pygame.time.Clock()
pygame.init()
def calculate_new_xy(old_xy,speed,angle_in_radians):
new_x = old_xy[0] + (speed*math.cos(angle_in_radians))
new_y = old_xy[1] + (speed*math.sin(angle_in_radians))
return new_x, new_y
class Bullet(pygame.sprite.Sprite):
def __init__(self,x,y,direction,speed):
pygame.sprite.Sprite.__init__(self)
self.image=pygame.Surface((16, 16))
self.image.fill((255,0,0))
self.rect=self.image.get_rect()
self.rect.center=(x,y)
self.direction=math.radians(direction)
self.speed=speed
def update(self):
self.rect.center=calculate_new_xy(self.rect.center,self.speed,self.direction)
spr=pygame.sprite.Group()
bullet=Bullet(160,120,45,2); spr.add(bullet)
play=True
while play:
clock.tick(60)
for ev in pygame.event.get():
if ev.type == pygame.QUIT:
play=False
screen.fill((0,0,0))
spr.update()
spr.draw(screen)
pygame.display.flip()
pygame.quit()
I recommend to use vectors. To get the velocity, rotate the start direction vector Vector2(1, 0) by the angle and multiply it by the desired speed. Then just add this velocity vector to the position vector in the update method and also update the rect position to move the sprite. (Press 'a' or 'd' to rotate, mouse 1 or space to shoot.)
import pygame as pg
from pygame.math import Vector2
pg.init()
screen = pg.display.set_mode((640, 480))
screen_rect = screen.get_rect()
FONT = pg.font.Font(None, 24)
BULLET_IMAGE = pg.Surface((20, 11), pg.SRCALPHA)
pg.draw.polygon(BULLET_IMAGE, pg.Color('aquamarine1'),
[(0, 0), (20, 5), (0, 11)])
class Bullet(pg.sprite.Sprite):
def __init__(self, pos, angle):
super().__init__()
self.image = pg.transform.rotate(BULLET_IMAGE, -angle)
self.rect = self.image.get_rect(center=pos)
# To apply an offset to the start position,
# create another vector and rotate it as well.
offset = Vector2(40, 0).rotate(angle)
# Then add the offset vector to the position vector.
self.pos = Vector2(pos) + offset # Center of the sprite.
# Rotate the direction vector (1, 0) by the angle.
# Multiply by desired speed.
self.velocity = Vector2(1, 0).rotate(angle) * 9
def update(self):
self.pos += self.velocity # Add velocity to pos to move the sprite.
self.rect.center = self.pos # Update rect coords.
if not screen_rect.contains(self.rect):
self.kill()
def main():
clock = pg.time.Clock()
cannon_img = pg.Surface((40, 20), pg.SRCALPHA)
cannon_img.fill(pg.Color('aquamarine3'))
cannon = cannon_img.get_rect(center=(320, 240))
angle = 0
bullet_group = pg.sprite.Group() # Add bullets to this group.
while True:
for event in pg.event.get():
if event.type == pg.QUIT:
return
elif event.type == pg.MOUSEBUTTONDOWN:
# Left button fires a bullet from center with
# current angle. Add the bullet to the bullet_group.
if event.button == 1:
bullet_group.add(Bullet(cannon.center, angle))
keys = pg.key.get_pressed()
if keys[pg.K_a]:
angle -= 3
elif keys[pg.K_d]:
angle += 3
if keys[pg.K_SPACE]:
bullet_group.add(Bullet(cannon.center, angle))
# Rotate the cannon image.
rotated_cannon_img = pg.transform.rotate(cannon_img, -angle)
cannon = rotated_cannon_img.get_rect(center=cannon.center)
bullet_group.update()
# Draw
screen.fill((30, 40, 50))
screen.blit(rotated_cannon_img, cannon)
bullet_group.draw(screen)
txt = FONT.render('angle {:.1f}'.format(angle), True, (150, 150, 170))
screen.blit(txt, (10, 10))
pg.display.update()
clock.tick(30)
if __name__ == '__main__':
main()
pg.quit()
Regarding the code in your added example, the easiest solution is to calculate the speed_x and speed_y ("velocity" would be more fitting) in the __init__ method and then just update the self.rect.x and y attributes in the update method.
import math
import pygame
pygame.init()
screen = pygame.display.set_mode((640, 480))
clock = pygame.time.Clock()
BULLET_IMAGE = pygame.Surface((20, 11), pygame.SRCALPHA)
pygame.draw.polygon(BULLET_IMAGE, pygame.Color('aquamarine1'),
[(0, 0), (20, 5), (0, 11)])
class Bullet(pygame.sprite.Sprite):
def __init__(self, x, y, angle, speed):
pygame.sprite.Sprite.__init__(self)
# Rotate the bullet image (negative angle because y-axis is flipped).
self.image = pygame.transform.rotate(BULLET_IMAGE, -angle)
self.rect = self.image.get_rect(center=(x, y))
angle = math.radians(angle)
self.speed_x = speed * math.cos(angle)
self.speed_y = speed * math.sin(angle)
def update(self):
self.rect.x += self.speed_x
self.rect.y += self.speed_y
spr = pygame.sprite.Group()
bullet = Bullet(10, 10, 60, 3)
bullet2 = Bullet(10, 10, 30, 3)
spr.add(bullet, bullet2)
play = True
while play:
clock.tick(60)
for ev in pygame.event.get():
if ev.type == pygame.QUIT:
play = False
screen.fill((30,30,40))
spr.update()
spr.draw(screen)
pygame.display.flip()
pygame.quit()
There's a problem, because pygame.Rects can only have ints as the x and y attributes, so the movement won't be 100% correct. To solve this problem, you need to store the coords/position of the sprite in separate variables, add the speed to them and afterwards update the rect:
# In `__init__`.
self.pos_x = x
self.pos_y = y
def update(self):
self.pos_x += self.speed_x
self.pos_y += self.speed_y
self.rect.center = (self.pos_x, self.pos_y)
If you are using Pygame, I suggest to use pygame.math.Vector2 for this task. Set a direction vector from its Polar coordinates (speed, angle_in_degrees) with from_polar(). Add this vector to the current position of the bullet. This is more comprehensible than using sin and cos:
def calculate_new_xy(old_xy, speed, angle_in_degrees):
move_vec = pygame.math.Vector2()
move_vec.from_polar((speed, angle_in_degrees))
return old_xy + move_vec
Be aware, that a pygame.Rect can only store integer coordinates. This is because a pygame.Rect is supposed to represent an area on the screen:
The coordinates for Rect objects are all integers. [...]
If you want the bullet to go straight in a certain direction at an angle that is not divisible by 45 °, you must store object positions with floating point accuracy. You have to store the location of the object in separate variables respectively attributes and to synchronize the pygame.Rect object. round the coordinates and assign it to the location (e.g. .topleft) of the rectangle:
class Bullet(pygame.sprite.Sprite):
def __init__(self, x, y, direction, speed):
# [...]
self.rect = self.image.get_rect(center = (x, y))
self.pos = (x, y)
def update(self, screen):
self.pos = calculate_new_xy(self.pos, self.speed, -self.direction)
self.rect.center = round(self.pos[0]), round(self.pos[1])
See also Move towards target
Minimal example
import pygame
def calculate_new_xy(old_xy, speed, angle_in_degrees):
move_vec = pygame.math.Vector2()
move_vec.from_polar((speed, angle_in_degrees))
return old_xy + move_vec
class Bullet(pygame.sprite.Sprite):
def __init__(self, x, y, direction, speed):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((16, 8), pygame.SRCALPHA)
self.image.fill((255, 0, 0))
self.image = pygame.transform.rotate(self.image, direction)
self.rect = self.image.get_rect(center = (x, y))
self.pos = (x, y)
self.direction = direction
self.speed = speed
def update(self, screen):
self.pos = calculate_new_xy(self.pos, self.speed, -self.direction)
self.rect.center = round(self.pos[0]), round(self.pos[1])
if not screen.get_rect().colliderect(self.rect):
self.kill()
pygame.init()
screen = pygame.display.set_mode((320,240))
clock = pygame.time.Clock()
spr = pygame.sprite.Group()
play = True
frame_count = 0
while play:
clock.tick(60)
for ev in pygame.event.get():
if ev.type == pygame.QUIT:
play = False
spr.update(screen)
if (frame_count % 10) == 0:
spr.add(Bullet(*screen.get_rect().center, frame_count, 2))
frame_count += 1
screen.fill((0,0,0))
spr.draw(screen)
pygame.draw.circle(screen, (64, 128, 255), screen.get_rect().center, 10)
pygame.display.flip()
pygame.quit()
exit()

how do i add a detect collision in pygame [duplicate]

This question already has answers here:
How do I detect collision in pygame?
(5 answers)
Closed 1 year ago.
i found this unfinished file in my files and now i need to finish it, only problem is idk really how do i detect collision with the bullet and the player thing and/or the bullet and the enemy thing, when the bullets collide with the enemy it still dies, i just don't remember how.
here's the code ig help thanks
import pygame, os, random,sys
pygame.init()
FPS=60
SCREEN = pygame.display.set_mode((400,500))
pygame.display.set_caption('caption')
x=50
y=450
vel = 3
width = 20
height = 20
class Player(pygame.sprite.Sprite):
def __init__(self,x,y,width,height):
super().__init__()
self.x = x
self.y = y
self.vel = 4
self.image = pygame.Surface((width, height))
self.image.fill((0,0,0))
self.rect = self.image.get_rect(topleft = (x, y))
class B(pygame.sprite.Sprite):
def __init__(self,x,y,radius, color):
super().__init__()
self.x = x
self.y = y
self.color = color
self.radius = radius
self.vel = vel
self.image = pygame.Surface((self.radius * 2, self.radius * 2), pygame.SRCALPHA)
pygame.draw.circle(self.image, self.color, (self.radius, self.radius), self.radius)
self.rect = self.image.get_rect(center = (self.x, self.y))
class Enemy(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((20,20))
self.image.fill((255, 0, 0))
y = random.randrange (0, 480)
x = 400
self.rect = self.image.get_rect(topleft = (x, y))
self.speed = random.randrange(1,2)
player = Player(x, y, width, height)
enemies = pygame.sprite.Group()
bullets = pygame.sprite.Group()
all_sprites = pygame.sprite.Group()
all_sprites.add(player)
score = 0
# main loop
running = True
clock = pygame.time.Clock()
while running:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE and len(bullets) < 8:
bullet = B(player.rect.centerx, player.rect.centery, 2, (0,0,0))
bullets.add(bullet)
all_sprites.add(bullet)
if len(enemies) < 2:
e = Enemy()
enemies.add(e)
all_sprites.add(e)
for bullet in bullets:
if bullet.rect.right < 500:
bullet.rect.x += bullet.vel
else:
bullet.kill()
for enemy in enemies:
if enemy.rect.right > 0:
enemy.rect.x -= enemy.speed
else:
enemy.kill()
print ("L")
pygame.quit()
sys.exit()
pygame.sprite.groupcollide(bullets, enemies, True, True)
keys = pygame.key.get_pressed()
if keys[pygame.K_UP] and player.rect.top > player.vel:
player.rect.y -= player.vel
if keys[pygame.K_DOWN] and player.rect.bottom < 500 - player.vel:
player.rect.y += player.vel
SCREEN.fill((190, 232, 220))
all_sprites.draw(SCREEN)
pygame.display.update()
pygame.quit()
Collision detection depends on your needs.
Bounding boxes are simple and can detect if the x, y edges of each object are not within one another. This is fine for fast moving games and things where you don't necessarily require point precision.
Distance vectors are also simple and perfect solution for circle collisions. They calculate the difference in distance between two objects according to a radius prescribed with the hypotenuse of distX^2 + distY^2.
Compound bounding objects are harder to calculate, especially for concave areas on objects of arbitrary shape. There are too many notable and novel approaches to collision detection beyond these to remark on here.
They're also increasingly complex depending on things like variability (if they're deformable objects, if they have particle seams, etc and 2d vs 3d object collision can be vastly different worlds as well. There are tons of articles, but I'll post one with implementation here

How Can I Make An Arrow Image Move Rotated Direction? [duplicate]

This question already has answers here:
How can you rotate the sprite and shoot the bullets towards the mouse position?
(1 answer)
calculating direction of the player to shoot pygame
(1 answer)
Shooting a bullet in pygame in the direction of mouse
(2 answers)
Closed 2 years ago.
I have an arrow image that rotates left and right VIDEO as you can see in the video when I click my arrow stops rotating and I want it to move the direction it stopped rotating on how would I do that?
right now I only have it so when I click the rotation stops but not really sure how I could make it move the direction it stopped rotating on
I tried to make it so when the player clicks the arrow starts to move but its the same thing it doesnt move towards like the rotated place it will just move my arrow down VIDEO
rotating_arrow = True
move = False
# our main loop
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.MOUSEBUTTONDOWN:
rotating_arrow = False
move = True
if move:
move_angle = bow2.angle + 15 # The arrow is painted approximately 15° downwards
speed = 20
move_vec = pygame.math.Vector2(speed, 0).rotate(move_angle)
bow2.x += move_vec.x
bow2.y += move_vec.y
bow2.rect.topleft = round(bow2.x), round(bow2.y)
this is how my rotated image is set
def blitRotate(surf, image, pos, originPos, angle):
# calcaulate the axis aligned bounding box of the rotated image
w, h = image.get_size()
sin_a, cos_a = math.sin(math.radians(angle)), math.cos(math.radians(angle))
min_x, min_y = min([0, sin_a*h, cos_a*w, sin_a*h + cos_a*w]), max([0, sin_a*w, -cos_a*h, sin_a*w - cos_a*h])
# calculate the translation of the pivot
pivot = pygame.math.Vector2(originPos[0], -originPos[1])
pivot_rotate = pivot.rotate(angle)
pivot_move = pivot_rotate - pivot
# calculate the upper left origin of the rotated image
origin = (pos[0] - originPos[0] + min_x - pivot_move[0], pos[1] - originPos[1] - min_y + pivot_move[1])
# get a rotated image
rotated_image = pygame.transform.rotate(image, angle)
# rotate and blit the image
surf.blit(rotated_image, origin)
my arrow class
class arrow:
def __init__(self,x,y,height,width,color):
self.x = x
self.y = y
self.height = height
self.width = width
self.color = color
self.shootsright = pygame.image.load("ner.png")
self.image = self.shootsright
self.rect = self.image.get_rect(center = (self.x, self.y))
self.look_at_pos = (self.x, self.y)
self.isLookingAtPlayer = False
self.look_at_pos = (x,y)
self.angle = 0
self.gun_size = self.image.get_size()
self.pivot = (8, self.gun_size[1]//2)
self.rect = pygame.Rect(x,y,height,width)
def draw(self):
blitRotate(window, self.image, self.rect.center, self.pivot, self.angle)
pygame.draw.rect(window,self.color,self.rect)
If you want to fire a bullet in a certain direction, the direction is defined the moment the bullet is fired, but it does not change continuously. This means that after the arrow is fired, the angle will not change.
Compute the direction of the arrow by the trigonometric functions sin and cos. Define the speed of the arrow. Change the position attributes (x, y) of the arrow in each frame:
speed = 20
self.x += speed * math.cos(math.radians(move_angle))
self.y -= speed * math.sin(math.radians(move_angle))
Update the rect attribute:
self.rect.topleft = round(self.x), round(self.y)
Since the arrow picture is painted at a certain angle, the motion angle may need to be corrected:
move_angle = self.angle - 15 # The arrow is painted approximately 15° downwards
Complete arrow.draw method:
class arrow:
# [...]
def draw(self):
move_angle = self.angle - 15 # The arrow is painted approximately 15° downwards
speed = 20
self.x += speed * math.cos(math.radians(move_angle))
self.y -= speed * math.sin(math.radians(move_angle))
self.rect.topleft = round(self.x), round(self.y)
blitRotate(window, self.image, self.rect.center, self.pivot, self.angle)
pygame.draw.rect(window,self.color,self.rect)

How to move a sprite according to an angle in Pygame

I'm having trouble with moving sprites. I can move them in the x and y axis with no problem at all. What I can't figure out is how can I move them according to a certain angle. What I mean is, I'm trying to create a function which includes the object I'm trying to move, its speed, and its direction(which should be measured in degrees). Something like:
MovingObject(obj,speed,direction) #this is the function I'm trying to define
It's more like a "spawning function" rather than just movement... Take for example that I would like to create an object of the class "Bullet" and want it to follow certain direction (different from the x and y axis, of course)
Actually I have no clear idea of how to do such thing and I would like some advice in order to achieve so.
Thanks for reading this!
EDIT:
#Joran Beasley I tried to do what you told me...but I guess I did it wrong...
import pygame, math, time
screen=pygame.display.set_mode((320,240))
clock=pygame.time.Clock()
pygame.init()
def calculate_new_xy(old_xy,speed,angle_in_radians):
new_x = old_xy.x + (speed*math.cos(angle_in_radians))
new_y = old_xy.y + (speed*math.sin(angle_in_radians))
return new_x, new_y
class Bullet(pygame.sprite.Sprite):
def __init__(self,x,y,direction,speed):
pygame.sprite.Sprite.__init__(self)
self.image=pygame.Surface((16, 16))
self.image.fill((255,0,0))
self.rect=self.image.get_rect()
self.rect.center=(x,y)
self.direction=math.radians(direction)
self.speed=speed
def update(self):
self.rect.center=calculate_new_xy(self.rect,self.speed,self.direction)
spr=pygame.sprite.Group()
bullet=Bullet(160,120,45,1); spr.add(bullet)
play=True
while play:
clock.tick(60)
for ev in pygame.event.get():
if ev.type == pygame.QUIT:
play=False
screen.fill((0,0,0))
spr.update()
spr.draw(screen)
pygame.display.flip()
pygame.quit()
The object moves but... not in the specified direction...
you just need a little basic trig
def calculat_new_xy(old_xy,speed,angle_in_radians):
new_x = old_xy.X + (speed*math.cos(angle_in_radians))
new_y = old_xy.Y + (speed*math.sin(angle_in_radians))
return new_x, new_y
--- edit ---
Here is your code from above edited to work
import pygame, math, time
screen=pygame.display.set_mode((320,240))
clock=pygame.time.Clock()
pygame.init()
def calculate_new_xy(old_xy,speed,angle_in_radians):
new_x = old_xy[0] + (speed*math.cos(angle_in_radians))
new_y = old_xy[1] + (speed*math.sin(angle_in_radians))
return new_x, new_y
class Bullet(pygame.sprite.Sprite):
def __init__(self,x,y,direction,speed):
pygame.sprite.Sprite.__init__(self)
self.image=pygame.Surface((16, 16))
self.image.fill((255,0,0))
self.rect=self.image.get_rect()
self.rect.center=(x,y)
self.direction=math.radians(direction)
self.speed=speed
def update(self):
self.rect.center=calculate_new_xy(self.rect.center,self.speed,self.direction)
spr=pygame.sprite.Group()
bullet=Bullet(160,120,45,2); spr.add(bullet)
play=True
while play:
clock.tick(60)
for ev in pygame.event.get():
if ev.type == pygame.QUIT:
play=False
screen.fill((0,0,0))
spr.update()
spr.draw(screen)
pygame.display.flip()
pygame.quit()
I recommend to use vectors. To get the velocity, rotate the start direction vector Vector2(1, 0) by the angle and multiply it by the desired speed. Then just add this velocity vector to the position vector in the update method and also update the rect position to move the sprite. (Press 'a' or 'd' to rotate, mouse 1 or space to shoot.)
import pygame as pg
from pygame.math import Vector2
pg.init()
screen = pg.display.set_mode((640, 480))
screen_rect = screen.get_rect()
FONT = pg.font.Font(None, 24)
BULLET_IMAGE = pg.Surface((20, 11), pg.SRCALPHA)
pg.draw.polygon(BULLET_IMAGE, pg.Color('aquamarine1'),
[(0, 0), (20, 5), (0, 11)])
class Bullet(pg.sprite.Sprite):
def __init__(self, pos, angle):
super().__init__()
self.image = pg.transform.rotate(BULLET_IMAGE, -angle)
self.rect = self.image.get_rect(center=pos)
# To apply an offset to the start position,
# create another vector and rotate it as well.
offset = Vector2(40, 0).rotate(angle)
# Then add the offset vector to the position vector.
self.pos = Vector2(pos) + offset # Center of the sprite.
# Rotate the direction vector (1, 0) by the angle.
# Multiply by desired speed.
self.velocity = Vector2(1, 0).rotate(angle) * 9
def update(self):
self.pos += self.velocity # Add velocity to pos to move the sprite.
self.rect.center = self.pos # Update rect coords.
if not screen_rect.contains(self.rect):
self.kill()
def main():
clock = pg.time.Clock()
cannon_img = pg.Surface((40, 20), pg.SRCALPHA)
cannon_img.fill(pg.Color('aquamarine3'))
cannon = cannon_img.get_rect(center=(320, 240))
angle = 0
bullet_group = pg.sprite.Group() # Add bullets to this group.
while True:
for event in pg.event.get():
if event.type == pg.QUIT:
return
elif event.type == pg.MOUSEBUTTONDOWN:
# Left button fires a bullet from center with
# current angle. Add the bullet to the bullet_group.
if event.button == 1:
bullet_group.add(Bullet(cannon.center, angle))
keys = pg.key.get_pressed()
if keys[pg.K_a]:
angle -= 3
elif keys[pg.K_d]:
angle += 3
if keys[pg.K_SPACE]:
bullet_group.add(Bullet(cannon.center, angle))
# Rotate the cannon image.
rotated_cannon_img = pg.transform.rotate(cannon_img, -angle)
cannon = rotated_cannon_img.get_rect(center=cannon.center)
bullet_group.update()
# Draw
screen.fill((30, 40, 50))
screen.blit(rotated_cannon_img, cannon)
bullet_group.draw(screen)
txt = FONT.render('angle {:.1f}'.format(angle), True, (150, 150, 170))
screen.blit(txt, (10, 10))
pg.display.update()
clock.tick(30)
if __name__ == '__main__':
main()
pg.quit()
Regarding the code in your added example, the easiest solution is to calculate the speed_x and speed_y ("velocity" would be more fitting) in the __init__ method and then just update the self.rect.x and y attributes in the update method.
import math
import pygame
pygame.init()
screen = pygame.display.set_mode((640, 480))
clock = pygame.time.Clock()
BULLET_IMAGE = pygame.Surface((20, 11), pygame.SRCALPHA)
pygame.draw.polygon(BULLET_IMAGE, pygame.Color('aquamarine1'),
[(0, 0), (20, 5), (0, 11)])
class Bullet(pygame.sprite.Sprite):
def __init__(self, x, y, angle, speed):
pygame.sprite.Sprite.__init__(self)
# Rotate the bullet image (negative angle because y-axis is flipped).
self.image = pygame.transform.rotate(BULLET_IMAGE, -angle)
self.rect = self.image.get_rect(center=(x, y))
angle = math.radians(angle)
self.speed_x = speed * math.cos(angle)
self.speed_y = speed * math.sin(angle)
def update(self):
self.rect.x += self.speed_x
self.rect.y += self.speed_y
spr = pygame.sprite.Group()
bullet = Bullet(10, 10, 60, 3)
bullet2 = Bullet(10, 10, 30, 3)
spr.add(bullet, bullet2)
play = True
while play:
clock.tick(60)
for ev in pygame.event.get():
if ev.type == pygame.QUIT:
play = False
screen.fill((30,30,40))
spr.update()
spr.draw(screen)
pygame.display.flip()
pygame.quit()
There's a problem, because pygame.Rects can only have ints as the x and y attributes, so the movement won't be 100% correct. To solve this problem, you need to store the coords/position of the sprite in separate variables, add the speed to them and afterwards update the rect:
# In `__init__`.
self.pos_x = x
self.pos_y = y
def update(self):
self.pos_x += self.speed_x
self.pos_y += self.speed_y
self.rect.center = (self.pos_x, self.pos_y)
If you are using Pygame, I suggest to use pygame.math.Vector2 for this task. Set a direction vector from its Polar coordinates (speed, angle_in_degrees) with from_polar(). Add this vector to the current position of the bullet. This is more comprehensible than using sin and cos:
def calculate_new_xy(old_xy, speed, angle_in_degrees):
move_vec = pygame.math.Vector2()
move_vec.from_polar((speed, angle_in_degrees))
return old_xy + move_vec
Be aware, that a pygame.Rect can only store integer coordinates. This is because a pygame.Rect is supposed to represent an area on the screen:
The coordinates for Rect objects are all integers. [...]
If you want the bullet to go straight in a certain direction at an angle that is not divisible by 45 °, you must store object positions with floating point accuracy. You have to store the location of the object in separate variables respectively attributes and to synchronize the pygame.Rect object. round the coordinates and assign it to the location (e.g. .topleft) of the rectangle:
class Bullet(pygame.sprite.Sprite):
def __init__(self, x, y, direction, speed):
# [...]
self.rect = self.image.get_rect(center = (x, y))
self.pos = (x, y)
def update(self, screen):
self.pos = calculate_new_xy(self.pos, self.speed, -self.direction)
self.rect.center = round(self.pos[0]), round(self.pos[1])
See also Move towards target
Minimal example
import pygame
def calculate_new_xy(old_xy, speed, angle_in_degrees):
move_vec = pygame.math.Vector2()
move_vec.from_polar((speed, angle_in_degrees))
return old_xy + move_vec
class Bullet(pygame.sprite.Sprite):
def __init__(self, x, y, direction, speed):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((16, 8), pygame.SRCALPHA)
self.image.fill((255, 0, 0))
self.image = pygame.transform.rotate(self.image, direction)
self.rect = self.image.get_rect(center = (x, y))
self.pos = (x, y)
self.direction = direction
self.speed = speed
def update(self, screen):
self.pos = calculate_new_xy(self.pos, self.speed, -self.direction)
self.rect.center = round(self.pos[0]), round(self.pos[1])
if not screen.get_rect().colliderect(self.rect):
self.kill()
pygame.init()
screen = pygame.display.set_mode((320,240))
clock = pygame.time.Clock()
spr = pygame.sprite.Group()
play = True
frame_count = 0
while play:
clock.tick(60)
for ev in pygame.event.get():
if ev.type == pygame.QUIT:
play = False
spr.update(screen)
if (frame_count % 10) == 0:
spr.add(Bullet(*screen.get_rect().center, frame_count, 2))
frame_count += 1
screen.fill((0,0,0))
spr.draw(screen)
pygame.draw.circle(screen, (64, 128, 255), screen.get_rect().center, 10)
pygame.display.flip()
pygame.quit()
exit()

Make a sprite move to the mouse click position step by step

I'm writing a little pirate game in Pygame. If you played sea battles in Empires Total War, you have an idea of what I would like to achieve:
The ship's sprite is at position (x1|y1). The player now clicks at position (x2|y2) on the screen. The sprite is now supposed to take (x2|y2) as its new position - by going there step by step, not by beaming there instantly.
I figured out that it has something to do with the diagonal of the rectangle (x1|y1),(x1|y2),(x2|y2),(x2|y1) but I just can't figure it out, especially not with keeping the speed the same no matter what angle that diagonal has and considering that the x and y values of either (ship or click) might be bigger or smaller than the respective other.
This little snippet is my last try to write a working function:
def update(self, new_x, new_y, speed, screen, clicked):
if clicked:
self.xshift = (self.x - new_x)
self.yshift = ((self.y - new_y) / (self.x - new_x))
if self.x > (new_x + 10):
self.x -= 1
self.y -= self.yshift
elif self.x > new_x and self.x < (new_x + 10):
self.x -= 1
self.y -= self.yshift
elif self.x < (new_x - 10):
self.x += 1
self.y += self.yshift
elif self.x < new_x and self.x < (new_x - 10):
self.x += 1
self.y += self.yshift
else:
self.x += 0
self.y += 0
screen.set_at((self.x, self.y), (255, 0, 255))
The "ship" is just a pink pixel here. The reaction it shows upon my clicks onto the screen is to move roughly towards my click but to stop at a seemingly random distance of the point I clicked.
The variables are:
new_x, new_y = position of mouseclick
speed = constant speed depending on ship types
clicked = set true by the MOUSEBUTTONDOWN event, to ensure that the xshift and yshift of self are only defined when the player clicked and not each frame again.
How can I make the ship move smoothly from its current position to the point the player clicked?
Say the current position is pos, and the point the player clicked is target_pos, then take the vector between pos and target_pos.
Now you know how to get from pos to target_pos, but to move in constant speed (and not the entire distance at once), you have to normalize the vector, and apply a speed constant by scalar multiplication.
That's it.
Complete example: (the relevant code is in the Ship.update method)
import pygame
class Ship(pygame.sprite.Sprite):
def __init__(self, speed, color):
super().__init__()
self.image = pygame.Surface((10, 10))
self.image.set_colorkey((12,34,56))
self.image.fill((12,34,56))
pygame.draw.circle(self.image, color, (5, 5), 3)
self.rect = self.image.get_rect()
self.pos = pygame.Vector2(0, 0)
self.set_target((0, 0))
self.speed = speed
def set_target(self, pos):
self.target = pygame.Vector2(pos)
def update(self):
move = self.target - self.pos
move_length = move.length()
if move_length < self.speed:
self.pos = self.target
elif move_length != 0:
move.normalize_ip()
move = move * self.speed
self.pos += move
self.rect.topleft = list(int(v) for v in self.pos)
def main():
pygame.init()
quit = False
screen = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()
group = pygame.sprite.Group(
Ship(1.5, pygame.Color('white')),
Ship(3.0, pygame.Color('orange')),
Ship(4.5, pygame.Color('dodgerblue')))
while not quit:
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
if event.type == pygame.MOUSEBUTTONDOWN:
for ship in group.sprites():
ship.set_target(pygame.mouse.get_pos())
group.update()
screen.fill((20, 20, 20))
group.draw(screen)
pygame.display.flip()
clock.tick(60)
if __name__ == '__main__':
main()

Categories