Example #1
0
class Ball(object):
    def __init__(self, game, res):
        self.ball_image = res.image_dict[const.BALL_IMAGE]
        center_image(self.ball_image)
        self.width = self.ball_image.width
        self.height = self.ball_image.height
    
        self.game = game
        self.res = res
        x = y = 200
        self.sprite = Sprite(self.ball_image, x = x, y = y)
        self.x = x
        self.y = y

        # State Machine

        self.state = "Stopped"

        # Sensors

        self.sensor_bottom = Sensor(self.res)
        self.sensor_top = Sensor(self.res)
        self.sensor_left = Sensor(self.res)
        self.sensor_right = Sensor(self.res)
        self.sensor_ground = Sensor(self.res)
        self.sensor_ground.red = 0.5
        self.sensor_ground.alpha = 0.5
        self.sensor_left_ground = TinySensor(self.res)
        self.sensor_middle_ground = TinySensor(self.res)
        self.sensor_right_ground = TinySensor(self.res)
        self.sensors = [self.sensor_bottom,
                        self.sensor_top,
                        self.sensor_left,
                        self.sensor_right,
                        self.sensor_ground,
                        self.sensor_left_ground,
                        self.sensor_middle_ground,
                        self.sensor_right_ground]

        # Values according to the Sonic Physics Guide
        
        self.acc = 0.046875 * const.SCALE
        self.frc = 0.046875 * const.SCALE
        self.rfrc = 0.0234375 * const.SCALE
        self.dec = 0.5 * const.SCALE
        self.max = 6.0 * const.SCALE

        self.rol = 1.03125 * const.SCALE

        self.slp = 0.125 * const.SCALE
        self.ruslp = 0.078125 * const.SCALE
        self.rdslp = 0.3125 * const.SCALE

        self.air = 0.09375 * const.SCALE
        self.grv = 0.21875 * const.SCALE
        self.maxg = 16.0 * const.SCALE

        self.drg = 0.96875

        self.jmp = 6.5 * const.SCALE
        self.jmpweak = 4.0 * const.SCALE

        # Flags

        self.flagGround = False
        self.flagAllowJump = False
        self.flagAllowHorizMovement = True
        self.flagAllowVertMovement = True
        self.flagJumpNextFrame = False
        self.flagFellOffWallOrCeiling = False

        # Trig

        self.angle = 0.0
        self.gangle = 0.0
        self.rangle = 0.0

        # Movement (dh = horizontal, dv = vertical.)
        # These can be rotated using angle and gangle above.

        self.dh = 0.0
        self.dv = 0.0
        
        self.keyUp = False
        self.keyDown = False
        self.keyLeft = False
        self.keyRight = False
        self.keyJump = False

        # Control lock timers
        self.hlock = 0

    def play_sound(self, string):
        self.res.sound_dict[string].play()

    def release_jump(self):
        self.dv = min(self.dv, self.jmpweak)
    
    def set_position(self, x = None, y = None):
        x = x or self.x
        y = y or self.y
        self.x = x
        self.y = y
        self.sprite.x = int(x)
        self.sprite.y = int(y)
        self.update_sensors()

    def handle_physics(self):
        # See if rolling
        if (abs(self.dh) >= self.rol):
            if (self.keyDown and self.flagGround and self.state != "Rolling"):
                self.state = "Rolling"
                self.play_sound('spin.wav')
                print "Rolling!"
        
        # Slope factor
        if self.state != "Rolling":
            if self.flagGround:
                self.dh -= self.slp * sin(self.rangle)
        else:
            if cmp(self.dh,0) == cmp(sin(self.rangle),0):
                self.dh -= self.ruslp * sin(self.rangle)
            elif cmp(self.dh,0) == -cmp(sin(self.rangle),0) or self.dh == 0:
                self.dh -= self.rdslp * sin(self.rangle)

        # Falling off walls and ceilings
        if 45 < self.rangle < 315 and abs(self.dh) < 2.5 * const.SCALE:
            self.set_gravity(self.gangle)
            self.flagFellOffWallOrCeiling = True

        if self.state == "Rolling" and self.dh == 0 and not self.keyDown:
            self.state = "Stopped"
            print "Stopped!"
        
        # Speed input and management
        if self.flagAllowHorizMovement:
            if self.keyLeft:
                if self.dh > 0 and self.flagGround:
                    # Decelerate
                    self.dh -= self.dec
                    if -self.dec < self.dh < 0:
                        self.dh = -self.dec
                elif self.dh > -self.max and self.state != "Rolling":
                    # Accelerate
                    if self.flagGround:
                        self.dh = max(self.dh - self.acc, -self.max)
                    else:
                        self.dh = max(self.dh - self.air, -self.max)
            elif self.keyRight:
                if self.dh < 0 and self.flagGround:
                    # Decelerate
                    self.dh += self.dec
                    if 0 < self.dh < self.dec:
                        self.dh = self.dec
                elif self.dh < self.max and self.state != "Rolling":
                    # Accelerate
                    if self.flagGround:
                        self.dh = min(self.dh + self.acc, self.max)
                    else:
                        self.dh = min(self.dh + self.air, self.max)
            elif not self.keyLeft and not self.keyRight and self.flagGround:
                if self.state == "Rolling":
                    self.dh -= min(abs(self.dh), self.rfrc) * cmp(self.dh,0)
                else:
                    self.dh -= min(abs(self.dh), self.frc) * cmp(self.dh,0)

        #Air Drag
        if 0 < self.dv < 4*const.SCALE and abs(self.dh) >= 0.125:
            self.dh *= self.drg

        #Jumping
        if self.flagJumpNextFrame:
            self.play_sound('jump.wav')
            self.dv = self.jmp
            self.set_gravity(self.gangle)
            self.state = "Jumping"
            self.flagGround = False
            self.flagAllowJump = False
            self.flagJumpNextFrame = False
        if self.keyJump and self.flagGround and self.flagAllowJump:
            self.flagJumpNextFrame = True

    def calculate_state(self):
        if self.flagGround:
            if self.state != "Rolling":
                if self.dh == 0:
                    self.state = "Stopped"
                elif 0 < abs(self.dh) < self.max:
                    self.state = "Walking"
                elif abs(self.dh) >= self.max:
                    self.state = "Running"
        else:
            if self.state != "Jumping":
                self.state = "Falling"

    def set_gravity(self, gangle):
        _ = rotate_basis(self.dh, self.dv, self.angle, gangle)
        self.dh = _[0]
        self.dv = _[1]
        self.angle = self.gangle = gangle

    def update_sensors(self):
        self.sensor_top.x = int(self.x) - sin(self.angle) * (
            self.height/2.0 - self.sensor_bottom.height/2.0)
        self.sensor_top.y = int(self.y) + cos(self.angle) * (
            self.height/2.0 - self.sensor_bottom.height/2.0)

        self.sensor_bottom.x = int(self.x) + sin(self.angle) * (
            self.height/2.0 - self.sensor_top.height/2.0)
        self.sensor_bottom.y = int(self.y) - cos(self.angle) * (
            self.height/2.0 - self.sensor_top.height/2.0)

        self.sensor_left.x = int(self.x) - cos(self.angle) * (
            self.width/2.0 - self.sensor_left.width/2.0)
        self.sensor_left.y = int(self.y) - sin(self.angle) * (
            self.width/2.0 - self.sensor_left.width/2.0)

        self.sensor_right.x = int(self.x) + cos(self.angle) * (
            self.width/2.0 - self.sensor_left.width/2.0)
        self.sensor_right.y = int(self.y) + sin(self.angle) * (
            self.width/2.0 - self.sensor_left.width/2.0)

        self.sensor_ground.x = int(self.x) + sin(self.angle) * (
            self.height/2.0 + self.sensor_ground.height/2.0)
        self.sensor_ground.y = int(self.y) - cos(self.angle) * (
            self.height/2.0 + self.sensor_ground.height/2.0)

    def calculate_rangle(self):
        self.rangle = (self.angle - self.gangle)

    def calculate_angle(self, colliding_line):
        if colliding_line is None:
            return self.gangle
        
        x1, y1, x2, y2 = colliding_line
        return direction(x2, y2, x1, y1)

    def perform_speed_movement(self, dt):
        leftcollided = False
        rightcollided = False
        for i in range(0, int(const.FAILSAFE_AMOUNT)):
            self.x += cos(self.angle) * self.dh/const.FAILSAFE_AMOUNT * dt
            self.y += sin(self.angle) * self.dh/const.FAILSAFE_AMOUNT * dt
            self.update_sensors()
            for platform in self.game.platforms:
                colliding_line = None
                '''
                if self.sensor_bottom.collide(platform):
                    # first, try see if it's a small slope
                    for _ in xrange(const.SLOPE_TEST):
                        self.y += 1
                        self.update_sensors()
                        if not self.sensor_bottom.collide(platform):
                            break
                '''
                if (self.sensor_left.collide(platform) or 
                self.sensor_right.collide(platform)):
                    self.update_sensors()
                    while self.sensor_left.collide(platform):
                        colliding_line = self.sensor_left.collide(platform)[1]
                        leftcollided = True
                        self.x += cos(self.angle)
                        self.y += sin(self.angle)
                        self.update_sensors()
                    while self.sensor_right.collide(platform):
                        colliding_line = self.sensor_right.collide(platform)[1]
                        rightcollided = True
                        self.x -= cos(self.angle)
                        self.y -= sin(self.angle)
                        self.update_sensors()

                while self.sensor_bottom.collide(platform):
                    colliding_line = self.sensor_bottom.collide(platform)[1]
                    self.y += cos(self.angle)
                    self.x -= sin(self.angle)
                    self.update_sensors()
            if leftcollided:
                if not self.flagGround:
                    self.catch_left_wall(colliding_line)
                else:
                    self.dh = 0
                break
            elif rightcollided:
                if not self.flagGround:
                    self.catch_right_wall(colliding_line)
                else:
                    self.dh = 0
                break
            elif colliding_line is not None:
                self.angle = self.calculate_angle(colliding_line)
        self.sprite.x = int(self.x)
        self.sprite.y = int(self.y)

    def perform_gravity_movement(self, dt):
        self.dv = max(self.dv - self.grv, -self.maxg)
        collided = False
        colliding_line = None
        
        y_speed = cos(self.angle)
        x_speed = sin(self.angle)
        
        # Failsafe movement
        for i in range(0, int(const.FAILSAFE_AMOUNT)):
            self.y += y_speed * (self.dv/const.FAILSAFE_AMOUNT) * dt
            self.x -= x_speed * (self.dv/const.FAILSAFE_AMOUNT) * dt
            self.update_sensors()
            for platform in self.game.platforms:
                while self.sensor_bottom.collide(platform):
                    if self.dv < 0:
                        collided = True
                        colliding_line = self.sensor_bottom.collide(platform)[1]
                    self.y += y_speed
                    self.x -= x_speed
                    self.update_sensors()
                while self.sensor_top.collide(platform):
                    if self.dv > 0:
                        collided = True
                        colliding_line = self.sensor_top.collide(platform)[1]
                    self.y -= y_speed
                    self.x += x_speed
                    self.update_sensors()
            if collided:
                if self.dv < 0:
                    self.flagGround = True
                    if self.flagFellOffWallOrCeiling:
                        self.set_hlock(30)
                        self.flagFellOffWallOrCeiling = False
                    self.angle = self.calculate_angle(colliding_line)
                    self.perform_landing_movement(colliding_line)
                elif self.dv > 0:
                    self.catch_ceiling(colliding_line)
                self.dv = 0
                break
        self.sprite.x = self.x = int(self.x)
        self.sprite.y = self.y = int(self.y)

    def perform_landing_movement(self, colliding_line):
        rangle = (self.calculate_angle(colliding_line) - self.gangle)
        if 0 <= rangle < 22.5 or 337.5 < rangle < 360:
            "Nothing extra happens!"
        elif 22.5 <= rangle < 45 or 315 < rangle <= 337.5:
            if abs(self.dh) > abs(self.dv):
                "Nothing extra happens!"
            else:
                self.dh = -self.dv * 0.5 * cmp(cos(rangle),1)
        elif 45 <= rangle < 90 or 270 < rangle <= 315:
            if abs(self.dh) > abs(self.dv):
                "Nothing extra happens!"
            else:
                self.dh = -self.dv * cmp(cos(rangle),1)

    def catch_ceiling(self, colliding_line):
        rangle = (self.calculate_angle(colliding_line) - self.gangle) % 360
        if 135 <= rangle <= 225:
            "Nothing extra happens!"
        if 90 <= rangle < 135 or 225 < rangle <= 270:
            self.flagGround = True
            self.angle = rangle
            self.calculate_rangle()

    def catch_left_wall(self, colliding_line):
        rangle = (self.calculate_angle(colliding_line) - self.gangle) % 360
        if 90 < rangle < 135:
            self.flagGround = True
            self.angle = rangle
            self.calculate_rangle()
        else:
            self.dh = 0

    def catch_right_wall(self, colliding_line):
        rangle = (self.calculate_angle(colliding_line) - self.gangle)
        if 225 < rangle < 270:
            self.flagGround = True
            self.angle = rangle
            self.calculate_rangle()
        else:
            self.dh = 0

    def perform_ground_test(self):
        for platform in self.game.platforms:
            collision = self.sensor_ground.collide(platform)
            if collision is not None:
                angle = self.calculate_angle(collision[1])
                if angle != self.angle and angle != 90:
                    return False
                return True
        self.flagGround = False
        self.set_gravity(self.gangle)
        return False

    def update(self, dt):
        self.update_lock_timers(dt)
        if self.flagGround:
            if not self.keyJump:
                self.flagAllowJump = True
        else:
            # I'd like to have the sensors be invisible when not on the ground.
            '''
            self.sensor_left_ground.visible = False
            self.sensor_middle_ground.visible = False
            self.sensor_right_ground.visible = False
            '''
        self.handle_physics()
        self.perform_speed_movement(dt)
        if not self.flagGround or not self.perform_ground_test():
            self.perform_gravity_movement(dt)
        self.calculate_state()
        self.calculate_rangle()

    def update_lock_timers(self, dt):
        if self.hlock > 0:
            self.hlock = max(self.hlock - 60 * dt, 0)
        if self.hlock == 0:
            self.flagAllowHorizMovement = True

    def set_hlock(self, frames):
        self.hlock = frames
        self.flagAllowHorizMovement = False
    
    def draw(self):
        self.sprite.render()

    def key_press(self, message):
        if message == 'up':
            self.keyUp = True
        elif message == 'down':
            self.keyDown = True
        elif message == 'left':
            self.keyLeft = True
        elif message == 'right':
            self.keyRight = True
        elif message == 'jump':
            self.keyJump = True
        elif message == 'reset':
            self.set_position(100,96)
            self.dv = 0
            self.dh = 0
            self.set_gravity(0.0)
        elif message == 'gdown':
            self.set_gravity(0.0)
        elif message == 'gright':
            self.set_gravity(90.0)
        elif message == 'gup':
            self.set_gravity(180.0)
        elif message == 'gleft':
            self.set_gravity(270.0)
        elif message == 'hlock':
            self.set_hlock(30)

    def key_release(self, message):
        if message == 'up':
            self.keyUp = False
        elif message == 'down':
            self.keyDown = False
        elif message == 'left':
            self.keyLeft = False
        elif message == 'right':
            self.keyRight = False
        elif message == 'jump':
            self.keyJump = False
            self.release_jump()