关于python:pygame不准确的碰撞检测和移动

pygame inaccurate collision detection and movement

在www.teachyourselfpython.com上的教程之后,我有一个用python3在pygame中开发的游戏的开始。这里有main.py,player.py和wall.py(分别具有player和walls类)。 播放器类包含用于碰撞检测和移动的代码。 不幸的是,玩家确实避免了平铺,但是在碰撞时,移动到了屏幕右侧的某个位置,而不是预期的移动(只是停止了)。 有谁能够帮助您解决此逻辑并纠正碰撞时的错误/不良运动。 下面是三个文件:main.py,player.py和walls.py。
主PY

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#main.py
import pygame
import random
from player import Player
from collectable import Collectable
from walls import Wall

pygame.init()
BLACK=(0,0,0)
WHITE=(255,255,255)
RED=(255,0,0)
GREEN =(0,255,0)

BLUE=(0,0,255)
GOLD=(255,215,0)
WIDTH=500
HEIGHT=500
size= (WIDTH,HEIGHT)
screen=pygame.display.set_mode(size)
pygame.display.set_caption("The Life Game")


done = False
clock=pygame.time.Clock()
wall_list=pygame.sprite.Group()
all_sprites = pygame.sprite.Group()
enemy_list  = pygame.sprite.Group()
player=Player()
player.walls=wall_list

all_sprites.add(player)


for i in range(random.randrange(100,200)):
        whiteStar = Collectable(WHITE, 3, 3,"White Star","Rect")
        whiteStar.rect.x = random.randrange(size[0])
        whiteStar.rect.y = random.randrange(size[1])
        all_sprites.add(whiteStar)

for i in range(50):

    enemy = Collectable(RED,6, 6,"Enemy","Ellipse")
    enemy.rect.x = random.randrange(300)
    enemy.rect.y = random.randrange(300)
    enemy_list.add(enemy)
    all_sprites.add(enemy)

coin1 = Collectable(GOLD,50,50,"Coin","Ellipse")
coin1.rect.x=440
coin1.rect.y=0
all_sprites.add(coin1)

coin2 = Collectable(GOLD,50,50,"Coin","Ellipse")
coin2.rect.x=0
coin2.rect.y=440
all_sprites.add(coin2)

enemy = Collectable(RED,100,100,"Enemy","Ellipse")
enemy.rect.x=70
enemy.rect.y=230
all_sprites.add(enemy)

#Make the walls (x_pos,y_pos, width, height,colour)



wall=Wall(0,0,10,600,GREEN)
wall_list.add(wall)
all_sprites.add(wall_list)

wall = Wall(50, 300, 400, 10,RED)
wall_list.add(wall)
all_sprites.add(wall_list)

wall = Wall(10, 200, 100, 10,BLUE)
wall_list.add(wall)
all_sprites.add(wall_list)


score=0
health=100

#- - - - - - - - - - - - - -Main Program Loop - - - - - - - - - - - - - - - -
def main():
        done=False
        score=0
        health=100
        while not done:


                  #- - - - - - Main event loop (this is where code for handling keyboard and mouse clicks will go)
        #Loop until the user clicks the 'x' button (to close program)
                  for event in pygame.event.get(): #User does something
                            if event.type == pygame.QUIT: #If the user clicked close
                                      done = True  #set the done flag to 'true' to exit the loop

                  keys = pygame.key.get_pressed() #checking pressed keys
                  if keys[pygame.K_LEFT]:
                     player.moveLeft(5)
                  if keys[pygame.K_RIGHT]:
                     player.moveRight(5)
                  if keys[pygame.K_UP]:
                     player.moveUp(5)
                  if keys[pygame.K_DOWN]:
                     player.moveDown(5)                


                 #>>----------DRAW SECTION -----------------------------------
                  #Clear the screen to BLACK. Any drawing commands should be put BELOW this or they will be reased with this command
                  screen.fill(BLACK)



                  #Select the font to be used (size, bold, italics, etc)
                  font_score = pygame.font.SysFont('Calibri',20,True,False)
                  font_health = pygame.font.SysFont('Calibri',20,True,False)
                #Printing a variable (score or health) to the screen involves converting the score (if integer) to a string first.score_label = font_score.render("Score:" + str(score),True,BLACK)
                  health_label = font_health.render("Health:"+str(health),True,WHITE)
                  score_label = font_score.render("Score:" + str(score),True, WHITE)
                #Now we can use this line of code to put the image of the text on the screen at a given position
                  screen.blit(score_label,[100,480])
                  screen.blit(health_label,[190,480])


                   #>>---------UPDATE SECTION / Put the logic of your game here (i.e. how objects move, when to fire them, etc)


                  all_sprites.update()

                  if coin1.collision_with(player):
                         score=score+1
                         coin1.kill()
                         coin1.rect.x=-20
                         coin1.rect.y=-330

                  if coin2.collision_with(player):
                         score=score+1
                         coin2.kill()
                         coin2.rect.x=-20
                         coin2.rect.y=-330

                  if enemy.collision_with(player):
                        health=health-25
                        enemy.kill()
                        enemy.rect.x=-20
                        enemy.rect.y=-330

                  enemy.update()





         #-------------PRINTING VARIABLES LIKE SCORE TO SCREEN
                  #Any drawing/graphics code should go here
                  all_sprites.draw(screen)

                  #Update the screen to show whatever you have drawn
                  pygame.display.flip()

                  #Set the frames per second (e.g. 30, 60 etc)
                  clock.tick(120)

main()

玩家

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import pygame
import random
from walls import Wall

class Player(pygame.sprite.Sprite):
    #-------------------Define Variables here
    speed=0
    #------------------Initialise Constructor
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image=pygame.image.load("player.png")
        self.rect = self.image.get_rect()

        #SET THE INITIAL SPEED TO ZERO
        self.change_x = 0
        self.change_y = 0

        #--------------Fetch the rectangle object that has the dimensions of the image
        self.rect =self.image.get_rect()
        #---------------Define movement
    def moveRight(self,pixels):
        self.rect.x+=pixels
    def moveLeft(self,pixels):
        self.rect.x-=pixels
    def moveUp(self,pixels):
        self.rect.y-=pixels
    def moveDown(self,pixels):
        self.rect.y+=pixels

    # Make our top-left corner the passed-in location.
    def settopleft():
        self.rect = self.image.get_rect()
        self.rect.y = y
        self.rect.x = x

        # Set speed vector
        self.change_x = 0
        self.change_y = 0
        self.walls = None

    def changespeed(self, x, y):
       """ Change the speed of the player."""
        self.change_x += x
        self.change_y += y


    def update(self):
        # Did this update cause us to hit a wall?
        block_hit_list = pygame.sprite.spritecollide(self, self.walls, False)
        for block in block_hit_list:
            # If we are moving right, set our right side to the left side of
            # the item we hit
            if self.change_x > 0:
                self.rect.right = block.rect.left
            else:
                # Otherwise if we are moving left, do the opposite.
                self.rect.left = block.rect.right

        # Move up/down
                self.rect.y += self.change_y

        # Check and see if we hit anything
        block_hit_list = pygame.sprite.spritecollide(self, self.walls, False)
        for block in block_hit_list:

            # Reset our position based on the top/bottom of the object.
            if self.change_y > 0:
                self.rect.top = block.rect.top
            else:
                self.rect.top = block.rect.bottom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import pygame

class Wall(pygame.sprite.Sprite):
    #Wall a player can run into
    def __init__(self, x, y, width, height,colour):
        #Constructor fo rthe wall that the player can run into
        #call the parent's constructor
        super().__init__()

        #Make a green wall, of the size specified in paramenters
        self.image=pygame.Surface([width,height])
        self.image.fill(colour)

        #Make the"passed-in" location ,the top left corner
        self.rect=self.image.get_rect()
        self.rect.y=y
        self.rect.x=x

附件中还有播放器和bg的图像:player background image


好吧,让我们先谈谈运动问题。 Player s更新方法中的代码应首先使玩家沿x轴移动,检查玩家是否与墙碰撞,以及是否碰撞,将其位置设置为块边缘。 然后,对y轴执行相同的操作。 必须以这种方式完成,因为否则我们将不知道玩家的方向,也无法将其位置重置为正确的边缘。 您似乎已经更改了代码,以致不再使用self.change_xchange_y属性,而是使用pygame.key.get_pressed移动播放器。 我将删除pygame.key.get_pressed块,并在事件循环中进行运动更改。 update方法中的一些问题也必须修复,例如:

1
2
if self.change_y > 0:
    self.rect.bottom = block.rect.top

对于碰撞检测,可以使用pygame.sprite.spritecollide并传递要检查的播放器和精灵组。 然后遍历返回的列表,并对每个碰撞的精灵执行一些操作。

这是您的更新代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import sys
import random
import pygame as pg


pg.init()


class Wall(pg.sprite.Sprite):
   """Wall a player can run into."""
    def __init__(self, x, y, width, height, colour):
        super().__init__()
        self.image = pg.Surface([width, height])
        self.image.fill(colour)
        # Make the"passed-in" location the top left corner.
        self.rect = self.image.get_rect(topleft=(x, y))


class Collectable(pg.sprite.Sprite):
   """A collectable item."""

    def __init__(self, colour, x, y, image, rect):
        super().__init__()
        self.image = pg.Surface((5, 5))
        self.image.fill(colour)
        self.rect = self.image.get_rect(topleft=(x, y))


class Player(pg.sprite.Sprite):

    def __init__(self):
        pg.sprite.Sprite.__init__(self)
        self.image = pg.Surface((30, 30))
        self.image.fill((50, 150, 250))
        self.rect = self.image.get_rect()
        #SET THE INITIAL SPEED TO ZERO
        self.change_x = 0
        self.change_y = 0
        self.health = 100

    def update(self):
        # Move left/right.
        self.rect.x += self.change_x
        # Did this update cause us to hit a wall?
        block_hit_list = pg.sprite.spritecollide(self, self.walls, False)
        for block in block_hit_list:
            # If we are moving right, set our right side to the left side of
            # the item we hit
            if self.change_x > 0:
                self.rect.right = block.rect.left
            else:
                # Otherwise if we are moving left, do the opposite.
                self.rect.left = block.rect.right

        # Move up/down.
        self.rect.y += self.change_y
        # Check and see if we hit anything.
        block_hit_list = pg.sprite.spritecollide(self, self.walls, False)
        for block in block_hit_list:
            # Reset our position based on the top/bottom of the object.
            if self.change_y > 0:
                self.rect.bottom = block.rect.top
            else:
                self.rect.top = block.rect.bottom


BLACK = (0,0,0)
WHITE = (255,255,255)
GREEN = (0,255,0)
RED = (255,0,0)
BLUE = (0,0,255)
GOLD = (255,215,0)

size = (500, 500)
screen = pg.display.set_mode(size)
pg.display.set_caption("The Life Game")

wall_list = pg.sprite.Group()
all_sprites = pg.sprite.Group()
enemy_list = pg.sprite.Group()
coins = pg.sprite.Group()

player = Player()
player.walls = wall_list
all_sprites.add(player)

for i in range(random.randrange(100,200)):
    x = random.randrange(size[0])
    y = random.randrange(size[1])
    whiteStar = Collectable(WHITE, x, y,"White Star","Rect")
    all_sprites.add(whiteStar)

for i in range(50):
    x = random.randrange(size[0])
    y = random.randrange(size[1])
    enemy = Collectable(RED, x, y,"Enemy","Ellipse")
    enemy_list.add(enemy)
    all_sprites.add(enemy)

coin1 = Collectable(GOLD,240,200,"Coin","Ellipse")
coin2 = Collectable(GOLD,100,340,"Coin","Ellipse")
all_sprites.add(coin1, coin2)
coins.add(coin1, coin2)

# Make the walls.
walls = [Wall(0,0,10,600,GREEN), Wall(50, 300, 400, 10,RED),
         Wall(10, 200, 100, 10,BLUE)]
wall_list.add(walls)
all_sprites.add(walls)


def main():
    clock = pg.time.Clock()
    done = False
    score = 0
    font_score = pg.font.SysFont('Calibri',20,True,False)
    font_health = pg.font.SysFont('Calibri',20,True,False)
    while not done:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                done = True
            # Do the movement in the event loop by setting
            # the player's change_x and y attributes.
            elif event.type == pg.KEYDOWN:
                if event.key == pg.K_LEFT:
                    player.change_x = -3
                elif event.key == pg.K_RIGHT:
                    player.change_x = 3
                elif event.key == pg.K_UP:
                    player.change_y = -3
                elif event.key == pg.K_DOWN:
                    player.change_y = 3
            elif event.type == pg.KEYUP:
                if event.key == pg.K_LEFT and player.change_x < 0:
                    player.change_x = 0
                elif event.key == pg.K_RIGHT and player.change_x > 0:
                    player.change_x = 0
                elif event.key == pg.K_UP and player.change_y < 0:
                    player.change_y = 0
                elif event.key == pg.K_DOWN and player.change_y > 0:
                    player.change_y = 0

        # UPDATE SECTION / Put the logic of your game here (i.e. how
        # objects move, when to fire them, etc).
        all_sprites.update()

        # spritecollide returns a list of the collided sprites in the
        # passed group. Iterate over this list to do something per
        # collided sprite. Set dokill argument to True to kill the sprite.
        collided_enemies = pg.sprite.spritecollide(player, enemy_list, True)
        for enemy in collided_enemies:
            player.health -= 25

        collided_coins = pg.sprite.spritecollide(player, coins, True)
        for coin in collided_coins:
            score += 1

        # DRAW SECTION
        screen.fill(BLACK)
        all_sprites.draw(screen)

        health_label = font_health.render("Health:"+str(player.health),True,WHITE)
        score_label = font_score.render("Score:" + str(score),True, WHITE)
        screen.blit(score_label,[100,480])
        screen.blit(health_label,[190,480])

        pg.display.flip()
        clock.tick(60)

if __name__ == '__main__':
    main()
    pg.quit()
    sys.exit()