extends Player class_name DownhillAutoscrollerPlayer export var min_speed : float = 3 export var max_speed : float = 11 export var player_initiated_acceleration : float = 5 export var boost_per_second : float = 4 / 1.36 # 6 mph var last_contacted_map_elem_type : int = Constants.MapElemType.SQUARE var boost : float = 0 # to movement speed var boost_effect: float = 0 var instant_accel: bool = false var respawn_pos : Vector2 var hit_audiostream_player : AudioStreamPlayer var spin_audiostream_player : AudioStreamPlayer var jump_audiostream_player : AudioStreamPlayer var land_audiostream_player : AudioStreamPlayer var snow_audiostream_player : AudioStreamPlayer func init_unit_w_scene(scene): .init_unit_w_scene(scene) hit_audiostream_player = scene.get_node("HitAudioStreamPlayer") spin_audiostream_player = scene.get_node("SpinAudioStreamPlayer") jump_audiostream_player = scene.get_node("JumpAudioStreamPlayer") land_audiostream_player = scene.get_node("LandAudioStreamPlayer") snow_audiostream_player = scene.get_node("SnowAudioStreamPlayer") func execute_actions(delta): .execute_actions(delta) for action_num in Constants.UNIT_TYPE_ACTIONS[Constants.UnitType.PLAYER]: if !actions[action_num]: continue match action_num: # handle custom actions Constants.ActionType.SPIN: spin(delta) _: pass func spin(delta): if (get_current_action() != Constants.UnitCurrentAction.SPINNING): spin_audiostream_player.play() set_current_action(Constants.UnitCurrentAction.SPINNING) boost += boost_per_second * delta current_sprite.visible = false get_node("SpinningSprite").visible = true get_node("SpinningSprite").rotation += delta * 32 func move(): set_unit_condition(Constants.UnitCondition.MOVING_STATUS, Constants.UnitMovingStatus.MOVING) if (get_current_action() == Constants.UnitCurrentAction.IDLE and unit_conditions[Constants.UnitCondition.IS_ON_GROUND]): if last_contacted_map_elem_type == Constants.MapElemType.SQUARE: set_sprite(Constants.SpriteClass.WALK, 0) elif (last_contacted_map_elem_type == Constants.MapElemType.SMALL_SLOPE_RIGHT_1 or last_contacted_map_elem_type == Constants.MapElemType.SMALL_SLOPE_RIGHT_2): set_sprite(Constants.SpriteClass.WALK, 1) elif last_contacted_map_elem_type == Constants.MapElemType.SLOPE_RIGHT: set_sprite(Constants.SpriteClass.WALK, 2) elif (last_contacted_map_elem_type == Constants.MapElemType.SMALL_SLOPE_LEFT_1 or last_contacted_map_elem_type == Constants.MapElemType.SMALL_SLOPE_LEFT_2): set_sprite(Constants.SpriteClass.WALK, 3) elif last_contacted_map_elem_type == Constants.MapElemType.SLOPE_LEFT: set_sprite(Constants.SpriteClass.WALK, 4) func jump(): if get_current_action() != Constants.UnitCurrentAction.JUMPING: jump_audiostream_player.play() .jump() func reset_current_action(): .reset_current_action() if get_current_action() == Constants.UnitCurrentAction.SPINNING: if not actions[Constants.ActionType.SPIN]: set_current_action(Constants.UnitCurrentAction.IDLE) get_node("SpinningSprite").visible = false get_node("SpinningSprite").rotation = 0 .handle_idle() current_sprite.visible = true else: spin_audiostream_player.stop() func custom_inputs(): if scene.input_table[Constants.PlayerInput.GBA_B][scene.I_T_JUST_PRESSED]: if not get_condition(Constants.UnitCondition.IS_ON_GROUND, true): set_action(Constants.ActionType.SPIN) if (get_current_action() == Constants.UnitCurrentAction.SPINNING and scene.input_table[Constants.PlayerInput.GBA_B][scene.I_T_PRESSED]): set_action(Constants.ActionType.SPIN) func handle_idle(): if boost == 0: .handle_idle() func handle_speed(delta): if get_current_action() == Constants.UnitCurrentAction.RECOILING: target_move_speed = min_speed return if scene.input_table[Constants.PlayerInput.LEFT][scene.I_T_PRESSED]: target_move_speed = move_toward(target_move_speed, min_speed, player_initiated_acceleration * delta) boost_effect = 0.0 return var slope: int = Constants.ELEM_TYPE_SLOPE[last_contacted_map_elem_type] var speed_limit: float = Constants.SLOPE_SPEED[slope] var accel: float = player_initiated_acceleration if not get_condition(Constants.UnitCondition.IS_ON_GROUND, true): speed_limit = target_move_speed if scene.input_table[Constants.PlayerInput.RIGHT][scene.I_T_PRESSED] and speed_limit < Constants.UNIT_TYPE_MOVE_SPEEDS[unit_type]: speed_limit = Constants.UNIT_TYPE_MOVE_SPEEDS[unit_type] speed_limit = max(min_speed, speed_limit) if get_condition(Constants.UnitCondition.IS_ON_GROUND, true): if scene.input_table[Constants.PlayerInput.RIGHT][scene.I_T_PRESSED]: speed_limit += 1.0 if boost_effect > 0: speed_limit += boost_effect if target_move_speed < boost_effect: boost_effect = 0.0 elif slope < 2: boost_effect -= delta accel = (Constants.SLOPE_ACCEL if target_move_speed < speed_limit else Constants.SLOPE_DECEL)[slope] target_move_speed = move_toward(target_move_speed, speed_limit, accel * delta) if target_move_speed > speed_limit + 4.0: target_move_speed -= speed_limit target_move_speed *= pow(0.5, delta) target_move_speed += speed_limit func process_unit(delta, time_elapsed : float): # always be movin' facing = Constants.Direction.RIGHT actions[Constants.ActionType.MOVE] = true handle_speed(delta) if boost_effect > 0: boost_effect -= delta .process_unit(delta, time_elapsed) # treat all collisions as right-side collisions func hit(dir : int): # Unit.gd implementation override hit_queued = true hit_dir = Constants.Direction.RIGHT # Player.gd implementation set_unit_condition_with_timer(Constants.UnitCondition.IS_INVINCIBLE) start_flash() set_action(Constants.ActionType.RECOIL) set_current_action(Constants.UnitCurrentAction.RECOILING) set_unit_condition(Constants.UnitCondition.MOVING_STATUS, Constants.UnitMovingStatus.IDLE) get_node("SpinningSprite").visible = false get_node("SpinningSprite").rotation = 0 hit_audiostream_player.play() # override super class's RECOIL_PUSHBACK func handle_recoil(): if not hit_queued: return hit_queued = false # skip recoil pushback logic, since target_move_speed is already # set to min_speed func landed(): get_node("SpinningSprite").visible = false get_node("SpinningSprite").rotation = 0 land_audiostream_player.play() if get_current_action() == Constants.UnitCurrentAction.SPINNING: hit(Constants.Direction.RIGHT) boost = 0 spin_audiostream_player.stop() return if boost > 0: scene.find_node("CanvasLayer").flash_boost = true boost_effect += boost target_move_speed += boost boost = 0 func react(delta): .react(delta) # Check if fallen off if last_contacted_map_elem_type == Constants.MapElemType.OOB_LOWER: # Called when the player falls in a hole # Calculate respawn point var tilemap : TileMap = get_node("../Stage") var tile : Vector2 = pos.floor() tile.y *= -1 while tilemap.get_cellv(tile) == 22 or tilemap.get_cellv(tile) < 0: tile += Vector2.RIGHT while tilemap.get_cellv(tile) >= 0: tile += Vector2.UP respawn_pos = tile respawn_pos.x += 0.5 respawn_pos.y *= -1 last_contacted_map_elem_type = Constants.MapElemType.SLOPE_LEFT # ======================= # reset speed # should other things be done here? # - player's ongoing trick has to be reset # - camera has to freeze its y position as player drops offscreen # - a smooth transition has to play # - player starts off recoiling and slowed down (they are hit) var spectator_cam : Camera2D = get_node("../SpectatorCam") spectator_cam.position = get_node("Camera2D").get_camera_screen_center() spectator_cam.offset = get_node("Camera2D").offset spectator_cam.make_current() var anim_player : AnimationPlayer = scene.find_node("PitTransitionPlayer") anim_player.play("PitTransition") if get_condition(Constants.UnitCondition.IS_ON_GROUND, true) and h_speed > 3: if not snow_audiostream_player.playing: snow_audiostream_player.play() else: snow_audiostream_player.stop() func respawn_from_pit(): pos = respawn_pos #hit(Constants.Direction.RIGHT) set_unit_condition_with_timer(Constants.UnitCondition.IS_INVINCIBLE) start_flash() get_node("SpinningSprite").visible = false get_node("SpinningSprite").rotation = 0 hit_audiostream_player.play() boost = 0 boost_effect = 0 get_node("Camera2D").make_current()