Changeset 97:a1d95c6152a0


Ignore:
Timestamp:
Apr 4, 2011, 4:45:48 PM (9 years ago)
Author:
Simon Cross <hodgestar@…>
Branch:
default
Message:

Shiny new collision detection. Read code for usage information.

Location:
skaapsteker
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • skaapsteker/level.py

    r94 r97  
    4646            tile.floor = floor
    4747            tile._layer = layer
     48            if not (tile.block or tile.floor):
     49                tile.collides_with = set()
    4850            return tile
    4951        return _tilefac
  • skaapsteker/physics.py

    r85 r97  
    1212class Sprite(pygame.sprite.DirtySprite):
    1313
     14    # physics attributes
    1415    mobile = True # whether the velocity may be non-zero
    1516    gravitates = True # whether gravity applies to the sprite
    16 
    1717    terminal_velocity = (300.0, 300.0) # maximum horizontal and vertial speeds (pixels / s)
    1818    bounce_factor = (0.98, 1.05) # bounce factor
     19
     20    # collision attributes
     21    # Sprite X collides with Y iff (X.collision_layer in Y.collides_with) and X.check_collides(Y)
     22    # Collisions result in the colliding movement being partially backed out, a call to X.bounce(frac) and a call to X.collided(Y)
     23    # X.bounce(frac) is only called for the first (as determined by backing out distance) collision in a multi-collision event
     24    collision_layer = None # never collides with anything
     25    collides_with = set() # nothing collides with this
    1926
    2027    def __init__(self, *args, **kwargs):
     
    5259        self.rect.topleft = int(f_x), int(f_y)
    5360
    54     def collide(self, other):
     61    def check_collides(self, other):
     62        return True # default to relying purefly on collision_layer and collides_with
     63
     64    def collided(self, other):
    5565        print "Collided:", self, other
    5666
    57     def collide_immobile(self, immobile):
    58         print "Collided with immobile:", self, immobile
    59         if not self.rect.colliderect(immobile.rect):
    60             print "  Collision avoided!"
    61             return
     67    def bounce(self, other, normal):
     68        """Alter velocity after a collision.
    6269
     70           other: sprite collided with
     71           normal: unit vector (tuple) normal to the collision
     72                   surface.
     73           """
    6374        v_x, v_y = self.velocity
    64         clip = self.rect.clip(immobile.rect)
    65         MAX_DT = 0.1
    66         frac_x = clip.width / abs(v_x) if abs(v_x) > EPSILON else MAX_DT
    67         frac_y = clip.height / abs(v_y) if abs(v_y) > EPSILON else MAX_DT
    68 
    69         if frac_x > frac_y:
    70             # collision in y
    71             frac = frac_y
    72             b_y = -v_y * self.bounce_factor[1] * immobile.bounce_factor[1]
    73             b_x = v_x
    74         else:
    75             # collision in x
    76             frac = frac_x
    77             b_x = -v_x * self.bounce_factor[0] * immobile.bounce_factor[0]
    78             b_y = v_y
    79 
    80         self.velocity = (-v_x, -v_y)
    81         self.deltap(frac * 1.1)
    82         self.velocity = (b_x, b_y)
     75        b_x = 1.0 + self.bounce_factor[0] * other.bounce_factor[0]
     76        b_y = 1.0 + self.bounce_factor[1] * other.bounce_factor[1]
     77        v_x = (1.0 - normal[0] * b_x) * v_x
     78        v_y = (1.0 - normal[1] * b_y) * v_y
     79        self.velocity = v_x, v_y
    8380
    8481
     
    9087        self._all = pygame.sprite.LayeredUpdates()
    9188        self._mobiles = pygame.sprite.Group()
    92         self._immobiles = pygame.sprite.Group()
    9389        self._gravitators = pygame.sprite.Group()
     90        self._collision_groups = { None: pygame.sprite.Group() }
    9491        self._last_time = None
    9592
     
    9996        if sprite.mobile:
    10097            self._mobiles.add(sprite)
    101         elif (sprite.floor or sprite.block):
    102             self._immobiles.add(sprite)
    10398        if sprite.gravitates:
    10499            self._gravitators.add(sprite)
     100        self._add_collision_group(sprite.collision_layer)
     101        for layer in sprite.collides_with:
     102            self._add_collision_group(layer)
     103            self._collision_groups[layer].add(sprite)
     104
     105    def _add_collision_group(self, layer):
     106        if layer in self._collision_groups:
     107            return
     108        self._collision_groups[layer] = pygame.sprite.Group()
     109
     110    def _backout_collisions(self, sprite, others, dt):
     111        frac, normal, idx = 0.0, None, None
     112        v_x, v_y = sprite.velocity
     113        abs_v_x, abs_v_y = abs(v_x), abs(v_y)
     114
     115        for i, other in enumerate(others):
     116            clip = sprite.rect.clip(other.rect)
     117            # TODO: avoid continual "if abs_v_? > EPSILON"
     118            frac_x = clip.width / abs_v_x if abs_v_x > EPSILON else dt
     119            frac_y = clip.height / abs_v_y if abs_v_y > EPSILON else dt
     120            if frac_x > frac_y:
     121               if frac_y > frac:
     122                  frac, normal, idx = frac_y, (0, 1), i
     123            else:
     124               if frac_x > frac:
     125                  frac, normal, idx = frac_x, (1, 0), i
     126
     127        sprite.deltap(max(-1.1 * frac, -dt))
     128        sprite.bounce(others[idx], normal)
     129        for other in others:
     130            sprite.collided(other)
    105131
    106132    def update(self):
     
    118144            sprite.deltav(dv)
    119145
    120         # position update (do last)
     146        # position update and collision check (do last)
    121147        for sprite in self._mobiles:
    122148            sprite.deltap(dt)
    123 
    124         # check for collisions
    125         collisions = []
    126         collide = collisions.append
    127         for sprite1 in self._mobiles.sprites():
    128             spritecollide = sprite1.rect.colliderect
    129             for sprite2 in self._mobiles.sprites():
    130                 if id(sprite1) < id(sprite2) and spritecollide(sprite2):
    131                     collide((sprite1, sprite2))
    132             for sprite2 in self._immobiles.sprites():
    133                 if spritecollide(sprite2):
    134                     collide((sprite1, sprite2))
    135         self.dispatch_collisions(collisions)
    136 
    137     def dispatch_collisions(self, collisions):
    138         for s1, s2 in collisions:
    139             if not s2.mobile:
    140                 s1.collide_immobile(s2)
    141             else:
    142                 s1.collide(s2)
     149            sprite_collides = sprite.rect.colliderect
     150            collisions = []
     151            for other in self._collision_groups[sprite.collision_layer]:
     152                if sprite_collides(other) and sprite.check_collides(other):
     153                    collisions.append(other)
     154            if collisions:
     155                self._backout_collisions(sprite, collisions, dt)
    143156
    144157    def draw(self, surface):
  • skaapsteker/sprites/base.py

    r88 r97  
    1010TILE_SIZE = (64, 64)
    1111
     12# Collision Layers (values are ids not numbers)
     13PC_LAYER = 0
     14MONSTER_LAYER = 1
     15
    1216
    1317class Monster(Sprite):
     18
    1419    image_file = None
     20    collision_layer = MONSTER_LAYER
     21    collides_with = set([PC_LAYER])
    1522
    1623    def __init__(self, pos, **opts):
     
    4350    mobile = False
    4451    gravitates = False
    45 
     52    collides_with = set([PC_LAYER, MONSTER_LAYER])
    4653
    4754    def __init__(self, pos, image):
     
    5057        self.image = image
    5158        self.rect = Rect((pos[0] * TILE_SIZE[0], pos[1] * TILE_SIZE[1]), TILE_SIZE)
    52 
    5359
    5460
  • skaapsteker/sprites/player.py

    r90 r97  
    44import os
    55
    6 from skaapsteker.sprites.base import TILE_SIZE
     6from skaapsteker.sprites.base import TILE_SIZE, PC_LAYER
    77from skaapsteker.physics import Sprite
    88from skaapsteker.constants import Layers
     
    1111
    1212class Player(Sprite):
     13
     14    collision_layer = PC_LAYER
    1315
    1416    def __init__(self):
Note: See TracChangeset for help on using the changeset viewer.