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
|
texture_filter = 1
|
||||||
position = Vector2(0, -6)
|
position = Vector2(0, -6)
|
||||||
sprite_frames = SubResource("SpriteFrames_nn08x")
|
sprite_frames = SubResource("SpriteFrames_nn08x")
|
||||||
animation = &"idle"
|
animation = &"death"
|
||||||
autoplay = "idle"
|
autoplay = "idle"
|
||||||
|
|
||||||
[node name="ChildGraphic" type="AnimatedSprite2D" parent="."]
|
[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
|
||||||
@ -29,6 +29,15 @@ func _physics_process(_delta: float) -> void:
|
|||||||
if is_dead:
|
if is_dead:
|
||||||
return
|
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:
|
if is_hurt:
|
||||||
velocity = knockback_velocity
|
velocity = knockback_velocity
|
||||||
|
|
||||||
@ -89,9 +98,9 @@ func do_attack_damage() -> void:
|
|||||||
print("Demon bodies found: ", bodies.size())
|
print("Demon bodies found: ", bodies.size())
|
||||||
|
|
||||||
for body in bodies:
|
for body in bodies:
|
||||||
if body is Player:
|
if body is Player and not body.is_dead:
|
||||||
print("DEMON HIT PLAYER")
|
print("DEMON HIT PLAYER")
|
||||||
body.playerTakesDamage(1)
|
body.receive_damage(1, global_position)
|
||||||
|
|
||||||
attack_hitbox.monitoring = false
|
attack_hitbox.monitoring = false
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,7 @@ var player:CharacterStats
|
|||||||
var enemy:CharacterStats
|
var enemy:CharacterStats
|
||||||
|
|
||||||
var playerHealth:int
|
var playerHealth:int
|
||||||
|
var playerLives: int = 3
|
||||||
|
|
||||||
var enemiesDict = {}
|
var enemiesDict = {}
|
||||||
|
|
||||||
@ -112,3 +113,16 @@ func addEnemyToLevel(slime):
|
|||||||
"damage":enemy.meleeDamage + randDamage
|
"damage":enemy.meleeDamage + randDamage
|
||||||
}
|
}
|
||||||
enemiesDict[slime]=enemyStat
|
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
|
@onready var punch_hitbox: Area2D = $PunchHitbox
|
||||||
var using_child_form := false
|
var using_child_form := false
|
||||||
var punch_hitbox_start_x := 0.0
|
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}
|
enum FaceDirection{LEFT, RIGHT}
|
||||||
var facing:FaceDirection = FaceDirection.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_z_below := -1 # filled by stairs trigger
|
||||||
var stairs_node: Node = null
|
var stairs_node: Node = null
|
||||||
|
|
||||||
|
var is_dead := false
|
||||||
|
|
||||||
signal deathAnimationCompleteSignal
|
signal deathAnimationCompleteSignal
|
||||||
|
|
||||||
func _physics_process(delta: float) -> void:
|
func _physics_process(delta: float) -> void:
|
||||||
@ -106,11 +114,22 @@ func update_animation():
|
|||||||
State.HURT:
|
State.HURT:
|
||||||
graphic.play("hurt")
|
graphic.play("hurt")
|
||||||
State.DEATH:
|
State.DEATH:
|
||||||
graphic.play("death")
|
if graphic.animation != "death":
|
||||||
|
graphic.play("death")
|
||||||
State.PUNCH:
|
State.PUNCH:
|
||||||
graphic.play("punch")
|
graphic.play("punch")
|
||||||
|
|
||||||
func handle_movement(_delta):
|
func handle_movement(_delta):
|
||||||
|
if is_dead:
|
||||||
|
velocity = Vector2.ZERO
|
||||||
|
return
|
||||||
|
|
||||||
|
if is_knocked_back:
|
||||||
|
return
|
||||||
|
|
||||||
|
if is_knocked_back:
|
||||||
|
return
|
||||||
|
|
||||||
if direction:
|
if direction:
|
||||||
velocity.x = direction * SPEED
|
velocity.x = direction * SPEED
|
||||||
if direction <0:
|
if direction <0:
|
||||||
@ -146,6 +165,9 @@ func update_punch_hitbox_position() -> void:
|
|||||||
print("PunchHitbox X: ", punch_hitbox.position.x)
|
print("PunchHitbox X: ", punch_hitbox.position.x)
|
||||||
|
|
||||||
func handle_input():
|
func handle_input():
|
||||||
|
if is_dead:
|
||||||
|
return
|
||||||
|
|
||||||
direction = Input.get_axis("ui_left", "ui_right")
|
direction = Input.get_axis("ui_left", "ui_right")
|
||||||
|
|
||||||
if direction < 0:
|
if direction < 0:
|
||||||
@ -182,7 +204,6 @@ func handle_input():
|
|||||||
if facing == FaceDirection.LEFT:
|
if facing == FaceDirection.LEFT:
|
||||||
%SceneManager.makeBullet(left_spawn.global_transform, -700)
|
%SceneManager.makeBullet(left_spawn.global_transform, -700)
|
||||||
|
|
||||||
|
|
||||||
func handle_collisions():
|
func handle_collisions():
|
||||||
for i in get_slide_collision_count():
|
for i in get_slide_collision_count():
|
||||||
var c = get_slide_collision(i)
|
var c = get_slide_collision(i)
|
||||||
@ -207,7 +228,6 @@ func handle_collisions():
|
|||||||
if not left_cast.is_colliding() && not right_cast.is_colliding():
|
if not left_cast.is_colliding() && not right_cast.is_colliding():
|
||||||
pushEnabled = false
|
pushEnabled = false
|
||||||
|
|
||||||
|
|
||||||
const STAIRS_LAYER := 3 # correct layer number for "stairs"
|
const STAIRS_LAYER := 3 # correct layer number for "stairs"
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
@ -274,6 +294,9 @@ func on_stairs_top_reached(stairs: Node) -> void:
|
|||||||
_end_stairs_mode()
|
_end_stairs_mode()
|
||||||
|
|
||||||
func _do_punch_damage() -> void:
|
func _do_punch_damage() -> void:
|
||||||
|
if is_dead:
|
||||||
|
return
|
||||||
|
|
||||||
print("Punch damage started")
|
print("Punch damage started")
|
||||||
|
|
||||||
punch_hitbox.monitoring = true
|
punch_hitbox.monitoring = true
|
||||||
@ -300,6 +323,12 @@ func _on_animation_finished() -> void:
|
|||||||
current_state = State.IDLE
|
current_state = State.IDLE
|
||||||
State.DEATH:
|
State.DEATH:
|
||||||
deathAnimationCompleteSignal.emit()
|
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:
|
State.PUNCH:
|
||||||
current_state = State.IDLE
|
current_state = State.IDLE
|
||||||
|
|
||||||
@ -319,11 +348,62 @@ func become_adult() -> void:
|
|||||||
|
|
||||||
print("Player switched to ADULT form")
|
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):
|
func playerTakesDamage(health):
|
||||||
print("Player sees remaining health "+str(health))
|
print("Player sees remaining health " + str(health))
|
||||||
current_state = State.HURT
|
current_state = State.HURT
|
||||||
|
|
||||||
func playerDies():
|
func playerDies():
|
||||||
|
if is_dead:
|
||||||
|
return
|
||||||
|
|
||||||
|
is_dead = true
|
||||||
|
is_invulnerable = true
|
||||||
|
is_knocked_back = false
|
||||||
|
velocity = Vector2.ZERO
|
||||||
|
|
||||||
print("Player sees they died.")
|
print("Player sees they died.")
|
||||||
current_state = State.DEATH
|
current_state = State.DEATH
|
||||||
|
|
||||||
|
|||||||
@ -4,9 +4,9 @@
|
|||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1_eiyfu")
|
script = ExtResource("1_eiyfu")
|
||||||
max_health = 100
|
max_health = 3
|
||||||
starting_health = 100
|
starting_health = 3
|
||||||
health = 100
|
health = 3
|
||||||
meleeDamage = 5
|
meleeDamage = 5
|
||||||
rangeDamage = 10
|
rangeDamage = 10
|
||||||
metadata/_custom_type_script = "uid://b4t8cviiuvt18"
|
metadata/_custom_type_script = "uid://b4t8cviiuvt18"
|
||||||
|
|||||||
@ -36,6 +36,15 @@ func _physics_process(delta: float) -> void:
|
|||||||
if is_dead:
|
if is_dead:
|
||||||
return
|
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():
|
if not is_on_floor():
|
||||||
velocity.y += gravity * delta
|
velocity.y += gravity * delta
|
||||||
|
|
||||||
@ -100,9 +109,9 @@ func do_attack_damage() -> void:
|
|||||||
print("Bodies found: ", bodies.size())
|
print("Bodies found: ", bodies.size())
|
||||||
|
|
||||||
for body in bodies:
|
for body in bodies:
|
||||||
if body is Player:
|
if body is Player and not body.is_dead:
|
||||||
print("PLAYER HIT")
|
print("SKELETON HIT PLAYER")
|
||||||
body.playerTakesDamage(1)
|
body.receive_damage(1, global_position)
|
||||||
|
|
||||||
attack_hitbox.monitoring = false
|
attack_hitbox.monitoring = false
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user