using System.Collections.Generic; using SanAndreasUnity.Importing.Conversion; using SanAndreasUnity.Importing.Items; using SanAndreasUnity.Importing.Items.Definitions; using SanAndreasUnity.Importing.Animation; using UnityEngine; using System.Linq; using SanAndreasUnity.Utilities; namespace SanAndreasUnity.Behaviours { using Anim = SanAndreasUnity.Importing.Conversion.Animation; public class PedModel : MonoBehaviour { /// State of last played anim. public AnimationState LastAnimState { get; private set; } public AnimationState LastSecondaryAnimState { get; private set; } /// Last played anim. public AnimId LastAnimId { get; private set; } public AnimId LastSecondaryAnimId { get; private set; } /// Did anims changed when played them last time ? public bool AnimsChanged { get { return this.FirstAnimChanged || this.SecondAnimChanged; } } public bool FirstAnimChanged { get; private set; } public bool SecondAnimChanged { get; private set; } private readonly AnimId m_invalidAnimId = new AnimId (); private UnityEngine.Animation _anim { get; set; } public UnityEngine.Animation AnimComponent { get { return _anim; } } private FrameContainer _frames; public FrameContainer Frames { get { return _frames; } } private Frame _root; public Frame RootFrame { get { return _root; } } public Frame UnnamedFrame { get; private set; } private readonly Dictionary _loadedAnims = new Dictionary(); public PedestrianDef Definition { get; private set; } [SerializeField] private int m_startingPedId = 167; public int StartingPedId { get { return m_startingPedId; } set { m_startingPedId = value; } } public int PedestrianId { get; private set; } // have we loaded model since the Loader has finished loading private bool loadedModelOnStartup = false; public bool IsInVehicle { get; set; } public Vector3 VehicleParentOffset { get; set; } private Transform m_leftFinger = null; public Transform LeftFinger { get { return m_leftFinger; } } private Transform m_rightFinger = null; public Transform RightFinger { get { return m_rightFinger; } } private Transform m_leftHand; public Transform LeftHand { get { return m_leftHand; } } private Transform m_rightHand; public Transform RightHand { get { return m_rightHand; } } public Transform LeftUpperArm { get; private set; } public Transform RightUpperArm { get; private set; } public Transform LeftForeArm { get; private set; } public Transform RightForeArm { get; private set; } public Transform LeftClavicle { get; private set; } public Transform RightClavicle { get; private set; } public Transform Head { get; private set; } public Transform Jaw { get; private set; } public Transform Neck { get; private set; } public Transform LBreast { get; private set; } public Transform RBreast { get; private set; } public Transform UpperSpine { get; private set; } public Transform Belly { get; private set; } public Transform Spine { get; private set; } public Transform R_Thigh { get; private set; } public Transform L_Thigh { get; private set; } public Transform R_Calf { get; private set; } public Transform L_Calf { get; private set; } public Transform R_Foot { get; private set; } public Transform L_Foot { get; private set; } public Transform Pelvis { get; private set; } readonly Dictionary m_damageLevelPerBones = new Dictionary(); public class FrameAnimData { public Vector3 pos; public Quaternion rot; public Vector3 velocity; public Frame frame; public FrameAnimData (Vector3 pos, Quaternion rot, Vector3 velocity, Frame frame) { this.pos = pos; this.rot = rot; this.velocity = velocity; this.frame = frame; } } private List m_originalFrameDatas = new List (); public List OriginalFrameDatas { get { return m_originalFrameDatas; } } private Ped m_ped; /// Velocity of the model extracted from animation. public Vector3 Velocity { get; private set; } /// Velocity axis to use. public int VelocityAxis { get; set; } private Dictionary> m_mixedTransforms = new Dictionary>(); public event System.Action onLateUpdate = delegate {}; private void Awake() { m_ped = this.GetComponentInParent (); } private void LateUpdate() { if (_root == null) return; var trans = _root.transform; if (IsInVehicle) { // 'Anchor' pedestrian model into the vehicle this.Velocity = Vector3.zero; trans.parent.localPosition = VehicleParentOffset; } else { // Store movement defined by animation for pedestrian model this.Velocity = _root.LocalVelocity; trans.parent.localPosition = new Vector3(0f, -trans.localPosition.y * .5f, -trans.localPosition.z); } this.onLateUpdate (); } private void Update() { if (Loader.HasLoaded) { // FIXME: should not be done like this if (!loadedModelOnStartup) { loadedModelOnStartup = true; // load model on startup // only load it if it wasn't loaded so far if (null == this.Definition) { Load (m_startingPedId); // and play animation PlayAnim(AnimGroup.WalkCycle, AnimIndex.Idle); } } } } private void OnDrawGizmosSelected() { float size = 0.1f; Gizmos.color = Color.red; SkinnedMeshRenderer[] renderers = GetComponentsInChildren(); foreach (SkinnedMeshRenderer smr in renderers) { foreach (Transform tr in smr.bones) { Gizmos.DrawWireCube(tr.position, new Vector3(size, size, size)); } } } public void Load(int id) { var newDefinition = Item.GetDefinition(id); if (null == newDefinition) return; PedestrianId = id; Definition = newDefinition; LastAnimId = m_invalidAnimId; LastSecondaryAnimId = m_invalidAnimId; LastAnimState = null; LastSecondaryAnimState = null; m_originalFrameDatas.Clear (); _anim = this.gameObject.GetOrAddComponent (); // remove mixing transforms for all anim states this.RemoveAllMixingTransformsForAllAnimStates(); LoadModel(Definition.ModelName, Definition.TextureDictionaryName); // save original model state // TODO: we should first reset all anim parameters (eg mixing transforms) ? var state = PlayAnim(AnimGroup.WalkCycle, AnimIndex.Idle); if (state != null) { state.time = 0f; this.AnimComponent.Sample (); } this.SaveModelState (); // load some anims LoadAnim(AnimGroup.WalkCycle, AnimIndex.Walk); LoadAnim(AnimGroup.WalkCycle, AnimIndex.Run); LoadAnim(AnimGroup.WalkCycle, AnimIndex.Panicked); LoadAnim(AnimGroup.WalkCycle, AnimIndex.Idle); LoadAnim(AnimGroup.WalkCycle, AnimIndex.RoadCross); LoadAnim(AnimGroup.WalkCycle, AnimIndex.WalkStart); LoadAnim(AnimGroup.Car, AnimIndex.Sit); LoadAnim(AnimGroup.Car, AnimIndex.DriveLeft); LoadAnim(AnimGroup.Car, AnimIndex.DriveRight); LoadAnim(AnimGroup.Car, AnimIndex.GetInLeft); LoadAnim(AnimGroup.Car, AnimIndex.GetInRight); LoadAnim(AnimGroup.Car, AnimIndex.GetOutLeft); LoadAnim(AnimGroup.Car, AnimIndex.GetOutRight); LoadAnim(AnimGroup.MyWalkCycle, AnimIndex.IdleArmed); LoadAnim(AnimGroup.MyWalkCycle, AnimIndex.GUN_STAND); // LoadAllAnimations (); } private void LoadModel(string modelName, params string[] txds) { if (_frames != null) { Destroy(_frames.Root.gameObject); Destroy(_frames); _frames = null; } var geoms = Geometry.Load(modelName, txds); _frames = geoms.AttachFrames(transform, MaterialFlags.Default); // we have to remove white spaces from frame names and their game objects, because some models // have white spaces, and some don't - and we need to have the same frame names for all models, // otherwise animations will not work on all of them foreach (var frame in _frames) { frame.Name = frame.Name.Replace (" ", ""); frame.gameObject.name = frame.Name; } _root = _frames.GetByName("Root"); UnnamedFrame = _frames.GetByName("unnamed"); System.Func getFrame = (string frameName) => { Frame frame = _frames.GetByName (frameName, true); if(null == frame) { Debug.LogErrorFormat("Failed to find frame by name '{0}' on model '{1}'", frameName, modelName); return null; } return frame.transform; }; m_rightFinger = getFrame(" R Finger"); m_leftFinger = getFrame(" L Finger"); m_rightHand = getFrame (" R Hand"); m_leftHand = getFrame (" L Hand"); RightUpperArm = getFrame (" R UpperArm"); LeftUpperArm = getFrame (" L UpperArm"); RightForeArm = getFrame (" R ForeArm"); LeftForeArm = getFrame (" L ForeArm"); RightClavicle = getFrame ("Bip01 R Clavicle"); LeftClavicle = getFrame ("Bip01 L Clavicle"); Head = getFrame (" Head"); Jaw = getFrame(" Jaw"); Neck = getFrame (" Neck"); LBreast = getFrame ("L breast"); RBreast = getFrame ("R breast"); UpperSpine = getFrame (" Spine1"); Belly = getFrame ("Belly"); Spine = getFrame(" Spine"); R_Thigh = getFrame(" R Thigh"); L_Thigh = getFrame(" L Thigh"); R_Calf = getFrame(" R Calf"); L_Calf = getFrame(" L Calf"); R_Foot = getFrame(" R Foot"); L_Foot = getFrame(" L Foot"); Pelvis = getFrame(" Pelvis"); F.RunExceptionSafe(() => this.SetupRagdoll()); } void SetupRagdoll() { m_damageLevelPerBones.Clear(); RagdollBuilder rb = new RagdollBuilder { pelvis = this.RootFrame.transform, leftHips = this.L_Thigh.transform, leftKnee = this.L_Calf.transform, leftFoot = this.L_Foot.transform, rightHips = this.R_Thigh.transform, rightKnee = this.R_Calf.transform, rightFoot = this.R_Foot.transform, leftArm = this.LeftUpperArm.transform, leftElbow = this.LeftForeArm.transform, rightArm = this.RightUpperArm.transform, rightElbow = this.RightForeArm.transform, middleSpine = this.UpperSpine.transform, head = this.Head.transform, jaw = this.Jaw.transform, }; rb.PrepareBones(); rb.OnWizardCreate(); // set layer this.RootFrame.gameObject.SetLayerRecursive(Ped.PedBoneLayerName); // assign damage level for bones Transform[] stomachAndChest = new Transform[] { this.RootFrame.transform, this.UpperSpine.transform }; Transform[] legsAndHands = new Transform[] { this.L_Thigh, this.R_Thigh, this.L_Calf, this.R_Calf, this.L_Foot, this.R_Foot, this.LeftUpperArm, this.RightUpperArm, this.LeftForeArm, this.RightForeArm }; Transform[] headAndJaw = new Transform[] { this.Head.transform, this.Jaw.transform }; foreach (var bone in stomachAndChest) { m_damageLevelPerBones[bone] = 2; } foreach (var bone in legsAndHands) { m_damageLevelPerBones[bone] = 1; } foreach (var bone in headAndJaw) { m_damageLevelPerBones[bone] = 3; } } public float GetAmountOfDamageForBone(Transform boneTransform, float baseDamageValue) { if (m_damageLevelPerBones.TryGetValue(boneTransform, out int damageLevel)) { if (1 == damageLevel) return baseDamageValue * PedManager.Instance.legAndArmDamageMultiplier; if (2 == damageLevel) return baseDamageValue * PedManager.Instance.stomachAndChestDamageMultiplier; if (3 == damageLevel) return baseDamageValue * PedManager.Instance.headDamageMultiplier; } return 0f; } /// /// Resets the state of the model. This includes position, rotation, and velocity of every frame (bone). /// It does it by playing idle anim, setting it's time to 0, and sampling from it. /// public void ResetModelState () { //var state = PlayAnim (AnimGroup.WalkCycle, AnimIndex.Idle); //state.normalizedTime = 0; //AnimComponent.Sample (); foreach (var frameData in m_originalFrameDatas) { if (null == frameData.frame) continue; ResetFrameState (frameData); } } private void SaveModelState () { m_originalFrameDatas.Clear (); foreach (var frame in this.Frames) { m_originalFrameDatas.Add (new FrameAnimData (frame.transform.localPosition, frame.transform.localRotation, frame.LocalVelocity, frame)); } } private void ResetFrameState (FrameAnimData frameData) { frameData.frame.transform.localPosition = frameData.pos; frameData.frame.transform.localRotation = frameData.rot; frameData.frame.LocalVelocity = frameData.velocity; } public void ResetFrameState (Transform frameTransform) { Frame frame = frameTransform.GetComponent (); if (null == frame) return; var frameData = m_originalFrameDatas.FirstOrDefault( f => f.frame == frame ); if (frameData != null) { ResetFrameState (frameData); } } public AnimationState PlayAnim (AnimId animId, PlayMode playMode = PlayMode.StopAll) { this.FirstAnimChanged = this.SecondAnimChanged = false; var animState = LoadAnim (animId); if (null == animState) return null; this.FirstAnimChanged = !animId.Equals (LastAnimId); this.SecondAnimChanged = !m_invalidAnimId.Equals (LastSecondaryAnimId); LastAnimId = animId; LastSecondaryAnimId = m_invalidAnimId; LastAnimState = animState; LastSecondaryAnimState = null; // reset velocity axis this.VelocityAxis = 2; //animState.layer = 0; RemoveAllMixingTransforms (animState); _anim.Play (animState.name, playMode); return animState; } public AnimationState PlayAnim (AnimGroup animGroup, AnimIndex animIndex, PlayMode playMode = PlayMode.StopAll) { return PlayAnim (new AnimId (animGroup, animIndex), playMode); } public AnimationState PlayAnim (string fileName, string animName, PlayMode playMode = PlayMode.StopAll) { return PlayAnim (new AnimId (fileName, animName), playMode); } public bool Play2Anims (AnimId animIdA, AnimId animIdB) { this.FirstAnimChanged = this.SecondAnimChanged = false; // load anims var stateA = LoadAnim (animIdA); var stateB = LoadAnim (animIdB); if (null == stateA || null == stateB) return false; this.FirstAnimChanged = !animIdA.Equals (LastAnimId); this.SecondAnimChanged = !animIdB.Equals (LastSecondaryAnimId); // reset velocity axis this.VelocityAxis = 2; // play anims // are mixing transforms and layers preserved ? - probably RemoveAllMixingTransforms (stateA); RemoveAllMixingTransforms (stateB); AddMixingTransform (stateA, this.Spine, true); AddMixingTransform (stateB, _root.transform, false); AddMixingTransform (stateB, this.L_Thigh, true); AddMixingTransform (stateB, this.R_Thigh, true); stateA.layer = 0; stateB.layer = 1; this.AnimComponent.Play( stateA.clip.name, PlayMode.StopSameLayer); this.AnimComponent.Play( stateB.clip.name, PlayMode.StopSameLayer); // assign last anims LastAnimId = animIdA; LastSecondaryAnimId = animIdB; LastAnimState = stateA; LastSecondaryAnimState = stateB; return true; } public bool AddMixingTransform (AnimationState state, Transform tr, bool recursive) { List list; if (m_mixedTransforms.TryGetValue (state, out list)) { if (list.Contains (tr)) return false; state.AddMixingTransform (tr, recursive); list.Add (tr); return true; } else { state.AddMixingTransform (tr, recursive); list = new List (){ tr }; m_mixedTransforms.Add (state, list); return true; } } public void AddMixingTransforms (AnimationState state, params Transform[] transforms) { foreach (var tr in transforms) AddMixingTransform (state, tr, false); } public bool RemoveMixingTransform (AnimationState state, Transform tr) { List list; if (m_mixedTransforms.TryGetValue (state, out list)) { if (!list.Contains (tr)) return false; state.RemoveMixingTransform (tr); list.Remove (tr); return true; } else { return false; } } public int RemoveAllMixingTransforms (AnimationState state) { List list; if (m_mixedTransforms.TryGetValue (state, out list)) { int count = list.Count; foreach (Transform mix in list) { if (mix != null) state.RemoveMixingTransform (mix); } list.Clear (); return count; } else { return 0; } } public void RemoveAllMixingTransformsForAllAnimStates() { foreach (AnimationState s in this.AnimComponent) { if (s != null) this.RemoveAllMixingTransforms(s); } m_mixedTransforms.Clear(); } public Anim GetAnim (string animName) { Anim result; return _loadedAnims.TryGetValue (animName, out result) ? result : null; } public Anim GetAnim (AnimGroup group, AnimIndex anim) { string animName = GetAnimName (group, anim); if (string.IsNullOrEmpty (animName)) return null; return GetAnim(animName); } public string GetAnimName (AnimGroup group, AnimIndex anim) { string animName = null, fileName = null; if (GetAnimNameAndFile (group, anim, ref animName, ref fileName)) return animName; return null; } public bool GetAnimNameAndFile (AnimGroup group, AnimIndex anim, ref string animName, ref string fileName) { if (anim == AnimIndex.None) return false; if (group == AnimGroup.None) return false; if (null == this.Definition || string.IsNullOrEmpty (this.Definition.AnimGroupName)) return false; var animGroup = AnimationGroup.Get (this.Definition.AnimGroupName, group); if (null == animGroup) return false; animName = animGroup [anim]; fileName = animGroup.FileName; return true; } private AnimationState LoadAnim (AnimGroup group, AnimIndex anim) { string animName = null, fileName = null; if (GetAnimNameAndFile (group, anim, ref animName, ref fileName)) return LoadAnim (animName, fileName); return null; } private AnimationState LoadAnim (string animName, string fileName) { AnimationState state = null; if (!_loadedAnims.ContainsKey(animName)) { var importedAnim = Anim.Load(fileName, animName, _frames); if (importedAnim != null && importedAnim.Clip != null) { _loadedAnims.Add(animName, importedAnim); _anim.AddClip(importedAnim.Clip, animName); state = _anim[animName]; } else { Debug.LogErrorFormat ("Failed to load anim - file: {0}, anim name: {1}", fileName, animName); } } else { state = _anim[animName]; } return state; } private AnimationState LoadAnim (AnimId animId) { return animId.UsesAnimGroup ? LoadAnim (animId.AnimGroup, animId.AnimIndex) : LoadAnim (animId.AnimName, animId.FileName); } private void LoadAllAnimations() { foreach (var dict in AnimationGroup._sGroups.Values) { foreach (AnimationGroup animGroup in dict.Values) { foreach (var animName in animGroup.Animations) { if (!_loadedAnims.ContainsKey(animName)) { var clip = Importing.Conversion.Animation.Load(animGroup.FileName, animName, _frames); _loadedAnims.Add(animName, clip); _anim.AddClip(clip.Clip, animName); // state = _anim [animName]; } } } } } } }