108 lines
4.3 KiB
GDScript
108 lines
4.3 KiB
GDScript
extends Node2D
|
|
@export var shield_radius := 50.0
|
|
var current_shields := 100.0
|
|
var max_shields := 100.0
|
|
var _hit_flash_timer := 0.0
|
|
var _hits: Array[Dictionary] = []
|
|
var _time := 0.0
|
|
static var _shell_texture: ImageTexture
|
|
static var _shell_material: ShaderMaterial
|
|
var _shell: Sprite2D
|
|
|
|
static func shell_texture() -> ImageTexture:
|
|
if _shell_texture != null: return _shell_texture
|
|
var image := Image.create(256,256,false,Image.FORMAT_RGBA8)
|
|
# Build the soft rim once; every ship shares this texture and material.
|
|
for y in 256:
|
|
for x in 256:
|
|
var point := (Vector2(x,y)+Vector2.ONE*0.5-Vector2.ONE*128.0)/112.0
|
|
var radius := point.length()
|
|
var edge := absf(radius-1.0)
|
|
var alpha := exp(-pow(edge/0.010,2.0))*0.34
|
|
alpha += exp(-pow(edge/0.05,2.0))*0.075
|
|
if radius < 1.0: alpha += 0.012+exp(-pow(edge/0.09,2.0))*0.025
|
|
var highlight := pow(maxf(0.0,cos(point.angle()*3.0)),24.0)
|
|
alpha += exp(-pow(edge/0.016,2.0))*highlight*0.18
|
|
image.set_pixel(x,y,Color(1,1,1,clampf(alpha,0.0,1.0)))
|
|
image.generate_mipmaps()
|
|
_shell_texture = ImageTexture.create_from_image(image)
|
|
return _shell_texture
|
|
|
|
func _uses_cached_shell() -> bool:
|
|
return true
|
|
|
|
func _sync_shell() -> void:
|
|
if _shell == null: return
|
|
_shell.visible = current_shields > 0.0
|
|
_shell.scale = Vector2.ONE*shield_radius/112.0
|
|
var ratio := clampf(current_shields/max_shields,0.0,1.0)
|
|
_shell.self_modulate = Color(Color("61d8ff").lerp(Color("a4edff"),ratio*0.35),0.35+ratio*0.65)
|
|
|
|
func _ready() -> void:
|
|
if _uses_cached_shell():
|
|
_shell = Sprite2D.new()
|
|
_shell.name = "CachedShieldShell"
|
|
_shell.texture = shell_texture()
|
|
_shell.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS
|
|
if _shell_material == null:
|
|
_shell_material = ShaderMaterial.new()
|
|
_shell_material.shader = preload("res://resources/ui/shield_shell.gdshader")
|
|
_shell.material = _shell_material
|
|
add_child(_shell)
|
|
_sync_shell()
|
|
var owner := get_parent()
|
|
if owner is CharacterBody2D and owner.has_method("take_damage"):
|
|
owner.add_to_group("shielded_combatants")
|
|
|
|
func _process(delta: float) -> void:
|
|
_time += delta
|
|
var had_hits := not _hits.is_empty()
|
|
_hit_flash_timer = maxf(0.0,_hit_flash_timer-delta)
|
|
for i in range(_hits.size()-1,-1,-1):
|
|
_hits[i].age += delta
|
|
if _hits[i].age >= 0.42: _hits.remove_at(i)
|
|
visible = current_shields > 0.0 or not _hits.is_empty()
|
|
var owner := get_parent()
|
|
if owner.has_method("is_mini_boosting") and bool(owner.get("_is_destroyed")): visible = false
|
|
# Only impacts rebuild geometry. The GPU pulses the cached ambient shell.
|
|
if had_hits or (visible and not _uses_cached_shell()): queue_redraw()
|
|
|
|
func set_shield_values(current: float, maximum: float) -> void:
|
|
var changed := current_shields != current or max_shields != maxf(1.0,maximum)
|
|
current_shields = current
|
|
max_shields = maxf(1.0,maximum)
|
|
visible = current_shields > 0.0 or not _hits.is_empty()
|
|
_sync_shell()
|
|
if changed and not _uses_cached_shell(): queue_redraw()
|
|
|
|
func flash_hit(world_point := Vector2.INF, damage_type := DamageTypes.Type.ENERGY, strength := 1.0) -> void:
|
|
_hit_flash_timer = 0.22
|
|
var direction := Vector2.UP if not world_point.is_finite() else to_local(world_point).normalized()
|
|
if direction.length_squared()<0.01: direction = Vector2.UP
|
|
if _hits.size()>=6: _hits.pop_front()
|
|
_hits.append({"angle":direction.angle(),"age":0.0,"kind":damage_type,"strength":clampf(strength,0.4,1.4)})
|
|
visible = true
|
|
queue_redraw()
|
|
|
|
func _draw() -> void:
|
|
_draw_impacts()
|
|
|
|
func _draw_impacts() -> void:
|
|
for hit in _hits:
|
|
var t := float(hit.age)/0.42
|
|
var fade := (1.0-t)*(1.0-t)*float(hit.strength)
|
|
var color: Color = [Color("ffd18b"),Color("75dfff"),Color("a1f475")][int(hit.kind)]
|
|
var angle: float = hit.angle
|
|
var spread := 0.12+t*1.2
|
|
var radius := shield_radius+t*2.0
|
|
for layer in 3:
|
|
draw_arc(Vector2.ZERO,radius-float(layer)*1.5,angle-spread,angle+spread,28,Color(color,fade*(0.6 if layer==0 else 0.13)),1.4 if layer==0 else 3.0,true)
|
|
var contact := Vector2.RIGHT.rotated(angle)*shield_radius
|
|
draw_circle(contact,8.0*(1.0-t),Color(color,fade*0.12))
|
|
for ring in 2:
|
|
draw_arc(contact,3.0+t*(17.0+ring*8.0),angle+PI*0.5,angle+PI*1.5,22,Color(color,fade*(0.5 if ring==0 else 0.17)),1.1,true)
|
|
if t<0.35:
|
|
var core := (1.0-t/0.35)*float(hit.strength)
|
|
draw_circle(contact,5*core,Color(color,core*0.22))
|
|
draw_circle(contact,1.8*core,Color(color.lightened(0.7),core))
|