How to make a sprite rotate to face the mouse? [duplicate]

ぃ、小莉子 提交于 2020-05-31 03:06:07

问题


After several hours of complete utter chaos, I still haven't found a solution to rotating a sprite to face the mouse position. I've implemented several examples from other post, the links are below, but none of them seem to be effective. Any help with my problem, would be very appreciated :)

  1. Pygame Making A Sprite Face The Mouse

  2. https://ubuntuforums.org/showthread.php?t=1823825

  3. https://gamedev.stackexchange.com/questions/132163/how-can-i-make-the-player-look-to-the-mouse-direction-pygame-2d

Original Code:

class Player(pygame.sprite.Sprite):
    def __init__(self, game, x, y):
        pygame.sprite.Sprite.__init__(self)
        self.game = game
        sprite_sheet = Spritesheet("Sprites/ships_spritesheet.png")
        image = sprite_sheet.get_image(204, 115, 66, 113)
        self.image = pygame.transform.flip(image, False, True)
        self.orig_img = self.image
        self.rect = self.image.get_rect()
        self.pos = vec(x, y)
        self.vel = vec(0, 0)
        self.health = PLAYER_HEALTH

    def update(self):
        self.rotate()
        self.pos += self.vel
        self.rect.x = self.pos.x
        self.collide_with_tiles(self.game.obstacle_list, "x")
        self.rect.y = self.pos.y
        self.collide_with_tiles(self.game.obstacle_list, "y")

    def rotate(self):
        mouse_x, mouse_y = pygame.mouse.get_pos()
        rel_x, rel_y = mouse_x - self.rect.x, mouse_y - self.rect.y
        angle = (180 / math.pi) * -math.atan2(rel_y, rel_x)
        self.image = pygame.transform.rotate(self.orig_img, int(angle))
        self.rect = self.image.get_rect(center=self.pos)

New Code:

class Player(pygame.sprite.Sprite):
    def __init__(self, game, pos):
        pygame.sprite.Sprite.__init__(self)
        self.game = game
        sprite_sheet = Spritesheet("Sprites/ships_spritesheet.png")
        image = sprite_sheet.get_image(204, 115, 66, 113)
        self.image = pygame.transform.flip(image, False, True)
        self.orig_img = self.image
        self.rect = self.image.get_rect(center=pos)
        self.pos = vec(pos)
        self.vel = vec(0, 0)
        self.health = PLAYER_HEALTH

    def update(self):
        self.rotate()
        self.pos += self.vel
        self.rect.centerx = self.pos.x
        #self.rect.x = self.pos.x
        self.collide_with_tiles(self.game.obstacle_list, "x")
        self.rect.centery = self.pos.y
        #self.rect.y = self.pos.y
        self.collide_with_tiles(self.game.obstacle_list, "y")
        self.vel = vec(0, 0)

    def rotate(self):
        mouse_pos = pygame.mouse.get_pos()
        rel_x, rel_y = mouse_pos - self.pos
        angle = -math.degrees(math.atan2(rel_y, rel_x))
        self.image = pygame.transform.rotate(self.orig_img, angle)
        self.rect = self.image.get_rect(center=self.rect.center)

    def collide_with_tiles(self, group, dir):
        if dir == "x":
            hits = pygame.sprite.spritecollide(self, group, False)
            if hits:
                if self.vel.x > 0:
                    self.rect.right = hits[0].rect.left
                if self.vel.x < 0:
                    self.rect.left = hits[0].rect.right
                self.pos.x = self.rect.centerx

        if dir == "y":
            hits = pygame.sprite.spritecollide(self, group, False)
            if hits:
                if self.vel.y > 0:
                    self.rect.bottom = hits[0].rect.top
                if self.vel.y < 0:
                    self.rect.top = hits[0].rect.bottom
                self.pos.y = self.rect.centery

My Game Class

import pygame, pytmx, sys, os

from Settings import *
from Obstacle import *
from Player import *
from Camera import *
from TiledMap import *
from MainMenu import *
from PauseMenu import *
from OptionsMenu import *
from HUD import *

class Game:
    def __init__(self):
        pygame.init()
        pygame.mixer.init()
        os.environ['SDL_VIDEO_CENTERED'] = '1'
        pygame.display.set_caption(GAME_TITLE)
        self.window = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
        self.clock = pygame.time.Clock()
        self.click_sound = pygame.mixer.Sound("Sounds/click1.ogg")
        self.main_menu = MainMenu(self, self.window)
        self.obstacle_list = pygame.sprite.Group()
        self.island_boundary_list = pygame.sprite.Group()
        self.pause_menu = PauseMenu(self.window)
        self.options_menu = OptionsMenu(self.window)
        self.hud = HUD()
        self.display_pause_menu = False
        self.display_options_menu = False
        self.display_main_menu = True

    def get_map(self):
        map_dir = TiledMap("Sprites/Maps/map_01.tmx")
        self.map = map_dir.generate_map()
        self.map_rect = self.map.get_rect()

        for tile_obj in map_dir.tmxdata.objects:
            if tile_obj.name == "Obstacle":
                obstacle = Obstacle(self, tile_obj.x, tile_obj.y, 64, 64)
                self.obstacle_list.add(obstacle)
            if tile_obj.name == "PLAYER":
                self.player = Player(self, (tile_obj.x, tile_obj.y))

    def game_loop(self):
        self.get_map()
        self.camera = Camera(5120, 5120)

        while True:
            self.clock.tick(FPS)
            self.game_events()

            if not self.display_pause_menu and not self.display_options_menu and not self.display_main_menu:
            self.update_game()

            self.draw_game()

    def game_events(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.run_game = False
                pygame.quit()
                sys.exit()

            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_p and not self.display_options_menu:
                    self.display_pause_menu = not self.display_pause_menu
                if event.key == pygame.K_o and not self.display_pause_menu:
                    self.display_options_menu = not self.display_options_menu

            if event.type == pygame.MOUSEBUTTONDOWN and self.display_main_menu:
                x, y = event.pos
                self.click_sound.play()
                if self.main_menu.play_button.collidepoint(x, y):
                    self.display_main_menu = False
                if self.main_menu.credits_button.collidepoint(x, y):
                    pass
                if self.main_menu.exit_button.collidepoint(x, y):
                    pygame.quit()
                    sys.exit()

            if event.type == pygame.MOUSEBUTTONDOWN and self.display_pause_menu:
                x, y = event.pos
                self.click_sound.play()
                if self.pause_menu.pause_resume_button.collidepoint(x, y) or self.pause_menu.pause_x_button.collidepoint(x, y):
                    self.display_pause_menu = False
                if self.pause_menu.pause_options_button.collidepoint(x, y):
                    self.display_pause_menu = False
                    self.display_options_menu = True
                if self.pause_menu.pause_quit_button.collidepoint(x, y):
                    pygame.quit()
                    sys.exit()

            if event.type == pygame.MOUSEBUTTONDOWN and self.display_options_menu:
                x, y = event.pos
                self.click_sound.play()
                if self.options_menu.options_x_button.collidepoint(x, y):
                    self.display_options_menu = False
                if self.options_menu.options_reset_button.collidepoint(x, y):
                    #reset options to original options if modified
                    pass
                if self.options_menu.options_home_button.collidepoint(x, y):
                    self.display_options_menu = False
                    self.display_main_menu = True
                if self.options_menu.options_ok_button.collidepoint(x, y):
                    #save settings
                    self.display_options_menu = False

            mouse_x, mouse_y = pygame.mouse.get_pos()
            if mouse_x > self.main_menu.play_button.x and mouse_x < self.main_menu.play_button.x + self.main_menu.play_button.width and mouse_y > self.main_menu.play_button.y and mouse_y < self.main_menu.play_button.y + self.main_menu.play_button.height:
                self.main_menu.img_id = 1

            elif mouse_x > self.main_menu.credits_button.x and mouse_x < self.main_menu.credits_button.x + self.main_menu.credits_button.width and mouse_y > self.main_menu.credits_button.y and mouse_y < self.main_menu.credits_button.y + self.main_menu.credits_button.height:
                self.main_menu.img_id = 2

            elif mouse_x > self.main_menu.exit_button.x and mouse_x < self.main_menu.exit_button.x + self.main_menu.exit_button.width and mouse_y > self.main_menu.exit_button.y and mouse_y < self.main_menu.exit_button.y + self.main_menu.exit_button.height:
                self.main_menu.img_id = 3

            else:
                self.main_menu.img_id = 0

            if mouse_x > self.pause_menu.pause_resume_button.x and mouse_x < self.pause_menu.pause_resume_button.x + self.pause_menu.pause_resume_button.width and mouse_y > self.pause_menu.pause_resume_button.y and mouse_y < self.pause_menu.pause_resume_button.y + self.pause_menu.pause_resume_button.height:
                self.pause_menu.img_id = 1

            elif mouse_x > self.pause_menu.pause_x_button.x and mouse_x < self.pause_menu.pause_x_button.x + self.pause_menu.pause_x_button.width and mouse_y > self.pause_menu.pause_x_button.y and mouse_y < self.pause_menu.pause_x_button.y + self.pause_menu.pause_x_button.height:
                self.pause_menu.img_id = 2

            elif mouse_x > self.pause_menu.pause_options_button.x and mouse_x < self.pause_menu.pause_options_button.x + self.pause_menu.pause_options_button.width and mouse_y > self.pause_menu.pause_options_button.y and mouse_y < self.pause_menu.pause_options_button.y + self.pause_menu.pause_options_button.height:
                self.pause_menu.img_id = 3

            elif mouse_x > self.pause_menu.pause_quit_button.x and mouse_x < self.pause_menu.pause_quit_button.x + self.pause_menu.pause_quit_button.width and mouse_y > self.pause_menu.pause_quit_button.y and mouse_y < self.pause_menu.pause_quit_button.y + self.pause_menu.pause_quit_button.height:
                self.pause_menu.img_id = 4

            else:
                self.pause_menu.img_id = 0

            if mouse_x > self.options_menu.options_x_button.x and mouse_x < self.options_menu.options_x_button.x + self.options_menu.options_x_button.width and mouse_y > self.options_menu.options_x_button.y and mouse_y < self.options_menu.options_x_button.y + self.options_menu.options_x_button.height:
                self.options_menu.img_id = 1

            elif mouse_x > self.options_menu.options_reset_button.x and mouse_x < self.options_menu.options_reset_button.x + self.options_menu.options_reset_button.width and mouse_y > self.options_menu.options_reset_button.y and mouse_y < self.options_menu.options_reset_button.y + self.options_menu.options_reset_button.height:
                self.options_menu.img_id = 2

            elif mouse_x > self.options_menu.options_home_button.x and mouse_x < self.options_menu.options_home_button.x + self.options_menu.options_home_button.width and mouse_y > self.options_menu.options_home_button.y and mouse_y < self.options_menu.options_home_button.y + self.options_menu.options_home_button.height:
                self.options_menu.img_id = 3

            elif mouse_x > self.options_menu.options_ok_button.x and mouse_x < self.options_menu.options_ok_button.x + self.options_menu.options_ok_button.width and mouse_y > self.options_menu.options_ok_button.y and mouse_y < self.options_menu.options_ok_button.y + self.options_menu.options_ok_button.height:
                self.options_menu.img_id = 4

            else:
                self.options_menu.img_id = 0

        keys = pygame.key.get_pressed()
        if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
            self.player.vel.x = PLAYER_SPEED
        if keys[pygame.K_LEFT] or keys[pygame.K_a]:
            self.player.vel.x = -PLAYER_SPEED
        if keys[pygame.K_UP] or keys[pygame.K_w]:
            self.player.vel.y = -PLAYER_SPEED
        if keys[pygame.K_DOWN] or keys[pygame.K_s]:
            self.player.vel.y = PLAYER_SPEED

    def update_game(self):
        self.player.update()
        self.camera.update(self.player)

        if self.player.rect.x <= 0:
            self.player.rect.x = 0
        if self.player.rect.right >= 5120:
            self.player.rect.right = 5120
        if self.player.rect.y <= 0:
            self.player.rect.y = 0
        if self.player.rect.bottom >= 5120:
            self.player.rect.bottom = 5120

    def draw_game(self):
        self.window.blit(self.map, self.camera.apply_rect(self.map_rect))
        self.window.blit(self.player.image, self.camera.apply(self.player))
        self.hud.draw_health(self.window, 10, 10, self.player.health / PLAYER_HEALTH)

        if self.display_main_menu:
            self.main_menu.draw()

        if self.display_pause_menu:
            self.pause_menu.draw()

        if self.display_options_menu:
            self.options_menu.draw()

        pygame.display.flip()

def main():
    g = Game()
    g.game_loop()

if __name__ == "__main__":
    main()

Camera class

import pygame

from Settings import *

class Camera:
    def __init__(self, width, height):
        self.camera = pygame.Rect(0, 0, width, height)
        self.width = width
        self.height = height

    def apply(self, target):
        return target.rect.move(self.camera.topleft)

    def apply_rect(self, rect):
        return rect.move(self.camera.topleft)

    def update(self, target):
        x = -target.rect.centerx + int(WINDOW_WIDTH/2)
        y = -target.rect.centery + int(WINDOW_HEIGHT/2)
        x = min(0, x)
        y = min(0, y)
        x = max(-(self.width - WINDOW_WIDTH), x)
        y = max(-(self.height - WINDOW_HEIGHT), y)
        self.camera = pygame.Rect(x, y, self.width, self.height)

回答1:


Use the center points self.rect.centerx and self.rect.centery or just self.rect.center and self.pos instead of self.rect.x self.rect.y (the topleft coordinates).

Here's the complete example that I used to test your code:

import math
import pygame
from pygame.math import Vector2


class Player(pygame.sprite.Sprite):
    def __init__(self, pos):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((50, 30), pygame.SRCALPHA)
        pygame.draw.polygon(
            self.image,
            pygame.Color('dodgerblue1'),
            ((0, 0), (50, 15), (0, 30)))
        self.rect = self.image.get_rect(center=pos)
        self.orig_img = self.image
        self.pos = Vector2(pos)
        self.vel = Vector2(0, 0)

    def update(self):
        self.rotate()
        self.pos += self.vel
        self.rect.centerx = self.pos.x
        self.rect.centery = self.pos.y

    def rotate(self):
        mouse_pos = pygame.mouse.get_pos()
        # Calculate the vector to the mouse position by subtracting
        # the self.pos vector from the mouse_pos.
        rel_x, rel_y = mouse_pos - self.pos
        # Use math.atan2 to get the angle in radians and convert it to degrees.
        angle = -math.degrees(math.atan2(rel_y, rel_x))
        # Rotate the image.
        self.image = pygame.transform.rotozoom(self.orig_img, angle, 1)
        # Update the rect and keep the center at the old position.
        self.rect = self.image.get_rect(center=self.rect.center)


def main():
    screen = pygame.display.set_mode((640, 480))
    clock = pygame.time.Clock()
    sprite_group = pygame.sprite.Group()
    player = Player((300, 200))
    sprite_group.add(player)

    done = False

    while not done:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True

        sprite_group.update()
        screen.fill((30, 30, 30))
        sprite_group.draw(screen)

        pygame.display.flip()
        clock.tick(30)


if __name__ == '__main__':
    pygame.init()
    main()
    pygame.quit()

Edit3: In this example I use another pygame.Rect (called hitbox) to handle the collisions with the walls. The rect of the player can't be used for the collision detection, because it changes its size with each rotation what causes jumps if the player touches a wall. I'm using Rect.colliderect because spritecollide uses the rect not the hitbox, but you could also pass a custom collided callback function to spritecollide. (The red and green rects show the player.rect and player.hitbox.)

import math
import pygame
from pygame.math import Vector2 as vec


class Player(pygame.sprite.Sprite):
    def __init__(self, x, y, walls):
        pygame.sprite.Sprite.__init__(self)
        self.walls = walls
        self.image = pygame.Surface((50, 30), pygame.SRCALPHA)
        pygame.draw.polygon(
            self.image,
            pygame.Color('dodgerblue1'),
            ((0, 0), (50, 15), (0, 30)))
        self.rect = self.image.get_rect(center=(x, y))
        self.hitbox = pygame.Rect(x, y, 50, 50)
        self.orig_img = self.image
        self.pos = vec(x, y)
        self.vel = vec(0, 0)

    def update(self):
        self.rotate()
        self.pos += self.vel
        self.hitbox.centerx = self.pos.x
        self.collide_with_tiles(self.walls, "x")
        self.hitbox.centery = self.pos.y
        self.collide_with_tiles(self.walls, "y")
        self.rect.center = self.pos

    def rotate(self):
        rel_x, rel_y = pygame.mouse.get_pos() - self.pos
        angle = -math.degrees(math.atan2(rel_y, rel_x))
        self.image = pygame.transform.rotate(self.orig_img, int(angle))
        self.rect = self.image.get_rect(center=self.pos)

    def collide_with_tiles(self, group, dir):
        if dir == "x":
             for wall in self.walls:
                 if self.hitbox.colliderect(wall.rect):
                     if self.vel.x > 0:
                         self.hitbox.right = wall.rect.left
                     if self.vel.x < 0:
                         self.hitbox.left = wall.rect.right
                     self.vel.x = 0
                     self.pos.x = self.hitbox.centerx

        if dir == "y":
            for wall in self.walls:
                if self.hitbox.colliderect(wall.rect):
                    if self.vel.y > 0:
                        self.hitbox.bottom = wall.rect.top
                    if self.vel.y < 0:
                        self.hitbox.top = wall.rect.bottom
                    self.vel.y = 0
                    self.pos.y = self.hitbox.centery


class Wall(pygame.sprite.Sprite):
    def __init__(self, x, y, w, h):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((w, h))
        self.image.fill(pygame.Color('sienna1'))
        self.rect = self.image.get_rect(topleft=(x, y))


def main():
    screen = pygame.display.set_mode((640, 480))
    clock = pygame.time.Clock()

    sprite_group = pygame.sprite.Group()
    walls = pygame.sprite.Group()

    wall = Wall(100, 200, 300, 30)
    walls.add(wall)
    sprite_group.add(wall)

    player = Player(300, 400, walls)
    sprite_group.add(player)

    done = False

    while not done:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_w:
                    player.vel.y = -3
                elif event.key == pygame.K_s:
                    player.vel.y = 3
                elif event.key == pygame.K_a:
                    player.vel.x = -3
                elif event.key == pygame.K_d:
                    player.vel.x = 3
            elif event.type == pygame.KEYUP:
                player.vel = vec(0, 0)

        sprite_group.update()
        screen.fill((30, 30, 30))
        sprite_group.draw(screen)
        pygame.draw.rect(screen, (200, 30, 30), player.rect, 2)
        pygame.draw.rect(screen, (0, 200, 30), player.hitbox, 2)

        pygame.display.flip()
        clock.tick(30)


if __name__ == '__main__':
    pygame.init()
    main()
    pygame.quit()

Edit4: To include the camera in the calculation, give the Player the camera as an attribute and in the rotate method just change this line:

rel_x, rel_y = pg.mouse.get_pos() - vec(self.camera.apply(self).center)


来源:https://stackoverflow.com/questions/44960680/how-to-make-a-sprite-rotate-to-face-the-mouse

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!