When moving player towards mouse, players direction isn't constant [duplicate] - python
This question already has answers here:
Pygame doesn't let me use float for rect.move, but I need it
(2 answers)
Pygame diagonal movement not at correct angle
(1 answer)
Closed 8 months ago.
When holding the left mouse button down, I would like the player to move towards the mouse position. This does work however, the direction the player travels in isn't always constant and doesn't move straight towards the mouse. Here is the player class:
class Player:
def __init__(self, x, y):
self.image = pygame.Surface((30, 30))
self.rect = self.image.get_rect(center = (x, y))
self.speed = 5
def move_player(self, x, y):
mx, my = pygame.mouse.get_pos()
self.dirx, self.diry = mx - x, my - y
length = math.hypot(*(self.dirx, self.diry))
if length == 0.0:
self.dirx, self.diry = 0, -1
else:
self.dirx, self.diry = self.dirx/length, self.diry/length
self.rect.x += self.dirx * self.speed
self.rect.y += self.diry * self.speed
def draw_player(self, screen, colour):
self.image.fill(colour)
screen.blit(self.image, (self.rect.x, self.rect.y))
In the while loop I move the player:
if mouse[0]:
player.move_player(player.rect.x, player.rect.y)
player.draw_player(screen, (255, 0, 0))
Related
I need to keep an object moving forward relative to itself while being rotated in pygame [duplicate]
This question already has answers here: How to make ball bounce off wall with PyGame? (1 answer) How do I rotate an image around its center using Pygame? (6 answers) Closed 1 year ago. I'm making a "pong" style game, where the ball rotates 15 degrees every time it hits either the top or the bottom of the screen. My problem is that when rotating, it still moves in either x or y direction, and being new to pygame, I am unsure of how to keep the ball moving "forward" relative to itself, in the direction of the rotation, and was not sure of how to phrase the question in Google. The rough code: class Ball(): def __init__(self) -> None: self.x, self.y = display_width / 2, display_height / 2 self.raw_sprite = pygame.image.load("assets/ball.png") self.raw_size = self.raw_sprite.get_size() self.sprite = pygame.transform.scale(self.raw_sprite, (int(self.raw_size[0]/2), int(self.raw_size[1]/2))) self.rect = self.sprite.get_rect() self.width, self.height = self.sprite.get_width(), self.sprite.get_height() self.rect.x = self.x self.rect.y = self.y def render(self) -> None: screen.blit(self.sprite, (self.x,self.y)) def is_collided_with(self, sprite) -> bool: return self.rect.colliderect(sprite.rect) def move(self, direction) -> None: speed = 5 if direction == "left": self.x -= speed else: self.x += speed def rotate(self) -> None: angle = 15 self.sprite = pygame.transform.rotate(self.sprite, (angle)) self.rect = self.sprite.get_rect() ball = Ball() ball_direction = "left" running = True while running: if (ball.y == (0 + ball.height)) or (ball.y == (display_height - ball.height)): ball.rotate() screen.fill(BLACK) ball.render() ball.move(ball_direction) pygame.quit() quit() I've omitted a lot of the code and only left the necessary parts to show the issue. Thanks for your time.
Move Character with Vector
I am teaching myself pygame and am looking at making my character able to rotate and then move in the direction they are facing. I can do the rotation but cannot get the character to move in the direction the image is then facing. The code is on Trinket HERE class Bob(pygame.sprite.Sprite): def __init__(self, color , height , width): super().__init__() self.image = pygame.Surface([width , height]) self.image.fill(BLACK) self.image.set_colorkey(BLACK) #Loading the image for the character self.img = pygame.image.load("char.jfif") #creating a copy of the image self.img_orig = self.img.copy() #defining the starting angle of the character image self.angle = 0 self.velocity = 5 self.rect = self.img_orig.get_rect() def moveLeft(self): self.angle += 1 self.img = pygame.transform.rotate(self.img_orig, self.angle) def moveRight(self): self.rect.x += self.velocity if self.rect.x > 485: self.rect.x = 485 while run: for event in pygame.event.get(): if event.type == pygame.QUIT: run = False keys = pygame.key.get_pressed() if keys[pygame.K_UP]: pSprite.moveForward() if keys[pygame.K_DOWN]: pSprite.moveDown() if keys[pygame.K_LEFT]: pSprite.moveLeft() if keys[pygame.K_RIGHT]: pSprite.moveRight() #---- Game Logic Here #--- Drawing Code Here #Reset the screen to blank screen.fill(BLUE) #Draw Play Area #Draw Sprites screen.blit(pSprite.img,(pSprite.rect.x, pSprite.rect.y))
You can use pygame's Vector2 class instead of calculating the position of your sprite yourself. I also suggest to let the sprite itself handle its movement instead of doing so in the main loop and using a clock for constant framerates and easy control of the speed of your sprites. You also probably want to use an image format with alpha channel (like PNG). Here's a simple example: import pygame class Actor(pygame.sprite.Sprite): def __init__(self, pos, *grps): super().__init__(*grps) self.image = pygame.image.load('char.png').convert_alpha() self.image_org = self.image.copy() self.rect = self.image.get_rect(center=pos) self.pos = pygame.Vector2(pos) self.direction = pygame.Vector2((0, -1)) def update(self, events, dt): pressed = pygame.key.get_pressed() # if a is pressed, rotate left with 360 degress per second if pressed[pygame.K_a]: self.direction.rotate_ip(dt * -360) # if d is pressed, rotate right with 360 degress per second if pressed[pygame.K_d]: self.direction.rotate_ip(dt * 360) # check if should move forward or backward movement = 0 if pressed[pygame.K_w]: movement = 1 if pressed[pygame.K_s]: movement = -1 movement_v = self.direction * movement if movement_v.length() > 0: movement_v.normalize_ip() # move 100px per second in the direction we're facing self.pos += movement_v * dt * 100 # rotate the image self.image = pygame.transform.rotate(self.image_org, self.direction.angle_to((0, -1))) self.rect = self.image.get_rect(center=self.pos) def main(): pygame.init() screen = pygame.display.set_mode((600, 480)) sprites = pygame.sprite.Group() Actor((100, 100), sprites) clock, dt = pygame.time.Clock(), 0 while True: events = pygame.event.get() for e in events: if e.type == pygame.QUIT: return screen.fill('grey') sprites.draw(screen) sprites.update(events, dt) pygame.display.flip() dt = clock.tick(60) / 1000 main() char.png
Rotate the player around its center (see How do I rotate an image around its center using PyGame?): self.angle += 1 self.img = pygame.transform.rotate(self.img_orig, self.angle) self.rect = self.img.get_rect(center = self.rect.center) Use an attribute x and y to store the position of the player with floating point accuracy. class Bob(pygame.sprite.Sprite): def __init__(self, color , height , width): # [...] self.x, self.y = self.rect.center Compute the direction of the player dependent on the angle with the trgonometric function math.sin and math.cos. Change the position attributes and update the rect attribute: self.x += self.velocity * math.cos(math.radians(self.angle + 90)) self.y -= self.velocity * math.sin(math.radians(self.angle + 90)) self.rect.center = round(self.x), round(self.y) The y-axis needs to be reversed (-dy) as the y-axis is generally pointing up, but in the PyGame coordinate system the y-axis is pointing down. In addition, a correction angle must be deducted (+ 90). Since the "angle" is 0 ° when the sprite is looking up, you need to add 90 ° to the angle for the calculation of the direction vector. See also te in pygame while moving with the keys](How to turn the sprite in pygame while moving with the keys. Class Bob: import pygame import math BLACK = (0,0,0) class Bob(pygame.sprite.Sprite): def __init__(self, color , height , width): super().__init__() self.image = pygame.Surface([width , height]) self.image.fill(BLACK) self.image.set_colorkey(BLACK) #Loading the image for the character self.img = pygame.image.load("char.jfif") #creating a copy of the image self.img_orig = self.img.copy() #defining the starting angle of the character image self.angle = 0 self.velocity = 5 self.rect = self.img_orig.get_rect() self.x, self.y = self.rect.center def rotate(self, change_angle): self.angle += change_angle self.img = pygame.transform.rotate(self.img_orig, self.angle) self.rect = self.img.get_rect(center = self.rect.center) def move(self, distance): self.x += distance * math.cos(math.radians(self.angle + 90)) self.y -= distance * math.sin(math.radians(self.angle + 90)) self.rect.center = round(self.x), round(self.y) def moveLeft(self): self.rotate(1) def moveRight(self): self.rotate(-1) def moveForward(self): self.move(self.velocity) def moveDown(self): self.move(-self.velocity) When setting the starting position of the player, you need to set the x, y and rect attribute: pSprite = Bob(WHITE , 25,25) pSprite.rect.x = 50 pSprite.rect.y = 50 pSprite.x, pSprite.y = pSprite.rect.center
pygame sprite collision with background elements [duplicate]
This question already has answers here: Pygame mask collision (1 answer) How can I made a collision mask? (1 answer) Closed 2 years ago. I've been looking for days to find a solution but any of the other threads could help me. I've been trying to make the sprite move over the background image. The background have transparent streets that should be the possible paths for the sprite. I need to detect when the sprite collide with the other part of the background image that is not transparent. i tried perfect collision method but i don't think it's the right solution for my case because the background.rect doesn't make any sense. I also tried the overlap method but it always return true. The pictures are 32 bit depth and i'm calling convert_alpha() class sprites(pygame.sprite.Sprite): def __init__(self): pygame.sprite.Sprite.__init__(self) self.x = 200 self.y = 300 self.img= player_sprite self.rect = player_sprite.get_rect() self.mask = pygame.mask.from_surface(player_sprite) def position(self): dx = mouse_x-self.x dy = self.y-mouse_y d = float((dx**2+dy**2)**0.5) displ_x = dx/d displ_y = dy/d self.x += displ_x self.y += displ_y if type(self.mask.overlap(object_background.mask,(0,0))): self.x -= displ_x self.y -= displ_y class object_background_class(pygame.sprite.Sprite): def __init__(self): pygame.sprite.Sprite.__init__(self) self.img = object_background_img self.rect = object_background_img.get_rect() self.mask = pygame.mask.from_surface(object_background_img.convert_alpha()) object_background = object_background_class() player = sprites() player.position() changes each time the coordinates of the sprite accordind to the mouse(x,y) and check if with the new x,y of the player make it collides with the background game_state = False while not game_state: for e in pygame.event.get(): if e == pygame.QUIT: game_state = True if e.type == pygame.KEYDOWN and e.type == pygame.K_ESCAPE: game_state = True if e.type == pygame.KEYDOWN: if e.key == 27: game_state = True mouse_x, mouse_y = pygame.mouse.get_pos() player.position() DISPLAYSURFACE.blit(color_background, (0, 0)) DISPLAYSURFACE.blit(player.img, (player.x, player.y)) DISPLAYSURFACE.blit(object_background.img, (0, 0)) pygame.display.flip()
The game screen is one big coordinate plane, just say if x, y coords of player go over or under whatever x,y coord threshold than do something
I also tried the overlap method but it always return true. Of course. pygame.sprite.collide_mask() use the .rect and .mask attribute of the sprite object for the collision detection. You have to update self.rect after moving the player and changing the self.x and self.y attribute of the player: class sprites(pygame.sprite.Sprite): def __init__(self): pygame.sprite.Sprite.__init__(self) self.x = 200 self.y = 300 self.img= player_sprite self.rect = player_sprite.get_rect() self.mask = pygame.mask.from_surface(player_sprite) def position(self): dx = mouse_x-self.x dy = self.y-mouse_y d = float((dx**2+dy**2)**0.5) displ_x = dx/d displ_y = dy/d self.x += displ_x self.y += displ_y if type(self.mask.overlap(object_background.mask,(0,0))): self.x -= displ_x self.y -= displ_y self.rect.topleft = (round(self.x), round(self.y)) # <--- THIS IS MISSING Now you can use collide_mask: if pygame.sprite.collide_mask(player, object_background): # [...]
pygame/python wont detect collision between sprites
im trying to detect a collision between pacman and the boxes, but its not detecting any collision, any help or advice? at the moment ive tried creating a list of instances but that hasnt worked, i dont know what else to do. also its telling me to add more detail but i dont really know what i can add to be honest, sorry import pygame import os import sys #intialise the game pygame.init() screen = pygame.display.set_mode((448, 576)) done = False y = 320 x = 216 #sets up clock and loads pacman image clock = pygame.time.Clock() PACMANSPRITE = pygame.image.load("pacman.png").convert_alpha() #gets pacman intro music, sets music to lower volume then plays it pygame.mixer.music.load('pacman_beginning.WAV') pygame.mixer.music.set_volume(0.01) pygame.mixer.music.play(0) #box class, used for boxes to border pacmans map class boxcollisions(pygame.sprite.Sprite): def __init__(self, x, y): self.y = y self.x = x self.rect = pygame.Rect(self.x, self.y, 15, 15) self.color = (0, 128, 255) def draw(self, screen): pygame.draw.rect(screen, self.color, self.rect) #pacmans class class pacman(pygame.sprite.Sprite): def __init__(self, image, x, y): self.image = image self.y=y self.x=x self.rect = self.image.get_rect() self.rect.left = self.x self.rect.top = self.y self.rect.width=16 self.rect.height=16 # move pacman def movement(self): pressed= pygame.key.get_pressed() if pressed[pygame.K_UP]: self.y -= 2 if pressed[pygame.K_DOWN]: self.y += 2 if pressed[pygame.K_LEFT]: self.x -= 2 if pressed[pygame.K_RIGHT]: self.x += 2 def draw(self, surface): """ Draw on surface """ # blit yourself at your current position surface.blit(self.image, (self.x, self.y)) #instances the pacman class sprite = pacman(PACMANSPRITE, x ,y) #main game loop while not done: for event in pygame.event.get(): if event.type == pygame.QUIT: done = True pygame.quit() sys.exit() screen.fill((100,0,0)) #co-ordinates for boxes to set up map boundaries boundaries=[ [], [], [], [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28], [1,14,15,28], #5 [1,3,4,5,6,8,9,10,11,12,14,15,17,18,19,20,21,23,24,25,26,28], [1,3,4,5,6,8,9,10,11,12,14,15,17,18,19,20,21,23,24,25,26,28], [1,3,4,5,6,8,9,10,11,12,14,15,17,18,19,20,21,23,24,25,26,28], [1,28], [1,3,4,5,6,8,9,11,12,13,14,15,16,17,18,20,21,23,24,25,26,28], #10 [1,3,4,5,6,8,9,11,12,13,14,15,16,17,18,20,21,23,24,25,26,28], [1,8,9,14,15,20,21,28], [1,2,3,4,5,6,8,9,10,11,12,14,15,17,18,19,20,21,23,24,25,26,27,28], [1,2,3,4,5,6,8,9,10,11,12,14,15,17,18,19,20,21,23,24,25,26,27,28], [6,8,9,20,21,23], #15 [6,8,9,11,12,13,14,15,16,17,18,20,21,23], [1,2,3,4,5,6,8,9,11,12,13,14,15,16,17,18,20,21,23,24,25,26,27,28], [1,11,12,13,14,15,16,17,18,28], [1,2,3,4,5,6,8,9,11,12,13,14,15,16,17,18,20,21,23,24,25,26,27,28], [6,8,9,11,12,13,14,15,16,17,18,20,21,23,24,25,26,27,28], #20 [6,8,9,20,21,23], [6,8,9,11,12,13,14,15,16,17,18,20,21,23], [1,2,3,4,5,6,8,9,11,12,13,14,15,16,17,18,20,21,23,24,25,26,27,28], [1,14,15,28], [1,3,4,5,6,8,9,10,11,12,14,15,17,18,19,20,21,23,24,25,26,28], #25 [1,3,4,5,6,8,9,10,11,12,14,15,17,18,19,20,21,23,24,25,26,28], [1,5,6,23,24,28], [1,2,3,5,6,8,9,11,12,13,14,15,16,17,18,20,21,23,24,26,27,28], [1,2,3,5,6,8,9,11,12,13,14,15,16,17,18,20,21,23,24,26,27,28], [1,8,9,14,15,20,21,28], # 30 [1,3,4,5,6,7,8,9,10,11,12,14,15,17,18,19,20,21,22,23,24,25,26,28], [1,3,4,5,6,7,8,9,10,11,12,14,15,17,18,19,20,21,22,23,24,25,26,28], [1,28], [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28], ] #builds the boxes bx=0 by=-16 for row in boundaries: #y co ordinate by=by+16 for n in row: #x co ordinate n=n-1 bx=n*16 box = boxcollisions(bx, by) box.draw(screen) #moves pacman sprite.movement() sprite.draw(screen) #tests for collision print(pygame.sprite.collide_rect(sprite, box)) pygame.display.flip() clock.tick(60)
1 - You need update the top and left position at moviment method. look: # move pacman def movement(self): pressed= pygame.key.get_pressed() if pressed[pygame.K_UP]: self.y -= 2 if pressed[pygame.K_DOWN]: self.y += 2 if pressed[pygame.K_LEFT]: self.x -= 2 if pressed[pygame.K_RIGHT]: self.x += 2 self.rect.left = self.x self.rect.top = self.y 2 - You have to verificate the collision into loop, for verification with all boxes for row in boundaries: #y co ordinate by=by+16 for n in row: #x co ordinate n=n-1 bx=n*16 box = boxcollisions(bx, by) box_list.append(box) box.draw(screen) if pygame.sprite.collide_rect(sprite, box): print("collided")
Use rect.collidelist to test if pacman is colliding with a wall sprites in your wall sprites list. It will return -1 as long as no collision is detected
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()