using Godot; namespace Cirno.Scripts.Components.FSM._3DPlayer; public partial class ShadowModule : ModuleBase { [Export] public NodePath ShadowPath; [Export] public float MaxShadowScale = 1.2f; [Export] public float MinShadowScale = 0.5f; [Export] public float MaxShadowHeight = 5f; private IStateMachine _machine; private CharacterBody3D MainObject => _machine.MainObject; [Export] public Node3D Shadow { get; private set; } [Export(PropertyHint.Layers3DPhysics)] public uint CollisionMask { get; set; } public override void EnterState(PlayerState state) { } public override void ExitState(PlayerState state) { } public override void Init(IStateMachine machine) { _machine = machine; } public override void Process(double delta) { } public override void PhysicsProcess(double delta) { if (Shadow == 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, CollisionMask = CollisionMask, Exclude = [MainObject.GetRid()] }); 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); Shadow.GlobalPosition = new Vector3(origin.X, groundPos.Y + 0.01f, origin.Z); Shadow.Scale = new Vector3(scale, 1f, scale); } else { Shadow.Visible = false; } } }