necrobody anims/play: любая загруженная анимация на теле (танцы зомби и т.п.)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AlexCube
2026-10-16 23:22:55 +03:00
co-authored by Claude Opus 5.5
parent 31ebbb9bc8
commit 5fc04fd668
+107 -1
View File
@@ -3,6 +3,8 @@ using System.Globalization;
using System.Text;
using HarmonyLib;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;
using UnityEngine.Scripting;
namespace NecromancerTome
@@ -31,6 +33,84 @@ namespace NecromancerTome
static readonly List<Quaternion> _frozenRot = new List<Quaternion>();
static int _playerId = -1;
/// <summary>Любая загруженная анимация на теле (16.10: «Может и танцевать сможешь заставить?.. сильно бы помогло в
/// создании контента»): клип играется поверх контроллера через Playables, по кругу. Танцы есть у зомби (twitch_dance) -
/// их клипы в памяти, пока в мире есть зомби с таким аниматором.</summary>
static PlayableGraph _graph;
static AnimationClipPlayable _clip;
public static string Playing;
public static List<AnimationClip> FindClips(string _filter)
{
var list = new List<AnimationClip>();
var seen = new HashSet<string>();
foreach (AnimationClip c in Resources.FindObjectsOfTypeAll<AnimationClip>())
{
if (c != null && (_filter.Length == 0 || c.name.IndexOf(_filter, System.StringComparison.OrdinalIgnoreCase) >= 0) && seen.Add(c.name))
{
list.Add(c);
}
}
list.Sort((x, y) => string.CompareOrdinal(x.name, y.name));
return list;
}
public static string Play(string _name, float _speed)
{
Animator a = Anim();
if (a == null)
{
return "нет аниматора тела";
}
AnimationClip clip = null;
foreach (AnimationClip c in FindClips(_name))
{
if (c.name == _name)
{
clip = c;
break;
}
clip = clip ?? c;
}
if (clip == null)
{
return "нет загруженной анимации «" + _name + "» (necrobody anims слово)";
}
StopPlay();
_graph = PlayableGraph.Create("NecroBodyPlay");
_graph.SetTimeUpdateMode(DirectorUpdateMode.GameTime);
var output = AnimationPlayableOutput.Create(_graph, "body", a);
_clip = AnimationClipPlayable.Create(_graph, clip);
_clip.SetSpeed(_speed);
output.SetSourcePlayable(_clip);
_graph.Play();
Playing = clip.name;
return "играет " + clip.name + " (" + NecroBody.F(clip.length) + " с" + (clip.isLooping ? ", петля" : "") + (clip.humanMotion ? ", гуманоид" : ", не гуманоид - может лечь криво") + "), скорость " + NecroBody.F(_speed);
}
public static void StopPlay()
{
if (_graph.IsValid())
{
_graph.Destroy();
}
Playing = null;
}
/// <summary>Петля для клипов без своей петли.</summary>
static void LoopPlay()
{
if (!_graph.IsValid())
{
return;
}
AnimationClip c = _clip.GetAnimationClip();
if (c != null && !c.isLooping && _clip.GetTime() >= c.length)
{
_clip.SetTime(0);
}
}
public static EntityPlayerLocal Player() => GameManager.Instance?.World?.GetPrimaryPlayer();
public static BodyAnimator Body()
@@ -67,6 +147,7 @@ namespace NecromancerTome
public static void Reset()
{
StopPlay();
Params.Clear();
Pose.Clear();
Freeze = false;
@@ -144,6 +225,7 @@ namespace NecromancerTome
_bones.Clear();
return;
}
LoopPlay();
if (!Active || Bone("") == null && _anim == null)
{
return;
@@ -235,7 +317,7 @@ namespace NecromancerTome
public class ConsoleCmdNecroBody : ConsoleCmdAbstract
{
public override string[] getCommands() => new[] { "necrobody" };
public override string getDescription() => "NecromancerTome: аниматор тела - params [ф]|clips|set п v|unset п|trigger п|speed k|freeze [off]|bones [ф]|show к|pose к x y z|add к dx dy dz|unpose к|item pos|rot|off|show|holdoffset [N]|save|off";
public override string getDescription() => "NecromancerTome: аниматор тела - params [ф]|clips|set п v|unset п|trigger п|speed k|freeze [off]|bones [ф]|show к|pose к x y z|add к dx dy dz|unpose к|item pos|rot|off|show|holdoffset [N]|anims [слово]|play имя [скорость]|play off|save|off";
static bool Num(List<string> _p, int _i, out float _v)
{
@@ -425,6 +507,30 @@ namespace NecromancerTome
Out(sb.ToString());
return;
}
case "anims":
{
List<AnimationClip> l = NecroBody.FindClips(arg);
var sb = new StringBuilder("анимаций в памяти" + (arg.Length > 0 ? " со словом " + arg : "") + ": " + l.Count);
int shown = 0;
foreach (AnimationClip c in l)
{
if (shown++ >= 200)
{
sb.Append("\n ... (уточните слово)");
break;
}
sb.Append("\n ").Append(c.name).Append(" ").Append(NecroBody.F(c.length)).Append(" с").Append(c.humanMotion ? "" : " [не гуманоид]");
}
Out(sb.ToString());
return;
}
case "play" when arg == "off":
NecroBody.StopPlay();
Out("анимация снята");
return;
case "play" when arg.Length > 0:
Out(NecroBody.Play(arg, Num(_params, 2, out float ps) ? ps : 1f));
return;
case "off":
NecroBody.Reset();
Out("тело - как у игры");