Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411581 Posts in 69386 Topics- by 58445 Members - Latest Member: Mansreign

May 05, 2024, 06:52:19 PM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)What am I doing wrong?
Pages: [1]
Print
Author Topic: What am I doing wrong?  (Read 1034 times)
ImaginaryThomas
Level 1
*


Reor, Creo, Incito


View Profile WWW
« on: October 05, 2009, 04:13:51 PM »

Hey guys, I've been gone for a while but I've come back to game dev with a vengeance!

I'm doing a tile based game in pygame and I'm trying to get an elegant solution for collision detection and I can't seem to get it right. I feel like there's an obvious solution that I just cannot see and maybe someone will take pity on me and throw me a bone?

So the map is 0 = wall and numbers 1-7 are different background depths. for example

00000000000000000000000000
00000000000000000000000000
00000000000000000000000000
00011111110111100000000000
00000000000000000000000000

Here's my player class:

class player(pygame.sprite.Sprite):
   def __init__(self, init):
           pygame.sprite.Sprite.__init__(self)
           self.image = pygame.Surface([boxsize, boxsize])
           self.image.fill([255, 255, 255])
           self.rect = self.image.get_rect()
           self.rect.topleft = init
           self.movement = 5
      self.jump = 0
      self.hijump = 10
   def move(self, mx, my):
      x = self.rect.topleft[0]
      y = self.rect.topleft[1]
      bump = wallHit(self, mx, my)

      if (mx > 0 and not ((bump[1] > 0 and bump[2] > 0 and bump[3] > 0) or (bump[1] > 0 and bump[3] > 0) or (bump[1] > 0 and bump[3] == 0) or   (bump[3] > 0 and bump[1] == 0 and bump[2] == 0))): #right
         x += (mx * self.movement)
      if (mx < 0 and not ((bump[0] > 0 and bump[2] > 0 and bump[3] > 0) or (bump[0] > 0 and bump[2] > 0) or (bump[0] > 0 and bump[2] == 0) or   (bump[2] > 0 and bump[0] == 0 and bump[3] == 0))): #left
         x += (mx * self.movement)
      if (my < 0 and (bump[0] == 0 and bump[1] == 0)): #up
         y += (my * self.movement)
      if (my > 0 and (bump[3] == 0 and bump[2] == 0)): #down
         y += (my * self.movement)


      if (self.jump == 0):
         if (bump[3] == 0 and bump[2] == 0): #down
            y += (1 * self.movement)
      elif (self.jump > 0):
         if (bump[1] == 0 and bump[0] == 0): #up
            y += (-1 * self.movement)
            player.jump -= 1
         else:
            player.jump = 0
            

      self.rect.topleft = [x,y]
      screen.blit(self.image,self.rect.topleft)
   def cammove(self, x, y):
      myx = self.rect.topleft[0]
      myy = self.rect.topleft[1]
      
      self.rect.topleft = [(myx + x),(myy + y)]
      screen.blit(self.image,self.rect.topleft)


In the move action it calls wallHit which returns the values in the map array that each corner is on. topleft = 0, topright=1 bottomleft = 2, bottom right=3

Now you can see I'm testing on left and right movements for several possible situations that would not allow the player to move in a given direction. For left I test that none of these are returned from wallhit: (x=hit)

X-  X-  --  X-
XX, X-, X-, --

Which seems right unless you're walking off a ledge and try to go back, then the 3rd situation is true.

Am I doing this wrong? Can someone point me in the right direction?
Logged

Triplefox
Level 9
****



View Profile WWW
« Reply #1 on: October 05, 2009, 05:56:03 PM »

First thing I did was to rewrite the code so that the mysterious and potentially confusing "bump" is split out into four variables:

Code:
        tl,tr,bl,br = wallHit(self, mx, my)

        if (mx > 0 and not ((tr > 0 and bl > 0 and br > 0) or (tr > 0 and br > 0) or (tr > 0 and br == 0) or (br > 0 and tr == 0 and bl == 0))): #right
            x += (mx * self.movement)
        if (mx < 0 and not ((tl > 0 and bl > 0 and br > 0) or (tl > 0 and bl > 0) or (tl > 0 and bl == 0) or (bl > 0 and tl == 0 and br == 0))): #left
            x += (mx * self.movement)
        if (my < 0 and (tl == 0 and tr == 0)): #up
            y += (my * self.movement)
        if (my > 0 and (br == 0 and bl == 0)): #down
            y += (my * self.movement)

        if (self.jump == 0):
            if (br == 0 and bl == 0): #down
                y += (1 * self.movement)
        elif (self.jump > 0):
            if (tr == 0 and tl == 0): #up
                y += (-1 * self.movement)
                player.jump -= 1
        else:
            player.jump = 0

This is still confusing, so I add:

Code:
        tl = (tl>0)
        tr = (tr>0)
        bl = (bl>0)
        br = (br>0)

And subsequently:

Code:
        if (mx > 0 and not ((tr and bl and br) or (tr and br) or (tr and not br) or (br and not tr and not bl))): #right
            x += (mx * self.movement)
        if (mx < 0 and not ((tl and bl and br) or (tl and bl) or (tl and not bl) or (bl and not tl and not br))): #left
            x += (mx * self.movement)
        if (my < 0 and (not tl and not tr)): #up
            y += (my * self.movement)
        if (my > 0 and (not br and not bl)): #down
            y += (my * self.movement)

        if (self.jump == 0):
            if (not br and not bl): #down
                y += (1 * self.movement)
        elif (self.jump > 0):
            if (not tr and not tl): #up
                y += (-1 * self.movement)
                player.jump -= 1
        else:
            player.jump = 0

There's no reason to make your code look nasty until you're absolutely sure it works the way you want and you have to do it for some performance reason. Now we can start talking about the problem domain, and the specific kinds of behavior you want from your collision response.

The system you have now has a majorly important property: It tries to avoid performing any movement if there's overlap, which is less popular than the "move first, then separate the overlapping collision" system. The reason why the latter gets used more is because it's easier to get started with and performs a bit better. However, once you move beyond "everything is a same-size square" cases, you start having to hack special cases into separation algorithms so that they correctly slide characters that are running pressed up against the environment geometry, and you can run into nasty stuff where separating along both axes will pop the character the wrong way.

Your method can be cleaner because it will never overlap, but you'll have to add in a way to iteratively narrow movement, or else objects will just stop some number of pixels before they touch the environment.

The other important thing here is that you treat x and y responses independently with ad-hoc logic. This is an easy source of "character walks through walls but can't jump" types of behavior. It's the most performant way to do things and can be made to work for simple platform behavior, but as you add special cases it can turn nasty very quickly, and it tends to interact poorly with animation behavior.

A more controlled solution for complex situations is to create a set of finite states and a transition system for "ground" "air" "slopeclimb" etc. Then you will be able to describe in very fine detail things like, "when the player is in the ground state, to transition to the air state, the player has to either have upwards y velocity, or have no ground underneath." And as you add more cases - moving platforms, ropes, slides, etc. - you add more states and more transitions.

If you do choose to code up a transition system, the rule of thumb is that the state is just a block of conditions to direct where to transition next(including "transition to the same state") - the action itself happens after you determine which transition to use.

Don't forget that a lot of collision problems can be conceptually inverted - the "avoid overlap" vs "overlap then separate" is one example. Another example is that instead of seeing the environment as an open space with walls, you can envision it as a totally solid area with gaps of geometry inside(this is how the BSP collision of Doom and Quake worked.)
Logged

ImaginaryThomas
Level 1
*


Reor, Creo, Incito


View Profile WWW
« Reply #2 on: October 06, 2009, 05:10:34 AM »

Thanks for the advice, I'm going to have a look at this when I get home.

I always saw the 'move then adjust' as a extraneous extra step. Why move then move back if you're in a wall when you can see where the movement will end up and not move if you don't have to?

Alos, thanks for the code cleanup advice. Python's very different from my normal languages in structure, I'm still getting used to it.

I give your response 5 out of 5 tigers  Tiger Tiger Tiger Tiger Tiger
Logged

Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic