Pygame 平台游戏 2D 中的敌人移动

Enemy Movement in Pygame Platformer 2D

在我的第一个项目中,我试图对敌人的移动进行排序,下面的代码是我当前的实现。敌人使用玩家位置 (target.pos.x) 和他们的 pos.x 之间的距离。要敌人左移20步再变向右移20步,冲洗重复

self.target = game.player
def movement(self):
    self.acc = vec(0, BOSS_GRAVITY)
    if (-17 >= (self.pos.x - self.target.pos.x) >= -200) and self.target.hit_rect.y == self.hit_rect.y:
        self.vel.x -= BOSS_SPEED * -1
        self.enemy_direction = 'R'

    if (200 >= (self.pos.x - self.target.pos.x) >= 17) and self.target.hit_rect.y == self.hit_rect.y:
        self.vel.x += BOSS_SPEED * -1
        self.enemy_direction = 'L'
self.acc.x += self.vel.x * BOSS_FRICTION

    self.vel += self.acc
    self.pos += self.vel
    self.pos += self.vel + 0.5 * self.acc

我希望我的敌人向右移动一定量然后改变速度并向相反的方向移动而不是闲着。

I want the enemy to move left 20 paces then change direction and move right 20 paces, rinse and repeat.

好的,那我们如何实现呢?首先是一些定义:

什么是“步伐”?让我们从 5 pixels 开始。 左边是-x;右边是 +x.

还有一些额外的事情需要处理。如果对象 不能 向所需方向移动怎么办?它可以 turn around.

所以我们需要保留关于这个敌人的一系列统计数据:位置、步数、行进方向。一旦有了几个基准点,就想:数据结构。现在我要将所有这些放入 Python class,但它也可以放入一个简单的列表中。但是,如果数据点过多,这些就会变得笨拙。

# As a list
enemy_image = pygame.image.load( "doomba.png" ).convert_alpha()
enemy_rect  = enemy_image.get_rect()
enemy_rect.center = ( x, y )
enemy1 = [ enemy_rect, enemy_image, PACE_SIZE, TURN_SIZE ]

作为 class:

好多了
# As a sprite class
class Enemy( pygame.sprite.Sprite ):
    def __init__( self, x, y, bitmap, pace=5, turn_after=20 ):
            """ Create a new Enemy at that is drawn at (x,y) as the /bitmap/.
                It moves /pace/ pixels each step, left, then right     """
        pygame.sprite.Sprite.__init__( self )
        self.image = pygame.image.load( bitmap ).convert_alpha()
        self.rect  = self.image.get_rect()
        self.rect.center = ( x, y )         # location
        self.pace_size   = pace             # How big each step is
        self.pace_count  = 0                # distance moved
        self.direction   = -1               # Start moving left (-x)
        self.turn_after  = turn_after       # distance limit

(我已经基于 PyGame Sprite 制作了数据结构,因为它只需要 2 行代码,并且提供了很多预构建的功能。)

所以现在我们有了一个数据结构(名为 Enemy),它包含位置、大小、位图,并记住它走了多远和方向。但是它还没有实现任何类型的运动算法。所以让我们添加这个。

Sprite class 想要将此算法写入名为 update() 的函数中。此函数称为 every 帧以决定该帧的移动。这可能是 no-movement 或其他。它可以是任何东西。

在这里你可以看到我们正在统计移动到 self.pace_count 的步数,然后调整位图的 x 位置(保持在 self.rect)的长度步伐(self.pace_size)。如果敌人向左移动,则需要减去 pace-size,而向右则添加。我们可以通过将添加的数量乘以 self.direction-11 来自动执行此操作。每当敌人转身时都会设置方向值。

    def update( self ):
        """ Implement the movement algorithm """
        # Walk pace in the current direction
        self.pace_count += 1
        self.rect.x     += self.direction * self.pace_size     # Move some pixels

        # We need to turn around if walked enough paces in the same direction
        if ( self.pace_count >= self.turn_after ):
            # Turn around!
            self.direction *= -1           # reverses the pixel distance
            self.pace_count = 0            # reset the pace count

        # We also should change direction if we hit the screen edge
        if ( self.rect.x <= 0 ):
            self.direction  = 1             # turn right
            self.pace_count = 0
        elif ( self.rect.x >= WINDOW_WIDTH - self.rect.width ):
            self.direction  = -1
            self.pace_count = 0

所以当敌人走完设定的步数时,方向就会反转,步数归零。但是如果我们撞到了屏幕的一侧,我们也需要转身。当发生这种情况时,只有一种明显的转向方式,因此方向是绝对改变的,而不是倒转的。这段代码可能会变得更简单,因为它基本上每次都做几乎相同的事情。但是为了清楚地说明所涉及的步骤,我把它留得更长了。

就这样,算法实现了。看一个演示,way太快了。因此,让我们也加入实时速度。

控制移动速度的一种简单方法是在步骤之间设置延迟。首先决定敌人移动的频率(例如:每 100 毫秒),存储在 self.speed 中,然后最后一步的时间存储在 self.pace_time 中。然后当需要更新时,查看 PyGame 时钟以查看是否已经过了足够的毫秒数,然后才移动 Enemy。否则什么也不做。

def update( self ):
    """ Implement the movement algorithm """
    time_now = pygame.time.get_ticks()               # what time is it
    if ( time_now > self.pace_time + self.speed ):   # time to move again?
        self.pace_time = time_now                    # remember move time

        # Walk pace in the current direction
        self.pace_count += 1

这使运动更加稳重。我调整了 Enemy 以更频繁地移动,但步幅更小。所以现在它也没有遍历那么多 window。重要的是将速度控制为时间的函数而不是帧速率。例如,如果我刚刚将 pace 大小设置为 0.2 像素以上,肯定会减慢蘑菇的速度。但它只在 我的 电脑上准确。如果帧速率只有 21 FPS,突然又慢了 2/3 会怎么样。如果帧速率是 160 FPS 呢?它会恢复到超快,就是这样。因此,保持任何类型的速度和移动由实时毫秒控制,而不是帧速率和距离。

无论如何,这应该足以让您继续使用自己的移动算法。如果对代码有疑问,请发表评论。

参考代码:

import pygame

# Window size
WINDOW_WIDTH    = 600
WINDOW_HEIGHT   = 400
WINDOW_SURFACE  = pygame.HWSURFACE|pygame.DOUBLEBUF

DARK_BLUE = (   3,   5,  54)

class Enemy( pygame.sprite.Sprite ):
    def __init__( self, x, y, pace, bitmap, turn_after=20, speed=100 ):
        """ Create a new Enemy at that is drawn at (x,y) as the /bitmap/.
            It moves /pace/ pixels left, then right   """
        pygame.sprite.Sprite.__init__( self )    
        self.image = pygame.image.load( bitmap ).convert_alpha()
        self.rect  = self.image.get_rect()
        self.rect.center = ( x, y )         # location
        self.pace_size   = pace             # How big each step is
        self.pace_count  = 0                # distance moved
        self.direction   = -1               # Start moving left (-x)
        self.turn_after  = turn_after       # distance limit
        self.speed       = speed            # Milliseconds per pace
        self.pace_time   = 0                # time of last step

    def update( self ):
        """ Implement the movement algorithm """
        time_now = pygame.time.get_ticks()               # what time is it
        if ( time_now > self.pace_time + self.speed ):   # is it time to move again
            self.pace_time = time_now

            # Walk pace in the current direction
            self.pace_count += 1
            self.rect.x     += self.direction * self.pace_size     # Move some pixels

            # We need to turn around if walked enough paces in the same direction
            if ( self.pace_count >= self.turn_after ):
                # Turn around!
                self.direction *= -1           # reverses the pixel distance
                self.pace_count = 0            # reset the pace count

            # We also should change direction if we hit the screen edge
            if ( self.rect.x <= 0 ):
                self.direction  = 1             # turn right
                self.pace_count = 0
            elif ( self.rect.x >= WINDOW_WIDTH - self.rect.width ):
                self.direction  = -1
                self.pace_count = 0


### initialisation
pygame.init()
pygame.mixer.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), WINDOW_SURFACE )
pygame.display.set_caption("Movement Algorithm Example")

### Sprite and Sprite Group
pos_x     = WINDOW_WIDTH//2
pos_y     = WINDOW_HEIGHT//2
pace_size = 7
enemy = Enemy( pos_x, pos_y, pace_size, "mushroom.png" )

all_sprites_group = pygame.sprite.Group()
all_sprites_group.add( enemy )


### Main Loop
clock = pygame.time.Clock()
done = False
while not done:

    # Handle user-input
    for event in pygame.event.get():
        if ( event.type == pygame.QUIT ):
            done = True
        elif ( event.type == pygame.MOUSEBUTTONUP ):
            # On mouse-click
            pass
        elif ( event.type == pygame.KEYUP ):
            pass

    # Movement keys
    #keys = pygame.key.get_pressed()
    #if ( keys[pygame.K_UP] ):
    #    print("up")

    # Update the window, but not more than 60fps
    all_sprites_group.update()
    window.fill( DARK_BLUE )
    all_sprites_group.draw( window )
    pygame.display.flip()

    # Clamp FPS
    clock.tick_busy_loop(60)

pygame.quit()