Files
valheim/Assets/Magic_Spline/Scripts/Spline.cs
2025-07-04 14:16:14 +08:00

343 lines
9.5 KiB
C#

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class SplineDraggableContext
{
public GameObject Dragable;
public float Time;
public bool Invalid;
public GameObject DependentObject;
}
public enum DraggableSplineEndAction
{
Respawn,
Destroy
}
public class Spline : MonoBehaviour
{
public GameObject DraggablePrefab;
public float NewNodeOffset;
public List<SplineSegment> Segments = new List<SplineSegment>();
public float SpawnStartDelay = 0;
public float SpawnDelay = 1;
public int SpawnCount = 0;
public float MoveSpeed = 0.5f;
public DraggableSplineEndAction DraggableSplineEndAction;
public float PreWarmTime = 100f;
private float _spawnTimer;
private float _totalLen;
private int _spawned;
private List<SplineDraggableContext> _contexts = new List<SplineDraggableContext>();
public void AddNode()
{
Vector3 point = transform.position;
Vector3 dir = Vector3.forward;
float len = NewNodeOffset;
if (_lines != null && _lines.Count > 0)
{
dir = (_lines.Last().To - _lines.Last().From).normalized;
point = _lines.Last().From;
}
var obj = new GameObject($"Spline{Segments.Count}");
obj.transform.parent = transform;
obj.transform.position = point + dir * len;
var segment = obj.AddComponent<SplineSegment>();
var lastSegment = Segments.LastOrDefault();
segment.H1 = new GameObject("Handle1").AddComponent<SplineHandle>();
segment.H2 = new GameObject("Handle2").AddComponent<SplineHandle>();
segment.H1.transform.parent = segment.transform;
segment.H1.transform.position = point + dir * (len / 3);
segment.H2.transform.parent = segment.transform;
segment.H2.transform.position = point + dir * ((len / 3) * 2);
Segments.Add(segment);
}
public void Close()
{
var first = Segments.First();
var last = Segments.Last();
last.transform.position = transform.position;
Init();
Segments.First().H1.SnapOppositeToAxis();
}
private bool Changed()
{
return Segments.Select(_ => _.Changed()).ToArray().Any(_ => _);
}
// Start is called before the first frame update
void Start()
{
}
void OnValidate()
{
Init();
}
void DrawDraggable(SplineDraggableContext ctx, float delta)
{
ctx.Time += delta;
var dist = MoveSpeed * ctx.Time;
if (dist > _totalLen)
{
if (DraggableSplineEndAction == DraggableSplineEndAction.Respawn)
{
dist = dist % _totalLen;
var loopTime = _totalLen / MoveSpeed;
ctx.Time = ctx.Time % loopTime;
}
else
{
ctx.Invalid = true;
return;
}
}
for (int i = 0; i < _lines.Count; ++i)
{
if (dist < _lines[i].Length)
{
var dir = (_lines[i].To - _lines[i].From).normalized;
ctx.Dragable.transform.position = _lines[i].From;
ctx.Dragable.transform.LookAt(_lines[i].To);
return;
}
else
dist -= _lines[i].Length;
}
}
void DoPrewarm()
{
while (PreWarmTime > 0)
{
var dt = 0f;
if (SpawnCount == 0 || _spawned < SpawnCount)
{
if (_contexts.Count == 0)
{
var d = SpawnStartDelay;
PreWarmTime -= d;
}
else
{
var d = SpawnDelay;
PreWarmTime -= d;
}
var obj = Instantiate(DraggablePrefab, transform);
var particleSystem = obj.GetComponentInChildren<ParticleSystem>();
_contexts.Add(new SplineDraggableContext()
{
Dragable = obj,
DependentObject = particleSystem != null ? particleSystem.gameObject : null
});
++_spawned;
if (PreWarmTime > 0)
{
dt = Mathf.Min(SpawnDelay, PreWarmTime);
PreWarmTime -= dt;
}
}
else
{
dt = PreWarmTime;
PreWarmTime = 0;
}
foreach (var context in _contexts)
{
DrawDraggable(context, dt);
if (context.Invalid || context.DependentObject == null)
Destroy(context.Dragable);
}
_contexts.RemoveAll(_ => _.Invalid || _.DependentObject == null);
}
}
// Update is called once per frame
void Update()
{
if(Changed())
Init();
if(Segments.RemoveAll(_ => _ == null) > 0)
Init();
if (PreWarmTime > 0)
{
DoPrewarm();
}
var dt = Time.deltaTime;
if (SpawnCount == 0 || _spawned < SpawnCount)
{
var spawnNew = false;
_spawnTimer += dt;
if (_contexts.Count == 0)
{
if (spawnNew = _spawnTimer > SpawnStartDelay)
{
_spawnTimer -= SpawnStartDelay;
}
}
else
{
if (spawnNew = _spawnTimer > SpawnDelay)
_spawnTimer -= SpawnDelay;
}
if (spawnNew)
{
var obj = Instantiate(DraggablePrefab, transform);
var particleSystem = obj.GetComponentInChildren<ParticleSystem>();
_contexts.Add(new SplineDraggableContext()
{
Dragable = obj,
DependentObject = particleSystem != null ? particleSystem.gameObject : null
});
++_spawned;
}
}
foreach (var context in _contexts)
{
DrawDraggable(context, dt);
if (context.Invalid || context.DependentObject == null)
Destroy(context.Dragable);
}
_contexts.RemoveAll(_ => _.Invalid || _.DependentObject == null);
PreWarmTime = 0;
}
void Init()
{
_lines.Clear();
_controls.Clear();
_totalLen = 0;
Segments.RemoveAll(_ => _ == null);
var p0 = transform.position;
for(var s = 0; s < Segments.Count; ++s)
{
var segment = Segments[s];
Vector3 p1 = segment.H1.transform.position;
Vector3 p2 = segment.H2.transform.position;
Vector3 p3 = segment.transform.position;
Vector3 v0 = p0;
Gizmos.color = Color.white;
for (int i = 1; i < 1001; i++)
{
var t = i / 1000.0f;
var v1 = Mathf.Pow(1f - t, 3) * p0
+ 3 * t * Mathf.Pow(1 - t, 2) * p1
+ 3 * Mathf.Pow(t, 2) * (1 - t) * p2
+ Mathf.Pow(t, 3) * p3;
var len = Vector3.Distance(v0, v1);
_totalLen += len;
_lines.Add(new Line()
{
From = v0,
To = v1,
Length = len
});
v0 = v1;
}
_controls.Add(new Line()
{
From = p0,
To = p1
});
_controls.Add(new Line()
{
From = p3,
To = p2
});
p0 = p3;
segment.H1.Opposite = null;
segment.H1.Origin = null;
segment.H2.Opposite = null;
segment.H2.Origin = null;
if (s > 0)
{
var prevSegment = Segments[s - 1];
prevSegment.H2.Opposite = segment.H1.gameObject;
prevSegment.H2.Origin = prevSegment.gameObject;
segment.H1.Opposite = prevSegment.H2.gameObject;
segment.H1.Origin = prevSegment.gameObject;
if (s + 1 == Segments.Count)
{
var firstSegment = Segments.First();
var dist = Vector3.Distance(segment.transform.position, transform.position);
if (dist < 0.0000001f)
{
segment.H2.Opposite = firstSegment.H1.gameObject;
segment.H2.Origin = gameObject;
firstSegment.H1.Opposite = segment.H2.gameObject;
firstSegment.H1.Origin = gameObject;
}
}
}
}
Debug.Log($"Full spline len {_totalLen}.");
transform.hasChanged = false;
}
void OnDrawGizmos()
{
if(Segments.RemoveAll(_ => _ == null) > 0)
Init();
if (Changed())
Init();
Gizmos.color = Color.white;
foreach (var line in _lines)
{
Gizmos.DrawLine(line.From, line.To);
}
Gizmos.color = Color.green;
foreach (var line in _controls)
{
Gizmos.DrawLine(line.From, line.To);
}
}
private List<Line> _controls = new List<Line>();
private List<Line> _lines = new List<Line>();
struct Line
{
public Vector3 From;
public Vector3 To;
public float Length;
}
}