旋转的角色和精灵墙

时间:2016-09-29 09:34:19

标签: python python-2.7 pygame sprite

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

基本上我想要的就是让我的精灵(Character)不进入精灵墙。现在因为墙壁的矩形比较大,所以实际的图片似乎和我的矩形不断增长和缩小,这取决于我的鼠标位置,它让我无能为力,如何做出一个声明,阻止我的精灵以令人信服的方式进入墙壁

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

我的角色类看起来像这样:

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

My 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

1 个答案:

答案 0 :(得分:2)

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

防止你的大小调整

由于旋转时图像越来越大,您可以通过删除额外的填充来补偿并保持图像的原始大小。

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

Ted Klein Bergman

要取出新图像的中心,我们只需取出图像的一个子表面,该图像的大小与我们之前在图像中心的矩形一样。

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。您不需要radius属性,它是可选的。但是如果你没有pygame将根据精灵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作为回调函数传递。此方法要求您的精灵具有rect属性和每像素alpha Surface或具有colorkey的Surface。

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

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

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惯例时(当然还有更多),它会告诉你。

重要的是要注意这些是惯例而不是规则。它们旨在使代码更具可读性,而不是严格遵守。如果您认为它提高了可读性,请打破它们。