straywild/world/world_character_display.gd

228 lines
5.9 KiB
GDScript

class_name WorldCharacterDisplay
extends Node3D
const CharacterCustomizationCatalogType = preload(
"res://progression/character_customization_catalog.gd"
)
const PlayerVisualPresenterType = preload(
"res://player/player_visual_presenter.gd"
)
@export_enum(
"round",
"pointy",
"anteater",
"axolotl",
"bat",
"bird",
"boar",
"butterfly",
"donkey",
"elephant",
"goat",
"hamster",
"horse",
"moth",
"opossum",
"owl",
"panda",
"pig",
"shark",
"sheep",
"snail",
) var species_id: String = "round"
@export_enum("mammal", "fish", "avian") var arms_id: String = "mammal"
@export_enum(
"white",
"gray",
"charcoal",
"brown",
"orange",
"yellow",
"green",
"teal",
) var fur_color_id: String = "white"
@export_enum(
"none",
"antlers_round",
"anteater",
"axolotl",
"bat",
"bear",
"boar",
"bunny",
"butterfly",
"cat",
"donkey",
"elephant",
"fox",
"goat",
"hamster",
"horse",
"long",
"moth",
"opossum",
"owl",
"panda",
"pig",
) var ears_id: String = "none"
@export_enum(
"none",
"anteater",
"axolotl",
"bear",
"bird",
"boar",
"bunny",
"butterfly",
"cat",
"donkey",
"fox",
"gator",
"horse",
"moth",
"opossum",
"pig",
"pointy",
"shark",
) var tail_id: String = "none"
@export_enum("none", "shell") var special_back_id: String = "none"
@export_enum("none", "bird", "owl") var special_beak_id: String = "none"
@export_enum("none", "fin") var special_head_id: String = "none"
@export_enum("none", "goat", "sheep") var special_horns_id: String = "none"
@export_enum("none", "boar", "elephant") var special_tusks_id: String = "none"
@export_enum("none", "bat", "butterfly", "moth") var special_wings_id: String = "none"
@export var eyes_id: String = "simple_shine"
@export var nose_id: String = "dog_round"
@export var mouth_id: String = "three"
@export_category("Head Look")
@export var head_look_enabled: bool = false
@export_range(0.5, 10.0, 0.1, "suffix:m") var head_look_distance: float = 4.0
@export_range(0.5, 3.0, 0.05, "suffix:m") var look_target_height: float = 1.25
@export_range(0.0, 80.0, 1.0, "degrees") var head_yaw_limit: float = 50.0
@export_range(0.0, 45.0, 1.0, "degrees") var head_pitch_limit: float = 22.0
@export_range(0.05, 2.0, 0.05, "suffix:s") var head_blend_seconds: float = 0.3
var _head_look: LookAtModifier3D
var _skeleton: Skeleton3D
var _look_target: Node3D
var _look_target_proxy: Node3D
var _head_look_requested: bool = false
var _animation_player: AnimationPlayer
var _locomotion_walking: bool = false
func _ready() -> void:
var appearance := CharacterCustomizationCatalogType.default_snapshot()
appearance["species"] = species_id
appearance["arms"] = arms_id
appearance["fur_pattern"] = fur_color_id
appearance["ears"] = ears_id
appearance["tail"] = tail_id
appearance["special_back"] = special_back_id
appearance["special_beak"] = special_beak_id
appearance["special_head"] = special_head_id
appearance["special_horns"] = special_horns_id
appearance["special_tusks"] = special_tusks_id
appearance["special_wings"] = special_wings_id
appearance["eyes"] = eyes_id
appearance["nose"] = nose_id
appearance["mouth"] = mouth_id
PlayerVisualPresenterType.apply_appearance(self, appearance)
_animation_player = find_child("AnimationPlayer", true, false) as AnimationPlayer
_prepare_head_look()
_apply_locomotion_animation()
func _process(delta: float) -> void:
if _head_look == null or _look_target_proxy == null:
return
var target_is_valid := (
_look_target != null and is_instance_valid(_look_target)
)
if target_is_valid:
_look_target_proxy.global_position = (
_look_target.global_position + Vector3.UP * look_target_height
)
var target_influence := (
1.0 if _head_look_requested and target_is_valid else 0.0
)
_head_look.influence = move_toward(
_head_look.influence,
target_influence,
delta / maxf(head_blend_seconds, 0.001),
)
func set_head_look_target(target: Node3D) -> void:
_look_target = target
func set_head_look_active(active: bool) -> void:
_head_look_requested = active
func set_locomotion_walking(walking: bool) -> void:
if _locomotion_walking == walking:
return
_locomotion_walking = walking
_apply_locomotion_animation()
func is_locomotion_walking() -> bool:
return _locomotion_walking
func _apply_locomotion_animation() -> void:
if _animation_player == null:
return
var requested: StringName = &"walking" if _locomotion_walking else &"idle"
if (
_animation_player.has_animation(requested)
and _animation_player.current_animation != requested
):
_animation_player.play(requested, 0.18)
func get_head_anchor_position() -> Vector3:
if _skeleton == null:
return global_position + Vector3.UP * 1.8
var head_index := _skeleton.find_bone("head")
if head_index < 0:
return global_position + Vector3.UP * 1.8
var head_pose := _skeleton.get_bone_global_pose(head_index)
return (
(_skeleton.global_transform * head_pose).origin
+ Vector3.UP * 0.85
)
func _prepare_head_look() -> void:
if not head_look_enabled:
set_process(false)
return
_skeleton = find_child("Skeleton3D", true, false) as Skeleton3D
if _skeleton == null or _skeleton.find_bone("head") < 0:
push_warning("World character head-look requires a head bone.")
set_process(false)
return
_look_target_proxy = Node3D.new()
_look_target_proxy.name = "HeadLookTarget"
add_child(_look_target_proxy)
_head_look = LookAtModifier3D.new()
_head_look.name = "HeadLookModifier"
_head_look.bone_name = "head"
_head_look.use_secondary_rotation = true
_head_look.use_angle_limitation = true
_head_look.symmetry_limitation = true
_head_look.primary_limit_angle = deg_to_rad(head_yaw_limit)
_head_look.secondary_limit_angle = deg_to_rad(head_pitch_limit)
_head_look.primary_damp_threshold = 0.65
_head_look.secondary_damp_threshold = 0.65
_head_look.duration = head_blend_seconds
_head_look.influence = 0.0
_head_look.set("origin_from", 1)
_head_look.set("origin_bone_name", "head")
_skeleton.add_child(_head_look)
_head_look.target_node = _head_look.get_path_to(_look_target_proxy)
set_process(true)