Одержимость от первого лица: руки как у игрока, серые, только у гуманоидов обычной комплекции

FpArms собирает руки для первого лица по шагам игры (baseRigFPPrefab + голая кожа basePartsFP, аниматор FPPlayerController), пол по зомби (BaseMale/BaseFemale), кожа серая через _Tint, без одежды. ~/ё работает только у списка гуманоидов; подсказка - только им. Коллайдеры рук выключены. Ударов пока нет.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AlexCube
2026-10-13 15:32:33 +03:00
co-authored by Claude Opus 5.5
parent eb540bd500
commit f5f4d92cef
4 changed files with 157 additions and 3 deletions
+142
View File
@@ -0,0 +1,142 @@
using UnityEngine;
namespace NecromancerTome
{
/// <summary>
/// Руки одержимого в виде от первого лица (указание 13.10: «делаем так, как у игрока»). У персонажа игры две
/// модели: полное тело (его видят другие и он сам со стороны) и отдельные руки для первого лица (baseRigFP,
/// SDCSUtils.CreateVizFP) - рисуются только у себя. Здесь собираем такие же руки по тем же шагам игры:
/// скелет baseRigFPPrefab с аниматором FPPlayerController, голая кожа (basePartsFP), без одежды и волос
/// (указание: «одежду пока не показываем»). Пол - по зомби (IsMale): BaseMale или BaseFemale из
/// archetypes.xml. Кожа серая (указание: «руки вообще серые - типа зомби их так видит») - цвет _Tint у
/// материала кожи, как игра красит тон кожи.
///
/// Руки - под UFPSRoot камеры игрока, как у персонажа; камеру одержимости мы ставим в голову зомби, руки
/// едут вместе с ней. Слой HoldingItem - его камера игрока рисует только при ShowHoldingItemLayer(true).
///
/// Только гуманоиды обычной комплекции (Allows), указание 13.10.
///
/// ПАМЯТЬ. Одна модель рук на время вида от первого лица; Hide выгружает её (SDCSUtils.UnloadViz - материалы
/// и ссылки на ассеты освобождаются) - на переключении вида и на выходе из одержимости.
/// </summary>
public static class FpArms
{
/// <summary>Начала имён классов, у кого вид от первого лица (варианты Feral/Radiated/Charged/Infernal - тоже).</summary>
static readonly string[] Allowed =
{
"zombieArlene", "zombieBiker", "zombieBoe", "zombieBowler", "zombieBurnt", "zombieBusinessMan", "zombieDarlene",
"zombieInmate", "zombieJanitor", "zombieJoe", "zombieLab", "zombieLumberjack", "zombieMaleHazmat", "zombieMarlene",
"zombieMoe", "zombieNurse", "zombiePartyGirl", "zombieSkateboarder", "zombieSoldier", "zombieSteve", "zombieTomClark",
"zombieUtilityWorker", "zombieYo",
};
public static readonly Color Tint = new Color(0.55f, 0.56f, 0.55f, 1f);
static GameObject _rig;
static SDCSUtils.TransformCatalog _bones;
static bool _layerWasShown;
public static bool Active => _rig != null;
public static bool Allows(EntityAlive _z)
{
if (_z == null || !EntityClass.list.TryGetValue(_z.entityClass, out EntityClass cls) || cls?.entityClassName == null)
{
return false;
}
string name = cls.entityClassName;
if (name.Contains("Crawler"))
{
return false;
}
foreach (string a in Allowed)
{
if (name.StartsWith(a))
{
return true;
}
}
return false;
}
public static void Show(EntityPlayerLocal _player, EntityAlive _z)
{
if (_rig != null || _player == null || _z == null)
{
return;
}
Archetype arch = Archetype.GetArchetype(_z.IsMale ? "BaseMale" : "BaseFemale");
Transform cam = _player.cameraTransform;
if (arch == null || cam == null)
{
Debug.LogWarning("[NecromancerTome] FpArms: нет архетипа или камеры - рук не будет");
return;
}
Transform parent = cam.FindInChildren("UFPSRoot") ?? cam;
Archetype wasArch = SDCSUtils.tmpArchetype;
SDCSUtils.AssetRefs wasOwner = SDCSUtils.tmpAssetsOwner;
try
{
SDCSUtils.tmpArchetype = arch;
SDCSUtils.setupRig(ref _rig, ref _bones, SDCSUtils.baseRigFPPrefab, parent, SDCSUtils.FPAnimController);
SDCSUtils.tmpAssetsOwner = SDCSUtils.GetAssetOwnerForRig(_rig);
SDCSUtils.setupBase(_rig, _bones, SDCSUtils.basePartsFP, true);
}
finally
{
SDCSUtils.tmpArchetype = wasArch;
SDCSUtils.tmpAssetsOwner = wasOwner;
}
_rig.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
_rig.name = "necroZombieArmsFP";
int layer = LayerMask.NameToLayer("HoldingItem");
int tinted = 0;
foreach (SkinnedMeshRenderer smr in _rig.GetComponentsInChildren<SkinnedMeshRenderer>(true))
{
smr.gameObject.layer = layer;
// Материалы с _Tint игра уже размножила для этой модели (SDCSUtils.Stitch) - красим свои копии.
foreach (Material m in smr.sharedMaterials)
{
if (m != null && m.HasColor("_Tint"))
{
m.SetColor("_Tint", Tint);
tinted++;
}
}
}
// Коллайдеры рук (у женского скелета игра вешает капсулу на таз) - выключены: руки в голове зомби ловили бы
// луч прицела и толкали бы предметы.
foreach (Collider c in _rig.GetComponentsInChildren<Collider>(true))
{
c.enabled = false;
}
foreach (HingeJoint j in _rig.GetComponentsInChildren<HingeJoint>(true))
{
if (j.connectedBody == null)
{
j.gameObject.SetActive(false);
}
}
_layerWasShown = _player.playerCamera != null && (_player.playerCamera.cullingMask & (1 << layer)) != 0;
_player.ShowHoldingItemLayer(true);
Debug.Log("[NecromancerTome] FpArms: руки " + arch.Name + " для " + _z.EntityName + ", серых материалов " + tinted +
", родитель " + parent.name);
}
public static void Hide(EntityPlayerLocal _player)
{
if (_rig == null)
{
return;
}
if (!_layerWasShown)
{
_player?.ShowHoldingItemLayer(false);
}
SDCSUtils.UnloadViz(_rig);
_rig = null;
_bones = null;
Debug.Log("[NecromancerTome] FpArms: руки убраны");
}
}
}
+15 -3
View File
@@ -356,6 +356,7 @@ namespace NecromancerTome
Active = false; Active = false;
FirstPerson = false; FirstPerson = false;
HideBody(null); HideBody(null);
FpArms.Hide(_player);
RatLoot.CloseIfViewing(); RatLoot.CloseIfViewing();
WolfBag.CloseWindow(); WolfBag.CloseWindow();
RatPhase.Stop(GameManager.Instance?.World, Zombie, "выход из одержимости"); RatPhase.Stop(GameManager.Instance?.World, Zombie, "выход из одержимости");
@@ -574,13 +575,20 @@ namespace NecromancerTome
// кнопка геймпада (тест 03.10: «подрыва нету»). // кнопка геймпада (тест 03.10: «подрыва нету»).
detonate = input.Activate.WasPressed || input.PermanentActions.Activate.WasPressed; detonate = input.Activate.WasPressed || input.PermanentActions.Activate.WasPressed;
} }
if (input != null && !gui && (input.CameraChange.WasPressed || input.QuickMenu.WasPressed || input.PermanentActions.QuickMenu.WasPressed)) // Только гуманоиды обычной комплекции (указание 13.10, FpArms.Allows) - у остальных ~/ё ничего не делает.
if (input != null && !gui && FpArms.Allows(z) &&
(input.CameraChange.WasPressed || input.QuickMenu.WasPressed || input.PermanentActions.QuickMenu.WasPressed))
{ {
FirstPerson = !FirstPerson; FirstPerson = !FirstPerson;
_fpEye = Vector3.zero; _fpEye = Vector3.zero;
if (!FirstPerson) if (FirstPerson)
{
FpArms.Show(_player, z);
}
else
{ {
HideBody(null); HideBody(null);
FpArms.Hide(_player);
} }
Debug.Log("[NecromancerTome] Possession: вид от " + (FirstPerson ? "первого" : "третьего") + " лица"); Debug.Log("[NecromancerTome] Possession: вид от " + (FirstPerson ? "первого" : "третьего") + " лица");
} }
@@ -1494,7 +1502,11 @@ namespace NecromancerTome
/// <summary>Подсказка при вселении: что умеет этот зомби.</summary> /// <summary>Подсказка при вселении: что умеет этот зомби.</summary>
static void Hint(EntityPlayerLocal _p, EntityAlive _z) static void Hint(EntityPlayerLocal _p, EntityAlive _z)
{ {
string text = Localization.Get("necroPossessHintBase") + ", " + Localization.Get("necroPossessHintView"); string text = Localization.Get("necroPossessHintBase");
if (FpArms.Allows(_z))
{
text += ", " + Localization.Get("necroPossessHintView");
}
if (Flying) if (Flying)
{ {
text += ", " + Localization.Get("necroPossessHintFly"); text += ", " + Localization.Get("necroPossessHintFly");
Binary file not shown.
Binary file not shown.