Reputation: 65
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.
Upvotes: 1
Views: 1678
Reputation: 14906
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.
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.
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()
Upvotes: 1