view skaapsteker/physics.py @ 85:6e8cfd6fcd63

Avoived?!
author Adrianna Pińska <adrianna.pinska@gmail.com>
date Mon, 04 Apr 2011 14:34:04 +0200
parents 6de531d648c3
children a1d95c6152a0
line wrap: on
line source

"""Model of gravity, acceleration, velocities and collisions.

   Works very closely with sprites/base.py.
   """

import pygame.sprite
import pygame.draw
import pygame
import time
from constants import DEBUG, EPSILON

class Sprite(pygame.sprite.DirtySprite):

    mobile = True # whether the velocity may be non-zero
    gravitates = True # whether gravity applies to the sprite

    terminal_velocity = (300.0, 300.0) # maximum horizontal and vertial speeds (pixels / s)
    bounce_factor = (0.98, 1.05) # bounce factor

    def __init__(self, *args, **kwargs):
        super(Sprite, self).__init__(*args, **kwargs)
        self.velocity = (0.0, 0.0)
        self.rect = pygame.Rect(0, 0, 10, 10) # sub-classes should override
        self.image = pygame.Surface((10, 10))
        self.image.fill((0, 0, 200))
        self.visible = 1
        self.dirty = 1
        self.blendmode = 0

    def init_pos(self):
        self._float_pos = self.rect.topleft

    def draw_debug(self, surface):
        pygame.draw.rect(surface, (240, 0, 0), self.rect, 1)

    def deltav(self, dv):
        v_x, v_y = self.velocity
        v_x, v_y = v_x + dv[0], v_y + dv[1]

        t_v = self.terminal_velocity
        v_x = max(min(v_x, t_v[0]), -t_v[0])
        v_y = max(min(v_y, t_v[1]), -t_v[1])

        self.velocity = (v_x, v_y)

    def deltap(self, dt):
        v_x, v_y = self.velocity
        f_x, f_y = self._float_pos
        d_x, d_y = v_x * dt, v_y * dt
        f_x, f_y = f_x + d_x, f_y + d_y
        self._float_pos = f_x, f_y
        self.rect.topleft = int(f_x), int(f_y)

    def collide(self, other):
        print "Collided:", self, other

    def collide_immobile(self, immobile):
        print "Collided with immobile:", self, immobile
        if not self.rect.colliderect(immobile.rect):
            print "  Collision avoided!"
            return

        v_x, v_y = self.velocity
        clip = self.rect.clip(immobile.rect)
        MAX_DT = 0.1
        frac_x = clip.width / abs(v_x) if abs(v_x) > EPSILON else MAX_DT
        frac_y = clip.height / abs(v_y) if abs(v_y) > EPSILON else MAX_DT

        if frac_x > frac_y:
            # collision in y
            frac = frac_y
            b_y = -v_y * self.bounce_factor[1] * immobile.bounce_factor[1]
            b_x = v_x
        else:
            # collision in x
            frac = frac_x
            b_x = -v_x * self.bounce_factor[0] * immobile.bounce_factor[0]
            b_y = v_y

        self.velocity = (-v_x, -v_y)
        self.deltap(frac * 1.1)
        self.velocity = (b_x, b_y)


class World(object):

    GRAVITY = 9.8 * 20.0 # pixels / s^2

    def __init__(self):
        self._all = pygame.sprite.LayeredUpdates()
        self._mobiles = pygame.sprite.Group()
        self._immobiles = pygame.sprite.Group()
        self._gravitators = pygame.sprite.Group()
        self._last_time = None

    def add(self, sprite):
        sprite.init_pos()
        self._all.add(sprite)
        if sprite.mobile:
            self._mobiles.add(sprite)
        elif (sprite.floor or sprite.block):
            self._immobiles.add(sprite)
        if sprite.gravitates:
            self._gravitators.add(sprite)

    def update(self):
        if self._last_time is None:
            self._last_time = time.time()
            return

        # find dt
        now = time.time()
        self._last_time, dt = now, now - self._last_time

        # gravity
        dv = (-self.GRAVITY * 0.5 * dt, self.GRAVITY * dt)
        for sprite in self._gravitators:
            sprite.deltav(dv)

        # position update (do last)
        for sprite in self._mobiles:
            sprite.deltap(dt)

        # check for collisions
        collisions = []
        collide = collisions.append
        for sprite1 in self._mobiles.sprites():
            spritecollide = sprite1.rect.colliderect
            for sprite2 in self._mobiles.sprites():
                if id(sprite1) < id(sprite2) and spritecollide(sprite2):
                    collide((sprite1, sprite2))
            for sprite2 in self._immobiles.sprites():
                if spritecollide(sprite2):
                    collide((sprite1, sprite2))
        self.dispatch_collisions(collisions)

    def dispatch_collisions(self, collisions):
        for s1, s2 in collisions:
            if not s2.mobile:
                s1.collide_immobile(s2)
            else:
                s1.collide(s2)

    def draw(self, surface):
        self._all.draw(surface)
        if DEBUG:
            for sprite in self._all:
                sprite.draw_debug(surface)