3

I created a simple game in pygame that is sort of a simple bullet-hell style game, with the goal of creating a deep reinforcement learning agent to learn the game. I got the game to work in pygame alone using keyboard controls, and I am now working on making it work with OpenAI gym.

Everything seems to be working out OK except for the main player sprite that I use to visualize it's location. In the step method I update the Player object rect based on the action, and then higher level I redraw the geometry of the level and flip it. What is interesting is that the bullet components seem to redraw and update perfectly fine, however the player sprite does not. What's more interesting is that if I analyze the player's rect using debug lines I created, it does seem to actually be moving, just the sprite itself does not move from the starting position. It may be easier to understand with a picture:

screenshot

https://i.stack.imgur.com/Bbdty.png

Here the blue lines converge to where the player's rect is. You can verify that is what is done with the code below. On the left you can see the centerx and centery properties to ensure that it is indeed updating on every step call. However, you can see the "ship" associated with this player is not anywhere near the convergence point. I'm very confused as to how the bullets in the screenshot can update and move perfectly fine, however the player object does not. It appears it gets repeatedly drawn in the same location.

My current working theory is it is some kind of mismatch between self.player and the Sprite object added to self.player_list, but I'm not sure how to rectify that, especially since I need self.player in other functions.

Any help would be appreciated.

(There is extraneous gym code I omitted for brevity)

MAIN:

def main():
    env = BulletHell()
    done = False
    env.reset()

    while not done:
        action = env.action_space.sample()
        # apply the action
        obs, reward, done, info = env.step(action)
        env.render()

    env.close()

BULLETHELL CLASS:

class BulletHell(gym.Env):
    metadata = {
        "render_modes": ["human", "rgb_array", "single_rgb_array"],
        "render_fps": 30
    }

    def __init__(self):
        pygame.init()
        self.stepcnt = 0
        self.screen = pygame.display.set_mode(constants.SIZE)
        self.player = Player(constants.width, constants.height)  # spawn player
        self.player.rect.x = constants.width // 2  # go to x
        self.player.rect.y = constants.height // 2  # go to y
        self.player_list = pygame.sprite.GroupSingle()
        self.player_list.add(self.player)

        self.timer = pygame.time.Clock()
        self.SPAWN_BULLET = pygame.USEREVENT + 1
        pygame.time.set_timer(self.SPAWN_BULLET, constants.BULLET_SPAWN_RATE)
        self.closest = None

        # 5 discrete actions; do nothing, up, down, left, right
        self.action_space = Discrete(5)
        #  Observation space is the closest bullet count (each with a distance) and 2 numbers representing the x and y
        #  of the player's current position
        self.observation_space = Box(low=0,
                                     high=constants.width,
                                     shape=(constants.CLOSEST_BULLET_COUNT + 2,))

        self.bullet_list = pygame.sprite.Group()
        for _ in range(5):
            bullet = Bullet(constants.width, constants.height)
            self.bullet_list.add(bullet)
            bullet.set_starting_loc_and_vel()

        self.steps = 2

    def observe(self):
        ...

    def step(self, action) -> Tuple[ObsType, float, bool, dict]:
        done = False
        reward = 0.1
        self.stepcnt += 1
        # Do nothing
        if action == 0:
            self.player.control(0, 0)
        # Go up
        if action == 1:
            self.player.control(0, self.steps)
        # Go down
        if action == 2:
            self.player.control(0, -self.steps)
        # Go left
        if action == 3:
            self.player.control(-self.steps, 0)
        # Go right
        if action == 4:
            self.player.control(self.steps, 0)

        for event in pygame.event.get():
            if event.type == self.SPAWN_BULLET:
                bullet = Bullet(constants.width, constants.height)
                self.bullet_list.add(bullet)
                bullet.set_starting_loc_and_vel()

        self.player.update()
        self.bullet_list.update()
        self.timer.tick(self.metadata['render_fps'])

        if self.stepcnt % 2 == 0:
            self.player.zero()

        blocks_hit_list = pygame.sprite.spritecollide(self.player, self.bullet_list, False)
        if len(blocks_hit_list) != 0:
            done = True
            reward = -5

        # Debug code, print coordinates of player rect
        print(self.observe()[-2:])
        return self.observe(), reward, done, {}

    def redraw(self):
        self.screen.fill(constants.BLACK)
        self.bullet_list.draw(self.screen)
        self.player_list.draw(self.screen)

        for dis, bullet in self.closest:
            pygame.draw.line(self.screen, 255, Vector2(self.player.rect.centerx, self.player.rect.centery),
                             Vector2(bullet.rect.centerx, bullet.rect.centery), width=1)
        pygame.display.flip()

    def reset(self, *, seed: Optional[int] = None, return_info: bool = False, options: Optional[dict] = None,):
        ...

    def render(self, mode="human"):
        self.redraw()

    ...

EDIT: upon request I added the Player class code:

class Player(pygame.sprite.Sprite):
    def __init__(self, bounds_x, bounds_y):
        pygame.sprite.Sprite.__init__(self)
        self.movex = 0
        self.movey = 0
        self.bounds_x = bounds_x
        self.bounds_y = bounds_y

        img = pygame.image.load('Ship_1.png').convert_alpha()
        self.image = img
        self.rect = self.image.get_rect()

    def control(self, x, y):
        self.movex += x
        self.movey += y

    def update(self):
        self.rect.x = self.rect.x + self.movex
        self.rect.y = self.rect.y + self.movey
        if self.rect.x >= self.bounds_x - self.image.get_width():
            self.rect.x = self.bounds_x - self.image.get_width()
        elif self.rect.x <= 0:
            self.rect.x = 0
        if self.rect.y >= self.bounds_y - self.image.get_height():
            self.rect.y = self.bounds_y - self.image.get_height()
        elif self.rect.y <= 0:
            self.rect.y = 0

    def zero(self):
        self.movex = 0
        self.movey = 0

    def get_closest_bullets(self, bullet_list, n=10):
        ...

    def dis(self, other):
        ...
jkcarney
  • 31
  • 2
  • 3
    This is documented as an issue with the `Player` class, but the code does not include this part. So I can only *guess* that `player.control()` isn't properly updating the Sprite rect. – Kingsley Jun 27 '22 at 05:17
  • @Kingsley I modified the post to include the Player class code. – jkcarney Jun 27 '22 at 23:01
  • I have not read your code so I apologise in advance if this doesn't apply to you but if you are rotating your player sprite, pygame always anchors your sprite to the top left of its bounding box assuming all pixels are used – Jacob Jun 30 '22 at 15:19

0 Answers0