Assemblies Made and Refactored Code Folders

Created assemblies for the new design code. Relocated legacy scripts into a legacy folder and made a "new_design" folder for new design.
This commit is contained in:
MarcoHampel
2023-09-11 19:39:27 -04:00
parent 84b455b473
commit f8590432ba
209 changed files with 729 additions and 0 deletions

View File

@ -0,0 +1,39 @@
using UnityEngine;
using System;
/*
# Enemy System
## States
1. **Passive**
- Enemy is unaware of danger and performing idle actions. Not actively searching for anything.
- Perception range is at its minimum.
- If agressive target is "visible" enter `Agressive` state.
- If nonagressive target is "visible" enter `Aware` state.
2. **Guard**
- Enemy is looking out for danger. Can be moving along a path or standing
- Perception range is at regular level.
- If aggressive target is "visible" enter `Agressive` state.
- If nonagressive target is "visible" enter `Aware` state.
3. **Aware**
- Enemy has noticed something and is moving towards location of interest.
- Peception range is at regular level.
- If player is "visible" enter `Agressive` state.
- If nonagresive target is "visible" of equal or greater importance change target.
- If at location and no new item of note is added to "visible", then enter either `Passive` or `Guard` mode based on original mode.
4. **Aggressive**
- Enemy has noticed player:
- Apply these actions in sequence:
a. If player has not been "visible" for more than 5 seconds enter `Guard` mode.
b. If player has not been "visible" for < 5 seconds move to last location it was visible.
c. If player is in attack range and is visible, stop moving, and attack if able.
d. If player is not in attack range and is visible, run in direction of player.
*/
class AIStateMachine : MonoBehaviour{
[SerializeField]
private EnemyAI.EnemyState state;
}

View File

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

View File

@ -0,0 +1,12 @@
using System;
namespace EnemyAI{
///The state the enemy is currently in.
///Passive: Enemy is not searching for anyone and is at minimum perception distance.
///Guard: Enemy is on the lookout for enemies. May be following a patrol path.
///Aware: Enemy has noticed something and is investigating it.
///Aggressive: Enemy has seen something it wants to attack and is performing its attack.
public enum EnemyState{PASSIVE, GUARD, AWARE, AGGRESSIVE};
}

View File

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

View File

@ -0,0 +1,134 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Enemy {
public class SkinlessMonsterAnimator : MonoBehaviour
{
[SerializeField] private Animator animator;
[SerializeField]
[Tooltip("This is the object with the skin dissolve material")]
private GameObject modelObject;
[SerializeField] private List<GameObject> objectsThatFallOnDeath = new();
[SerializeField] private float deathSpeed = 5f;
[SerializeField] private float fastDeathduration = 1f;
[SerializeField] private float deathDuration = 5f;
private float curDeathSpeed = 5f;
private Material dissolveMaterial;
private bool isAlive = true;
private float speed;
public bool IsRunning { get; private set; }
// Start is called before the first frame update
private void Start()
{
dissolveMaterial = modelObject.GetComponent<SkinnedMeshRenderer>().materials[0];
curDeathSpeed = deathSpeed;
}
// Update is called once per frame
private void Update()
{
if (isAlive)
{
animator.SetFloat("Speed", speed);
}
else
{
var quantity = dissolveMaterial.GetFloat("_DissolveQuantity");
quantity = Mathf.Lerp(quantity, -2, Time.deltaTime * curDeathSpeed);
dissolveMaterial.SetFloat("_DissolveQuantity", quantity);
}
}
public void StartMoving()
{
if (isAlive)
{
IsRunning = true;
speed = 1;
}
}
public void StopMoving()
{
if (isAlive)
{
speed = 0;
IsRunning = false;
}
}
public void Attack()
{
if (isAlive) animator.SetTrigger("Attack");
}
/// <summary>
/// 0,1,2,3
/// </summary>
/// <param name="attackType"></param>
public void SetAttackType(int attackType)
{
animator.SetInteger("AttackIndex", attackType);
}
public void InLight()
{
if (isAlive)
animator.SetBool("InLight", true);
}
public void NotInLight()
{
if (isAlive)
animator.SetBool("InLight", false);
}
public void AttackScream()
{
if (isAlive)
animator.SetTrigger("AttackScream");
}
public void Kill(bool fast = false)
{
//animator.speed = 0;
InLight();
isAlive = false;
foreach (var obj in objectsThatFallOnDeath)
{
obj.AddComponent<Rigidbody>();
if (obj.GetComponent<Collider>() != null) obj.GetComponent<Collider>().enabled = false;
}
var dur = deathDuration;
if (fast)
{
dur = fastDeathduration;
curDeathSpeed = deathSpeed * (deathDuration / fastDeathduration);
}
StartCoroutine(destroyAfterSeconds(dur));
}
private IEnumerator destroyAfterSeconds(float duration)
{
yield return new WaitForSeconds(duration);
Destroy(gameObject);
}
}
}

View File

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

View File

@ -0,0 +1,397 @@
using UnityEngine;
using UnityEngine.AI;
public class SkinlessMonsterComponent : MonoBehaviour
{
[SerializeField] private NavMeshAgent agent;
[SerializeField] private Enemy.SkinlessMonsterAnimator animator;
[SerializeField] private float atTargetDistance = 2;
[SerializeField] private float health = 2f;
[SerializeField] [Tooltip("This is the angle of visibility the enemy has")]
private float visibilityConeLimit = 90f;
[SerializeField] private float bulletSoundRange = 15f;
[SerializeField] private float newTargetCooldown = 5f;
//private bool prePauseStoppedState = false;
[SerializeField] private BoxCollider leftHandDamage;
[SerializeField] private BoxCollider rightHandDamage;
private bool atTarget;
private float distanceToPlayer;
private Item.FlareRegister flareRegister;
private bool inDamageMargin;
private bool inDamageRange;
private bool isAlive = true;
private Vector3 oppositeVector;
private Player.PlayerComponent player;
private TargetInformation target;
private GameObject targetObject;
private float timeSinceTarget;
private void Awake()
{
//Find active player rn.
var players = FindObjectsOfType<Player.PlayerComponent>();
foreach (var p in players)
if (p.isActiveAndEnabled)
player = p;
}
private void Start()
{
flareRegister = FindObjectOfType<Item.FlareRegister>();
if (targetObject == null)
targetObject = new GameObject();
targetObject.name = "Enemy Target";
if (player == null) player = FindObjectOfType<Player.PlayerComponent>();
}
private void Update()
{
if (!player.IsAlive) Stop();
if (target != null) HandleTargetOperations();
CheckForAOERanges();
if (isAlive && player.IsAlive)
SetLiveTargeting();
timeSinceTarget += Time.deltaTime;
if (isAlive)
{
leftHandDamage.enabled = true;
rightHandDamage.enabled = true;
}
else
{
leftHandDamage.enabled = false;
rightHandDamage.enabled = false;
}
/*
AI Behavior:
A. If not targeting player
1. Get distance to player
- if distance < player.NoiseDistance then target player.
2. Raycast to player to test line of sight
- if in line of sight and angle between LOS vector and forward vector is < LOF margin, then
target player.
B. If targeting player
1. If out of sound range and no line of sight then lose target.
- stop()
2. If beacon placed down and in range
- remove agent
- kill()
3. If beacon placed down and in margin:
- stop() and wait for player to leave AOE (enforced by navigation).
*/
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.GetComponent<Item.BulletComponent>() != null)
health -= other.gameObject.GetComponent<Item.BulletComponent>().DamageMagnitude;
}
private void HandleTargetOperations()
{
//Update booleans for movement
var distToTarget = Vector3.Distance(target.target.transform.position, agent.transform.position);
atTarget = atTargetDistance >= distToTarget;
if (target.hasDamageRange)
{
inDamageRange = target.damageRange <= distToTarget;
inDamageMargin = target.damageMargin + target.damageRange <= distToTarget;
}
else
{
inDamageRange = false;
inDamageMargin = false;
}
//Perform actions.
if (target.runTowards)
{
if (inDamageRange)
{
//Damage is being dealt
animator.InLight();
//Deal Damage
health -= Time.deltaTime;
if (health < 0 && isAlive) Kill();
}
else if (inDamageMargin)
{
//Effective stop
agent.SetDestination(transform.position);
animator.StopMoving();
}
else
{
if (atTarget)
{
//Effective stop
agent.SetDestination(transform.position);
animator.StopMoving();
if (target.isHostile)
{
animator.Attack();
animator.SetAttackType(Random.Range(0, 0));
}
}
else
{
agent.SetDestination(target.target.transform.position);
animator.StartMoving();
animator.NotInLight();
}
}
}
else
{
//Run away logic
if (atTarget || distToTarget < 3)
{
animator.StopMoving();
}
else
{
var r = new Ray();
r.origin = transform.position;
r.direction = oppositeVector;
RaycastHit hit;
if (Physics.Raycast(r, out hit))
{
animator.StartMoving();
agent.SetDestination(hit.point);
}
else
{
agent.SetDestination(transform.position + oppositeVector * 100);
agent.isStopped = false;
}
}
}
if (atTarget && target.isHostile) SlowLookAt(target.target.transform.position - transform.position);
}
private void SlowLookAt(Vector3 targetVector, float animatedRotationSpeed = 1f)
{
var relativePos = targetVector;
var toRotation = Quaternion.LookRotation(relativePos);
transform.rotation = Quaternion.Lerp(transform.rotation, toRotation, animatedRotationSpeed * Time.deltaTime);
}
private void CheckForAOERanges()
{
foreach (var bullet in flareRegister.bullets)
{
var dist = Vector3.Distance(bullet.transform.position, transform.position);
if (dist <= bullet.DamageRange)
{
health = 0;
Kill(true);
}
}
foreach (var beacon in flareRegister.beacons)
{
var dist = Vector3.Distance(beacon.transform.position, transform.position);
if (dist <= beacon.Range) health = 0f;
}
if (health <= 0) Kill();
}
public bool isPlayerVisible(bool withAngle)
{
var r = new Ray();
r.origin = transform.position;
r.direction = player.transform.position - transform.position;
var toPosition = (player.transform.position - transform.position).normalized;
var angleToPosition = Vector3.Angle(transform.forward, toPosition);
RaycastHit hit;
if (Physics.Raycast(r, out hit))
{
var hitObject = hit.transform.gameObject;
if (hitObject.GetComponent<Player.PlayerComponent>() != null)
//hit player
return angleToPosition <= visibilityConeLimit || !withAngle;
if (hitObject.GetComponentInParent<Player.PlayerComponent>() != null)
//also hit player
return angleToPosition <= visibilityConeLimit || !withAngle;
}
return false;
}
private void SetLiveTargeting()
{
if (!isAlive)
//this.targetObject.transform.position = transform.position;
Stop();
distanceToPlayer = Vector3.Distance(transform.position, player.transform.position);
//print("Dist Comparison "+distanceToPlayer.ToString()+" Noise:"+player.NoiseManager.NoiseDistance);
var isPlayerVisible = this.isPlayerVisible(true);
//check if player is heard or player line of sight
if (distanceToPlayer <= player.NoiseManager.NoiseDistance || isPlayerVisible)
{
//check that nothing in between
if (this.isPlayerVisible(false)) Target(player.gameObject, true);
}
else
{
if (timeSinceTarget < newTargetCooldown)
//no further targeting
//Stop();
return;
Item.BulletComponent closestBullet = null;
var closestDistance = Mathf.Infinity;
foreach (var bullet in flareRegister.bullets)
{
var bDist = Vector3.Distance(bullet.transform.position, transform.position);
if (closestBullet == null || (bDist < bulletSoundRange && bDist < closestDistance))
{
closestBullet = bullet;
closestDistance = bDist;
}
}
if (closestBullet != null && closestBullet.DamageRange == 0)
{
targetObject.transform.position = closestBullet.transform.position;
Target(targetObject);
}
else
{
targetObject.transform.position = transform.position;
Target(targetObject);
Stop();
}
}
}
/*
STANDARD BEHAVIOR:
- OnSeeing/Hearing Player:
- Run towards Player
- OnHearing Bolt:
- Run towards bolt
- OnSeeing Flare:
- Run towards flare if not within (flareRange + flareMargin).
- This acts like if you are far enough away that you don't feel the pain of the flare
- OnSeeing FlareBeacon:
- Run away if within (flareBeaconRange + flareBeaconMargin).
*/
//Runs towards target. If its meant to be hostile, it will melee attack once in range.
public void Target(GameObject obj, bool hostile = false)
{
if (target == null || target.target != obj)
{
//target = new TargetInformation(obj,hostile,false,runTowards:true);
target = new TargetInformation();
target.target = obj;
target.isHostile = hostile;
target.hasDamageRange = false;
target.runTowards = true;
timeSinceTarget = 0;
}
}
//Runs towards flare such that it stops randomly within margin before range.
public void TargetFlare(GameObject obj, float range, float margin)
{
if (target == null || target.target != obj)
{
//target = new TargetInformation(obj, false, true, range, margin,runTowards:true);
target = new TargetInformation();
target.target = obj;
target.isHostile = false;
target.hasDamageRange = true;
target.damageRange = range;
target.damageMargin = margin;
target.runTowards = true;
timeSinceTarget = 0;
}
}
/// <summary>
/// Runs away from object. A minimum range is specified where it no longer takes damage, and a minimum margin is
/// speicifed where it can stop running.
/// </summary>
/// <param name="obj"></param>
/// <param name="minRange"></param>
/// <param name="minMargin"></param>
public void RunAwayFrom(GameObject obj, float minRange, float minMargin)
{
if (target == null || target.target != obj)
{
//target = new TargetInformation(obj, false, true, minRange, minMargin, false);
target = new TargetInformation();
target.target = obj;
target.isHostile = false;
target.hasDamageRange = true;
target.damageRange = minRange;
target.damageMargin = minMargin;
target.runTowards = false;
oppositeVector = -(target.target.transform.position - transform.position).normalized;
timeSinceTarget = 0;
}
}
public void Stop()
{
target = new TargetInformation();
target.target = gameObject;
target.isHostile = false;
target.hasDamageRange = false;
target.runTowards = true;
}
public void Kill(bool fast = false)
{
if (isAlive)
{
animator.Kill(fast);
isAlive = false;
agent.isStopped = true;
}
}
}
internal class TargetInformation
{
public float damageMargin;
public float damageRange;
public bool hasDamageRange;
public bool isHostile;
public bool runTowards;
public GameObject target;
}

View File

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

View File

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

View File

@ -0,0 +1,22 @@
using UnityEngine;
public class TestSkinnlessMonster : MonoBehaviour
{
[SerializeField] private SkinlessMonsterComponent monster;
[SerializeField] private Camera cam;
// Start is called before the first frame update
private void Start()
{
}
// Update is called once per frame
private void Update()
{
if (Input.GetKeyDown(KeyCode.Alpha1)) monster.Target(cam.gameObject, true);
if (Input.GetKeyDown(KeyCode.Alpha2)) monster.TargetFlare(cam.gameObject, 1, 1);
if (Input.GetKeyDown(KeyCode.Alpha3)) monster.RunAwayFrom(cam.gameObject, 1, 1);
if (Input.GetKeyDown(KeyCode.Alpha0)) monster.Stop();
}
}

View File

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