extends KinematicBody2D const ArrowProjectile = preload("res://objects/player/arrow_projectile.tscn") ##CLEAN UP CODE LATER ##Movement export var use_iframes = false export var walk_speed = 50 export var gravity = 12 export var max_fall_speed = INF export var jump_time = 15 export var jump_force = 150 export var doublejump_force = 122 ##Children onready var sprite = $Sprite onready var climb_ray = $ClimbRay onready var anims = $AnimationPlayer onready var sword_sprite = $SwordSprite onready var sword_hitbox = $SwordArea onready var death_particles = $DeathSplatter onready var dust_particles = $DustParticles onready var iframe_timer = $IframeTimer #Map onready var map = get_owner() ##States enum State {IDLE,WALK,JUMP,FALL,STUNNED,CLIMB,SWORD,SHOOT,INACTIVE,TRANSPORT} var current_state = State.IDLE var can_die = true ##Runtime var axis = Vector2.ZERO #Current direction being held var trail_color = Color(0.25,0,1,0.4) #Physics var velocity = Vector2.ZERO var current_ladder = null #Used for checking climbing every frame instead of area entered var jump_pressure = 0 var can_doublejump = true var can_move_in_air = false var transport_speed = 0.0 var transport_direction = Vector2.ZERO #Positions var arrowpos = Vector2(5,3) ##Preload #Set initial respawn point func _ready(): Game.respawn_point = global_position func _physics_process(delta): if current_state == State.INACTIVE: return axis = Vector2(Input.get_axis("ui_left","ui_right"),Input.get_axis("ui_up","ui_down")) #Check ladder check_ladder() match current_state: State.IDLE: _process_idle() continue State.WALK: _process_walk() continue State.IDLE, State.WALK: _process_idle_walk() continue State.JUMP: _process_jump() continue State.FALL: _process_fall() continue State.JUMP, State.FALL: _process_jump_fall() continue State.CLIMB: _process_climb() continue State.SWORD: _process_sword() continue State.SHOOT: _process_shoot() continue State.TRANSPORT: _process_transport(delta) return #Gravity if current_state != State.CLIMB: velocity.y = min(velocity.y + gravity, max_fall_speed) #Cut y velocity when hitting ceiling if is_on_ceiling(): current_state = State.FALL position.y += 1 velocity.y = 0 #Apply velocity move_and_slide(velocity,Vector2.UP) #Moon Jump if Debug.moon_jump == true: can_doublejump = true func _process_idle(): if anims.get_current_animation() != "idle": anims.play("idle") #Stop velocity.x = 0 #Goto Walk if axis.x != 0: current_state = State.WALK func _process_walk(): if anims.get_current_animation() != "walk": anims.play("walk") #Move move(walk_speed,0,true) #Goto Idle if axis.x == 0: current_state = State.IDLE #Push Blocks for i in get_slide_count(): var collision = get_slide_collision(i) if collision.get_collider().is_in_group("pushable"): collision.get_collider().push(collision.normal) func _process_idle_walk(): can_doublejump = false can_move_in_air = false velocity.y = 0 #Goto Fall if !is_on_floor(): current_state = State.FALL #Goto Jump check_jump() #Goto Sword if Debug.allow_sword && Input.is_action_just_pressed("sword"): Audio.play_sound(Audio.a_sword,Audio.ac_jump) current_state = State.SWORD return #Goto Shoot check_shoot() func _process_jump(): jump_pressure += 1 #Pressure sensitive jump if jump_pressure >= jump_time or Input.is_action_just_released("jump"): velocity.y = -jump_force / 4 #velocity.y = 0 current_state = State.FALL func _process_fall(): if anims.get_current_animation() != "doublejump": anims.play("jump") #Return to idle if is_on_floor(): current_state = State.IDLE return #Cant move in air if !can_move_in_air: velocity.x = 0 func _process_jump_fall(): check_double_jump() move(walk_speed,0,true) #Goto Shoot check_shoot() func _process_climb(): can_move_in_air = true can_doublejump = true #Graphics anims.play("climb") anims.set_speed_scale(abs(axis.y)) #Climb position.y += axis.y * 0.65 #Sound if axis.y == -1: if Audio.ac_climb.get_stream() != Audio.a_climb_up: Audio.play_sound(Audio.a_climb_up,Audio.ac_climb) if axis.y == 1: if Audio.ac_climb.get_stream() != Audio.a_climb_down: Audio.play_sound(Audio.a_climb_down,Audio.ac_climb) if axis.y == 0: Audio.ac_climb.set_stream(null) #Manual Jump,, only works when holding neutral or away from ladder if axis.x != sprite.scale.x && Input.is_action_just_pressed("jump"): position.x -= sprite.scale.x * 3 velocity.y = -jump_force anims.set_speed_scale(1) current_state = State.FALL Audio.ac_climb.set_stream(null) return if climb_ray.get_collider() == null: if axis.y == -1: #Auto Jump velocity.y = -jump_force Audio.play_sound(Audio.a_jump,Audio.ac_jump) #Auto dismount Audio.ac_climb.set_stream(null) current_state = State.FALL return #Side dismount if axis.x != sprite.scale.x && Input.is_action_just_pressed("shoot"): position.x -= sprite.scale.x * 3 current_state = State.FALL anims.set_speed_scale(1) Audio.ac_climb.set_stream(null) return func _process_sword(): anims.play("stab") #Stop velocity.x = 0 sword_sprite.scale.x = sprite.scale.x #Move hitbox with flip sword_hitbox.position.x = sprite.scale.x * 10 #Return to idle after animationplayer end anim signal func _process_shoot(): #Stop velocity.x = 0 if anims.get_current_animation() == "shoot air": #Cancel air shoot animation when grounded if is_on_floor(): current_state = State.IDLE return move(walk_speed,0,true) func _process_transport(delta): position += transport_direction * transport_speed * delta func spawn_arrow(): Audio.play_sound(Audio.a_shoot,Audio.ac_jump) var arrow = ArrowProjectile.instance() arrow.global_position = Vector2( global_position.x + arrowpos.x * sprite.scale.x, global_position.y + arrowpos.y ) arrow.direction = sprite.scale.x arrow.add_to_group("player_arrow") map.add_child(arrow) func check_jump(): if Input.is_action_just_pressed("jump"): #Detach ladder if current_state == State.CLIMB: Audio.ac_climb.set_stream(null) # stop climb sound position.x -= sprite.scale.x * 3 else: dust_particles.restart() anims.set_speed_scale(1) # Jump can_doublejump = true can_move_in_air = true velocity.y = 0 jump_pressure = 0 current_state = State.JUMP Audio.play_sound(Audio.a_jump,Audio.ac_jump) anims.play("jump") velocity.y = -jump_force move(walk_speed,0,true) func check_double_jump(): if is_on_floor(): check_jump() if Input.is_action_just_pressed("jump") && can_doublejump: Audio.play_sound(Audio.a_doublejump,Audio.ac_jump) can_doublejump = false velocity.y = -doublejump_force anims.play("doublejump") func check_shoot(): #Only Shoot if have arrows and there are no arrows onscreen if Input.is_action_just_pressed("shoot") && Game.arrows > 0 && get_tree().get_nodes_in_group("player_arrow").size() == 0: current_state = State.SHOOT if is_on_floor(): anims.play("shoot grounded") else: anims.play("shoot air") #Shoot immediately in air func move(hsp,vsp,flip:bool): if is_on_floor() or can_move_in_air: velocity.x = hsp * axis.x #Flip if flip: if sign(axis.x) != 0: sprite.scale.x = axis.x func check_ladder(): if climb_ray.get_collider() != null: current_ladder = climb_ray.get_collider().get_parent() #Stop the velocity velocity = Vector2.ZERO #Snap to closest side if position.x < current_ladder.middle: position.x = current_ladder.left_snap.global_position.x sprite.scale.x = 1 else: position.x = current_ladder.right_snap.global_position.x sprite.scale.x = -1 #Start Climbing current_state = State.CLIMB #Move the raycast #climb_ray.position.x = 4 * sprite.scale.x func enter_transport(speed, direction): transport_speed = speed transport_direction = direction current_state = State.TRANSPORT can_doublejump = false anims.play("doublejump") func exit_transport(): current_state = State.FALL func die(): if can_die: Audio.ac_climb.set_stream(null) # stop climbing sound\ #If the player is already dead, don't kill them again if current_state == State.INACTIVE: return #Create particles var new_particles = death_particles.duplicate() get_parent().add_child(new_particles) new_particles.global_position = global_position new_particles.emitting = true sprite.visible = false current_state = State.INACTIVE # Set to state where player is not controllable position = Game.respawn_point # Set respawn point if Game.lives <= 0 && Game.use_lives: #Gover #Particles new_particles.amount = 64 new_particles.lifetime = 0.45 new_particles.speed_scale = 1.5 current_state = State.INACTIVE # Set to state where player is not controllable Audio.play_sound(Audio.a_gover, Audio.ac_die) #Slow down time var time_tween = get_tree().create_tween() time_tween.tween_property(Engine, "time_scale", 0.1, 0.3) Audio.ac_music.stream_paused = true yield(time_tween, "finished") #Resume from freeze frame yield(get_tree().create_timer(1.0 * 0.1), "timeout") Game.call_deferred("restart_level") else: #Die Game.lives -= 1 Game.deaths += 1 Audio.play_sound(Audio.a_die, Audio.ac_die) yield(Game.freeze_frame(0.3), "timeout") #Reduce points if playing in infinite lives mode if Game.use_lives == false && Game.lives < 0: Game.score = max(0,Game.score - 500) #Iframes after respawn if use_iframes: iframe_timer.start() can_die = false #Respawn player current_state = State.IDLE sprite.visible = true func _on_AnimationPlayer_animation_finished(anim_name): if current_state == State.INACTIVE: return #Return to idle after slash if anim_name == "stab": current_state = State.IDLE return #Return to idle after grounded shoot if anim_name == "shoot grounded": current_state = State.IDLE return #Return to fall or idle after air shoot if anim_name == "shoot air": if is_on_floor(): current_state = State.IDLE return else: current_state = State.FALL return func _on_SwordArea_area_entered(area): if area.is_in_group("enemy_hitbox"): var target = area.get_parent() # create block text and return if blocked if area.is_in_group("blocks_sword"): var pos = target.global_position Game.instance_node(Game.block_text, pos.x, pos.y, target.get_parent()) return else: target.die() func _on_Area2D_body_entered(body): if body.is_in_group("death"): die() func _on_IframeTimer_timeout(): can_die = true