Houses ~8.5 m; chimps capped ~0.85 m with safer axis fitting. Large GLB/PBR packs stay local until Git LFS (Forgejo HTTP 413). Co-authored-by: Cursor <cursoragent@cursor.com>
159 lines
4.7 KiB
GDScript
159 lines
4.7 KiB
GDScript
extends Node3D
|
|
class_name MeleeVfx
|
|
## Visible melee swing (fist/tooth/sword) + range rings + hit particles.
|
|
|
|
@export var attack_range: float = 2.75
|
|
@export var interact_range: float = 2.8
|
|
|
|
var _swinging: bool = false
|
|
var _swing_t: float = 0.0
|
|
var _swing_dur: float = 0.22
|
|
var _weapon: Node3D
|
|
var _weapon_bill: BillboardSprite
|
|
var _atk_ring: MeshInstance3D
|
|
var _use_ring: MeshInstance3D
|
|
var _particles: CPUParticles3D
|
|
|
|
func _ready() -> void:
|
|
_build_weapon()
|
|
_build_rings()
|
|
_build_particles()
|
|
visible = true
|
|
|
|
func _build_weapon() -> void:
|
|
_weapon = Node3D.new()
|
|
_weapon.name = "WeaponSwing"
|
|
add_child(_weapon)
|
|
var packed: PackedScene = load("res://scenes/prefabs/billboard_sprite.tscn")
|
|
if packed:
|
|
_weapon_bill = packed.instantiate()
|
|
_weapon.add_child(_weapon_bill)
|
|
_weapon_bill.position = Vector3(0.85, 0.9, 0.0)
|
|
_weapon_bill.set_target_height(0.55)
|
|
_weapon_bill.max_width = 0.7
|
|
_weapon.visible = false
|
|
|
|
func _build_rings() -> void:
|
|
_atk_ring = _make_ring(Color(1.0, 0.35, 0.25, 0.35), attack_range)
|
|
_atk_ring.name = "AttackRangeRing"
|
|
add_child(_atk_ring)
|
|
_use_ring = _make_ring(Color(1.0, 0.9, 0.25, 0.28), interact_range)
|
|
_use_ring.name = "InteractRangeRing"
|
|
add_child(_use_ring)
|
|
_atk_ring.visible = false
|
|
_use_ring.visible = false
|
|
|
|
func _make_ring(color: Color, radius: float) -> MeshInstance3D:
|
|
var mi := MeshInstance3D.new()
|
|
var torus := TorusMesh.new()
|
|
torus.inner_radius = maxf(0.05, radius - 0.06)
|
|
torus.outer_radius = radius
|
|
torus.rings = 12
|
|
torus.ring_segments = 24
|
|
mi.mesh = torus
|
|
var mat := StandardMaterial3D.new()
|
|
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
|
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
|
mat.albedo_color = color
|
|
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
|
|
mi.material_override = mat
|
|
mi.position = Vector3(0, 0.05, 0)
|
|
mi.rotation_degrees = Vector3(0, 0, 0)
|
|
return mi
|
|
|
|
func _build_particles() -> void:
|
|
_particles = CPUParticles3D.new()
|
|
_particles.name = "HitParticles"
|
|
_particles.emitting = false
|
|
_particles.one_shot = true
|
|
_particles.explosiveness = 0.95
|
|
_particles.amount = 18
|
|
_particles.lifetime = 0.35
|
|
_particles.direction = Vector3(0, 1, 0)
|
|
_particles.spread = 180.0
|
|
_particles.initial_velocity_min = 2.5
|
|
_particles.initial_velocity_max = 6.0
|
|
_particles.gravity = Vector3(0, -8, 0)
|
|
_particles.scale_amount_min = 0.08
|
|
_particles.scale_amount_max = 0.18
|
|
var mat := StandardMaterial3D.new()
|
|
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
|
mat.albedo_color = Color(1.0, 0.85, 0.3)
|
|
mat.billboard_mode = BaseMaterial3D.BILLBOARD_ENABLED
|
|
_particles.mesh = SphereMesh.new()
|
|
(_particles.mesh as SphereMesh).radius = 0.08
|
|
(_particles.mesh as SphereMesh).height = 0.16
|
|
_particles.material_override = mat
|
|
add_child(_particles)
|
|
|
|
func _process(delta: float) -> void:
|
|
if _swinging:
|
|
_swing_t += delta
|
|
var u := clampf(_swing_t / _swing_dur, 0.0, 1.0)
|
|
# Arc from -70° to +70° around player up-axis
|
|
var ang := deg_to_rad(lerp(-70.0, 70.0, u))
|
|
_weapon.rotation = Vector3(0, ang, 0)
|
|
_weapon.position = Vector3(0, 0.15 + 0.1 * sin(u * PI), 0)
|
|
if u >= 1.0:
|
|
_swinging = false
|
|
_weapon.visible = false
|
|
|
|
func set_ranges(attack_r: float, interact_r: float) -> void:
|
|
attack_range = attack_r
|
|
interact_range = interact_r
|
|
_resize_ring(_atk_ring, attack_r)
|
|
_resize_ring(_use_ring, interact_r)
|
|
|
|
func _resize_ring(mi: MeshInstance3D, radius: float) -> void:
|
|
if mi == null:
|
|
return
|
|
var torus := TorusMesh.new()
|
|
torus.inner_radius = maxf(0.05, radius - 0.07)
|
|
torus.outer_radius = radius
|
|
torus.rings = 12
|
|
torus.ring_segments = 24
|
|
mi.mesh = torus
|
|
|
|
func show_attack_range(on: bool) -> void:
|
|
if _atk_ring:
|
|
_atk_ring.visible = on
|
|
|
|
func show_interact_range(on: bool) -> void:
|
|
if _use_ring:
|
|
_use_ring.visible = on
|
|
|
|
func play_swing() -> void:
|
|
_refresh_weapon_texture()
|
|
_swinging = true
|
|
_swing_t = 0.0
|
|
_weapon.visible = true
|
|
_weapon.rotation = Vector3(0, deg_to_rad(-70), 0)
|
|
|
|
func burst_at(world_pos: Vector3, hit: bool) -> void:
|
|
if _particles == null:
|
|
return
|
|
_particles.global_position = world_pos + Vector3(0, 0.9, 0)
|
|
var mat := _particles.material_override as StandardMaterial3D
|
|
if mat:
|
|
mat.albedo_color = Color(1.0, 0.35, 0.25) if hit else Color(0.7, 0.7, 0.75)
|
|
_particles.restart()
|
|
_particles.emitting = true
|
|
|
|
func _refresh_weapon_texture() -> void:
|
|
if _weapon_bill == null:
|
|
return
|
|
var path := "res://assets/sprites/placeholders/fist.png"
|
|
var h := 0.55
|
|
if Inventory.has_item("moon_sword"):
|
|
path = "res://assets/sprites/placeholders/moon_sword.png"
|
|
h = 0.9
|
|
elif Inventory.has_item("wartchen"):
|
|
path = "res://assets/sprites/placeholders/wartchen.png"
|
|
h = 0.65
|
|
elif Inventory.has_item("tooth"):
|
|
path = "res://assets/sprites/placeholders/tooth.png"
|
|
h = 0.6
|
|
_weapon_bill.set_target_height(h)
|
|
var tex: Texture2D = load(path)
|
|
if tex:
|
|
_weapon_bill.set_texture(tex)
|