using Godot; using Godot.Collections; namespace Cirno.Scripts.Components.Actors; public partial class ShadowProvider : Node3D { [Export] public bool AutoEnable { get; private set; } = true; public bool Enabled { get; private set; } [Export] public float MaxShadowScale = 1.2f; [Export] public float MinShadowScale = 0.5f; [Export] public float MaxShadowHeight = 5f; [Export(PropertyHint.Layers3DPhysics)] public uint CollisionMask { get; set; } private Node3D _mainObject; private Array Exclusions = []; public void Init(Node3D mainObject) { _mainObject = mainObject; if (_mainObject is CharacterBody3D body) { Exclusions.Add(body.GetRid()); } } public void Enable() { Enabled = true; } public void Disable() { Enabled = false; } public override void _Ready() { if (!AutoEnable) return; Init(GetParentNode3D()); Enable(); } public override void _PhysicsProcess(double delta) { if (!Enabled || _mainObject is null) return; // Raycast down to get ground height Vector3 origin = _mainObject.GlobalTransform.Origin; Vector3 from = origin + Vector3.Up * 0.5f; Vector3 to = origin + Vector3.Down * 20f; var spaceState = _mainObject.GetWorld3D().DirectSpaceState; var result = spaceState.IntersectRay(new PhysicsRayQueryParameters3D { From = from, To = to, CollideWithBodies = true, CollideWithAreas = false, CollisionMask = CollisionMask, Exclude = Exclusions }); if (result.Count > 0) { Vector3 groundPos = (Vector3)result["position"]; float height = origin.Y - groundPos.Y; // Clamp and scale float t = Mathf.Clamp(height / MaxShadowHeight, 0f, 1f); float scale = Mathf.Lerp(MaxShadowScale, MinShadowScale, t); this.GlobalPosition = new Vector3(origin.X, groundPos.Y + 0.01f, origin.Z); this.Scale = new Vector3(scale, scale, scale); this.Visible = true; } else { this.Visible = false; } } }