Updated Enemy AI System to use STRIPS

This commit is contained in:
MarcoHampel
2024-02-12 02:23:19 -05:00
parent e22b8f8cc2
commit 94e7b67d97
65 changed files with 14816 additions and 19766 deletions

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: dbac5972a56c36e43baee7fc025ac678
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e276205caf65a264ab7cafae4de4bd9c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,2 @@
Hiding Resolver:
Given a list of hiding locations, check los for each to the player. Then pick closest (if any) that does not have line of sight.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 60fbee275bbfe7040a5286cdf454d180
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,110 @@
using STRIPS;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
namespace Obscurum.ModularAI
{
public class MeleeResolver : AStripsActionResolver
{
[SerializeField]
private Animator animator;
[SerializeField]
private NavMeshAgent navAgent;
private PlayerComponent player;
private bool inAnimation = false;
[SerializeField]
private float attackAnimationDuration = 1.5f;
[SerializeField]
private float screamAnimationDuration = 1.5f;
[SerializeField]
private float health = 10f;
public override void Attack()
{
if (!inAnimation)
{
animator.SetTrigger("Attack");
StartCoroutine(InAnimationHolder(attackAnimationDuration));
}
}
public override void Hide()
{
}
public override void LookAt(Vector3 position)
{
// Create a direction vector that ignores the y-axis difference by setting the y component of both positions to be the same.
Vector3 direction = new Vector3(position.x, this.transform.position.y, position.z) - this.transform.position;
// Create a rotation that looks in the 'direction' but only rotates around the y-axis.
Quaternion toRotation = Quaternion.LookRotation(direction);
// Apply the rotation over time to smooth the transition.
transform.rotation = Quaternion.Lerp(transform.rotation, toRotation, Time.deltaTime * 150f);
}
public override void LookAt()
{
LookAt(new Vector3(player.transform.position.x, transform.position.y, player.transform.position.z));
}
public override void MoveTo(Vector3 position)
{
navAgent.SetDestination(position);
}
public override void MoveTo()
{
MoveTo(player.transform.position);
}
public override void Roar()
{
if(!inAnimation)
{
animator.SetTrigger("AttackScream");
StartCoroutine(InAnimationHolder(screamAnimationDuration));
}
}
public override void Idle()
{
this.navAgent.isStopped = true;
this.navAgent.speed = 0;
}
private IEnumerator InAnimationHolder(float duration)
{
inAnimation = true;
yield return new WaitForSeconds(duration);
inAnimation = false;
}
// Start is called before the first frame update
void Start()
{
player = PlayerComponent.Instance;
}
// Update is called once per frame
void Update()
{
this.animator.SetFloat("Speed", navAgent.velocity.magnitude / 4);
}
public override bool Collect(ref StripsVariableMapping.PackageMapping package)
{
package.SetMapping("Distance2Player", Vector3.Distance(player.transform.position, transform.position).ToString());
package.SetMapping("IsPlayerVisible", true.ToString());
package.SetMapping("AIHealth", health.ToString());
return true;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 29c3b8c81f374df47a8542d2caed73cb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c824588c9289fbc449c20c47b8a2c46e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: cbd2e2888b0373d46aefb6f1424c3427
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,101 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
using static STRIPS.StripsAction;
namespace STRIPS
{
[CustomEditor(typeof(StripsAction))]
[CanEditMultipleObjects]
public class StripsActionCustomEditor : Editor
{
SerializedProperty associatedMapping;
SerializedProperty preConditions;
ReorderableList preConditionsList;
SerializedProperty functions;
void OnEnable()
{
associatedMapping = serializedObject.FindProperty("associatedMapping");
preConditions = serializedObject.FindProperty("preConditions");
functions = serializedObject.FindProperty("functions");
preConditionsList = new ReorderableList(serializedObject, preConditions, true, true, true, true);
preConditionsList.elementHeightCallback = (int index) =>
{
// Define the height of each element here. You might want to adjust this based on the complexity of each element.
// For simplicity, we're using a fixed height, but you could calculate this dynamically based on the content.
return EditorGUIUtility.singleLineHeight * 2.5f + 6; // Example height for two lines and a bit of padding
};
preConditionsList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
{
var element = preConditions.GetArrayElementAtIndex(index);
rect.y += 2;
var variableNames = GetVariableNames();
// Adjusting the widths to account for the operator dropdown and ensuring everything fits nicely.
float width = rect.width / 2 - 4; // Adjust as necessary for layout
// Draw the variableName1 as a dropdown
var variableName1Prop = element.FindPropertyRelative("variableName1");
int selectedIndex = variableNames.IndexOf(variableName1Prop.stringValue);
selectedIndex = Mathf.Max(0, selectedIndex); // Ensure the index is at least 0
selectedIndex = EditorGUI.Popup(new Rect(rect.x, rect.y, width*1.5f, EditorGUIUtility.singleLineHeight), "Variable Name 1", selectedIndex, variableNames.ToArray());
if (variableNames.Count > 0)
{
variableName1Prop.stringValue = variableNames[selectedIndex];
}
// Draw the operator dropdown
EditorGUI.PropertyField(new Rect(rect.x + (width*1.5f) + 8, rect.y, width/2, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("op"), GUIContent.none);
// Draw the variableName2 directly as a text field for simplicity
EditorGUI.PropertyField(new Rect(rect.x, rect.y + EditorGUIUtility.singleLineHeight + 4, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("variableName2"), new GUIContent("Variable Name 2"));
};
preConditionsList.drawHeaderCallback = (Rect rect) =>
{
EditorGUI.LabelField(rect, "PreConditions");
};
}
public override void OnInspectorGUI()
{
serializedObject.Update();
StripsAction stripsAction = (StripsAction)target;
if (stripsAction.associatedMapping == null)
{
EditorGUILayout.HelpBox("Associated Mapping is null. Please assign an Associated Mapping.", MessageType.Warning);
}
EditorGUILayout.PropertyField(associatedMapping);
preConditionsList.DoLayoutList();
EditorGUILayout.PropertyField(functions);
serializedObject.ApplyModifiedProperties();
}
private List<string> GetVariableNames()
{
var variableNames = new List<string>();
StripsAction stripsAction = (StripsAction)target;
if (stripsAction.associatedMapping != null)
{
var mappingDict = stripsAction.associatedMapping.Mappings;
foreach (var mapping in mappingDict)
{
variableNames.Add(mapping.Key);
}
}
return variableNames;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 088236bfbb7a12c418687cdbd9c644ce
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,29 @@
using UnityEngine;
namespace STRIPS
{
public interface IStripsActionResolver
{
}
[System.Serializable]
public abstract class AStripsActionResolver : MonoBehaviour {
public enum StripsActionResolverAction { MoveTo, Attack, Hide, Roar, LookAt, Idle }
public virtual void MoveTo(Vector3 position) { }
public virtual void MoveTo() { }
public virtual void Attack() { }
public virtual void Hide() { }
public virtual void Roar() { }
public virtual void LookAt(Vector3 position) { }
public virtual void LookAt() { }
public virtual void Idle() { }
public abstract bool Collect(ref StripsVariableMapping.PackageMapping package);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3966f902ea4f69a48ba68f1977d9c298
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,14 @@
{
"name": "Strips",
"rootNamespace": "STRIPS",
"references": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 5761c008b1a68e24fb989c60f3245d36
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,104 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace STRIPS
{
[CreateAssetMenu(fileName = "NewAction", menuName = "STRIPS/Create New Action", order = 1)]
public class StripsAction : ScriptableObject
{
public StripsVariableMapping associatedMapping;
public enum Operator { Less, LessEqual, Equal, GreaterEqual, Greater }
public BooleanOperation[] preConditions;
public AStripsActionResolver.StripsActionResolverAction[] functions;
[System.Serializable]
public class BooleanOperation
{
public string variableName1;
public Operator op;
public string variableName2;
}
private bool EvalWithOperatorString(string val, string val2, Operator op)
{
switch(op)
{
case Operator.Equal: return val == val2;
}
return false;
}
private bool EvalWithOperatorFloat(float val, float val2, Operator op)
{
switch (op)
{
case Operator.Less: return val < val2;
case Operator.Greater: return val > val2;
case Operator.LessEqual: return val <= val2;
case Operator.GreaterEqual: return val >= val2;
case Operator.Equal: return val == val2;
}
return false;
}
private bool EvalWithOperatorBool(bool val, bool val2, Operator op)
{
switch (op)
{
case Operator.Equal: return val == val2;
}
return false;
}
private bool EvalWithOperatorInt(int val, int val2, Operator op)
{
switch (op)
{
case Operator.Less: return val < val2;
case Operator.Greater: return val > val2;
case Operator.LessEqual: return val <= val2;
case Operator.GreaterEqual: return val >= val2;
case Operator.Equal: return val == val2;
}
return false;
}
public bool EvaluateConditionals(StripsVariableMapping.PackageMapping variables)
{
bool finalResult = true;
foreach(BooleanOperation operation in preConditions)
{
StripsVariableMapping.VariableMapping mapping = variables.Mappings[operation.variableName1];
switch (mapping.variable)
{
case StripsVariableMapping.VarType.String:
{
finalResult= finalResult&& EvalWithOperatorString(mapping.value, operation.variableName2, operation.op);
break;
}
case StripsVariableMapping.VarType.Float:
{
finalResult= finalResult && EvalWithOperatorFloat(float.Parse(mapping.value), float.Parse(operation.variableName2), operation.op);
break;
}
case StripsVariableMapping.VarType.Boolean:
{
finalResult= finalResult&& EvalWithOperatorBool(bool.Parse(mapping.value), bool.Parse(operation.variableName2),operation.op);
break;
}
case StripsVariableMapping.VarType.Integer:
{
finalResult = finalResult && EvalWithOperatorInt(int.Parse(mapping.value), int.Parse(operation.variableName2), operation.op);
break;
}
}
}
return finalResult;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 88c10d9c5ffa3de4686d031fe60f0c9a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,89 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace STRIPS
{
public class StripsActionResolver : MonoBehaviour
{
[SerializeField]
private List<StripsGoal> goals;
private StripsGoal activeGoal;
[SerializeField]
private AStripsActionResolver chosenResolver;
[SerializeField]
private StripsVariableMapping variableMapping;
private StripsVariableMapping.PackageMapping updatedStats;
private void PerformAction(AStripsActionResolver.StripsActionResolverAction action)
{
switch(action)
{
case AStripsActionResolver.StripsActionResolverAction.Attack:
chosenResolver.Attack();
break;
case AStripsActionResolver.StripsActionResolverAction.Hide:
chosenResolver.Hide();
break;
case AStripsActionResolver.StripsActionResolverAction.LookAt:
chosenResolver.LookAt();
break;
case AStripsActionResolver.StripsActionResolverAction.Roar:
chosenResolver.Roar();
break;
case AStripsActionResolver.StripsActionResolverAction.MoveTo:
chosenResolver.MoveTo();
break;
default:
chosenResolver.Idle();
break;
}
}
public void Selector()
{
foreach (StripsGoal goal in goals)
{
goal.UpdateActionConditionals(updatedStats);
if (goal.Available.Count > 0)
{
activeGoal = goal;
break;
}
}
if(activeGoal != null)
{
AStripsActionResolver.StripsActionResolverAction[] functs = activeGoal.Available[0].functions;
foreach(AStripsActionResolver.StripsActionResolverAction action in functs)
{
PerformAction(action);
}
}
}
// Start is called before the first frame update
void Start()
{
updatedStats = variableMapping.CreateCopy();
InvokeRepeating("Selector", 1, 1);
}
// Update is called once per frame
void Update()
{
if (chosenResolver.Collect(ref updatedStats))
{
//all good
}
print("Current Action:" + activeGoal.name);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 08f8e4c1b4b4ae1458d1801b75d35b00
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,31 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace STRIPS
{
[CreateAssetMenu(fileName = "New Goal", menuName = "STRIPS/Create New Goal", order = 3)]
public class StripsGoal : ScriptableObject
{
public string goalName;
public StripsAction[] actions;
private List<StripsAction> available;
public List<StripsAction> Available { get { return available; } }
public void UpdateActionConditionals(StripsVariableMapping.PackageMapping updatedMappings)
{
List<StripsAction> available = new List<StripsAction> ();
for(int i =0; i < actions.Length; i++)
{
if (actions[i].EvaluateConditionals(updatedMappings))
{
available.Add(actions[i]);
}
}
this.available=available;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f3ff90239675959479df777d26d59713
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,93 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace STRIPS
{
[CreateAssetMenu(fileName = "VarMapping", menuName = "STRIPS/Create New Variable Mapping", order = 2)]
public class StripsVariableMapping : ScriptableObject
{
[SerializeField]
private VariableMapping[] variableMappings;
public Dictionary<string, VariableMapping> Mappings
{
get
{
Dictionary<string, VariableMapping> mapping = new Dictionary<string, VariableMapping>();
foreach (VariableMapping map in variableMappings)
{
mapping.Add(map.varName, map);
}
return mapping;
}
}
public VariableMapping Find(string name)
{
return Mappings[name];
}
public PackageMapping CreateCopy()
{
VariableMapping[] copiedMappings = new VariableMapping[variableMappings.Length];
int i = 0;
foreach(VariableMapping mapping in variableMappings)
{
VariableMapping newMapping = new VariableMapping();
newMapping.variable = mapping.variable;
newMapping.value = mapping.value;
newMapping.varName = mapping.varName;
copiedMappings[i] = newMapping;
i++;
}
return new PackageMapping(copiedMappings);
}
public class PackageMapping
{
public PackageMapping(VariableMapping[] variableMappings)
{
this.variableMappings= variableMappings;
}
public void SetMapping(string name, string value)
{
for(int i =0;i <variableMappings.Length; i++)
{
if (variableMappings[i].varName == name)
{
variableMappings[i].value = value;
return;
}
}
}
[SerializeField]
private VariableMapping[] variableMappings;
public Dictionary<string, VariableMapping> Mappings
{
get
{
Dictionary<string, VariableMapping> mapping = new Dictionary<string, VariableMapping>();
foreach (VariableMapping map in variableMappings)
{
mapping.Add(map.varName, map);
}
return mapping;
}
}
}
[System.Serializable]
public class VariableMapping
{
public string varName;
public VarType variable;
public string value;
}
public enum VarType { Integer, Float, String, Boolean };
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a962e2bb81deaf54cacbdac4c96bf7aa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: