Feat/outline thickness and tp weapon hold #22
@@ -6,7 +6,7 @@ render_mode cull_front, unshaded;
|
|||||||
// culled, leaving a colored shell visible only at the silhouette.
|
// culled, leaving a colored shell visible only at the silhouette.
|
||||||
|
|
||||||
uniform vec4 outline_color : source_color = vec4(0.06, 0.05, 0.09, 1.0);
|
uniform vec4 outline_color : source_color = vec4(0.06, 0.05, 0.09, 1.0);
|
||||||
uniform float outline_width : hint_range(0.0, 0.1) = 0.02;
|
uniform float outline_width : hint_range(0.0, 0.1) = 0.006;
|
||||||
|
|
||||||
void vertex() {
|
void vertex() {
|
||||||
VERTEX += NORMAL * outline_width;
|
VERTEX += NORMAL * outline_width;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ var upper_arm_r_pivot: Node3D
|
|||||||
var upper_arm_r: MeshInstance3D
|
var upper_arm_r: MeshInstance3D
|
||||||
var lower_arm_r_pivot: Node3D
|
var lower_arm_r_pivot: Node3D
|
||||||
var lower_arm_r: MeshInstance3D
|
var lower_arm_r: MeshInstance3D
|
||||||
|
var hand_r_pivot: Node3D
|
||||||
|
|
||||||
var thigh_l_pivot: Node3D
|
var thigh_l_pivot: Node3D
|
||||||
var thigh_l: MeshInstance3D
|
var thigh_l: MeshInstance3D
|
||||||
@@ -117,6 +118,13 @@ func _ready() -> void:
|
|||||||
lower_arm_r.cast_shadow = shadow_setting
|
lower_arm_r.cast_shadow = shadow_setting
|
||||||
lower_arm_r_pivot.add_child(lower_arm_r)
|
lower_arm_r_pivot.add_child(lower_arm_r)
|
||||||
|
|
||||||
|
# Right hand: the wrist joint at the far end of the forearm. The
|
||||||
|
# third-person weapon hangs here so it travels with the arm instead of
|
||||||
|
# floating in front of the chest.
|
||||||
|
hand_r_pivot = Node3D.new()
|
||||||
|
hand_r_pivot.position = Vector3(0, -0.35, 0)
|
||||||
|
lower_arm_r_pivot.add_child(hand_r_pivot)
|
||||||
|
|
||||||
# Legs (Thigh: 0.45 length, Calf: 0.45 length)
|
# Legs (Thigh: 0.45 length, Calf: 0.45 length)
|
||||||
var t_leg_mesh = BoxMesh.new()
|
var t_leg_mesh = BoxMesh.new()
|
||||||
t_leg_mesh.size = Vector3(0.15, 0.45, 0.15)
|
t_leg_mesh.size = Vector3(0.15, 0.45, 0.15)
|
||||||
@@ -166,7 +174,7 @@ func _ready() -> void:
|
|||||||
# Cel-shade + ink outline the blocky fallback body so it matches skinned
|
# Cel-shade + ink outline the blocky fallback body so it matches skinned
|
||||||
# player models (shadows-only bodies skip it: outlines would cast shadows)
|
# player models (shadows-only bodies skip it: outlines would cast shadows)
|
||||||
if not shadows_only:
|
if not shadows_only:
|
||||||
LevelMaterials.apply_toon_recursive(root_pivot, 0.012)
|
LevelMaterials.apply_toon_recursive(root_pivot, 0.004)
|
||||||
|
|
||||||
## Skin System
|
## Skin System
|
||||||
var current_skin: PlayerSkin
|
var current_skin: PlayerSkin
|
||||||
@@ -229,7 +237,7 @@ func _apply_color(col: Color) -> void:
|
|||||||
head.mesh.material = hmat
|
head.mesh.material = hmat
|
||||||
# Re-toon after the material swap (fresh StandardMaterials above)
|
# Re-toon after the material swap (fresh StandardMaterials above)
|
||||||
if not shadows_only:
|
if not shadows_only:
|
||||||
LevelMaterials.apply_toon_recursive(root_pivot, 0.012)
|
LevelMaterials.apply_toon_recursive(root_pivot, 0.004)
|
||||||
|
|
||||||
func update_state(state: String, speed: float, is_crouching: bool = false) -> void:
|
func update_state(state: String, speed: float, is_crouching: bool = false) -> void:
|
||||||
current_state = state
|
current_state = state
|
||||||
@@ -240,8 +248,11 @@ func update_state(state: String, speed: float, is_crouching: bool = false) -> vo
|
|||||||
var is_holding_weapon: bool = false
|
var is_holding_weapon: bool = false
|
||||||
|
|
||||||
func set_weapon(script_path: String) -> void:
|
func set_weapon(script_path: String) -> void:
|
||||||
# Clear existing weapons from root_pivot
|
# Clear existing weapons (older builds parented them to root_pivot)
|
||||||
for child in root_pivot.get_children():
|
for parent in [root_pivot, hand_r_pivot]:
|
||||||
|
if not parent:
|
||||||
|
continue
|
||||||
|
for child in parent.get_children():
|
||||||
if child.has_meta("is_third_person_weapon"):
|
if child.has_meta("is_third_person_weapon"):
|
||||||
child.queue_free()
|
child.queue_free()
|
||||||
|
|
||||||
@@ -269,13 +280,40 @@ func set_weapon(script_path: String) -> void:
|
|||||||
# Toon shading only — FBX weapon normals tear inverted-hull outlines
|
# Toon shading only — FBX weapon normals tear inverted-hull outlines
|
||||||
LevelMaterials.apply_toon_recursive(w, 0.0)
|
LevelMaterials.apply_toon_recursive(w, 0.0)
|
||||||
|
|
||||||
# Force position after the weapon's _build_model() sets it for 1st person
|
# Undo the first-person viewmodel placement from _build_model()
|
||||||
w.position = Vector3(-0.15, 1.0, 0.4)
|
_seat_weapon_in_hand(w)
|
||||||
w.rotation_degrees = Vector3(0, 180, 0)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Attach to root_pivot so it stays steady and points perfectly forward
|
# Riding the hand keeps the gun with the arm; _process re-aims it forward.
|
||||||
root_pivot.add_child(w)
|
(hand_r_pivot if hand_r_pivot else root_pivot).add_child(w)
|
||||||
|
|
||||||
|
|
||||||
|
## Slide the weapon along its own barrel axis so a plausible grip point — not
|
||||||
|
## whatever origin the artist left the model at — ends up in the fist, and turn
|
||||||
|
## it to face the way the body faces (the model is yawed 180° in _ready, so the
|
||||||
|
## gun's -Z muzzle axis has to point along the model's local +Z).
|
||||||
|
func _seat_weapon_in_hand(w: Node3D) -> void:
|
||||||
|
w.rotation_degrees = Vector3(0, 180, 0)
|
||||||
|
w.position = Vector3.ZERO
|
||||||
|
var min_t := INF # most negative along the barrel = stock end
|
||||||
|
var max_t := -INF # most positive = muzzle end
|
||||||
|
for mi in w.find_children("*", "MeshInstance3D", true, false):
|
||||||
|
if not mi.mesh:
|
||||||
|
continue
|
||||||
|
# mi.get_aabb(), NOT mi.mesh.get_aabb(): the FBX gun parts are skinned and
|
||||||
|
# the Mesh resource still carries huge bind-pose bounds.
|
||||||
|
var xf: Transform3D = w.global_transform.affine_inverse() * mi.global_transform
|
||||||
|
var aabb: AABB = mi.get_aabb()
|
||||||
|
for i in 8:
|
||||||
|
var t: float = (xf * aabb.get_endpoint(i)).dot(Vector3(0, 0, -1))
|
||||||
|
min_t = minf(min_t, t)
|
||||||
|
max_t = maxf(max_t, t)
|
||||||
|
if min_t > max_t:
|
||||||
|
return
|
||||||
|
# ~a third back from the muzzle is where a pistol grip sits on every gun in
|
||||||
|
# the set, which leaves real length of weapon behind the hand.
|
||||||
|
var grip_at := min_t + (max_t - min_t) * 0.32
|
||||||
|
w.position = Vector3(0, 0, -grip_at)
|
||||||
|
|
||||||
func _set_shadows_recursive(node: Node) -> void:
|
func _set_shadows_recursive(node: Node) -> void:
|
||||||
if node is GeometryInstance3D:
|
if node is GeometryInstance3D:
|
||||||
@@ -438,15 +476,18 @@ func _process(delta: float) -> void:
|
|||||||
|
|
||||||
# Override right arm (and left) if holding a weapon
|
# Override right arm (and left) if holding a weapon
|
||||||
if is_holding_weapon and current_state != "death":
|
if is_holding_weapon and current_state != "death":
|
||||||
# Pose the right arm to look like it's holding the weapon handle
|
# Right arm holds the grip: elbow tucked at the ribs, forearm level so
|
||||||
t_upper_arm_r_rot.x = -0.6
|
# the fist (and the weapon parented to it) sits chest-high just in
|
||||||
t_upper_arm_r_rot.z = 0.15
|
# front of the torso rather than out at arm's length.
|
||||||
t_lower_arm_r_rot.x = -1.0
|
t_upper_arm_r_rot.x = -0.15
|
||||||
|
t_upper_arm_r_rot.z = -0.10
|
||||||
|
t_lower_arm_r_rot.x = -1.45
|
||||||
|
|
||||||
# Pose the left arm to look like it's holding the foregrip
|
# Left arm crosses over to the handguard: up, in, and a little further
|
||||||
t_upper_arm_l_rot.x = -0.5
|
# forward than the grip hand.
|
||||||
t_upper_arm_l_rot.z = -0.15
|
t_upper_arm_l_rot.x = -0.55
|
||||||
t_lower_arm_l_rot.x = -1.1
|
t_upper_arm_l_rot.z = 0.55
|
||||||
|
t_lower_arm_l_rot.x = -1.0
|
||||||
|
|
||||||
var lerp_speed = 40.0 * delta
|
var lerp_speed = 40.0 * delta
|
||||||
root_pivot.position = root_pivot.position.lerp(t_root_pos, lerp_speed)
|
root_pivot.position = root_pivot.position.lerp(t_root_pos, lerp_speed)
|
||||||
@@ -462,6 +503,12 @@ func _process(delta: float) -> void:
|
|||||||
calf_l_pivot.rotation = _lerp_vec3(calf_l_pivot.rotation, t_calf_l_rot, lerp_speed)
|
calf_l_pivot.rotation = _lerp_vec3(calf_l_pivot.rotation, t_calf_l_rot, lerp_speed)
|
||||||
calf_r_pivot.rotation = _lerp_vec3(calf_r_pivot.rotation, t_calf_r_rot, lerp_speed)
|
calf_r_pivot.rotation = _lerp_vec3(calf_r_pivot.rotation, t_calf_r_rot, lerp_speed)
|
||||||
|
|
||||||
|
# The weapon rides the hand for position, but its AIM belongs to the body:
|
||||||
|
# cancel the arm chain's rotation so the barrel stays pointed where the
|
||||||
|
# player faces instead of swinging around with the arm animation.
|
||||||
|
if hand_r_pivot:
|
||||||
|
hand_r_pivot.global_basis = root_pivot.global_basis
|
||||||
|
|
||||||
func _lerp_vec3(a: Vector3, b: Vector3, t: float) -> Vector3:
|
func _lerp_vec3(a: Vector3, b: Vector3, t: float) -> Vector3:
|
||||||
return Vector3(
|
return Vector3(
|
||||||
lerp_angle(a.x, b.x, t),
|
lerp_angle(a.x, b.x, t),
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ static func toonify(src: Material) -> Material:
|
|||||||
## Swap every mesh surface under `node` to toon shading and add an
|
## Swap every mesh surface under `node` to toon shading and add an
|
||||||
## inverted-hull outline overlay. Safe on skinned meshes (material_overlay
|
## inverted-hull outline overlay. Safe on skinned meshes (material_overlay
|
||||||
## re-renders the same deformed mesh).
|
## re-renders the same deformed mesh).
|
||||||
static func apply_toon_recursive(node: Node, outline_width: float = 0.015) -> void:
|
static func apply_toon_recursive(node: Node, outline_width: float = 0.005) -> void:
|
||||||
if node is MeshInstance3D:
|
if node is MeshInstance3D:
|
||||||
var mi := node as MeshInstance3D
|
var mi := node as MeshInstance3D
|
||||||
var surface_count: int = mi.mesh.get_surface_count() if mi.mesh else 0
|
var surface_count: int = mi.mesh.get_surface_count() if mi.mesh else 0
|
||||||
@@ -112,7 +112,7 @@ static func apply_toon_recursive(node: Node, outline_width: float = 0.015) -> vo
|
|||||||
apply_toon_recursive(child, outline_width)
|
apply_toon_recursive(child, outline_width)
|
||||||
|
|
||||||
|
|
||||||
static func outline(width: float = 0.015) -> ShaderMaterial:
|
static func outline(width: float = 0.005) -> ShaderMaterial:
|
||||||
var key := "%.4f" % width
|
var key := "%.4f" % width
|
||||||
if _outline_cache.has(key):
|
if _outline_cache.has(key):
|
||||||
return _outline_cache[key]
|
return _outline_cache[key]
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ func _hero(key: String, pos: Vector3, yaw_deg: float, col_size: Vector3,
|
|||||||
body.add_child(shape)
|
body.add_child(shape)
|
||||||
var inst: Node3D = _kit_scene_at("res://assets/props/hero/" + key + ".glb").instantiate()
|
var inst: Node3D = _kit_scene_at("res://assets/props/hero/" + key + ".glb").instantiate()
|
||||||
body.add_child(inst)
|
body.add_child(inst)
|
||||||
LevelMaterials.apply_toon_recursive(inst, 0.015)
|
LevelMaterials.apply_toon_recursive(inst, 0.005)
|
||||||
|
|
||||||
|
|
||||||
## Small visual-only kit prop (parasols, awnings): no collider needed.
|
## Small visual-only kit prop (parasols, awnings): no collider needed.
|
||||||
@@ -125,7 +125,7 @@ func _kit_prop(key: String, pos: Vector3, yaw_deg: float, scale_f: float) -> voi
|
|||||||
add_child(inst)
|
add_child(inst)
|
||||||
inst.global_position = pos
|
inst.global_position = pos
|
||||||
inst.rotation_degrees.y = yaw_deg
|
inst.rotation_degrees.y = yaw_deg
|
||||||
LevelMaterials.apply_toon_recursive(inst, 0.02)
|
LevelMaterials.apply_toon_recursive(inst, 0.007)
|
||||||
|
|
||||||
|
|
||||||
## Place a kit model with a box collider matching its scaled bounds.
|
## Place a kit model with a box collider matching its scaled bounds.
|
||||||
@@ -249,7 +249,7 @@ func _box_static(pos: Vector3, size: Vector3, color: Color, node_name: String =
|
|||||||
for mi in body.find_children("*", "MeshInstance3D", false, false):
|
for mi in body.find_children("*", "MeshInstance3D", false, false):
|
||||||
mi.mesh.surface_set_material(0, LevelMaterials.flat(color))
|
mi.mesh.surface_set_material(0, LevelMaterials.flat(color))
|
||||||
if maxf(size.x, maxf(size.y, size.z)) < 10.0:
|
if maxf(size.x, maxf(size.y, size.z)) < 10.0:
|
||||||
var w := clampf(maxf(size.x, maxf(size.y, size.z)) * 0.008, 0.02, 0.05)
|
var w := clampf(maxf(size.x, maxf(size.y, size.z)) * 0.003, 0.007, 0.018)
|
||||||
mi.material_overlay = LevelMaterials.outline(w)
|
mi.material_overlay = LevelMaterials.outline(w)
|
||||||
return body
|
return body
|
||||||
|
|
||||||
@@ -707,7 +707,7 @@ func _build_rail_line() -> void:
|
|||||||
fb.add_child(fb_shape)
|
fb.add_child(fb_shape)
|
||||||
var fb_inst: Node3D = _kit_scene_at("res://assets/props/hero/footbridge.glb").instantiate()
|
var fb_inst: Node3D = _kit_scene_at("res://assets/props/hero/footbridge.glb").instantiate()
|
||||||
fb.add_child(fb_inst)
|
fb.add_child(fb_inst)
|
||||||
LevelMaterials.apply_toon_recursive(fb_inst, 0.015)
|
LevelMaterials.apply_toon_recursive(fb_inst, 0.005)
|
||||||
# Walkable stair ramps at both ends (over the stair visuals)
|
# Walkable stair ramps at both ends (over the stair visuals)
|
||||||
for e in [-1.0, 1.0]:
|
for e in [-1.0, 1.0]:
|
||||||
var ang := rad_to_deg(atan2(5.5, 6.4))
|
var ang := rad_to_deg(atan2(5.5, 6.4))
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ static func spawn_bolt(scene: Node, origin: Vector3, target: Vector3,
|
|||||||
core.mesh = core_mesh
|
core.mesh = core_mesh
|
||||||
core.rotation.x = deg_to_rad(90) # orient capsule along Z
|
core.rotation.x = deg_to_rad(90) # orient capsule along Z
|
||||||
core.position = Vector3(0, 0, -0.55) # tail sits at the origin
|
core.position = Vector3(0, 0, -0.55) # tail sits at the origin
|
||||||
core.material_overlay = LevelMaterials.outline(0.012)
|
core.material_overlay = LevelMaterials.outline(0.004)
|
||||||
tracer.add_child(core)
|
tracer.add_child(core)
|
||||||
|
|
||||||
tracer.position = origin
|
tracer.position = origin
|
||||||
|
|||||||
Reference in New Issue
Block a user