This commit is contained in:
2026-05-30 09:16:35 +07:00
parent 2f87ce19a7
commit 1c0ee6efb7
4001 changed files with 3363438 additions and 1738 deletions

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 4efae272364c1504a90b0f25a1f76c7e
folderAsset: yes
timeCreated: 1437421792
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,710 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace Invector.vCharacterController
{
class vRagdollBuilder : ScriptableWizard
{
public enum vRagdollAvatarType
{
Human, Generic
}
public vRagdollAvatarType avatarType = vRagdollAvatarType.Human;
[Header("--- Animator of cursorObject Character ---")]
public Animator animator;
[Header("---Generic Character template---")]
public vRagdollGenericTemplate genericTemplate = null;
[Header("--- Bones ---")]
public Transform root;
public Transform leftHips;
public Transform leftKnee;
public Transform leftFoot;
public Transform rightHips;
public Transform rightKnee;
public Transform rightFoot;
public Transform leftArm;
public Transform leftElbow;
public Transform rightArm;
public Transform rightElbow;
public Transform middleSpine;
public Transform head;
[Header("--- Properties ---")]
public bool enableProjection = true;
public bool proportionalMass = true;
[Header("Total Mass will be ignored and set to 1 if Proportional Mass is true")]
public float totalMass = 20;
public float strength = 0.0F;
Vector3 right = Vector3.right;
Vector3 up = Vector3.up;
Vector3 forward = Vector3.forward;
Vector3 worldRight = Vector3.right;
Vector3 worldUp = Vector3.up;
Vector3 worldForward = Vector3.forward;
public bool flipForward = false;
class BoneInfo
{
public string name;
public Transform anchor;
public CharacterJoint joint;
public BoneInfo parent;
public float minLimit;
public float maxLimit;
public float swingLimit;
public Vector3 axis;
public Vector3 normalAxis;
public float radiusScale;
public Type colliderType;
public ArrayList children = new ArrayList();
public float density;
public float summedMass;// The mass of this and all children bodies
}
ArrayList bones;
BoneInfo rootBone;
string CheckConsistency()
{
PrepareBones();
Hashtable map = new Hashtable();
foreach (BoneInfo bone in bones)
{
if (bone.anchor)
{
if (map[bone.anchor] != null)
{
BoneInfo oldBone = (BoneInfo)map[bone.anchor];
return String.Format("{0} and {1} may not be assigned to the same bone.", bone.name, oldBone.name);
}
map[bone.anchor] = bone;
}
}
foreach (BoneInfo bone in bones)
{
if (bone.anchor == null)
{
return String.Format("{0} has not been assigned yet.\n", bone.name);
}
}
return "";
}
[MenuItem("Invector/Basic Locomotion/Components/Ragdoll")]
static void CreateWizard()
{
ScriptableWizard.DisplayWizard("Create Ragdoll", typeof(vRagdollBuilder));
//ScriptableWizard.DisplayWizard("Create Ragdoll", typeof (RagdollBuilder),"Create","Load Bones");
}
void DecomposeVector(out Vector3 normalCompo, out Vector3 tangentCompo, Vector3 outwardDir, Vector3 outwardNormal)
{
outwardNormal = outwardNormal.normalized;
normalCompo = outwardNormal * Vector3.Dot(outwardDir, outwardNormal);
tangentCompo = outwardDir - normalCompo;
}
void CalculateAxes()
{
if (head != null && root != null)
{
up = CalculateDirectionAxis(root.InverseTransformPoint(head.position));
}
if (rightElbow != null && root != null)
{
Vector3 removed, temp;
DecomposeVector(out temp, out removed, root.InverseTransformPoint(rightElbow.position), up);
right = CalculateDirectionAxis(removed);
}
forward = Vector3.Cross(right, up);
if (flipForward)
{
forward = -forward;
}
}
void Update()
{
errorString = CheckConsistency();
CalculateAxes();
if (errorString.Length != 0)
{
helpString = "Drag all bones from the hierarchy into their slots.\nMake sure your character is in T-Stand.\n";
}
else
{
helpString = "Make sure your character is in T-Stand.\nMake sure the blue axis faces in the same direction the chracter is looking.\nUse flipForward to flip the direction";
}
isValid = errorString.Length == 0;
}
void PrepareBones()
{
if (Selection.activeGameObject != null && Selection.activeGameObject.transform.GetComponent<Animator>() != null)
{
animator = Selection.activeGameObject.transform.GetComponent<Animator>();
animator.Rebind();
}
if (animator != null && avatarType == vRagdollAvatarType.Human)
{
GetHumanBones();
}
else
{
GetGenericBones();
}
if (root)
{
worldRight = root.TransformDirection(right);
worldUp = root.TransformDirection(up);
worldForward = root.TransformDirection(forward);
}
bones = new ArrayList();
rootBone = new BoneInfo();
rootBone.name = "Root";
rootBone.anchor = root;
rootBone.parent = null;
rootBone.density = 2.5F;
bones.Add(rootBone);
AddMirroredJoint("Hips", leftHips, rightHips, "Root", worldRight, worldForward, -20, 70, 30, typeof(CapsuleCollider), 0.3F, 1.5F);
AddMirroredJoint("Knee", leftKnee, rightKnee, "Hips", worldRight, worldForward, -80, 0, 0, typeof(CapsuleCollider), 0.25F, 1.5F);
// AddMirroredJoint ("Hips", leftHips, rightHips, "Root", worldRight, worldForward, -0, -70, 30, typeof(CapsuleCollider), 0.3F, 1.5F);
// AddMirroredJoint ("Knee", leftKnee, rightKnee, "Hips", worldRight, worldForward, -0, -50, 0, typeof(CapsuleCollider), .25F, 1.5F);
AddJoint("Middle Spine", middleSpine, "Root", worldRight, worldForward, -20, 20, 10, null, 1, 2.5F);
AddMirroredJoint("Arm", leftArm, rightArm, "Middle Spine", worldUp, worldForward, -70, 10, 50, typeof(CapsuleCollider), 0.25F, 1.0F);
AddMirroredJoint("Elbow", leftElbow, rightElbow, "Arm", worldForward, worldUp, -90, 0, 0, typeof(CapsuleCollider), 0.20F, 1.0F);
AddJoint("Head", head, "Middle Spine", worldRight, worldForward, -40, 25, 25, null, 1, 1.0F);
}
void GetHumanBones()
{
try
{
root = animator.GetBoneTransform(HumanBodyBones.Hips);
leftHips = animator.GetBoneTransform(HumanBodyBones.LeftUpperLeg);
leftKnee = animator.GetBoneTransform(HumanBodyBones.LeftLowerLeg);
leftFoot = animator.GetBoneTransform(HumanBodyBones.LeftFoot);
rightHips = animator.GetBoneTransform(HumanBodyBones.RightUpperLeg);
rightKnee = animator.GetBoneTransform(HumanBodyBones.RightLowerLeg);
rightFoot = animator.GetBoneTransform(HumanBodyBones.RightFoot);
leftArm = animator.GetBoneTransform(HumanBodyBones.LeftUpperArm);
leftElbow = animator.GetBoneTransform(HumanBodyBones.LeftLowerArm);
rightArm = animator.GetBoneTransform(HumanBodyBones.RightUpperArm);
rightElbow = animator.GetBoneTransform(HumanBodyBones.RightLowerArm);
middleSpine = animator.GetBoneTransform(HumanBodyBones.Chest);
head = animator.GetBoneTransform(HumanBodyBones.Head);
EditorUtility.SetDirty(this);
}
catch
{
}
}
void GetGenericBones()
{
if (!genericTemplate && Selection.activeObject)
{
return;
}
try
{
var rootTransform = Selection.activeTransform;
root = genericTemplate.GetRoot(rootTransform);
leftHips = genericTemplate.GetLeftHips(rootTransform);
leftKnee = genericTemplate.GetLeftKnee(rootTransform);
leftFoot = genericTemplate.GetLeftFoot(rootTransform);
rightHips = genericTemplate.GetRightHips(rootTransform);
rightKnee = genericTemplate.GetRightKnee(rootTransform);
rightFoot = genericTemplate.GetRightFoot(rootTransform);
leftArm = genericTemplate.GetLeftArm(rootTransform);
leftElbow = genericTemplate.GetLeftElbow(rootTransform);
rightArm = genericTemplate.GetRightArm(rootTransform);
rightElbow = genericTemplate.GetRightElbow(rootTransform);
middleSpine = genericTemplate.GetMiddleSpine(rootTransform);
head = genericTemplate.GetHead(rootTransform);
EditorUtility.SetDirty(this);
}
catch
{
}
}
void OnWizardCreate()
{
//if(Selection.activeGameObject!=null)
var hasRagdoll = Selection.activeGameObject.GetComponent<vRagdoll>();
if (!hasRagdoll)
{
Selection.activeGameObject.AddComponent<vRagdoll>();
}
Selection.activeGameObject.GetComponent<Rigidbody>().collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
Cleanup();
BuildCapsules();
AddBreastColliders();
AddHeadCollider();
BuildBodies();
BuildJoints();
CalculateMass();
}
BoneInfo FindBone(string name)
{
foreach (BoneInfo bone in bones)
{
if (bone.name == name)
{
return bone;
}
}
return null;
}
void AddMirroredJoint(string name, Transform leftAnchor, Transform rightAnchor, string parent, Vector3 worldTwistAxis, Vector3 worldSwingAxis, float minLimit, float maxLimit, float swingLimit, Type colliderType, float radiusScale, float density)
{
AddJoint("Left " + name, leftAnchor, parent, worldTwistAxis, worldSwingAxis, minLimit, maxLimit, swingLimit, colliderType, radiusScale, density);
AddJoint("Right " + name, rightAnchor, parent, worldTwistAxis, worldSwingAxis, minLimit, maxLimit, swingLimit, colliderType, radiusScale, density);
}
void AddJoint(string name, Transform anchor, string parent, Vector3 worldTwistAxis, Vector3 worldSwingAxis, float minLimit, float maxLimit, float swingLimit, Type colliderType, float radiusScale, float density)
{
BoneInfo bone = new BoneInfo();
bone.name = name;
bone.anchor = anchor;
bone.axis = worldTwistAxis;
bone.normalAxis = worldSwingAxis;
bone.minLimit = minLimit;
bone.maxLimit = maxLimit;
bone.swingLimit = swingLimit;
bone.density = density;
bone.colliderType = colliderType;
bone.radiusScale = radiusScale;
if (FindBone(parent) != null)
{
bone.parent = FindBone(parent);
}
else if (name.StartsWith("Left"))
{
bone.parent = FindBone("Left " + parent);
}
else if (name.StartsWith("Right"))
{
bone.parent = FindBone("Right " + parent);
}
bone.parent.children.Add(bone);
bones.Add(bone);
}
void BuildCapsules()
{
foreach (BoneInfo bone in bones)
{
if (bone.colliderType != typeof(CapsuleCollider))
{
continue;
}
int direction;
float distance;
if (bone.children.Count == 1)
{
BoneInfo childBone = (BoneInfo)bone.children[0];
Vector3 endPoint = childBone.anchor.position;
CalculateDirection(bone.anchor.InverseTransformPoint(endPoint), out direction, out distance);
}
else
{
Vector3 endPoint = (bone.anchor.position - bone.parent.anchor.position) + bone.anchor.position;
CalculateDirection(bone.anchor.InverseTransformPoint(endPoint), out direction, out distance);
if (bone.anchor.GetComponentsInChildren(typeof(Transform)).Length > 1)
{
Bounds bounds = new Bounds();
foreach (Transform child in bone.anchor.GetComponentsInChildren(typeof(Transform)))
{
bounds.Encapsulate(bone.anchor.InverseTransformPoint(child.position));
}
if (distance > 0)
{
distance = bounds.max[direction];
}
else
{
distance = bounds.min[direction];
}
}
}
CapsuleCollider collider = bone.anchor.gameObject.AddComponent<CapsuleCollider>();
collider.direction = direction;
Vector3 center = Vector3.zero;
center[direction] = distance * 0.5F;
collider.center = center;
collider.height = Mathf.Abs(distance);
collider.radius = Mathf.Abs(distance * bone.radiusScale);
}
}
void Cleanup()
{
foreach (BoneInfo bone in bones)
{
if (!bone.anchor)
{
continue;
}
Component[] joints = bone.anchor.GetComponentsInChildren(typeof(Joint));
foreach (Joint joint in joints)
{
DestroyImmediate(joint);
}
Component[] bodies = bone.anchor.GetComponentsInChildren(typeof(Rigidbody));
foreach (Rigidbody body in bodies)
{
DestroyImmediate(body);
}
Component[] colliders = bone.anchor.GetComponentsInChildren(typeof(Collider));
foreach (Collider collider in colliders)
{
if (collider.transform != leftFoot.transform && collider.transform != rightFoot)
{
DestroyImmediate(collider);
}
}
}
}
void BuildBodies()
{
foreach (BoneInfo bone in bones)
{
bone.anchor.gameObject.AddComponent<Rigidbody>();
var hasDamageReceiver = bone.anchor.gameObject.GetComponent<vDamageReceiver>();
if (!hasDamageReceiver)
{
bone.anchor.gameObject.AddComponent<vDamageReceiver>();
}
bone.anchor.GetComponent<Rigidbody>().mass = bone.density;
bone.anchor.GetComponent<Rigidbody>().collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
// set layer "BodyPart" to each bone
bone.anchor.gameObject.layer = LayerMask.NameToLayer("BodyPart");
}
}
void BuildJoints()
{
foreach (BoneInfo bone in bones)
{
if (bone.parent == null)
{
continue;
}
CharacterJoint joint = bone.anchor.gameObject.AddComponent<CharacterJoint>();
bone.joint = joint;
// Setup connection and axis
joint.axis = CalculateDirectionAxis(bone.anchor.InverseTransformDirection(bone.axis));
joint.swingAxis = CalculateDirectionAxis(bone.anchor.InverseTransformDirection(bone.normalAxis));
joint.anchor = Vector3.zero;
joint.connectedBody = bone.parent.anchor.GetComponent<Rigidbody>();
// Setup limits
SoftJointLimit limit = new SoftJointLimit();
limit.limit = bone.minLimit;
joint.lowTwistLimit = limit;
limit.limit = bone.maxLimit;
joint.highTwistLimit = limit;
limit.limit = bone.swingLimit;
joint.swing1Limit = limit;
limit.limit = 0;
joint.swing2Limit = limit;
joint.enableProjection = enableProjection;
}
}
void CalculateMassRecurse(BoneInfo bone)
{
float mass = bone.anchor.GetComponent<Rigidbody>().mass;
foreach (BoneInfo child in bone.children)
{
CalculateMassRecurse(child);
mass += child.summedMass;
}
bone.summedMass = mass;
}
void CalculateMass()
{
// Calculate allChildMass by summing all bodies
CalculateMassRecurse(rootBone);
// Rescale the mass so that the whole character weights totalMass
float massScale = totalMass / rootBone.summedMass;
foreach (BoneInfo bone in bones)
{
if (proportionalMass)
{
bone.anchor.GetComponent<Rigidbody>().mass = 10;
}
else
{
bone.anchor.GetComponent<Rigidbody>().mass *= massScale;
}
}
// Recalculate allChildMass by summing all bodies
CalculateMassRecurse(rootBone);
}
static void CalculateDirection(Vector3 point, out int direction, out float distance)
{
// Calculate longest axis
direction = 0;
if (Mathf.Abs(point[1]) > Mathf.Abs(point[0]))
{
direction = 1;
}
if (Mathf.Abs(point[2]) > Mathf.Abs(point[direction]))
{
direction = 2;
}
distance = point[direction];
}
static Vector3 CalculateDirectionAxis(Vector3 point)
{
int direction = 0;
float distance;
CalculateDirection(point, out direction, out distance);
Vector3 axis = Vector3.zero;
if (distance > 0)
{
axis[direction] = 1.0F;
}
else
{
axis[direction] = -1.0F;
}
return axis;
}
static int SmallestComponent(Vector3 point)
{
int direction = 0;
if (Mathf.Abs(point[1]) < Mathf.Abs(point[0]))
{
direction = 1;
}
if (Mathf.Abs(point[2]) < Mathf.Abs(point[direction]))
{
direction = 2;
}
return direction;
}
static int LargestComponent(Vector3 point)
{
int direction = 0;
if (Mathf.Abs(point[1]) > Mathf.Abs(point[0]))
{
direction = 1;
}
if (Mathf.Abs(point[2]) > Mathf.Abs(point[direction]))
{
direction = 2;
}
return direction;
}
static int SecondLargestComponent(Vector3 point)
{
int smallest = SmallestComponent(point);
int largest = LargestComponent(point);
if (smallest < largest)
{
int temp = largest;
largest = smallest;
smallest = temp;
}
if (smallest == 0 && largest == 1)
{
return 2;
}
else if (smallest == 0 && largest == 2)
{
return 1;
}
else
{
return 0;
}
}
Bounds Clip(Bounds bounds, Transform relativeTo, Transform clipTransform, bool below)
{
int axis = LargestComponent(bounds.size);
if (Vector3.Dot(worldUp, relativeTo.TransformPoint(bounds.max)) > Vector3.Dot(worldUp, relativeTo.TransformPoint(bounds.min)) == below)
{
Vector3 min = bounds.min;
min[axis] = relativeTo.InverseTransformPoint(clipTransform.position)[axis];
bounds.min = min;
}
else
{
Vector3 max = bounds.max;
max[axis] = relativeTo.InverseTransformPoint(clipTransform.position)[axis];
bounds.max = max;
}
return bounds;
}
Bounds GetBreastBounds(Transform relativeTo)
{
// Root bounds
Bounds bounds = new Bounds();
bounds.Encapsulate(relativeTo.InverseTransformPoint(leftHips.position));
bounds.Encapsulate(relativeTo.InverseTransformPoint(rightHips.position));
bounds.Encapsulate(relativeTo.InverseTransformPoint(leftArm.position));
bounds.Encapsulate(relativeTo.InverseTransformPoint(rightArm.position));
Vector3 size = bounds.size;
size[SmallestComponent(bounds.size)] = size[LargestComponent(bounds.size)] / 2.0F;
bounds.size = size;
return bounds;
}
void AddBreastColliders()
{
// Middle spine and root
if (middleSpine != null && root != null)
{
Bounds bounds;
BoxCollider box;
// Middle spine bounds
bounds = Clip(GetBreastBounds(root), root, middleSpine, false);
box = root.gameObject.AddComponent<BoxCollider>();
box.center = bounds.center;
box.size = bounds.size;
bounds = Clip(GetBreastBounds(middleSpine), middleSpine, middleSpine, true);
box = middleSpine.gameObject.AddComponent<BoxCollider>();
box.center = bounds.center;
box.size = bounds.size;
}
// Only root
else
{
Bounds bounds = new Bounds();
bounds.Encapsulate(root.InverseTransformPoint(leftHips.position));
bounds.Encapsulate(root.InverseTransformPoint(rightHips.position));
bounds.Encapsulate(root.InverseTransformPoint(leftArm.position));
bounds.Encapsulate(root.InverseTransformPoint(rightArm.position));
Vector3 size = bounds.size;
size[SmallestComponent(bounds.size)] = size[LargestComponent(bounds.size)] / 2.0F;
BoxCollider box = root.gameObject.AddComponent<BoxCollider>();
box.center = bounds.center;
box.size = size;
}
}
void AddHeadCollider()
{
if (head.GetComponent<Collider>())
{
Destroy(head.GetComponent<Collider>());
}
float radius = Vector3.Distance(root.InverseTransformPoint(rightArm.transform.position), root.InverseTransformPoint(leftArm.transform.position));
radius /= 4;
SphereCollider sphere = head.gameObject.AddComponent<SphereCollider>();
sphere.radius = radius;
Vector3 center = Vector3.zero;
int direction;
float distance;
CalculateDirection(head.InverseTransformPoint(root.position), out direction, out distance);
if (distance > 0)
{
center[direction] = -radius;
}
else
{
center[direction] = radius;
}
sphere.center = center;
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 3609ddfcf623a544ead9f496b6e1bfee
timeCreated: 1437405409
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,101 @@
using UnityEngine;
namespace Invector.vCharacterController
{
[vClassHeader("DAMAGE RECEIVER", "You can add damage multiplier for example causing twice damage on Headshots", openClose = false)]
public partial class vDamageReceiver : vMonoBehaviour, vIDamageReceiver
{
[vEditorToolbar("Default")]
public GameObject targetReceiver;
public vIHealthController healthController;
public float damageMultiplier = 1f;
[HideInInspector]
public vRagdoll ragdoll;
public bool overrideReactionID;
[vHideInInspector("overrideReactionID")]
public int reactionID;
[vEditorToolbar("Random")]
public bool useRandomValues;
[vHideInInspector("useRandomValues")]
public bool fixedValues;
[vHideInInspector("useRandomValues")]
public float minDamageMultiplier, maxDamageMultiplier;
[vHideInInspector("useRandomValues")]
public int minReactionID, maxReactionID;
[vHideInInspector("useRandomValues;fixedValues"), Tooltip("Change Between 0 and 100")]
public float changeToMaxValue;
public UnityEngine.Events.UnityEvent OnGetMaxValue;
[vEditorToolbar("Events")]
[SerializeField] protected OnReceiveDamage _onStartReceiveDamage = new OnReceiveDamage();
[SerializeField] protected OnReceiveDamage _onReceiveDamage = new OnReceiveDamage();
public OnReceiveDamage onStartReceiveDamage { get { return _onStartReceiveDamage; } protected set { _onStartReceiveDamage = value; } }
public OnReceiveDamage onReceiveDamage { get { return _onReceiveDamage; } protected set { _onReceiveDamage = value; } }
protected virtual void Start()
{
ragdoll = GetComponentInParent<vRagdoll>();
}
protected virtual void OnCollisionEnter(Collision collision)
{
if (collision != null)
{
if (ragdoll && ragdoll.isActive)
{
ragdoll.OnRagdollCollisionEnter(new vRagdollCollision(this.gameObject, collision));
}
}
}
public virtual void TakeDamage(vDamage damage)
{
if (healthController == null && targetReceiver)
healthController = targetReceiver.GetComponent<vIHealthController>();
else if (healthController == null)
healthController = GetComponentInParent<vIHealthController>();
if (healthController != null)
{
onStartReceiveDamage.Invoke(damage);
var _damage = ApplyDamageModifiers(damage);
healthController.TakeDamage(_damage);
onReceiveDamage.Invoke(_damage);
}
}
public virtual vDamage ApplyDamageModifiers(vDamage damage)
{
float multiplier = (useRandomValues && !fixedValues) ? Random.Range(minDamageMultiplier, maxDamageMultiplier) :
(useRandomValues && fixedValues) ? randomChange ? maxDamageMultiplier : minDamageMultiplier : damageMultiplier;
var _damage = new vDamage(damage);
_damage.damageValue *= multiplier;
if (multiplier == maxDamageMultiplier) OnGetMaxValue.Invoke();
OverrideReaction(ref _damage);
return _damage;
}
protected virtual void OverrideReaction(ref vDamage damage)
{
if (overrideReactionID)
{
if (useRandomValues && !fixedValues) damage.reaction_id = Random.Range(minReactionID, maxReactionID);
else if (useRandomValues && fixedValues) damage.reaction_id = randomChange ? maxReactionID : minReactionID;
else
damage.reaction_id = reactionID;
}
}
protected virtual bool randomChange
{
get
{
return Random.Range(0f, 100f) < changeToMaxValue;
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 065db88beeb95054185e2f6e1c8cee54
timeCreated: 1439758984
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,4 @@
public interface vIRagdollListener
{
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: fa9e79529c1f3024a8e986548a3f13a3
timeCreated: 1518762024
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,685 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Invector.vCharacterController
{
[vClassHeader("RAGDOLL SYSTEM", true, "ragdollIcon", true, "Every gameobject children of the character must have their tag added in the IgnoreTag List.")]
public class vRagdoll : vMonoBehaviour
{
#region public variables
[vEditorToolbar("Debug")]
public bool startRagdolled = false;
[vButton("Active Ragdoll And Keep Ragdolled", "ActivateRagdollWithDelayToGetUp", typeof(vRagdoll))]
[vButton("Active Ragdoll", "ActivateRagdoll", typeof(vRagdoll))]
[SerializeField] protected float debugTimeToStayRagdolled;
[vEditorToolbar("Settings")]
public LayerMask groundLayer = 1 << 0;
public bool keepRagdolled;
public bool invertGetUpAnim;
public bool _ignoreGetUpAnimation;
public bool removePhysicsAfterDie;
[Tooltip("SHOOTER: Keep false to use detection hit on each children collider, don't forget to change the layer to BodyPart from hips to all children. MELEE: Keep true to only hit the main Capsule Collider.")]
public bool disableColliders = false;
public AudioSource collisionSource;
public AudioClip collisionClip;
[Header("Add Tags for Weapons or Itens here:")]
public List<string> ignoreTags = new List<string>() { "Weapon", "Ignore Ragdoll" };
public AnimatorStateInfo stateInfo;
[Range(0, 2f)]
[Tooltip("The velocity of the parent rigidbody will be applied to the Ragdoll when enabled, creating a more realistic physics")]
public float horizontalMultiplier = 1f;
[Range(0, 2f)]
public float verticalMultiplier = 0.5f;
#endregion
#region private variables
internal vICharacter iChar;
protected Animator animator;
protected Rigidbody _parentRigb;
internal Transform characterChest, characterHips;
[vEditorToolbar("Debug")]
[vReadOnly]
public bool isActive;
[vReadOnly]
public bool inStabilize, updateBehaviour;
//The current state
[vReadOnly]
public RagdollState state = RagdollState.animated;
public bool ignoreGetUpAnimation { get => _ignoreGetUpAnimation; set => _ignoreGetUpAnimation = value; }
protected Vector3 localY { get; set; }
bool ragdolled
{
get
{
return state != RagdollState.animated;
}
set
{
if (value == true)
{
if (state == RagdollState.animated)
{
//Transition from animated to ragdolled
setKinematic(false); //allow the ragdoll RigidBodies to react to the environment
setCollider(false);
animator.enabled = false; //disable animation
state = RagdollState.ragdolled;
}
}
else
{
characterHips.SetParent(hipsParent);
isActive = false;
ragdollingEndTime = Time.time;
if (state == RagdollState.ragdolled)
{
setKinematic(true); //disable gravity etc.
setCollider(true);
//store the state change time
animator.enabled = true; //enable animation
state = RagdollState.blendToAnim;
//Store the ragdolled position for blending
foreach (BodyPart b in bodyParts)
{
b.storedRotation = b.transform.rotation;
b.storedPosition = b.transform.position;
}
//Remember some key positions
ragdolledFeetPosition = 0.5f * (animator.GetBoneTransform(HumanBodyBones.LeftToes).position + animator.GetBoneTransform(HumanBodyBones.RightToes).position);
ragdolledHeadPosition = animator.GetBoneTransform(HumanBodyBones.Head).position;
ragdolledHipPosition = animator.GetBoneTransform(HumanBodyBones.Hips).position;
float lastlocalY = characterHips.TransformDirection(localY).y;
//animator.Rebind();
//Initiate the get up animation
//hip hips forward vector pointing upwards, initiate the get up from back animation
if (!ignoreGetUpAnimation && animator.enabled && gameObject.activeInHierarchy)
{
if (lastlocalY > 0)
{
animator.Play("StandUp@FromBack");
}
else
{
animator.Play("StandUp@FromBelly");
}
}
}
else if (state == RagdollState.animated)
{
setKinematic(true); //disable gravity etc.
setCollider(true);
animator.enabled = true; //enable animation
}
}
}
}
//Possible states of the ragdoll
public enum RagdollState
{
animated, //Mecanim is fully in control
ragdolled, //Mecanim turned off, physics controls the ragdoll
blendToAnim //Mecanim in control, but LateUpdate() is used to partially blend in the last ragdolled pose
}
//How long do we blend when transitioning from ragdolled to animated
readonly float ragdollToMecanimBlendTime = 0.25f;
readonly float mecanimToGetUpTransitionTime = 0.05f;
//A helper variable to store the time when we transitioned from ragdolled to blendToAnim state
float ragdollingEndTime = -100;
//Additional vectores for storing the pose the ragdoll ended up in.
Vector3 ragdolledHipPosition, ragdolledHeadPosition, ragdolledFeetPosition;
//Declare a list of body parts, initialized in Start()
readonly List<BodyPart> bodyParts = new List<BodyPart>();
// used to reset parent of hips
Transform hipsParent;
//used to controll damage frequency
bool inApplyCollisionSound;
private GameObject _ragdollContainer;
class BodyPart
{
public Transform transform;
public Rigidbody rigidbody;
public Collider collider;
public Vector3 storedPosition;
public Quaternion storedRotation;
public BodyPart(Transform t)
{
this.transform = t;
this.rigidbody = t.GetComponent<Rigidbody>();
this.collider = t.GetComponent<Collider>();
}
}
#endregion
protected virtual void Start()
{
// store the Animator component
_parentRigb = GetComponent<Rigidbody>();
iChar = GetComponent<vICharacter>();
if (iChar != null)
{
iChar.onActiveRagdoll.AddListener(ActivateRagdoll);
}
if (!collisionSource)
{
var _collisionPrefab = new GameObject("ragdollAudioSource");
_collisionPrefab.transform.SetParent(gameObject.transform);
_collisionPrefab.transform.position = transform.position;
collisionSource = _collisionPrefab.AddComponent<AudioSource>();
}
LoadBodyPart();
//CreateRagdollContainer();
if (startRagdolled)
{
Invoke("ActivateRagdoll", 0.1f);
}
_ragdollContainer = vObjectContainer.root.gameObject;
}
protected virtual void OnDisable()
{
inStabilize = false;
Invoke("ChangeToHipsParent", 0.01f);
setKinematic(true);
setCollider(true);
}
protected virtual void ChangeToHipsParent()
{
if (characterHips.parent != hipsParent)
{
characterHips.position = transform.position;
characterHips.SetParent(hipsParent);
}
}
public virtual void RestoreRagdoll()
{
if (isActive)
{
ChangeToHipsParent();
iChar.ResetRagdoll();
isActive = false;
state = RagdollState.animated;
inStabilize = false;
setKinematic(true); //disable gravity etc.
setCollider(true);
animator.enabled = true; //enable animation
}
}
public void LoadBodyPart()
{
bodyParts.Clear();
// find character chest and hips
if (!animator)
{
animator = GetComponent<Animator>();
}
characterChest = animator.GetBoneTransform(HumanBodyBones.Chest);
characterHips = animator.GetBoneTransform(HumanBodyBones.Hips);
localY = characterHips.InverseTransformDirection(transform.forward);
hipsParent = characterHips.parent;
// set all RigidBodies to kinematic so that they can be controlled with Mecanim
// and there will be no glitches when transitioning to a ragdoll
// find all the transforms in the character, assuming that this script is attached to the root
if (characterHips)
{
Component[] components = characterHips.GetComponentsInChildren(typeof(Transform));
bodyParts.Add(new BodyPart(characterHips));
// for each of the transforms, create a BodyPart instance and store the transform
for (int i = 0; i < components.Length; i++)
{
var c = components[i];
if (!ignoreTags.Contains(c.tag) && c)
{
var t = c as Transform;
if (t != transform && t.TryGetComponent<Rigidbody>(out Rigidbody r))
{
BodyPart bodyPart = new BodyPart(t);
if (bodyPart.rigidbody != null)
{
bodyPart.rigidbody.isKinematic = true;
// COMMENTED TO UNLOCK CUSTOM TAG FOR BODYPARTS
//c.tag = gameObject.tag;
}
bodyParts.Add(bodyPart);
}
}
}
setKinematic(true);
setCollider(true);
}
}
//void CreateRagdollContainer()
//{
// if (!_ragdollContainer)
// {
// _ragdollContainer = new GameObject("RagdollContainer " + gameObject.name);
// }
// //_ragdollContainer.hideFlags = HideFlags.HideInHierarchy;
//}
protected virtual void LateUpdate()
{
if (animator == null)
{
return;
}
if (!updateBehaviour && animator.updateMode == AnimatorUpdateMode.Fixed)
{
return;
}
updateBehaviour = false;
RagdollBehaviour();
}
protected virtual void FixedUpdate()
{
updateBehaviour = true;
if (!isActive)
{
return;
}
//if (!_ragdollContainer)
//{
// CreateRagdollContainer();
//}
if (characterHips.parent != _ragdollContainer.transform)
{
characterHips.SetParent(_ragdollContainer.transform);
}
if (ragdolled && !inStabilize && !keepRagdolled && !iChar.isDead)
{
ragdolled = false;
StartCoroutine(ResetPlayer(1.1f));
}
else if (animator != null && !animator.isActiveAndEnabled && ragdolled || (animator == null && ragdolled))
{
transform.position = characterHips.position;
}
}
protected virtual void OnDestroy()
{
try
{
if (_ragdollContainer && characterHips && characterHips.parent == _ragdollContainer.transform)
{
//characterHips.SetParent(hipsParent);
Destroy(characterHips.gameObject);
}
}
catch (UnityException e)
{
Debug.LogWarning(e.Message, gameObject);
}
}
/// <summary>
/// Reset the inApplyDamage variable. Set to false;
/// </summary>
protected virtual void ResetCollisionSound()
{
inApplyCollisionSound = false;
}
public virtual void ActivateRagdollWithDelayToGetUp()
{
ActivateRagdoll(null, debugTimeToStayRagdolled);
}
protected virtual void KeepRagdolled(float time)
{
if (time > 0)
{
keepRagdolled = true;
}
CancelInvoke("ResetStayRagdolled");
Invoke("ResetStayRagdolled", time);
}
/// <summary>
/// Reset keep ragdolled time
/// </summary>
public virtual void ResetStayRagdolled()
{
keepRagdolled = false;
}
/// <summary>
/// Active Ragdoll
/// </summary>
public virtual void ActivateRagdoll()
{
ActivateRagdoll(null);
}
/// <summary>
/// Active Ragdoll
/// </summary>
/// <param name="damage">Damage</param>
/// <param name="timeToStayRagdolled">Time to keep ragdolled active</param>
public virtual void ActivateRagdoll(vDamage damage, float timeToStayRagdolled)
{
if (isActive || (damage != null && !damage.activeRagdoll) || state == RagdollState.blendToAnim)
{
return;
}
ActivateRagdoll(damage);
KeepRagdolled(timeToStayRagdolled);
}
// active ragdoll - call this method to turn the ragdoll on
public virtual void ActivateRagdoll(vDamage damage)
{
if (isActive || (damage != null && !damage.activeRagdoll) || state == RagdollState.blendToAnim)
{
return;
}
StopAllCoroutines();
//if (!_ragdollContainer)
//{
// CreateRagdollContainer();
//}
if (damage != null && damage.senselessTime > 0)
{
KeepRagdolled(damage.senselessTime);
}
inApplyCollisionSound = true;
isActive = true;
if (transform.parent != null && !transform.parent.gameObject.isStatic)
{
transform.parent = null;
}
// turn ragdoll on
inStabilize = true;
ragdolled = true;
if (iChar != null)
{
iChar.EnableRagdoll();
}
// start to check if the ragdoll is stable
StartCoroutine(RagdollStabilizer(2f));
// if(!iChar.isDead)
characterHips.SetParent(_ragdollContainer.transform);
Invoke("ResetCollisionSound", 0.2f);
}
// ragdoll collision sound
public virtual void OnRagdollCollisionEnter(vRagdollCollision ragdolCollision)
{
if (!inApplyCollisionSound && ragdolCollision.ImpactForce > 1)
{
if (collisionSource)
{
collisionSource.clip = collisionClip;
collisionSource.volume = ragdolCollision.ImpactForce * 0.05f;
if (!collisionSource.isPlaying)
{
inApplyCollisionSound = true;
collisionSource.Play();
Invoke("ResetCollisionSound", 0.2f);
}
}
}
}
// ragdoll stabilizer - wait until the ragdoll became stable based on the chest velocity.magnitude
protected virtual IEnumerator RagdollStabilizer(float delay)
{
float rdStabilize = Mathf.Infinity;
yield return new WaitForSeconds(delay);
while (rdStabilize > (iChar != null && iChar.isDead ? 0.0001f : 0.1f))
{
if (animator != null && !animator.isActiveAndEnabled)
{
rdStabilize = characterChest.GetComponent<Rigidbody>().linearVelocity.magnitude;
}
else
{
break;
}
yield return new WaitForEndOfFrame();
}
if (iChar != null && iChar.isDead)
{
//Destroy(iChar as Component);
yield return new WaitForEndOfFrame();
DestroyComponents();
}
inStabilize = false;
}
// reset player - restore control to the character
protected virtual IEnumerator ResetPlayer(float waitTime)
{
yield return new WaitForSeconds(waitTime);
//Debug.Log("Ragdoll OFF");
if (iChar != null)
{
iChar.ResetRagdoll();
}
}
// ragdoll blend - code based on the script by Perttu Hämäläinen with modifications to work with this Controller
protected virtual void RagdollBehaviour()
{
var isDead = !(iChar != null && iChar.currentHealth > 0);
if (isDead)
{
return;
}
if (iChar == null || !iChar.ragdolled)
{
return;
}
//Blending from ragdoll back to animated
if (state == RagdollState.blendToAnim)
{
if (Time.time <= ragdollingEndTime + mecanimToGetUpTransitionTime)
{
//If we are waiting for Mecanim to start playing the get up animations, update the root of the mecanim
//character to the best match with the ragdoll
Vector3 animatedToRagdolled = ragdolledHipPosition - animator.GetBoneTransform(HumanBodyBones.Hips).position;
Vector3 newRootPosition = transform.position + animatedToRagdolled;
//Now cast a ray from the computed position downwards and find the highest hit that does not belong to the character
RaycastHit[] hits = Physics.RaycastAll(new Ray(newRootPosition + Vector3.up, Vector3.down), 1, groundLayer, QueryTriggerInteraction.Ignore);
foreach (RaycastHit hit in hits)
{
if (!hit.transform.IsChildOf(transform))
{
newRootPosition.y = Mathf.Max(newRootPosition.y, hit.point.y);
}
}
transform.position = newRootPosition;
//Get body orientation in ground plane for both the ragdolled pose and the animated get up pose
Vector3 ragdolledDirection = ragdolledHeadPosition - ragdolledFeetPosition;
ragdolledDirection.y = 0;
Vector3 meanFeetPosition = 0.5f * (animator.GetBoneTransform(HumanBodyBones.LeftFoot).position + animator.GetBoneTransform(HumanBodyBones.RightFoot).position);
Vector3 animatedDirection = animator.GetBoneTransform(HumanBodyBones.Head).position - meanFeetPosition;
animatedDirection.y = 0;
//Try to match the rotations. Note that we can only rotate around Y axis, as the animated characted must stay upright,
//hence setting the y components of the vectors to zero.
transform.rotation *= Quaternion.FromToRotation(animatedDirection.normalized, ragdolledDirection.normalized);
}
//compute the ragdoll blend amount in the range 0...1
float ragdollBlendAmount = 1.0f - (Time.time - ragdollingEndTime - mecanimToGetUpTransitionTime) / ragdollToMecanimBlendTime;
ragdollBlendAmount = Mathf.Clamp01(ragdollBlendAmount);
//In LateUpdate(), Mecanim has already updated the body pose according to the animations.
//To enable smooth transitioning from a ragdoll to animation, we lerp the position of the hips
//and slerp all the rotations towards the ones stored when ending the ragdolling
foreach (BodyPart b in bodyParts)
{
if (b.transform != transform)
{ //this if is to prevent us from modifying the root of the character, only the actual body parts
//position is only interpolated for the hips
if (b.transform == animator.GetBoneTransform(HumanBodyBones.Hips))
{
b.transform.position = Vector3.Lerp(b.transform.position, b.storedPosition, ragdollBlendAmount);
}
//rotation is interpolated for all body parts
b.transform.rotation = Quaternion.Slerp(b.transform.rotation, b.storedRotation, ragdollBlendAmount);
}
}
//if the ragdoll blend amount has decreased to zero, move to animated state
//Debug.Log($"Blend {ragdollBlendAmount}");
if (ragdollBlendAmount == 0)
{
state = RagdollState.animated;
return;
}
}
}
// set all rigidbodies to kinematic
protected virtual void setKinematic(bool newValue)
{
foreach (var bp in bodyParts)
{
if (bp.transform != null && !ignoreTags.Contains(bp.transform.tag) && bp.rigidbody)
{
if (bp.rigidbody.isKinematic != newValue)
{
bp.rigidbody.isKinematic = newValue;
if (newValue == false)
{
var v = new Vector3(_parentRigb.linearVelocity.x * horizontalMultiplier, _parentRigb.linearVelocity.y * verticalMultiplier, _parentRigb.linearVelocity.z * horizontalMultiplier);
bp.rigidbody.linearVelocity = v;
}
}
}
}
}
// set all colliders to trigger
protected virtual void setCollider(bool newValue)
{
//if (!disableColliders) return;
foreach (var bp in bodyParts)
{
if (bp.transform != null && !ignoreTags.Contains(bp.transform.tag))
{
if (!bp.transform.Equals(transform) && bp.collider)
{
if (disableColliders)
{
bp.collider.enabled = !newValue;
}
else
{
bp.collider.isTrigger = newValue;
}
}
}
}
}
// destroy the components if the character is dead
protected virtual void DestroyComponents()
{
if (removePhysicsAfterDie)
{
var comps = GetComponentsInChildren<MonoBehaviour>();
for (int i = 0; i < comps.Length; i++)
{
if (comps[i].transform != transform)
{
Destroy(comps[i]);
}
}
var joints = GetComponentsInChildren<CharacterJoint>();
if (joints != null)
{
foreach (CharacterJoint comp in joints)
{
if (!ignoreTags.Contains(comp.gameObject.tag) && comp.transform != transform)
{
Destroy(comp);
}
}
}
var rigidbodies = GetComponentsInChildren<Rigidbody>();
if (rigidbodies != null)
{
foreach (Rigidbody comp in rigidbodies)
{
if (!ignoreTags.Contains(comp.gameObject.tag) && comp.transform != transform)
{
Destroy(comp);
}
}
}
var colliders = GetComponentsInChildren<Collider>();
if (colliders != null)
{
foreach (Collider comp in colliders)
{
if (!ignoreTags.Contains(comp.gameObject.tag) && comp.transform != transform)
{
Destroy(comp);
}
}
}
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 6a6e29aa7f269ea44973293e4f139d58
timeCreated: 1527092742
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,37 @@
using UnityEngine;
namespace Invector.vCharacterController
{
public class vRagdollCollision
{
private GameObject sender;
private Collision collision;
private float impactForce;
/// <summary>
/// Gameobjet receiver of collision info
/// </summary>
public GameObject Sender { get { return sender; } }
/// <summary>
/// Collision info
/// </summary>
public Collision Collision { get { return collision; } }
/// <summary>
/// Magnitude of relative linear velocity of the two colliding objects
/// </summary>
public float ImpactForce { get { return impactForce; } }
/// <summary>
/// Create a New collision info seu trouxa
/// </summary>
/// <param name="sender">current gameobjet</param>
/// <param name="collision">current collision info</param>
public vRagdollCollision(GameObject sender, Collision collision)
{
this.sender = sender;
this.collision = collision;
this.impactForce = collision.relativeVelocity.magnitude;
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 746adb0668d598845a517fedb7731b85
timeCreated: 1439758984
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,26 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5cee53205cc08d644b9b81187c31836b, type: 3}
m_Name: vRagdollGenericTemplate
m_EditorClassIdentifier:
root:
leftHips:
leftKnee:
leftFoot:
rightHips:
rightKnee:
rightFoot:
leftArm:
leftElbow:
rightArm:
rightElbow:
middleSpine:
head:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b212dcbbabae4364fa28f5188c894060
timeCreated: 1505424966
licenseType: Store
NativeFormatImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,95 @@
using UnityEngine;
namespace Invector.vCharacterController
{
public class vRagdollGenericTemplate : ScriptableObject
{
[Header("--- Bones Names ---")]
public string root = "Hips";
public string leftHips = "LeftUpperLeg";
public string leftKnee = "LeftLowerLeg";
public string leftFoot = "LeftFoot";
public string rightHips = "RightUpperLeg";
public string rightKnee = "RightLowerLeg";
public string rightFoot = "RightFoot";
public string leftArm = "LeftUpperArm";
public string leftElbow = "LeftLowerArm";
public string rightArm = "RightUpperArm";
public string rightElbow = "RightLowerArm";
public string middleSpine = "Chest";
public string head = "Head";
public Transform GetRoot(Transform rootTransform)
{
return GetBone(root, rootTransform);
}
public Transform GetLeftHips(Transform rootTransform)
{
return GetBone(leftHips, rootTransform);
}
public Transform GetLeftKnee(Transform rootTransform)
{
return GetBone(leftKnee, rootTransform);
}
public Transform GetLeftFoot(Transform rootTransform)
{
return GetBone(leftFoot, rootTransform);
}
public Transform GetRightHips(Transform rootTransform)
{
return GetBone(rightHips, rootTransform);
}
public Transform GetRightKnee(Transform rootTransform)
{
return GetBone(rightKnee, rootTransform);
}
public Transform GetRightFoot(Transform rootTransform)
{
return GetBone(rightFoot, rootTransform);
}
public Transform GetLeftArm(Transform rootTransform)
{
return GetBone(leftArm, rootTransform);
}
public Transform GetLeftElbow(Transform rootTransform)
{
return GetBone(leftElbow, rootTransform);
}
public Transform GetRightArm(Transform rootTransform)
{
return GetBone(rightArm, rootTransform);
}
public Transform GetRightElbow(Transform rootTransform)
{
return GetBone(rightElbow, rootTransform);
}
public Transform GetMiddleSpine(Transform rootTransform)
{
return GetBone(middleSpine, rootTransform);
}
public Transform GetHead(Transform rootTransform)
{
return GetBone(head, rootTransform);
}
Transform GetBone(string boneName, Transform rootTransform)
{
var transforms = rootTransform.GetComponentsInChildren<Transform>();
for (int i = 0; i < transforms.Length; i++)
{
if (transforms[i].gameObject.name.Contains(boneName)) return transforms[i];
if (transforms[i].gameObject.name.ToUpper().Contains(boneName)) return transforms[i];
if (transforms[i].gameObject.name.ToUpper().Contains(boneName.ToUpper())) return transforms[i];
if (transforms[i].gameObject.name.ToLower().Contains(boneName.ToUpper())) return transforms[i];
if (transforms[i].gameObject.name.ToLower().Contains(boneName.ToLower())) return transforms[i];
if (transforms[i].gameObject.name.ToLower().Contains(boneName)) return transforms[i];
}
return null;
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 5cee53205cc08d644b9b81187c31836b
timeCreated: 1527092759
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 09d7f53ccb0d241498eb37d5b8838f2e, type: 3}
userData:
assetBundleName:
assetBundleVariant: