【问题标题】:Rotating character and sprite wall旋转角色和精灵墙
【发布时间】:2016-09-29 09:34:18
【问题描述】:

我有一个代表我的角色的精灵。这个精灵根据我的鼠标位置旋转每一帧,这反过来又使我的矩形根据鼠标的位置变得越来越小。

基本上我想要做的是让我的精灵 (Character) 不会进入精灵墙。现在,由于墙壁的矩形更大,所以实际图片看起来并且我的矩形根据我的鼠标位置不断增长和缩小,这让我不知道如何做出一个声明来阻止我的精灵以令人信服的方式移动到墙壁中.

我已经确定我的 ColideList 只是应该与之碰撞的块。我找到了Detecting collision of two sprites that can rotate,但它是在 Java 中的,我不需要检查两个旋转精灵之间的碰撞,而是一个和一堵墙。

我的 Character 类如下所示:

class Character(pygame.sprite.Sprite):
    walking_frame = []
    Max_Hp = 100
    Current_HP = 100
    Alive = True
    X_Speed = 0
    Y_Speed = 0
    Loc_x = 370
    Loc_y = 430
    size = 15
    Current_Weapon = Weapon()
    Angle = 0
    reloading = False
    shot = False
    LastFrame = 0
    TimeBetweenFrames = 0.05
    frame = 0
    Walking = False
    Blocked = 0
    rel_path = "Sprite Images/All.png"
    image_file = os.path.join(script_dir, rel_path)
    sprite_sheet = SpriteSheet(image_file) #temp
    image = sprite_sheet.get_image(0, 0, 48, 48) #Temp
    image = pygame.transform.scale(image, (60, 60))
    orgimage = image
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.walking_frame.append(self.image)
        image = self.sprite_sheet.get_image(48, 0, 48, 48)
        self.walking_frame.append(image)
        image = self.sprite_sheet.get_image(96, 0, 48, 48)
        self.walking_frame.append(image)
        image = self.sprite_sheet.get_image(144, 0, 48, 48)
        self.walking_frame.append(image)
        image = self.sprite_sheet.get_image(0, 48, 48, 48)
        self.walking_frame.append(image)
        image = self.sprite_sheet.get_image(48, 48, 48, 48)
        self.walking_frame.append(image)
        image = self.sprite_sheet.get_image(96, 48, 48, 48)
        self.walking_frame.append(image)
        image = self.sprite_sheet.get_image(144, 48, 48, 48)
        self.walking_frame.append(image)
        self.rect = self.image.get_rect()
        self.rect.left, self.rect.top = [self.Loc_x,self.Loc_y]
        print "Shabat Shalom"
    def Shoot(self):
        if self.Alive:
            if(self.reloading == False):
                if(self.Current_Weapon.Clip_Ammo > 0):
                    bullet = Bullet(My_Man)
                    bullet_list.add(bullet)
                    self.Current_Weapon.Clip_Ammo -= 1
    def move(self):
        if self.Alive:
            self.Animation()

            self.Loc_x += self.X_Speed
            self.Loc_y += self.Y_Speed
            Wall_hit_List = pygame.sprite.spritecollide(My_Man, CollideList, False)
            self.Blocked = 0
            for wall in Wall_hit_List:
                if self.rect.right <= wall.rect.left and self.rect.right >= wall.rect.right:
                    self.Blocked = 1 #right
                    self.X_Speed= 0
                elif self.rect.left <= wall.rect.right and self.rect.left >= wall.rect.left:
                    self.Blocked = 3 #Left
                    self.X_Speed = 0
                elif self.rect.top <= wall.rect.bottom and self.rect.top >= wall.rect.top:
                    self.Blocked = 2 #Up
                    self.Y_Speed = 0
                elif self.rect.top >= wall.rect.bottom and self.rect.top <= wall.rect.top:
                    self.Blocked = 4 #Down
                    self.Y_Speed = 0
            self.image = pygame.transform.rotate(self.orgimage, self.Angle)
            self.rect = self.image.get_rect()
            self.rect.left, self.rect.top = [self.Loc_x, self.Loc_y]
    def Animation(self):
    #      #Character Walk Animation
        if self.X_Speed != 0 or self.Y_Speed != 0:
            if(self.Walking == False):
                self.LastFrame = time.clock()
                self.Walking = True
                if (self.frame < len(self.walking_frame)):
                    self.image = self.walking_frame[self.frame]
                    self.image = pygame.transform.scale(self.image, (60, 60))
                    self.orgimage = self.image
                    self.frame += 1
                else:
                    self.frame = 0
        else:
            if self.frame != 0:
                self.frame = 0
                self.image = self.walking_frame[self.frame]
                self.image = pygame.transform.scale(self.image, (60, 60))
                self.orgimage = self.image
        if self.Walking and time.clock() - self.LastFrame > self.TimeBetweenFrames:
            self.Walking = False
    def CalAngle(self,X,Y):
        angle = math.atan2(self.Loc_x - X, self.Loc_y - Y)
        self.Angle = math.degrees(angle) + 180

我的 Wall 类如下所示:

class Wall(pygame.sprite.Sprite):
    def __init__(self, PosX, PosY, image_file, ImageX,ImageY):
        pygame.sprite.Sprite.__init__(self)
        self.sprite_sheet = SpriteSheet(image_file)
        self.image = self.sprite_sheet.get_image(ImageX, ImageY, 64, 64)
        self.image = pygame.transform.scale(self.image, (32, 32))
        self.image.set_colorkey(Black)
        self.rect = self.image.get_rect()
        self.rect.x = PosX
        self.rect.y = PosY

我的 BuildWall 函数如下所示:

def BuildWall(NumberOfBlocks,TypeBlock,Direction,X,Y,Collide):
    for i in range(NumberOfBlocks):
        if Direction == 1:
            wall = Wall(X + (i * 32), Y, spriteList, 0, TypeBlock)
            wall_list.add(wall)
        if Direction == 2:
            wall = Wall(X - (i * 32), Y, spriteList, 0, TypeBlock)
            wall_list.add(wall)
        if Direction == 3:
            wall = Wall(X, Y + (i * 32), spriteList, 0, TypeBlock)
            wall_list.add(wall)
        if Direction == 4:
            wall = Wall(X, Y - (i * 32), spriteList, 0, TypeBlock)
            wall_list.add(wall)
        if(Collide):
            CollideList.add(wall)

最后我的步行活动是这样的:

elif event.type == pygame.KEYDOWN:
        if event.key == pygame.K_ESCAPE: #Press escape also leaves game
            Game = False
        elif event.key == pygame.K_w and My_Man.Blocked != 2:
            My_Man.Y_Speed = -3
        elif event.key == pygame.K_s and My_Man.Blocked != 4:
            My_Man.Y_Speed = 3
        elif event.key == pygame.K_a and My_Man.Blocked != 3:
            My_Man.X_Speed = -3
        elif event.key == pygame.K_d and My_Man.Blocked != 1:
            My_Man.X_Speed = 3
        elif event.key == pygame.K_r and (My_Man.reloading == False):
            lastReloadTime = time.clock()
            My_Man.reloading = True
            if (My_Man.Current_Weapon.Name == "Pistol"):
                My_Man.Current_Weapon.Clip_Ammo = My_Man.Current_Weapon.Max_Clip_Ammo
            else:
                My_Man.Current_Weapon.Clip_Ammo, My_Man.Current_Weapon.Max_Ammo = Reload(My_Man.Current_Weapon.Max_Ammo,My_Man.Current_Weapon.Clip_Ammo,My_Man.Current_Weapon.Max_Clip_Ammo)
    elif event.type == pygame.KEYUP:
        if event.key == pygame.K_w:
            My_Man.Y_Speed = 0
        elif event.key == pygame.K_s:
            My_Man.Y_Speed = 0
        elif event.key == pygame.K_a:
            My_Man.X_Speed = 0
        elif event.key == pygame.K_d:
            My_Man.X_Speed = 0

【问题讨论】:

  • 我曾经遇到过类似的问题,并决定不旋转播放器精灵,而是为不同的朝向设置 8 张不同的图像。不确定这是否适用于您的游戏,但它可能是一个很好的最后解决方案。
  • @CharltonLane 我真的希望它能够跟随光标,这样我就可以以最直接的方式向光标射击。我决定,如果 StackOverFlow 不能帮助我,我可能会忽略墙壁并使其成为一个开放区域。但我还是有信心的

标签: python python-2.7 pygame sprite


【解决方案1】:

这完全取决于您的精灵的外观以及您想要的结果。我相信有 3 种不同类型的碰撞检测可以在您的场景中发挥作用。

防止调整矩形大小

由于图像在旋转时会变大,因此您可以通过移除多余的内边距并保持图像的原始大小来进行补偿。

假设原始图像的大小是 32 像素宽和 32 像素高。旋转后,图像宽 36 像素,高 36 像素。我们想要取出图像的中心(因为在它周围添加了填充)。

要取出新图像的中心,我们只需取出图像的一个次表面,其大小与图像内部的先前矩形大小相同。

def rotate(self, degrees):
    self.rotation = (self.rotation + degrees) % 360  # Keep track of the current rotation.
    self.image = pygame.transform.rotate(self.original_image, self.rotation))

    center_x = self.image.get_width() // 2
    center_y = self.image.get_height() // 2
    rect_surface = self.rect.copy()  # Create a new rectangle.
    rect_surface.center = (center_x, center_y)  # Move the new rectangle to the center of the new image.
    self.image = self.image.subsurface(rect_surface)  # Take out the center of the new image.

由于矩形的大小没有改变,我们不需要做任何事情来重新计算它(换句话说:self.rect = self.image.get_rect() 不是必需的)。

矩形检测

从这里你只需像往常一样使用pygame.sprite.spritecollide(或者如果你有自己的函数)。

def collision_rect(self, walls):
    last = self.rect.copy()  # Keep track on where you are.
    self.rect.move_ip(*self.velocity)  # Move based on the objects velocity.
    current = self.rect  # Just for readability we 'rename' the objects rect attribute to 'current'.
    for wall in pygame.sprite.spritecollide(self, walls, dokill=False):
        wall = wall.rect  # Just for readability we 'rename' the wall's rect attribute to just 'wall'.
        if last.left >= wall.right > current.left:  # Collided left side.
            current.left = wall.right
        elif last.right <= wall.left < current.right:  # Collided right side.
            current.right = wall.left
        elif last.top >= wall.bottom > current.top:  # Collided from above.
            current.top = wall.bottom
        elif last.bottom <= wall.top < current.bottom:  # Collided from below.
            current.bottom = wall.top

圆形碰撞

如果您要平铺墙壁,这可能效果不佳,因为您可以根据墙壁的大小和您的角色在瓷砖之间穿行。它对许多其他事情都有好处,所以我会保留它。

如果您将属性radius 添加到您的播放器和墙,您可以使用pygame.sprite.spritecollide 并传递回调函数pygame.sprite.collide_circle。您不需要半径属性,它是可选的。但是如果你不这样做,pygame 会根据 sprites 的 rect 属性计算半径,除非半径不断变化,否则这是不必要的。

def collision_circular(self, walls):
    self.rect.move_ip(*self.velocity)
    current = self.rect
    for wall in pygame.sprite.spritecollide(self, walls, dokill=False, collided=pygame.sprite.collide_circle):
        distance = self.radius + wall.radius
        dx = current.centerx - wall.rect.centerx
        dy = current.centery - wall.rect.centery
        multiplier = ((distance ** 2) / (dx ** 2 + dy ** 2)) ** (1/2)
        current.centerx = wall.rect.centerx + (dx * multiplier)
        current.centery = wall.rect.centery + (dy * multiplier)

像素完美碰撞

这是最难实现的,而且性能很重,但可以给你最好的结果。我们仍将使用pygame.sprite.spritecollide,但这次我们将传递pygame.sprite.collide_mask 作为回调函数。此方法要求您的 sprite 具有 rect 属性和每像素 alpha Surface 或带有颜色键的 Surface。

掩码属性是可选的,如果没有,该函数将临时创建一个。如果您使用蒙版属性,则每次更改精灵图像时都需要更改更新它。

这种碰撞的难点不是检测它,而是正确响应并使其适当地移动/停止。我做了一个错误的例子,展示了一种稍微体面地处理它的方法。

def collision_mask(self, walls):
    last = self.rect.copy()
    self.rect.move_ip(*self.velocity)
    current = self.rect
    for wall in pygame.sprite.spritecollide(self, walls, dokill=False, collided=pygame.sprite.collide_mask):
        if not self.rect.center == last.center:
            self.rect.center = last.center
            break
        wall = wall.rect
        x_distance = current.centerx - wall.centerx
        y_distance = current.centery - wall.centery
        if abs(x_distance) > abs(y_distance):
            current.centerx += (x_distance/abs(x_distance)) * (self.velocity[0] + 1)
        else:
            current.centery += (y_distance/abs(y_distance)) * (self.velocity[1] + 1)

完整代码

您可以尝试不同的示例,按 1 表示矩形碰撞,按 2 表示圆形碰撞,按 3 表示像素完美碰撞。在某些地方有点小问题,动作不是一流的,性能也不理想,但这只是一个简单的演示。

import pygame
pygame.init()

SIZE = WIDTH, HEIGHT = (256, 256)
clock = pygame.time.Clock()
screen = pygame.display.set_mode(SIZE)
mode = 1
modes = ["Rectangular collision", "Circular collision", "Pixel perfect collision"]


class Player(pygame.sprite.Sprite):

    def __init__(self, pos):
        super(Player, self).__init__()
        self.original_image = pygame.Surface((32, 32))
        self.original_image.set_colorkey((0, 0, 0))
        self.image = self.original_image.copy()
        pygame.draw.ellipse(self.original_image, (255, 0, 0), pygame.Rect((0, 8), (32, 16)))

        self.rect = self.image.get_rect(center=pos)
        self.rotation = 0
        self.velocity = [0, 0]
        self.radius = self.rect.width // 2
        self.mask = pygame.mask.from_surface(self.image)

    def rotate_clipped(self, degrees):
        self.rotation = (self.rotation + degrees) % 360  # Keep track of the current rotation
        self.image = pygame.transform.rotate(self.original_image, self.rotation)

        center_x = self.image.get_width() // 2
        center_y = self.image.get_height() // 2
        rect_surface = self.rect.copy()  # Create a new rectangle.
        rect_surface.center = (center_x, center_y)  # Move the new rectangle to the center of the new image.
        self.image = self.image.subsurface(rect_surface)  # Take out the center of the new image.

        self.mask = pygame.mask.from_surface(self.image)

    def collision_rect(self, walls):
        last = self.rect.copy()  # Keep track on where you are.
        self.rect.move_ip(*self.velocity)  # Move based on the objects velocity.
        current = self.rect  # Just for readability we 'rename' the objects rect attribute to 'current'.
        for wall in pygame.sprite.spritecollide(self, walls, dokill=False):
            wall = wall.rect  # Just for readability we 'rename' the wall's rect attribute to just 'wall'.
            if last.left >= wall.right > current.left:  # Collided left side.
                current.left = wall.right
            elif last.right <= wall.left < current.right:  # Collided right side.
                current.right = wall.left
            elif last.top >= wall.bottom > current.top:  # Collided from above.
                current.top = wall.bottom
            elif last.bottom <= wall.top < current.bottom:  # Collided from below.
                current.bottom = wall.top

    def collision_circular(self, walls):
        self.rect.move_ip(*self.velocity)
        current = self.rect
        for wall in pygame.sprite.spritecollide(self, walls, dokill=False, collided=pygame.sprite.collide_circle):
            distance = self.radius + wall.radius
            dx = current.centerx - wall.rect.centerx
            dy = current.centery - wall.rect.centery
            multiplier = ((distance ** 2) / (dx ** 2 + dy ** 2)) ** (1/2)
            current.centerx = wall.rect.centerx + (dx * multiplier)
            current.centery = wall.rect.centery + (dy * multiplier)

    def collision_mask(self, walls):
        last = self.rect.copy()
        self.rect.move_ip(*self.velocity)
        current = self.rect
        for wall in pygame.sprite.spritecollide(self, walls, dokill=False, collided=pygame.sprite.collide_mask):
            if not self.rect.center == last.center:
                self.rect.center = last.center
                break
            wall = wall.rect
            x_distance = current.centerx - wall.centerx
            y_distance = current.centery - wall.centery
            if abs(x_distance) > abs(y_distance):
                current.centerx += (x_distance/abs(x_distance)) * (self.velocity[0] + 1)
            else:
                current.centery += (y_distance/abs(y_distance)) * (self.velocity[1] + 1)

    def update(self, walls):
        self.rotate_clipped(1)

        if mode == 1:
            self.collision_rect(walls)
        elif mode == 2:
            self.collision_circular(walls)
        else:
            self.collision_mask(walls)


class Wall(pygame.sprite.Sprite):

    def __init__(self, pos):
        super(Wall, self).__init__()
        size = (32, 32)
        self.image = pygame.Surface(size)
        self.image.fill((0, 0, 255))  # Make the Surface blue.
        self.image.set_colorkey((0, 0, 0))  # Will not affect the image but is needed for collision with mask.
        self.rect = pygame.Rect(pos, size)

        self.radius = self.rect.width // 2
        self.mask = pygame.mask.from_surface(self.image)


def show_rects(player, walls):
    for wall in walls:
        pygame.draw.rect(screen, (1, 1, 1), wall.rect, 1)
    pygame.draw.rect(screen, (1, 1, 1), player.rect, 1)


def show_circles(player, walls):
    for wall in walls:
        pygame.draw.circle(screen, (1, 1, 1), wall.rect.center, wall.radius, 1)
    pygame.draw.circle(screen, (1, 1, 1), player.rect.center, player.radius, 1)


def show_mask(player, walls):
    for wall in walls:
        pygame.draw.rect(screen, (1, 1, 1), wall.rect, 1)
    for pixel in player.mask.outline():
        pixel_x = player.rect.x + pixel[0]
        pixel_y = player.rect.y + pixel[1]
        screen.set_at((pixel_x, pixel_y), (1, 1, 1))

# Create walls around the border.
walls = pygame.sprite.Group()
walls.add(Wall(pos=(col, 0)) for col in range(0, WIDTH, 32))
walls.add(Wall(pos=(0, row)) for row in range(0, HEIGHT, 32))
walls.add(Wall(pos=(col, HEIGHT - 32)) for col in range(0, WIDTH, 32))
walls.add(Wall(pos=(WIDTH - 32, row)) for row in range(0, HEIGHT, 32))
walls.add(Wall(pos=(WIDTH//2, HEIGHT//2)))  # Obstacle in the middle of the screen

player = Player(pos=(64, 64))
speed = 2  # Speed of the player.
while True:
    screen.fill((255, 255, 255))
    clock.tick(60)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            quit()
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_a:
                player.velocity[0] = -speed
            elif event.key == pygame.K_d:
                player.velocity[0] = speed
            elif event.key == pygame.K_w:
                player.velocity[1] = -speed
            elif event.key == pygame.K_s:
                player.velocity[1] = speed
            elif pygame.K_1 <= event.key <= pygame.K_3:
                mode = event.key - 48
                print(modes[mode - 1])
        elif event.type == pygame.KEYUP:
            if event.key == pygame.K_a or event.key == pygame.K_d:
                player.velocity[0] = 0
            elif event.key == pygame.K_w or event.key == pygame.K_s:
                player.velocity[1] = 0

    player.update(walls)
    walls.draw(screen)
    screen.blit(player.image, player.rect)

    if mode == 1:
        show_rects(player, walls)  # Show rectangles for circular collision detection.
    elif mode == 2:
        show_circles(player, walls)  # Show circles for circular collision detection.
    else:
        show_mask(player, walls)  # Show mask for pixel perfect collision detection.

    pygame.display.update()

最后一点

在进一步编程之前,您确实需要重构代码。我试图阅读你的一些代码,但它真的很难理解。试试关注Python's naming conventions,它会让其他程序员更容易阅读和理解你的代码,从而更容易帮助你解决问题。

只要遵循这些简单的准则,您的代码就会变得更易读:

  • 变量名只能包含小写字母。超过 1 个单词的名称应使用下划线分隔。示例:variablevariable_with_words
  • 函数和属性应遵循与变量相同的命名约定。
  • 每个单词的类名都应该以大写开头,其余的应该是小写。示例:ClassMyClass。称为 CamelCase。
  • 类中的方法用一行分隔,函数和类用两行分隔。

我不知道您使用的是哪种 IDE,但Pycharm Community Edition 是一个非常适合 Python 的 IDE。它会在您违反 Python 约定时向您显示(当然还有更多)。

请务必注意,这些是约定而非规则。它们旨在使代码更具可读性,而不是严格遵循。如果您认为这样可以提高可读性,请打破它们。

【讨论】:

  • 首先,非常感谢您提供的所有帮助,一旦这里的假期过去,我将确保尽快完成工作,并且我有更多的空闲时间。我正在使用 Pycharm,但实际上这是我第一次使用 python 编程,也是我第一次在没有老师帮助的情况下承担一个大项目,所以是的,我知道我可能应该重构我的代码,但我不知道那么多当我第一次开始研究它时,我心想我可能是唯一一个在研究它的人,所以我只是推迟了它。无论如何,再次非常感谢您提供的所有帮助。
  • 没问题,很高兴为您提供帮助。请记住,在您的编码风格中保持一致并使用这些指南也将帮助您以后自己阅读代码。如果您将脚本保留几周或几个月,您将忘记大部分代码。此外,如果此答案回答了您的问题,请单击复选标记以接受它作为答案。这将有助于在搜索时快速区分已解决的问题和未解决的问题。如果其他问题已得到解答,也请这样做。发表评论,否则会解释缺少的内容,以便我们或其他人再试一次。
  • 更新:我清理了添加所需的代码(我使用矩形碰撞)并且一切正常。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多