【问题标题】:How to make a sprite rotate to face the mouse? [duplicate]如何使精灵旋转以面对鼠标? [复制]
【发布时间】:2017-07-07 00:19:12
【问题描述】:

经过几个小时的彻底混乱后,我仍然没有找到将精灵旋转到鼠标位置的解决方案。我已经实现了其他帖子中的几个示例,链接如下,但似乎没有一个有效。对我的问题有任何帮助,将不胜感激:)

  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

原代码:

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)

新代码:

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

我的游戏课

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()

相机类

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)

【问题讨论】:

  • 仅供参考,玩家旋转了一些,但似乎没有跟随鼠标,或者没有跟上鼠标的移动。
  • 如果你只在一个连续的单一方向上滚动鼠标,它们会在圆周上旋转吗?
  • 你能不能再详细点,我不太明白你说的是什么。
  • 所以如果你只从左到右拖动鼠标,拿起它并把它放在左边并继续向右拖动..角色会旋转360度吗?
  • 当我顺时针或逆时针以 360 度完整的运动拖动鼠标时,精灵会正确旋转。但是一旦我向一个方向拖动鼠标,播放器图像就不能正确旋转,所有的混乱都会出现。

标签: python rotation pygame mouse


【解决方案1】:

使用中心点 self.rect.centerxself.rect.centery 或仅使用 self.rect.centerself.pos 而不是 self.rect.x self.rect.y(左上角坐标)。

这是我用来测试您的代码的完整示例:

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:在这个例子中,我使用另一个pygame.Rect(称为hitbox)来处理与墙壁的碰撞。玩家的rect 不能用于碰撞检测,因为它会随着每次旋转而改变它的大小,这会在玩家碰到墙壁时导致跳跃。我使用Rect.colliderect 因为spritecollide 使用rect 而不是hitbox,但您也可以将自定义collided 回调函数传递给spritecollide。 (红色和绿色矩形显示player.rectplayer.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:要将相机包含在计算中,请将相机作为属性赋予Player,并在rotate 方法中更改此行:

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

【讨论】:

  • 尽管我希望它能够工作,但这个解决方案似乎无效。旋转仍然无法正常工作:(
  • 那么你必须提供一个完整的例子,我可以测试,因为其他的东西(你的帖子中没有包含)似乎是错误的。
  • 好的,我将提供更多来自 Player 类的内容。
  • 查看这个完整的例子。
  • 我怀疑错误可能出在我的 collide_with_tiles 函数中。在这一点上,我真的一无所知。
猜你喜欢
  • 2011-10-10
  • 1970-01-01
  • 2011-06-19
  • 1970-01-01
  • 2018-10-23
  • 2020-03-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多