Source

Fat x Fast / FatxFast / physics / physics.py

The branch 'advanced-physics' does not exist.
Full commit
# This file is part of FatxFast.                                                
#                                                                               
#    FatxFast is free software: you can redistribute it and/or modify           
#    it under the terms of the GNU General Public License as published by       
#    the Free Software Foundation, either version 3 of the License, or          
#    (at your option) any later version.                                        
#                                                                               
#    FatxFast is distributed in the hope that it will be useful,                
#    but WITHOUT ANY WARRANTY; without even the implied warranty of             
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              
#    GNU General Public License for more details.                               
#                                                                               
#    You should have received a copy of the GNU General Public License          
#    along with FatxFast.  If not, see <http://www.gnu.org/licenses/>.          
                                                                                
# Versioning based on:                                                          
# http://en.wikipedia.org/wiki/Versioning#Designating_development_stage         
__author__ = "dryatu (c) 2013"                                                  
__version__ = "1.2.4"

from FatxFast.fileio import LOGGER
try:
    from pygame.math import Vector2
except ImportError:
    LOGGER.log_message(
"Pygame's vector class not found. "+\
"Using the fallback version. Read the game's pygame release site!")
    from FatxFast.utils.vector2 import Vector2
from FatxFast.objects import triggers
import pygame
from FatxFast.physics import axismoving
from FatxFast.tilemap.editor.layer import COLLIDE_LAYER

class Physics(object):
    
    """Attributes:
        Speedvector(Vec2), AccelVector(Vec2), Max & Min speeds(Floats), 
        Collides(Boolean)
    Methods:
        Move by speed: Does collision detection based on speed
        moves entity next to the colliding object if one is present.

    Collision detection in detail:
        Fetches the border area tile positions for the entity ->
        Fetches the end position for the axis -> Does a tile by tile
        iteration from starting position to the end position. Upon collision
        returns the colliding tile -> Entity is moved to the new position.
        If there was a collision the new maximum speed is so that next move
        will move the entity next to the colliding point.
    """
    maxspeed = 1.30
    minspeed = 0.3
    accelspeed = 0.8
    bb_rect_component_relations = {"x":"width", "y":"height"}
    def __init__(self, entity, worldmap, collides=True):
        self.collides = collides
        self.speedvector = Vector2(0,0)
        self.accelvector = Vector2(0,0)
        self._collision_sprite = pygame.sprite.Sprite()
        self.collision_map = {}
        self.entity = entity
        self.worldmap = worldmap
        self._xmove = axismoving.MoveOnXAxis(self)
        self._ymove = axismoving.MoveOnYAxis(self)
        self._speed_down_factor = 0.4

    def get_collision_tile(self, x, y):
        return self.worldmap.sprite_layers[COLLIDE_LAYER].content2D[y][x]
            
    def get_default_maxspeed(self):
        return Physics.maxspeed

    def get_first_obstacle(self, xstart, xend, ystart, yend, 
        fstep=0, sstep=0):
        for first in xrange(ystart, yend+fstep, fstep):
            for second in xrange(xstart, xend+sstep, sstep):
                collision = None
                try:
                    collision = self.get_collision_tile(second,first)
                except IndexError:
                    pass
                if collision is not None:
                    return collision

    def _move_by_speed(self, dt):
        self._slow_down('x', dt)
        self._slow_down('y', dt)
        self.speedvector = self.speedvector+(self.accelvector*(dt/4))
        if self.speedvector.length() > (self.maxspeed*dt):
            self.speedvector.scale_to_length(self.maxspeed*dt)  

        # Check for collisions on both axes & move by speed
        self._xmove()
        self._ymove()

    def set_bounding_box_rect(self, component, size):
        # Sets a rectangle from the speedvector component
        rect = self.entity.rect.copy()
        size_component = self.bb_rect_component_relations[component]
        if size < 0:
            component_value = getattr(rect, component)
            setattr(rect, component, component_value+size)
        size_value = getattr(rect, size_component)
        setattr(rect, size_component, size_value+abs(size))
        self._collision_sprite.rect = rect

    def _slow_down(self, axis, dt):
        if not getattr(self.accelvector, axis):
            self._stop_movement(axis, dt)
            if getattr(self.speedvector, axis):
                try:
                    setattr(self.accelvector, axis, 
                        -getattr(self.speedvector.normalize()*\
                            self._speed_down_factor, axis))
                except ZeroDivisionError:
                    pass

    def _stop_movement(self, axis, dt):
        try:
            if (abs(getattr(self.speedvector, axis))/\
                (self._speed_down_factor*dt) < self.minspeed):
                setattr(self.speedvector, axis, 0)
        except ZeroDivisionError:
            setattr(self.speedvector, axis, 0)

    def trigger_events(self):
        collisions = pygame.sprite.spritecollide(self._collision_sprite, 
            self.worldmap.world, False)
        if not collisions:
            return False
        for col in collisions:
            if not col.entity_id in triggers.TRIGGER_RANGE:
                continue
            collisionevent = self.collision_map.get(col.entity_id)
            position_changed = col.process(self.entity)
            if collisionevent:
                self.collision_map[col.entity_id]()
                if collisionevent.kill:
                    col.kill()
            if position_changed:
                return True

    def update(self, dt):
        self._move_by_speed(dt)