Add player health, lives, death, and cutscene system
Added player damage, invulnerability frames, knockback, and death handling. Enemies stop attacking after player death and player controls are disabled. Added a persistent three-life system with ambulance cutscenes after the first two deaths and a final Game Over cutscene after the third death.
This commit is contained in:
parent
13f95562a7
commit
53fd05c165
BIN
Assets/Cut Scenes/Calendar Cutscene.mov
Normal file
BIN
Assets/Cut Scenes/Calendar Cutscene.mov
Normal file
Binary file not shown.
BIN
Assets/Cut Scenes/Calendar Cutscene.ogv
Normal file
BIN
Assets/Cut Scenes/Calendar Cutscene.ogv
Normal file
Binary file not shown.
1
Assets/Cut Scenes/Calendar Cutscene.ogv.uid
Normal file
1
Assets/Cut Scenes/Calendar Cutscene.ogv.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bbi3yd8miilul
|
||||
BIN
Assets/Cut Scenes/Game over video.mov
Normal file
BIN
Assets/Cut Scenes/Game over video.mov
Normal file
Binary file not shown.
BIN
Assets/Cut Scenes/Game over video.ogv
Normal file
BIN
Assets/Cut Scenes/Game over video.ogv
Normal file
Binary file not shown.
1
Assets/Cut Scenes/Game over video.ogv.uid
Normal file
1
Assets/Cut Scenes/Game over video.ogv.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://b41725daretoj
|
||||
BIN
Assets/Cut Scenes/Weewoo.mov
Normal file
BIN
Assets/Cut Scenes/Weewoo.mov
Normal file
Binary file not shown.
BIN
Assets/Cut Scenes/Weewoo.ogv
Normal file
BIN
Assets/Cut Scenes/Weewoo.ogv
Normal file
Binary file not shown.
1
Assets/Cut Scenes/Weewoo.ogv.uid
Normal file
1
Assets/Cut Scenes/Weewoo.ogv.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://l34f82rsegd6
|
||||
@ -716,7 +716,7 @@ z_index = 10
|
||||
texture_filter = 1
|
||||
position = Vector2(0, -6)
|
||||
sprite_frames = SubResource("SpriteFrames_nn08x")
|
||||
animation = &"idle"
|
||||
animation = &"death"
|
||||
autoplay = "idle"
|
||||
|
||||
[node name="ChildGraphic" type="AnimatedSprite2D" parent="."]
|
||||
|
||||
25
Scenes/ambulance_cutscene.tscn
Normal file
25
Scenes/ambulance_cutscene.tscn
Normal file
@ -0,0 +1,25 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://ci5nqe61278ke"]
|
||||
|
||||
[ext_resource type="VideoStream" uid="uid://l34f82rsegd6" path="res://Assets/Cut Scenes/Weewoo.ogv" id="1_3dlu2"]
|
||||
[ext_resource type="Script" uid="uid://basrxhaxblk5q" path="res://Scripts/ambulance_cutscene.gd" id="1_yn2le"]
|
||||
|
||||
[node name="AmbulanceCutscene" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 0
|
||||
offset_right = 960.0
|
||||
offset_bottom = 540.0
|
||||
script = ExtResource("1_yn2le")
|
||||
|
||||
[node name="VideoStreamPlayer" type="VideoStreamPlayer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
scale = Vector2(0.34, 0.34)
|
||||
stream = ExtResource("1_3dlu2")
|
||||
autoplay = true
|
||||
expand = true
|
||||
|
||||
[connection signal="finished" from="VideoStreamPlayer" to="." method="_on_video_stream_player_finished"]
|
||||
21
Scenes/final_death_cutscene.tscn
Normal file
21
Scenes/final_death_cutscene.tscn
Normal file
@ -0,0 +1,21 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://dofx2pgo5xgla"]
|
||||
|
||||
[ext_resource type="VideoStream" uid="uid://b41725daretoj" path="res://Assets/Cut Scenes/Game over video.ogv" id="1_h6d1g"]
|
||||
|
||||
[node name="FinalDeathCutscene" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 0
|
||||
offset_right = 960.0
|
||||
offset_bottom = 540.0
|
||||
|
||||
[node name="VideoStreamPlayer" type="VideoStreamPlayer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
scale = Vector2(0.34, 0.34)
|
||||
stream = ExtResource("1_h6d1g")
|
||||
autoplay = true
|
||||
expand = true
|
||||
13
Scripts/ambulance_cutscene.gd
Normal file
13
Scripts/ambulance_cutscene.gd
Normal file
@ -0,0 +1,13 @@
|
||||
extends Control
|
||||
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
pass # Replace with function body.
|
||||
|
||||
|
||||
|
||||
func _on_video_stream_player_finished() -> void:
|
||||
print("AMBULANCE VIDEO FINISHED")
|
||||
Gamecontroller.playerHealth = Gamecontroller.player.starting_health
|
||||
get_tree().change_scene_to_file("res://Scenes/Levels/MainGame.tscn")
|
||||
1
Scripts/ambulance_cutscene.gd.uid
Normal file
1
Scripts/ambulance_cutscene.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://basrxhaxblk5q
|
||||
@ -28,6 +28,15 @@ func _ready() -> void:
|
||||
func _physics_process(_delta: float) -> void:
|
||||
if is_dead:
|
||||
return
|
||||
|
||||
if player and player.is_dead:
|
||||
player = null
|
||||
is_attacking = false
|
||||
attack_hitbox.monitoring = false
|
||||
velocity = Vector2.ZERO
|
||||
sprite.play("idle")
|
||||
move_and_slide()
|
||||
return
|
||||
|
||||
if is_hurt:
|
||||
velocity = knockback_velocity
|
||||
@ -89,9 +98,9 @@ func do_attack_damage() -> void:
|
||||
print("Demon bodies found: ", bodies.size())
|
||||
|
||||
for body in bodies:
|
||||
if body is Player:
|
||||
if body is Player and not body.is_dead:
|
||||
print("DEMON HIT PLAYER")
|
||||
body.playerTakesDamage(1)
|
||||
body.receive_damage(1, global_position)
|
||||
|
||||
attack_hitbox.monitoring = false
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ var player:CharacterStats
|
||||
var enemy:CharacterStats
|
||||
|
||||
var playerHealth:int
|
||||
var playerLives: int = 3
|
||||
|
||||
var enemiesDict = {}
|
||||
|
||||
@ -112,3 +113,16 @@ func addEnemyToLevel(slime):
|
||||
"damage":enemy.meleeDamage + randDamage
|
||||
}
|
||||
enemiesDict[slime]=enemyStat
|
||||
|
||||
func damage_player(amount: int) -> void:
|
||||
playerHealth -= amount
|
||||
|
||||
if playerHealth <= 0:
|
||||
playerHealth = 0
|
||||
playerLives -= 1
|
||||
|
||||
print("Player lives remaining: ", playerLives)
|
||||
|
||||
playerDiesSignal.emit()
|
||||
else:
|
||||
playerTakesDamageSignal.emit(playerHealth)
|
||||
|
||||
@ -14,6 +14,12 @@ const JUMP_VELOCITY = -400.0
|
||||
@onready var punch_hitbox: Area2D = $PunchHitbox
|
||||
var using_child_form := false
|
||||
var punch_hitbox_start_x := 0.0
|
||||
var is_invulnerable := false
|
||||
var is_knocked_back := false
|
||||
|
||||
@export var knockback_strength := 250.0
|
||||
@export var knockback_upward := 100.0
|
||||
@export var knockback_duration := 0.25
|
||||
|
||||
enum FaceDirection{LEFT, RIGHT}
|
||||
var facing:FaceDirection = FaceDirection.RIGHT
|
||||
@ -34,6 +40,8 @@ var stairs_z_above := 0 # filled by stairs trigger
|
||||
var stairs_z_below := -1 # filled by stairs trigger
|
||||
var stairs_node: Node = null
|
||||
|
||||
var is_dead := false
|
||||
|
||||
signal deathAnimationCompleteSignal
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
@ -68,7 +76,7 @@ func _physics_process(delta: float) -> void:
|
||||
|
||||
move_and_slide()
|
||||
handle_collisions()
|
||||
|
||||
|
||||
func update_states():
|
||||
match current_state:
|
||||
#idle when movement in x
|
||||
@ -85,12 +93,12 @@ func update_states():
|
||||
current_state = State.IDLE
|
||||
else:
|
||||
current_state = State.RUN
|
||||
|
||||
|
||||
func get_active_graphic() -> AnimatedSprite2D:
|
||||
if using_child_form:
|
||||
return child_graphic
|
||||
return player_graphic
|
||||
|
||||
|
||||
func update_animation():
|
||||
var graphic = get_active_graphic()
|
||||
match current_state:
|
||||
@ -106,11 +114,22 @@ func update_animation():
|
||||
State.HURT:
|
||||
graphic.play("hurt")
|
||||
State.DEATH:
|
||||
graphic.play("death")
|
||||
if graphic.animation != "death":
|
||||
graphic.play("death")
|
||||
State.PUNCH:
|
||||
graphic.play("punch")
|
||||
|
||||
|
||||
func handle_movement(_delta):
|
||||
if is_dead:
|
||||
velocity = Vector2.ZERO
|
||||
return
|
||||
|
||||
if is_knocked_back:
|
||||
return
|
||||
|
||||
if is_knocked_back:
|
||||
return
|
||||
|
||||
if direction:
|
||||
velocity.x = direction * SPEED
|
||||
if direction <0:
|
||||
@ -146,6 +165,9 @@ func update_punch_hitbox_position() -> void:
|
||||
print("PunchHitbox X: ", punch_hitbox.position.x)
|
||||
|
||||
func handle_input():
|
||||
if is_dead:
|
||||
return
|
||||
|
||||
direction = Input.get_axis("ui_left", "ui_right")
|
||||
|
||||
if direction < 0:
|
||||
@ -181,7 +203,6 @@ func handle_input():
|
||||
%SceneManager.makeBullet(right_spawn.global_transform, 700)
|
||||
if facing == FaceDirection.LEFT:
|
||||
%SceneManager.makeBullet(left_spawn.global_transform, -700)
|
||||
|
||||
|
||||
func handle_collisions():
|
||||
for i in get_slide_collision_count():
|
||||
@ -207,7 +228,6 @@ func handle_collisions():
|
||||
if not left_cast.is_colliding() && not right_cast.is_colliding():
|
||||
pushEnabled = false
|
||||
|
||||
|
||||
const STAIRS_LAYER := 3 # correct layer number for "stairs"
|
||||
|
||||
func _ready() -> void:
|
||||
@ -274,6 +294,9 @@ func on_stairs_top_reached(stairs: Node) -> void:
|
||||
_end_stairs_mode()
|
||||
|
||||
func _do_punch_damage() -> void:
|
||||
if is_dead:
|
||||
return
|
||||
|
||||
print("Punch damage started")
|
||||
|
||||
punch_hitbox.monitoring = true
|
||||
@ -300,9 +323,15 @@ func _on_animation_finished() -> void:
|
||||
current_state = State.IDLE
|
||||
State.DEATH:
|
||||
deathAnimationCompleteSignal.emit()
|
||||
# Wait 2 seconds after the death animation
|
||||
await get_tree().create_timer(2.0).timeout
|
||||
if Gamecontroller.playerLives > 0:
|
||||
get_tree().change_scene_to_file("res://Scenes/ambulance_cutscene.tscn")
|
||||
else:
|
||||
get_tree().change_scene_to_file("res://Scenes/final_death_cutscene.tscn")
|
||||
State.PUNCH:
|
||||
current_state = State.IDLE
|
||||
|
||||
|
||||
func become_child() -> void:
|
||||
using_child_form = true
|
||||
|
||||
@ -318,12 +347,63 @@ func become_adult() -> void:
|
||||
child_graphic.visible = false
|
||||
|
||||
print("Player switched to ADULT form")
|
||||
|
||||
|
||||
func flash_while_invulnerable() -> void:
|
||||
var graphic = get_active_graphic()
|
||||
|
||||
while is_invulnerable:
|
||||
graphic.visible = false
|
||||
await get_tree().create_timer(0.1).timeout
|
||||
|
||||
graphic.visible = true
|
||||
await get_tree().create_timer(0.1).timeout
|
||||
|
||||
func receive_damage(amount: int, attacker_position: Vector2) -> void:
|
||||
if is_dead:
|
||||
return
|
||||
|
||||
if is_invulnerable:
|
||||
print("Player is invulnerable - damage ignored")
|
||||
return
|
||||
|
||||
is_invulnerable = true
|
||||
is_knocked_back = true
|
||||
|
||||
# Push player AWAY from whoever attacked
|
||||
var knockback_direction: float = sign(global_position.x - attacker_position.x)
|
||||
|
||||
# Fallback in case both are exactly on top of one another
|
||||
if knockback_direction == 0:
|
||||
knockback_direction = -1
|
||||
|
||||
velocity.x = knockback_direction * knockback_strength
|
||||
velocity.y = -knockback_upward
|
||||
|
||||
Gamecontroller.damage_player(amount)
|
||||
|
||||
flash_while_invulnerable()
|
||||
|
||||
await get_tree().create_timer(knockback_duration).timeout
|
||||
is_knocked_back = false
|
||||
|
||||
await get_tree().create_timer(1.0 - knockback_duration).timeout
|
||||
|
||||
is_invulnerable = false
|
||||
get_active_graphic().visible = true
|
||||
print("Player can take damage again")
|
||||
|
||||
func playerTakesDamage(health):
|
||||
print("Player sees remaining health "+str(health))
|
||||
print("Player sees remaining health " + str(health))
|
||||
current_state = State.HURT
|
||||
|
||||
func playerDies():
|
||||
if is_dead:
|
||||
return
|
||||
|
||||
is_dead = true
|
||||
is_invulnerable = true
|
||||
is_knocked_back = false
|
||||
velocity = Vector2.ZERO
|
||||
|
||||
print("Player sees they died.")
|
||||
current_state = State.DEATH
|
||||
|
||||
|
||||
@ -4,9 +4,9 @@
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_eiyfu")
|
||||
max_health = 100
|
||||
starting_health = 100
|
||||
health = 100
|
||||
max_health = 3
|
||||
starting_health = 3
|
||||
health = 3
|
||||
meleeDamage = 5
|
||||
rangeDamage = 10
|
||||
metadata/_custom_type_script = "uid://b4t8cviiuvt18"
|
||||
|
||||
@ -35,6 +35,15 @@ func _ready() -> void:
|
||||
func _physics_process(delta: float) -> void:
|
||||
if is_dead:
|
||||
return
|
||||
|
||||
if player and player.is_dead:
|
||||
player = null
|
||||
is_attacking = false
|
||||
attack_hitbox.monitoring = false
|
||||
velocity.x = 0
|
||||
sprite.play("idle")
|
||||
move_and_slide()
|
||||
return
|
||||
|
||||
if not is_on_floor():
|
||||
velocity.y += gravity * delta
|
||||
@ -100,9 +109,9 @@ func do_attack_damage() -> void:
|
||||
print("Bodies found: ", bodies.size())
|
||||
|
||||
for body in bodies:
|
||||
if body is Player:
|
||||
print("PLAYER HIT")
|
||||
body.playerTakesDamage(1)
|
||||
if body is Player and not body.is_dead:
|
||||
print("SKELETON HIT PLAYER")
|
||||
body.receive_damage(1, global_position)
|
||||
|
||||
attack_hitbox.monitoring = false
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user