1

My enemy sprites are generally able to follow the player but as I continue to play, the enemy sprites sometimes all start going toward the wall and overlapping with each other. Even if I make the player sprite move in the other direction, the enemy sprites will only move along the wall instead of towards my player.

In addition, I feel like my enemy sprites are of no threat at all. Half of the time, they don't really move towards the player. If I ever collide into the enemy, it's because I was moving towards them because I wasn't paying attention.

I want to fix the problem of my enemy sprites clustering towards the wall and make my enemies more threatening. I also find my game too lacklustre so I appreciate any suggestions on what I can add to make the game more interesting.

This is my game code:

Code is removed for now. Will re-upload in 1 to 2 months.
helpless
  • 99
  • 1
  • 10

1 Answers1

1

To get a "smooth" you've to do the computation of the enemy position and movement with floating point values rather than integral values.
Note, the position and size of a pygame.Rect object is stored integral. Every time when a movement is added to the position, then the fractional part is lost. This causes that small movements in a direction are not performed at all. If a value less than 0.5 (round) is added to a integral coordinate then the position will never change, even beyond frames. If the coordinate is a floating point value, then the integral part of the position will change after some frames.

Add a position attribute (.pos) the the class Enemy:

class Enemy(pygame.sprite.Sprite):

    def __init__(self, x, y, speed, walls):

        # [...]
        self.pos = [x, y]
        self.rect.y = y
        self.rect.x = x

Do the change of the position on the attribute .pos and update .rect by .pos. e.g.:

self.pos[0] = self.pos[0] + self.move_x
self.rect.x = round(self.pos[0])

If a collision has been detected, then the attribute .pos has to be corrected by the integral position in of the attribute .rect. e.g.:

if block_collide:
    self.pos[0] = self.rect.x

Further you've to create the object attributes attribute self.move_x and self.move_y rather than the class attributes Enemy.move_x and Enemy.move_y.
Note, a class exists attribute once for each type (calss), but an object attribute exists ist once for each object ("enemy"). Each enemy has to have its own movement vector, else the movement what was computed by the last enemy would be applied to all enemies. This causes that enemies do unexpected movement. See Class Definition Syntax.

Of course you've to move the enemies in a loop

for e in enemy_list:
   e.move(player)

Complete code of the class Enemy:

class Enemy(pygame.sprite.Sprite):

    def __init__(self, x, y, speed, walls):
        pygame.sprite.Sprite.__init__(self)

        self.image = pygame.Surface([20, 20])
        self.image.fill(red)

        self.rect = self.image.get_rect()
        self.pos = [x, y]
        self.rect.y = y
        self.rect.x = x
        self.move_x = 0
        self.move_y = 0

        self.speed = speed # speed of the enemy
        self.walls = walls # walls for the collision test

    def move(self, player):
        dx, dy = player.rect.x - self.pos[0], player.rect.y - self.pos[1]
        dist = math.hypot(dx, dy)
        dx, dy = dx / dist, dy / dist
        self.move_x = dx * min(dist, self.speed)
        self.move_y = dy * min(dist, self.speed)

    def update(self):

        self.pos[0] = self.pos[0] + self.move_x
        self.rect.x = round(self.pos[0])
        block_collide = pygame.sprite.spritecollide(self, self.walls, False)
        for block in block_collide:
            if self.move_x > 0:
                self.rect.right = block.rect.left
            else:
                self.rect.left = block.rect.right
        if block_collide:
            self.pos[0] = self.rect.x

        self.pos[1] = self.pos[1] + self.move_y
        self.rect.y = round(self.pos[1])
        block_collide = pygame.sprite.spritecollide(self, self.walls, False)
        for block in block_collide:
            if self.move_y > 0:
                self.rect.bottom = block.rect.top
            else:
                self.rect.top = block.rect.bottom
        if block_collide:
            self.pos[1] = self.rect.y
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
  • The movement is smoother (thank you) but my enemy sprites still go towards the walls when my player sprite is not there. It will move towards the left or right wall, after which it moves downwards until its out of screen. Do you know why that happens? @Rabbid76 – helpless Jun 13 '19 at 01:32
  • @helpless This can only happen if the player is out of the screen. Ar the coordinates of the player always in the window? `0 < player.rect.x < window_width` and `0 < player.rect.y < window_height`? Note, there was a issue in the answer in `move()`: `self.move_x` respectively `self.move_y` rather than `Enemy.move_x` and `Enemy.move_y` – Rabbid76 Jun 13 '19 at 05:23
  • No. My player is always in the screen. I even have walls to prevent player sprite and enemy sprite from moving out of screen. I don't quite understand what you meant by an issue in `move(): self.move_x`. How should I change it? @Rabbid76 – helpless Jun 13 '19 at 10:30
  • @helpless See the last changes to the answer. It has to be `self.move_x` respectively `self.move_y` rather than `Enemy.move_x` and `Enemy.move_y`. Init in the constructor of `Enemy`: `self.move_x = 0` `self.move_y = 0` and move the enemies in a loop: `for e in enemy_list: e.move(player)`. I added all this code to the answer. – Rabbid76 Jun 13 '19 at 13:46