diff options
Diffstat (limited to 'player.gd')
-rw-r--r-- | player.gd | 464 |
1 files changed, 372 insertions, 92 deletions
@@ -3,51 +3,81 @@ extends CharacterBody2D signal hp_changed +signal inhale_finished -const MAX_SPEED = 80.0 +const MAX_SPEED = 70.0 const ACCELERATION = MAX_SPEED * 0.1 const DECELERATION = ACCELERATION const JUMP_VELOCITY = -200.0 const MAX_AIR_SPEED = MAX_SPEED * 0.75 const FLY_VELOCITY = -80 const HURT_VELOCITY = MAX_SPEED * 5 +const MAX_WATER_SPEED = MAX_SPEED * 0.5 const MAX_GRAVITY = 300.0 const MAX_GRAVITY_AIR = 100.0 +const MAX_GRAVITY_WATER = 50.0 var previous_velocity: Vector2 = Vector2.ZERO var jump_height_modifier: float var last_direction: float = 1.0 var max_gravity: float = MAX_GRAVITY +var max_up_velocity: float = MAX_GRAVITY * 2 +var previous_state: Dictionary = state_idle() var current_state: Dictionary = state_idle(): set(new_state): - if current_state.id != new_state.id: - current_state.exit.call() - new_state.enter.call() - current_state = new_state + if current_state.id != "knockout": # final state + if current_state.id != new_state.id: + previous_state = current_state + current_state.exit.call() + new_state.enter.call() + current_state = new_state var max_hp = 6 var hp = max_hp : set(value): hp = clamp(value, 0, max_hp) hp_changed.emit() -var sprite_small_star_base: Sprite2D +var sprite_small_star_base: PackedScene var air_shot_base: Area2D var star_shot_base: Area2D +var is_inhaling = false +var is_flying = false : + set(value): + is_flying = value + set_collision_mask_value(8, value) var is_inflated = false var inhale_min_duration = 0.5 var does_inhale_transition = false +var invincibility_duration = 0.6 +var is_invincible = false +var did_wall_bounce = false var entered_door: Node2D +var blink_tween: Tween + +var visual_position: Vector2 : + get(): + return Vector2(0, 0) + set(value): + position = value + Vector2(0, 0) + +var visual_global_position: Vector2 : + get(): + return global_position + Vector2(0, 0) + set(value): + global_position = value - Vector2(0, 0) + +@onready var navigation_shape: CollisionShape2D = $NavigationShape + func _ready(): current_state = state_idle() - sprite_small_star_base = $SpriteSmallStar.duplicate() - $SpriteSmallStar.queue_free() + sprite_small_star_base = preload("res://effect_star.tscn") air_shot_base = $AirShot.duplicate() $AirShot.queue_free() @@ -55,16 +85,16 @@ func _ready(): star_shot_base = $StarShot.duplicate() $StarShot.queue_free() - hp = max_hp # to set UI labels - - $CollisionArea.add_child($CollisionShape2D.duplicate()) + $InvincibilityTimer.wait_time = invincibility_duration func _physics_process(delta: float) -> void: # Add the gravity. if not is_on_floor(): velocity += get_gravity() * delta - velocity.y = min(velocity.y, max_gravity) + if is_flying: + velocity -= (get_gravity() * delta) / 1.5 + velocity.y = clamp(velocity.y, -max_up_velocity, max_gravity) previous_velocity = velocity current_state.process.call(delta) @@ -78,7 +108,7 @@ func apply_directional_movement(max_speed = MAX_SPEED): else: $AnimatedSprite2D.flip_h = false - if sign(direction) != sign(last_direction): + if sign(direction) != sign(last_direction): # no slidey-ness velocity.x = 0 velocity.x = move_toward(velocity.x, direction * max_speed, ACCELERATION) @@ -98,8 +128,8 @@ func apply_jump_height_modification(): func spawn_small_star(): - var star := sprite_small_star_base.duplicate() - star.global_position = global_position + var star := sprite_small_star_base.instantiate() + star.global_position = visual_global_position get_tree().current_scene.add_child(star) var tween = get_tree().create_tween() var target_x = randi_range(-1, 1) * 16 @@ -117,13 +147,28 @@ func spawn_small_star(): func air_shot(direction: float): var shot = air_shot_base.duplicate() as Area2D - shot.global_position = global_position + shot.global_position = visual_global_position get_tree().current_scene.add_child(shot) %SoundAirShot.play() shot.get_node("Sprite2D").flip_h = direction < 0 var tween = get_tree().create_tween() + + var shot_hit = func(node): + Game.score += 400 + %ShotObstacleHit.play() + tween.stop() + + if node.has_method("hit_by_projectile"): + node.hit_by_projectile(shot.global_position) + else: + Game.hit_enemy(node, shot.global_position) + + shot.queue_free() + shot.body_entered.connect(shot_hit) + shot.area_entered.connect(shot_hit) + tween.tween_property( shot, "global_position", @@ -132,12 +177,13 @@ func air_shot(direction: float): ).set_ease(Tween.EASE_OUT) tween.tween_callback(func(): await get_tree().create_timer(0.2).timeout - shot.queue_free() + if is_instance_valid(shot): + shot.queue_free() ) func star_shot(direction: float): var shot = star_shot_base.duplicate() as Area2D - shot.global_position = global_position + shot.global_position = visual_global_position get_tree().current_scene.add_child(shot) %SoundStarShot.play() @@ -145,13 +191,19 @@ func star_shot(direction: float): var tween = get_tree().create_tween() - shot.body_entered.connect(func(body): - Game.score += randi_range(100, 1000) + var shot_hit = func(node): + Game.score += 400 %ShotObstacleHit.play() tween.stop() + + if node.has_method("hit_by_projectile"): + node.hit_by_projectile(shot.global_position) + else: + Game.hit_enemy(node, shot.global_position) + shot.queue_free() - body.queue_free() - ) + shot.body_entered.connect(shot_hit) + shot.area_entered.connect(shot_hit) shot.get_node("StarShotVisibleNotifier").screen_exited.connect(func(): if is_instance_valid(shot): @@ -182,12 +234,73 @@ func check_door_enter(): return null +func start_blink(): + blink_tween = get_tree().create_tween() + blink_tween.tween_property($AnimatedSprite2D, "self_modulate", Color(1, 1, 1, 0.2), 0.1) + blink_tween.tween_property($AnimatedSprite2D, "self_modulate", Color(1, 1, 1, 0.8), 0.1) + + await blink_tween.finished + start_blink() + +func stop_blink(): + blink_tween.stop() + $AnimatedSprite2D.self_modulate = Color(1, 1, 1, 1) + + +func inhale(node: CollisionObject2D): + node.collision_layer = 0 + + var position_difference = node.global_position - global_position + if position_difference.length() > 25: + var tween = get_tree().create_tween() + tween.tween_property( + node, + "global_position", + global_position + (Vector2(1, 0) * 16 * sign(last_direction)), + (node.global_position - global_position).normalized().length() * 0.25 + ) + await tween.finished + else: + await get_tree().create_timer(0.1).timeout + + node.queue_free() + + Game.score += 200 + + inhale_finished.emit() + + +func wall_bounce_check(): + return abs(previous_velocity.x) > 0 and is_on_wall() + #if not did_wall_bounce and abs(previous_velocity.x) > 0 and is_on_wall(): + #%SoundFall.play() + #spawn_small_star() + #$AnimationPlayer.play("wall_bounce") + #did_wall_bounce = true + #get_tree().create_timer(0.1).timeout.connect(func(): + #did_wall_bounce = false + #) + # + #return true + # + #return false + + func _on_hurt_area_body_entered(body: Node2D) -> void: if body.is_in_group("enemy"): - current_state.id = "" # to force trigger state change into hurt - current_state = state_hurt({"collider": body}) + if is_invincible: + %ShotObstacleHit.play() + body.queue_free() + else: + current_state.id = "" # to force trigger state change into hurt + current_state = state_hurt({"collider": body}) elif body is TileMapLayer: - current_state = state_ko() + current_state = state_knockout() + + +func _on_invincibility_timer_timeout() -> void: + stop_blink() + is_invincible = false func state_idle(): @@ -195,14 +308,16 @@ func state_idle(): "id": "idle", "enter": func(): is_inflated = false - $AnimatedSprite2D.play("idle") + $AnimationPlayer.play("idle") , "process": func(_delta): velocity.x = move_toward(velocity.x, 0, DECELERATION) move_and_slide() var direction = Input.get_axis("ui_left", "ui_right") - if direction: + if (direction and + ((direction == -1 and not $WallDetection/Left.is_colliding()) or + (direction == 1 and not $WallDetection/Right.is_colliding()))): current_state = state_walk() if check_door_enter(): @@ -229,7 +344,7 @@ func state_walk(): return { "id": "walk", "enter": func(): - $AnimatedSprite2D.play("walk") + $AnimationPlayer.play("walk") , "process": func(_delta): var direction = apply_directional_movement() @@ -241,6 +356,9 @@ func state_walk(): if velocity.x == 0: current_state = state_idle() + if wall_bounce_check(): + current_state = state_wall_bounce(state_idle()) + if check_door_enter(): current_state = state_enter_door() elif Input.is_action_just_pressed("jump"): @@ -267,7 +385,7 @@ func state_jump(): "id": "jump", "enter": func(): %SoundJump.play() - $AnimatedSprite2D.play("jump") + $AnimationPlayer.play("jump") velocity.y = JUMP_VELOCITY jump_height_modifier = JUMP_VELOCITY , @@ -276,6 +394,9 @@ func state_jump(): apply_directional_movement() move_and_slide() + if wall_bounce_check(): + current_state = state_wall_bounce(state_fall()) + if Input.is_action_just_pressed("jump"): velocity.y = FLY_VELOCITY %SoundFly.play() @@ -297,7 +418,7 @@ func state_fall(): return { "id": "fall", "enter": func(): - $AnimatedSprite2D.play("jump") + $AnimationPlayer.play("jump") , "process": func(_delta): if not $CoyoteTimer.is_stopped() and Input.is_action_just_pressed("jump"): @@ -311,22 +432,50 @@ func state_fall(): if Input.is_action_just_pressed("inhale_exhale"): current_state = state_inhale() - if velocity.y >= MAX_GRAVITY: + if velocity.y >= MAX_GRAVITY * 0.9: $AnimatedSprite2D.play("fall") + is_invincible = true apply_directional_movement(MAX_AIR_SPEED) move_and_slide() + if wall_bounce_check(): + current_state = state_wall_bounce(state_fall()) + if is_on_floor(): + spawn_small_star() if $AnimatedSprite2D.animation == "fall": %SoundFall2.play() + current_state = state_bump() else: %SoundFall.play() + current_state = state_idle() + , + "exit": func(): + pass + , + } + + +func state_bump(): + return { + "id": "bump", + "enter": func(): + $AnimatedSprite2D.play("fall") + velocity = Vector2(sign(last_direction), -1) * clamp(velocity.x * 1.5, MAX_SPEED * 0.75, MAX_SPEED) + , + "process": func(_delta): + move_and_slide() + + if is_on_floor(): + #velocity.x = 0 + %SoundFall.play() spawn_small_star() current_state = state_idle() , "exit": func(): - pass + if $InvincibilityTimer.is_stopped(): + is_invincible = false , } @@ -338,7 +487,7 @@ func state_duck(): $AnimatedSprite2D.play("duck") , "process": func(_delta): - if Input.is_action_just_released("duck"): + if not Input.is_action_pressed("duck"): current_state = state_idle() , "exit": func(): @@ -351,17 +500,16 @@ func state_fly_idle(): return { "id": "fly_idle", "enter": func(): + is_flying = true max_gravity = MAX_GRAVITY_AIR - $AnimatedSprite2D.play("fly") + $AnimationPlayer.play("fly") , - "process": func(delta): - velocity -= (get_gravity() * delta) / 1.5 - + "process": func(_delta): if check_door_enter(): current_state = state_enter_door() elif Input.is_action_just_pressed("jump"): velocity.y = FLY_VELOCITY - $AnimatedSprite2D.play("fly") + $AnimationPlayer.play("fly") %SoundFly.play() velocity.x = move_toward(velocity.x, 0, DECELERATION) @@ -373,9 +521,11 @@ func state_fly_idle(): if Input.is_action_just_pressed("inhale_exhale"): air_shot(last_direction) - current_state = state_idle() + current_state = state_fly_exhale() , "exit": func(): + $AnimationPlayer.play("RESET") + is_flying = false max_gravity = MAX_GRAVITY , } @@ -385,11 +535,12 @@ func state_fly_walk(): return { "id": "fly_walk", "enter": func(): + is_flying = true max_gravity = MAX_GRAVITY_AIR - $AnimatedSprite2D.play("fly") + $AnimationPlayer.play("fly") , - "process": func(delta): - velocity -= (get_gravity() * delta) / 1.5 + "process": func(_delta): + #velocity -= (get_gravity() * delta) / 1.5 velocity.y = min(velocity.y, 100) if check_door_enter(): @@ -400,48 +551,60 @@ func state_fly_walk(): var direction = apply_directional_movement(MAX_AIR_SPEED) if direction: - $AnimatedSprite2D.play("fly") + $AnimationPlayer.play("fly") move_and_slide() else: current_state = state_fly_idle() if Input.is_action_just_pressed("inhale_exhale"): air_shot(last_direction) - current_state = state_walk() + current_state = state_fly_exhale() , "exit": func(): + is_flying = false max_gravity = MAX_GRAVITY , } func state_fly_exhale(): - # todo: play animation, can't move during that, fly gravity applies? - # goto idle state? fall state? - return {} + return { + "id": "fly_exhale", + "enter": func(): + $AnimationPlayer.play("fly_exhale") + await $AnimationPlayer.animation_finished + current_state = state_idle() + , + "process": func(_delta): + move_and_slide() + , + "exit": func(): + pass + , + } func state_inhale(): return { "id": "inhale", "enter": func(): + is_inhaling = true + $InhaleMinDurationTimer.wait_time = inhale_min_duration $InhaleMinDurationTimer.start() - #$AnimationPlayer.play("inhale") - $AnimatedSprite2D.play("inhale") + $AnimationPlayer.play("inhale") %SoundInhale.play() %SoundInhale.finished.connect(func(): - #$SoundInhaleContinue.play() %SoundInhale.play() ) - %InhaleArea.position.x = abs(%InhaleArea.position.x) * sign(last_direction) + %InhaleArea.position.x = visual_position.x + (abs(visual_position.x - %InhaleArea.position.x) * sign(last_direction)) %InhaleParticles.process_material.gravity.x = - ( abs(%InhaleParticles.process_material.gravity.x) * sign(last_direction) ) - %InhaleParticles.position.x = abs(%InhaleParticles.position.x) * sign(last_direction) + %InhaleParticles.position.x = visual_position.x + (abs(visual_position.x - %InhaleParticles.position.x) * sign(last_direction)) %InhaleParticles.restart() %InhaleParticles.emitting = true , @@ -453,10 +616,9 @@ func state_inhale(): if bodies.size() > 0: does_inhale_transition = true for body in bodies: - body.queue_free() - Game.score += randi_range(10, 100) - # todo: stop process? -> remove collision -> inhale aka move towards center player -> queue_free - await get_tree().create_timer(inhale_min_duration).timeout + inhale(body) + await inhale_finished + await get_tree().create_timer(inhale_min_duration / 2).timeout current_state = state_inflated_idle() return @@ -467,6 +629,9 @@ func state_inhale(): current_state = state_idle() , "exit": func(): + #$AnimationTree.get("parameters/playback").travel("idle") + $AnimationPlayer.play("RESET") + is_inhaling = false %SoundInhale.stop() %InhaleParticles.emitting = false does_inhale_transition = false @@ -479,7 +644,7 @@ func state_inflated_idle(): "id": "inflated_idle", "enter": func(): is_inflated = true - $AnimatedSprite2D.play("inflated_idle") + $AnimationPlayer.play("inflated_idle") , "process": func(_delta): velocity.x = move_toward(velocity.x, 0, DECELERATION) @@ -495,15 +660,14 @@ func state_inflated_idle(): current_state = state_inflated_jump() if not is_on_floor(): - #current_state = state_inflated_fall() - current_state = state_inflated_jump() + current_state = state_inflated_fall() if Input.is_action_pressed("duck"): current_state = state_digest() if Input.is_action_just_pressed("inhale_exhale"): star_shot(last_direction) - current_state = state_idle() + current_state = state_inflated_exhale() , "exit": func(): is_inflated = false @@ -516,7 +680,7 @@ func state_inflated_walk(): "id": "inflated_walk", "enter": func(): is_inflated = true - $AnimatedSprite2D.play("inflated_walk") + $AnimationPlayer.play("inflated_walk") , "process": func(_delta): var direction = apply_directional_movement() @@ -539,7 +703,7 @@ func state_inflated_walk(): if Input.is_action_just_pressed("inhale_exhale"): star_shot(last_direction) - current_state = state_idle() + current_state = state_inflated_exhale() , "exit": func(): is_inflated = false @@ -564,7 +728,7 @@ func state_inflated_jump(): if Input.is_action_just_pressed("inhale_exhale"): star_shot(last_direction) - current_state = state_fall() + current_state = state_inflated_exhale() elif velocity.y >= 0: current_state = state_inflated_fall() @@ -588,7 +752,7 @@ func state_inflated_fall(): if Input.is_action_just_pressed("inhale_exhale"): star_shot(last_direction) - current_state = state_fall() + current_state = state_inflated_exhale() apply_directional_movement(MAX_AIR_SPEED) move_and_slide() @@ -604,6 +768,23 @@ func state_inflated_fall(): } +func state_inflated_exhale(): + return { + "id": "inflated_exhale", + "enter": func(): + $AnimationPlayer.play("inflated_exhale") + await $AnimationPlayer.animation_finished + current_state = state_idle() + , + "process": func(_delta): + move_and_slide() + , + "exit": func(): + pass + , + } + + func state_digest(): return { "id": "digest", @@ -611,10 +792,13 @@ func state_digest(): %SoundDigest.play() is_inflated = false hp += 1 - Game.score += randi_range(10, 100) + Game.score += 100 + $AnimationPlayer.play("digest") + await $AnimationPlayer.animation_finished + current_state = state_duck() , "process": func(_delta): - current_state = state_duck() + move_and_slide() # animation position adjustment , "exit": func(): pass @@ -626,12 +810,15 @@ func state_hurt(data): return { "id": "hurt", "enter": func(): + is_invincible = true + $InvincibilityTimer.start() + if data.collider.is_in_group("enemy"): data.collider.queue_free() hp -= 1 if hp == 0: - current_state = state_ko() + current_state = state_knockout() return else: %SoundHurt.play() @@ -641,25 +828,39 @@ func state_hurt(data): else: $AnimatedSprite2D.play("jump") - var impulse = sign(global_position - data.collider.global_position) * HURT_VELOCITY + var impulse = sign(visual_global_position - data.collider.global_position) * HURT_VELOCITY velocity.x = impulse.x velocity.y = -100 move_and_slide() velocity.x = 0 - var tween = get_tree().create_tween() - tween.tween_property($AnimatedSprite2D, "self_modulate", Color(1, 1, 1, 0.2), 0.1) - tween.tween_property($AnimatedSprite2D, "self_modulate", Color(1, 1, 1, 0.8), 0.1) - tween.tween_property($AnimatedSprite2D, "self_modulate", Color(1, 1, 1, 0.2), 0.1) - tween.tween_property($AnimatedSprite2D, "self_modulate", Color(1, 1, 1, 0.8), 0.1) - tween.tween_callback(func(): - $AnimatedSprite2D.self_modulate = Color(1, 1, 1, 1) - if is_inflated: - current_state = state_inflated_idle() - else: - current_state = state_idle() - ) + start_blink() + await get_tree().create_timer(0.4).timeout + if is_inflated: + current_state = state_inflated_idle() + else: + current_state = state_idle() + , + "process": func(_delta): + move_and_slide() + , + "exit": func(): + pass + , + } + + +func state_wall_bounce(return_state): + return { + "id": "wall_bounce", + "enter": func(): + %SoundFall.play() + spawn_small_star() + $AnimationPlayer.play("wall_bounce") + await $AnimationPlayer.animation_finished + + current_state = return_state , "process": func(_delta): move_and_slide() @@ -669,6 +870,68 @@ func state_hurt(data): , } + +func state_water_idle(): + return { + "id": "water_idle", + "enter": func(): + max_gravity = MAX_GRAVITY_WATER + $AnimationPlayer.play("walk") + , + "process": func(_delta): + if check_door_enter(): + current_state = state_enter_door() + elif Input.is_action_just_pressed("jump"): + %SoundJump.play() + velocity.y -= MAX_GRAVITY_WATER * 2 + + velocity.x = move_toward(velocity.x, 0, DECELERATION) + move_and_slide() + + if velocity.length() > 0: + $AnimationPlayer.play("walk") + else: + $AnimationPlayer.play("idle") + + var direction = Input.get_axis("ui_left", "ui_right") + if direction: + current_state = state_water_swim() + , + "exit": func(): + max_gravity = MAX_GRAVITY + , + } + + +func state_water_swim(): + return { + "id": "water_swim", + "enter": func(): + max_gravity = MAX_GRAVITY_WATER + $AnimationPlayer.play("walk") + , + "process": func(_delta): + if check_door_enter(): + current_state = state_enter_door() + elif Input.is_action_just_pressed("jump"): + %SoundJump.play() + velocity.y -= MAX_GRAVITY_WATER * 2 + + var direction = apply_directional_movement() + if direction: + move_and_slide() + else: + velocity.x = move_toward(velocity.x, 0, DECELERATION) + move_and_slide() + if velocity.x == 0: + current_state = state_water_idle() + , + "exit": func(): + max_gravity = MAX_GRAVITY + , + } + + func state_enter_door(): return { "id": "enter_door", @@ -705,45 +968,46 @@ func state_exit_door(): , } -func state_ko(): + +func state_knockout(): return { - "id": "ko", + "id": "knockout", "enter": func(): SoundManager.current_background.stop() - $AnimatedSprite2D.play("ko") + $AnimatedSprite2D.play("knockout") $AnimatedSprite2D.stop() $AnimatedSprite2D.set_frame_and_progress(0, 0) %SoundNoHp.play() await %SoundNoHp.finished - $AnimatedSprite2D.play("ko") + $AnimatedSprite2D.play("knockout") $Camera2D.drag_top_margin = 1.0 var tween = get_tree().create_tween() - var to_up_difference = global_position.y - 16 - var to_down_difference = global_position.y + get_viewport_rect().size.y + var to_up_difference = visual_global_position.y - 16 + var to_down_difference = visual_global_position.y + get_viewport_rect().size.y tween.tween_property( self, - "global_position", + "visual_global_position", Vector2( - global_position.x, + visual_global_position.x, to_up_difference ), (to_up_difference / to_down_difference) * 1.0 ) tween.tween_property( self, - "global_position", + "visual_global_position", Vector2( - global_position.x, + visual_global_position.x, to_down_difference ), 1.0 ) - %SoundKO.play() - await %SoundKO.finished + %SoundKnockout.play() + await %SoundKnockout.finished Game.transition_to_scene( get_tree().current_scene.scene_file_path.replace(".tscn", "Intro.tscn") @@ -756,3 +1020,19 @@ func state_ko(): pass , } + + +func _on_collision_area_body_entered(body: Node2D) -> void: + if body is TileMapLayer: + current_state = state_water_idle() + +func _on_collision_area_body_exited(body: Node2D) -> void: + if body is TileMapLayer: + (func(): + var bodies: Array = $CollisionArea.get_overlapping_bodies() + bodies = bodies.filter(func(value): + return value is TileMapLayer + ) + if bodies.is_empty(): + current_state = state_jump() + ).call_deferred() |