我有一个代表我角色的精灵。这个精灵根据我的鼠标位置旋转每一帧,这反过来使得我的矩形变得越来越小,这取决于鼠标的位置。
基本上我想要的就是让我的精灵(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
答案 0 :(得分:2)
这完全取决于你的精灵的外观以及你想要的结果。我相信有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
。您不需要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,让其他程序员更轻松地阅读和理解您的代码,这样他们就可以更轻松地帮助您解决问题。
遵循这些简单的指南将使您的代码更具可读性:
variable
,variable_with_words
。Class
,MyClass
。被称为CamelCase。我不知道您使用的IDE类型,但Pycharm Community Edition是一个很棒的Python IDE。当你打破Python惯例时(当然还有更多),它会告诉你。
重要的是要注意这些是惯例而不是规则。它们旨在使代码更具可读性,而不是严格遵守。如果您认为它提高了可读性,请打破它们。