mirror of
https://gitlab.com/MaddoScientisto/cirnogodot.git
synced 2026-06-01 08:55:35 +00:00
302 lines
8.1 KiB
C#
302 lines
8.1 KiB
C#
|
|
using Godot;
|
||
|
|
|
||
|
|
namespace Cirno.Scripts.Actors._3D;
|
||
|
|
|
||
|
|
public partial class Laser : Area3D
|
||
|
|
{
|
||
|
|
public enum LaserState
|
||
|
|
{
|
||
|
|
Inactive,
|
||
|
|
Warning,
|
||
|
|
Expanding,
|
||
|
|
Active,
|
||
|
|
Finished
|
||
|
|
}
|
||
|
|
|
||
|
|
// ================= CONFIG =================
|
||
|
|
|
||
|
|
[Export] public LaserConfig Config;
|
||
|
|
|
||
|
|
// ================= NODES =================
|
||
|
|
|
||
|
|
private MeshInstance3D _mesh;
|
||
|
|
private CollisionShape3D _collision;
|
||
|
|
private RayCast3D _ray;
|
||
|
|
|
||
|
|
private ShaderMaterial _beamMaterial;
|
||
|
|
private float _currentRadius;
|
||
|
|
|
||
|
|
// ================= STATE =================
|
||
|
|
|
||
|
|
private LaserState _state = LaserState.Inactive;
|
||
|
|
private float _stateTimer = 0f;
|
||
|
|
|
||
|
|
private Vector3 _origin;
|
||
|
|
private Vector3 _direction;
|
||
|
|
private float _currentLength;
|
||
|
|
|
||
|
|
// ================= SETUP =================
|
||
|
|
|
||
|
|
public override void _Ready()
|
||
|
|
{
|
||
|
|
_mesh = GetNode<MeshInstance3D>("MeshInstance3D");
|
||
|
|
_collision = GetNode<CollisionShape3D>("CollisionShape3D");
|
||
|
|
_ray = GetNode<RayCast3D>("RayCast3D");
|
||
|
|
|
||
|
|
_beamMaterial = _mesh.MaterialOverride as ShaderMaterial;
|
||
|
|
|
||
|
|
_ray.CollisionMask = Config.GeometryLayer;
|
||
|
|
ChangeCollisionStateDeferred(true);
|
||
|
|
//_collision.Disabled = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
private void ChangeCollisionStateDeferred(bool value)
|
||
|
|
{
|
||
|
|
_collision.SetDeferred(CollisionShape3D.PropertyName.Disabled, value);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ================= SPAWN API =================
|
||
|
|
|
||
|
|
public void SpawnFromDirection(Vector3 origin, Vector3 direction)
|
||
|
|
{
|
||
|
|
_origin = origin;
|
||
|
|
_direction = direction.Normalized();
|
||
|
|
StartLaser();
|
||
|
|
}
|
||
|
|
|
||
|
|
public void SpawnFromTarget(Vector3 origin, Vector3 target)
|
||
|
|
{
|
||
|
|
_origin = origin;
|
||
|
|
_direction = (target - origin).Normalized();
|
||
|
|
StartLaser();
|
||
|
|
}
|
||
|
|
|
||
|
|
private void StartLaser()
|
||
|
|
{
|
||
|
|
GlobalPosition = _origin;
|
||
|
|
//Basis = Basis.LookingAt(_origin + _direction, Vector3.Up);
|
||
|
|
|
||
|
|
_mesh.Scale = Vector3.One;
|
||
|
|
_collision.Scale = Vector3.One;
|
||
|
|
|
||
|
|
_stateTimer = 0f;
|
||
|
|
_state = Config.WarningDuration > 0
|
||
|
|
? LaserState.Warning
|
||
|
|
: LaserState.Expanding;
|
||
|
|
|
||
|
|
//UpdateLaserLength(MaxLength);
|
||
|
|
UpdateVisualOrientation();
|
||
|
|
SetRadius(Config.WarningRadius);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ================= PROCESS =================
|
||
|
|
|
||
|
|
public override void _Process(double delta)
|
||
|
|
{
|
||
|
|
if (_state == LaserState.Finished)
|
||
|
|
return;
|
||
|
|
|
||
|
|
_stateTimer += (float)delta;
|
||
|
|
|
||
|
|
UpdateRaycast();
|
||
|
|
UpdateBeam();
|
||
|
|
|
||
|
|
switch (_state)
|
||
|
|
{
|
||
|
|
case LaserState.Warning:
|
||
|
|
if (_stateTimer >= Config.WarningDuration)
|
||
|
|
TransitionTo(LaserState.Expanding);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case LaserState.Expanding:
|
||
|
|
HandleExpansion();
|
||
|
|
break;
|
||
|
|
|
||
|
|
case LaserState.Active:
|
||
|
|
if (Config.ActiveDuration >= 0 &&
|
||
|
|
_stateTimer >= Config.ActiveDuration)
|
||
|
|
TransitionTo(LaserState.Finished);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ================= STATE HANDLING =================
|
||
|
|
|
||
|
|
private void TransitionTo(LaserState next)
|
||
|
|
{
|
||
|
|
_state = next;
|
||
|
|
_stateTimer = 0f;
|
||
|
|
|
||
|
|
switch (next)
|
||
|
|
{
|
||
|
|
case LaserState.Expanding:
|
||
|
|
if (Config.ExpansionDuration <= 0f)
|
||
|
|
{
|
||
|
|
SetRadius(Config.DamageRadius);
|
||
|
|
EnableCollision();
|
||
|
|
TransitionTo(LaserState.Active);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case LaserState.Active:
|
||
|
|
EnableCollision();
|
||
|
|
SetRadius(Config.DamageRadius);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case LaserState.Finished:
|
||
|
|
QueueFree();
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void HandleExpansion()
|
||
|
|
{
|
||
|
|
if (_stateTimer < Config.ExpansionDelay)
|
||
|
|
return;
|
||
|
|
|
||
|
|
var t = Mathf.Clamp(
|
||
|
|
(_stateTimer - Config.ExpansionDelay) / Mathf.Max(Config.ExpansionDuration, 0.001f),
|
||
|
|
0f, 1f
|
||
|
|
);
|
||
|
|
|
||
|
|
var radius = Mathf.Lerp(Config.WarningRadius, Config.DamageRadius, t);
|
||
|
|
SetRadius(radius);
|
||
|
|
|
||
|
|
if (t >= 1f)
|
||
|
|
TransitionTo(LaserState.Active);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ================= RAYCAST =================
|
||
|
|
|
||
|
|
private void UpdateRaycast()
|
||
|
|
{
|
||
|
|
_ray.TargetPosition = _direction * Config.MaxLength;
|
||
|
|
_ray.CollisionMask = Config.GeometryLayer; // only solid obstacles
|
||
|
|
_ray.ForceRaycastUpdate();
|
||
|
|
|
||
|
|
_currentLength = _ray.IsColliding()
|
||
|
|
? GlobalPosition.DistanceTo(_ray.GetCollisionPoint())
|
||
|
|
: Config.MaxLength;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ================= VISUALS & COLLISION =================
|
||
|
|
|
||
|
|
private void UpdateVisualOrientation()
|
||
|
|
{
|
||
|
|
// Godot's LookingAt aligns the -Z axis to the target direction.
|
||
|
|
// Our beam is built along +Y, so we must compensate.
|
||
|
|
|
||
|
|
Basis look = Basis.LookingAt(_direction, Vector3.Up);
|
||
|
|
|
||
|
|
// Rotate so +Y becomes forward instead of -Z
|
||
|
|
// This is a fixed correction: -Z → +Y
|
||
|
|
Basis correction = new Basis(Vector3.Right, Mathf.Pi / 2f);
|
||
|
|
|
||
|
|
Basis finalBasis = look * correction;
|
||
|
|
|
||
|
|
_mesh.Basis = finalBasis;
|
||
|
|
_collision.Basis = finalBasis;
|
||
|
|
}
|
||
|
|
|
||
|
|
private void UpdateBeam()
|
||
|
|
{
|
||
|
|
// 1. Set mesh height (NO SCALE)
|
||
|
|
if (_mesh.Mesh is CylinderMesh cyl)
|
||
|
|
cyl.Height = _currentLength;
|
||
|
|
|
||
|
|
// 2. Set collision height
|
||
|
|
if (_collision.Shape is CapsuleShape3D capsule)
|
||
|
|
capsule.Height = _currentLength;
|
||
|
|
|
||
|
|
// 3. Position at exact world-space center
|
||
|
|
Vector3 center = _origin + _direction * (_currentLength * 0.5f);
|
||
|
|
|
||
|
|
Transform3D meshXform = _mesh.GlobalTransform;
|
||
|
|
meshXform.Origin = center;
|
||
|
|
_mesh.GlobalTransform = meshXform;
|
||
|
|
|
||
|
|
Transform3D colXform = _collision.GlobalTransform;
|
||
|
|
colXform.Origin = center;
|
||
|
|
_collision.GlobalTransform = colXform;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
private Vector3 GetBeamCenterWorld()
|
||
|
|
{
|
||
|
|
return _origin + _direction * (_currentLength * 0.5f);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void SetRadius(float radius)
|
||
|
|
{
|
||
|
|
_currentRadius = radius;
|
||
|
|
|
||
|
|
if (_mesh.Mesh is CylinderMesh cyl)
|
||
|
|
{
|
||
|
|
cyl.TopRadius = radius;
|
||
|
|
cyl.BottomRadius = radius;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_collision.Shape is CapsuleShape3D capsule)
|
||
|
|
capsule.Radius = radius;
|
||
|
|
|
||
|
|
UpdateMaterial();
|
||
|
|
}
|
||
|
|
|
||
|
|
private void EnableCollision()
|
||
|
|
{
|
||
|
|
ChangeCollisionStateDeferred(false);
|
||
|
|
//_collision.Disabled = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ======================================================
|
||
|
|
// POINT-INSIDE-LASER QUERY (NO PHYSICS REQUIRED)
|
||
|
|
// ======================================================
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Returns true if the given point lies within the
|
||
|
|
/// current laser segment and its damage radius.
|
||
|
|
/// </summary>
|
||
|
|
public bool IsPointInsideBeam(Vector3 worldPoint)
|
||
|
|
{
|
||
|
|
if (_state != LaserState.Active)
|
||
|
|
return false;
|
||
|
|
|
||
|
|
Vector3 local = worldPoint - _origin;
|
||
|
|
|
||
|
|
float projection = local.Dot(_direction);
|
||
|
|
|
||
|
|
if (projection < 0 || projection > _currentLength)
|
||
|
|
return false;
|
||
|
|
|
||
|
|
Vector3 closestPoint =
|
||
|
|
_origin + _direction * projection;
|
||
|
|
|
||
|
|
float distanceSq =
|
||
|
|
worldPoint.DistanceSquaredTo(closestPoint);
|
||
|
|
|
||
|
|
float radius = Config.DamageRadius;
|
||
|
|
return distanceSq <= radius * radius;
|
||
|
|
}
|
||
|
|
|
||
|
|
private void UpdateMaterial()
|
||
|
|
{
|
||
|
|
if (_beamMaterial == null)
|
||
|
|
return;
|
||
|
|
|
||
|
|
_beamMaterial.SetShaderParameter("beam_length", _currentLength);
|
||
|
|
_beamMaterial.SetShaderParameter("beam_radius", _currentRadius);
|
||
|
|
|
||
|
|
switch (_state)
|
||
|
|
{
|
||
|
|
case LaserState.Warning:
|
||
|
|
_beamMaterial.SetShaderParameter("beam_color", new Color(1f, 1f, 0.2f));
|
||
|
|
_beamMaterial.SetShaderParameter("intensity", 0.5f);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case LaserState.Active:
|
||
|
|
_beamMaterial.SetShaderParameter("beam_color", new Color(1f, 0.2f, 0.2f));
|
||
|
|
_beamMaterial.SetShaderParameter("intensity", 1.0f);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|