Огонёк оссуария: подъём снаружи котла и дуга точно через центр окошка (#69)

22.10: «Огонёк летит сквозь котёл. А надо чтобы поднимался почти на
уровень окошка, и по дуге летел прямо через его центр». Путь - гладкая
кривая через точки: нутро, центр окошка, дуга снаружи, вертикаль на 25 см
дальше самого широкого места котла (по модели), земля. Скорость по пути
ровная. necroossuary wisp log - вылет/влёт в лог (чтобы снять).
Проверено в test8 снимками по сигналу из лога.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AlexCube
2026-10-22 23:13:30 +03:00
co-authored by Claude Opus 5.5
parent 643c9fa056
commit ead6823c8a
3 changed files with 77 additions and 22 deletions
+77 -22
View File
@@ -17,6 +17,8 @@ namespace NecromancerTome
public static bool Enabled = true;
/// <summary>Дикий дух-огонёк (#69): летает, пока оссуарий работает. Выключатель - necroossuary wisp off.</summary>
public static bool WispEnabled = true;
/// <summary>necroossuary wisp log - писать в лог вылет и влёт (чтобы успеть снять).</summary>
public static bool WispLog;
public const string WispPath = "#@modfolder(NecromancerTome):Resources/necrowisp?necroWispPrefab.prefab";
static GameObject _wispPrefab;
static bool _wispTried;
@@ -196,7 +198,10 @@ namespace NecromancerTome
/// <summary>Окошки крышки (модель #67): шесть, между швами, через 60°; по высоте - на WindowUp от низа крышки,
/// от оси - на WindowR (доли полуширины крышки; в модели 0.14 м и 0.37 м при полуширине 0.418).</summary>
const float WindowUp = 0.14f / 0.836f, WindowR = 0.37f / 0.418f;
const float Out = 0.9f; // насколько дальше от оси котла дух уходит в землю, м
/// <summary>Подъём и спуск - вертикально снаружи, на Clear дальше самого широкого места котла (22.10: «Огонёк летит сквозь
/// котёл. А надо чтобы поднимался почти на уровень окошка, и по дуге летел прямо через его центр»).</summary>
const float Clear = 0.25f;
const float BelowWindow = 0.1f; // вертикаль кончается на столько ниже окошка, м; дальше дуга
const float Under = 0.35f; // насколько под землю, м
const float FlyTime = 2.4f;
static readonly int TintId = Shader.PropertyToID("_TintColor");
@@ -208,7 +213,7 @@ namespace NecromancerTome
TrailRenderer _trail;
int _phase = -1; // -1 дома, 0 вылет и уход в землю, 1 под землёй, 2 подъём и влёт
float _t0, _until;
Vector3 _winOut, _winIn, _inOut, _inIn, _down, _up;
Vector3[] _pathOut, _pathIn; // вылет: нутро -> окошко -> дуга -> вниз в землю; влёт - обратный порядок с другой стороны
static float Ease(float _k) => _k * _k * (3f - 2f * _k);
@@ -239,12 +244,16 @@ namespace NecromancerTome
{
return;
}
Show(_inOut);
Show(_pathOut[0]);
if (OssuaryGlow.WispLog)
{
Debug.Log("[NecromancerTome] OssuaryWisp: вылет " + _pathOut[1]);
}
Next(0, FlyTime);
return;
case 0: // из окошка наружу и вниз, в землю; тает
Fly(_inOut, _winOut, _down, k);
Alpha(1f - Ease(Mathf.InverseLerp(0.25f, 1f, k)));
Fly(_pathOut, k);
Alpha(1f - Ease(Mathf.InverseLerp(0.45f, 1f, k)));
if (k >= 1f)
{
Hide();
@@ -255,13 +264,17 @@ namespace NecromancerTome
if (now >= _until)
{
Alpha(0f);
Show(_up);
Show(_pathIn[0]);
if (OssuaryGlow.WispLog)
{
Debug.Log("[NecromancerTome] OssuaryWisp: влёт " + _pathIn[_pathIn.Length - 2]);
}
Next(2, FlyTime);
}
return;
case 2: // с другой стороны снизу вверх, проявляется, влетает в противоположное окошко
Fly(_up, _winIn, _inIn, k);
Alpha(Ease(Mathf.InverseLerp(0f, 0.75f, k)));
Fly(_pathIn, k);
Alpha(Ease(Mathf.InverseLerp(0f, 0.55f, k)));
if (k >= 1f)
{
Hide();
@@ -279,41 +292,77 @@ namespace NecromancerTome
_until = _t0 + _dur;
}
/// <summary>Квадратичная кривая _a -> _b -> _c: проходит окошко _b по касательной.</summary>
void Fly(Vector3 _a, Vector3 _b, Vector3 _c, float _k)
/// <summary>Гладкая кривая (Catmull-Rom) точно через точки пути; скорость по пути ровная (время - по длинам отрезков).</summary>
void Fly(Vector3[] _path, float _k)
{
float k = Ease(_k);
Vector3 p = (1f - k) * (1f - k) * _a + 2f * k * (1f - k) * _b + k * k * _c;
p.y += 0.02f * Mathf.Sin(Time.time * 7f); // лёгкое дрожание огонька
float total = 0f;
for (int i = 1; i < _path.Length; i++)
{
total += Vector3.Distance(_path[i - 1], _path[i]);
}
float s = Ease(_k) * total;
int seg = 0;
float len = 0f;
for (; seg < _path.Length - 2; seg++)
{
len = Vector3.Distance(_path[seg], _path[seg + 1]);
if (s <= len)
{
break;
}
s -= len;
}
len = Vector3.Distance(_path[seg], _path[seg + 1]);
float u = len > 0.0001f ? Mathf.Clamp01(s / len) : 1f;
Vector3 p0 = _path[Mathf.Max(0, seg - 1)], p1 = _path[seg], p2 = _path[seg + 1], p3 = _path[Mathf.Min(_path.Length - 1, seg + 2)];
Vector3 p = 0.5f * (2f * p1 + (p2 - p0) * u + (2f * p0 - 5f * p1 + 4f * p2 - p3) * u * u + (3f * p1 - p0 - 3f * p2 + p3) * u * u * u);
p.y += 0.015f * Mathf.Sin(Time.time * 7f); // лёгкое дрожание огонька
_go.transform.position = p;
}
/// <summary>Окошко вылета - случайное из шести, влёта - напротив; точки в земле - снаружи от них, высота земли - лучом.</summary>
/// <summary>Окошко вылета - случайное из шести, влёта - напротив. Путь: нутро, центр окошка, точка дуги снаружи на высоте
/// окошка, вертикаль снаружи котла чуть ниже окошка, та же вертикаль под землёй (высота земли - лучом).</summary>
bool Places(Transform _model)
{
Transform lid = _model.Find("lid");
Transform body = _model.Find("body");
Renderer lr = lid != null ? lid.GetComponent<Renderer>() : null;
Renderer br = body != null ? body.GetComponent<Renderer>() : null;
if (lr == null)
{
return false;
}
Bounds b = lr.bounds;
Vector3 axis = new Vector3(b.center.x, b.min.y + WindowUp * (b.size.x + b.size.z) * 0.5f, b.center.z);
float half = (b.size.x + b.size.z) * 0.25f;
float bodyHalf = br != null ? (br.bounds.size.x + br.bounds.size.z) * 0.25f : half * 1.1f;
float winY = b.min.y + WindowUp * half * 2f;
Vector3 axis = new Vector3(b.center.x, winY, b.center.z);
int w = Random.Range(0, 6);
Vector3 dOut = lid.rotation * Quaternion.Euler(0f, w * 60f, 0f) * Vector3.right;
dOut.y = 0f;
dOut.Normalize();
Vector3 dIn = -dOut;
_winOut = axis + dOut * (half * WindowR);
_winIn = axis + dIn * (half * WindowR);
_inOut = axis + dOut * (half * WindowR * 0.4f);
_inIn = axis + dIn * (half * WindowR * 0.4f);
_down = Ground(axis + dOut * (half + Out)) + Vector3.down * Under;
_up = Ground(axis + dIn * (half + Out)) + Vector3.down * Under;
_pathOut = Path(axis, dOut, half, Mathf.Max(bodyHalf, half) + Clear);
_pathIn = Path(axis, -dOut, half, Mathf.Max(bodyHalf, half) + Clear);
System.Array.Reverse(_pathIn);
return true;
}
Vector3[] Path(Vector3 _axis, Vector3 _dir, float _half, float _rOut)
{
float rWin = _half * WindowR;
Vector3 low = _axis + _dir * _rOut + Vector3.down * BelowWindow;
Vector3 under = Ground(_axis + _dir * _rOut) + Vector3.down * Under;
return new[]
{
_axis + _dir * (rWin * 0.45f), // внутри котла
_axis + _dir * rWin, // центр окошка
_axis + _dir * (rWin + (_rOut - rWin) * 0.55f), // дуга: снаружи на высоте окошка
low, // вертикаль снаружи, чуть ниже окошка
new Vector3(low.x, Mathf.Lerp(low.y, under.y, 0.5f), low.z),
under // в земле
};
}
Vector3 Ground(Vector3 _p)
{
if (Physics.Raycast(_p + Vector3.up * 1.5f, Vector3.down, out RaycastHit hit, 8f, SnakeControl.WallMask, QueryTriggerInteraction.Ignore))
@@ -547,6 +596,12 @@ namespace NecromancerTome
con.Output("necroossuary: пульсация " + OssuaryGlow.GlowPulse + ", скорость " + OssuaryGlow.GlowPulseSpeed);
break;
case "wisp":
if (_params.Count > 1 && _params[1] == "log")
{
OssuaryGlow.WispLog = !OssuaryGlow.WispLog;
con.Output("necroossuary: лог огонька " + (OssuaryGlow.WispLog ? "включён" : "выключен"));
break;
}
if (_params.Count > 1)
{
OssuaryGlow.WispEnabled = _params[1] == "on";
Binary file not shown.
Binary file not shown.