core/npc/NpcAnimator.cs
public class NpcAnimator : Component {
[Property, ReadOnly] public IsoEntity Owner {get; set;}
public IsoActivitySet ActivitySet => Owner.ActivitySet;
private SkinnedModelRenderer Renderer => Owner.Renderer;
[Property] public string Weapon {get; set;} = "unarmed";
[Property] public string Activity {get; set;}
public Action AttackAction {get; set;}
protected override void OnAwake() {
Tags.Add("dynamic");
Tags.Add("entity");
if (ActivitySet.IsValid() && string.IsNullOrEmpty(Activity))
Activity = ActivitySet.Default;
base.OnAwake();
}
protected override void OnFixedUpdate() {
UpdateAnimation();
base.OnFixedUpdate();
}
[Button] public void ResetToIdle() {
AnimationState = AnimatorState.Idle;
Renderer.Sequence.TimeNormalized = 1f;
UpdateAnimation();
}
private enum AnimatorState {
Enter,
WaitForEnter,
Idle,
Exit
}
[Property, Category("anm"), ReadOnly] private AnimatorState AnimationState {get; set;}
[Property, Category("anm"), ReadOnly] private string CurrentActivity;
[Property, ReadOnly] public List<Component> Attached {get; set;} = new();
[Property, ReadOnly] public bool PlayedAttackEvent {get; set;}
public void UpdateAnimation() {
void InternalUpdate() {
if (!ActivitySet.IsValid())
return;
Renderer.UseAnimGraph = false;
Renderer.Sequence.Looping = false;
var lastactivity = CurrentActivity;
if (!string.IsNullOrEmpty(Activity) && ActivitySet.WeaponActivityMap.TryGetValue(Weapon, out var map)) {
if (map.Map.TryGetValue(Activity, out var newactivity))
Activity = newactivity;
}
if (ActivitySet.Activities.ContainsKey(Activity))
CurrentActivity = Activity;
if (string.IsNullOrEmpty(CurrentActivity))
CurrentActivity = ActivitySet.Default;
if (string.IsNullOrEmpty(CurrentActivity))
return;
if (lastactivity != CurrentActivity) {
if (lastactivity == "die")
return;
if (AnimationState == AnimatorState.WaitForEnter || AnimationState == AnimatorState.Idle)
AnimationState = AnimatorState.Exit;
else if(!string.IsNullOrEmpty(lastactivity))
CurrentActivity = lastactivity;
}
if (AnimationState == AnimatorState.Enter) { //entry
if (Renderer.Sequence.Duration > 0 && !Renderer.Sequence.IsFinished)
return;
if (!string.IsNullOrEmpty(ActivitySet.Activities[CurrentActivity].Entry))
Renderer.Sequence.Name = ActivitySet.Activities[CurrentActivity].Entry;
AnimationState = AnimatorState.WaitForEnter;
}
if (AnimationState == AnimatorState.WaitForEnter || AnimationState == AnimatorState.Idle) { //idle
if (Renderer.Sequence.Duration > 0 && !Renderer.Sequence.IsFinished)
return;
AnimationState = AnimatorState.Idle;
var act = ActivitySet.Activities[CurrentActivity].Idle;
if ((string.IsNullOrEmpty(act) || Random.Shared.Int(3) == 1) && ActivitySet.Activities[CurrentActivity].Fidget.Count > 0)
act = Random.Shared.FromList(ActivitySet.Activities[CurrentActivity].Fidget);
if (!string.IsNullOrEmpty(act))
Renderer.Sequence.Name = act;
else
AnimationState = AnimatorState.Exit;
}
if (AnimationState == AnimatorState.Exit) { //exit
if (lastactivity == CurrentActivity)
AnimationState = AnimatorState.Enter;
if (string.IsNullOrEmpty(lastactivity))
AnimationState = AnimatorState.Enter;
else if (!string.IsNullOrEmpty(ActivitySet.Activities[lastactivity].Exit))
Renderer.Sequence.Name = ActivitySet.Activities[lastactivity].Exit;
else
Renderer.Sequence.TimeNormalized = 1;
if (!string.IsNullOrEmpty(lastactivity) && !string.IsNullOrEmpty(ActivitySet.Activities[lastactivity].ReturnToActivity))
Activity = ActivitySet.Activities[lastactivity].ReturnToActivity;
AnimationState = AnimatorState.Enter;
}
}
var startcycle = Renderer.Sequence.TimeNormalized;
var startsequence = Renderer.Sequence.Name;
var startact = CurrentActivity;
InternalUpdate();
if (startcycle <= Renderer.Sequence.TimeNormalized) {
if (string.IsNullOrEmpty(startact))
return;
if (startact != CurrentActivity)
return;
if (ActivitySet.DeathActivies.Contains(CurrentActivity)) {
Components.Get<NpcMovement>()?.Destroy();
Components.Get<NpcTalker>()?.Destroy();
Components.Get<BoxCollider>()?.Destroy();
var blood = Owner.Renderer.GetAttachment("blood");
if (blood.HasValue) {
var pool = Scene.CreateObject().Components.Create<BloodPool>();
pool.GameObject.WorldTransform = blood.Value;
pool.GameObject.Name = "BLOOD_POOL";
}
Owner.Destroy();
Renderer.Sequence.Name = ActivitySet.Activities[CurrentActivity].Entry;
Destroy();
return;
}
if (!PlayedAttackEvent && ActivitySet.AttackEvents.TryGetValue(startact, out var time) && time <= Renderer.Sequence.Time) {
AttackAction?.Invoke();
PlayedAttackEvent = true;
}
return;
}
PlayedAttackEvent = false;
foreach (var attached in Attached)
attached.Destroy();
Attached.Clear();
if (ActivitySet.ActivityAttachments.TryGetValue(CurrentActivity, out var attachments)) {
Renderer.CreateAttachments = true;
foreach (var model in attachments.Models) {
var smr = Renderer.GetAttachmentObject(model.Key).Components.Create<SkinnedModelRenderer>();
smr.Model = model.Value;
Attached.Add(smr);
}
}
if (ActivitySet.SequenceRootMotion.TryGetValue(startsequence, out var motion)) {
if (startcycle > 0.9f)
WorldPosition += motion.RotateAround(Vector3.Zero, WorldRotation);
else
WorldPosition += motion.RotateAround(Vector3.Zero, WorldRotation) * startcycle;
var m = Components.Get<NpcMovement>();
if (m.IsValid())
m.EntityRoot = WorldPosition;
GameObject.Transform.ClearInterpolation();
}
}
}
[GameResource("Activity Set", "anm", "", Icon = "sports_martial_arts")]
public class IsoActivitySet : GameResource {
public struct Activity {
public string Entry {get; set;}
public string Idle {get; set;}
public List<string> Fidget {get; set;}
public string Exit {get; set;}
public string ReturnToActivity {get; set;}
}
[WideMode, InlineEditor] public Dictionary<string, Activity> Activities {get; set;} = new();
public string Default {get; set;}
public struct WeaponActivities {
public Dictionary<string, string> Map {get; set;}
}
[WideMode, InlineEditor] public Dictionary<string, WeaponActivities> WeaponActivityMap {get; set;} = new();
public struct Attachments {
[WideMode] public Dictionary<string, Model> Models {get; set;}
}
[WideMode, InlineEditor] public Dictionary<string, Attachments> ActivityAttachments {get; set;} = new();
[WideMode] public Dictionary<string, Vector3> SequenceRootMotion {get; set;} = new();
public List<string> NonInteruptActivities {get; set;} = new();
public List<string> DeathActivies {get; set;} = new();
[WideMode] public Dictionary<string, float> AttackEvents {get; set;} = new();
}