pygame collision detection question

  • (3 Pages)
  • +
  • 1
  • 2
  • 3

41 Replies - 5508 Views - Last Post: 01 April 2013 - 12:18 AM Rate Topic: -----

#16 slidon  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 29
  • Joined: 09-February 13

Re: pygame collision detection question

Posted 01 March 2013 - 03:12 PM

I changed the line but its not working yet.
here are my modules

Main
import pygame, sys, runner, crate
pygame.init()
class main():
    def __init__(self):
        self.create_obstacles()
        self.bg_mask, self.bg_image = self.create_mask()
        for obstacle in self.obstacles:
            self.bg_image.blit(obstacle.image, (obstacle.rect))
    def main(self):
        screen.fill(white)
        runner.detect_keys()
        runner.detect_move()
        runner.detect_jump()
        runner.detect_collision(self.bg_mask)
        screen.blit(self.bg_image, (0, 0))
        screen.blit(runner.image, runner.rect)
    def create_mask(self):
        self.temp = pygame.Surface((640, 480)).convert_alpha()
        self.temp.fill((0, 0, 0, 0))
        for obs in self.obstacles:
            obs.update(self.temp)
        return pygame.mask.from_surface(self.temp), self.temp
    
    def create_obstacles(self):
        self.obstacles = [crate.crate]
    
# sets up colors
black = (0, 0, 0)
white = (255, 255, 255)
red = (255, 0, 0)
green = (0, 255, 0)
blue = (0, 0, 255)
# sets up screen
background = white
screen = pygame.display.set_mode((640, 480))
screen.fill(background)
# defines general veriables and objects
clock = pygame.time.Clock()
running = True
runit = main()
crate = crate.crate
runner = runner.runner
# main loop
while running:
    runit.main()
    clock.tick(60)
    pygame.display.flip()


my runner/player module
import pygame, sys
pygame.init()

class runner(pygame.sprite.Sprite):
    def __init__(self):
        self.image = pygame.image.load("runner.png").convert_alpha()
        self.mask = pygame.mask.from_surface(self.image)
        self.rect = self.image.get_rect()
        self.rect.topleft = [320, 400 - self.rect.bottomleft[1] - self.rect.topleft[1]]
        self.speed = 7
        self.ddown = False
        self.adown = False
        self.spacedown = False
        self.canmoveright = True
        self.canmoveleft = True
        self.yvelocity = 20
        self.groundy = self.rect.y
        self.jumping = False
        self.colliding = False
        self.collidewith = ""
        self.falling = False
    def detect_keys(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_d and self.canmoveright:
                        self.ddown = True
                    if event.key == pygame.K_a and self.canmoveleft:
                        self.adown = True
                    if event.key == pygame.K_SPACE:
                        self.spacedown = True
                        
            if event.type == pygame.KEYUP:
                    if event.key == pygame.K_d:
                        self.ddown = False
                    if event.key == pygame.K_a:
                        self.adown = False
                    if event.key == pygame.K_SPACE:
                        self.spacedown = False
    def detect_move(self):
        if self.ddown:
            self.rect.x += self.speed
        if self.adown:
            self.rect.x -= self.speed
        if self.colliding == False:
            self.collidewith = ""
            if self.jumping == False:
                if self.groundy <= self.rect.y:
                    if not self.rect.y >= 400 - (self.rect.bottomleft[1] - self.rect.topleft[1]):
                        self.rect.y += 10
                        self.falling = True
                    else:
                        self.rect.y = 400 - (self.rect.bottomleft[1] - self.rect.topleft[1])
                        self.groundy = self.rect.y
                        self.falling = False
                
    def detect_jump(self):
        if self.spacedown and self.groundy == self.rect.y:
            self.jumping = True
            self.falling = False
        if self.jumping:
            self.rect.y -= self.yvelocity
            self.yvelocity -= 1
            if self.yvelocity <= 0:
                self.falling = True
        if self.groundy <= self.rect.y:
            self.jumping = False
            self.groundy = self.rect.y
            self.yvelocity = 20
    def reset_jump(self):
        self.jumping = False
        self.groundy = self.rect.y
        self.yvelocity = 20
        self.falling = False
        
    def detect_collision(self, Mask):
        if Mask.overlap(self.mask, (self.rect.x, self.rect.y)):
            print "overlapping"
        else:
            self.canmoveright = True
            self.canmoveleft = True
            self.colliding = False
            
# set up screen
screen = pygame.display.set_mode((640, 480))
# defines objects
runner = runner()


my crate module
import pygame, sys
pygame.init()

class crate(pygame.sprite.Sprite):
    def __init__(self):
        self.image = pygame.image.load("crate.png").convert_alpha()
        self.rect = self.image.get_rect()
        self.rect.topleft = [200, 400 - (self.rect.bottomleft[1] - self.rect.topleft[1])]
# sets up screen
screen = pygame.display.set_mode((640, 480))
# defines objects
crate = crate()


thanks again, slidon
Was This Post Helpful? 0
  • +
  • -

#17 Mekire  Icon User is offline

  • D.I.C Head

Reputation: 117
  • View blog
  • Posts: 215
  • Joined: 11-January 13

Re: pygame collision detection question

Posted 01 March 2013 - 09:35 PM

Ok, so I think I made this too advanced too quickly and I apologize. There seem to be some basics you don't have a full grasp on yet. I can't really make your code work without completely rewriting it.

First lets talk about naming. Don't name a class within a module the same thing as the module. The simple convention calls for class names to be capital and module names to be lower case. This will allow you to have a class Crate within a module called crate without causing confusion (to the interpreter at least). This is why you couldn't create crates in your other module. It thought you were referring to the module rather than the class, of course you could have written crate.crate but that is still just confusing. Next global constants; no matter how insignificant (like colors for instance) should be written in all CAPS. This instantly tells someone they are global CONSTANTS.

The most important thing regarding your program however is how you are thinking about your character jumping/falling. You are trying to tell the player where the ground is before he hits it. This won't work. The player needs to "discover" where the ground is via collision detection. Stop thinking about there being a difference between jumping and falling. Jumping is just falling with an initial velocity (in this case in the negative-y direction) added on. Also as I stated before you need to test collision before the player moves, not after. Test collision with the location the player is going to end up if you move him, then move him after you know it's clear. If you move first and then test you will get stuck in walls.

While you of course don't need to copy my code to make your program work (and it wouldn't teach you much if you did), you should really try to figure out what every part is doing and why. If there is something about it you don't grasp, that would be a good thing to ask about.

Cheers,
-Mek
Was This Post Helpful? 0
  • +
  • -

#18 Mekire  Icon User is offline

  • D.I.C Head

Reputation: 117
  • View blog
  • Posts: 215
  • Joined: 11-January 13

Re: pygame collision detection question

Posted 02 March 2013 - 01:57 AM

Here is a reduced example with movement only in a single direction. Let's forget about jumping and gravity for now and just focus on getting accurate collision in one dimension.
Spoiler

All of the collision detection is taken care of in the functions get_pos, adjust_pos, and simple_overlap.

The get_pos function performs the first check for collision. If no collision is detected the sprite is moved. If a collision is detected I call the adjust_pos function; note specifically how the checks in that function are in a while loop. Without this loop your sprite won't move the remaining pixels. ie If your speed is 7 but a wall is 6 pixels away, you wouldn't move; as such the loop is essential. Once the adjust_pos function returns with the exact number of pixels we can safely move, only then do we move the sprite. The simple_overlap function is just a shorthand-convenience. When doing collision with masks the offset is the distance between the topleft corners of the rects that the masks correspond to. My simple_overlap function takes advantage of the fact that we aren't moving our background mask (this however wouldn't be true if the background was side-scrolling).

I know that starting this simple is no fun, but it really is essential. If you can fully grasp why the above code works, extending this to two dimensions is fairly trivial.

-Mek
Was This Post Helpful? 0
  • +
  • -

#19 slidon  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 29
  • Joined: 09-February 13

Re: pygame collision detection question

Posted 02 March 2013 - 06:05 AM

ok so. I re-wrote most of my code, now its like this.
main
import pygame, sys, runner, crate
pygame.init()
class Control():
    def __init__(self):
        self.bg_mask, self.bg_image = self.make_bg_mask()
        self.obstacles = [crate.Crate((250, 100))]
    def update(self):
        SCREEN.blit(self.bg_image, (0, 0))
    def main(self):
        self.update()
        self.event_loop()
        runner.update(self.bg_mask, self.bg_image)
        
    def make_bg_mask(self):
        temp = pygame.Surface(SCREEN.get_size())
        temp.fill((255, 255, 255, 0))
        for obs in self.obstacles:
            obs.update(temp)
        return pygame.mask.from_surface(temp), temp
    
    def event_loop(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_d:
                        runner.ddown = True
                        if runner.x_vel < 0:
                            runner.x_vel = -runner.x_vel
                    if event.key == pygame.K_a:
                        runner.adown = True
                        if runner.x_vel > 0:
                            runner.x_vel = -runner.x_vel
                    if event.key == pygame.K_SPACE:
                        runner.spacedown = True
                        
            if event.type == pygame.KEYUP:
                    if event.key == pygame.K_d:
                        runner.ddown = False
                    if event.key == pygame.K_a:
                        runner.adown = False
                    if event.key == pygame.K_SPACE:
                        runner.spacedown = False
# sets up colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
# sets up screen
SCREEN = pygame.display.set_mode((640, 480))
SCREEN.fill(WHITE)
# defines general veriables and objects
clock = pygame.time.Clock()
running = True
runit = Control()
runner = runner.Runner([100, 100])
# main loop
while running:
    runit.main()
    clock.tick(60)
    pygame.display.flip()

crate
import pygame, sys
pygame.init()

class Crate(pygame.sprite.Sprite):
    def __init__(self, location):
        self.image = pygame.image.load("crate.png").convert_alpha()
        self.rect = pygame.Rect(location, (self.image.get_width(), self.image.get_height()))

runner
import pygame, sys
pygame.init()

class Runner(pygame.sprite.Sprite):
    def __init__(self, location):
        self.image = pygame.image.load("runner.png").convert_alpha()
        self.mask = pygame.mask.from_surface(self.image)
        self.rect = pygame.Rect(location, (50, 50))
        self.x_vel = 7
        self.y_vel = 0
        self.ddown = False
        self.adown = False
        self.spacedown = False
        
    def update(self, Mask, Image):
        self.move(Mask)
        Image.blit(self.image, self.rect)
        
    def move(self, Mask):
        if self.adown:
            self.calc_next_pos(Mask)
        if self.ddown:
            self.calc_next_pos(Mask)
            
    def calc_next_pos(self, Mask):
        if self.test_overlap(Mask, (self.x_vel, self.y_vel)):
            self.adjust_pos(Mask)
        self.rect.x += self.x_vel
    def test_overlap(self, Mask, offset):
        off = (self.rect.x + offset[0], self.rect.y + offset[1])
        return Mask.overlap(self.mask, off)
    def adjust_pos(self, Mask):
        while self.test_overlap(Mask, (self.x_vel, self.y_vel)):
            self.x_vel += (1 if self.adown else -1)

there are two things I've found. One in my main module, unless I define self.obstacles at the start of the make_bg_mask module I get this error
AttributeError: Control instance has no attribute 'obstacles'
and two, if I fix that, my player does not move. It seem's to think that the runner is always colliding with bg_mask. If you put a print line in overlap_test you will see.

is this any better?
slidon
Was This Post Helpful? 0
  • +
  • -

#20 Mekire  Icon User is offline

  • D.I.C Head

Reputation: 117
  • View blog
  • Posts: 215
  • Joined: 11-January 13

Re: pygame collision detection question

Posted 02 March 2013 - 07:02 AM

OK. I can work with that. I'll try to point out what I changed.

You have to define self.obstacles in your init before you try to call make_bg_mask. That function works by iterating through self.obstacles. You did this:
def __init__(self):
    self.bg_mask, self.bg_image = self.make_bg_mask()
    self.obstacles = [crate.Crate((250, 100))]
But you needed to do this:
def __init__(self):
    self.obstacles = [crate.Crate((250, 100))]
    self.bg_mask, self.bg_image = self.make_bg_mask()

Now that brings us to the next problem which you also ran into; your player wouldn't move because you were always colliding. This is because of a few misunderstandings about surfaces and about how making masks with the from_surface function works.

So... If you want to use alpha (transparency) in a surface, you have to use convert_alpha(). If you try to fill a surface with transparency and you haven't done that, it won't work. Essentially, instead of creating a mask with your crate, you created a solid mask the size of your screen. The from surface function makes all the opaque parts in your image solid, and all the transparent parts not. This is what you needed instead:
def make_bg_mask(self):
    temp = pygame.Surface(SCREENSIZE).convert_alpha()
    temp.fill((0,0,0,0))
    for obs in self.obstacles:
        obs.update(temp)
    return pygame.mask.from_surface(temp), temp
The last couple problems were one line in the adjust_pos function. You had self.x_vel += (1 if self.adown else -1) instead of this self.x_vel += (1 if self.x_vel<0 else -1). And then finally, you were changing the x_vel variable but hadn't recorded anywhere what the non-modified speed was; I added a self.speed attribute to your Runner class and made it so key-presses use that. Oh yeah, only call pygame.init() once. Don't call it in every module.

Anyway, here are the modified files.

main.py:
Spoiler

crate.py:
Spoiler

runner.py:
Spoiler


Take a look at those and keep me updated.
-Mek
Was This Post Helpful? 1
  • +
  • -

#21 Mekire  Icon User is offline

  • D.I.C Head

Reputation: 117
  • View blog
  • Posts: 215
  • Joined: 11-January 13

Re: pygame collision detection question

Posted 02 March 2013 - 07:13 AM

Edit: I realized that your self.x_vel += (1 if self.adown else -1) actually worked just fine, but when you generalize the function to work for both x and y you will have to change it anyway :bigsmile:
Was This Post Helpful? 0
  • +
  • -

#22 slidon  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 29
  • Joined: 09-February 13

Re: pygame collision detection question

Posted 03 March 2013 - 02:50 AM

ok. I have made all those changes and now the player moves! but even though I changed the control classes update function to this
def update(self):
        SCREEN.fill(WHITE)
        SCREEN.blit(self.bg_image, (0, 0))
        runner.update(self.bg_mask, self.bg_image)

It looks like I haven't filled the screen at all, the players sprite from the previous frame is still there. Also the crate does not appear... but this line
obs.update(temp)

should do it right?

slidon
Was This Post Helpful? 0
  • +
  • -

#23 Mekire  Icon User is offline

  • D.I.C Head

Reputation: 117
  • View blog
  • Posts: 215
  • Joined: 11-January 13

Re: pygame collision detection question

Posted 03 March 2013 - 08:42 AM

Please take a closer look at the files I pasted in my previous post (the ones in the spoiler tags). I am not updating SCREEN directly; I am passing SCREEN around (this makes my update functions have the versatility to draw on any surface; not coded to only draw to the display surface). But.. if you really must do it that way, it would be this:
def update(self):
    SCREEN.fill(WHITE)
    SCREEN.blit(self.bg_image, (0, 0))
    self.MyRunner.update(self.bg_mask,SCREEN)
Note, I'm passing SCREEN to my MyRunner.update function, not self.bg_image.

-Mek
Was This Post Helpful? 0
  • +
  • -

#24 slidon  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 29
  • Joined: 09-February 13

Re: pygame collision detection question

Posted 03 March 2013 - 11:17 AM

great, sorry I needed to look at the code some more... what about the crate?
Was This Post Helpful? 0
  • +
  • -

#25 Mekire  Icon User is offline

  • D.I.C Head

Reputation: 117
  • View blog
  • Posts: 215
  • Joined: 11-January 13

Re: pygame collision detection question

Posted 03 March 2013 - 04:33 PM

View Postslidon, on 03 March 2013 - 06:17 PM, said:

great, sorry I needed to look at the code some more... what about the crate?
Check the update function on your crate class. I'm guessing you did something similar; updating to SCREEN instead of to the surface that we pass as an argument most likely (temp in this case). Compare your Crate update function to my previously posted Crate update function.

-Mek
Was This Post Helpful? 0
  • +
  • -

#26 slidon  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 29
  • Joined: 09-February 13

Re: pygame collision detection question

Posted 05 March 2013 - 03:40 AM

Ahh yes. That was my problem. Now all is working on the x axis! Looking into jumping...
What is the best way to do jumping without calculating the ground position before the jump?
I looked at your game code but its quite hard to understand the method of it.

Slidon
Was This Post Helpful? 0
  • +
  • -

#27 Mekire  Icon User is offline

  • D.I.C Head

Reputation: 117
  • View blog
  • Posts: 215
  • Joined: 11-January 13

Re: pygame collision detection question

Posted 05 March 2013 - 06:19 AM

I wrote a little physics class that our player inherited.
Spoiler
When we create our player we also run the physics __init__. The player is given a current x-velocity, current y-velocity, and an initial y-velocity all set to 0 when we start. Gravity here is set to 20 pixels/second^2. The player also needs an attribute to keep track of whether they are falling or not; and lastly we need a timer to keep track of how long we have been falling.

Then in the phys_update function we check if the player is falling; if they are, then we check if the fall timer has started yet. If it hasn't, then we start it. Then we update the current y-velocity based on the equation V=At+Vi or in words, Velocity equals Acceleration times time plus initial Velocity.

If the player is not falling we reset the timer and set initial y-velocity back to zero.

The player detects whether or not they are falling in my get_pos function with:
#Has the player walked off an edge?
if not self.fall and not self.simple_overlap(Mask,[0,1]):
    self.fall = True
This first checks if the player is already falling. If the player is not falling already, then we perform a collision check one pixel below us. If that collision test returns false then we know there is no ground beneath us and set fall to true. The function for actually checking the collisions while falling will be identical to the one we used for x; the only difference is it will also have to reset initial y-velocity to zero and set fall to False after a collision.

Whenever the player presses the jump button, all we have to do is set the initial y-velocity to the desired jump strength of our player and set fall to True. We must, of course, also run the physics update function in the players update phase as well.

Program pasted in below so you don't have to search through the thread for the current one:
Spoiler


Cheers,
-Mek
Was This Post Helpful? 0
  • +
  • -

#28 slidon  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 29
  • Joined: 09-February 13

Re: pygame collision detection question

Posted 05 March 2013 - 11:15 AM

ok I made those changes and things are all going a bit weird. When a player falls and lands on a block, first the collision is the same as when I first started this thread. And second pygame freezes and loads infinitly. I think that a working collision detection system is something for when I get more about pygame. I know you can debug my code but then I am just typing the same code into my program and not understanding enough of it to create my own games. Is there some sort of step up to this? how did you get good?

in case you need it here is my updated runner module.
the main and crate modules are basically the same.
import pygame, sys
pygame.init()

class _Physics:
    def __init__(self):
        self.gravity = 10
        self.falling = True
        self.falltime = None
        self.y_vel = 0
        self.speed = 7
    def physics_update(self):
        if self.falling:
            if not self.falltime:
                self.falltime = pygame.time.get_ticks()
            self.y_vel = self.gravity*((pygame.time.get_ticks() - self.falltime)/1000.0) + self.y_vel 
        else:
            self.y_vel = 0
            self.falltime = None
            
class Runner(_Physics):
    def __init__(self, location):
        _Physics.__init__(self)
        self.image = pygame.image.load("runner.png").convert_alpha()
        self.mask = pygame.mask.from_surface(self.image)
        self.rect = pygame.Rect(location, (50, 50))
        self.ddown = False
        self.adown = False
        self.spacedown = False
    def update(self, Mask, Image):
        self.move(Mask)
        Image.blit(self.image, self.rect)
        
    def move(self, Mask):
        if self.adown:
            self.speed = -7
        if self.ddown:
            self.speed = 7
        self.calc_next_pos(Mask)
        self.rect.y += self.y_vel
            
    def calc_next_pos(self, Mask):
        if not self.falling and not self.test_overlap(Mask, [0, 1]):
            self.falling = True
        elif self.test_overlap(Mask, [0, self.y_vel]) and self.falling:
            self.y_vel = self.adjust_y(Mask, [0, self.y_vel])
            self.falling = False
            
        if self.test_overlap(Mask, (self.speed, self.y_vel)):
            self.adjust_x(Mask, [self.speed, 0])
        
    def test_overlap(self, Mask, offset):
        off = (int(self.rect.x + offset[0]), int(self.rect.y + offset[1]))
        return Mask.overlap(self.mask, off)
    
    def adjust_x(self, Mask, offset):
        while self.test_overlap(Mask, offset):
            self.speed += (1 if self.adown else -1)
            
    def adjust_y(self, Mask, offset):
        while self.test_overlap(Mask, offset):
            self.y_vel += (1 if offset[1] > 0 else -1)
            

sorry its not in spoiler tags but I dont know how to use them yet

slidon
Was This Post Helpful? 0
  • +
  • -

#29 Mekire  Icon User is offline

  • D.I.C Head

Reputation: 117
  • View blog
  • Posts: 215
  • Joined: 11-January 13

Re: pygame collision detection question

Posted 05 March 2013 - 05:01 PM

Well I think you'd find most people wouldn't suggest a platformer as your first game. Usually it is suggested that you start with something like Tetris, then perhaps a break-out game, and then maybe a platformer for your third. This way you can build up some of the fundamentals a little more gradually, instead of trying to jump in with both feet. A lot actually goes into making a platformer.

I started working with Pygame (and Python in general really) a little over two years ago. My first game was a Tetris style game. I heavily based it off of someone else's code. In the end I had a working project, but looking back at the code now, it was awful (I am almost finished rewriting it).

The next project I tackled was a topdown game in the style of the original NES Zelda. I literally had issues with my monsters and player getting stuck in walls for over a month (collision detection isn't easy, especially at first). The game reached a nice working state, but got nowhere near completion. The code got so convoluted and obtuse as the complexity of the program increased that trying to maintain and add to it is a nightmare. I definitely want to rewrite this one soon.

After these two I did some minor projects investigating path-finding (a*), maze-generation (depth-first search), as well as cellular automata (Conway's Game of Life). My most recent project is a 1 on 1 space fighter in the style of the 90s game Star-Control. This is literally the only one of all of them that I would consider remotely well-written.

Basically, as long as you don't give up you will eventually create something you like. I strongly recommend going through other people's code (the above one I gave you for instance), and really looking at it line by line until you understand every little bit. Why did the author do some things and not others? Why is the program structured in a certain way? Why does the author pass a surface to his update functions rather than drawing on the global display surface? Etc.


As for your current code, the first things that jump out at me are, you didn't make an initial velocity attribute; you tried to use current velocity for initial velocity as well. Also, you don't actually seem to call your physics update function in your player update function. Anyway, I will try to take a closer look later and see if I can't make it work.

Cheers,
-Mek
Was This Post Helpful? 0
  • +
  • -

#30 slidon  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 29
  • Joined: 09-February 13

Re: pygame collision detection question

Posted 06 March 2013 - 10:14 AM

Ok I think I will explore more pygame and come back to this thread when I am more experienced.
Thanks for all the debugging and code.

Slidon
Was This Post Helpful? 0
  • +
  • -

  • (3 Pages)
  • +
  • 1
  • 2
  • 3