Add blood decals, gore, mobile HUD, web start gate + touch/perf tests
Remove tools/fstest.html scratch page used to probe browser fullscreen/orientation APIs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EznnY8rH2dXhtono1kwsXg
This commit is contained in:
+164
@@ -0,0 +1,164 @@
|
||||
extends CanvasLayer
|
||||
## Mobile-only health readouts (instanced by hud.gd only when Controls.use_touch_ui()).
|
||||
## The desktop HUD's bull pips are tiny and, on a phone, the on-screen thumb cluster crowds
|
||||
## the bottom edge — so on touch we hide that cluster (hud.gd) and paint health up top where
|
||||
## it's clear of the joystick and abilities:
|
||||
## • bull health — a big pip row, top-left
|
||||
## • boss health — a wide Souls-style bar, top-centre, shown only while a bear is alive
|
||||
##
|
||||
## Everything is drawn in one IGNORE-filtered Control (never eats a touch), same immediate-mode
|
||||
## approach as touch_controls.gd. Cached values are refreshed from each character's
|
||||
## health_changed signal; the bear is discovered by group (&"bear") because it spawns after the
|
||||
## HUD, and a level swap frees it so we re-acquire the next one.
|
||||
|
||||
const _HP_FULL: Color = Color(0.82, 0.16, 0.12)
|
||||
const _HP_BONUS: Color = Color(1.00, 0.84, 0.18)
|
||||
const _SOCKET: Color = Color(0.05, 0.02, 0.02, 0.92)
|
||||
const _PANEL_BG: Color = Color(0.06, 0.04, 0.02, 0.82)
|
||||
|
||||
const _PIP_W: float = 42.0
|
||||
const _PIP_H: float = 28.0
|
||||
const _PIP_GAP: float = 7.0
|
||||
|
||||
var _surface: Control
|
||||
|
||||
var _player: Node = null
|
||||
var _bull_cur: int = 0
|
||||
var _bull_max: int = 0
|
||||
var _bull_bonus: int = 0
|
||||
|
||||
var _boss: Node = null
|
||||
var _boss_alive: bool = false
|
||||
var _boss_cur: int = 0
|
||||
var _boss_max: int = 1
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
layer = 9 # below touch UI (10) and the HUD/game-over (11) so the outcome screen covers it
|
||||
process_mode = Node.PROCESS_MODE_ALWAYS
|
||||
|
||||
_surface = Control.new()
|
||||
_surface.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
_surface.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_surface.draw.connect(_draw_surface)
|
||||
add_child(_surface)
|
||||
|
||||
get_viewport().size_changed.connect(_surface.queue_redraw)
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if not Controls.use_touch_ui():
|
||||
return
|
||||
|
||||
if _player == null:
|
||||
var players := get_tree().get_nodes_in_group(&"player")
|
||||
if not players.is_empty():
|
||||
_player = players[0]
|
||||
_player.health_changed.connect(_on_bull_health)
|
||||
_on_bull_health(_player.call(&"get_hp"), _player.call(&"get_max_hp"))
|
||||
|
||||
# A level swap frees the old bear; drop the stale ref so the next level's bear is picked up.
|
||||
if _boss != null and not is_instance_valid(_boss):
|
||||
_boss = null
|
||||
_boss_alive = false
|
||||
_surface.queue_redraw()
|
||||
if _boss == null:
|
||||
var bears := get_tree().get_nodes_in_group(&"bear")
|
||||
if not bears.is_empty():
|
||||
_boss = bears[0]
|
||||
_boss_alive = true
|
||||
_boss.health_changed.connect(_on_boss_health)
|
||||
if _boss.has_signal(&"killed"):
|
||||
_boss.killed.connect(_on_boss_killed)
|
||||
_boss_cur = int(_boss.call(&"get_hp"))
|
||||
_boss_max = maxi(_boss_cur, int(DP.f("bear_max_hp")))
|
||||
_surface.queue_redraw()
|
||||
|
||||
|
||||
func _on_bull_health(current: int, max_hp: int) -> void:
|
||||
_bull_cur = maxi(current, 0)
|
||||
_bull_max = maxi(max_hp, 0)
|
||||
_bull_bonus = 0
|
||||
if _player != null and _player.has_method(&"get_bonus_hp"):
|
||||
_bull_bonus = int(_player.call(&"get_bonus_hp"))
|
||||
_surface.queue_redraw()
|
||||
|
||||
|
||||
func _on_boss_health(current: int, max_hp: int) -> void:
|
||||
_boss_cur = maxi(current, 0)
|
||||
_boss_max = maxi(max_hp, maxi(_boss_cur, 1))
|
||||
if _boss_cur <= 0:
|
||||
_boss_alive = false
|
||||
_surface.queue_redraw()
|
||||
|
||||
|
||||
func _on_boss_killed() -> void:
|
||||
_boss_alive = false
|
||||
_surface.queue_redraw()
|
||||
|
||||
|
||||
# ── Drawing ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
func _draw_surface() -> void:
|
||||
if not Controls.use_touch_ui():
|
||||
return
|
||||
var boss_showing := _boss != null and is_instance_valid(_boss) and _boss_alive and _boss_cur > 0
|
||||
# The boss bar is centred at the very top; the bull pip row is top-left. On a narrow landscape
|
||||
# phone a long pip row reaches the centre and collides with the boss bar, so when the bar is up
|
||||
# the bull row drops to a second line just beneath it (guaranteed clear, whatever the HP count).
|
||||
var bull_top := 22.0
|
||||
if boss_showing:
|
||||
bull_top = _draw_boss_bar() + 12.0
|
||||
_draw_bull_health(bull_top)
|
||||
|
||||
|
||||
# Big pip row in the top-left. Bonus pips (barrel reward) sit at the high end and light gold,
|
||||
# matching the desktop HUD; base pips light red; spent pips read as dark sockets.
|
||||
func _draw_bull_health(top: float) -> void:
|
||||
var font := UiFonts.body()
|
||||
var x := 24.0
|
||||
var y := top
|
||||
if font != null:
|
||||
var label := "BULL"
|
||||
var fs := 20
|
||||
_surface.draw_string(
|
||||
font, Vector2(x, y + _PIP_H * 0.85), label, HORIZONTAL_ALIGNMENT_LEFT, -1, fs,
|
||||
UiTheme.GOLD)
|
||||
x += font.get_string_size(label, HORIZONTAL_ALIGNMENT_LEFT, -1, fs).x + 12.0
|
||||
|
||||
var base_max := _bull_max - _bull_bonus
|
||||
for i: int in _bull_max:
|
||||
var r := Rect2(x + i * (_PIP_W + _PIP_GAP), y, _PIP_W, _PIP_H)
|
||||
_surface.draw_rect(r, _SOCKET)
|
||||
if i < _bull_cur:
|
||||
var c := _HP_BONUS if i >= base_max else _HP_FULL
|
||||
_surface.draw_rect(r.grow(-2.0), c)
|
||||
_surface.draw_rect(r, Color(UiTheme.GOLD.r, UiTheme.GOLD.g, UiTheme.GOLD.b, 0.45), false, 2.0)
|
||||
|
||||
|
||||
# Wide boss bar centred at the top: dark trough, red fill scaled to remaining HP, gold trim,
|
||||
# name centred above it. Returns the bar's bottom Y so the bull pip row can sit clear beneath it.
|
||||
func _draw_boss_bar() -> float:
|
||||
var vp := get_viewport().get_visible_rect().size
|
||||
var bw := clampf(vp.x * 0.55, 280.0, 620.0)
|
||||
var bh := 30.0
|
||||
var bx := (vp.x - bw) * 0.5
|
||||
var by := 20.0
|
||||
var bar := Rect2(bx, by, bw, bh)
|
||||
|
||||
_surface.draw_rect(bar, _PANEL_BG)
|
||||
var frac := clampf(float(_boss_cur) / float(_boss_max), 0.0, 1.0)
|
||||
var fill := bar.grow(-3.0)
|
||||
fill.size.x *= frac
|
||||
_surface.draw_rect(fill, _HP_FULL)
|
||||
_surface.draw_rect(bar, UiTheme.BORDER, false, 2.0)
|
||||
|
||||
var font := UiFonts.body()
|
||||
if font != null:
|
||||
var boss_name := "BEAR"
|
||||
var fs := 20
|
||||
var tw := font.get_string_size(boss_name, HORIZONTAL_ALIGNMENT_LEFT, -1, fs)
|
||||
_surface.draw_string(
|
||||
font, Vector2(bx + (bw - tw.x) * 0.5, by - 6.0), boss_name,
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, fs, UiTheme.GOLD)
|
||||
return by + bh
|
||||
Reference in New Issue
Block a user