1
votes

Working on my first project and I'm trying to get enemy movement sorted, the code below is my current implementation. The enemy using the distance between the player position (target.pos.x) and their pos.x. Want the enemy to move left 20 paces then change direction and move right 20 paces, rinse and repeat.

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 my enemies to move to the right a certain amount then change velocity and go the opposite way not remain idle.

1
It's a bit hard to determine what code changes are necessary, when there is not really a clear description of what's intended. The phrase "move around independently" means a lot of things. If you're just beginning at this, think up a simple, exact, achievable enemy algorithm. Maybe something like: "Always move 1 step towards the player if within 200 pixels, otherwise move 1 step randomly up/down/left/right".Kingsley
@Kingsley I want the enemy to move left 20 paces then change direction and move right 20 paces, rinse and repeat. Much like goombas in super mario or enemies in early 2D platformersMaster Chef

1 Answers

1
votes

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

Ok, so how do we achieve that? First some definitions:

What is a "pace"? Let's start with 5 pixels. Left is -x; Right is +x.

There's a few extra things to take care of too. What about when the object can't move in the desired direction? It can turn around.

So we need to keep a bunch of stats about this Enemy: Location, Step Count, Direction of Travel. As soon as you have a few datum points, think: Data Structure. Now I'm going to put all this into a Python class, but it could also go into a simple list. But these get unwieldy with more than a few points of data.

# 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 ]

Much better as a 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

(I've made the data structure based on a PyGame Sprite because it costs only 2 lines of code, and provides lots of pre-built functionality.)

So now we have a data structure (named Enemy) that contains a location, size, bitmap, and remembers how far it's walked, and in which direction. However it doesn't yet implement any sort of movement algorithm. So let's add this.

The Sprite class wants this algorithm to be written into a function named update(). This function is called every frame to decide on the movement for that frame. This might be no-movement, or something else. It can be anything.

Here you can see we're tallying the number of paces moved into self.pace_count, and then adjusting the bitmap's x position (held in self.rect) by the length of a pace (self.pace_size). If the enemy is moving left, the pace-size needs to be subtracted, and for right, added. We can do this automatically by multiplying the amount we add by the self.direction, either -1 or 1. The direction value is set whenever the Enemy turns around.

    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

So when the Enemy walks the set number of paces, the direction is reversed and the paces-tally zeroed. But we also need to turn around if we hit the side of the screen. When this happens, there is only a single obvious way to turn, so the direction is changed absolutely, rather than being reversed. This code could probably be made simpler, since it basically does almost the same things each time. But I've left it a bit longer to clearly illustrate the steps involved.

And that it's, the algorithm is implemented. Looking at a demo, it's way too fast. So let's also incorporate a real-time speed.

too fast mushroom

An easy way to control movement speed is to put in a delay between steps. First decide how often the Enemy will move (e.g.: every 100 milliseconds), stored in self.speed and then the time the last step was taken in self.pace_time. Then when it comes time to update, look at the PyGame clock to see if enough milliseconds have elapsed, and only then move the Enemy. Otherwise do nothing.

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

This gives a much more sedate movement. I tweaked the Enemy to move more often, but in smaller steps. So now it also does not traverse as much of the window. It's important to control speed as a function of time rather than frame-rate. For example, if I'd just made the pace size 0.2 pixels above, sure that would slow the mushroom down to some speed. But it's only accurate on my computer. What if the frame-rate was only 21 FPS, suddenly it moving 2/3 slower again. And what if the frame rate was 160 FPS? It would be back to super-fast, that's what. So keep any sort of speed and movement controlled by real-time milliseconds, not frame-rate & distance.

slower mushroom

Anyway, that should be enough to get you going on your own movement algorithms. Please comment if there's questions about the code.

Reference Code:

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()