Update
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d5a0f09d1aa7594198de26184d0e3f3
|
||||
folderAsset: yes
|
||||
timeCreated: 1456602722
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,246 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AI;
|
||||
|
||||
namespace Invector.vCharacterController.AI
|
||||
{
|
||||
public class vCreateEnemyEditor : EditorWindow
|
||||
{
|
||||
GUISkin skin;
|
||||
GameObject charObj;
|
||||
Animator charAnimator;
|
||||
RuntimeAnimatorController controller;
|
||||
Vector2 rect = new Vector2(500, 680);
|
||||
Vector2 scrool;
|
||||
UnityEditor.Editor humanoidpreview;
|
||||
Texture2D m_Logo;
|
||||
|
||||
public enum CharacterType
|
||||
{
|
||||
EnemyAI,
|
||||
CompanionAI
|
||||
}
|
||||
|
||||
public CharacterType charType = CharacterType.EnemyAI;
|
||||
|
||||
/// <summary>
|
||||
/// 3rdPersonController Menu
|
||||
/// </summary>
|
||||
[MenuItem("Invector/Melee Combat/Create Simple Melee AI")]
|
||||
public static void CreateNewCharacter()
|
||||
{
|
||||
GetWindow<vCreateEnemyEditor>();
|
||||
}
|
||||
|
||||
bool isHuman, isValidAvatar, charExist;
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
if (!skin)
|
||||
{
|
||||
skin = Resources.Load("vSkin") as GUISkin;
|
||||
}
|
||||
|
||||
GUI.skin = skin;
|
||||
m_Logo = Resources.Load("icon_v2") as Texture2D;
|
||||
|
||||
this.minSize = rect;
|
||||
this.titleContent = new GUIContent("Character", null, "Simple Melee AI Character Creator");
|
||||
|
||||
GUILayout.BeginVertical("SIMPLE MELEE AI CHARACTER CREATOR WINDOW", "window");
|
||||
GUILayout.Label(m_Logo, GUILayout.MaxHeight(25));
|
||||
GUILayout.Space(5);
|
||||
|
||||
GUILayout.BeginVertical("box");
|
||||
|
||||
EditorGUILayout.HelpBox("This is a Simple Melee AI solution that comes with the Melee Combat Package, if you're looking for a more advanced and customizable AI check our AI Template in the AssetStore, it has Shooter Behavior, Waypoint System, NodeEditor for custom behaviours and more...", MessageType.Info);
|
||||
|
||||
charType = (CharacterType)EditorGUILayout.EnumPopup("Character Type", charType);
|
||||
|
||||
if (!charObj)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Make sure to select the FBX model and not a Prefab already with components attached!", MessageType.Info);
|
||||
}
|
||||
else if (!charExist)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Missing a Animator Component", MessageType.Error);
|
||||
}
|
||||
else if (!isHuman)
|
||||
{
|
||||
EditorGUILayout.HelpBox("This is not a Humanoid", MessageType.Error);
|
||||
}
|
||||
else if (!isValidAvatar)
|
||||
{
|
||||
EditorGUILayout.HelpBox(charObj.name + " is a invalid Humanoid", MessageType.Info);
|
||||
}
|
||||
|
||||
charObj = EditorGUILayout.ObjectField("FBX Model", charObj, typeof(GameObject), true, GUILayout.ExpandWidth(true)) as GameObject;
|
||||
|
||||
if (GUI.changed && charObj != null && charObj.GetComponent<vSimpleMeleeAI_Controller>() == null)
|
||||
{
|
||||
humanoidpreview = Editor.CreateEditor(charObj);
|
||||
}
|
||||
|
||||
if (charObj != null && charObj.GetComponent<vSimpleMeleeAI_Controller>() != null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("This gameObject already contains the component v_AIController", MessageType.Warning);
|
||||
}
|
||||
controller = EditorGUILayout.ObjectField("Animator Controller: ", controller, typeof(RuntimeAnimatorController), false) as RuntimeAnimatorController;
|
||||
|
||||
GUILayout.EndVertical();
|
||||
|
||||
GUILayout.BeginHorizontal("box");
|
||||
EditorGUILayout.LabelField("Need to know how it works?");
|
||||
if (GUILayout.Button("Video Tutorial"))
|
||||
{
|
||||
Application.OpenURL("https://www.youtube.com/watch?v=tuwg-H8vjqY&list=PLvgXGzhT_qehtuCYl2oyL-LrWoT7fhg9d&index=3");
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (charObj)
|
||||
{
|
||||
charAnimator = charObj.GetComponent<Animator>();
|
||||
}
|
||||
|
||||
charExist = charAnimator != null;
|
||||
isHuman = charExist ? charAnimator.isHuman : false;
|
||||
isValidAvatar = charExist ? charAnimator.avatar.isValid : false;
|
||||
|
||||
if (CanCreate())
|
||||
{
|
||||
DrawHumanoidPreview();
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
if (controller != null)
|
||||
{
|
||||
if (GUILayout.Button("Create"))
|
||||
{
|
||||
Create();
|
||||
}
|
||||
}
|
||||
GUILayout.FlexibleSpace();
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
bool CanCreate()
|
||||
{
|
||||
return isValidAvatar && isHuman && charObj != null && charObj.GetComponent<vSimpleMeleeAI_Controller>() == null; ;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw the Preview window
|
||||
/// </summary>
|
||||
void DrawHumanoidPreview()
|
||||
{
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
if (humanoidpreview != null)
|
||||
{
|
||||
humanoidpreview.OnInteractivePreviewGUI(GUILayoutUtility.GetRect(100, 400), "window");
|
||||
}
|
||||
}
|
||||
|
||||
private GameObject InstantiateNewCharacter(GameObject selected)
|
||||
{
|
||||
if (selected == null)
|
||||
{
|
||||
return selected;
|
||||
}
|
||||
|
||||
if (selected.scene.IsValid())
|
||||
{
|
||||
return selected;
|
||||
}
|
||||
|
||||
return PrefabUtility.InstantiatePrefab(selected) as GameObject;
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// Created the Third Person Controller
|
||||
/// </summary>
|
||||
public virtual void Create()
|
||||
{
|
||||
// base for the character
|
||||
GameObject newCharacter = InstantiateNewCharacter(charObj);
|
||||
|
||||
if (!newCharacter)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (charType == CharacterType.CompanionAI)
|
||||
{
|
||||
newCharacter.tag = "CompanionAI";
|
||||
newCharacter.name = "vCompanionAI";
|
||||
newCharacter.AddComponent<vSimpleMeleeAI_Companion>();
|
||||
|
||||
var p_layer = LayerMask.NameToLayer("CompanionAI");
|
||||
newCharacter.layer = p_layer;
|
||||
foreach (Transform t in newCharacter.transform.GetComponentsInChildren<Transform>())
|
||||
{
|
||||
t.gameObject.layer = p_layer;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newCharacter.name = "vEnemyAI";
|
||||
newCharacter.tag = "Enemy";
|
||||
newCharacter.AddComponent<vSimpleMeleeAI_Controller>();
|
||||
|
||||
var p_layer = LayerMask.NameToLayer("Enemy");
|
||||
newCharacter.layer = p_layer;
|
||||
foreach (Transform t in newCharacter.transform.GetComponentsInChildren<Transform>())
|
||||
{
|
||||
t.gameObject.layer = p_layer;
|
||||
}
|
||||
}
|
||||
|
||||
// rigidbody settings
|
||||
var rigidbody = newCharacter.AddComponent<Rigidbody>();
|
||||
rigidbody.useGravity = true;
|
||||
rigidbody.constraints = RigidbodyConstraints.FreezeRotation;
|
||||
rigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous;
|
||||
rigidbody.mass = 50;
|
||||
|
||||
// capsule collider settings
|
||||
var collider = newCharacter.AddComponent<CapsuleCollider>();
|
||||
collider.height = ColliderHeight(newCharacter.GetComponent<Animator>());
|
||||
collider.center = new Vector3(0, (float)System.Math.Round(collider.height * 0.5f, 2), 0);
|
||||
collider.radius = (float)System.Math.Round(collider.height * 0.15f, 2);
|
||||
|
||||
// navmesh settings
|
||||
var navMesh = newCharacter.AddComponent<NavMeshAgent>();
|
||||
navMesh.radius = 0.4f;
|
||||
navMesh.height = 1.8f;
|
||||
navMesh.speed = 1f;
|
||||
navMesh.angularSpeed = 300f;
|
||||
navMesh.acceleration = 8f;
|
||||
navMesh.stoppingDistance = 2f;
|
||||
navMesh.autoBraking = false;
|
||||
|
||||
if (controller)
|
||||
{
|
||||
newCharacter.GetComponent<Animator>().runtimeAnimatorController = controller;
|
||||
}
|
||||
|
||||
this.Close();
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Capsule Collider height based on the Character height
|
||||
/// </summary>
|
||||
/// <param name="animator">animator humanoid</param>
|
||||
/// <returns></returns>
|
||||
float ColliderHeight(Animator animator)
|
||||
{
|
||||
var foot = animator.GetBoneTransform(HumanBodyBones.LeftFoot);
|
||||
var hips = animator.GetBoneTransform(HumanBodyBones.Hips);
|
||||
return (float)System.Math.Round(Vector3.Distance(foot.position, hips.position) * 2f, 2);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce527d42ae211d84282aeb892f847e20
|
||||
timeCreated: 1467828935
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,176 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
namespace Invector.vCharacterController.AI
|
||||
{
|
||||
[CanEditMultipleObjects]
|
||||
[CustomEditor(typeof(vSimpleMeleeAI_Motor), true)]
|
||||
public class vSimpleMeleeAI_Editor : vEditorBase
|
||||
{
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
vSimpleMeleeAI_Motor motor = (vSimpleMeleeAI_Motor)target;
|
||||
|
||||
if (motor.gameObject.layer == LayerMask.NameToLayer("Default"))
|
||||
{
|
||||
PopUpLayerInfoEditor window = ScriptableObject.CreateInstance<PopUpLayerInfoEditor>();
|
||||
window.position = new Rect(Screen.width, Screen.height / 2, 360, 100);
|
||||
window.ShowPopup();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSceneGUI()
|
||||
{
|
||||
if (Selection.activeTransform == null || !Selection.activeGameObject.activeSelf)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
vSimpleMeleeAI_Motor motor = (vSimpleMeleeAI_Motor)target;
|
||||
if (!motor)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!motor.displayGizmos)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Handles.color = new Color(0, 0, 0, 0.5f);
|
||||
Handles.DrawSolidDisc(motor.transform.position, Vector3.up, motor.lostTargetDistance);
|
||||
Handles.color = new Color(1, 1, 0, 0.2f);
|
||||
Handles.DrawSolidArc(motor.transform.position, Vector3.up, motor.transform.forward, motor.fieldOfView * 0.5f, motor.maxDetectDistance);
|
||||
Handles.DrawSolidArc(motor.transform.position, Vector3.up, motor.transform.forward, -motor.fieldOfView * 0.5f, motor.maxDetectDistance);
|
||||
Handles.color = new Color(1, 1, 1, 0.5f);
|
||||
Handles.DrawWireDisc(motor.transform.position, Vector3.up, motor.maxDetectDistance);
|
||||
Handles.color = new Color(0, 1, 0, 0.1f);
|
||||
Handles.DrawSolidDisc(motor.transform.position, Vector3.up, motor.strafeDistance);
|
||||
Handles.color = new Color(1, 0, 0, 0.2f);
|
||||
Handles.DrawSolidDisc(motor.transform.position, Vector3.up, motor.minDetectDistance);
|
||||
Handles.color = new Color(0, 0, 1, 0.2f);
|
||||
Handles.DrawSolidDisc(motor.transform.position, Vector3.up, motor.distanceToAttack);
|
||||
}
|
||||
|
||||
void CreateSensor(vSimpleMeleeAI_Motor motor)
|
||||
{
|
||||
if (Selection.activeTransform == null || !Selection.activeGameObject.activeSelf)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
motor.sphereSensor = motor.GetComponentInChildren<vSimpleMeleeAI_SphereSensor>();
|
||||
if (motor.sphereSensor != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var sensor = new GameObject("SphereSensor", typeof(SphereCollider));
|
||||
var layer = LayerMask.NameToLayer("Triggers");
|
||||
sensor.layer = layer;
|
||||
sensor.tag = "Weapon";
|
||||
motor.sphereSensor = sensor.AddComponent<vSimpleMeleeAI_SphereSensor>();
|
||||
sensor.transform.position = motor.transform.position;
|
||||
sensor.transform.parent = motor.transform;
|
||||
motor.sphereSensor.GetComponent<SphereCollider>().isTrigger = true;
|
||||
EditorUtility.SetDirty(motor);
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
vSimpleMeleeAI_Motor motor = (vSimpleMeleeAI_Motor)target;
|
||||
serializedObject.Update();
|
||||
if (!motor)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (motor.sphereSensor == null)
|
||||
{
|
||||
CreateSensor(motor);
|
||||
}
|
||||
else
|
||||
{
|
||||
motor.sphereSensor.SetColliderRadius(motor.maxDetectDistance);
|
||||
}
|
||||
|
||||
if (motor.gameObject.layer == LayerMask.NameToLayer("Default"))
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.HelpBox("Please assign the Layer of the Character to 'Enemy'", MessageType.Warning);
|
||||
}
|
||||
|
||||
if (motor.groundLayer == 0)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.HelpBox("Please assign the Ground Layer to 'Default' ", MessageType.Warning);
|
||||
}
|
||||
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
GUILayout.Box("Current Health: " + motor.currentHealth.ToString());
|
||||
}
|
||||
|
||||
base.OnInspectorGUI();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
}
|
||||
|
||||
//**********************************************************************************//
|
||||
// DEBUG RAYCASTS //
|
||||
// draw the casts of the controller on play mode //
|
||||
//**********************************************************************************//
|
||||
[DrawGizmo(GizmoType.Selected)]
|
||||
private static void CustomDrawGizmos(Transform aTarget, GizmoType aGizmoType)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
vSimpleMeleeAI_Motor motor = aTarget.GetComponent<vSimpleMeleeAI_Motor>();
|
||||
|
||||
if (!motor || !motor.enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Selection.activeTransform == null || !Selection.activeGameObject.activeSelf)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// debug auto crouch
|
||||
Vector3 posHead = motor.transform.position + Vector3.up * ((motor._capsuleCollider.height * 0.5f) - motor._capsuleCollider.radius);
|
||||
Ray ray1 = new Ray(posHead, Vector3.up);
|
||||
Gizmos.DrawWireSphere(ray1.GetPoint((motor.headDetect - (motor._capsuleCollider.radius * 0.1f))), motor._capsuleCollider.radius * 0.9f);
|
||||
Handles.Label(ray1.GetPoint((motor.headDetect + (motor._capsuleCollider.radius))), "Head Detection");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public class PopUpLayerInfoEditor : EditorWindow
|
||||
{
|
||||
GUISkin skin;
|
||||
Vector2 rect = new Vector2(360, 100);
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
this.titleContent = new GUIContent("Warning!");
|
||||
this.minSize = rect;
|
||||
|
||||
EditorGUILayout.HelpBox("Please assign your EnemyAI to the Layer 'Enemy'.", MessageType.Warning);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (GUILayout.Button("OK", GUILayout.Width(80), GUILayout.Height(20)))
|
||||
{
|
||||
this.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 29c5953a8ccf7dd41a954286bb5af48f
|
||||
timeCreated: 1456602756
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,271 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Invector.vCharacterController.AI
|
||||
{
|
||||
using vItemManager;
|
||||
[CustomEditor(typeof(vSimpleMeleeAI_WeaponsControl))]
|
||||
public class vSimpleMeleeAI_WeaponsControl_Editor : UnityEditor.Editor
|
||||
{
|
||||
GUISkin skin;
|
||||
vSimpleMeleeAI_WeaponsControl weaponCtrl;
|
||||
bool editLeftCustomPoint, isOpenL;
|
||||
bool editRightCustomPoint, isOpenR;
|
||||
Animator animator;
|
||||
Transform leftHand;
|
||||
Transform rightHand;
|
||||
string customPointName;
|
||||
int selectedItemL, selectedItemR;
|
||||
int[] ids;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
weaponCtrl = (vSimpleMeleeAI_WeaponsControl)target;
|
||||
weaponCtrl.itemCollection = weaponCtrl.GetComponentInChildren<vItemCollection>(true);
|
||||
skin = Resources.Load("vSkin") as GUISkin;
|
||||
animator = weaponCtrl.GetComponent<Animator>();
|
||||
if (animator)
|
||||
{
|
||||
leftHand = animator.GetBoneTransform(HumanBodyBones.LeftHand);
|
||||
rightHand = animator.GetBoneTransform(HumanBodyBones.RightHand);
|
||||
if (leftHand && weaponCtrl.defaultEquipPointL == null)
|
||||
{
|
||||
var customPoint = new GameObject("defaultEquipPoint");
|
||||
|
||||
customPoint.transform.parent = leftHand;
|
||||
customPoint.transform.localPosition = Vector3.zero;
|
||||
customPoint.transform.forward = weaponCtrl.transform.forward;
|
||||
weaponCtrl.defaultEquipPointL = customPoint.transform;
|
||||
EditorUtility.SetDirty(weaponCtrl);
|
||||
}
|
||||
if (rightHand && weaponCtrl.defaultEquipPointR == null)
|
||||
{
|
||||
var child = rightHand.Find("defaultEquipPoint");
|
||||
GameObject customPoint;
|
||||
if (child)
|
||||
{
|
||||
customPoint = child.gameObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
customPoint = new GameObject("defaultEquipPoint");
|
||||
}
|
||||
|
||||
customPoint.transform.parent = leftHand;
|
||||
customPoint.transform.localPosition = Vector3.zero;
|
||||
customPoint.transform.forward = weaponCtrl.transform.forward;
|
||||
weaponCtrl.defaultEquipPointR = customPoint.transform;
|
||||
EditorUtility.SetDirty(weaponCtrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string[] GetItems()
|
||||
{
|
||||
if (weaponCtrl.itemCollection && weaponCtrl.itemCollection.items != null && weaponCtrl.itemCollection.itemListData && weaponCtrl.itemCollection.itemListData.items != null)
|
||||
{
|
||||
var items = weaponCtrl.itemCollection.items.FindAll(_item => weaponCtrl.itemCollection.itemListData.items.Find(_item2 => _item2.id == _item.id && _item2.type != vItemType.Consumable));
|
||||
string[] names = new string[items.Count];
|
||||
ids = new int[items.Count];
|
||||
for (int i = 0; i < names.Length; i++)
|
||||
{
|
||||
var item = weaponCtrl.itemCollection.itemListData.items.Find(_item => _item.id == items[i].id);
|
||||
if (item != null)
|
||||
{
|
||||
names[i] = item.name;
|
||||
ids[i] = item.id;
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
ids = new int[0];
|
||||
return new string[0];
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
if (skin)
|
||||
{
|
||||
GUI.skin = skin;
|
||||
}
|
||||
|
||||
serializedObject.Update();
|
||||
GUILayout.BeginVertical("AI Weapons Control", "window");
|
||||
|
||||
GUILayout.Space(30);
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("m_Script"));
|
||||
weaponCtrl.itemCollection = (vItemCollection)EditorGUILayout.ObjectField("Item Collection", weaponCtrl.itemCollection, typeof(vItemCollection), true);
|
||||
if (weaponCtrl.itemCollection)
|
||||
{
|
||||
var names = GetItems();
|
||||
GUILayout.BeginVertical("box");
|
||||
GUILayout.Box("Left Weapon");
|
||||
weaponCtrl.useLeftWeapon = EditorGUILayout.Toggle("Use Left Weapon", weaponCtrl.useLeftWeapon);
|
||||
if (weaponCtrl.useLeftWeapon)
|
||||
{
|
||||
weaponCtrl.randomLeftWeapon = EditorGUILayout.Toggle("Random Weapon", weaponCtrl.randomLeftWeapon);
|
||||
if (!weaponCtrl.randomLeftWeapon)
|
||||
{
|
||||
if (names.Length > 0)
|
||||
{
|
||||
if (!Array.Exists<int>(ids, num => num == weaponCtrl.leftWeaponID))
|
||||
{
|
||||
weaponCtrl.leftWeaponID = ids[0];
|
||||
}
|
||||
|
||||
var indexOf = Array.IndexOf(ids, weaponCtrl.leftWeaponID);
|
||||
indexOf = EditorGUILayout.Popup("Weapon", indexOf, names);
|
||||
weaponCtrl.leftWeaponID = ids[indexOf];
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.HelpBox("ItemCollect dosent have Weapon Items", MessageType.Warning);
|
||||
}
|
||||
|
||||
}
|
||||
weaponCtrl.defaultEquipPointL = (Transform)EditorGUILayout.ObjectField("LeftDefaultPoint", weaponCtrl.defaultEquipPointL, typeof(Transform), true);
|
||||
DrawCustomEquipPoint(ref weaponCtrl.customEquipPointL, ref isOpenL, ref editLeftCustomPoint, "Left Custom Equip Points");
|
||||
}
|
||||
GUILayout.EndVertical();
|
||||
GUILayout.BeginVertical("box");
|
||||
GUILayout.Box("Right Weapon");
|
||||
weaponCtrl.useRightWeapon = EditorGUILayout.Toggle("Use Right Weapon", weaponCtrl.useRightWeapon);
|
||||
if (weaponCtrl.useRightWeapon)
|
||||
{
|
||||
weaponCtrl.randomRightWeapon = EditorGUILayout.Toggle("Random Weapon", weaponCtrl.randomRightWeapon);
|
||||
if (!weaponCtrl.randomRightWeapon)
|
||||
{
|
||||
if (names.Length > 0)
|
||||
{
|
||||
if (!Array.Exists<int>(ids, num => num == weaponCtrl.rightWeaponID))
|
||||
{
|
||||
weaponCtrl.rightWeaponID = ids[0];
|
||||
}
|
||||
|
||||
var indexOf = Array.IndexOf(ids, weaponCtrl.rightWeaponID);
|
||||
indexOf = EditorGUILayout.Popup("Weapon", indexOf, names);
|
||||
weaponCtrl.rightWeaponID = ids[indexOf];
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.HelpBox("ItemCollect dosent have Weapon Items", MessageType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
weaponCtrl.defaultEquipPointR = (Transform)EditorGUILayout.ObjectField("RightDefaultPoint", weaponCtrl.defaultEquipPointR, typeof(Transform), true);
|
||||
DrawCustomEquipPoint(ref weaponCtrl.customEquipPointR, ref isOpenR, ref editRightCustomPoint, "Right Custom Equip Points");
|
||||
}
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.HelpBox("Please Create a item Collection inside Character to use", MessageType.Warning);
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
if (GUI.changed)
|
||||
{
|
||||
EditorUtility.SetDirty(weaponCtrl);
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawCustomEquipPoint(ref List<Transform> list, ref bool isOpen, ref bool inEdition, string name)
|
||||
{
|
||||
if (list == null)
|
||||
{
|
||||
list = new List<Transform>();
|
||||
}
|
||||
|
||||
GUILayout.BeginVertical("box");
|
||||
isOpen = GUILayout.Toggle(isOpen, name, EditorStyles.miniButton);
|
||||
|
||||
if (isOpen)
|
||||
{
|
||||
if (!inEdition && GUILayout.Button("New Custom Point", EditorStyles.miniButton))
|
||||
{
|
||||
inEdition = true;
|
||||
}
|
||||
if (inEdition)
|
||||
{
|
||||
Transform parentBone;
|
||||
if (name.Contains("Left") || name.Contains("left"))
|
||||
{
|
||||
leftHand = (Transform)EditorGUILayout.ObjectField("Parent Bone", leftHand, typeof(Transform), true);
|
||||
parentBone = leftHand;
|
||||
}
|
||||
else
|
||||
{
|
||||
rightHand = (Transform)EditorGUILayout.ObjectField("Parent Bone", rightHand, typeof(Transform), true);
|
||||
parentBone = rightHand;
|
||||
}
|
||||
|
||||
customPointName = EditorGUILayout.TextField("Custom Point Name", customPointName);
|
||||
bool valid = true;
|
||||
if (string.IsNullOrEmpty(customPointName))
|
||||
{
|
||||
valid = false;
|
||||
EditorGUILayout.HelpBox("Custom Point Name is empty", MessageType.Error);
|
||||
}
|
||||
if (list.Find(t => t.gameObject.name.Equals(customPointName)) != null)
|
||||
{
|
||||
valid = false;
|
||||
EditorGUILayout.HelpBox("Custom Point Name already exist", MessageType.Error);
|
||||
}
|
||||
GUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("Cancel", EditorStyles.miniButton))
|
||||
{
|
||||
inEdition = false;
|
||||
}
|
||||
|
||||
GUI.enabled = parentBone && valid;
|
||||
|
||||
if (GUILayout.Button("Create", EditorStyles.miniButton))
|
||||
{
|
||||
var customPoint = new GameObject(customPointName);
|
||||
|
||||
customPoint.transform.parent = parentBone;
|
||||
customPoint.transform.localPosition = Vector3.zero;
|
||||
customPoint.transform.forward = weaponCtrl.transform.forward;
|
||||
list.Add(customPoint.transform);
|
||||
EditorUtility.SetDirty(weaponCtrl);
|
||||
inEdition = false;
|
||||
}
|
||||
GUI.enabled = true;
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
bool remove = false;
|
||||
GUILayout.BeginHorizontal();
|
||||
list[i] = (Transform)EditorGUILayout.ObjectField(list[i], typeof(Transform), true);
|
||||
if (GUILayout.Button("X", EditorStyles.miniButton, GUILayout.Width(20)))
|
||||
{
|
||||
remove = true;
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
if (remove)
|
||||
{
|
||||
if (list[i] != null)
|
||||
{
|
||||
DestroyImmediate(list[i].gameObject);
|
||||
}
|
||||
|
||||
list.RemoveAt(i);
|
||||
EditorUtility.SetDirty(weaponCtrl);
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1e83b0485525f1418768f711221c54a
|
||||
timeCreated: 1476291778
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,382 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Invector.vCharacterController.AI
|
||||
{
|
||||
public class vSimpleMeleeAI_Animator : vSimpleMeleeAI_Motor
|
||||
{
|
||||
#region AI Variables
|
||||
|
||||
private bool triggerDieBehaviour;
|
||||
private bool resetState;
|
||||
private float strafeInput;
|
||||
|
||||
// get Layers from the Animator Controller
|
||||
public AnimatorStateInfo baseLayerInfo, rightArmInfo, leftArmInfo, fullBodyInfo, upperBodyInfo, underBodyInfo;
|
||||
|
||||
int baseLayer { get { return animator.GetLayerIndex("Base Layer"); } }
|
||||
int underBodyLayer { get { return animator.GetLayerIndex("UnderBody"); } }
|
||||
int rightArmLayer { get { return animator.GetLayerIndex("RightArm"); } }
|
||||
int leftArmLayer { get { return animator.GetLayerIndex("LeftArm"); } }
|
||||
int upperBodyLayer { get { return animator.GetLayerIndex("UpperBody"); } }
|
||||
int fullbodyLayer { get { return animator.GetLayerIndex("FullBody"); } }
|
||||
|
||||
#endregion
|
||||
|
||||
public void UpdateAnimator(float _speed, float _direction)
|
||||
{
|
||||
if (animator == null || !animator.enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LayerControl();
|
||||
LocomotionAnimation(_speed, _direction);
|
||||
|
||||
RollAnimation();
|
||||
CrouchAnimation();
|
||||
|
||||
ResetAndLockAgent();
|
||||
MoveSetIDControl();
|
||||
MeleeATK_Animation();
|
||||
DEF_Animation();
|
||||
|
||||
DeadAnimation();
|
||||
}
|
||||
|
||||
void LayerControl()
|
||||
{
|
||||
baseLayerInfo = animator.GetCurrentAnimatorStateInfo(baseLayer);
|
||||
underBodyInfo = animator.GetCurrentAnimatorStateInfo(underBodyLayer);
|
||||
rightArmInfo = animator.GetCurrentAnimatorStateInfo(rightArmLayer);
|
||||
leftArmInfo = animator.GetCurrentAnimatorStateInfo(leftArmLayer);
|
||||
upperBodyInfo = animator.GetCurrentAnimatorStateInfo(upperBodyLayer);
|
||||
fullBodyInfo = animator.GetCurrentAnimatorStateInfo(fullbodyLayer);
|
||||
}
|
||||
|
||||
void OnAnimatorMove()
|
||||
{
|
||||
actions = baseLayerInfo.IsTag("CustomAction") || lockMovement;
|
||||
|
||||
if (Time.timeScale == 0 || agent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (agent.enabled && !agent.isOnOffMeshLink && agent.updatePosition)
|
||||
{
|
||||
Vector3 velocity = animator.deltaPosition / Time.deltaTime;
|
||||
if (!velocity.IsVectorNaN())
|
||||
{
|
||||
agent.velocity = velocity;
|
||||
}
|
||||
else
|
||||
{
|
||||
agent.velocity = Vector3.zero;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_rigidbody.useGravity && !actions && !agent.isOnOffMeshLink)
|
||||
{
|
||||
_rigidbody.linearVelocity = animator.deltaPosition;
|
||||
}
|
||||
|
||||
if (!agent.updatePosition && !actions)
|
||||
{
|
||||
var point = agent.enabled ? agent.nextPosition : destination;
|
||||
if (Vector3.Distance(transform.position, point) > 0.5f)
|
||||
{
|
||||
desiredRotation = Quaternion.LookRotation(point - transform.position);
|
||||
var rot = Quaternion.Euler(transform.eulerAngles.x, desiredRotation.eulerAngles.y, transform.eulerAngles.z);
|
||||
transform.rotation = Quaternion.RotateTowards(transform.rotation, rot, agent.angularSpeed * Time.deltaTime);
|
||||
}
|
||||
transform.position = animator.rootPosition;
|
||||
return;
|
||||
}
|
||||
// Strafe Movement
|
||||
if (OnStrafeArea && !actions && currentTarget.transform != null && canSeeTarget && currentHealth > 0f)
|
||||
{
|
||||
Vector3 targetDir = currentTarget.transform.position - transform.position;
|
||||
float step = (meleeManager != null && isAttacking) ? attackRotationSpeed * Time.deltaTime : (strafeRotationSpeed * Time.deltaTime);
|
||||
Vector3 newDir = Vector3.RotateTowards(transform.forward, targetDir, step, 0.0F);
|
||||
var rot = Quaternion.LookRotation(newDir);
|
||||
transform.eulerAngles = new Vector3(transform.eulerAngles.x, rot.eulerAngles.y, transform.eulerAngles.z);
|
||||
}
|
||||
// Rotate the Character to the OffMeshLink End
|
||||
else if (agent.isOnOffMeshLink && !actions)
|
||||
{
|
||||
var pos = agent.nextOffMeshLinkData.endPos;
|
||||
targetPos = pos;
|
||||
UnityEngine.AI.OffMeshLinkData data = agent.currentOffMeshLinkData;
|
||||
desiredRotation = Quaternion.LookRotation(new Vector3(data.endPos.x, transform.position.y, data.endPos.z) - transform.position);
|
||||
transform.rotation = Quaternion.RotateTowards(transform.rotation, desiredRotation, (agent.angularSpeed * 2f) * Time.deltaTime);
|
||||
}
|
||||
// Free Movement
|
||||
else if (agent.desiredVelocity.magnitude > 0.1f && !actions && agent.enabled && currentHealth > 0f)
|
||||
{
|
||||
if (meleeManager != null && isAttacking)
|
||||
{
|
||||
desiredRotation = Quaternion.LookRotation(agent.desiredVelocity);
|
||||
transform.rotation = Quaternion.RotateTowards(transform.rotation, desiredRotation, agent.angularSpeed * attackRotationSpeed * Time.deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
desiredRotation = Quaternion.LookRotation(agent.desiredVelocity);
|
||||
transform.rotation = Quaternion.RotateTowards(transform.rotation, desiredRotation, agent.angularSpeed * Time.deltaTime);
|
||||
}
|
||||
}
|
||||
// Use the Animator rotation while doing an Action
|
||||
else if (actions || currentHealth <= 0f || isAttacking)
|
||||
{
|
||||
if (isRolling)
|
||||
{
|
||||
desiredRotation = Quaternion.LookRotation(rollDirection, Vector3.up);
|
||||
transform.rotation = desiredRotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.rotation = animator.rootRotation;
|
||||
}
|
||||
|
||||
// Use the Animator position while doing an Action
|
||||
if (!agent.enabled)
|
||||
{
|
||||
destination = transform.position;
|
||||
transform.position = animator.rootPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region AI Locomotion Animations
|
||||
|
||||
/// <summary>
|
||||
/// Control the Locomotion behaviour of the AI
|
||||
/// </summary>
|
||||
/// <param name="_speed"></param>
|
||||
/// <param name="_direction"></param>
|
||||
void LocomotionAnimation(float _speed, float _direction)
|
||||
{
|
||||
isGrounded = agent.enabled ? agent.isOnNavMesh : isRolling ? true : groundDistance <= groundCheckDistance;
|
||||
animator.SetBool("IsGrounded", isGrounded);
|
||||
_speed = Mathf.Clamp(_speed, -maxSpeed, maxSpeed);
|
||||
if (OnStrafeArea)
|
||||
{
|
||||
_direction = Mathf.Clamp(_direction, -strafeSpeed, strafeSpeed);
|
||||
}
|
||||
|
||||
var animSpeed = Mathf.Abs(_speed) > 0.1f ? _speed : 0;
|
||||
var animDirection = Mathf.Abs(_direction) > 0.1f ? _direction : 0;
|
||||
var newInput = new Vector2(animSpeed, animDirection);
|
||||
strafeInput = Mathf.Clamp(newInput.magnitude, 0, 1.5f);
|
||||
|
||||
animator.SetFloat("InputMagnitude", strafeInput, .2f, Time.deltaTime);
|
||||
animator.SetFloat("InputVertical", actions ? 0 : (_speed != 0) ? _speed : 0, 0.2f, Time.fixedDeltaTime);
|
||||
animator.SetFloat("InputHorizontal", _direction, 0.2f, Time.fixedDeltaTime);
|
||||
animator.SetBool("IsStrafing", OnStrafeArea);
|
||||
animator.SetBool("isDead", isDead);
|
||||
}
|
||||
|
||||
protected virtual float maxSpeed
|
||||
{
|
||||
get
|
||||
{
|
||||
return (OnStrafeArea ? strafeSpeed : chaseSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trigger a Death by Animation, Animation with Ragdoll or just turn the Ragdoll On
|
||||
/// </summary>
|
||||
void DeadAnimation()
|
||||
{
|
||||
if (!isDead)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!triggerDieBehaviour)
|
||||
{
|
||||
triggerDieBehaviour = true;
|
||||
DeathBehaviour();
|
||||
}
|
||||
|
||||
// death by animation
|
||||
if (deathBy == DeathBy.Animation)
|
||||
{
|
||||
if (fullBodyInfo.IsName("Dead"))
|
||||
{
|
||||
if (fullBodyInfo.normalizedTime >= 0.99f && groundDistance <= 0.15f)
|
||||
{
|
||||
RemoveComponents();
|
||||
}
|
||||
}
|
||||
}
|
||||
// death by animation & ragdoll after a time
|
||||
else if (deathBy == DeathBy.AnimationWithRagdoll)
|
||||
{
|
||||
if (fullBodyInfo.IsName("Dead"))
|
||||
{
|
||||
// activate the ragdoll after the animation finish played
|
||||
if (fullBodyInfo.normalizedTime >= 0.8f)
|
||||
{
|
||||
onActiveRagdoll.Invoke(null);
|
||||
RemoveComponents();
|
||||
}
|
||||
}
|
||||
}
|
||||
// death by ragdoll
|
||||
else if (deathBy == DeathBy.Ragdoll)
|
||||
{
|
||||
onActiveRagdoll.Invoke(null);
|
||||
RemoveComponents();
|
||||
}
|
||||
}
|
||||
|
||||
private void DeathBehaviour()
|
||||
{
|
||||
// change the culling mode to render the animation until finish
|
||||
animator.cullingMode = AnimatorCullingMode.AlwaysAnimate;
|
||||
// trigger die animation
|
||||
if (deathBy == DeathBy.Animation || deathBy == DeathBy.AnimationWithRagdoll)
|
||||
{
|
||||
animator.SetBool("isDead", isDead);
|
||||
}
|
||||
}
|
||||
|
||||
void CrouchAnimation()
|
||||
{
|
||||
animator.SetBool("IsCrouching", isCrouched);
|
||||
|
||||
if (animator != null && animator.enabled)
|
||||
{
|
||||
CheckAutoCrouch();
|
||||
}
|
||||
}
|
||||
|
||||
protected void RollAnimation()
|
||||
{
|
||||
if (animator == null || animator.enabled == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
isRolling = baseLayerInfo.IsName("Roll");
|
||||
if (isRolling)
|
||||
{
|
||||
_rigidbody.constraints = RigidbodyConstraints.None | RigidbodyConstraints.FreezeRotation;
|
||||
_rigidbody.useGravity = true;
|
||||
agent.enabled = false;
|
||||
agent.updatePosition = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ResetAIRotation()
|
||||
{
|
||||
transform.eulerAngles = new Vector3(0, transform.eulerAngles.y, 0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AI Melee Combat Animations
|
||||
|
||||
/// <summary>
|
||||
/// MOVE SET ID - check the Animator to see what MoveSet the character will move, also check your weapon to see if the moveset matches
|
||||
/// ps* Move Set is the way your character will move, ATK_ID is the way your character will attack. You can have different locomotion animations and attacks.
|
||||
/// </summary>
|
||||
void MoveSetIDControl()
|
||||
{
|
||||
if (meleeManager == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
animator.SetFloat("MoveSet_ID", meleeManager.GetMoveSetID());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Control Attack Behaviour
|
||||
/// </summary>
|
||||
void MeleeATK_Animation()
|
||||
{
|
||||
if (meleeManager == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (actions)
|
||||
{
|
||||
attackCount = 0;
|
||||
}
|
||||
|
||||
animator.SetInteger("AttackID", meleeManager.GetAttackID());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ATTACK MELEE ANIMATION - it's activate by the AttackInput() method at the TPController by a trigger
|
||||
/// </summary>
|
||||
void DEF_Animation()
|
||||
{
|
||||
if (meleeManager == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBlocking)
|
||||
{
|
||||
animator.SetInteger("DefenseID", meleeManager.GetDefenseID());
|
||||
}
|
||||
animator.SetBool("IsBlocking", isBlocking);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trigger the Attack animation
|
||||
/// </summary>
|
||||
public void MeleeAttack()
|
||||
{
|
||||
if (animator != null && animator.enabled && !actions)
|
||||
{
|
||||
animator.SetTrigger("WeakAttack");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if is in LockMovement to reset attack and disable agent
|
||||
/// </summary>
|
||||
void ResetAndLockAgent()
|
||||
{
|
||||
lockMovement = fullBodyInfo.IsTag("LockMovement") || upperBodyInfo.IsTag("ResetState");
|
||||
|
||||
if (lockMovement)
|
||||
{
|
||||
if (attackCount > 0)
|
||||
{
|
||||
canAttack = false;
|
||||
attackCount = 0;
|
||||
}
|
||||
|
||||
if (baseLayerInfo.normalizedTime > 0.1f)
|
||||
{
|
||||
animator.ResetTrigger("ResetState");
|
||||
_rigidbody.constraints = RigidbodyConstraints.None | RigidbodyConstraints.FreezeRotation;
|
||||
_rigidbody.useGravity = true;
|
||||
agent.enabled = false;
|
||||
agent.updatePosition = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trigger Recoil Animation - It's Called at the MeleeWeapon
|
||||
/// </summary>
|
||||
public void TriggerRecoil(int recoil_id)
|
||||
{
|
||||
if (animator != null && animator.enabled && !isRolling)
|
||||
{
|
||||
animator.SetInteger("RecoilID", recoil_id);
|
||||
animator.SetTrigger("TriggerRecoil");
|
||||
animator.SetTrigger("ResetState");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d7f27e9e1a4aa34cab4f80a72164fa7
|
||||
timeCreated: 1452173169
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,306 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Invector.vCharacterController.AI
|
||||
{
|
||||
public class vSimpleMeleeAI_Companion : vSimpleMeleeAI_Controller
|
||||
{
|
||||
[vEditorToolbar("Companion")]
|
||||
[SerializeField] protected string _companionTag = "Player";
|
||||
public virtual string companionTag { get { return _companionTag; } set { _companionTag = value; } }
|
||||
public virtual float companionMaxDistance { get { return _companionMaxDistance; } set { _companionMaxDistance = value; } }
|
||||
[SerializeField] protected float _companionMaxDistance = 10f;
|
||||
[Range(0f, 1.5f)]
|
||||
[SerializeField] protected float _followSpeed = 1f;
|
||||
public virtual float followSpeed { get { return _followSpeed; } set { _followSpeed = value; } }
|
||||
[SerializeField] protected float _followStopDistance = 2f;
|
||||
public virtual float followStopDistance { get { return _followStopDistance; } set { _followStopDistance = value; } }
|
||||
[Range(0f, 1.5f)]
|
||||
[SerializeField] protected float _moveToStopDistance = 0.5f;
|
||||
public virtual float moveToStopDistance { get { return _moveToStopDistance; } set { _moveToStopDistance = value; } }
|
||||
[SerializeField] protected Transform _moveToTarget;
|
||||
public virtual Transform moveToTarget { get { return _moveToTarget; } set { _moveToTarget = value; } }
|
||||
[SerializeField] protected CompanionState _companionState = CompanionState.Follow;
|
||||
public virtual CompanionState companionState { get { return _companionState; } set { _companionState = value; } }
|
||||
[SerializeField] protected Transform _companion;
|
||||
public virtual Transform companion { get { return _companion; } set { _companion = value; } }
|
||||
|
||||
public bool debug = true;
|
||||
public UnityEngine.UI.Text debugUIText;
|
||||
|
||||
public enum CompanionState
|
||||
{
|
||||
None, // this state works with AiController normal routine
|
||||
Follow,
|
||||
MoveTo,
|
||||
Stay
|
||||
}
|
||||
|
||||
protected void LateUpdate()
|
||||
{
|
||||
CompanionInputs();
|
||||
}
|
||||
|
||||
protected virtual void CompanionInputs()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.Alpha1))
|
||||
{
|
||||
companionState = CompanionState.Stay;
|
||||
agressiveAtFirstSight = false;
|
||||
}
|
||||
if (Input.GetKeyDown(KeyCode.Alpha2))
|
||||
{
|
||||
companionState = CompanionState.Follow;
|
||||
agressiveAtFirstSight = false;
|
||||
}
|
||||
if (Input.GetKeyDown(KeyCode.Alpha3))
|
||||
{
|
||||
agressiveAtFirstSight = !agressiveAtFirstSight;
|
||||
}
|
||||
if (Input.GetKeyDown(KeyCode.Alpha4) && moveToTarget != null)
|
||||
{
|
||||
SetMoveTo(moveToTarget);
|
||||
companionState = CompanionState.MoveTo;
|
||||
agressiveAtFirstSight = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the companion distance.
|
||||
/// </summary>
|
||||
/// <value>The companion distance.</value>
|
||||
protected virtual float companionDistance
|
||||
{
|
||||
get { return companion != null ? Vector3.Distance(transform.position, companion.transform.position) : 0f; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this <see cref="vSimpleMeleeAI_Companion"/> is near of companion. Relative to <see cref="companionMaxDistance"/>
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if near of companion; otherwise, <c>false</c>.</value>
|
||||
protected virtual bool nearOfCompanion
|
||||
{
|
||||
get
|
||||
{
|
||||
var value = ((companion != null && companion.gameObject.activeSelf && companionDistance < companionMaxDistance) || (companion == null || !companion.gameObject.activeSelf));
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the target Move to.
|
||||
/// </summary>
|
||||
/// <param name="_target">Target.</param>
|
||||
public virtual void SetMoveTo(Transform _target)
|
||||
{
|
||||
companionState = CompanionState.MoveTo;
|
||||
moveToTarget = _target;
|
||||
}
|
||||
|
||||
#region Override Ai Controller rotine
|
||||
protected override void Start()
|
||||
{
|
||||
try
|
||||
{
|
||||
var comp = GameObject.FindGameObjectWithTag(companionTag);
|
||||
if (comp != null)
|
||||
{
|
||||
companion = comp.transform;
|
||||
}
|
||||
else
|
||||
{
|
||||
companionState = CompanionState.None;
|
||||
Debug.LogWarning("Cant find the " + companionTag);
|
||||
}
|
||||
}
|
||||
catch (UnityException e)
|
||||
{
|
||||
companionState = CompanionState.None;
|
||||
Debug.LogWarning("AICompanion Cant find the " + companionTag);
|
||||
Debug.LogWarning("AICompanion " + e.Message);
|
||||
}
|
||||
Init();
|
||||
agent.enabled = true;
|
||||
StartCoroutine(CompanionStateRoutine());
|
||||
StartCoroutine(FindTarget());
|
||||
StartCoroutine(DestinationBehaviour());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// override <see cref="vSimpleMeleeAI_Companion.StateRoutine()"/>
|
||||
/// ps: this rotine work with internal while loop
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected IEnumerator CompanionStateRoutine()
|
||||
{
|
||||
while (this.enabled)
|
||||
{
|
||||
yield return new WaitForEndOfFrame();
|
||||
System.Text.StringBuilder debugString = new System.Text.StringBuilder();
|
||||
debugString.AppendLine("----DEBUG----");
|
||||
debugString.AppendLine("Agressive : " + agressiveAtFirstSight);
|
||||
|
||||
CheckIsOnNavMesh();
|
||||
CheckAutoCrouch();
|
||||
SetTarget();
|
||||
|
||||
//Companion Behavior (override Aicontroller Behavior)
|
||||
switch (companionState)
|
||||
{
|
||||
#region Companion rotine
|
||||
case CompanionState.Follow:
|
||||
if (canSeeTarget && nearOfCompanion)
|
||||
{
|
||||
yield return StartCoroutine(base.Chase());
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return StartCoroutine(FollowCompanion());
|
||||
}
|
||||
|
||||
debugString.AppendLine(canSeeTarget && nearOfCompanion ? "Chase/Follow" : "Follow");
|
||||
|
||||
break;
|
||||
case CompanionState.MoveTo:
|
||||
if (canSeeTarget)
|
||||
{
|
||||
yield return StartCoroutine(base.Chase());
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return StartCoroutine(MoveTo());
|
||||
}
|
||||
|
||||
debugString.AppendLine(canSeeTarget ? "Chase/MoveTo" : "MoveTo");
|
||||
break;
|
||||
case CompanionState.Stay:
|
||||
if (canSeeTarget)
|
||||
{
|
||||
yield return StartCoroutine(base.Chase());
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return StartCoroutine(Stay());
|
||||
}
|
||||
|
||||
debugString.AppendLine(canSeeTarget ? "Chase/Stay" : "Stay");
|
||||
break;
|
||||
#endregion
|
||||
case CompanionState.None:
|
||||
//Aicontroller Behavior
|
||||
#region Ai controller Normal Rotine
|
||||
debugString.AppendLine("None : using normal AI routine");
|
||||
switch (currentState)
|
||||
{
|
||||
case AIStates.Idle:
|
||||
debugString.AppendLine("idle");
|
||||
yield return StartCoroutine(base.Idle());
|
||||
break;
|
||||
case AIStates.Chase:
|
||||
yield return StartCoroutine(base.Chase());
|
||||
break;
|
||||
case AIStates.Wander:
|
||||
yield return StartCoroutine(base.Wander());
|
||||
break;
|
||||
}
|
||||
break;
|
||||
#endregion
|
||||
}
|
||||
if (debugUIText != null && debug)
|
||||
{
|
||||
debugUIText.text = debugString.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// override <see cref="vSimpleMeleeAI_Companion.Idle()"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected IEnumerator Stay()
|
||||
{
|
||||
if (companion != null)
|
||||
{
|
||||
agent.speed = Mathf.Lerp(agent.speed, 0, 2f * Time.deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return StartCoroutine(Idle());
|
||||
}
|
||||
}
|
||||
|
||||
protected override void SetAggressive(bool value)
|
||||
{
|
||||
if (companionState != CompanionState.Follow)
|
||||
{
|
||||
base.SetAggressive(value);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Companion rotine
|
||||
|
||||
/// <summary>
|
||||
/// Follows the companion.
|
||||
/// </summary>
|
||||
/// <returns>The companion.</returns>
|
||||
protected virtual IEnumerator FollowCompanion()
|
||||
{
|
||||
while (!agent.enabled || currentHealth <= 0)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// check if companion exist in Scene to work follow rotine
|
||||
if (companion != null && companion.gameObject.activeSelf)
|
||||
{
|
||||
agent.speed = Mathf.Lerp(agent.speed, followSpeed, 10f * Time.deltaTime);
|
||||
agent.stoppingDistance = followStopDistance;
|
||||
UpdateDestination(companion.position);
|
||||
}
|
||||
else // go to start position case companion dont exist
|
||||
{
|
||||
agent.speed = Mathf.Lerp(agent.speed, moveToSpeed, 10f * Time.deltaTime);
|
||||
agent.stoppingDistance = moveToStopDistance;
|
||||
UpdateDestination(startPosition);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves to target applied from <see cref="SetMoveTo"/>
|
||||
/// </summary>
|
||||
/// <returns>The to.</returns>
|
||||
protected virtual IEnumerator MoveTo()
|
||||
{
|
||||
while (!agent.enabled || currentHealth <= 0)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
agent.speed = Mathf.Lerp(agent.speed, moveToSpeed, 2f * Time.deltaTime);
|
||||
agent.stoppingDistance = moveToStopDistance;
|
||||
// update destination to moveTo target position
|
||||
UpdateDestination(moveToTarget.position);
|
||||
// check if can see some target (included from SetUpTarget method)
|
||||
if (canSeeTarget && nearOfCompanion)
|
||||
{
|
||||
currentState = AIStates.Chase;
|
||||
}
|
||||
}
|
||||
|
||||
protected override float maxSpeed
|
||||
{
|
||||
get
|
||||
{
|
||||
if (companionState != CompanionState.None)
|
||||
{
|
||||
return companionState == CompanionState.Follow ? followSpeed : companionState == CompanionState.MoveTo ? moveToSpeed : 0;
|
||||
}
|
||||
return base.maxSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2df1049f14967d64791ed2a2dbbdccc1
|
||||
timeCreated: 1460650630
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,731 @@
|
||||
using Invector.vEventSystems;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Invector.vCharacterController.AI
|
||||
{
|
||||
[vClassHeader("Simple Melee AI", "This is a Simple Melee AI that comes with the MeleeCombat package as a bonus, if you want a more advanced AI check our AI Template")]
|
||||
public class vSimpleMeleeAI_Controller : vSimpleMeleeAI_Animator, vIMeleeFighter
|
||||
{
|
||||
[vEditorToolbar("Iterations")]
|
||||
public float stateRoutineIteration = 0.15f;
|
||||
public float destinationRoutineIteration = 0.25f;
|
||||
public float findTargetIteration = 0.25f;
|
||||
public float smoothSpeed = 5f;
|
||||
|
||||
[vEditorToolbar("Events")]
|
||||
[Header("--- On Change State Events ---")]
|
||||
public UnityEngine.Events.UnityEvent onIdle;
|
||||
public UnityEngine.Events.UnityEvent onChase;
|
||||
public UnityEngine.Events.UnityEvent onPatrol;
|
||||
protected AIStates oldState;
|
||||
protected float ignorePatrolTimer;
|
||||
protected float _moveToSpeed;
|
||||
protected virtual float moveToSpeed { get { return _moveToSpeed; } set { _moveToSpeed = value; } }
|
||||
protected Vector3 _moveToDestination;
|
||||
protected virtual Vector3 moveToDestination { get { return _moveToDestination; } set { _moveToDestination = value; } }
|
||||
|
||||
protected override void Start()
|
||||
{
|
||||
base.Start();
|
||||
ignorePatrolTimer = -1f;
|
||||
moveToDestination = transform.position;
|
||||
Init();
|
||||
StartCoroutine(StateRoutine());
|
||||
StartCoroutine(FindTarget());
|
||||
StartCoroutine(DestinationBehaviour());
|
||||
}
|
||||
|
||||
protected void FixedUpdate()
|
||||
{
|
||||
ControlLocomotion();
|
||||
}
|
||||
|
||||
#region AI Target
|
||||
|
||||
public virtual void SetCurrentTarget(Transform target)
|
||||
{
|
||||
if (target != currentTarget.transform)
|
||||
{
|
||||
currentTarget.transform = target;
|
||||
currentTarget.colliderTarget = target.GetComponent<Collider>();
|
||||
currentTarget.character = target.GetComponent<vIHealthController>();
|
||||
}
|
||||
AddTagsToDetect(target.gameObject.tag);
|
||||
sphereSensor.AddTarget(target);
|
||||
}
|
||||
|
||||
public virtual void RemoveCurrentTarget()
|
||||
{
|
||||
if (currentTarget.transform)
|
||||
{
|
||||
currentTarget.transform = null;
|
||||
currentTarget.colliderTarget = null;
|
||||
currentTarget.character = null;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void AddTagsToDetect(string tag)
|
||||
{
|
||||
if (!tagsToDetect.Contains(tag))
|
||||
{
|
||||
tagsToDetect.Add(tag);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void RemoveTagToDetect(string tag)
|
||||
{
|
||||
if (tagsToDetect.Contains(tag))
|
||||
{
|
||||
tagsToDetect.Remove(tag);
|
||||
}
|
||||
}
|
||||
|
||||
protected void SetTarget()
|
||||
{
|
||||
if (currentHealth > 0 && sphereSensor != null)
|
||||
{
|
||||
if (currentTarget.transform == null || (sortTargetFromDistance))
|
||||
{
|
||||
sphereSensor.CheckTargetsAround(fieldOfView, minDetectDistance, maxDetectDistance, tagsToDetect, layersToDetect, sortTargetFromDistance);
|
||||
var vChar = sphereSensor.GetTargetvCharacter();
|
||||
if (vChar != null && vChar.currentHealth > 0)
|
||||
{
|
||||
currentTarget.transform = vChar.transform;
|
||||
currentTarget.character = vChar;
|
||||
}
|
||||
}
|
||||
|
||||
if (!CheckTargetIsAlive() || TargetDistance > lostTargetDistance)
|
||||
{
|
||||
currentTarget.transform = null;
|
||||
}
|
||||
}
|
||||
else if (currentHealth <= 0f)
|
||||
{
|
||||
destination = transform.position;
|
||||
currentTarget.transform = null;
|
||||
}
|
||||
}
|
||||
|
||||
bool CheckTargetIsAlive()
|
||||
{
|
||||
if (currentTarget.transform == null || currentTarget.character == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentTarget.character.currentHealth > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected IEnumerator FindTarget()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return new WaitForSeconds(findTargetIteration);
|
||||
if (currentHealth > 0)
|
||||
{
|
||||
SetTarget();
|
||||
CheckTarget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AI Locomotion
|
||||
|
||||
void ControlLocomotion()
|
||||
{
|
||||
if (AgentDone() && agent.updatePosition || lockMovement)
|
||||
{
|
||||
agent.speed = 0f;
|
||||
combatMovement = Vector3.zero;
|
||||
}
|
||||
if (agent.isOnOffMeshLink)
|
||||
{
|
||||
float speed = agent.desiredVelocity.magnitude;
|
||||
UpdateAnimator(AgentDone() ? 0f : speed, direction);
|
||||
}
|
||||
else
|
||||
{
|
||||
var desiredVelocity = agent.enabled ? agent.updatePosition ? agent.desiredVelocity : (agent.nextPosition - transform.position) : (destination - transform.position);
|
||||
if (OnStrafeArea)
|
||||
{
|
||||
var destin = transform.InverseTransformDirection(desiredVelocity).normalized;
|
||||
combatMovement = Vector3.Lerp(combatMovement, destin, 2f * Time.deltaTime);
|
||||
UpdateAnimator(AgentDone() ? 0f : combatMovement.z, combatMovement.x);
|
||||
}
|
||||
else
|
||||
{
|
||||
float speed = desiredVelocity.magnitude;
|
||||
combatMovement = Vector3.zero;
|
||||
UpdateAnimator(AgentDone() ? 0f : speed, 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vector3 AgentDirection()
|
||||
{
|
||||
var forward = AgentDone() ? (currentTarget.transform != null && OnStrafeArea && canSeeTarget ?
|
||||
(new Vector3(destination.x, transform.position.y, destination.z) - transform.position) :
|
||||
transform.forward) : agent.desiredVelocity;
|
||||
|
||||
fwd = Vector3.Lerp(fwd, forward, 20 * Time.deltaTime);
|
||||
return fwd;
|
||||
}
|
||||
|
||||
protected virtual IEnumerator DestinationBehaviour()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return new WaitForSeconds(destinationRoutineIteration);
|
||||
CheckGroundDistance();
|
||||
if (agent.updatePosition)
|
||||
{
|
||||
UpdateDestination(destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void UpdateDestination(Vector3 position)
|
||||
{
|
||||
if (agent.isOnNavMesh)
|
||||
{
|
||||
agent.SetDestination(position);
|
||||
}
|
||||
|
||||
#region debug Path
|
||||
if (agent.enabled && agent.hasPath)
|
||||
{
|
||||
if (drawAgentPath)
|
||||
{
|
||||
Debug.DrawLine(transform.position, position, Color.red, 0.5f);
|
||||
var oldPos = transform.position;
|
||||
for (int i = 0; i < agent.path.corners.Length; i++)
|
||||
{
|
||||
var pos = agent.path.corners[i];
|
||||
Debug.DrawLine(oldPos, pos, Color.green, 0.5f);
|
||||
oldPos = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
protected void CheckIsOnNavMesh()
|
||||
{
|
||||
// check if the AI is on a valid Navmesh, if not he dies
|
||||
if (!agent.isOnNavMesh && agent.enabled && !ragdolled)
|
||||
{
|
||||
Debug.LogWarning("Missing NavMesh Bake, character will die - Please Bake your navmesh again!");
|
||||
currentHealth = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AI States
|
||||
|
||||
protected IEnumerator StateRoutine()
|
||||
{
|
||||
while (this.enabled)
|
||||
{
|
||||
CheckIsOnNavMesh();
|
||||
CheckAutoCrouch();
|
||||
yield return new WaitForSeconds(stateRoutineIteration);
|
||||
if (!lockMovement)
|
||||
{
|
||||
switch (currentState)
|
||||
{
|
||||
case AIStates.Idle:
|
||||
if (currentState != oldState) { onIdle.Invoke(); oldState = currentState; }
|
||||
yield return StartCoroutine(Idle());
|
||||
break;
|
||||
case AIStates.Chase:
|
||||
if (currentState != oldState) { onChase.Invoke(); oldState = currentState; }
|
||||
yield return StartCoroutine(Chase());
|
||||
break;
|
||||
case AIStates.PatrolSubPoints:
|
||||
if (currentState != oldState) { onPatrol.Invoke(); oldState = currentState; }
|
||||
yield return StartCoroutine(PatrolSubPoints());
|
||||
break;
|
||||
case AIStates.PatrolWaypoints:
|
||||
if (currentState != oldState) { onPatrol.Invoke(); oldState = currentState; }
|
||||
yield return StartCoroutine(PatrolWaypoints());
|
||||
break;
|
||||
case AIStates.Wander:
|
||||
if (currentState != oldState) { onPatrol.Invoke(); oldState = currentState; }
|
||||
yield return StartCoroutine(Wander());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected IEnumerator Idle()
|
||||
{
|
||||
while (currentHealth <= 0)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
if (canSeeTarget)
|
||||
{
|
||||
currentState = AIStates.Chase;
|
||||
}
|
||||
else
|
||||
{
|
||||
agent.speed = Mathf.Lerp(agent.speed, 0f, smoothSpeed * Time.deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
protected IEnumerator Chase()
|
||||
{
|
||||
while (currentHealth <= 0)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
agent.speed = Mathf.Lerp(agent.speed, chaseSpeed, smoothSpeed * Time.deltaTime);
|
||||
agent.stoppingDistance = chaseStopDistance;
|
||||
|
||||
if (!isBlocking && !tryingBlock)
|
||||
{
|
||||
StartCoroutine(CheckChanceToBlock(chanceToBlockInStrafe, lowerShield));
|
||||
}
|
||||
|
||||
if (currentTarget.transform == null || !agressiveAtFirstSight)
|
||||
{
|
||||
currentState = AIStates.PatrolWaypoints;
|
||||
}
|
||||
|
||||
|
||||
// begin the Attack Routine when close to the Target
|
||||
if (TargetDistance <= distanceToAttack && meleeManager != null && canAttack && !actions)
|
||||
{
|
||||
canAttack = false;
|
||||
|
||||
yield return StartCoroutine(MeleeAttackRotine());
|
||||
}
|
||||
if (attackCount <= 0 && !inResetAttack && !isAttacking)
|
||||
{
|
||||
StartCoroutine(ResetAttackCount());
|
||||
yield return null;
|
||||
}
|
||||
// strafing while close to the Target
|
||||
if (OnStrafeArea && strafeSideways)
|
||||
{
|
||||
//Debug.DrawRay(transform.position, dir * 2, Color.red, 0.2f);
|
||||
if (strafeSwapeFrequency <= 0)
|
||||
{
|
||||
sideMovement = GetRandonSide();
|
||||
strafeSwapeFrequency = UnityEngine.Random.Range(minStrafeSwape, maxStrafeSwape);
|
||||
}
|
||||
else
|
||||
{
|
||||
strafeSwapeFrequency -= Time.deltaTime;
|
||||
}
|
||||
fwdMovement = (TargetDistance < distanceToAttack) ? (strafeBackward ? -1 : 0) : TargetDistance > distanceToAttack ? 1 : 0;
|
||||
var dir = ((transform.right * sideMovement) + (transform.forward * fwdMovement));
|
||||
Ray ray = new Ray(new Vector3(transform.position.x, currentTarget.transform != null ? currentTarget.transform.position.y : transform.position.y, transform.position.z), dir);
|
||||
if (TargetDistance < strafeDistance - 0.5f)
|
||||
{
|
||||
destination = OnStrafeArea ? ray.GetPoint(agent.stoppingDistance + 0.5f) : currentTarget.transform.position;
|
||||
}
|
||||
else if (currentTarget.transform != null)
|
||||
{
|
||||
destination = currentTarget.transform.position;
|
||||
}
|
||||
}
|
||||
// chase Target
|
||||
else
|
||||
{
|
||||
if (!OnStrafeArea && currentTarget.transform != null)
|
||||
{
|
||||
destination = currentTarget.transform.position;
|
||||
}
|
||||
else
|
||||
{
|
||||
fwdMovement = (TargetDistance < distanceToAttack) ? (strafeBackward ? -1 : 0) : TargetDistance > distanceToAttack ? 1 : 0;
|
||||
Ray ray = new Ray(transform.position, transform.forward * fwdMovement);
|
||||
if (TargetDistance < strafeDistance - 0.5f)
|
||||
{
|
||||
destination = (fwdMovement != 0) ? ray.GetPoint(agent.stoppingDistance + ((fwdMovement > 0) ? TargetDistance : 1f)) : transform.position;
|
||||
}
|
||||
else if (currentTarget.transform != null)
|
||||
{
|
||||
destination = currentTarget.transform.position;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected IEnumerator PatrolSubPoints()
|
||||
{
|
||||
while (!agent.enabled)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
if (targetWaypoint)
|
||||
{
|
||||
if (targetPatrolPoint == null || !targetPatrolPoint.isValid)
|
||||
{
|
||||
targetPatrolPoint = GetPatrolPoint(targetWaypoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
agent.speed = Mathf.Lerp(agent.speed, (agent.hasPath && targetPatrolPoint.isValid) ? patrolSpeed : 0, smoothSpeed * Time.deltaTime);
|
||||
agent.stoppingDistance = patrollingStopDistance;
|
||||
destination = targetPatrolPoint.isValid ? targetPatrolPoint.position : transform.position;
|
||||
if (Vector3.Distance(transform.position, destination) < targetPatrolPoint.areaRadius && targetPatrolPoint.CanEnter(transform) && !targetPatrolPoint.IsOnWay(transform))
|
||||
{
|
||||
targetPatrolPoint.Enter(transform);
|
||||
wait = Time.time + targetPatrolPoint.timeToStay;
|
||||
visitedPatrolPoint.Add(targetPatrolPoint);
|
||||
}
|
||||
else if (Vector3.Distance(transform.position, destination) < targetPatrolPoint.areaRadius && (!targetPatrolPoint.CanEnter(transform) || !targetPatrolPoint.isValid))
|
||||
{
|
||||
targetPatrolPoint = GetPatrolPoint(targetWaypoint);
|
||||
}
|
||||
|
||||
if (targetPatrolPoint != null && (targetPatrolPoint.IsOnWay(transform) && Vector3.Distance(transform.position, destination) < distanceToChangeWaypoint))
|
||||
{
|
||||
if (wait < Time.time || !targetPatrolPoint.isValid)
|
||||
{
|
||||
wait = 0;
|
||||
if (visitedPatrolPoint.Count == pathArea.GetValidSubPoints(targetWaypoint).Count)
|
||||
{
|
||||
currentState = AIStates.PatrolWaypoints;
|
||||
targetWaypoint.Exit(transform);
|
||||
targetPatrolPoint.Exit(transform);
|
||||
targetWaypoint = null;
|
||||
targetPatrolPoint = null;
|
||||
visitedPatrolPoint.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
targetPatrolPoint.Exit(transform);
|
||||
targetPatrolPoint = GetPatrolPoint(targetWaypoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (canSeeTarget)
|
||||
{
|
||||
currentState = AIStates.Chase;
|
||||
}
|
||||
}
|
||||
|
||||
protected IEnumerator PatrolWaypoints()
|
||||
{
|
||||
while (!agent.enabled)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
if (pathArea != null && pathArea.waypoints.Count > 0)
|
||||
{
|
||||
if (targetWaypoint == null || !targetWaypoint.isValid)
|
||||
{
|
||||
targetWaypoint = GetWaypoint();
|
||||
}
|
||||
else
|
||||
{
|
||||
agent.speed = Mathf.Lerp(agent.speed, (agent.hasPath && targetWaypoint.isValid) ? patrolSpeed : 0, smoothSpeed * Time.deltaTime);
|
||||
|
||||
agent.stoppingDistance = patrollingStopDistance;
|
||||
|
||||
destination = targetWaypoint.position;
|
||||
if (Vector3.Distance(transform.position, destination) < targetWaypoint.areaRadius && targetWaypoint.CanEnter(transform) && !targetWaypoint.IsOnWay(transform))
|
||||
{
|
||||
targetWaypoint.Enter(transform);
|
||||
wait = Time.time + targetWaypoint.timeToStay;
|
||||
}
|
||||
else if (Vector3.Distance(transform.position, destination) < targetWaypoint.areaRadius && (!targetWaypoint.CanEnter(transform) || !targetWaypoint.isValid))
|
||||
{
|
||||
targetWaypoint = GetWaypoint();
|
||||
}
|
||||
|
||||
if (targetWaypoint != null && targetWaypoint.IsOnWay(transform) && Vector3.Distance(transform.position, destination) < distanceToChangeWaypoint)
|
||||
{
|
||||
if (wait < Time.time || !targetWaypoint.isValid)
|
||||
{
|
||||
wait = 0;
|
||||
if (targetWaypoint.subPoints.Count > 0)
|
||||
{
|
||||
currentState = AIStates.PatrolSubPoints;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetWaypoint.Exit(transform);
|
||||
visitedPatrolPoint.Clear();
|
||||
targetWaypoint = GetWaypoint();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ignorePatrolTimer < Time.time)
|
||||
{
|
||||
switch (patrolWithoutAreaStyle)
|
||||
{
|
||||
case AIPatrolWithOutAreaStyle.GoToStartPoint:
|
||||
yield return StartCoroutine(GoToStartingPoint());
|
||||
break;
|
||||
case AIPatrolWithOutAreaStyle.Idle:
|
||||
currentState = AIStates.Idle;
|
||||
break;
|
||||
case AIPatrolWithOutAreaStyle.Wander:
|
||||
currentState = AIStates.Wander;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
else if (ignorePatrolTimer > Time.time)
|
||||
{
|
||||
yield return StartCoroutine(GoToDestionation());
|
||||
}
|
||||
|
||||
if (canSeeTarget)
|
||||
{
|
||||
currentState = AIStates.Chase;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual IEnumerator GoToDestionation()
|
||||
{
|
||||
yield return null;
|
||||
agent.speed = Mathf.Lerp(agent.speed, moveToSpeed, smoothSpeed * Time.deltaTime);
|
||||
agent.stoppingDistance = patrollingStopDistance;
|
||||
destination = moveToDestination;
|
||||
}
|
||||
|
||||
protected virtual IEnumerator GoToStartingPoint()
|
||||
{
|
||||
yield return null;
|
||||
agent.speed = Mathf.Lerp(agent.speed, patrolSpeed, smoothSpeed * Time.deltaTime);
|
||||
agent.stoppingDistance = patrollingStopDistance;
|
||||
destination = startPosition;
|
||||
|
||||
}
|
||||
|
||||
protected virtual IEnumerator Wander()
|
||||
{
|
||||
|
||||
agent.speed = Mathf.Lerp(agent.speed, wanderSpeed, smoothSpeed * Time.deltaTime);
|
||||
do
|
||||
{
|
||||
yield return null;
|
||||
destination = transform.position + (Quaternion.AngleAxis(UnityEngine.Random.Range(-120, 120), transform.up) * transform.forward) * (patrollingStopDistance + 4);
|
||||
} while (agent.enabled && agent.isOnNavMesh && agent.remainingDistance <= patrollingStopDistance);
|
||||
if (canSeeTarget)
|
||||
{
|
||||
currentState = AIStates.Chase;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AI Waypoint & PatrolPoint
|
||||
|
||||
vWaypoint GetWaypoint()
|
||||
{
|
||||
var waypoints = pathArea.GetValidPoints();
|
||||
|
||||
if (randomWaypoints)
|
||||
{
|
||||
currentWaypoint = randomWaypoint.Next(waypoints.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentWaypoint++;
|
||||
}
|
||||
|
||||
if (currentWaypoint >= waypoints.Count)
|
||||
{
|
||||
currentWaypoint = 0;
|
||||
}
|
||||
|
||||
if (waypoints.Count == 0)
|
||||
{
|
||||
agent.isStopped = true;
|
||||
return null;
|
||||
}
|
||||
if (visitedWaypoint.Count == waypoints.Count)
|
||||
{
|
||||
visitedWaypoint.Clear();
|
||||
}
|
||||
|
||||
if (visitedWaypoint.Contains(waypoints[currentWaypoint]))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
agent.isStopped = false;
|
||||
return waypoints[currentWaypoint];
|
||||
}
|
||||
|
||||
vPoint GetPatrolPoint(vWaypoint waypoint)
|
||||
{
|
||||
var subPoints = pathArea.GetValidSubPoints(waypoint);
|
||||
if (waypoint.randomPatrolPoint)
|
||||
{
|
||||
currentPatrolPoint = randomPatrolPoint.Next(subPoints.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentPatrolPoint++;
|
||||
}
|
||||
|
||||
if (currentPatrolPoint >= subPoints.Count)
|
||||
{
|
||||
currentPatrolPoint = 0;
|
||||
}
|
||||
|
||||
if (subPoints.Count == 0)
|
||||
{
|
||||
agent.isStopped = true;
|
||||
return null;
|
||||
}
|
||||
if (visitedPatrolPoint.Contains(subPoints[currentPatrolPoint]))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
agent.isStopped = false;
|
||||
return subPoints[currentPatrolPoint];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AI Melee Combat
|
||||
|
||||
protected IEnumerator MeleeAttackRotine()
|
||||
{
|
||||
if (!isAttacking && !actions && attackCount > 0 && !lockMovement && !isRolling)
|
||||
{
|
||||
sideMovement = GetRandonSide();
|
||||
agent.stoppingDistance = distanceToAttack;
|
||||
attackCount--;
|
||||
MeleeAttack();
|
||||
yield return null;
|
||||
}
|
||||
//else if (!actions && attackCount > 0) canAttack = true;
|
||||
}
|
||||
|
||||
public void FinishAttack()
|
||||
{
|
||||
// if(attackCount > 0)
|
||||
canAttack = true;
|
||||
}
|
||||
|
||||
IEnumerator ResetAttackCount()
|
||||
{
|
||||
inResetAttack = true;
|
||||
canAttack = false;
|
||||
var value = 0f;
|
||||
if (firstAttack)
|
||||
{
|
||||
firstAttack = false;
|
||||
value = firstAttackDelay;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = UnityEngine.Random.Range(minTimeToAttack, maxTimeToAttack);
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(value);
|
||||
attackCount = randomAttackCount ? UnityEngine.Random.Range(1, maxAttackCount + 1) : maxAttackCount;
|
||||
canAttack = true;
|
||||
inResetAttack = false;
|
||||
}
|
||||
|
||||
public void OnEnableAttack()
|
||||
{
|
||||
isAttacking = true;
|
||||
}
|
||||
|
||||
public void OnDisableAttack()
|
||||
{
|
||||
isAttacking = false;
|
||||
canAttack = true;
|
||||
}
|
||||
|
||||
public void ResetAttackTriggers()
|
||||
{
|
||||
animator.ResetTrigger("WeakAttack");
|
||||
}
|
||||
|
||||
public void BreakAttack(int breakAtkID)
|
||||
{
|
||||
ResetAttackCount();
|
||||
ResetAttackTriggers();
|
||||
OnRecoil(breakAtkID);
|
||||
}
|
||||
|
||||
public void OnRecoil(int recoilID)
|
||||
{
|
||||
TriggerRecoil(recoilID);
|
||||
}
|
||||
|
||||
public void OnReceiveAttack(vDamage damage, vIMeleeFighter attacker)
|
||||
{
|
||||
StartCoroutine(CheckChanceToBlock(chanceToBlockAttack, 0));
|
||||
|
||||
var attackPos = (attacker != null && attacker.character != null) ? attacker.character.transform.position : damage.hitPosition;
|
||||
if (!damage.ignoreDefense && isBlocking && meleeManager != null && meleeManager.CanBlockAttack(attackPos))
|
||||
{
|
||||
var damageReduction = meleeManager != null ? meleeManager.GetDefenseRate() : 0;
|
||||
if (damageReduction > 0)
|
||||
{
|
||||
damage.ReduceDamage(damageReduction);
|
||||
}
|
||||
|
||||
if (attacker != null && meleeManager != null && meleeManager.CanBreakAttack())
|
||||
{
|
||||
attacker.OnRecoil(meleeManager.GetDefenseRecoilID());
|
||||
}
|
||||
|
||||
meleeManager.OnDefense();
|
||||
}
|
||||
// apply tag from the character that hit you and start chase
|
||||
if (!passiveToDamage && damage.sender != null)
|
||||
{
|
||||
SetCurrentTarget(damage.sender);
|
||||
currentState = AIStates.Chase;
|
||||
|
||||
}
|
||||
damage.hitReaction = !isBlocking;
|
||||
if (!passiveToDamage)
|
||||
{
|
||||
SetAggressive(true);
|
||||
}
|
||||
|
||||
TakeDamage(damage);
|
||||
}
|
||||
|
||||
public virtual void MoveTo(Vector3 position, float moveToSpeed = 1f, float ignorePatrolTimer = 2f)
|
||||
{
|
||||
moveToDestination = position;
|
||||
currentState = AIStates.PatrolWaypoints;
|
||||
this.moveToSpeed = moveToSpeed;
|
||||
this.ignorePatrolTimer = Time.time + ignorePatrolTimer;
|
||||
|
||||
}
|
||||
|
||||
public vICharacter character
|
||||
{
|
||||
get { return this; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7fce10a830503234d997fb0f9d1d94b5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,730 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Invector.vCharacterController.AI
|
||||
{
|
||||
using UnityEngine.AI;
|
||||
using vMelee;
|
||||
|
||||
public class vSimpleMeleeAI_Motor : vCharacter
|
||||
{
|
||||
#region Variables
|
||||
|
||||
#region Layers
|
||||
[vEditorToolbar("Layers")]
|
||||
|
||||
[Tooltip("Layers that the character can walk on")]
|
||||
public LayerMask groundLayer = 1 << 0;
|
||||
[Tooltip("Distance to became not grounded")]
|
||||
[SerializeField]
|
||||
protected float groundCheckDistance = 0.5f;
|
||||
[Tooltip("What objects can make the character auto crouch")]
|
||||
public LayerMask autoCrouchLayer = 1 << 0;
|
||||
[Tooltip("[SPHERECAST] ADJUST IN PLAY MODE - White Spherecast put just above the head, this will make the character Auto-Crouch if something hit the sphere.")]
|
||||
public float headDetect = 0.95f;
|
||||
#endregion
|
||||
|
||||
#region AI variables
|
||||
[vEditorToolbar("Locomotion")]
|
||||
[Tooltip("Use to limit your locomotion animation, if you want to patrol walking set this value to 0.5f")]
|
||||
[Range(0f, 1.5f)]
|
||||
public float patrolSpeed = 0.5f;
|
||||
[Tooltip("Use to limit your locomotion animation, if you want to wander walking set this value to 0.5f")]
|
||||
[Range(0f, 1.5f)]
|
||||
public float wanderSpeed = 0.5f;
|
||||
[Tooltip("Use to limit your locomotion animation, if you want to chase the target walking set this value to 0.5f")]
|
||||
[Range(0f, 1.5f)]
|
||||
public float chaseSpeed = 1f;
|
||||
[Tooltip("Use to limit your locomotion animation, if you want to strafe the target walking set this value to 0.5f")]
|
||||
[Range(0f, 1.5f)]
|
||||
public float strafeSpeed = 1f;
|
||||
|
||||
[Header("--- Strafe ---")]
|
||||
[Tooltip("Strafe around the target")]
|
||||
public bool strafeSideways = true;
|
||||
[Tooltip("Strafe a few steps backwards")]
|
||||
public bool strafeBackward = true;
|
||||
[Tooltip("Distance to switch to the strafe locomotion, leave with 0 if you don't want your character to strafe")]
|
||||
public float strafeDistance = 3f;
|
||||
[Tooltip("Min time to change the strafe direction")]
|
||||
public float minStrafeSwape = 2f;
|
||||
[Tooltip("Max time to change the strafe direction")]
|
||||
public float maxStrafeSwape = 5f;
|
||||
[Tooltip("Velocity to rotate the character while strafing")]
|
||||
public float strafeRotationSpeed = 5f;
|
||||
|
||||
[vEditorToolbar("Detection")]
|
||||
protected AIStates _currentState = AIStates.PatrolWaypoints;
|
||||
public virtual AIStates currentState { get { return _currentState; } set { _currentState = value; } }
|
||||
[Header("Who is your Target?")]
|
||||
public vSimpleMeleeAI_SphereSensor sphereSensor;
|
||||
public vTagMask tagsToDetect = new vTagMask { "Player" };
|
||||
public LayerMask layersToDetect = 1 << 0;
|
||||
public LayerMask obstaclesLayer;
|
||||
public bool sortTargetFromDistance = false;
|
||||
[Range(0f, 360f)]
|
||||
public float fieldOfView = 95f;
|
||||
[Tooltip("Max Distance to detect the Target with FOV")]
|
||||
public float maxDetectDistance = 5f;
|
||||
[Tooltip("Min Distance to noticed the Target without FOV")]
|
||||
public float minDetectDistance = 2f;
|
||||
[Tooltip("Distance to lost the Target")]
|
||||
public float distanceToLostTarget = 5f;
|
||||
public float lostTargetDistance { get { return maxDetectDistance + distanceToLostTarget; } }
|
||||
[Tooltip("Distance to stop when chasing the Player")]
|
||||
public float chaseStopDistance = 1f;
|
||||
public bool drawAgentPath = false;
|
||||
public bool displayGizmos;
|
||||
|
||||
[vEditorToolbar("Combat")]
|
||||
|
||||
[Tooltip("Check if you want the Enemy to be passive even if you attack him")]
|
||||
public bool passiveToDamage = false;
|
||||
[Tooltip("Check if you want the Enemy to chase the Target at first sight")]
|
||||
public bool agressiveAtFirstSight = true;
|
||||
[Tooltip("Velocity to rotate the character while attacking")]
|
||||
public float attackRotationSpeed = 0.5f;
|
||||
[Tooltip("Delay to trigger the first attack when close to the target")]
|
||||
public float firstAttackDelay = 0f;
|
||||
[Tooltip("Min frequency to attack")]
|
||||
public float minTimeToAttack = 4f;
|
||||
[Tooltip("Max frequency to attack")]
|
||||
public float maxTimeToAttack = 6f;
|
||||
[Tooltip("How many attacks the AI will make on a combo")]
|
||||
public int maxAttackCount = 3;
|
||||
[Tooltip("Randomly attacks based on the maxAttackCount")]
|
||||
public bool randomAttackCount = true;
|
||||
[Range(0f, 1f)]
|
||||
public float chanceToRoll = .1f;
|
||||
[Range(0f, 1f)]
|
||||
public float chanceToBlockInStrafe = .1f;
|
||||
[Range(0f, 1f)]
|
||||
public float chanceToBlockAttack = 0f;
|
||||
[Tooltip("How much time the character will stand up the shield")]
|
||||
public float raiseShield = 4f;
|
||||
[Tooltip("How much time the character will lower the shield")]
|
||||
public float lowerShield = 2f;
|
||||
|
||||
[vEditorToolbar("Waypoint")]
|
||||
|
||||
[Tooltip("Max Distance to change waypoint")]
|
||||
[Range(0.5f, 100f)]
|
||||
public float distanceToChangeWaypoint = 1f;
|
||||
[Tooltip("Min Distance to stop when Patrolling through waypoints")]
|
||||
[Range(0.5f, 100f)]
|
||||
public float patrollingStopDistance = 0.5f;
|
||||
public AIPatrolWithOutAreaStyle patrolWithoutAreaStyle = AIPatrolWithOutAreaStyle.GoToStartPoint;
|
||||
public vWaypointArea pathArea;
|
||||
public bool randomWaypoints;
|
||||
|
||||
public vFisherYatesRandom randomWaypoint = new vFisherYatesRandom();
|
||||
public vFisherYatesRandom randomPatrolPoint = new vFisherYatesRandom();
|
||||
[HideInInspector]
|
||||
public CapsuleCollider _capsuleCollider;
|
||||
// there is a prefab of health hud example that you can drag and drop into the head bone of your character
|
||||
[HideInInspector]
|
||||
public v_SpriteHealth healthSlider;
|
||||
// attach a meleeManager component to create new hitboxs and set up different weapons
|
||||
[HideInInspector]
|
||||
public vMeleeManager meleeManager;
|
||||
// check your MeleeWeapon Inspector, each weapon can set up different distances to attack
|
||||
public OnSetAgressiveEvent onSetAgressive = new OnSetAgressiveEvent();
|
||||
public class OnSetAgressiveEvent : UnityEngine.Events.UnityEvent<bool> { }
|
||||
[HideInInspector]
|
||||
public bool lockMovement;
|
||||
|
||||
public enum AIPatrolWithOutAreaStyle
|
||||
{
|
||||
GoToStartPoint,
|
||||
Wander,
|
||||
Idle
|
||||
}
|
||||
|
||||
public enum AIStates
|
||||
{
|
||||
Idle,
|
||||
PatrolSubPoints,
|
||||
PatrolWaypoints,
|
||||
Wander,
|
||||
Chase
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Protected Variables
|
||||
public CharacterTarget currentTarget;// { protected set; get; }
|
||||
[System.Serializable]
|
||||
public struct CharacterTarget
|
||||
{
|
||||
public Transform transform;
|
||||
public vIHealthController character;
|
||||
public Collider colliderTarget;
|
||||
}
|
||||
protected Vector3 _targetPos;
|
||||
protected virtual Vector3 targetPos { get { return _targetPos; } set { _targetPos = value; } }
|
||||
[SerializeField, vReadOnly]
|
||||
protected bool _canSeeTarget;
|
||||
protected virtual bool canSeeTarget { get { return _canSeeTarget; } set { _canSeeTarget = value; } }
|
||||
protected Vector3 _destination;
|
||||
protected virtual Vector3 destination { get { return _destination; } set { _destination = value; } }
|
||||
protected Vector3 _fwd;
|
||||
protected virtual Vector3 fwd { get { return _fwd; } set { _fwd = value; } }
|
||||
|
||||
protected virtual bool _isGrounded { get; set; }
|
||||
protected virtual bool isGrounded { get { return _isGrounded; } set { _isGrounded = value; } }
|
||||
protected bool _isStrafing;
|
||||
protected virtual bool isStrafing { get { return _isStrafing; } set { _isStrafing = value; } }
|
||||
protected virtual bool inResetAttack { get; set; }
|
||||
protected virtual bool firstAttack { get; set; }
|
||||
protected virtual int attackCount { get; set; }
|
||||
protected virtual int _currentWaypoint { get; set; }
|
||||
protected virtual int currentWaypoint { get { return _currentWaypoint; } set { _currentWaypoint = value; } }
|
||||
protected virtual int _currentPatrolPoint { get; set; }
|
||||
protected virtual int currentPatrolPoint { get { return _currentPatrolPoint; } set { _currentPatrolPoint = value; } }
|
||||
protected float _direction;
|
||||
protected virtual float direction { get { return _direction; } set { _direction = value; } }
|
||||
protected float _timer;
|
||||
protected virtual float timer { get { return _timer; } set { _timer = value; } }
|
||||
protected float _wait;
|
||||
protected virtual float wait { get { return _wait; } set { _wait = value; } }
|
||||
|
||||
protected float _fovAngle;
|
||||
protected virtual float fovAngle { get { return _fovAngle; } set { _fovAngle = value; } }
|
||||
protected virtual float sideMovement { get; set; }
|
||||
protected virtual float fwdMovement { get; set; }
|
||||
protected virtual float strafeSwapeFrequency { get; set; }
|
||||
protected virtual float groundDistance { get; set; }
|
||||
protected Vector3 _startPosition;
|
||||
protected virtual Vector3 startPosition { get { return _startPosition; } set { _startPosition = value; } }
|
||||
protected RaycastHit groundHit;
|
||||
protected NavMeshAgent agent;
|
||||
protected NavMeshPath agentPath;
|
||||
protected Quaternion freeRotation;
|
||||
protected Quaternion desiredRotation;
|
||||
protected Vector3 oldPosition;
|
||||
protected Vector3 combatMovement;
|
||||
protected Vector3 rollDirection;
|
||||
protected Rigidbody _rigidbody;
|
||||
protected PhysicsMaterial frictionPhysics;
|
||||
protected Transform head;
|
||||
protected Collider colliderTarget;
|
||||
protected vWaypoint targetWaypoint;
|
||||
protected vPoint targetPatrolPoint;
|
||||
protected List<vPoint> visitedPatrolPoint = new List<vPoint>();
|
||||
protected List<vWaypoint> visitedWaypoint = new List<vWaypoint>();
|
||||
#endregion
|
||||
|
||||
#region Actions
|
||||
protected bool _isCrouched;
|
||||
protected bool _canAttack;
|
||||
protected bool _tryingBlock;
|
||||
protected bool _isRolling;
|
||||
protected virtual bool isCrouched { get { return _isCrouched; } set { _isCrouched = value; } }
|
||||
protected virtual bool canAttack { get { return _canAttack; } set { _canAttack = value; } }
|
||||
protected virtual bool tryingBlock { get { return _tryingBlock; } set { _tryingBlock = value; } }
|
||||
protected virtual bool isRolling { get { return _isRolling; } set { _isRolling = value; } }
|
||||
protected bool _isBlocking;
|
||||
public virtual bool isBlocking { get { return _isBlocking; } protected set { _isBlocking = value; } }
|
||||
protected bool _isAttacking;
|
||||
public bool isAttacking { get { return _isAttacking; } protected set { _isAttacking = value; } }
|
||||
|
||||
public bool isArmed { get { return meleeManager != null && (meleeManager.rightWeapon != null || (meleeManager.leftWeapon != null && meleeManager.leftWeapon.meleeType != vMeleeType.OnlyDefense)); } }
|
||||
|
||||
protected bool _actions;
|
||||
public virtual bool actions { get { return _actions; } set { _actions = value; } }
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
|
||||
fwd = transform.forward;
|
||||
destination = transform.position;
|
||||
agent = GetComponent<UnityEngine.AI.NavMeshAgent>();
|
||||
|
||||
agentPath = new UnityEngine.AI.NavMeshPath();
|
||||
sphereSensor = GetComponentInChildren<vSimpleMeleeAI_SphereSensor>();
|
||||
if (sphereSensor)
|
||||
{
|
||||
sphereSensor.root = transform;
|
||||
}
|
||||
meleeManager = GetComponent<vMeleeManager>();
|
||||
canAttack = true;
|
||||
attackCount = 0;
|
||||
sideMovement = GetRandonSide();
|
||||
destination = transform.position;
|
||||
|
||||
_rigidbody = GetComponent<Rigidbody>();
|
||||
_rigidbody.useGravity = true;
|
||||
_rigidbody.constraints = RigidbodyConstraints.None | RigidbodyConstraints.FreezeRotation;
|
||||
agent.updatePosition = false;
|
||||
agent.updateRotation = false;
|
||||
agent.enabled = false;
|
||||
_capsuleCollider = GetComponent<CapsuleCollider>();
|
||||
|
||||
// avoid collision detection with inside colliders
|
||||
Collider[] AllColliders = this.GetComponentsInChildren<Collider>();
|
||||
Collider thisCollider = GetComponent<Collider>();
|
||||
for (int i = 0; i < AllColliders.Length; i++)
|
||||
{
|
||||
Physics.IgnoreCollision(thisCollider, AllColliders[i]);
|
||||
}
|
||||
|
||||
healthSlider = GetComponentInChildren<v_SpriteHealth>();
|
||||
head = animator.GetBoneTransform(HumanBodyBones.Head);
|
||||
oldPosition = transform.position;
|
||||
if (!fillHealthOnStart) currentHealth = _currentHealth;
|
||||
startPosition = transform.position;
|
||||
}
|
||||
|
||||
#region AI Locomotion
|
||||
public float distanceToAttack
|
||||
{
|
||||
get { if (meleeManager) { return meleeManager.GetAttackDistance(); } return 1f; }
|
||||
}
|
||||
|
||||
public bool OnCombatArea
|
||||
{
|
||||
get
|
||||
{
|
||||
if (currentTarget.transform == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var inFloor = Vector3.Distance(new Vector3(0, transform.position.y, 0), new Vector3(0, currentTarget.transform.position.y, 0)) < distanceToAttack;
|
||||
return (inFloor && agressiveAtFirstSight && TargetDistance <= strafeDistance && !agent.isOnOffMeshLink);
|
||||
}
|
||||
}
|
||||
|
||||
public bool OnStrafeArea
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!canSeeTarget)
|
||||
{
|
||||
isStrafing = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentTarget.transform == null || !agressiveAtFirstSight)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var inFloor = Vector3.Distance(new Vector3(0, transform.position.y, 0), new Vector3(0, currentTarget.transform.position.y, 0)) < 1.5f;
|
||||
// exit strafe
|
||||
if (isStrafing)
|
||||
{
|
||||
isStrafing = TargetDistance < (strafeDistance + 2f);
|
||||
}
|
||||
// enter strafe
|
||||
else
|
||||
{
|
||||
isStrafing = OnCombatArea;
|
||||
}
|
||||
|
||||
return inFloor ? isStrafing : false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool AgentDone()
|
||||
{
|
||||
if (!agent.enabled && agent.updatePosition)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return !agent.pathPending && AgentStopping() && agent.updatePosition;
|
||||
}
|
||||
|
||||
public bool AgentStopping()
|
||||
{
|
||||
if (!agent.enabled || !agent.isOnNavMesh)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return agent.remainingDistance <= agent.stoppingDistance;
|
||||
}
|
||||
|
||||
public int GetRandonSide()
|
||||
{
|
||||
var side = UnityEngine.Random.Range(-1, 1);
|
||||
if (side < 0)
|
||||
{
|
||||
side = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
side = 1;
|
||||
}
|
||||
|
||||
return side;
|
||||
}
|
||||
|
||||
protected void CheckGroundDistance()
|
||||
{
|
||||
if (_capsuleCollider != null)
|
||||
{
|
||||
var dist = 10f;
|
||||
Ray ray1 = new Ray(transform.position + new Vector3(0, _capsuleCollider.height / 2, 0), Vector3.down);
|
||||
|
||||
if (Physics.Raycast(ray1, out groundHit, _capsuleCollider.height * 0.75f, groundLayer))
|
||||
{
|
||||
dist = transform.position.y - groundHit.point.y;
|
||||
}
|
||||
|
||||
groundDistance = dist;
|
||||
|
||||
if (!actions && !isRolling && groundDistance < 0.3f)
|
||||
{
|
||||
if (currentHealth > 0)
|
||||
{
|
||||
UnityEngine.AI.NavMeshHit navHit;
|
||||
if (UnityEngine.AI.NavMesh.SamplePosition(transform.position, out navHit, 5f, UnityEngine.AI.NavMesh.AllAreas))
|
||||
{
|
||||
_rigidbody.constraints = RigidbodyConstraints.None | RigidbodyConstraints.FreezeRotation | RigidbodyConstraints.FreezePositionX | RigidbodyConstraints.FreezePositionZ;
|
||||
_rigidbody.useGravity = false;
|
||||
agent.updatePosition = false;
|
||||
agent.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (agent.enabled && agent.isOnNavMesh && Vector3.Distance(agent.nextPosition, transform.position) <= 0.1f)
|
||||
{
|
||||
agent.updatePosition = true;
|
||||
}
|
||||
else if (agent.enabled && agent.isOnNavMesh && !agent.updatePosition)
|
||||
{
|
||||
agent.nextPosition = transform.position;
|
||||
}
|
||||
}
|
||||
|
||||
if (!agent.isOnNavMesh && groundDistance > 0.3f && !ragdolled)
|
||||
{
|
||||
_rigidbody.useGravity = true;
|
||||
_rigidbody.constraints = RigidbodyConstraints.None | RigidbodyConstraints.FreezeRotation;
|
||||
agent.enabled = false;
|
||||
agent.updatePosition = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckAutoCrouch()
|
||||
{
|
||||
// radius of SphereCast
|
||||
float radius = _capsuleCollider.radius * 0.9f;
|
||||
// Position of SphereCast origin stating in base of capsule
|
||||
Vector3 pos = transform.position + Vector3.up * ((_capsuleCollider.height * 0.5f) - _capsuleCollider.radius);
|
||||
// ray for SphereCast
|
||||
Ray ray2 = new Ray(pos, Vector3.up);
|
||||
RaycastHit groundHit;
|
||||
// sphere cast around the base of capsule for check ground distance
|
||||
//if (Physics.SphereCast(ray2, radius, out groundHit, _capsuleCollider.bounds.max.y - (_capsuleCollider.radius * 0.1f), groundLayer))
|
||||
if (Physics.SphereCast(ray2, radius, out groundHit, headDetect - (_capsuleCollider.radius * 0.1f), autoCrouchLayer))
|
||||
{
|
||||
isCrouched = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
isCrouched = false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Check Target
|
||||
|
||||
/// <summary>
|
||||
/// Calculate Fov Angle
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool onFovAngle()
|
||||
{
|
||||
if (currentTarget.transform == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var freeRotation = Quaternion.LookRotation(currentTarget.transform.position - transform.position, Vector3.up);
|
||||
var newAngle = freeRotation.eulerAngles - transform.eulerAngles;
|
||||
fovAngle = newAngle.NormalizeAngle().y;
|
||||
|
||||
if (fovAngle < fieldOfView && fovAngle > -fieldOfView)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int canSeeTargetIteration;
|
||||
/// <summary>
|
||||
/// Target Detection
|
||||
/// </summary>
|
||||
/// <param name="_target"></param>
|
||||
/// <returns></returns>
|
||||
public void CheckTarget()
|
||||
{
|
||||
if (currentTarget.transform == null || !agressiveAtFirstSight)
|
||||
{
|
||||
canSeeTarget = false;
|
||||
canSeeTargetIteration = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (TargetDistance > maxDetectDistance)
|
||||
{
|
||||
canSeeTarget = false;
|
||||
canSeeTargetIteration = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentTarget.colliderTarget == null || currentTarget.colliderTarget.transform != currentTarget.transform)
|
||||
{
|
||||
currentTarget.colliderTarget = currentTarget.transform.GetComponent<Collider>();
|
||||
}
|
||||
|
||||
if (currentTarget.colliderTarget == null)
|
||||
{
|
||||
canSeeTarget = false;
|
||||
canSeeTargetIteration = 0;
|
||||
return;
|
||||
}
|
||||
var top = new Vector3(currentTarget.colliderTarget.bounds.center.x, currentTarget.colliderTarget.bounds.max.y, currentTarget.colliderTarget.bounds.center.z);
|
||||
var bottom = new Vector3(currentTarget.colliderTarget.bounds.center.x, currentTarget.colliderTarget.bounds.min.y, currentTarget.colliderTarget.bounds.center.z);
|
||||
var offset = Vector3.Distance(top, bottom) * 0.15f;
|
||||
top.y -= offset;
|
||||
bottom.y += offset;
|
||||
|
||||
if (!onFovAngle() && TargetDistance > minDetectDistance)
|
||||
{
|
||||
canSeeTarget = false;
|
||||
canSeeTargetIteration = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
RaycastHit hit;
|
||||
if (canSeeTargetIteration == 0 && !Physics.Linecast(head.position, top, out hit, obstaclesLayer))
|
||||
{
|
||||
|
||||
canSeeTarget = true;
|
||||
canSeeTargetIteration = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
else if (canSeeTargetIteration == 1 && !Physics.Linecast(head.position, bottom, out hit, obstaclesLayer))
|
||||
{
|
||||
canSeeTarget = true;
|
||||
canSeeTargetIteration = 0;
|
||||
return;
|
||||
}
|
||||
else if (canSeeTargetIteration == 2 && !Physics.Linecast(head.position, currentTarget.colliderTarget.bounds.center, out hit, obstaclesLayer))
|
||||
{
|
||||
canSeeTarget = true;
|
||||
canSeeTargetIteration = 0;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
canSeeTargetIteration++;
|
||||
}
|
||||
|
||||
if (canSeeTargetIteration > 1)
|
||||
{
|
||||
canSeeTargetIteration = 0;
|
||||
}
|
||||
|
||||
canSeeTarget = false;
|
||||
}
|
||||
|
||||
public float TargetDistance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (currentTarget.transform != null)
|
||||
{
|
||||
return currentTarget.colliderTarget ? Vector3.Distance(_capsuleCollider.bounds.center, currentTarget.colliderTarget.bounds.center) :
|
||||
Vector3.Distance(transform.position, currentTarget.transform.position);
|
||||
}
|
||||
|
||||
return maxDetectDistance + 1f;
|
||||
}
|
||||
}
|
||||
|
||||
public Transform headTarget
|
||||
{
|
||||
get
|
||||
{
|
||||
if (currentTarget.transform != null && currentTarget.transform.GetComponent<Animator>() != null)
|
||||
{
|
||||
return currentTarget.transform.GetComponent<Animator>().GetBoneTransform(HumanBodyBones.Head);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AI Health
|
||||
|
||||
protected void RemoveComponents()
|
||||
{
|
||||
if (!removeComponentsAfterDie)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_capsuleCollider != null)
|
||||
{
|
||||
Destroy(_capsuleCollider);
|
||||
}
|
||||
|
||||
if (_rigidbody != null)
|
||||
{
|
||||
Destroy(_rigidbody);
|
||||
}
|
||||
|
||||
if (animator != null)
|
||||
{
|
||||
Destroy(animator);
|
||||
}
|
||||
|
||||
if (agent != null)
|
||||
{
|
||||
Destroy(agent);
|
||||
}
|
||||
|
||||
var comps = GetComponents<MonoBehaviour>();
|
||||
for (int i = 0; i < comps.Length; i++)
|
||||
{
|
||||
Destroy(comps[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TAKE DAMAGE - you can override the take damage method from the vCharacter and add your own calls
|
||||
/// </summary>
|
||||
/// <param name="damage"> damage to apply </param>
|
||||
public override void TakeDamage(vDamage damage)
|
||||
{
|
||||
// ignore damage if the character is rolling, dead or the animator is disable
|
||||
if (isRolling || currentHealth <= 0 || !animator.enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!damage.ignoreDefense && !actions && CheckChanceToRoll())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
base.TakeDamage(damage);
|
||||
}
|
||||
|
||||
protected override void TriggerDamageReaction(vDamage damage)
|
||||
{
|
||||
if (!isRolling)
|
||||
{
|
||||
base.TriggerDamageReaction(damage);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region AI Melee Combat Methods
|
||||
|
||||
protected bool CheckChanceToRoll()
|
||||
{
|
||||
if (isAttacking || actions)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
float randomRoll = UnityEngine.Random.value;
|
||||
if (randomRoll < chanceToRoll && randomRoll > 0 && currentTarget.transform != null)
|
||||
{
|
||||
animator.SetTrigger("ResetState");
|
||||
sideMovement = GetRandonSide();
|
||||
Ray ray = new Ray(currentTarget.transform.position, currentTarget.transform.right * sideMovement);
|
||||
rollDirection = ray.direction;
|
||||
animator.CrossFadeInFixedTime("Roll", 0.1f);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected IEnumerator CheckChanceToBlock(float chance, float timeToEnter)
|
||||
{
|
||||
tryingBlock = true;
|
||||
float randomBlock = UnityEngine.Random.value;
|
||||
if (randomBlock < chance && randomBlock > 0 && !isBlocking)
|
||||
{
|
||||
if (timeToEnter > 0)
|
||||
{
|
||||
yield return new WaitForSeconds(timeToEnter);
|
||||
}
|
||||
|
||||
isBlocking = currentTarget.transform == null || (actions) || isAttacking ? false : true;
|
||||
StartCoroutine(ResetBlock());
|
||||
tryingBlock = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
tryingBlock = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected IEnumerator ResetBlock()
|
||||
{
|
||||
yield return new WaitForSeconds(currentTarget.transform == null ? 0 : raiseShield);
|
||||
isBlocking = false;
|
||||
}
|
||||
|
||||
protected virtual void SetAggressive(bool value)
|
||||
{
|
||||
agressiveAtFirstSight = value;//
|
||||
onSetAgressive.Invoke(value);
|
||||
}
|
||||
|
||||
int GetDamageResult(int damage, float defenseRate)
|
||||
{
|
||||
int result = (int)(damage - ((damage * defenseRate) / 100));
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Ragdoll
|
||||
|
||||
public override void ResetRagdoll()
|
||||
{
|
||||
oldPosition = transform.position;
|
||||
ragdolled = false;
|
||||
_capsuleCollider.isTrigger = false;
|
||||
_rigidbody.isKinematic = false;
|
||||
agent.updatePosition = false;
|
||||
agent.enabled = true;
|
||||
}
|
||||
|
||||
public override void EnableRagdoll()
|
||||
{
|
||||
agent.enabled = false;
|
||||
agent.updatePosition = false;
|
||||
ragdolled = true;
|
||||
_rigidbody.isKinematic = true;
|
||||
_capsuleCollider.isTrigger = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0e30d18d6120c6c41bfcf6015dabc83b
|
||||
timeCreated: 1452892889
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,155 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
namespace Invector.vCharacterController.AI
|
||||
{
|
||||
public class vSimpleMeleeAI_SphereSensor : MonoBehaviour
|
||||
{
|
||||
public Transform root;
|
||||
|
||||
public List<Transform> targetsInArea;
|
||||
protected bool getFromDistance;
|
||||
protected float lastDetectionDistance;
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
targetsInArea = new List<Transform>();
|
||||
}
|
||||
|
||||
public virtual void AddTarget(Transform _transform)
|
||||
{
|
||||
if (!targetsInArea.Contains(_transform))
|
||||
{
|
||||
targetsInArea.Add(_transform);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void SetColliderRadius(float radius)
|
||||
{
|
||||
var collider = GetComponent<SphereCollider>();
|
||||
if (collider)
|
||||
{
|
||||
collider.radius = radius;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual Transform GetTargetTransform()
|
||||
{
|
||||
if (targetsInArea.Count > 0)
|
||||
{
|
||||
SortTargets();
|
||||
if (targetsInArea.Count > 0)
|
||||
{
|
||||
return targetsInArea[0].transform;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual vIHealthController GetTargetvCharacter()
|
||||
{
|
||||
if (targetsInArea.Count > 0)
|
||||
{
|
||||
SortCharacters();
|
||||
if (targetsInArea.Count > 0)
|
||||
{
|
||||
var vChar = targetsInArea[0].GetComponent<vIHealthController>();
|
||||
if (vChar != null && vChar.currentHealth > 0)
|
||||
{
|
||||
return vChar;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected virtual void SortCharacters()
|
||||
{
|
||||
for (int i = targetsInArea.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var t = targetsInArea[i];
|
||||
var dist = Vector3.Distance(transform.position, targetsInArea[i].transform.position);
|
||||
if (t == null || dist > lastDetectionDistance || t.GetComponent<vIHealthController>() == null)
|
||||
{
|
||||
targetsInArea.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (getFromDistance)
|
||||
{
|
||||
targetsInArea.Sort(delegate (Transform c1, Transform c2)
|
||||
{
|
||||
return Vector3.Distance(this.transform.position, c1 != null ? c1.transform.position : Vector3.one * Mathf.Infinity).CompareTo
|
||||
((Vector3.Distance(this.transform.position, c2 != null ? c2.transform.position : Vector3.one * Mathf.Infinity)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void SortTargets()
|
||||
{
|
||||
for (int i = targetsInArea.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var t = targetsInArea[i];
|
||||
var dist = Vector3.Distance(transform.position, targetsInArea[i].transform.position);
|
||||
if (t == null || dist > lastDetectionDistance)
|
||||
{
|
||||
targetsInArea.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
if (getFromDistance)
|
||||
{
|
||||
targetsInArea.Sort(delegate (Transform c1, Transform c2)
|
||||
{
|
||||
return Vector3.Distance(this.transform.position, c1 != null ? c1.transform.position : Vector3.one * Mathf.Infinity).CompareTo
|
||||
((Vector3.Distance(this.transform.position, c2 != null ? c2.transform.position : Vector3.one * Mathf.Infinity)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void CheckTargetsAround(float FOV, float minDistance, float maxDistance, vTagMask detectTags, LayerMask detectMask, bool getTargetFromDistance = false)
|
||||
{
|
||||
this.getFromDistance = getTargetFromDistance;
|
||||
lastDetectionDistance = maxDistance;
|
||||
var targetsAround = Physics.OverlapSphere(transform.position, maxDistance, detectMask);
|
||||
targetsAround = System.Array.FindAll(targetsAround, t =>
|
||||
(root && root != t.transform)
|
||||
&& (detectTags != null && detectTags.Count > 0 && detectTags.Contains(t.gameObject.tag))
|
||||
&& InFovAngle(t.transform, minDistance, FOV));
|
||||
targetsInArea = System.Array.ConvertAll(targetsAround, c => c.transform).vToList();
|
||||
}
|
||||
|
||||
protected virtual bool InFovAngle(Transform target, float minDistance, float FOV)
|
||||
{
|
||||
var dist = Vector3.Distance(transform.position, target.position);
|
||||
if (dist < minDistance)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var rot = Quaternion.LookRotation(target.position - transform.position, Vector3.up);
|
||||
var detectionAngle = transform.eulerAngles;
|
||||
var newAngle = rot.eulerAngles - detectionAngle;
|
||||
var fovAngleY = newAngle.NormalizeAngle().y;
|
||||
var fovAngleX = newAngle.NormalizeAngle().x;
|
||||
if (fovAngleY <= (FOV * 0.5f) && fovAngleY >= -(FOV * 0.5f) && fovAngleX <= (FOV * 0.5f) && fovAngleX >= -(FOV * 0.5f))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
//void OnTriggerEnter(Collider other)
|
||||
//{
|
||||
// if (tagsToDetect.Contains(other.gameObject.tag) && !targetsInArea.Contains(other.transform))
|
||||
// targetsInArea.Add(other);
|
||||
//}
|
||||
|
||||
//void OnTriggerExit(Collider other)
|
||||
//{
|
||||
// if (tagsToDetect.Contains(other.gameObject.tag) && targetsInArea.Contains(other.transform))
|
||||
// targetsInArea.Remove(other);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 43ce90429324b114c883f287bad02448
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,368 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
namespace Invector.vCharacterController.AI
|
||||
{
|
||||
using vItemManager;
|
||||
using vMelee;
|
||||
public class vSimpleMeleeAI_WeaponsControl : MonoBehaviour
|
||||
{
|
||||
#region Variables
|
||||
|
||||
[Header("---- Right Weapon Settings----")]
|
||||
public bool useRightWeapon = true;
|
||||
public int rightWeaponID;
|
||||
public bool randomRightWeapon = true;
|
||||
|
||||
[Header("Right Equip Point")]
|
||||
public Transform defaultEquipPointR;
|
||||
public List<Transform> customEquipPointR;
|
||||
|
||||
[Header("---- Left Weapon Settings----")]
|
||||
public bool useLeftWeapon = true;
|
||||
public int leftWeaponID;
|
||||
public bool randomLeftWeapon = true;
|
||||
|
||||
[Header("Left Equip Point")]
|
||||
public Transform defaultEquipPointL;
|
||||
public List<Transform> customEquipPointL;
|
||||
|
||||
public vItemCollection itemCollection;
|
||||
protected vSimpleMeleeAI_Controller ai;
|
||||
protected vMeleeManager manager;
|
||||
protected vItem leftWeaponItem, rightWeaponItem;
|
||||
protected GameObject leftWeapon, rightWeapon;
|
||||
protected List<vItem> weaponItems = new List<vItem>();
|
||||
protected Transform leftArm;
|
||||
protected Transform rightArm;
|
||||
protected bool inEquip;
|
||||
protected bool inUnequip;
|
||||
protected float equipTimer, unequipTimer;
|
||||
protected float timeToStart;
|
||||
protected int equipCalls;
|
||||
protected bool changeLeft, changeRight;
|
||||
#if !UNITY_5_4_OR_NEWER
|
||||
protected System.Random random;
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
IEnumerator Start()
|
||||
{
|
||||
itemCollection = GetComponentInChildren<vItemCollection>(true);
|
||||
ai = GetComponent<vSimpleMeleeAI_Controller>();
|
||||
manager = GetComponent<vMeleeManager>();
|
||||
yield return new WaitForEndOfFrame();
|
||||
|
||||
if (itemCollection && ai && manager)
|
||||
{
|
||||
ai.onSetAgressive.AddListener(OnSetAgressive);
|
||||
leftArm = ai.animator.GetBoneTransform(HumanBodyBones.LeftLowerArm);
|
||||
rightArm = ai.animator.GetBoneTransform(HumanBodyBones.RightLowerArm);
|
||||
|
||||
for (int i = 0; i < itemCollection.items.Count; i++)
|
||||
{
|
||||
if (itemCollection.items[i].amount > 0)
|
||||
{
|
||||
var item = itemCollection.itemListData.items.Find(_item => _item.id == itemCollection.items[i].id && _item.type == vItemType.MeleeWeapon);
|
||||
if (item != null)
|
||||
{
|
||||
AddItem(itemCollection.items[i].id, itemCollection.items[i].amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (useRightWeapon)
|
||||
{
|
||||
if (randomRightWeapon)
|
||||
{
|
||||
GetRandomWeapon(ref rightWeaponItem, vMeleeType.OnlyAttack);
|
||||
}
|
||||
else
|
||||
{
|
||||
GetItemWeapon(rightWeaponID, ref rightWeaponItem, vMeleeType.OnlyAttack);
|
||||
}
|
||||
}
|
||||
|
||||
if (useLeftWeapon)
|
||||
{
|
||||
if (randomLeftWeapon)
|
||||
{
|
||||
GetRandomWeapon(ref leftWeaponItem, vMeleeType.OnlyDefense);
|
||||
}
|
||||
else
|
||||
{
|
||||
GetItemWeapon(leftWeaponID, ref leftWeaponItem, vMeleeType.OnlyDefense);
|
||||
}
|
||||
}
|
||||
|
||||
if (rightArm && rightWeaponItem)
|
||||
{
|
||||
Transform equipPoint = null;
|
||||
if (customEquipPointR.Count > 0)
|
||||
{
|
||||
equipPoint = customEquipPointR.Find(t => t.name == rightWeaponItem.customHandler);
|
||||
}
|
||||
|
||||
if (equipPoint == null)
|
||||
{
|
||||
equipPoint = defaultEquipPointR;
|
||||
}
|
||||
|
||||
if (equipPoint)
|
||||
{
|
||||
rightWeapon = Instantiate(rightWeaponItem.originalObject);
|
||||
rightWeapon.transform.parent = equipPoint;
|
||||
rightWeapon.transform.localPosition = Vector3.zero;
|
||||
rightWeapon.transform.localEulerAngles = Vector3.zero;
|
||||
manager.SetRightWeapon(rightWeapon);
|
||||
rightWeapon.SetActive(false);
|
||||
if (ai.agressiveAtFirstSight)
|
||||
{
|
||||
StartCoroutine(EquipItemRoutine(false, rightWeaponItem, rightWeapon));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (leftArm && leftWeaponItem)
|
||||
{
|
||||
Transform equipPoint = null;
|
||||
if (customEquipPointL.Count > 0)
|
||||
{
|
||||
equipPoint = customEquipPointL.Find(t => t.name == leftWeaponItem.customHandler);
|
||||
}
|
||||
|
||||
if (equipPoint == null)
|
||||
{
|
||||
equipPoint = defaultEquipPointL;
|
||||
}
|
||||
|
||||
if (equipPoint)
|
||||
{
|
||||
leftWeapon = Instantiate(leftWeaponItem.originalObject);
|
||||
leftWeapon.transform.parent = equipPoint;
|
||||
leftWeapon.transform.localPosition = Vector3.zero;
|
||||
leftWeapon.transform.localEulerAngles = Vector3.zero;
|
||||
var scale = leftWeapon.transform.localScale;
|
||||
scale.x *= -1;
|
||||
leftWeapon.transform.localScale = scale;
|
||||
manager.SetLeftWeapon(leftWeapon);
|
||||
leftWeapon.SetActive(false);
|
||||
if (ai.agressiveAtFirstSight)
|
||||
{
|
||||
StartCoroutine(EquipItemRoutine(true, leftWeaponItem, leftWeapon));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSetAgressive(bool value)
|
||||
{
|
||||
timeToStart = 2f;
|
||||
if (value)
|
||||
{
|
||||
if (rightWeapon && !rightWeapon.activeSelf && !changeRight)
|
||||
{
|
||||
changeRight = true;
|
||||
StartCoroutine(EquipItemRoutine(false, rightWeaponItem, rightWeapon));
|
||||
}
|
||||
if (leftWeapon && !leftWeapon.activeSelf && !changeLeft)
|
||||
{
|
||||
changeLeft = true;
|
||||
StartCoroutine(EquipItemRoutine(true, leftWeaponItem, leftWeapon));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rightWeapon && rightWeapon.activeSelf)
|
||||
{
|
||||
StartCoroutine(UnequipItemRoutine(false, rightWeaponItem, rightWeapon));
|
||||
}
|
||||
|
||||
if (leftWeapon && leftWeapon.activeSelf)
|
||||
{
|
||||
StartCoroutine(UnequipItemRoutine(true, leftWeaponItem, leftWeapon));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GetItemWeapon(int id, ref vItem weaponItem, vMeleeType type)
|
||||
{
|
||||
if (weaponItems.Count > 0)
|
||||
{
|
||||
weaponItem = weaponItems.Find(_item => _item.id == id &&
|
||||
_item.originalObject != null && _item.originalObject.GetComponent<vMeleeWeapon>() != null &&
|
||||
(_item.originalObject.GetComponent<vMeleeWeapon>().meleeType == vMeleeType.AttackAndDefense || _item.originalObject.GetComponent<vMeleeWeapon>().meleeType == type));
|
||||
|
||||
weaponItems.Remove(weaponItem);
|
||||
}
|
||||
}
|
||||
|
||||
void GetRandomWeapon(ref vItem weaponItem, vMeleeType type)
|
||||
{
|
||||
if (weaponItems.Count > 0)
|
||||
{
|
||||
var _weaponItems = weaponItems.FindAll(_item =>
|
||||
_item.originalObject != null && _item.originalObject.GetComponent<vMeleeWeapon>() != null &&
|
||||
(_item.originalObject.GetComponent<vMeleeWeapon>().meleeType == vMeleeType.AttackAndDefense || _item.originalObject.GetComponent<vMeleeWeapon>().meleeType == type));
|
||||
var itemIndex = 0;
|
||||
#if UNITY_5_4_OR_NEWER
|
||||
Random.InitState(Random.Range(0, System.DateTime.Now.Millisecond));
|
||||
itemIndex = Random.Range(0, _weaponItems.Count - 1);
|
||||
#else
|
||||
random = new System.Random(Random.Range(0, System.DateTime.Now.Millisecond));
|
||||
itemIndex = random.Next(0, _weaponItems.Count - 1);
|
||||
#endif
|
||||
weaponItem = _weaponItems[itemIndex];
|
||||
weaponItems.Remove(weaponItem);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator EquipItemRoutine(bool flipEquip, vItem item, GameObject weapon)
|
||||
{
|
||||
equipCalls++;
|
||||
while (inEquip || timeToStart > 0 || ai.ragdolled)
|
||||
{
|
||||
timeToStart -= Time.deltaTime;
|
||||
yield return new WaitForEndOfFrame();
|
||||
}
|
||||
|
||||
if (!inUnequip)
|
||||
{
|
||||
inEquip = true;
|
||||
if (weapon != null && item != null)
|
||||
{
|
||||
equipTimer = item.enableDelayTime;
|
||||
var type = item.type;
|
||||
if (type != vItemType.Consumable && (!ai.isDead))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(item.EnableAnim))
|
||||
{
|
||||
ai.animator.SetBool("FlipEquip", flipEquip);
|
||||
ai.animator.CrossFade(item.EnableAnim, 0.25f);
|
||||
}
|
||||
}
|
||||
if (weapon != null)
|
||||
{
|
||||
while (equipTimer > 0)
|
||||
{
|
||||
equipTimer -= Time.deltaTime;
|
||||
yield return new WaitForEndOfFrame();
|
||||
}
|
||||
if (!ai.isDead)
|
||||
{
|
||||
weapon.SetActive(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inEquip = false;
|
||||
equipCalls--;
|
||||
|
||||
if (equipCalls == 0)
|
||||
{
|
||||
ai.lockMovement = false;
|
||||
}
|
||||
|
||||
if (flipEquip)
|
||||
{
|
||||
changeLeft = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
changeRight = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
IEnumerator UnequipItemRoutine(bool flipEquip, vItem item, GameObject weapon)
|
||||
{
|
||||
ai.lockMovement = true;
|
||||
|
||||
while (inUnequip || ai.actions || ai.isAttacking || ai.ragdolled)
|
||||
{
|
||||
yield return new WaitForEndOfFrame();
|
||||
}
|
||||
|
||||
if (!inEquip)
|
||||
{
|
||||
yield return new WaitForSeconds(1);
|
||||
inUnequip = true;
|
||||
if (weapon != null && item != null)
|
||||
{
|
||||
unequipTimer = item.enableDelayTime;
|
||||
var type = item.type;
|
||||
if (type != vItemType.Consumable)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(item.DisableAnim))
|
||||
{
|
||||
ai.animator.SetBool("FlipEquip", flipEquip);
|
||||
ai.animator.CrossFade(item.DisableAnim, 0.25f);
|
||||
}
|
||||
|
||||
}
|
||||
if (weapon != null)
|
||||
{
|
||||
while (unequipTimer > 0)
|
||||
{
|
||||
unequipTimer -= Time.deltaTime;
|
||||
yield return new WaitForEndOfFrame();
|
||||
}
|
||||
weapon.SetActive(false);
|
||||
}
|
||||
}
|
||||
inUnequip = false;
|
||||
}
|
||||
ai.lockMovement = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add new Instance of Item to itemList
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
public void AddItem(int itemID, int amount)
|
||||
{
|
||||
if (itemCollection.itemListData != null && itemCollection.itemListData.items.Count > 0)
|
||||
{
|
||||
var item = itemCollection.itemListData.items.Find(t => t.id.Equals(itemID));
|
||||
if (item)
|
||||
{
|
||||
var sameItems = weaponItems.FindAll(i => i.stackable && i.id == item.id && i.amount < i.maxStack);
|
||||
if (sameItems.Count == 0)
|
||||
{
|
||||
var _item = Instantiate(item);
|
||||
_item.name = _item.name.Replace("(Clone)", string.Empty);
|
||||
_item.amount = 0;
|
||||
for (int i = 0; i < item.maxStack && _item.amount < _item.maxStack && amount > 0; i++)
|
||||
{
|
||||
_item.amount++;
|
||||
amount--;
|
||||
}
|
||||
|
||||
weaponItems.Add(_item);
|
||||
if (amount > 0)
|
||||
{
|
||||
AddItem(item.id, amount);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var indexOffItem = weaponItems.IndexOf(sameItems[0]);
|
||||
|
||||
for (int i = 0; i < weaponItems[indexOffItem].maxStack && weaponItems[indexOffItem].amount < weaponItems[indexOffItem].maxStack && amount > 0; i++)
|
||||
{
|
||||
weaponItems[indexOffItem].amount++;
|
||||
amount--;
|
||||
}
|
||||
if (amount > 0)
|
||||
{
|
||||
AddItem(item.id, amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c97737e385a96a741a0e10d55c34b9ea
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user