cirnogodot/Scripts/Components/Actors/ShadowProvider.cs
2025-06-22 13:52:23 +02:00

89 lines
No EOL
2.3 KiB
C#

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<Rid> 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;
}
}
}