Нити цинерария: нож «хватом для нитей» (R), нити урна-ящик как у стриппера, урна-логист перевозит по образцу, зелёные глазницы

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AlexCube
2026-10-16 20:29:50 +03:00
co-authored by Claude Opus 5.5
parent fad57a3756
commit d8e7c8c66a
10 changed files with 885 additions and 63 deletions
+91 -56
View File
@@ -227,15 +227,22 @@ namespace NecromancerTome
float _yaw;
float _phase;
/// <summary>Полёт к месту работы и обратно (16.10: «возвращается задом», «делал действие не сразу, а хоть руками
/// пошевелив»): туда - лицом к месту, над местом - жест руками (дело делается в его середине, см. ActDelay),
/// пошевелив»): туда - лицом к месту, над местом - жест руками (дело делается в его середине, см. Visit),
/// обратно - развернувшись лицом к урне. Точка - в координатах модели урны; _t - время с начала полёта, -1 - дома.</summary>
Vector3 _visit;
struct Stop
{
public Vector3 Hover, Look; // в осях модели урны
public float Fly;
}
readonly List<Stop> _stops = new List<Stop>();
float _backFly;
float _t = -1f;
/// <summary>Скорость полёта, м/с: время перелёта - не меньше OutSeconds/BackSeconds, дальше - по расстоянию (нити до 15 м).</summary>
public static float FlySpeed = 4f;
public static float OutSeconds = 1.3f;
public static float WorkSeconds = 1.6f;
public static float BackSeconds = 1.3f;
/// <summary>Через сколько после начала полёта дело выполняется - в середине жеста.</summary>
public static float ActDelay => OutSeconds + WorkSeconds * 0.5f;
/// <summary>Жест: плечи вперёд до GestureFwd градусов, локти ходят на ±GestureWave вразнобой.</summary>
public static float GestureFwd = 55f;
public static float GestureWave = 30f;
@@ -309,21 +316,63 @@ namespace NecromancerTome
/// <summary>Светящиеся материалы рендереров - вернуть после почернения.</summary>
readonly Dictionary<Renderer, Material[]> _glowing = new Dictionary<Renderer, Material[]>();
/// <summary>Куда смотреть за работой (в координатах модели урны); _beside - дух стоит сбоку от места, а не над ним.</summary>
Vector3 _look;
bool _beside;
/// <summary>Точки - в координатах сцены (без Origin): _stand - где встать (низ клетки), _look - низ клетки места работы.</summary>
public void Visit(Vector3 _stand, Vector3 _lookAt, bool _besideIt)
/// <summary>Облёт мест работы по порядку (у перевозки по нити - ящик «откуда», потом «куда»), у каждого - жест, затем
/// назад к урне. Точки - в координатах сцены (без Origin): stand - где встать (низ клетки), look - низ клетки места,
/// beside - встал сбоку, а не над местом. Возвращает, через сколько секунд дело (середина последнего жеста).</summary>
public float Visit(List<(Vector3 stand, Vector3 look, bool beside)> _list)
{
if (transform.parent == null)
if (transform.parent == null || _list == null || _list.Count == 0)
{
return;
return 0f;
}
_visit = transform.parent.InverseTransformPoint(_stand);
_look = transform.parent.InverseTransformPoint(_lookAt);
_beside = _besideIt;
_stops.Clear();
Vector3 home = Home();
Vector3 prev = home;
float delay = 0f;
foreach ((Vector3 stand, Vector3 look, bool beside) in _list)
{
Stop st = new Stop
{
Hover = transform.parent.InverseTransformPoint(stand) + new Vector3(0f, CinerariumGenie.OffsetY + (beside ? 0.15f : 0.8f), 0f),
Look = transform.parent.InverseTransformPoint(look)
};
st.Fly = Mathf.Max(OutSeconds, Vector3.Distance(prev, st.Hover) / FlySpeed);
delay += st.Fly + WorkSeconds;
prev = st.Hover;
_stops.Add(st);
}
_backFly = Mathf.Max(BackSeconds, Vector3.Distance(prev, home) / FlySpeed);
_t = 0f;
return delay - WorkSeconds * 0.5f;
}
Vector3 Home()
{
return new Vector3(0f, CinerariumGenie.OffsetY, 0f) + Quaternion.Euler(0f, _yaw, 0f) * new Vector3(0f, 0f, CinerariumGenie.Forward);
}
/// <summary>Повернуться лицом по направлению _dir (горизонтально), не быстрее 300°/с.</summary>
void Face(Vector3 _dir)
{
if (_dir.x * _dir.x + _dir.z * _dir.z > 0.01f)
{
_yaw = Mathf.MoveTowardsAngle(_yaw, Mathf.Atan2(_dir.x, _dir.z) * Mathf.Rad2Deg, 300f * Time.deltaTime) % 360f;
}
}
void Gesture(float _w)
{
if (_w > 0f)
{
float wave = Mathf.Sin(_t * Mathf.PI * 2f * 1.5f) * GestureWave * _w;
Pose(Mathf.Lerp(CinerariumGenie.ArmFwd, GestureFwd, _w), CinerariumGenie.Elbow + 15f * _w + wave, CinerariumGenie.Elbow + 15f * _w - wave);
_gesturing = true;
}
else if (_gesturing)
{
Pose();
_gesturing = false;
}
}
public void SetStuck(bool _on)
@@ -379,58 +428,44 @@ namespace NecromancerTome
return; // замер как был
}
float yawTarget = _yaw + CinerariumGenie.TurnSpeed * Time.deltaTime;
Vector3 home = new Vector3(0f, CinerariumGenie.OffsetY, 0f) + Quaternion.Euler(0f, _yaw, 0f) * new Vector3(0f, 0f, CinerariumGenie.Forward);
if (_t >= 0f)
Vector3 home = Home();
if (_t >= 0f && _stops.Count > 0)
{
_t += Time.deltaTime;
if (_t < OutSeconds + WorkSeconds + BackSeconds)
float acc = 0f;
Vector3 from = home;
foreach (Stop st in _stops)
{
// Сбоку - на уровне места (тело вровень со станцией), над местом - выше него.
Vector3 over = _visit + new Vector3(0f, CinerariumGenie.OffsetY + (_beside ? 0.15f : 0.8f), 0f) + Quaternion.Euler(0f, _yaw, 0f) * new Vector3(0f, 0f, CinerariumGenie.Forward);
Vector3 d = _visit - new Vector3(0f, CinerariumGenie.OffsetY, 0f);
bool far = d.x * d.x + d.z * d.z > 0.01f;
float toPlace = far ? Mathf.Atan2(d.x, d.z) * Mathf.Rad2Deg : _yaw;
Vector3 dl = _look - _visit;
float toLook = dl.x * dl.x + dl.z * dl.z > 0.01f ? Mathf.Atan2(dl.x, dl.z) * Mathf.Rad2Deg : toPlace;
float k, face, w = 0f;
if (_t < OutSeconds)
if (_t < acc + st.Fly)
{
k = Mathf.SmoothStep(0f, 1f, _t / OutSeconds);
face = toPlace;
transform.localPosition = Vector3.Lerp(from, st.Hover, Mathf.SmoothStep(0f, 1f, (_t - acc) / st.Fly));
Face(st.Hover - from);
Gesture(0f);
transform.localRotation = Quaternion.Euler(0f, _yaw, 0f);
return;
}
else if (_t < OutSeconds + WorkSeconds)
acc += st.Fly;
if (_t < acc + WorkSeconds)
{
k = 1f;
face = toLook; // за работой - лицом к станции
w = Mathf.Sin((_t - OutSeconds) / WorkSeconds * Mathf.PI);
transform.localPosition = st.Hover;
Face(st.Look - st.Hover); // за работой - лицом к месту
Gesture(Mathf.Sin((_t - acc) / WorkSeconds * Mathf.PI));
transform.localRotation = Quaternion.Euler(0f, _yaw, 0f);
return;
}
else
{
k = 1f - Mathf.SmoothStep(0f, 1f, (_t - OutSeconds - WorkSeconds) / BackSeconds);
face = far ? toPlace + 180f : _yaw; // назад - лицом к урне
}
_yaw = Mathf.MoveTowardsAngle(_yaw, face, 300f * Time.deltaTime) % 360f;
transform.localPosition = Vector3.Lerp(home, over, k);
acc += WorkSeconds;
from = st.Hover;
}
if (_t < acc + _backFly)
{
transform.localPosition = Vector3.Lerp(from, home, Mathf.SmoothStep(0f, 1f, (_t - acc) / _backFly));
Face(home - from); // назад - лицом к урне
Gesture(0f);
transform.localRotation = Quaternion.Euler(0f, _yaw, 0f);
if (w > 0f)
{
float wave = Mathf.Sin(_t * Mathf.PI * 2f * 1.5f) * GestureWave * w;
Pose(Mathf.Lerp(CinerariumGenie.ArmFwd, GestureFwd, w), CinerariumGenie.Elbow + 15f * w + wave, CinerariumGenie.Elbow + 15f * w - wave);
_gesturing = true;
}
else if (_gesturing)
{
Pose();
_gesturing = false;
}
return;
}
_t = -1f;
if (_gesturing)
{
Pose();
_gesturing = false;
}
Gesture(0f);
}
_yaw = yawTarget % 360f;
float bob = CinerariumGenie.BobPeriod > 0.01f