Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 91d88cd5c7 | |||
| 6dff45c659 | |||
| 2fd41f4f69 | |||
| a147527e91 | |||
| 09d4a60f43 | |||
| c0a022fed4 | |||
| 9458085abb | |||
| 565c132848 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -23,7 +23,7 @@ script = ExtResource("2_camera_iso")
|
||||
[node name="bull" parent="." unique_id=1386495166 instance=ExtResource("4_e80uo")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.655, 0.23)
|
||||
|
||||
[node name="Skeleton3D" parent="bull/Armature" parent_id_path=PackedInt32Array(1386495166, 1942410469) index="0" unique_id=550138996]
|
||||
[node name="Skeleton3D" parent="bull/Armature" parent_id_path=PackedInt32Array(1386495166, 1057197468) index="0" unique_id=840295370]
|
||||
bones/0/position = Vector3(0, 0, 0.009963839)
|
||||
bones/0/rotation = Quaternion(0.7071068, 0, 0, 0.7071067)
|
||||
bones/0/scale = Vector3(1, 1, 1)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
extends Node3D
|
||||
|
||||
var player: Node3D = null
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
# If we don't have the player yet, try to find them in the group
|
||||
if not is_instance_valid(player):
|
||||
var players = get_tree().get_nodes_in_group("player")
|
||||
if players.size() > 0:
|
||||
player = players[0] as Node3D
|
||||
|
||||
# If the player exists, follow their position
|
||||
if is_instance_valid(player):
|
||||
global_position = player.global_position
|
||||
@@ -0,0 +1 @@
|
||||
uid://cmapsaj010q40
|
||||
@@ -0,0 +1,14 @@
|
||||
[gd_scene format=3 uid="uid://4v25hb0qu5l0"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cmapsaj010q40" path="res://actors/player/player_follow_light.gd" id="1_w7wrt"]
|
||||
|
||||
[node name="PlayerFollowLight" type="Node3D" unique_id=1889971494]
|
||||
script = ExtResource("1_w7wrt")
|
||||
|
||||
[node name="SpotLight3D" type="SpotLight3D" parent="." unique_id=1523840927]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0, 2.6136062, 1.0431258e-07)
|
||||
light_color = Color(0.9999998, 0.8702623, 0.60548127, 1)
|
||||
light_energy = 8.0
|
||||
shadow_enabled = true
|
||||
distance_fade_length = 30.45
|
||||
spot_angle = 72.62192
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://bbfvwolhomilp" path="res://props/torch.tscn" id="1_w7uyf"]
|
||||
[ext_resource type="PackedScene" uid="uid://bd56iuttyiypv" path="res://props/barrel_simple.tscn" id="2_3o0md"]
|
||||
[ext_resource type="PackedScene" uid="uid://4v25hb0qu5l0" path="res://actors/player/player_follow_light.tscn" id="2_8bh2m"]
|
||||
[ext_resource type="PackedScene" uid="uid://dctmlh4hnuux1" path="res://art/levels/lobby.glb" id="2_w7uyf"]
|
||||
[ext_resource type="Script" uid="uid://cwbf7jfkr8j10" path="res://levels/lobby_underground.gd" id="4_73452"]
|
||||
[ext_resource type="Script" uid="uid://dn77mjyxllgl6" path="res://levels/to_the_bear_level.gd" id="5_6j2sa"]
|
||||
@@ -45,8 +46,8 @@ size = Vector3(4.1274414, 8.299622, 6.9958496)
|
||||
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="Lights" unique_id=955995990]
|
||||
transform = Transform3D(-0.502238, -0.82287306, 0.26577574, 0.36594617, 0.07621761, 0.9275096, -0.78347975, 0.5630903, 0.26284787, 0, 0, 0)
|
||||
light_color = Color(0.78431374, 0.8117647, 1, 1)
|
||||
light_energy = 0.228
|
||||
light_color = Color(0.5266291, 0.5549962, 0.9999985, 1)
|
||||
light_energy = 0.466
|
||||
shadow_enabled = true
|
||||
|
||||
[node name="SpotLight3D" type="SpotLight3D" parent="Lights" unique_id=1783040386]
|
||||
@@ -99,6 +100,9 @@ transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, -0.3
|
||||
[node name="Torch8" parent="Lights/Torches" unique_id=987701581 instance=ExtResource("1_w7uyf")]
|
||||
transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, -0.349617, 1.9073486e-06, -0.31505227)
|
||||
|
||||
[node name="PlayerFollowLight" parent="Lights" unique_id=1889971494 instance=ExtResource("2_8bh2m")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.1712027, 0)
|
||||
|
||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=939963056]
|
||||
environment = SubResource("Environment_lobby")
|
||||
|
||||
@@ -240,7 +244,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.1928787, 0.11255789, 2.8063
|
||||
transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 0, 0, 0)
|
||||
script = ExtResource("4_73452")
|
||||
|
||||
[node name="Plane" parent="Lobby_underground" index="1" unique_id=1962555538]
|
||||
[node name="Plane" parent="Lobby_underground" index="1" unique_id=2038841618]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.2352347, 0)
|
||||
|
||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="Lobby_underground" unique_id=2070502042]
|
||||
|
||||
@@ -33,6 +33,14 @@ var cleared: Dictionary = {}
|
||||
## loads — the player reads it on spawn — and is capped so it can't stack past BONUS_HP_CAP.
|
||||
var bonus_hp: int = 0
|
||||
const BONUS_HP_CAP: int = 2
|
||||
|
||||
## Wall-clock run timer: starts the moment the player walks through the arena gate (the
|
||||
## run's first fight) and freezes the instant the bear falls, so the completion screen can
|
||||
## show a final time. Time.get_ticks_msec()-based — real time, not delta-summed — so it
|
||||
## keeps ticking correctly across the scene reload between the arena and the bear's den.
|
||||
var _timer_start_msec: int = -1
|
||||
var _timer_end_msec: int = -1
|
||||
var _timer_running: bool = false
|
||||
## True once start_new_run() has built a run. Lets ensure_run() avoid wiping an active run
|
||||
## when the shell reloads for each fight.
|
||||
var _run_active: bool = false
|
||||
@@ -105,6 +113,9 @@ func start_new_run() -> void:
|
||||
lobby_choice = null
|
||||
cleared = {}
|
||||
bonus_hp = 0
|
||||
_timer_start_msec = -1
|
||||
_timer_end_msec = -1
|
||||
_timer_running = false
|
||||
_run_active = true
|
||||
run_started.emit()
|
||||
|
||||
@@ -154,3 +165,39 @@ func add_bonus_hp(amount: int) -> int:
|
||||
var before := bonus_hp
|
||||
bonus_hp = clampi(bonus_hp + amount, 0, BONUS_HP_CAP)
|
||||
return bonus_hp - before
|
||||
|
||||
|
||||
## Begin the run clock. No-op while the clock is already running, so walking back through
|
||||
## the arena gate mid-run can't restart it — but once it's been stopped (the bear is dead
|
||||
## and the player has looped back to the arena to go again), the next call restarts it from
|
||||
## zero rather than staying stuck showing the previous run's final time.
|
||||
func start_run_timer() -> void:
|
||||
if _timer_running:
|
||||
return
|
||||
_timer_start_msec = Time.get_ticks_msec()
|
||||
_timer_end_msec = -1
|
||||
_timer_running = true
|
||||
|
||||
|
||||
## Freeze the run clock — called the instant the bear falls, so the recorded time is the
|
||||
## kill moment itself, not whatever the win-screen's celebratory delay adds on top.
|
||||
func stop_run_timer() -> void:
|
||||
if not _timer_running:
|
||||
return
|
||||
_timer_end_msec = Time.get_ticks_msec()
|
||||
_timer_running = false
|
||||
|
||||
|
||||
## Seconds elapsed since the timer started (live while running, held steady once
|
||||
## stop_run_timer() has been called). 0 before the timer has ever started.
|
||||
func run_elapsed() -> float:
|
||||
if _timer_start_msec < 0:
|
||||
return 0.0
|
||||
var end_msec := Time.get_ticks_msec() if _timer_running else _timer_end_msec
|
||||
return float(end_msec - _timer_start_msec) / 1000.0
|
||||
|
||||
|
||||
## Whether the run clock has been started (the player has entered the arena at least once
|
||||
## this run). Lets the HUD hide the timer readout until there's a time worth showing.
|
||||
func run_timer_started() -> bool:
|
||||
return _timer_start_msec >= 0
|
||||
|
||||
@@ -8,5 +8,6 @@ func _ready() -> void:
|
||||
func _on_body_entered(body: Node3D) -> void:
|
||||
if not body.is_in_group("player"):
|
||||
return
|
||||
Run.start_run_timer()
|
||||
Run.choose_level(&"arena")
|
||||
get_tree().change_scene_to_file(Run.active_level().scene_path)
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ config_version=5
|
||||
[application]
|
||||
|
||||
config/name="Bullosseum"
|
||||
config/version="0.4.0"
|
||||
config/version="0.5.0"
|
||||
run/main_scene="uid://bmnmenu0intr1"
|
||||
config/features=PackedStringArray("4.7", "GL Compatibility")
|
||||
boot_splash/bg_color=Color(0, 0, 0, 1)
|
||||
|
||||
@@ -167,6 +167,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9.011785, -2.371254, 16.2322
|
||||
[node name="OmniLight3D" type="OmniLight3D" parent="MeshInstance3D/TorchLight" unique_id=1094582672]
|
||||
transform = Transform3D(0.9999986, -0.001596809, 0.00045388562, 0.0014380338, 0.96984345, 0.24372463, -0.00082937966, -0.24372365, 0.9698444, 9.096397, 6.2542057, -15.77264)
|
||||
light_color = Color(0.92773837, 0.6304423, 0, 1)
|
||||
light_energy = 2.0
|
||||
shadow_enabled = true
|
||||
omni_range = 13.657384
|
||||
|
||||
|
||||
@@ -66,6 +66,10 @@ const _BULL_FACE_PAD: float = 1.15 # breathing room around the cluster within
|
||||
# records which way it went (read by the gameplay test).
|
||||
const _WIN_VIDEO := "res://videos/bull_win.ogv" # the bull triumphs
|
||||
const _LOSS_VIDEO := "res://videos/matador_win.ogv" # the matador triumphs
|
||||
# Clearing the bear — the run's final boss — replaces the usual bull-win clip with the
|
||||
# closing "Thank You For Playing" screen. That clip already bakes its own title text in,
|
||||
# so _over_label is left blank for this outcome (see _show_game_over).
|
||||
const _THANKYOU_VIDEO := "res://videos/thankyou.ogv"
|
||||
|
||||
# Beat between the run ending and the outcome screen. A loss is a short freeze on the
|
||||
# dead bull; a win gives the player a longer stretch to strut a victory lap around the
|
||||
@@ -81,6 +85,12 @@ var _over_label: Label
|
||||
var _over_continue: Button
|
||||
var _over_again: Button
|
||||
|
||||
# Run clock readout — top-left, desktop only (touch's top-left corner is already the
|
||||
# mobile HP readout from mobile_hud.gd). Tracks Run.run_elapsed(); hidden until the timer
|
||||
# has actually started (the player hasn't entered the arena yet) and while the game-over
|
||||
# screen is up.
|
||||
var _run_timer_label: Label
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
layer = 11
|
||||
@@ -92,6 +102,7 @@ func _ready() -> void:
|
||||
_build_ability_bar()
|
||||
_build_health()
|
||||
_build_version_label()
|
||||
_build_run_timer()
|
||||
_build_damage_vignette()
|
||||
_build_game_over()
|
||||
_build_touch_controls()
|
||||
@@ -206,8 +217,41 @@ func _build_version_label() -> void:
|
||||
add_child(lbl)
|
||||
|
||||
|
||||
# Run clock, top-left. Desktop only — touch already owns that corner for the mobile HP
|
||||
# readout, and a run timer is a nice-to-have there, not worth the crowding.
|
||||
func _build_run_timer() -> void:
|
||||
if Controls.use_touch_ui():
|
||||
return
|
||||
_run_timer_label = Label.new()
|
||||
_run_timer_label.text = _format_time(0.0)
|
||||
_run_timer_label.visible = false
|
||||
_run_timer_label.add_theme_font_override("font", UiFonts.body())
|
||||
_run_timer_label.add_theme_color_override("font_color", _CREAM)
|
||||
_run_timer_label.add_theme_color_override("font_outline_color", Color(0, 0, 0, 0.85))
|
||||
_run_timer_label.add_theme_constant_override("outline_size", 4)
|
||||
_run_timer_label.add_theme_font_size_override("font_size", 20)
|
||||
_run_timer_label.set_anchors_preset(Control.PRESET_TOP_LEFT)
|
||||
_run_timer_label.offset_left = 20.0
|
||||
_run_timer_label.offset_top = 16.0
|
||||
_run_timer_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(_run_timer_label)
|
||||
|
||||
|
||||
# mm:ss.hh — matches how a speedrun-style clock reads at a glance.
|
||||
static func _format_time(seconds: float) -> String:
|
||||
var total_cs := int(round(seconds * 100.0))
|
||||
var minutes := total_cs / 6000
|
||||
var secs := (total_cs / 100) % 60
|
||||
var centis := total_cs % 100
|
||||
return "%02d:%02d.%02d" % [minutes, secs, centis]
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
_update_control_hints()
|
||||
if _run_timer_label != null:
|
||||
_run_timer_label.visible = Run.run_timer_started() and not _game_over
|
||||
if _run_timer_label.visible:
|
||||
_run_timer_label.text = _format_time(Run.run_elapsed())
|
||||
if _player == null:
|
||||
var players := get_tree().get_nodes_in_group(&"player")
|
||||
if not players.is_empty():
|
||||
@@ -434,6 +478,10 @@ func _on_player_died(cause: String = "") -> void:
|
||||
|
||||
|
||||
func _on_all_defeated() -> void:
|
||||
# Freeze the run clock the instant the bear falls — before _show_game_over's
|
||||
# celebratory delay — so the recorded time is the kill moment, not the victory lap.
|
||||
if Run.active_level().id == &"bear":
|
||||
Run.stop_run_timer()
|
||||
_show_game_over(true)
|
||||
|
||||
|
||||
@@ -450,12 +498,16 @@ func _show_game_over(win: bool, _cause: String = "") -> void:
|
||||
|
||||
await get_tree().create_timer(_WIN_DELAY if win else _LOSS_DELAY, false, false, true).timeout
|
||||
|
||||
_over_label.text = "Bull Won!" if _result_win else "Matadors Won!"
|
||||
var is_final_boss := _result_win and Run.active_level().id == &"bear"
|
||||
# The thank-you clip already bakes in its own title, so this label carries the
|
||||
# winning time instead — the one thing that clip can't show.
|
||||
_over_label.text = ("Your Time: %s" % _format_time(Run.run_elapsed())) if is_final_boss \
|
||||
else ("Bull Won!" if _result_win else "Matadors Won!")
|
||||
# A win advances the run (on to the map); a loss ends it (retry from the top).
|
||||
_over_continue.visible = _result_win
|
||||
_over_again.visible = not _result_win
|
||||
|
||||
var path := _WIN_VIDEO if _result_win else _LOSS_VIDEO
|
||||
var path := _THANKYOU_VIDEO if is_final_boss else (_WIN_VIDEO if _result_win else _LOSS_VIDEO)
|
||||
if ResourceLoader.exists(path):
|
||||
_over_video.stream = load(path)
|
||||
_over_video.play()
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
uid://b5rupuoe1lk5j
|
||||
Reference in New Issue
Block a user