[Pygame] moving enemy towards player

[Pygame] moving enemy towards player

Postby metulburr » Thu May 09, 2013 9:35 pm

I am very noob when it comes to putting this with programming. The math i havent used since high school, and when i did learn it, i thought i would never need it, and low and behold, here is somewhere that i would use it daily. And now 8 years later after not using it, i am like, "hmm i think i remember that math"

Now i wish i listened in school more, lol.

I was attempting to get the enemy player to move towards the player at hte speed of 2 pixel per frame, while the player moves at 4 pixels per frame.

This was my method of getting hte new postition
Code: Select all
    def pos_towards_player(self, player_rect):
        '''get new coords towards player'''
        c = math.sqrt((player_rect.x - self.rect[0]) ** 2 + (player_rect.y - self.rect[1]) ** 2)
        x = (player_rect.x - self.rect[0]) / c
        y = (player_rect.y - self.rect[1]) / c
        return (x,y)

and
in the update()
Code: Select all
        new_pos = self.enemy.pos_towards_player(self.player.rect)
        self.enemy.rect = (self.player.rect.x + new_pos[0] * self.enemy.speed, self.player.rect.y + new_pos[1] * self.enemy.speed)


full code:
Code: Select all
import pygame
import sys
import math

def image_from_url(url):
    try:
        from urllib2 import urlopen
        from cStringIO import StringIO as inout
    except ImportError:
        from urllib.request import urlopen
        from io import BytesIO as inout
    myurl = urlopen(url)
    return inout(myurl.read())

class Projectile:
    limit = 3
    def __init__(self, loc):
        self.surf = pygame.Surface((5,10)).convert()
        self.surf.set_colorkey((255,0,255))
        self.surf.fill((255,255,255))
        self.rect = self.surf.get_rect()
        self.rect.center = loc
        self.mask = pygame.mask.from_surface(self.surf)
        self.speed = 8

class Enemy:
    def __init__(self):
        link = 'http://i1297.photobucket.com/albums/ag23/metulburr/spaceship_enemy3_zpscfeb13bf.png'
        self.orig_image = pygame.image.load(image_from_url(link)).convert()
        self.orig_image = pygame.transform.scale(self.orig_image, (40,80))
        self.orig_image.set_colorkey((255,0,255))
        self.image = pygame.transform.rotate(self.orig_image, 180)
        self.mask = pygame.mask.from_surface(self.image)
        self.rect = self.image.get_rect()
        self.rect.topleft = (100,0)
        self.bullets = []
        self.speed = 2
       
    def pos_towards_player(self, player_rect):
        '''get new coords towards player'''
        c = math.sqrt((player_rect.x - self.rect[0]) ** 2 + (player_rect.y - self.rect[1]) ** 2)
        x = (player_rect.x - self.rect[0]) / c
        y = (player_rect.y - self.rect[1]) / c
        return (x,y)
        #(dx, dy) = ((xp - xz)/math.sqrt((xp - xz) ** 2 + (yp - yz) ** 2),
        #            (yp - yz)/math.sqrt((xp - xz) ** 2 + (yp - yz) ** 2))


class Player:
    def __init__(self):
        self.speed = 4

        link = 'http://i1297.photobucket.com/albums/ag23/metulburr/spaceship2_zps095c332a.png'
        self.orig_image = pygame.image.load(image_from_url(link)).convert()
        self.orig_image = pygame.transform.scale(self.orig_image, (40,80))
        self.orig_image.set_colorkey((255,0,255))

        self.image = pygame.transform.rotate(self.orig_image, 180)
        self.mask = pygame.mask.from_surface(self.image)
        self.rect = self.image.get_rect()

        self.bullets = []

    def move(self, x, y):
        self.rect[0] += x * self.speed
        self.rect[1] += y * self.speed


class Control:
    def __init__(self):
        self.screensize = (800,600)
        self.screen = pygame.display.set_mode(self.screensize)
        self.screenrect = self.screen.get_rect()
        self.clock = pygame.time.Clock()
        self.player = Player()
        self.enemy = Enemy()


        self.mainloop()

    def mainloop(self):
        run = True
        while run:
            self.screen.fill((0,0,0))

            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    run = False
                elif event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_SPACE:
                        if len(self.player.bullets) <= Projectile.limit:
                            self.player.bullets.append(Projectile(self.player.rect.center))

            self.update()
            pygame.display.flip()
            self.clock.tick(60)

    def update(self):
        self.keys = pygame.key.get_pressed()

        if self.player.bullets:
            for obj in self.player.bullets:
                self.screen.blit(obj.surf, obj.rect)
                obj.rect[1] -= obj.speed

                #did obj move off screen
                if obj.rect[1] < 0:
                    self.player.bullets.remove(obj)
                #did obj hit enemy rect
                if obj.rect.colliderect(self.enemy.rect):
                    offset_x =  self.enemy.rect.x - obj.rect.x
                    offset_y =  self.enemy.rect.y - obj.rect.y
                    #did object hit enemy mask
                    if obj.mask.overlap(self.enemy.mask, (offset_x, offset_y)):
                        self.player.bullets.remove(obj)
                        self.enemy.die = True
                       
        new_pos = self.enemy.pos_towards_player(self.player.rect)
        self.enemy.rect = (self.player.rect.x + new_pos[0] * self.enemy.speed, self.player.rect.y + new_pos[1] * self.enemy.speed)

        if self.keys[pygame.K_UP]:
            self.player.move(0,-1)
        if self.keys[pygame.K_DOWN]:
            self.player.move(0,1)
        if self.keys[pygame.K_RIGHT]:
            self.player.move(1,0)
        if self.keys[pygame.K_LEFT]:
            self.player.move(-1,0)

        self.player.rect.clamp_ip(self.screenrect) #make sure rect does not go outside of screen
        self.screen.blit(self.enemy.image, self.enemy.rect)
        self.screen.blit(self.player.image, self.player.rect)

app = Control()
pygame.quit();sys.exit()


instead, the enemy follows the player underneath, which will eventually end up in both dying, but for now just wanted the enemy to slowly follow the player around. I also sometimes get an random ZeroDivisionError upon
Code: Select all
x = (player_rect.x - self.rect[0]) / c
. Sometimes i get it and sometimes i dont, not sure why c would sometimes result in 0

and also by adding this method in the Control.update() i somehow messed up the player fire method, when enabled.

EDIT:OK disregard the fire method error mess up as i was reassigning the enemy.rect instead of the coords. so fixed that:
Code: Select all
        new_pos = self.enemy.pos_towards_player(self.player.rect)
        self.enemy.rect.x, self.enemy.rect.y = (self.player.rect.x + new_pos[0] * self.enemy.speed, self.player.rect.y + new_pos[1] * self.enemy.speed)
New Users, Read This
OS Ubuntu 14.04, Arch Linux, Gentoo, Windows 7/8
https://github.com/metulburr
steam
User avatar
metulburr
 
Posts: 1469
Joined: Thu Feb 07, 2013 4:47 pm
Location: Elmira, NY

Re: [Pygame] moving enemy towards player

Postby metulburr » Thu May 09, 2013 10:22 pm

Ok i figured it out, just a typo. put player.x and y instead of enemy.x and y,

and now the zerodivvisionerror makes sense if the enemy is exactly underneath the player as it woiuld result in 0, which i didnt htink of because i was later not even going to let them overlap at all. So i just put a try, except for that just for now, but will probably remove it after adding hte code to remove them both upon dying as they never wil get to overlap like that.

This is the final result of fixing:
Code: Select all
import pygame
import sys
import math

def image_from_url(url):
    try:
        from urllib2 import urlopen
        from cStringIO import StringIO as inout
    except ImportError:
        from urllib.request import urlopen
        from io import BytesIO as inout
    myurl = urlopen(url)
    return inout(myurl.read())

class Projectile:
    limit = 3
    def __init__(self, loc):
        self.surf = pygame.Surface((5,10)).convert()
        self.surf.set_colorkey((255,0,255))
        self.surf.fill((255,255,255))
        self.rect = self.surf.get_rect()
        self.rect.center = loc
        self.mask = pygame.mask.from_surface(self.surf)
        self.speed = 8

class Enemy:
    def __init__(self):
        link = 'http://i1297.photobucket.com/albums/ag23/metulburr/spaceship_enemy3_zpscfeb13bf.png'
        self.orig_image = pygame.image.load(image_from_url(link)).convert()
        self.orig_image = pygame.transform.scale(self.orig_image, (40,80))
        self.orig_image.set_colorkey((255,0,255))
        self.image = pygame.transform.rotate(self.orig_image, 180)
        self.mask = pygame.mask.from_surface(self.image)
        self.rect = self.image.get_rect()
        self.rect.topleft = (100,0)
        self.bullets = []
        self.speed = 2
       
    def pos_towards_player(self, player_rect):
        '''get new coords towards player'''
        c = math.sqrt((player_rect.x - self.rect[0]) ** 2 + (player_rect.y - self.rect[1]) ** 2)
        try:
            x = (player_rect.x - self.rect[0]) / c
            y = (player_rect.y - self.rect[1]) / c
        except ZeroDivisionError:
            return (self.rect.x, self.rect.y)
        return (x,y)
        #(dx, dy) = ((xp - xz)/math.sqrt((xp - xz) ** 2 + (yp - yz) ** 2),
        #            (yp - yz)/math.sqrt((xp - xz) ** 2 + (yp - yz) ** 2))


class Player:
    def __init__(self):
        self.speed = 4

        link = 'http://i1297.photobucket.com/albums/ag23/metulburr/spaceship2_zps095c332a.png'
        self.orig_image = pygame.image.load(image_from_url(link)).convert()
        self.orig_image = pygame.transform.scale(self.orig_image, (40,80))
        self.orig_image.set_colorkey((255,0,255))

        self.image = pygame.transform.rotate(self.orig_image, 180)
        self.mask = pygame.mask.from_surface(self.image)
        self.rect = self.image.get_rect()

        self.bullets = []

    def move(self, x, y):
        self.rect[0] += x * self.speed
        self.rect[1] += y * self.speed


class Control:
    def __init__(self):
        self.screensize = (800,600)
        self.screen = pygame.display.set_mode(self.screensize)
        self.screenrect = self.screen.get_rect()
        self.clock = pygame.time.Clock()
        self.player = Player()
        self.enemy = Enemy()


        self.mainloop()

    def mainloop(self):
        run = True
        while run:
            self.screen.fill((0,0,0))

            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    run = False
                elif event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_SPACE:
                        if len(self.player.bullets) <= Projectile.limit:
                            self.player.bullets.append(Projectile(self.player.rect.center))

            self.update()
            pygame.display.flip()
            self.clock.tick(60)

    def update(self):
        self.keys = pygame.key.get_pressed()

        if self.player.bullets:
            for obj in self.player.bullets:
                self.screen.blit(obj.surf, obj.rect)
                obj.rect[1] -= obj.speed

                #did obj move off screen
                if obj.rect[1] < 0:
                    self.player.bullets.remove(obj)
                #did obj hit enemy rect
                if obj.rect.colliderect(self.enemy.rect):
                    offset_x =  self.enemy.rect.x - obj.rect.x
                    offset_y =  self.enemy.rect.y - obj.rect.y
                    #did object hit enemy mask
                    if obj.mask.overlap(self.enemy.mask, (offset_x, offset_y)):
                        self.player.bullets.remove(obj)
                        self.enemy.die = True
                       
        new_pos = self.enemy.pos_towards_player(self.player.rect)
        self.enemy.rect.x, self.enemy.rect.y = (self.enemy.rect.x + new_pos[0] * self.enemy.speed, self.enemy.rect.y + new_pos[1] * self.enemy.speed)
        if self.keys[pygame.K_UP]:
            self.player.move(0,-1)
        if self.keys[pygame.K_DOWN]:
            self.player.move(0,1)
        if self.keys[pygame.K_RIGHT]:
            self.player.move(1,0)
        if self.keys[pygame.K_LEFT]:
            self.player.move(-1,0)

        self.player.rect.clamp_ip(self.screenrect) #make sure rect does not go outside of screen
        self.screen.blit(self.enemy.image, self.enemy.rect)
        self.screen.blit(self.player.image, self.player.rect)

app = Control()
pygame.quit();sys.exit()


EDIT:
OK now after playing with this for awhile, i see the enemy jumps around after overlapping the player at times. Of course i wouldnt have to worry about this if they were to not exist if in the event they were to overlap. SO maybe i should even worry about it.
New Users, Read This
OS Ubuntu 14.04, Arch Linux, Gentoo, Windows 7/8
https://github.com/metulburr
steam
User avatar
metulburr
 
Posts: 1469
Joined: Thu Feb 07, 2013 4:47 pm
Location: Elmira, NY

Re: [Pygame] moving enemy towards player

Postby metulburr » Thu May 09, 2013 11:51 pm

with the code:
Code: Select all
import pygame
import sys
import math
import random

def image_from_url(url):
    try:
        from urllib2 import urlopen
        from cStringIO import StringIO as inout
    except ImportError:
        from urllib.request import urlopen
        from io import BytesIO as inout
    myurl = urlopen(url)
    return inout(myurl.read())

class Projectile:
    limit = 3
    def __init__(self, loc):
        self.surf = pygame.Surface((5,10)).convert()
        self.surf.set_colorkey((255,0,255))
        self.surf.fill((255,255,255))
        self.rect = self.surf.get_rect()
        self.rect.center = loc
        self.mask = pygame.mask.from_surface(self.surf)
        self.speed = 8

class Enemy:
    count = 3
    def __init__(self, start_loc, orig_image):
        self.orig_image = orig_image
        #link = 'http://i1297.photobucket.com/albums/ag23/metulburr/spaceship_enemy3_zpscfeb13bf.png'
        #self.orig_image = pygame.image.load(image_from_url(link)).convert()
        self.orig_image = pygame.transform.scale(self.orig_image, (40,80))
        self.orig_image.set_colorkey((255,0,255))
        self.image = pygame.transform.rotate(self.orig_image, 180)
        self.mask = pygame.mask.from_surface(self.image)
        self.rect = self.image.get_rect()
        self.rect.center = start_loc
        self.bullets = []
        self.speed = 2
        self.is_hit = False
       
    def pos_towards_player(self, player_rect):
        '''get new coords towards player'''
        c = math.sqrt((player_rect.x - self.rect[0]) ** 2 + (player_rect.y - self.rect[1]) ** 2)
        try:
            x = (player_rect.x - self.rect[0]) / c
            y = (player_rect.y - self.rect[1]) / c
        except ZeroDivisionError:
            return (self.rect.x, self.rect.y)
        return (x,y)
       
    def destroy(self):
        ...
       
    def update(self, player_rect):
        if not self.is_hit:
            #move enemy towards player
            new_pos = self.pos_towards_player(player_rect)
            self.rect.x, self.rect.y = (self.rect.x + new_pos[0] * self.speed, self.rect.y + new_pos[1] * self.speed)
           
           


class Player:
    def __init__(self, start_loc):
        self.speed = 4

        link = 'http://i1297.photobucket.com/albums/ag23/metulburr/spaceship2_zps095c332a.png'
        self.orig_image = pygame.image.load(image_from_url(link)).convert()
        self.orig_image = pygame.transform.scale(self.orig_image, (40,80))
        self.orig_image.set_colorkey((255,0,255))

        self.image = pygame.transform.rotate(self.orig_image, 180)
        self.mask = pygame.mask.from_surface(self.image)
        self.rect = self.image.get_rect()
        self.rect.center = start_loc

        self.bullets = []

    def move(self, x, y):
        self.rect[0] += x * self.speed
        self.rect[1] += y * self.speed


class Control:
    def __init__(self):
        self.screensize = (800,600)
        self.screen = pygame.display.set_mode(self.screensize)
        self.screenrect = self.screen.get_rect()
        self.clock = pygame.time.Clock()
        self.player = Player((0,600))
        #self.enemy = Enemy((800,0))
        link = 'http://i1297.photobucket.com/albums/ag23/metulburr/spaceship_enemy3_zpscfeb13bf.png'
        self.enemy_image = pygame.image.load(image_from_url(link)).convert()
       
        self.enemies = []


        self.mainloop()

    def mainloop(self):
        run = True
        while run:
            self.screen.fill((0,0,0))

            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    run = False
                elif event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_SPACE:
                        if len(self.player.bullets) <= Projectile.limit:
                            self.player.bullets.append(Projectile(self.player.rect.center))

            self.update()
            pygame.display.flip()
            self.clock.tick(60)

    def update(self):
        self.keys = pygame.key.get_pressed()
       
        #for obj in self.enemies:
        #    obj.update(self.player.rect)
           
       
        #make new enemies
        #print(len(self.enemies))
       
        if len(self.enemies) <= Enemy.count:
            self.enemies.append(Enemy((random.randint(1,800),-100), self.enemy_image))
           

        if self.player.bullets:
            for obj in self.player.bullets:
                self.screen.blit(obj.surf, obj.rect)
                obj.rect[1] -= obj.speed

                #did obj move off screen
                if obj.rect[1] < 0:
                    self.player.bullets.remove(obj)
                #did obj hit enemy rect
                for enem in self.enemies:
                    if obj.rect.colliderect(enem.rect):
                        offset_x =  enem.rect.x - obj.rect.x
                        offset_y =  enem.rect.y - obj.rect.y
                        #did object hit enemy mask
                        if obj.mask.overlap(enem.mask, (offset_x, offset_y)):
                            self.player.bullets.remove(obj)
                            self.enemies.remove(enem)
                            enem.is_hit = True
   
                       

        if self.keys[pygame.K_UP]:
            self.player.move(0,-1)
        if self.keys[pygame.K_DOWN]:
            self.player.move(0,1)
        if self.keys[pygame.K_RIGHT]:
            self.player.move(1,0)
        if self.keys[pygame.K_LEFT]:
            self.player.move(-1,0)

        self.player.rect.clamp_ip(self.screenrect) #make sure rect does not go outside of screen
        for obj in self.enemies:
            obj.update(self.player.rect)
            if not obj.is_hit:
                self.screen.blit(obj.image, obj.rect)
        #if not self.enemy.is_hit:
         #   self.screen.blit(self.enemy.image, self.enemy.rect)
        self.screen.blit(self.player.image, self.player.rect)

app = Control()
pygame.quit();sys.exit()


sometime i get an ValueERror for the obj not being in the list when i attempt to remove it.
Code: Select all
Traceback (most recent call last):
  File "forum3.py", line 172, in <module>
    app = Control()
  File "forum3.py", line 100, in __init__
    self.mainloop()
  File "forum3.py", line 115, in mainloop
    self.update()
  File "forum3.py", line 148, in update
    self.player.bullets.remove(obj)
ValueError: list.remove(x): x not in list


to recreate this, if you swirl around the enemies and let them overlap eachother, then move down and shoot once, this error is caused, but that is the only way that i can recreate this error
New Users, Read This
OS Ubuntu 14.04, Arch Linux, Gentoo, Windows 7/8
https://github.com/metulburr
steam
User avatar
metulburr
 
Posts: 1469
Joined: Thu Feb 07, 2013 4:47 pm
Location: Elmira, NY

Re: [Pygame] moving enemy towards player

Postby Mekire » Thu May 09, 2013 11:55 pm

Try iterating through a copy of your enemy list instead of the actual list here:
Code: Select all
for enem in self.enemies:
    if obj.rect.colliderect(enem.rect):
        offset_x =  enem.rect.x - obj.rect.x
        offset_y =  enem.rect.y - obj.rect.y
        #did object hit enemy mask
        if obj.mask.overlap(enem.mask, (offset_x, offset_y)):
            self.player.bullets.remove(obj)
            self.enemies.remove(enem)
            enem.is_hit = True
Changing the size of a list while iterating over it is never a good idea. Not sure if this is the problem though.

-Mek

Edit: Ok that was one problem... but the main problem here is that your bullets are hitting multiple enemies at once. Therefore you try to remove the same bullet multiple times. And thus your object not in list.

This fixes it for the moment:
Code: Select all
if self.player.bullets:
    for obj in self.player.bullets[:]:
        self.screen.blit(obj.surf, obj.rect)
        obj.rect[1] -= obj.speed

        #did obj move off screen
        if obj.rect[1] < 0:
            self.player.bullets.remove(obj)
            break

        #did obj hit enemy rect
        for enem in self.enemies[:]:
            if obj.rect.colliderect(enem.rect):
                offset_x =  enem.rect.x - obj.rect.x
                offset_y =  enem.rect.y - obj.rect.y
                #did object hit enemy mask
                if obj.mask.overlap(enem.mask, (offset_x, offset_y)):
                    self.player.bullets.remove(obj)
                    self.enemies.remove(enem)
                    enem.is_hit = True
                    break
User avatar
Mekire
 
Posts: 988
Joined: Thu Feb 07, 2013 11:33 pm
Location: Amakusa, Japan

Re: [Pygame] moving enemy towards player

Postby metulburr » Fri May 10, 2013 12:12 am

ah ok, thanks mekire. I am thinking so much about pygame and other things in relation that i forget about python basics.
New Users, Read This
OS Ubuntu 14.04, Arch Linux, Gentoo, Windows 7/8
https://github.com/metulburr
steam
User avatar
metulburr
 
Posts: 1469
Joined: Thu Feb 07, 2013 4:47 pm
Location: Elmira, NY


Return to Game Development

Who is online

Users browsing this forum: Baidu [Spider] and 2 guests