1
\$\begingroup\$

I've been playing around with python for a while, but I only did basic functional programming. Lately, I've been teaching myself about classes. I've been using pygame (instead of tk, because let's face it, it's much more fun)

So I have a simple script, with a player class, a wall class, and a game class. When the player hits the wall, it stops in it's place like it should, but only if it's moving in one direction, that is - x>0 and only x. if y is >0 too, I'm getting really odd behaviors as the player just flies out of the screen :( I shall post the script below, I realize it's not as orderly as an object-oriented game should be, please take into consideration that I am new to OO.

import pygame
pygame.init()

BLACK = 0, 0, 0
RED = 255,0,0
GREEN = 0,255,0
BLUE = 0,0,255
WHITE = 255,255,255



class Player(pygame.sprite.Sprite):

    def __init__(self, color, width, height, x, y, speed):
        super(pygame.sprite.Sprite, self).__init__()

        self.move_x = 0
        self.move_y = 0
        self.speed = speed
        self.width = width
        self.height = height
        self.image = pygame.Surface([self.width, self.height])
        self.image.fill(color)
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y

    def move(self):
        self.rect.x += self.move_x
        self.rect.y += self.move_y

class Wall(pygame.sprite.Sprite):

    def __init__(self, width, height, x, y):
        super(pygame.sprite.Sprite, self).__init__()

        self.width = width
        self.height = height
        self.image = pygame.Surface([self.width, self.height])
        self.image.fill(RED)
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y

    def collision(self, player):

        if self.rect.colliderect(player.rect):

            if player.move_x > 0:
                # moving right
                player.rect.right = self.rect.left

            elif player.move_x < 0:
                #moving left
                player.rect.left = self.rect.right

            elif player.move_y < 0:
                # moving up
                player.rect.top = self.rect.bottom

            elif player.move_y >0:
                #moving down
                player.rect.bottom = self.rect.top


class Game():

    def __init__( self, x, y, fps ):

        #initialize
        self.x = x
        self.y = y

        self.screen = pygame.display.set_mode( ( x,y ) )
        self.running = True

        self.clock = pygame.time.Clock()
        self.fps = fps
        self.fpsFont = pygame.font.SysFont('mono', 22)

        self.player = Player(BLUE, 50, 50, 400, 400, 5)
        self.wall = wall = Wall(1220,40,0,0)

    def on_event(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.running = False

            # Get keydowns and adjust player speed  
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_RIGHT:
                    self.player.move_x += self.player.speed
                if event.key == pygame.K_LEFT:
                    self.player.move_x -= self.player.speed
                if event.key == pygame.K_UP:
                    self.player.move_y -= self.player.speed
                if event.key == pygame.K_DOWN:
                    self.player.move_y += self.player.speed

            # Get keyups and resets vector
            if event.type == pygame.KEYUP:
                if event.key == pygame.K_RIGHT:
                    self.player.move_x = 0
                if event.key == pygame.K_LEFT:
                    self.player.move_x = 0
                if event.key == pygame.K_UP:
                    self.player.move_y = 0
                if event.key == pygame.K_DOWN:
                    self.player.move_y = 0

    def draw_rects(self):
        # draw all the images to the screen
        self.screen.blit(self.player.image, (self.player.rect.x, self.player.rect.y))
        self.screen.blit(self.wall.image, (self.wall.rect.x, self.wall.rect.y))     

    def draw_fps(self):
        #Displays an fps counter
        text = "FPS: " + str(round(self.clock.get_fps()))
        self.renderedFPS = self.fpsFont.render(text, True, BLACK)
        self.screen.blit(self.renderedFPS, (0 , self.y - self.y / 20))

    def mainloop(self):
        #Game loop
        while self.running == True:

            # Event loop
            self.on_event()

            # Game Logic
            self.player.move()
            self.wall.collision(self.player)


            # Draw everything
            self.screen.fill( WHITE )
            self.draw_fps()
            self.draw_rects()
            pygame.display.flip()

            # Limit FPS to 60
            self.clock.tick(self.fps)


if __name__ == "__main__":  

    game = Game(1220, 720, 60)
    game.mainloop()
\$\endgroup\$
2
\$\begingroup\$

You might need to look into axis separation. When it comes to collision detection in 2d with rects, you need to split up your movement and collision detection into separate x- and y-axis.

In other words, you should try moving your player on the x-axis first, then check for collision on that axis. If there is a collision, adjust on the x-axis only.

After that, do the same for the y-axis. Move the player on the y-axis and then check and correct movement against the rect on the y-axis.

This prevents the object from jumping around and it makes it easier to detect the position as you're only dealing with one axis at a time.

So add separate functions for moving your character on the x- and y-axis.

Do the same for the collision function and I think this could improve your collision detection. Look up axis separation if you need further information. I believe that is what it is called.

I had similar issues way back when I was first starting out with game programming in pygame. Axis separation as I have described it has become an essential tool for player collision detection, even in 3D.

| improve this answer | |
\$\endgroup\$
0
\$\begingroup\$

The problem happens because when you are pressing both the up key and either the left or right key, lets use right in this case, your player.move_y is negative, and that moved the player.rect.top to the wall.rect.bottom as you want, but the player.move_x is also possative, or negative if moving left, so the function not only moves your player to the bottom of the wall but it also teleports him/her/it to the left of the wall as well because the player.rect.left = wall.rect.left argument is also being called.

To fix this issue, you may be able to put something like this bit of code before your first if statement after if "self.rect.colliderect(player.rect):" in Wall.collisions() function:

if player.move_x > 0 and player.move_y < 0:
# moving up and right
    player.rect.top = self.rect.bottom
    return
elif player.move_x < 0 and player.move_y < 0:
# moving up and left
    player.rect.top = self.rect.bottom
    return

The return is meant to return the function before it has a chance to hit the rest of the statement and cause the bug if you are moving up right/up left.

This bit of code only fixes it for the bottom of the wall, but from this, I'm sure you can work out how to get the rest of the wall covered if you need it.

I hope this helps. :)

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thank you for the answer, it does work when going up and left/right. However, it doesn't work for down and left/right nor when hitting the sides of a rectangle.. In which case, the sprite will jump to the top or bottom of the rectangle. \$\endgroup\$ – Netanel Sep 16 '15 at 23:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.