Некротехнолог: джинн из урны - появляется по черепу в багажнике
Дух - не сущность, а модель из бандла necrotechnologist, дочерний объект модели урны (как каркас зоны): без хитбокса, ИИ и сохранения. Кончик хвоста в горлышке, дух покачивается и медленно поворачивается; тело полупрозрачное (0.3, как у торговцев), глаза светятся. Появляется и исчезает с порывом (necroTechGust). Команда necrogenie - подгонка высоты, поворота, покачивания и прозрачности в игре. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
35313f2e90
commit
0558f46069
@@ -69,6 +69,7 @@ namespace NecromancerTome
|
||||
{
|
||||
base.OnRemove(_world);
|
||||
RemoveBounds();
|
||||
RemoveGenie(false);
|
||||
XUiC_NecroCinerariumSkullGrid.CloseIfShowing(this);
|
||||
if (!ConnectionManager.Instance.IsServer)
|
||||
{
|
||||
@@ -94,6 +95,7 @@ namespace NecromancerTome
|
||||
{
|
||||
base.OnUnload(_world);
|
||||
RemoveBounds();
|
||||
RemoveGenie(false);
|
||||
XUiC_NecroCinerariumSkullGrid.CloseIfShowing(this);
|
||||
}
|
||||
|
||||
@@ -145,6 +147,7 @@ namespace NecromancerTome
|
||||
{
|
||||
SpawnBounds();
|
||||
}
|
||||
SyncGenie(false);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
@@ -160,6 +163,7 @@ namespace NecromancerTome
|
||||
}
|
||||
HasSkull = _has;
|
||||
SetModified();
|
||||
SyncGenie(true);
|
||||
Debug.Log("[NecromancerTome] Cinerarium: урна " + ToWorldPos() + (_has ? " - череп положен" : " - череп вынут"));
|
||||
}
|
||||
|
||||
@@ -202,6 +206,44 @@ namespace NecromancerTome
|
||||
return true;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Джинн из урны (CinerariumGenie.cs): есть череп - есть дух.
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
GameObject _genie;
|
||||
public const string GenieSound = "necroTechGust";
|
||||
|
||||
/// <summary>_sound - череп только что положили или вынули (порыв); при загрузке чанка дух появляется молча.</summary>
|
||||
public void SyncGenie(bool _sound)
|
||||
{
|
||||
if (HasSkull && _genie == null)
|
||||
{
|
||||
_genie = CinerariumGenie.Spawn(ModelTransform());
|
||||
if (_genie != null && _sound)
|
||||
{
|
||||
Audio.Manager.BroadcastPlay(World.blockToTransformPos(ToWorldPos()), GenieSound);
|
||||
}
|
||||
}
|
||||
else if (!HasSkull && _genie != null)
|
||||
{
|
||||
RemoveGenie(_sound);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveGenie(bool _sound)
|
||||
{
|
||||
if (_genie == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
UnityEngine.Object.Destroy(_genie);
|
||||
_genie = null;
|
||||
if (_sound)
|
||||
{
|
||||
Audio.Manager.BroadcastPlay(World.blockToTransformPos(ToWorldPos()), GenieSound);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Границы зоны: каркас параллелепипеда, как у земельного камня. Дочерний объект модели блока - сдвиги
|
||||
// Origin и выгрузка чанка его не теряют (как кольцо пирамиды); материал один на все урны.
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Scripting;
|
||||
|
||||
namespace NecromancerTome
|
||||
{
|
||||
/// <summary>
|
||||
/// Дух-работник урны - «джинн из урны» (решение 16.10): кончик хвоста уходит в горлышко, дух вырастает над урной,
|
||||
/// без работы висит, чуть покачиваясь и медленно поворачиваясь. Этап 3 некротехнолога.
|
||||
///
|
||||
/// Не сущность игры, а видимая модель - дочерний объект модели блока урны, как каркас зоны (решение 16.10 вместо
|
||||
/// плана 15.10 «сущность на префабе медсестры»): хитбокса, ИИ и сохранения у неё нет, поэтому «убить нельзя»,
|
||||
/// «зомби не замечают», «сквозь стены» выходят сами, а сейвы дух не трогает - он появляется по черепу в урне
|
||||
/// (TEFeatureCinerarium.HasSkull) и пропадает без него. Сдвиги Origin и выгрузку чанка переживает с моделью урны.
|
||||
///
|
||||
/// Модель - бандл necrotechnologist (NecroTechnologistSetup.cs): своя сетка на своём скелете NT_rig в позе покоя,
|
||||
/// глаза - отдельные рендереры (в имени «eye»). Тело - полупрозрачное, как у торговцев-призраков (alpha 0.3, Standard
|
||||
/// в режиме Fade, как у крысы при проходе); текстура уже серая (сделана под обесцвечивание). Глаза - свой
|
||||
/// непрозрачный светящийся материал из бандла, очередь отрисовки 3100 - после тела.
|
||||
/// </summary>
|
||||
public static class CinerariumGenie
|
||||
{
|
||||
public const string ModelPath = "#@modfolder(NecromancerTome):Resources/necrotechnologist?necroTechnologistPrefab.prefab";
|
||||
|
||||
/// <summary>Высота корня модели над низом блока урны, м (кончик хвоста в модели - на 0.45 м). Подгоняется командой necrogenie.</summary>
|
||||
public static float OffsetY = -0.25f;
|
||||
/// <summary>Поворот без работы, градусов в секунду.</summary>
|
||||
public static float TurnSpeed = 6f;
|
||||
/// <summary>Покачивание: размах (м) и период (с).</summary>
|
||||
public static float BobHeight = 0.04f;
|
||||
public static float BobPeriod = 4f;
|
||||
public static float Alpha = 0.3f;
|
||||
|
||||
static GameObject _prefab;
|
||||
static bool _tried;
|
||||
static readonly Dictionary<Material, Material> _ghostMats = new Dictionary<Material, Material>();
|
||||
/// <summary>Все джинны на сцене - команда necrogenie правит их сразу.</summary>
|
||||
public static readonly List<CinerariumGenieMotion> Live = new List<CinerariumGenieMotion>();
|
||||
|
||||
static bool Load()
|
||||
{
|
||||
if (!_tried)
|
||||
{
|
||||
_tried = true;
|
||||
_prefab = DataLoader.LoadAsset<GameObject>(ModelPath);
|
||||
Debug.Log("[NecromancerTome] CinerariumGenie: модель " + (_prefab != null ? "загружена" : "НЕ загружена: " + ModelPath));
|
||||
}
|
||||
return _prefab != null;
|
||||
}
|
||||
|
||||
public static GameObject Spawn(Transform _urnModel)
|
||||
{
|
||||
if (_urnModel == null || !Load())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
GameObject g = Object.Instantiate(_prefab, _urnModel, false);
|
||||
g.name = "NecroCinerariumGenie";
|
||||
foreach (Renderer r in g.GetComponentsInChildren<Renderer>(true))
|
||||
{
|
||||
r.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
|
||||
r.receiveShadows = false;
|
||||
if (r is SkinnedMeshRenderer smr)
|
||||
{
|
||||
smr.updateWhenOffscreen = true; // скелет свой и неподвижный - границы не пересчитываются, не пропадать с краю экрана
|
||||
}
|
||||
if (r.name.Contains("eye"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Material[] mats = r.sharedMaterials;
|
||||
for (int i = 0; i < mats.Length; i++)
|
||||
{
|
||||
mats[i] = Ghost(mats[i]);
|
||||
}
|
||||
r.sharedMaterials = mats;
|
||||
}
|
||||
foreach (Collider c in g.GetComponentsInChildren<Collider>(true))
|
||||
{
|
||||
Object.Destroy(c);
|
||||
}
|
||||
g.AddComponent<CinerariumGenieMotion>();
|
||||
return g;
|
||||
}
|
||||
|
||||
/// <summary>Полупрозрачная копия материала тела - одна на исходный материал на всю игру.</summary>
|
||||
static Material Ghost(Material _src)
|
||||
{
|
||||
if (_src == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (_ghostMats.TryGetValue(_src, out Material m) && m != null)
|
||||
{
|
||||
return m;
|
||||
}
|
||||
m = new Material(_src) { name = _src.name + "_ghost" };
|
||||
Shader std = Shader.Find("Standard");
|
||||
if (std != null && m.shader != std)
|
||||
{
|
||||
m.shader = std;
|
||||
}
|
||||
m.SetFloat("_Mode", 2f);
|
||||
m.SetOverrideTag("RenderType", "Transparent");
|
||||
m.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
|
||||
m.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
|
||||
m.SetInt("_ZWrite", 0);
|
||||
m.DisableKeyword("_ALPHATEST_ON");
|
||||
m.EnableKeyword("_ALPHABLEND_ON");
|
||||
m.DisableKeyword("_ALPHAPREMULTIPLY_ON");
|
||||
m.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent;
|
||||
SetAlpha(m, Alpha);
|
||||
_ghostMats[_src] = m;
|
||||
return m;
|
||||
}
|
||||
|
||||
static void SetAlpha(Material _m, float _a)
|
||||
{
|
||||
if (_m != null && _m.HasProperty("_Color"))
|
||||
{
|
||||
Color c = _m.color;
|
||||
c.a = _a;
|
||||
_m.color = c;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ApplyAlpha()
|
||||
{
|
||||
foreach (Material m in _ghostMats.Values)
|
||||
{
|
||||
SetAlpha(m, Alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Покачивание и поворот джинна над урной. Только вид - никакой логики.</summary>
|
||||
[Preserve]
|
||||
public class CinerariumGenieMotion : MonoBehaviour
|
||||
{
|
||||
float _yaw;
|
||||
float _phase;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
CinerariumGenie.Live.Add(this);
|
||||
_phase = Random.value * 10f;
|
||||
_yaw = Random.value * 360f;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
CinerariumGenie.Live.Remove(this);
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
_yaw = (_yaw + CinerariumGenie.TurnSpeed * Time.deltaTime) % 360f;
|
||||
float bob = CinerariumGenie.BobPeriod > 0.01f
|
||||
? Mathf.Sin((Time.time + _phase) * Mathf.PI * 2f / CinerariumGenie.BobPeriod) * CinerariumGenie.BobHeight
|
||||
: 0f;
|
||||
transform.localPosition = new Vector3(0f, CinerariumGenie.OffsetY + bob, 0f);
|
||||
transform.localRotation = Quaternion.Euler(0f, _yaw, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Подгонка джинна в игре: высота над урной, скорость поворота, покачивание, прозрачность. Не сохраняется -
|
||||
/// найденные значения переносятся в константы CinerariumGenie.</summary>
|
||||
[Preserve]
|
||||
public class ConsoleCmdNecroGenie : ConsoleCmdAbstract
|
||||
{
|
||||
public override bool IsExecuteOnClient => true;
|
||||
|
||||
public override bool AllowedInMainMenu => false;
|
||||
|
||||
public override string[] getCommands()
|
||||
{
|
||||
return new string[] { "necrogenie" };
|
||||
}
|
||||
|
||||
public override string getDescription()
|
||||
{
|
||||
return "Necromancer's Tome: tune the cinerarium spirit (height over the urn, turning, bobbing, transparency).";
|
||||
}
|
||||
|
||||
public override string getHelp()
|
||||
{
|
||||
return "necrogenie - show the current values and how many spirits are on screen\n" +
|
||||
"necrogenie y <m> - model root height above the urn block's bottom (tail tip is 0.45 m above the root)\n" +
|
||||
"necrogenie turn <deg/s> - idle turning speed\n" +
|
||||
"necrogenie bob <m> <s> - bobbing height and period\n" +
|
||||
"necrogenie alpha <0-1> - body opacity";
|
||||
}
|
||||
|
||||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo)
|
||||
{
|
||||
SdtdConsole con = SingletonMonoBehaviour<SdtdConsole>.Instance;
|
||||
if (_params.Count >= 2 && float.TryParse(_params[1], System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float v))
|
||||
{
|
||||
switch (_params[0].ToLowerInvariant())
|
||||
{
|
||||
case "y":
|
||||
CinerariumGenie.OffsetY = v;
|
||||
break;
|
||||
case "turn":
|
||||
CinerariumGenie.TurnSpeed = v;
|
||||
break;
|
||||
case "bob":
|
||||
CinerariumGenie.BobHeight = v;
|
||||
if (_params.Count >= 3 && float.TryParse(_params[2], System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float per))
|
||||
{
|
||||
CinerariumGenie.BobPeriod = per;
|
||||
}
|
||||
break;
|
||||
case "alpha":
|
||||
CinerariumGenie.Alpha = Mathf.Clamp01(v);
|
||||
CinerariumGenie.ApplyAlpha();
|
||||
break;
|
||||
default:
|
||||
con.Output("necrogenie: unknown '" + _params[0] + "' - see 'help necrogenie'");
|
||||
return;
|
||||
}
|
||||
}
|
||||
con.Output("necrogenie: y " + CinerariumGenie.OffsetY.ToString("F2") + ", turn " + CinerariumGenie.TurnSpeed.ToString("F1") +
|
||||
", bob " + CinerariumGenie.BobHeight.ToString("F2") + " / " + CinerariumGenie.BobPeriod.ToString("F1") + " s, alpha " +
|
||||
CinerariumGenie.Alpha.ToString("F2") + "; spirits on screen: " + CinerariumGenie.Live.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Reference in New Issue
Block a user