Fixed small visual bugs
This commit is contained in:
@@ -0,0 +1,500 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Pathfinding;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
//Designed by Jacob Weedman
|
||||
//Use on Brute type enemies
|
||||
|
||||
public class BruteAI : MonoBehaviour
|
||||
{
|
||||
|
||||
//MISC
|
||||
GameObject target;
|
||||
float nextWaypointDistance = 5;
|
||||
Path path;
|
||||
int currentWaypoint = 0;
|
||||
bool reachedEndOfPath = false;
|
||||
GameObject projectile;
|
||||
GameObject player;
|
||||
GameObject barrel;
|
||||
public List<GameObject> SuitablePositions;
|
||||
public List<GameObject> AllPositions;
|
||||
float DistanceFromPlayer;
|
||||
Vector2 StartPosition;
|
||||
public List<GameObject> PatrolPositions;
|
||||
GameObject LastKnownPlayerLocation;
|
||||
|
||||
//CONDITIONS/GENERAL INFORMATION
|
||||
bool canJump = true;
|
||||
bool canMove = true; // Prevent all movement
|
||||
bool canPursue = false; // Follow player
|
||||
bool canFire = false;
|
||||
bool currentlyReloading = false;
|
||||
bool currentlyPatrolling;
|
||||
bool currentlyMovingToNextPatrolTarget = false;
|
||||
float DesiredDistance;
|
||||
float MinumumDistance = 5f;
|
||||
bool targetingPlayer = false;
|
||||
bool inFiringCycle = false;
|
||||
int WeaponCurrentMagazineAmmount;
|
||||
int NumberOfHitsPerMag = 0;
|
||||
float angle;
|
||||
bool canDash = true;
|
||||
|
||||
//ENEMY STATS (Changeable)
|
||||
public float Speed = 0.6f; // In relation to player's walking speed
|
||||
public float JumpHeight = 0.8f; // In relation to player's regular jump height
|
||||
public float Health = 250;
|
||||
public float PatrolDistance = 10;
|
||||
public int PatrolStallTime = 2000; //ms
|
||||
public int PlayerDetectionRange = 25;
|
||||
public float dashSpeed = 20f; // Strength of the dash
|
||||
|
||||
//WEAPON STATS (CHANGEABLE)
|
||||
public int WeaponDamage = 20; // Damage per hit
|
||||
public int WeaponFireRate = 1000; // Delay in time between attacks both melee and ranged
|
||||
public float WeaponRandomSpread = 6f; // Random direction of lanched projectiles
|
||||
public int WeaponRange = 30; // Maximum range of the projectile before it drops off
|
||||
public float WeaponProjectileSpeed = 30f; // Speed of launched projectiles
|
||||
public int WeaponMagazineSize = 15; // Number of shots the enemy will take before having to reload
|
||||
public int WeaponReloadTime = 7000; // Time it takes to reload the magazine
|
||||
|
||||
//REFERENCES
|
||||
Seeker seeker;
|
||||
Rigidbody2D rb;
|
||||
|
||||
//ONCE THE GAME STARTS
|
||||
void Start()
|
||||
{
|
||||
seeker = GetComponent<Seeker>();
|
||||
rb = GetComponent<Rigidbody2D>();
|
||||
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
|
||||
StartPosition = transform.position;
|
||||
AllPositions = GameObject.FindGameObjectsWithTag("PossiblePositions").ToList();
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
if (Vector2.Distance(pos.transform.position, StartPosition) <= PatrolDistance)
|
||||
{
|
||||
PatrolPositions.Add(pos);
|
||||
}
|
||||
}
|
||||
target = PatrolPositions[UnityEngine.Random.Range(0, PatrolPositions.Count)];
|
||||
|
||||
projectile = GameObject.Find("EnemyProjectile");
|
||||
player = GameObject.FindGameObjectWithTag("Player");
|
||||
barrel = transform.Find("Barrel").gameObject;
|
||||
LastKnownPlayerLocation = null;
|
||||
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
DesiredDistance = WeaponRange - 5;
|
||||
|
||||
InvokeRepeating("UpdatePath", 0f, 0.1f);
|
||||
InvokeRepeating("PathfindingTimeout", 0f, 10);
|
||||
|
||||
}
|
||||
|
||||
// When enemy has reached the next node
|
||||
void OnPathComplete(Path p)
|
||||
{
|
||||
if (!p.error)
|
||||
{
|
||||
path = p;
|
||||
currentWaypoint = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Select next node
|
||||
void UpdatePath()
|
||||
{
|
||||
if (seeker.IsDone() && target != null)
|
||||
{
|
||||
seeker.StartPath(rb.position, target.transform.position, OnPathComplete);
|
||||
}
|
||||
}
|
||||
|
||||
// Pathfiniding Timeout
|
||||
void PathfindingTimeout()
|
||||
{
|
||||
if (Vector2.Distance(transform.position, target.transform.position) > 0.5 && canDash == true)
|
||||
{
|
||||
target = gameObject;
|
||||
MoveNextPatrol();
|
||||
}
|
||||
}
|
||||
|
||||
//MAIN LOGIC
|
||||
void FixedUpdate()
|
||||
{
|
||||
//DEATH
|
||||
if (Health <= 0)
|
||||
{
|
||||
GameObject DeadBody;
|
||||
DeadBody = Instantiate(gameObject, transform.position, Quaternion.identity);
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<TestEnemyAIGroundRanged>());
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<Seeker>());
|
||||
|
||||
foreach (Transform child in DeadBody.transform)
|
||||
{
|
||||
GameObject.Destroy(child.gameObject);
|
||||
}
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
//ICONS
|
||||
if (targetingPlayer)
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
if (currentlyReloading)
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
//if (90 <= angle || angle <= 270)
|
||||
//{
|
||||
// barrel.transform.localScale = new Vector2(-barrel.transform.localScale.x, barrel.transform.localScale.y);
|
||||
//}
|
||||
|
||||
//MISC PATHFINDING
|
||||
if (path == null)
|
||||
return;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
if (currentWaypoint >= path.vectorPath.Count)
|
||||
{
|
||||
reachedEndOfPath = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
reachedEndOfPath = false;
|
||||
}
|
||||
|
||||
//RANGED ATTACK
|
||||
// Check if enemy has line of sight on the player & if they are in the acceptable range
|
||||
if (WeaponCurrentMagazineAmmount > 0 && canFire == true && currentlyReloading == false && currentlyPatrolling == false)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= WeaponRange && Vector2.Distance(transform.position, player.transform.position) <= DesiredDistance)
|
||||
{
|
||||
UseWeapon();
|
||||
}
|
||||
}
|
||||
else if (WeaponCurrentMagazineAmmount == 0 && currentlyReloading == false)
|
||||
{
|
||||
ReloadWeapon();
|
||||
}
|
||||
|
||||
//CALL DASH
|
||||
if (DetermineLineOfSight(gameObject, player) && targetingPlayer && canDash == true && canJump == true)
|
||||
{
|
||||
Dash(player);
|
||||
}
|
||||
|
||||
//MOVEMENT
|
||||
if (canMove == true)
|
||||
{
|
||||
canFire = false;
|
||||
|
||||
Vector2 direction = (Vector2)path.vectorPath[currentWaypoint] - rb.position;
|
||||
//float xDirection = target.transform.position.x - transform.position.x;
|
||||
|
||||
if (direction.x > 0) // Move right
|
||||
{
|
||||
rb.AddForce(new Vector2(Speed * 20, rb.linearVelocity.y));
|
||||
}
|
||||
|
||||
if (direction.x < 0) // Move left
|
||||
{
|
||||
rb.AddForce(new Vector2(-1 * Speed * 20, rb.linearVelocity.y));
|
||||
}
|
||||
|
||||
if (direction.y > 1f) // Wants to jump
|
||||
{
|
||||
JumpMethod();
|
||||
}
|
||||
|
||||
// A* logic
|
||||
float distance = Vector2.Distance(rb.position, path.vectorPath[currentWaypoint]);
|
||||
|
||||
if (distance < nextWaypointDistance)
|
||||
{
|
||||
currentWaypoint++;
|
||||
}
|
||||
}
|
||||
|
||||
//GROUND DETECTION
|
||||
//Detecting if the enemy has reached the ground
|
||||
if (GetComponentInChildren<GroundCheck>().isGrounded == true && rb.linearVelocity.y == 0)
|
||||
{
|
||||
canJump = true;
|
||||
if (inFiringCycle == false)
|
||||
{
|
||||
canFire = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
canJump = false;
|
||||
canFire = false;
|
||||
}
|
||||
|
||||
//PATROL & DETECTION
|
||||
// Enemy detects player
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= PlayerDetectionRange)
|
||||
{
|
||||
currentlyPatrolling = false;
|
||||
targetingPlayer = true;
|
||||
canPursue = true;
|
||||
// Get the last know player location by finding which of the positions is closest to the player
|
||||
if (LastKnownPlayerLocation == null)
|
||||
{
|
||||
LastKnownPlayerLocation = gameObject;
|
||||
}
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
if (Vector2.Distance(player.transform.position, pos.transform.position) < Vector2.Distance(player.transform.position, LastKnownPlayerLocation.transform.position))
|
||||
{
|
||||
LastKnownPlayerLocation = pos;
|
||||
}
|
||||
}
|
||||
|
||||
// Angle barrel towards player
|
||||
angle = Mathf.Atan2(player.transform.position.y - barrel.transform.position.y, player.transform.position.x - barrel.transform.position.x) * Mathf.Rad2Deg;
|
||||
|
||||
}
|
||||
// Player has broken line of sight and the enemy will attempt to move to the last known location
|
||||
else if (LastKnownPlayerLocation != null)
|
||||
{
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) > 0.5)
|
||||
{
|
||||
canPursue = false;
|
||||
target = LastKnownPlayerLocation;
|
||||
}
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) < 0.5 && DetermineLineOfSight(player, gameObject) == false)
|
||||
{
|
||||
targetingPlayer = false;
|
||||
LastKnownPlayerLocation = null;
|
||||
}
|
||||
|
||||
// Reset barrel rotation
|
||||
angle = 0f;
|
||||
|
||||
}
|
||||
// Go back to patrol move
|
||||
else
|
||||
{
|
||||
currentlyPatrolling = true;
|
||||
targetingPlayer = false;
|
||||
if (canMove == true && currentlyMovingToNextPatrolTarget == false)
|
||||
{
|
||||
MoveNextPatrol();
|
||||
}
|
||||
|
||||
// Reset barrel rotation
|
||||
angle = 0f;
|
||||
}
|
||||
|
||||
// Rotate barrel towards player
|
||||
Quaternion targetRotation = Quaternion.Euler(new Vector3(0, 0, angle));
|
||||
barrel.transform.rotation = Quaternion.RotateTowards(barrel.transform.rotation, targetRotation, 100 * Time.deltaTime);
|
||||
|
||||
//TARGET DETERMINATION
|
||||
if (canPursue == true)
|
||||
{
|
||||
// Catch desired distance error
|
||||
if (DesiredDistance <= MinumumDistance)
|
||||
{
|
||||
DesiredDistance = MinumumDistance + 1;
|
||||
}
|
||||
|
||||
// If the player moves away (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) > DesiredDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the player is too close to the target (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) < MinumumDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the target is on the other side of the player (CHANGE TARGET)
|
||||
if (Vector2.Distance(transform.position, player.transform.position) < Vector2.Distance(target.transform.position, player.transform.position))
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the player is not within line of sight of the desired position (CHANGE TARGET)
|
||||
if (DetermineLineOfSight(target, player) == false)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the enemy reaches the target
|
||||
if (Vector2.Distance(target.transform.position, transform.position) <= 1 && DetermineLineOfSight(gameObject, player))
|
||||
{
|
||||
if (canFire == false && inFiringCycle == false)
|
||||
{
|
||||
canFire = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MISC METHODS
|
||||
// Perform jump
|
||||
async Task JumpMethod()
|
||||
{
|
||||
if (canJump == true)
|
||||
{
|
||||
rb.linearVelocity = new Vector2(rb.linearVelocity.x, JumpHeight * 12);
|
||||
canJump = false;
|
||||
await Task.Delay(500);
|
||||
}
|
||||
}
|
||||
|
||||
// Dash
|
||||
async Task Dash(GameObject endLocation)
|
||||
{
|
||||
canDash = false;
|
||||
|
||||
// Perform dash
|
||||
rb.linearVelocity = new Vector2(transform.position.x - player.transform.position.x, transform.position.y - player.transform.position.y).normalized * dashSpeed * -1;
|
||||
|
||||
await Task.Delay(5000);
|
||||
|
||||
canDash = true;
|
||||
}
|
||||
|
||||
// Use Ranged Weapon
|
||||
async Task UseWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
inFiringCycle = true;
|
||||
|
||||
// Create Projectile
|
||||
GameObject BulletInstance;
|
||||
BulletInstance = Instantiate(projectile, transform.position, Quaternion.LookRotation(transform.position - GameObject.FindGameObjectWithTag("Player").transform.position + new Vector3(UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), 0)));
|
||||
BulletInstance.transform.parent = transform;
|
||||
|
||||
// Send it on it's way
|
||||
BulletInstance.GetComponent<Rigidbody2D>().linearVelocity = BulletInstance.transform.forward * -1 * WeaponProjectileSpeed;
|
||||
BulletInstance.transform.rotation = Quaternion.Euler(new Vector3(0, 0, Vector2.SignedAngle(Vector2.right, BulletInstance.transform.forward) - 90));
|
||||
WeaponCurrentMagazineAmmount--;
|
||||
|
||||
await Task.Delay(WeaponFireRate);
|
||||
|
||||
canFire = true;
|
||||
inFiringCycle = false;
|
||||
}
|
||||
|
||||
// Reload Weapon
|
||||
async Task ReloadWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
//play reload animation
|
||||
currentlyReloading = true;
|
||||
await Task.Delay(WeaponReloadTime);
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
currentlyReloading = false;
|
||||
|
||||
if (NumberOfHitsPerMag / WeaponMagazineSize < 0.5)
|
||||
{
|
||||
DesiredDistance -= 5;
|
||||
}
|
||||
NumberOfHitsPerMag = 0;
|
||||
|
||||
canFire = true;
|
||||
}
|
||||
|
||||
// Part of the patrol cycle
|
||||
async Task MoveNextPatrol()
|
||||
{
|
||||
LastKnownPlayerLocation = null;
|
||||
DesiredDistance = WeaponRange - 5;
|
||||
|
||||
canPursue = false;
|
||||
currentlyMovingToNextPatrolTarget = true;
|
||||
|
||||
//Find Random Position nearby
|
||||
if (Vector2.Distance(transform.position, target.transform.position) <= 0.5 || PatrolPositions.Contains(target) == false)
|
||||
{
|
||||
target = PatrolPositions[UnityEngine.Random.Range(0, PatrolPositions.Count)];
|
||||
await Task.Delay(PatrolStallTime + UnityEngine.Random.Range((PatrolStallTime * -1), PatrolStallTime));
|
||||
}
|
||||
|
||||
currentlyMovingToNextPatrolTarget = false;
|
||||
|
||||
}
|
||||
|
||||
// General Utility
|
||||
bool DetermineLineOfSight(GameObject object1, GameObject object2)
|
||||
{
|
||||
Vector3 RaycastStart = object1.transform.position;
|
||||
Vector3 RaycastDirection = (object2.transform.position - object1.transform.position).normalized;
|
||||
float RaycastDistance = Vector3.Distance(object2.transform.position, object1.transform.position);
|
||||
|
||||
if (Physics2D.Raycast(RaycastStart, RaycastDirection, RaycastDistance, LayerMask.GetMask("SolidGround")) == false)
|
||||
{
|
||||
Debug.DrawRay(RaycastStart, RaycastDirection * RaycastDistance);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Part of the pursue cycle
|
||||
void ComputeClosestPositionToPlayer()
|
||||
{
|
||||
canFire = false;
|
||||
canMove = true;
|
||||
|
||||
SuitablePositions.Clear();
|
||||
foreach (GameObject query in AllPositions)
|
||||
{
|
||||
// Check the distance of the position
|
||||
if (Vector2.Distance(query.transform.position, player.transform.position) < DesiredDistance && Vector2.Distance(query.transform.position, player.transform.position) >= MinumumDistance)
|
||||
{
|
||||
// Check line of sight of the position
|
||||
if (DetermineLineOfSight(query, player))
|
||||
{
|
||||
SuitablePositions.Add(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (SuitablePositions.Count > 0)
|
||||
{
|
||||
target = SuitablePositions[UnityEngine.Random.Range(0, SuitablePositions.Count)];
|
||||
foreach (GameObject pos in SuitablePositions)
|
||||
{
|
||||
//Find the point that is closest to the enemy
|
||||
if (Vector2.Distance(transform.position, pos.transform.position) < Vector2.Distance(transform.position, target.transform.position))
|
||||
{
|
||||
target = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9776525c92b362242a58f959cbe6b91e
|
||||
@@ -0,0 +1 @@
|
||||
uid://d4eldf5x4kddm
|
||||
@@ -0,0 +1,328 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Pathfinding;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
//Designed by Jacob Weedman
|
||||
//Use on Cloaker type enemies
|
||||
|
||||
public class CloakerAI : MonoBehaviour
|
||||
{
|
||||
|
||||
//MISC
|
||||
GameObject target;
|
||||
public float nextWaypointDistance = 5;
|
||||
Path path;
|
||||
int currentWaypoint = 0;
|
||||
bool reachedEndOfPath = false;
|
||||
GameObject projectile;
|
||||
GameObject player;
|
||||
public List<GameObject> SuitablePositions;
|
||||
public List<GameObject> AllPositions;
|
||||
float DistanceFromPlayer;
|
||||
Vector2 StartPosition;
|
||||
public List<GameObject> PatrolPositions;
|
||||
GameObject LastKnownPlayerLocation;
|
||||
|
||||
//CONDITIONS/GENERAL INFORMATION
|
||||
bool canJump = true;
|
||||
bool canMove = true; // Prevent all movement
|
||||
bool canPursue = false; // Follow player
|
||||
bool canFire = false;
|
||||
bool currentlyReloading = false;
|
||||
bool targetingPlayer = false;
|
||||
bool inFiringCycle = false;
|
||||
bool canDash = true;
|
||||
|
||||
//ENEMY STATS (Changeable)
|
||||
public float Speed = 0.7f; // In relation to player's walking speed
|
||||
public float JumpHeight = 1.4f; // In relation to player's regular jump height
|
||||
public float Health = 15;
|
||||
public int PlayerDetectionRange = 25;
|
||||
public float HiddenTransparencyAmmount = 0.05f;
|
||||
public float dashSpeed = 40f; // Strength of the dash
|
||||
|
||||
//WEAPON STATS (CHANGEABLE)
|
||||
public int WeaponDamage = 40; // Damage per hit
|
||||
public int WeaponFireRate = 1500; // Delay in time between attacks both melee and ranged
|
||||
public float WeaponMeleeRange = 0.75f;
|
||||
|
||||
//REFERENCES
|
||||
Seeker seeker;
|
||||
Rigidbody2D rb;
|
||||
|
||||
//ONCE THE GAME STARTS
|
||||
void Start()
|
||||
{
|
||||
seeker = GetComponent<Seeker>();
|
||||
rb = GetComponent<Rigidbody2D>();
|
||||
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
|
||||
StartPosition = transform.position;
|
||||
AllPositions = GameObject.FindGameObjectsWithTag("PossiblePositions").ToList();
|
||||
|
||||
projectile = GameObject.Find("EnemyProjectile");
|
||||
player = GameObject.FindGameObjectWithTag("Player");
|
||||
LastKnownPlayerLocation = null;
|
||||
target = gameObject;
|
||||
|
||||
InvokeRepeating("UpdatePath", 0f, 0.1f);
|
||||
InvokeRepeating("PathfindingTimeout", 0f, 10);
|
||||
|
||||
}
|
||||
|
||||
// When enemy has reached the next node
|
||||
void OnPathComplete(Path p)
|
||||
{
|
||||
if (!p.error)
|
||||
{
|
||||
path = p;
|
||||
currentWaypoint = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Select next node
|
||||
void UpdatePath()
|
||||
{
|
||||
if (seeker.IsDone() && target != null)
|
||||
{
|
||||
seeker.StartPath(rb.position, target.transform.position, OnPathComplete);
|
||||
}
|
||||
}
|
||||
|
||||
// Pathfiniding Timeout
|
||||
void PathfindingTimeout()
|
||||
{
|
||||
if (Vector2.Distance(transform.position, target.transform.position) > 0.5)
|
||||
{
|
||||
LastKnownPlayerLocation = null;
|
||||
targetingPlayer = false;
|
||||
target = gameObject;
|
||||
}
|
||||
}
|
||||
|
||||
//MAIN LOGIC
|
||||
void FixedUpdate()
|
||||
{
|
||||
|
||||
//DEATH
|
||||
if (Health <= 0)
|
||||
{
|
||||
GameObject DeadBody;
|
||||
DeadBody = Instantiate(gameObject, transform.position, Quaternion.identity);
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<TestEnemyAIGroundRanged>());
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<Seeker>());
|
||||
|
||||
foreach (Transform child in DeadBody.transform) {
|
||||
GameObject.Destroy(child.gameObject);
|
||||
}
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
//ICONS
|
||||
if (targetingPlayer && gameObject.GetComponent<SpriteRenderer>().color.a == 1)
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
gameObject.GetComponent<SpriteRenderer>().color = new Color(1f, 0f, 0f, HiddenTransparencyAmmount);
|
||||
}
|
||||
|
||||
//MISC PATHFINDING
|
||||
if (path == null)
|
||||
return;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
if (currentWaypoint >= path.vectorPath.Count)
|
||||
{
|
||||
reachedEndOfPath = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
reachedEndOfPath = false;
|
||||
}
|
||||
|
||||
//ATTACK
|
||||
// Check if enemy has line of sight on the player & if they are in the acceptable range
|
||||
if (canFire == true && currentlyReloading == false)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(player.transform.position, transform.position) <= WeaponMeleeRange)
|
||||
{
|
||||
gameObject.GetComponent<SpriteRenderer>().color = new Color(1f, 0f, 0f, 1f);
|
||||
UseWeapon();
|
||||
}
|
||||
}
|
||||
//CALL DASH
|
||||
if (DetermineLineOfSight(gameObject, player) && targetingPlayer && canDash == true)
|
||||
{
|
||||
Dash();
|
||||
}
|
||||
|
||||
//MOVEMENT
|
||||
if (canMove == true)
|
||||
{
|
||||
canFire = false;
|
||||
|
||||
Vector2 direction = (Vector2)path.vectorPath[currentWaypoint] - rb.position;
|
||||
|
||||
if (direction.x > 0) // Move right
|
||||
{
|
||||
rb.AddForce(new Vector2(Speed * 20, rb.linearVelocity.y));
|
||||
}
|
||||
|
||||
if (direction.x < 0) // Move left
|
||||
{
|
||||
rb.AddForce(new Vector2(-1 * Speed * 20, rb.linearVelocity.y));
|
||||
}
|
||||
|
||||
if (direction.y > 1f) // Wants to jump
|
||||
{
|
||||
JumpMethod();
|
||||
}
|
||||
|
||||
// A* logic
|
||||
float distance = Vector2.Distance(rb.position, path.vectorPath[currentWaypoint]);
|
||||
|
||||
if (distance < nextWaypointDistance)
|
||||
{
|
||||
currentWaypoint++;
|
||||
}
|
||||
}
|
||||
|
||||
//GROUND DETECTION
|
||||
//Detecting if the enemy has reached the ground
|
||||
if (GetComponentInChildren<GroundCheck>().isGrounded == true && rb.linearVelocity.y == 0)
|
||||
{
|
||||
canJump = true;
|
||||
if (inFiringCycle == false)
|
||||
{
|
||||
canFire = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
canJump = false;
|
||||
canFire = false;
|
||||
}
|
||||
|
||||
//DETECTION
|
||||
// Enemy detects player
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= PlayerDetectionRange)
|
||||
{
|
||||
targetingPlayer = true;
|
||||
canPursue = true;
|
||||
|
||||
target = player;
|
||||
// Get the last know player location by finding which of the positions is closest to the player
|
||||
if (LastKnownPlayerLocation == null)
|
||||
{
|
||||
LastKnownPlayerLocation = gameObject;
|
||||
}
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
if (Vector2.Distance(player.transform.position, pos.transform.position) < Vector2.Distance(player.transform.position, LastKnownPlayerLocation.transform.position))
|
||||
{
|
||||
LastKnownPlayerLocation = pos;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// Player has broken line of sight and the enemy will attempt to move to the last known location
|
||||
else if (LastKnownPlayerLocation != null)
|
||||
{
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) > 0.5)
|
||||
{
|
||||
canPursue = false;
|
||||
target = LastKnownPlayerLocation;
|
||||
}
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) < 0.5 && DetermineLineOfSight(player, gameObject) == false)
|
||||
{
|
||||
targetingPlayer = false;
|
||||
LastKnownPlayerLocation = null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//TARGET DETERMINATION
|
||||
if (canPursue == true)
|
||||
{
|
||||
// If the enemy reaches the target
|
||||
if (Vector2.Distance(target.transform.position, transform.position) <= 1 && DetermineLineOfSight(gameObject, player))
|
||||
{
|
||||
if (canFire == false && inFiringCycle == false)
|
||||
{
|
||||
canFire = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MISC METHODS
|
||||
// Perform jump
|
||||
async Task JumpMethod()
|
||||
{
|
||||
if (canJump == true)
|
||||
{
|
||||
rb.linearVelocity = new Vector2(rb.linearVelocity.x, JumpHeight * 12);
|
||||
canJump = false;
|
||||
await Task.Delay(500);
|
||||
}
|
||||
}
|
||||
|
||||
// Dash
|
||||
async Task Dash()
|
||||
{
|
||||
canDash = false;
|
||||
|
||||
// Perform dash
|
||||
rb.linearVelocity = new Vector2(transform.position.x - player.transform.position.x, transform.position.y - player.transform.position.y).normalized * dashSpeed * -1;
|
||||
|
||||
await Task.Delay(1000); // Dash delay ms
|
||||
|
||||
canDash = true;
|
||||
}
|
||||
|
||||
// Use Ranged Weapon
|
||||
async Task UseWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
inFiringCycle = true;
|
||||
|
||||
// melee attack
|
||||
//Damage
|
||||
GameObject.Find("GameData").GetComponent<GameData>().CurrentHealth -= WeaponDamage;
|
||||
|
||||
await Task.Delay(WeaponFireRate);
|
||||
|
||||
canFire = true;
|
||||
inFiringCycle = false;
|
||||
}
|
||||
|
||||
// General Utility
|
||||
bool DetermineLineOfSight(GameObject object1, GameObject object2)
|
||||
{
|
||||
Vector3 RaycastStart = object1.transform.position;
|
||||
Vector3 RaycastDirection = (object2.transform.position - object1.transform.position).normalized;
|
||||
float RaycastDistance = Vector3.Distance(object2.transform.position, object1.transform.position);
|
||||
|
||||
if (Physics2D.Raycast(RaycastStart, RaycastDirection, RaycastDistance, LayerMask.GetMask("SolidGround")) == false)
|
||||
{
|
||||
Debug.DrawRay(RaycastStart, RaycastDirection * RaycastDistance);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a49219e9fdacd2640a56d6100c2983c9
|
||||
@@ -0,0 +1 @@
|
||||
uid://b56cltkpns8eg
|
||||
@@ -0,0 +1,465 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Pathfinding;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
//Designed by Jacob Weedman
|
||||
//V 0.0.3
|
||||
//DO NOT USE THIS ON ANY ENEMIES
|
||||
|
||||
public class CustomEnemyAI : MonoBehaviour
|
||||
{
|
||||
|
||||
//MISC
|
||||
public GameObject target;
|
||||
public float nextWaypointDistance = 5;
|
||||
Path path;
|
||||
int currentWaypoint = 0;
|
||||
bool reachedEndOfPath = false;
|
||||
GameObject projectile;
|
||||
GameObject player;
|
||||
public List<GameObject> SuitablePositions;
|
||||
public List<GameObject> AllPositions;
|
||||
public float DistanceFromPlayer;
|
||||
Vector2 StartPosition;
|
||||
public List<GameObject> PatrolPositions;
|
||||
public GameObject LastKnownPlayerLocation;
|
||||
|
||||
//CONDITIONS/GENERAL INFORMATION
|
||||
public bool canJump = true;
|
||||
public bool canMove = true;
|
||||
public bool canPursue = true;
|
||||
public bool canFire = false;
|
||||
public bool currentlyReloading = false;
|
||||
public bool currentlyPatrolling;
|
||||
public bool currentlyMovingToNextPatrolTarget = false;
|
||||
public float DesiredDistance;
|
||||
public float MinumumDistance = 5f;
|
||||
public bool targetingPlayer = false;
|
||||
|
||||
//ENEMY STATS (Changeable)
|
||||
public float Speed = 1; // In relation to player's walking speed
|
||||
public float JumpHeight = 1; // In relation to player's regular jump height
|
||||
public float Health = 100;
|
||||
public float PatrolDistance = 10;
|
||||
public int PatrolStallTime = 2000; //ms
|
||||
public int PlayerDetectionRange = 30;
|
||||
|
||||
//WEAPON STATS (CHANGEABLE)
|
||||
public string WeaponType = "RANGED"; // RANGED, MELEE, PHYSICS
|
||||
public int WeaponDamage = 15; // Damage per hit
|
||||
public int WeaponFireRate = 250; // Delay in time between attacks both melee and ranged
|
||||
public float WeaponRandomSpread = 5f; // Random direction of lanched projectiles (DOES NOT APPLY TO MELEE ATTACKS)
|
||||
public int WeaponRange = 40; // Maximum range of the projectile before it drops off (DOES NOT APPLY TO MELEE ATTACKS)
|
||||
int WeaponMeleeRange = 5; // Minimum distance the enemy has to be away from the player in order to be able to hit the player (DOES NOT APPLY TO RANGED ATTACKS)
|
||||
public float WeaponProjectileSpeed = 30f; // Speed of launched projectiles (DOES NOT APPLY TO MELEE WEAPONS)
|
||||
public int WeaponMagazineSize = 20; // Number of shots the enemy will take before having to reload
|
||||
public int WeaponReloadTime = 5000; // Time it takes to reload the magazine
|
||||
public int WeaponCurrentMagazineAmmount; // Current number of ammo in the mag (DO NOT CHANGE THIS)
|
||||
|
||||
Seeker seeker;
|
||||
Rigidbody2D rb;
|
||||
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
void Start()
|
||||
{
|
||||
seeker = GetComponent<Seeker>();
|
||||
rb = GetComponent<Rigidbody2D>();
|
||||
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
|
||||
StartPosition = transform.position;
|
||||
AllPositions = GameObject.FindGameObjectsWithTag("PossiblePositions").ToList();
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
if (Vector2.Distance(pos.transform.position, StartPosition) <= PatrolDistance)
|
||||
{
|
||||
PatrolPositions.Add(pos);
|
||||
}
|
||||
}
|
||||
target = PatrolPositions[UnityEngine.Random.Range(0, PatrolPositions.Count)];
|
||||
|
||||
projectile = GameObject.Find("TestBullet");
|
||||
player = GameObject.FindGameObjectWithTag("Player");
|
||||
LastKnownPlayerLocation = gameObject;
|
||||
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
DesiredDistance = WeaponRange;
|
||||
|
||||
InvokeRepeating("UpdatePath", 0f, 0.1f);
|
||||
}
|
||||
|
||||
// When enemy has reached the next node
|
||||
void OnPathComplete(Path p)
|
||||
{
|
||||
if (!p.error)
|
||||
{
|
||||
path = p;
|
||||
currentWaypoint = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Select next node
|
||||
void UpdatePath()
|
||||
{
|
||||
if (seeker.IsDone() && target != null)
|
||||
{
|
||||
seeker.StartPath(rb.position, target.transform.position, OnPathComplete);
|
||||
}
|
||||
}
|
||||
|
||||
//Main logic
|
||||
void FixedUpdate()
|
||||
{
|
||||
if (Health <= 0)
|
||||
{
|
||||
Destroy(transform);
|
||||
}
|
||||
|
||||
// See where the enemy wants to go (DEBUGGING ONLY)
|
||||
if (target != null)
|
||||
{
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
pos.GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
target.GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
|
||||
// Change Icons
|
||||
if (targetingPlayer)
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
if (currentlyReloading)
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
//MISC PATHFINDING
|
||||
if (path == null)
|
||||
return;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
if (currentWaypoint >= path.vectorPath.Count)
|
||||
{
|
||||
reachedEndOfPath = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
reachedEndOfPath = false;
|
||||
}
|
||||
|
||||
//MOVEMENT
|
||||
if (canMove == true)
|
||||
{
|
||||
canFire = false;
|
||||
|
||||
Vector2 direction = (Vector2)path.vectorPath[currentWaypoint] - rb.position;
|
||||
float xDirection = path.vectorPath[currentWaypoint].x - rb.position.x;
|
||||
|
||||
if (xDirection > 0) // Move right
|
||||
{
|
||||
rb.AddForce(new Vector2(Speed * 20, rb.linearVelocity.y));
|
||||
}
|
||||
|
||||
if (xDirection < 0) // Move left
|
||||
{
|
||||
rb.AddForce(new Vector2(-1 * Speed * 20, rb.linearVelocity.y));
|
||||
}
|
||||
|
||||
if (direction.y > 1f) // Wants to jump
|
||||
{
|
||||
JumpMethod();
|
||||
}
|
||||
|
||||
// A* logic
|
||||
float distance = Vector2.Distance(rb.position, path.vectorPath[currentWaypoint]);
|
||||
|
||||
if (distance < nextWaypointDistance)
|
||||
{
|
||||
currentWaypoint++;
|
||||
}
|
||||
}
|
||||
|
||||
//Detecting if the enemy has reached the ground
|
||||
if (GetComponentInChildren<GroundCheck>().isGrounded == true)
|
||||
{
|
||||
canJump = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
canJump = false;
|
||||
}
|
||||
|
||||
//PATROL
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= PlayerDetectionRange)
|
||||
{
|
||||
//target = null;
|
||||
currentlyPatrolling = false;
|
||||
targetingPlayer = true;
|
||||
canPursue = true;
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
if (Vector2.Distance(player.transform.position, pos.transform.position) < Vector2.Distance(player.transform.position, LastKnownPlayerLocation.transform.position))
|
||||
{
|
||||
LastKnownPlayerLocation = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) > 0.5)
|
||||
{
|
||||
canPursue = false;
|
||||
if (targetingPlayer == false)
|
||||
{
|
||||
target = LastKnownPlayerLocation;
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
//target = PatrolPositions[UnityEngine.Random.Range(0, PatrolPositions.Count)];
|
||||
LastKnownPlayerLocation = gameObject;
|
||||
currentlyPatrolling = true;
|
||||
currentlyPatrolling = true;
|
||||
targetingPlayer = false;
|
||||
}
|
||||
|
||||
if (currentlyPatrolling)
|
||||
{
|
||||
if (canMove == true && currentlyMovingToNextPatrolTarget == false)
|
||||
{
|
||||
MoveNextPatrol();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//RANGED ATTACK
|
||||
if (WeaponType == "RANGED")
|
||||
{
|
||||
// Check if enemy has line of sight on the player
|
||||
// Does the ray intersect any ground objects
|
||||
//if (Physics2D.Raycast(EnemyRaycastStart, EnemyRaycastDirection, EnemyRaycastDistance, LayerMask.GetMask("Default")))
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, GameObject.FindGameObjectWithTag("Player").transform.position) <= WeaponRange)
|
||||
{// Line of sight
|
||||
// Check to see if the enemy is within range of the player to begin shooting
|
||||
//if (Vector2.Distance(rb.position, GameObject.FindGameObjectWithTag("Player").transform.position) <= WeaponRange)
|
||||
//{
|
||||
if (WeaponCurrentMagazineAmmount > 0 && canFire == true && currentlyReloading == false)
|
||||
{
|
||||
UseWeapon();
|
||||
}
|
||||
else if (WeaponCurrentMagazineAmmount == 0 && currentlyReloading == false)
|
||||
{
|
||||
ReloadWeapon();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
canMove = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (canPursue == true)
|
||||
{
|
||||
//TARGET DETERMINATION
|
||||
// If the player moves away (CHANGE TARGET)
|
||||
|
||||
ComputeClosestPositionToPlayer();
|
||||
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) > WeaponRange - 2)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
canFire = false;
|
||||
canMove = true;
|
||||
}
|
||||
|
||||
//If the player is too close to the target (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) < MinumumDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
canFire = false;
|
||||
canMove = true;
|
||||
}
|
||||
|
||||
//If the target is on the other side of the player
|
||||
if (Vector2.Distance(transform.position, player.transform.position) < Vector2.Distance(target.transform.position, player.transform.position))
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
canFire = false;
|
||||
canMove = true;
|
||||
}
|
||||
|
||||
// If the player is not within line of sight of the desired position (CHANGE TARGET)
|
||||
if (DetermineLineOfSight(target, player) == false)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
canFire = false;
|
||||
canMove = true;
|
||||
}
|
||||
|
||||
// If the enemy reaches the target
|
||||
if (Vector2.Distance(target.transform.position, transform.position) < 0.5)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player)) // If the enemy has LOS on the player
|
||||
{
|
||||
if (currentlyReloading == false)
|
||||
{
|
||||
canFire = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
canFire = false;
|
||||
canMove = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If the enemy has not yet reached the target
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Perform jump
|
||||
async Task JumpMethod()
|
||||
{
|
||||
if (canJump == true)
|
||||
{
|
||||
|
||||
rb.linearVelocity = new Vector2(rb.linearVelocity.x, JumpHeight * 12);
|
||||
canJump = false;
|
||||
await Task.Delay(500);
|
||||
}
|
||||
}
|
||||
// Use Ranged Weapon
|
||||
async Task UseWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
canMove = false;
|
||||
|
||||
// Create Projectile
|
||||
GameObject BulletInstance;
|
||||
BulletInstance = Instantiate(projectile, transform.position, Quaternion.LookRotation(transform.position - GameObject.FindGameObjectWithTag("Player").transform.position + new Vector3(UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), 0)));
|
||||
BulletInstance.transform.parent = transform;
|
||||
|
||||
// Generate random angle
|
||||
//Vector2 ExitVelocity = BulletInstance.transform.forward * new Vector2(UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread)).normalized;
|
||||
|
||||
// Send it on it's way
|
||||
BulletInstance.GetComponent<Rigidbody2D>().linearVelocity = BulletInstance.transform.forward * -1 * WeaponProjectileSpeed;
|
||||
BulletInstance.transform.rotation = Quaternion.Euler(new Vector3(0, 0, Vector2.SignedAngle(Vector2.right, BulletInstance.transform.forward) - 90));
|
||||
WeaponCurrentMagazineAmmount--;
|
||||
|
||||
await Task.Delay(WeaponFireRate);
|
||||
|
||||
canFire = true;
|
||||
canMove = true;
|
||||
}
|
||||
|
||||
// Reload Weapon
|
||||
async Task ReloadWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
canMove = false;
|
||||
//play reload animation
|
||||
currentlyReloading = true;
|
||||
await Task.Delay(WeaponReloadTime);
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
currentlyReloading = false;
|
||||
canFire = true;
|
||||
canMove = true;
|
||||
}
|
||||
|
||||
// Part of the patrol cycle
|
||||
async Task MoveNextPatrol()
|
||||
{
|
||||
canPursue = false;
|
||||
currentlyMovingToNextPatrolTarget = true;
|
||||
|
||||
//Find Random Position nearby
|
||||
if (Vector2.Distance(transform.position, target.transform.position) <= 0.5 || PatrolPositions.Contains(target) == false)
|
||||
{
|
||||
target = PatrolPositions[UnityEngine.Random.Range(0, PatrolPositions.Count)];
|
||||
await Task.Delay(PatrolStallTime);
|
||||
}
|
||||
|
||||
canPursue = true;
|
||||
currentlyMovingToNextPatrolTarget = false;
|
||||
|
||||
}
|
||||
|
||||
// General Utility
|
||||
bool DetermineLineOfSight(GameObject object1, GameObject object2)
|
||||
{
|
||||
Vector3 RaycastStart = object1.transform.position;
|
||||
Vector3 RaycastDirection = (object2.transform.position - object1.transform.position).normalized;
|
||||
float RaycastDistance = Vector3.Distance(object2.transform.position, object1.transform.position);
|
||||
|
||||
if (Physics2D.Raycast(RaycastStart, RaycastDirection, RaycastDistance, LayerMask.GetMask("Default")) == false)
|
||||
{
|
||||
Debug.DrawRay(RaycastStart, RaycastDirection * RaycastDistance);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Partof the pursue cycle
|
||||
void ComputeClosestPositionToPlayer()
|
||||
{
|
||||
SuitablePositions.Clear();
|
||||
foreach (GameObject query in AllPositions)
|
||||
{
|
||||
// Check the distance of the position
|
||||
if (Vector2.Distance(query.transform.position, player.transform.position) <= DesiredDistance && Vector2.Distance(query.transform.position, player.transform.position) >= MinumumDistance)
|
||||
{
|
||||
// Check line of sight of the position
|
||||
|
||||
if (DetermineLineOfSight(query, player))
|
||||
{
|
||||
SuitablePositions.Add(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (SuitablePositions.Count > 0)
|
||||
{
|
||||
target = SuitablePositions[UnityEngine.Random.Range(0, SuitablePositions.Count)];
|
||||
foreach (GameObject pos in SuitablePositions)
|
||||
{
|
||||
//Find the point that is closest to the enemy
|
||||
if (Vector2.Distance(transform.position, pos.transform.position) < Vector2.Distance(transform.position, target.transform.position))
|
||||
{
|
||||
if (Vector2.Distance(player.transform.position, pos.transform.position) < Vector2.Distance(player.transform.position, target.transform.position))
|
||||
{
|
||||
target = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1f4adc563b83f549b3b3fc9eeb38e9a
|
||||
@@ -0,0 +1 @@
|
||||
uid://bmvsmx2cghvjx
|
||||
@@ -0,0 +1,190 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Pathfinding;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
//Designed by Jacob Weedman
|
||||
//Use on Flamethrower turrets
|
||||
|
||||
public class FlameTurretAI : MonoBehaviour
|
||||
{
|
||||
|
||||
//MISC
|
||||
GameObject projectile;
|
||||
GameObject player;
|
||||
GameObject barrel;
|
||||
float DistanceFromPlayer;
|
||||
Vector2 StartPosition;
|
||||
|
||||
//CONDITIONS/GENERAL INFORMATION
|
||||
bool canFire = true;
|
||||
bool currentlyReloading = false;
|
||||
bool targetingPlayer = false;
|
||||
bool inFiringCycle = false;
|
||||
int WeaponCurrentMagazineAmmount;
|
||||
float angle;
|
||||
|
||||
//ENEMY STATS (Changeable)
|
||||
public float Health = 100;
|
||||
public int PlayerDetectionRange = 40;
|
||||
|
||||
//WEAPON STATS (CHANGEABLE)
|
||||
public int WeaponDamage = 1; // Damage per hit
|
||||
public int WeaponFireRate = 50; // Delay in time between attacks both melee and ranged
|
||||
public float WeaponRandomSpread = 2.5f; // Random direction of lanched projectiles
|
||||
public int WeaponRange = 20; // Maximum range of the projectile before it drops off
|
||||
public float WeaponProjectileSpeed = 20f; // Speed of launched projectiles
|
||||
public int WeaponMagazineSize = 80; // Number of shots the enemy will take before having to reload
|
||||
public int WeaponReloadTime = 10000; // Time it takes to reload the magazine
|
||||
|
||||
//ONCE THE GAME STARTS
|
||||
void Start()
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
|
||||
projectile = GameObject.Find("EnemyProjectile");
|
||||
player = GameObject.FindGameObjectWithTag("Player");
|
||||
barrel = transform.Find("Barrel").gameObject;
|
||||
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
|
||||
canFire = true;
|
||||
}
|
||||
|
||||
//MAIN LOGIC
|
||||
void FixedUpdate()
|
||||
{
|
||||
DistanceFromPlayer = Vector2.Distance(transform.position, player.transform.position);
|
||||
|
||||
// Death
|
||||
if (Health <= 0)
|
||||
{
|
||||
GameObject DeadBody;
|
||||
DeadBody = Instantiate(gameObject, transform.position, Quaternion.identity);
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<TestEnemyAIGroundRanged>());
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<Seeker>());
|
||||
|
||||
foreach (Transform child in DeadBody.transform) {
|
||||
GameObject.Destroy(child.gameObject);
|
||||
}
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
|
||||
//ICONS
|
||||
if (targetingPlayer)
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
if (currentlyReloading)
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
//RANGED ATTACK
|
||||
// Check if enemy has line of sight on the player & if they are in the acceptable range
|
||||
if (WeaponCurrentMagazineAmmount > 0 && canFire == true && currentlyReloading == false)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= WeaponRange)
|
||||
{
|
||||
UseWeapon();
|
||||
}
|
||||
}
|
||||
else if (WeaponCurrentMagazineAmmount == 0 && currentlyReloading == false)
|
||||
{
|
||||
ReloadWeapon();
|
||||
}
|
||||
|
||||
//PLAYER TARGETING
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= WeaponRange + 10)
|
||||
{
|
||||
targetingPlayer = true;
|
||||
|
||||
// Set angle to player
|
||||
angle = Mathf.Atan2(player.transform.position.y - barrel.transform.position.y, player.transform.position.x - barrel.transform.position.x) * Mathf.Rad2Deg;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reset barrel rotation
|
||||
angle = 90f;
|
||||
}
|
||||
|
||||
// Rotate barrel towards player
|
||||
Quaternion targetRotation = Quaternion.Euler(new Vector3(0, 0, angle));
|
||||
barrel.transform.rotation = Quaternion.RotateTowards(barrel.transform.rotation, targetRotation, 100 * Time.deltaTime);
|
||||
|
||||
}
|
||||
|
||||
//MISC METHODS
|
||||
// Use Ranged Weapon
|
||||
async Task UseWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
inFiringCycle = true;
|
||||
|
||||
GameObject Flame;
|
||||
Flame = Instantiate(GameObject.Find("Flame"), new Vector3(transform.position.x + UnityEngine.Random.Range(-0.5f, 0.5f), transform.position.y + UnityEngine.Random.Range(-0.5f, 0.5f), GameObject.Find("EvilAura").transform.position.z), Quaternion.identity);
|
||||
Flame.transform.rotation = Quaternion.Euler(Vector3.forward * UnityEngine.Random.Range(-90, 90));
|
||||
|
||||
// Send it on its way
|
||||
Flame.GetComponent<Rigidbody2D>().linearVelocity = new Vector2(transform.position.x - player.transform.position.x + UnityEngine.Random.Range(-WeaponRandomSpread, WeaponRandomSpread), transform.position.y - player.transform.position.y + UnityEngine.Random.Range(-WeaponRandomSpread, WeaponRandomSpread)).normalized * 3 * WeaponRange * -1;
|
||||
|
||||
//Set Variables
|
||||
Flame.GetComponent<EnemyParticleWeapon>().destroy = true;
|
||||
Flame.GetComponent<EnemyParticleWeapon>().opacity = true;
|
||||
Flame.GetComponent<EnemyParticleWeapon>().timer = 3;
|
||||
Flame.GetComponent<EnemyParticleWeapon>().destroyOnCollide= true;
|
||||
Flame.GetComponent<EnemyParticleWeapon>().damageAmmount = WeaponDamage;
|
||||
|
||||
await Task.Delay(WeaponFireRate);
|
||||
|
||||
canFire = true;
|
||||
inFiringCycle = false;
|
||||
}
|
||||
|
||||
// Reload Weapon
|
||||
async Task ReloadWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
//play reload animation
|
||||
currentlyReloading = true;
|
||||
await Task.Delay(WeaponReloadTime);
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
currentlyReloading = false;
|
||||
|
||||
canFire = true;
|
||||
}
|
||||
|
||||
// General Utility
|
||||
bool DetermineLineOfSight(GameObject object1, GameObject object2)
|
||||
{
|
||||
Vector3 RaycastStart = object1.transform.position;
|
||||
Vector3 RaycastDirection = (object2.transform.position - object1.transform.position).normalized;
|
||||
float RaycastDistance = Vector3.Distance(object2.transform.position, object1.transform.position);
|
||||
|
||||
if (Physics2D.Raycast(RaycastStart, RaycastDirection, RaycastDistance, LayerMask.GetMask("SolidGround")) == false)
|
||||
{
|
||||
Debug.DrawRay(RaycastStart, RaycastDirection * RaycastDistance);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1b9c0ca6eef46ec49b42f46141997c46
|
||||
@@ -0,0 +1 @@
|
||||
uid://cw5sup7xiuq2s
|
||||
@@ -0,0 +1,473 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Pathfinding;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
//Designed by Jacob Weedman
|
||||
//Use on Flamethrower type enemies
|
||||
|
||||
public class FlamethrowerAI : MonoBehaviour
|
||||
{
|
||||
|
||||
//MISC
|
||||
GameObject target;
|
||||
float nextWaypointDistance = 5;
|
||||
Path path;
|
||||
int currentWaypoint = 0;
|
||||
bool reachedEndOfPath = false;
|
||||
GameObject projectile;
|
||||
GameObject player;
|
||||
GameObject barrel;
|
||||
public List<GameObject> SuitablePositions;
|
||||
public List<GameObject> AllPositions;
|
||||
float DistanceFromPlayer;
|
||||
Vector2 StartPosition;
|
||||
public List<GameObject> PatrolPositions;
|
||||
public GameObject LastKnownPlayerLocation;
|
||||
|
||||
//CONDITIONS/GENERAL INFORMATION
|
||||
bool canJump = true;
|
||||
bool canMove = true; // Prevent all movement
|
||||
bool canPursue = false; // Follow player
|
||||
bool canFire = false;
|
||||
bool currentlyReloading = false;
|
||||
bool currentlyPatrolling;
|
||||
bool currentlyMovingToNextPatrolTarget = false;
|
||||
float DesiredDistance;
|
||||
float MinumumDistance = 5f;
|
||||
bool targetingPlayer = false;
|
||||
bool inFiringCycle = false;
|
||||
int WeaponCurrentMagazineAmmount;
|
||||
float angle;
|
||||
|
||||
//ENEMY STATS (Changeable)
|
||||
public float Speed = 1.3f; // In relation to player's walking speed
|
||||
public float JumpHeight = 1.3f; // In relation to player's regular jump height
|
||||
public float Health = 30;
|
||||
public float PatrolDistance = 15;
|
||||
public int PatrolStallTime = 2000; //ms
|
||||
public int PlayerDetectionRange = 20;
|
||||
|
||||
//WEAPON STATS (CHANGEABLE)
|
||||
public int WeaponDamage = 1; // Damage per hit
|
||||
public int WeaponFireRate = 50; // Delay in time between attacks both melee and ranged
|
||||
public float WeaponRandomSpread = 3f; // Random direction of lanched projectiles
|
||||
public int WeaponRange = 15; // Maximum range of the projectile before it drops off
|
||||
public int WeaponMagazineSize = 200; // Number of shots the enemy will take before having to reload
|
||||
public int WeaponReloadTime = 5000; // Time it takes to reload the magazine
|
||||
|
||||
//REFERENCES
|
||||
Seeker seeker;
|
||||
Rigidbody2D rb;
|
||||
|
||||
//ONCE THE GAME STARTS
|
||||
void Start()
|
||||
{
|
||||
seeker = GetComponent<Seeker>();
|
||||
rb = GetComponent<Rigidbody2D>();
|
||||
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
|
||||
StartPosition = transform.position;
|
||||
AllPositions = GameObject.FindGameObjectsWithTag("PossiblePositions").ToList();
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
if (Vector2.Distance(pos.transform.position, StartPosition) <= PatrolDistance)
|
||||
{
|
||||
PatrolPositions.Add(pos);
|
||||
}
|
||||
}
|
||||
target = PatrolPositions[UnityEngine.Random.Range(0, PatrolPositions.Count)];
|
||||
|
||||
projectile = GameObject.Find("EnemyProjectile");
|
||||
player = GameObject.FindGameObjectWithTag("Player");
|
||||
barrel = transform.Find("Barrel").gameObject;
|
||||
LastKnownPlayerLocation = null;
|
||||
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
DesiredDistance = WeaponRange - 5;
|
||||
|
||||
InvokeRepeating("UpdatePath", 0f, 0.1f);
|
||||
InvokeRepeating("PathfindingTimeout", 0f, 10);
|
||||
|
||||
}
|
||||
|
||||
// When enemy has reached the next node
|
||||
void OnPathComplete(Path p)
|
||||
{
|
||||
if (!p.error)
|
||||
{
|
||||
path = p;
|
||||
currentWaypoint = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Select next node
|
||||
void UpdatePath()
|
||||
{
|
||||
if (seeker.IsDone() && target != null)
|
||||
{
|
||||
seeker.StartPath(rb.position, target.transform.position, OnPathComplete);
|
||||
}
|
||||
}
|
||||
|
||||
// Pathfiniding Timeout
|
||||
void PathfindingTimeout()
|
||||
{
|
||||
if (Vector2.Distance(transform.position, target.transform.position) > 0.5)
|
||||
{
|
||||
target = gameObject;
|
||||
MoveNextPatrol();
|
||||
}
|
||||
}
|
||||
|
||||
//MAIN LOGIC
|
||||
void FixedUpdate()
|
||||
{
|
||||
//DEATH
|
||||
if (Health <= 0)
|
||||
{
|
||||
GameObject DeadBody;
|
||||
DeadBody = Instantiate(gameObject, transform.position, Quaternion.identity);
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<TestEnemyAIGroundRanged>());
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<Seeker>());
|
||||
|
||||
foreach (Transform child in DeadBody.transform) {
|
||||
GameObject.Destroy(child.gameObject);
|
||||
}
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
//ICONS
|
||||
if (targetingPlayer)
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
if (currentlyReloading)
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
//if (90 <= angle || angle <= 270)
|
||||
//{
|
||||
// barrel.transform.localScale = new Vector2(-barrel.transform.localScale.x, barrel.transform.localScale.y);
|
||||
//}
|
||||
|
||||
//MISC PATHFINDING
|
||||
if (path == null)
|
||||
return;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
if (currentWaypoint >= path.vectorPath.Count)
|
||||
{
|
||||
reachedEndOfPath = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
reachedEndOfPath = false;
|
||||
}
|
||||
|
||||
//RANGED ATTACK
|
||||
// Check if enemy has line of sight on the player & if they are in the acceptable range
|
||||
if (WeaponCurrentMagazineAmmount > 0 && canFire == true && currentlyReloading == false && currentlyPatrolling == false)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= WeaponRange && Vector2.Distance(transform.position, player.transform.position) <= DesiredDistance)
|
||||
{
|
||||
UseWeapon();
|
||||
}
|
||||
}
|
||||
else if (WeaponCurrentMagazineAmmount == 0 && currentlyReloading == false)
|
||||
{
|
||||
ReloadWeapon();
|
||||
}
|
||||
|
||||
//MOVEMENT
|
||||
if (canMove == true)
|
||||
{
|
||||
canFire = false;
|
||||
|
||||
Vector2 direction = (Vector2)path.vectorPath[currentWaypoint] - rb.position;
|
||||
|
||||
if (direction.x > 0) // Move right
|
||||
{
|
||||
rb.AddForce(new Vector2(Speed * 20, rb.linearVelocity.y));
|
||||
}
|
||||
|
||||
if (direction.x < 0) // Move left
|
||||
{
|
||||
rb.AddForce(new Vector2(-1 * Speed * 20, rb.linearVelocity.y));
|
||||
}
|
||||
|
||||
if (direction.y > 1f) // Wants to jump
|
||||
{
|
||||
JumpMethod();
|
||||
}
|
||||
|
||||
// A* logic
|
||||
float distance = Vector2.Distance(rb.position, path.vectorPath[currentWaypoint]);
|
||||
|
||||
if (distance < nextWaypointDistance)
|
||||
{
|
||||
currentWaypoint++;
|
||||
}
|
||||
}
|
||||
|
||||
//GROUND DETECTION
|
||||
//Detecting if the enemy has reached the ground
|
||||
if (GetComponentInChildren<GroundCheck>().isGrounded == true && rb.linearVelocity.y == 0)
|
||||
{
|
||||
canJump = true;
|
||||
if (inFiringCycle == false)
|
||||
{
|
||||
canFire = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
canJump = false;
|
||||
canFire = false;
|
||||
}
|
||||
|
||||
//PATROL & DETECTION
|
||||
// Enemy detects player
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= PlayerDetectionRange)
|
||||
{
|
||||
currentlyPatrolling = false;
|
||||
targetingPlayer = true;
|
||||
canPursue = true;
|
||||
// Get the last know player location by finding which of the positions is closest to the player
|
||||
if (LastKnownPlayerLocation == null)
|
||||
{
|
||||
LastKnownPlayerLocation = gameObject;
|
||||
}
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
if (Vector2.Distance(player.transform.position, pos.transform.position) < Vector2.Distance(player.transform.position, LastKnownPlayerLocation.transform.position))
|
||||
{
|
||||
LastKnownPlayerLocation = pos;
|
||||
}
|
||||
}
|
||||
|
||||
// Angle barrel towards player
|
||||
angle = Mathf.Atan2(player.transform.position.y - barrel.transform.position.y, player.transform.position.x - barrel.transform.position.x) * Mathf.Rad2Deg;
|
||||
|
||||
}
|
||||
// Player has broken line of sight and the enemy will attempt to move to the last known location
|
||||
else if (LastKnownPlayerLocation != null)
|
||||
{
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) > 0.5)
|
||||
{
|
||||
canPursue = false;
|
||||
target = LastKnownPlayerLocation;
|
||||
}
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) < 0.5 && DetermineLineOfSight(player, gameObject) == false)
|
||||
{
|
||||
targetingPlayer = false;
|
||||
LastKnownPlayerLocation = null;
|
||||
}
|
||||
|
||||
// Reset barrel rotation
|
||||
angle = 0f;
|
||||
|
||||
}
|
||||
// Go back to patrol move
|
||||
else
|
||||
{
|
||||
currentlyPatrolling = true;
|
||||
targetingPlayer = false;
|
||||
if (canMove == true && currentlyMovingToNextPatrolTarget == false)
|
||||
{
|
||||
MoveNextPatrol();
|
||||
}
|
||||
|
||||
// Reset barrel rotation
|
||||
angle = 0f;
|
||||
}
|
||||
|
||||
// Rotate barrel towards player
|
||||
Quaternion targetRotation = Quaternion.Euler(new Vector3(0, 0, angle));
|
||||
barrel.transform.rotation = Quaternion.RotateTowards(barrel.transform.rotation, targetRotation, 200 * Time.deltaTime);
|
||||
|
||||
//TARGET DETERMINATION
|
||||
if (canPursue == true)
|
||||
{
|
||||
// Catch desired distance error
|
||||
if (DesiredDistance <= MinumumDistance)
|
||||
{
|
||||
DesiredDistance = MinumumDistance + 1;
|
||||
}
|
||||
|
||||
// If the player moves away (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) > DesiredDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the player is too close to the target (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) < MinumumDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the target is on the other side of the player (CHANGE TARGET)
|
||||
if (Vector2.Distance(transform.position, player.transform.position) < Vector2.Distance(target.transform.position, player.transform.position))
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the player is not within line of sight of the desired position (CHANGE TARGET)
|
||||
if (DetermineLineOfSight(target, player) == false)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the enemy reaches the target
|
||||
if (Vector2.Distance(target.transform.position, transform.position) <= 1 && DetermineLineOfSight(gameObject, player))
|
||||
{
|
||||
if (canFire == false && inFiringCycle == false)
|
||||
{
|
||||
canFire = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MISC METHODS
|
||||
// Perform jump
|
||||
async Task JumpMethod()
|
||||
{
|
||||
if (canJump == true)
|
||||
{
|
||||
rb.linearVelocity = new Vector2(rb.linearVelocity.x, JumpHeight * 12);
|
||||
canJump = false;
|
||||
await Task.Delay(500);
|
||||
}
|
||||
}
|
||||
// Use Ranged Weapon
|
||||
async Task UseWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
inFiringCycle = true;
|
||||
|
||||
GameObject Flame;
|
||||
Flame = Instantiate(GameObject.Find("Flame"), new Vector3(transform.position.x + UnityEngine.Random.Range(-0.5f, 0.5f), transform.position.y + UnityEngine.Random.Range(-0.5f, 0.5f), GameObject.Find("EvilAura").transform.position.z), Quaternion.identity);
|
||||
Flame.transform.rotation = Quaternion.Euler(Vector3.forward * UnityEngine.Random.Range(-90, 90));
|
||||
|
||||
// Send it on its way
|
||||
Flame.GetComponent<Rigidbody2D>().linearVelocity = new Vector2(transform.position.x - player.transform.position.x + UnityEngine.Random.Range(-WeaponRandomSpread, WeaponRandomSpread), transform.position.y - player.transform.position.y + UnityEngine.Random.Range(-WeaponRandomSpread, WeaponRandomSpread)).normalized * 1.25f * WeaponRange * -1;
|
||||
|
||||
//Set Variables
|
||||
Flame.GetComponent<EnemyParticleWeapon>().destroy = true;
|
||||
Flame.GetComponent<EnemyParticleWeapon>().opacity = true;
|
||||
Flame.GetComponent<EnemyParticleWeapon>().destroyOnCollide= true;
|
||||
Flame.GetComponent<EnemyParticleWeapon>().damageAmmount = WeaponDamage;
|
||||
|
||||
await Task.Delay(WeaponFireRate);
|
||||
|
||||
canFire = true;
|
||||
inFiringCycle = false;
|
||||
}
|
||||
|
||||
// Reload Weapon
|
||||
async Task ReloadWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
canMove = false;
|
||||
//play reload animation
|
||||
currentlyReloading = true;
|
||||
await Task.Delay(WeaponReloadTime);
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
currentlyReloading = false;
|
||||
|
||||
canFire = true;
|
||||
canMove = true;
|
||||
}
|
||||
|
||||
// Part of the patrol cycle
|
||||
async Task MoveNextPatrol()
|
||||
{
|
||||
LastKnownPlayerLocation = null;
|
||||
DesiredDistance = WeaponRange - 5;
|
||||
|
||||
canPursue = false;
|
||||
currentlyMovingToNextPatrolTarget = true;
|
||||
|
||||
//Find Random Position nearby
|
||||
if (Vector2.Distance(transform.position, target.transform.position) <= 0.5 || PatrolPositions.Contains(target) == false)
|
||||
{
|
||||
target = PatrolPositions[UnityEngine.Random.Range(0, PatrolPositions.Count)];
|
||||
await Task.Delay(PatrolStallTime + UnityEngine.Random.Range((PatrolStallTime * -1), PatrolStallTime));
|
||||
}
|
||||
|
||||
currentlyMovingToNextPatrolTarget = false;
|
||||
|
||||
}
|
||||
|
||||
// General Utility
|
||||
bool DetermineLineOfSight(GameObject object1, GameObject object2)
|
||||
{
|
||||
Vector3 RaycastStart = object1.transform.position;
|
||||
Vector3 RaycastDirection = (object2.transform.position - object1.transform.position).normalized;
|
||||
float RaycastDistance = Vector3.Distance(object2.transform.position, object1.transform.position);
|
||||
|
||||
if (Physics2D.Raycast(RaycastStart, RaycastDirection, RaycastDistance, LayerMask.GetMask("SolidGround")) == false)
|
||||
{
|
||||
Debug.DrawRay(RaycastStart, RaycastDirection * RaycastDistance);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Part of the pursue cycle
|
||||
void ComputeClosestPositionToPlayer()
|
||||
{
|
||||
canFire = false;
|
||||
canMove = true;
|
||||
|
||||
SuitablePositions.Clear();
|
||||
foreach (GameObject query in AllPositions)
|
||||
{
|
||||
// Check the distance of the position
|
||||
if (Vector2.Distance(query.transform.position, player.transform.position) < DesiredDistance && Vector2.Distance(query.transform.position, player.transform.position) >= MinumumDistance)
|
||||
{
|
||||
// Check line of sight of the position
|
||||
if (DetermineLineOfSight(query, player))
|
||||
{
|
||||
SuitablePositions.Add(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (SuitablePositions.Count > 0)
|
||||
{
|
||||
target = SuitablePositions[UnityEngine.Random.Range(0, SuitablePositions.Count)];
|
||||
foreach (GameObject pos in SuitablePositions)
|
||||
{
|
||||
//Find the point that is closest to the enemy
|
||||
if (Vector2.Distance(transform.position, pos.transform.position) < Vector2.Distance(transform.position, target.transform.position))
|
||||
{
|
||||
target = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9a8b83d0c517ff040ac4a88b082b85ec
|
||||
@@ -0,0 +1 @@
|
||||
uid://dfgtcghp4yxk4
|
||||
@@ -0,0 +1,480 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Pathfinding;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
//Designed by Jacob Weedman
|
||||
//Use on Grunt type enemies
|
||||
|
||||
public class GruntAI : MonoBehaviour
|
||||
{
|
||||
|
||||
//MISC
|
||||
GameObject target;
|
||||
float nextWaypointDistance = 5;
|
||||
Path path;
|
||||
int currentWaypoint = 0;
|
||||
bool reachedEndOfPath = false;
|
||||
GameObject projectile;
|
||||
GameObject player;
|
||||
GameObject barrel;
|
||||
public List<GameObject> SuitablePositions;
|
||||
public List<GameObject> AllPositions;
|
||||
float DistanceFromPlayer;
|
||||
Vector2 StartPosition;
|
||||
public List<GameObject> PatrolPositions;
|
||||
public GameObject LastKnownPlayerLocation;
|
||||
|
||||
//CONDITIONS/GENERAL INFORMATION
|
||||
bool canJump = true;
|
||||
bool canMove = true; // Prevent all movement
|
||||
bool canPursue = false; // Follow player
|
||||
bool canFire = false;
|
||||
bool currentlyReloading = false;
|
||||
bool currentlyPatrolling;
|
||||
bool currentlyMovingToNextPatrolTarget = false;
|
||||
float DesiredDistance;
|
||||
float MinumumDistance = 5f;
|
||||
bool targetingPlayer = false;
|
||||
bool inFiringCycle = false;
|
||||
int WeaponCurrentMagazineAmmount;
|
||||
public int NumberOfHitsPerMag = 0;
|
||||
float angle;
|
||||
|
||||
//ENEMY STATS (Changeable)
|
||||
public float Speed = 1.3f; // In relation to player's walking speed
|
||||
public float JumpHeight = 1.3f; // In relation to player's regular jump height
|
||||
public float Health = 30;
|
||||
public float PatrolDistance = 15;
|
||||
public int PatrolStallTime = 2000; //ms
|
||||
public int PlayerDetectionRange = 20;
|
||||
|
||||
//WEAPON STATS (CHANGEABLE)
|
||||
public int WeaponDamage = 1; // Damage per hit
|
||||
public int WeaponFireRate = 100; // Delay in time between attacks both melee and ranged
|
||||
public float WeaponRandomSpread = 7.5f; // Random direction of lanched projectiles
|
||||
public int WeaponRange = 15; // Maximum range of the projectile before it drops off
|
||||
public float WeaponProjectileSpeed = 40f; // Speed of launched projectiles
|
||||
public int WeaponMagazineSize = 20; // Number of shots the enemy will take before having to reload
|
||||
public int WeaponReloadTime = 3000; // Time it takes to reload the magazine
|
||||
|
||||
//REFERENCES
|
||||
Seeker seeker;
|
||||
Rigidbody2D rb;
|
||||
|
||||
//ONCE THE GAME STARTS
|
||||
void Start()
|
||||
{
|
||||
seeker = GetComponent<Seeker>();
|
||||
rb = GetComponent<Rigidbody2D>();
|
||||
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
|
||||
StartPosition = transform.position;
|
||||
AllPositions = GameObject.FindGameObjectsWithTag("PossiblePositions").ToList();
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
if (Vector2.Distance(pos.transform.position, StartPosition) <= PatrolDistance)
|
||||
{
|
||||
PatrolPositions.Add(pos);
|
||||
}
|
||||
}
|
||||
target = PatrolPositions[UnityEngine.Random.Range(0, PatrolPositions.Count)];
|
||||
|
||||
projectile = GameObject.Find("EnemyProjectile");
|
||||
player = GameObject.FindGameObjectWithTag("Player");
|
||||
barrel = transform.Find("Barrel").gameObject;
|
||||
LastKnownPlayerLocation = null;
|
||||
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
DesiredDistance = WeaponRange - 5;
|
||||
|
||||
InvokeRepeating("UpdatePath", 0f, 0.1f);
|
||||
InvokeRepeating("PathfindingTimeout", 0f, 10);
|
||||
|
||||
}
|
||||
|
||||
// When enemy has reached the next node
|
||||
void OnPathComplete(Path p)
|
||||
{
|
||||
if (!p.error)
|
||||
{
|
||||
path = p;
|
||||
currentWaypoint = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Select next node
|
||||
void UpdatePath()
|
||||
{
|
||||
if (seeker.IsDone() && target != null)
|
||||
{
|
||||
seeker.StartPath(rb.position, target.transform.position, OnPathComplete);
|
||||
}
|
||||
}
|
||||
|
||||
// Pathfiniding Timeout
|
||||
void PathfindingTimeout()
|
||||
{
|
||||
if (Vector2.Distance(transform.position, target.transform.position) > 0.5)
|
||||
{
|
||||
target = gameObject;
|
||||
MoveNextPatrol();
|
||||
}
|
||||
}
|
||||
|
||||
//MAIN LOGIC
|
||||
void FixedUpdate()
|
||||
{
|
||||
//DEATH
|
||||
if (Health <= 0)
|
||||
{
|
||||
GameObject DeadBody;
|
||||
DeadBody = Instantiate(gameObject, transform.position, Quaternion.identity);
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<TestEnemyAIGroundRanged>());
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<Seeker>());
|
||||
|
||||
foreach (Transform child in DeadBody.transform) {
|
||||
GameObject.Destroy(child.gameObject);
|
||||
}
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
//ICONS
|
||||
if (targetingPlayer)
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
if (currentlyReloading)
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
//if (90 <= angle || angle <= 270)
|
||||
//{
|
||||
// barrel.transform.localScale = new Vector2(-barrel.transform.localScale.x, barrel.transform.localScale.y);
|
||||
//}
|
||||
|
||||
//MISC PATHFINDING
|
||||
if (path == null)
|
||||
return;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
if (currentWaypoint >= path.vectorPath.Count)
|
||||
{
|
||||
reachedEndOfPath = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
reachedEndOfPath = false;
|
||||
}
|
||||
|
||||
//RANGED ATTACK
|
||||
// Check if enemy has line of sight on the player & if they are in the acceptable range
|
||||
if (WeaponCurrentMagazineAmmount > 0 && canFire == true && currentlyReloading == false && currentlyPatrolling == false)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= WeaponRange && Vector2.Distance(transform.position, player.transform.position) <= DesiredDistance)
|
||||
{
|
||||
UseWeapon();
|
||||
}
|
||||
}
|
||||
else if (WeaponCurrentMagazineAmmount == 0 && currentlyReloading == false)
|
||||
{
|
||||
ReloadWeapon();
|
||||
}
|
||||
|
||||
//MOVEMENT
|
||||
if (canMove == true)
|
||||
{
|
||||
canFire = false;
|
||||
|
||||
Vector2 direction = (Vector2)path.vectorPath[currentWaypoint] - rb.position;
|
||||
|
||||
if (direction.x > 0) // Move right
|
||||
{
|
||||
rb.AddForce(new Vector2(Speed * 20, rb.linearVelocity.y));
|
||||
}
|
||||
|
||||
if (direction.x < 0) // Move left
|
||||
{
|
||||
rb.AddForce(new Vector2(-1 * Speed * 20, rb.linearVelocity.y));
|
||||
}
|
||||
|
||||
if (direction.y > 1f) // Wants to jump
|
||||
{
|
||||
JumpMethod();
|
||||
}
|
||||
|
||||
// A* logic
|
||||
float distance = Vector2.Distance(rb.position, path.vectorPath[currentWaypoint]);
|
||||
|
||||
if (distance < nextWaypointDistance)
|
||||
{
|
||||
currentWaypoint++;
|
||||
}
|
||||
}
|
||||
|
||||
//GROUND DETECTION
|
||||
//Detecting if the enemy has reached the ground
|
||||
if (GetComponentInChildren<GroundCheck>().isGrounded == true && rb.linearVelocity.y == 0)
|
||||
{
|
||||
canJump = true;
|
||||
if (inFiringCycle == false)
|
||||
{
|
||||
canFire = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
canJump = false;
|
||||
canFire = false;
|
||||
}
|
||||
|
||||
//PATROL & DETECTION
|
||||
// Enemy detects player
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= PlayerDetectionRange)
|
||||
{
|
||||
currentlyPatrolling = false;
|
||||
targetingPlayer = true;
|
||||
canPursue = true;
|
||||
// Get the last know player location by finding which of the positions is closest to the player
|
||||
if (LastKnownPlayerLocation == null)
|
||||
{
|
||||
LastKnownPlayerLocation = gameObject;
|
||||
}
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
if (Vector2.Distance(player.transform.position, pos.transform.position) < Vector2.Distance(player.transform.position, LastKnownPlayerLocation.transform.position))
|
||||
{
|
||||
LastKnownPlayerLocation = pos;
|
||||
}
|
||||
}
|
||||
|
||||
// Angle barrel towards player
|
||||
angle = Mathf.Atan2(player.transform.position.y - barrel.transform.position.y, player.transform.position.x - barrel.transform.position.x) * Mathf.Rad2Deg;
|
||||
|
||||
}
|
||||
// Player has broken line of sight and the enemy will attempt to move to the last known location
|
||||
else if (LastKnownPlayerLocation != null)
|
||||
{
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) > 0.5)
|
||||
{
|
||||
canPursue = false;
|
||||
target = LastKnownPlayerLocation;
|
||||
}
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) < 0.5 && DetermineLineOfSight(player, gameObject) == false)
|
||||
{
|
||||
targetingPlayer = false;
|
||||
LastKnownPlayerLocation = null;
|
||||
}
|
||||
|
||||
// Reset barrel rotation
|
||||
angle = 0f;
|
||||
|
||||
}
|
||||
// Go back to patrol move
|
||||
else
|
||||
{
|
||||
currentlyPatrolling = true;
|
||||
targetingPlayer = false;
|
||||
if (canMove == true && currentlyMovingToNextPatrolTarget == false)
|
||||
{
|
||||
MoveNextPatrol();
|
||||
}
|
||||
|
||||
// Reset barrel rotation
|
||||
angle = 0f;
|
||||
}
|
||||
|
||||
// Rotate barrel towards player
|
||||
Quaternion targetRotation = Quaternion.Euler(new Vector3(0, 0, angle));
|
||||
barrel.transform.rotation = Quaternion.RotateTowards(barrel.transform.rotation, targetRotation, 200 * Time.deltaTime);
|
||||
|
||||
//TARGET DETERMINATION
|
||||
if (canPursue == true)
|
||||
{
|
||||
// Catch desired distance error
|
||||
if (DesiredDistance <= MinumumDistance)
|
||||
{
|
||||
DesiredDistance = MinumumDistance + 1;
|
||||
}
|
||||
|
||||
// If the player moves away (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) > DesiredDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the player is too close to the target (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) < MinumumDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the target is on the other side of the player (CHANGE TARGET)
|
||||
if (Vector2.Distance(transform.position, player.transform.position) < Vector2.Distance(target.transform.position, player.transform.position))
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the player is not within line of sight of the desired position (CHANGE TARGET)
|
||||
if (DetermineLineOfSight(target, player) == false)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the enemy reaches the target
|
||||
if (Vector2.Distance(target.transform.position, transform.position) <= 1 && DetermineLineOfSight(gameObject, player))
|
||||
{
|
||||
if (canFire == false && inFiringCycle == false)
|
||||
{
|
||||
canFire = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MISC METHODS
|
||||
// Perform jump
|
||||
async Task JumpMethod()
|
||||
{
|
||||
if (canJump == true)
|
||||
{
|
||||
rb.linearVelocity = new Vector2(rb.linearVelocity.x, JumpHeight * 12);
|
||||
canJump = false;
|
||||
await Task.Delay(500);
|
||||
}
|
||||
}
|
||||
// Use Ranged Weapon
|
||||
async Task UseWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
canMove = false;
|
||||
inFiringCycle = true;
|
||||
|
||||
// Create Projectile
|
||||
GameObject BulletInstance;
|
||||
BulletInstance = Instantiate(projectile, transform.position, Quaternion.LookRotation(transform.position - GameObject.FindGameObjectWithTag("Player").transform.position + new Vector3(UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), 0)));
|
||||
BulletInstance.transform.parent = transform;
|
||||
|
||||
// Send it on it's way
|
||||
BulletInstance.GetComponent<Rigidbody2D>().linearVelocity = BulletInstance.transform.forward * -1 * WeaponProjectileSpeed;
|
||||
BulletInstance.transform.rotation = Quaternion.Euler(new Vector3(0, 0, Vector2.SignedAngle(Vector2.right, BulletInstance.transform.forward) - 90));
|
||||
WeaponCurrentMagazineAmmount--;
|
||||
|
||||
await Task.Delay(WeaponFireRate);
|
||||
|
||||
canFire = true;
|
||||
canMove = true;
|
||||
inFiringCycle = false;
|
||||
}
|
||||
|
||||
// Reload Weapon
|
||||
async Task ReloadWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
canMove = false;
|
||||
//play reload animation
|
||||
currentlyReloading = true;
|
||||
await Task.Delay(WeaponReloadTime);
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
currentlyReloading = false;
|
||||
|
||||
if (NumberOfHitsPerMag / WeaponMagazineSize < 0.5)
|
||||
{
|
||||
DesiredDistance -= 5;
|
||||
}
|
||||
NumberOfHitsPerMag = 0;
|
||||
|
||||
canFire = true;
|
||||
canMove = true;
|
||||
}
|
||||
|
||||
// Part of the patrol cycle
|
||||
async Task MoveNextPatrol()
|
||||
{
|
||||
LastKnownPlayerLocation = null;
|
||||
DesiredDistance = WeaponRange - 5;
|
||||
|
||||
canPursue = false;
|
||||
currentlyMovingToNextPatrolTarget = true;
|
||||
|
||||
//Find Random Position nearby
|
||||
if (Vector2.Distance(transform.position, target.transform.position) <= 0.5 || PatrolPositions.Contains(target) == false)
|
||||
{
|
||||
target = PatrolPositions[UnityEngine.Random.Range(0, PatrolPositions.Count)];
|
||||
await Task.Delay(PatrolStallTime + UnityEngine.Random.Range((PatrolStallTime * -1), PatrolStallTime));
|
||||
}
|
||||
|
||||
currentlyMovingToNextPatrolTarget = false;
|
||||
|
||||
}
|
||||
|
||||
// General Utility
|
||||
bool DetermineLineOfSight(GameObject object1, GameObject object2)
|
||||
{
|
||||
Vector3 RaycastStart = object1.transform.position;
|
||||
Vector3 RaycastDirection = (object2.transform.position - object1.transform.position).normalized;
|
||||
float RaycastDistance = Vector3.Distance(object2.transform.position, object1.transform.position);
|
||||
|
||||
if (Physics2D.Raycast(RaycastStart, RaycastDirection, RaycastDistance, LayerMask.GetMask("SolidGround")) == false)
|
||||
{
|
||||
Debug.DrawRay(RaycastStart, RaycastDirection * RaycastDistance);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Part of the pursue cycle
|
||||
void ComputeClosestPositionToPlayer()
|
||||
{
|
||||
canFire = false;
|
||||
canMove = true;
|
||||
|
||||
SuitablePositions.Clear();
|
||||
foreach (GameObject query in AllPositions)
|
||||
{
|
||||
// Check the distance of the position
|
||||
if (Vector2.Distance(query.transform.position, player.transform.position) < DesiredDistance && Vector2.Distance(query.transform.position, player.transform.position) >= MinumumDistance)
|
||||
{
|
||||
// Check line of sight of the position
|
||||
if (DetermineLineOfSight(query, player))
|
||||
{
|
||||
SuitablePositions.Add(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (SuitablePositions.Count > 0)
|
||||
{
|
||||
target = SuitablePositions[UnityEngine.Random.Range(0, SuitablePositions.Count)];
|
||||
foreach (GameObject pos in SuitablePositions)
|
||||
{
|
||||
//Find the point that is closest to the enemy
|
||||
if (Vector2.Distance(transform.position, pos.transform.position) < Vector2.Distance(transform.position, target.transform.position))
|
||||
{
|
||||
target = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 81f1c8e2d50cae34da2edf9525005d91
|
||||
@@ -0,0 +1 @@
|
||||
uid://cf3cm245clgqf
|
||||
@@ -0,0 +1,468 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Pathfinding;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
//Designed by Jacob Weedman
|
||||
//Use on heavy drone type enemies
|
||||
|
||||
public class HeavyDroneAI : MonoBehaviour
|
||||
{
|
||||
//MISC
|
||||
GameObject target;
|
||||
float nextWaypointDistance = 5;
|
||||
Path path;
|
||||
int currentWaypoint = 0;
|
||||
bool reachedEndOfPath = false;
|
||||
GameObject projectile;
|
||||
GameObject player;
|
||||
GameObject barrel;
|
||||
public List<GameObject> AllPositions;
|
||||
float DistanceFromPlayer;
|
||||
Vector2 StartPosition;
|
||||
GameObject LastKnownPlayerLocation;
|
||||
Transform DroneBayLocation;
|
||||
|
||||
//CONDITIONS/GENERAL INFORMATION
|
||||
bool canMove = false;
|
||||
bool canPursue = false;
|
||||
bool canFire = true;
|
||||
bool currentlyReloading = false;
|
||||
bool currentlyInDroneBay = true;
|
||||
float DesiredDistance;
|
||||
float MinumumDistance = 10f;
|
||||
bool targetingPlayer = false;
|
||||
bool inFiringCycle = false;
|
||||
int WeaponCurrentMagazineAmmount;
|
||||
int CurrentBatteryCapacity;
|
||||
bool currentlyTravelingToDroneBay = false;
|
||||
bool currentlyRecharging = false;
|
||||
float angle;
|
||||
|
||||
//ENEMY STATS (Changeable)
|
||||
public float Speed = 0.7f; // In relation to player's walking speed
|
||||
public float Health = 200;
|
||||
public int PlayerDetectionRange = 25;
|
||||
public int MaxBatteryCapacity = 30; // Depletes one every second it is out of the bay
|
||||
public int RechargeTime = 6000; //ms
|
||||
|
||||
//WEAPON STATS (CHANGEABLE)
|
||||
public int WeaponDamage = 20; // Damage per hit
|
||||
public int WeaponFireRate = 2000; // Delay in time between attacks both melee and ranged
|
||||
public float WeaponRandomSpread = 5f; // Random direction of lanched projectiles (DOES NOT APPLY TO MELEE ATTACKS)
|
||||
public int WeaponRange = 30; // Maximum range of the projectile before it drops off (DOES NOT APPLY TO MELEE ATTACKS)
|
||||
public float WeaponProjectileSpeed = 30f; // Speed of launched projectiles (DOES NOT APPLY TO MELEE WEAPONS)
|
||||
public int WeaponMagazineSize = 12; // Number of shots the enemy will take before having to reload
|
||||
public int WeaponReloadTime = 5000; // Time it takes to reload the magazine
|
||||
|
||||
|
||||
Seeker seeker;
|
||||
Rigidbody2D rb;
|
||||
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
void Start()
|
||||
{
|
||||
seeker = GetComponent<Seeker>();
|
||||
rb = GetComponent<Rigidbody2D>();
|
||||
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
transform.Find("BatteryIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
|
||||
StartPosition = transform.position;
|
||||
AllPositions = GameObject.FindGameObjectsWithTag("PossiblePositions").ToList();
|
||||
|
||||
target = Instantiate(GameObject.Find("FlyingTarget"), transform.position, Quaternion.identity);
|
||||
|
||||
projectile = GameObject.Find("EnemyGrenade");
|
||||
player = GameObject.FindGameObjectWithTag("Player");
|
||||
barrel = transform.Find("Barrel").gameObject;
|
||||
LastKnownPlayerLocation = null;
|
||||
DroneBayLocation = transform.parent.transform;
|
||||
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
DesiredDistance = WeaponRange - 5;
|
||||
CurrentBatteryCapacity = MaxBatteryCapacity;
|
||||
|
||||
InvokeRepeating("UpdatePath", 0f, 0.1f);
|
||||
InvokeRepeating("PathfindingTimeout", 0f, 30);
|
||||
InvokeRepeating("BatteryDrain", 0f, 1);
|
||||
|
||||
Recharge();
|
||||
}
|
||||
|
||||
// When enemy has reached the next node
|
||||
void OnPathComplete(Path p)
|
||||
{
|
||||
if (!p.error)
|
||||
{
|
||||
path = p;
|
||||
currentWaypoint = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Select next node
|
||||
void UpdatePath()
|
||||
{
|
||||
if (seeker.IsDone())
|
||||
{
|
||||
seeker.StartPath(rb.position, target.transform.position, OnPathComplete);
|
||||
}
|
||||
}
|
||||
|
||||
// Pathfiniding Timeout
|
||||
void PathfindingTimeout()
|
||||
{
|
||||
if (Vector2.Distance(transform.position, target.transform.position) > 0.5)
|
||||
{
|
||||
//target = gameObject;
|
||||
targetingPlayer = false;
|
||||
LastKnownPlayerLocation = null;
|
||||
//ReturnToDroneBay();
|
||||
}
|
||||
}
|
||||
|
||||
// Battery Drain
|
||||
void BatteryDrain()
|
||||
{
|
||||
if (currentlyInDroneBay == false)
|
||||
{
|
||||
CurrentBatteryCapacity -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// MAIN LOGIC
|
||||
void FixedUpdate()
|
||||
{
|
||||
//DEATH
|
||||
if (CurrentBatteryCapacity == 0)
|
||||
{
|
||||
Health = 0;
|
||||
}
|
||||
|
||||
if (Health <= 0)
|
||||
{
|
||||
GameObject DeadBody;
|
||||
DeadBody = Instantiate(gameObject, transform.position, Quaternion.identity);
|
||||
DeadBody.GetComponent<Rigidbody2D>().gravityScale = 3;
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<LightDroneAI>());
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<Seeker>());
|
||||
|
||||
foreach (Transform child in DeadBody.transform)
|
||||
{
|
||||
GameObject.Destroy(child.gameObject);
|
||||
}
|
||||
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
//Battery
|
||||
// Low battery return
|
||||
if (CurrentBatteryCapacity <= 10)
|
||||
{
|
||||
ReturnToDroneBay();
|
||||
}
|
||||
|
||||
// Recharge battery & bay detection
|
||||
if (Vector2.Distance(transform.position, DroneBayLocation.position) <= 0.5)
|
||||
{
|
||||
currentlyInDroneBay = true;
|
||||
currentlyTravelingToDroneBay = false;
|
||||
if (currentlyRecharging == false && CurrentBatteryCapacity < 10)
|
||||
{
|
||||
Recharge();
|
||||
}
|
||||
else
|
||||
{
|
||||
canMove = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//canMove = true;
|
||||
currentlyInDroneBay = false;
|
||||
}
|
||||
|
||||
//CHANGE ICONS
|
||||
if (targetingPlayer)
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
if (currentlyReloading)
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
if (CurrentBatteryCapacity <= MaxBatteryCapacity / 10)
|
||||
{
|
||||
transform.Find("BatteryIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("BatteryIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
if (currentlyInDroneBay == false)
|
||||
{
|
||||
seeker.enabled = true;
|
||||
}
|
||||
|
||||
//MISC PATHFINDING
|
||||
if (path == null)
|
||||
return;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
if (currentWaypoint >= path.vectorPath.Count)
|
||||
{
|
||||
reachedEndOfPath = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
reachedEndOfPath = false;
|
||||
}
|
||||
|
||||
//RANGED ATTACK
|
||||
// Check if enemy has line of sight on the player & if they are in the acceptable range
|
||||
|
||||
if (WeaponCurrentMagazineAmmount > 0 && canFire == true && currentlyReloading == false && currentlyInDroneBay == false)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == true && inFiringCycle == false && Vector2.Distance(transform.position, player.transform.position) <= WeaponRange && Vector2.Distance(transform.position, player.transform.position) <= DesiredDistance)
|
||||
{
|
||||
UseWeapon();
|
||||
}
|
||||
}
|
||||
else if (WeaponCurrentMagazineAmmount == 0 && currentlyReloading == false)
|
||||
{
|
||||
ReloadWeapon();
|
||||
}
|
||||
|
||||
//MOVEMENT
|
||||
if (canMove == true)
|
||||
{
|
||||
Vector2 direction = ((Vector2)path.vectorPath[currentWaypoint] - rb.position).normalized;
|
||||
Vector2 force = direction * Speed * 20;
|
||||
|
||||
rb.AddForce(force);
|
||||
|
||||
// A* logic
|
||||
float distance = Vector2.Distance(rb.position, path.vectorPath[currentWaypoint]);
|
||||
|
||||
if (distance < nextWaypointDistance)
|
||||
{
|
||||
currentWaypoint++;
|
||||
}
|
||||
}
|
||||
|
||||
//DETECTION
|
||||
// Enemy detects player
|
||||
if (DetermineLineOfSight(transform.parent.gameObject, player) && Vector2.Distance(DroneBayLocation.position, player.transform.position) <= PlayerDetectionRange && currentlyRecharging == false)
|
||||
{
|
||||
if (currentlyInDroneBay == true)
|
||||
{
|
||||
rb.linearVelocity = transform.parent.transform.up * 10;
|
||||
}
|
||||
canMove = true;
|
||||
targetingPlayer = true;
|
||||
canPursue = true;
|
||||
|
||||
// Angle barrel towards player
|
||||
angle = Mathf.Atan2(player.transform.position.y - barrel.transform.position.y, player.transform.position.x - barrel.transform.position.x) * Mathf.Rad2Deg;
|
||||
|
||||
}
|
||||
else if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= PlayerDetectionRange)
|
||||
{
|
||||
targetingPlayer = true;
|
||||
canPursue = true;
|
||||
// Get the last know player location by finding which of the positions is closest to the player
|
||||
if (LastKnownPlayerLocation == null)
|
||||
{
|
||||
LastKnownPlayerLocation = gameObject;
|
||||
}
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
if (Vector2.Distance(player.transform.position, pos.transform.position) < Vector2.Distance(player.transform.position, LastKnownPlayerLocation.transform.position))
|
||||
{
|
||||
LastKnownPlayerLocation = pos;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset barrel rotation
|
||||
angle = -90f;
|
||||
}
|
||||
// Player has broken line of sight and the enemy will attempt to move to the last known location
|
||||
else if (LastKnownPlayerLocation != null)
|
||||
{
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) > 0.5)
|
||||
{
|
||||
canPursue = false;
|
||||
target.transform.position = LastKnownPlayerLocation.transform.position;
|
||||
}
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) < 0.5 && DetermineLineOfSight(player, gameObject) == false)
|
||||
{
|
||||
targetingPlayer = false;
|
||||
LastKnownPlayerLocation = null;
|
||||
}
|
||||
|
||||
// Reset barrel rotation
|
||||
angle = -90f;
|
||||
}
|
||||
// Go back to drone bay
|
||||
else
|
||||
{
|
||||
LastKnownPlayerLocation = null;
|
||||
ReturnToDroneBay();
|
||||
targetingPlayer = false;
|
||||
|
||||
// Reset barrel rotation
|
||||
angle = -90f;
|
||||
}
|
||||
|
||||
// Rotate barrel towards player
|
||||
Quaternion targetRotation = Quaternion.Euler(new Vector3(0, 0, angle));
|
||||
barrel.transform.rotation = Quaternion.RotateTowards(barrel.transform.rotation, targetRotation, 100 * Time.deltaTime);
|
||||
|
||||
|
||||
if (currentlyTravelingToDroneBay == false)
|
||||
{
|
||||
//TARGET DETERMINATION
|
||||
|
||||
// If enemy is at the target position
|
||||
if (Vector2.Distance(target.transform.position, transform.position) < 1.5)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the player moves away (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) > DesiredDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
//If the player is too close to the target (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) < MinumumDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the player is not within line of sight of the desired position (CHANGE TARGET)
|
||||
if (DetermineLineOfSight(target, player) == false)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the enemy reaches the target
|
||||
if (Vector2.Distance(target.transform.position, transform.position) <= 1)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == false) // If the enemy has LOS on the player
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MISC METHODS
|
||||
// Use Ranged Weapon
|
||||
async Task UseWeapon()
|
||||
{
|
||||
inFiringCycle = true;
|
||||
|
||||
// Create Grenade
|
||||
GameObject GrenadeInstance;
|
||||
GrenadeInstance = Instantiate(projectile, transform.position, Quaternion.LookRotation(transform.position - GameObject.FindGameObjectWithTag("Player").transform.position + new Vector3(UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), 0)));
|
||||
GrenadeInstance.GetComponent<EnemyGrenade>().WeaponDamage = WeaponDamage;
|
||||
|
||||
// Send it on it's way
|
||||
GrenadeInstance.GetComponent<Rigidbody2D>().linearVelocity = GrenadeInstance.transform.forward * -1 * WeaponProjectileSpeed;
|
||||
GrenadeInstance.transform.rotation = Quaternion.Euler(new Vector3(0, 0, Vector2.SignedAngle(Vector2.right, GrenadeInstance.transform.forward) - 90));
|
||||
WeaponCurrentMagazineAmmount--;
|
||||
|
||||
await Task.Delay(WeaponFireRate);
|
||||
|
||||
inFiringCycle = false;
|
||||
}
|
||||
|
||||
// Reload Weapon
|
||||
async Task ReloadWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
|
||||
//play reload animation
|
||||
currentlyReloading = true;
|
||||
await Task.Delay(WeaponReloadTime);
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
currentlyReloading = false;
|
||||
|
||||
canFire = true;
|
||||
}
|
||||
|
||||
// Return To Drone Bay
|
||||
async Task ReturnToDroneBay()
|
||||
{
|
||||
if (currentlyTravelingToDroneBay == false && currentlyInDroneBay == false)
|
||||
{
|
||||
currentlyTravelingToDroneBay = true;
|
||||
target.transform.position = DroneBayLocation.position;
|
||||
}
|
||||
}
|
||||
|
||||
// Recharge
|
||||
async Task Recharge()
|
||||
{
|
||||
currentlyRecharging = true;
|
||||
canMove = false;
|
||||
canFire = false;
|
||||
seeker.enabled = false;
|
||||
transform.position = DroneBayLocation.position;
|
||||
rb.linearVelocity = Vector3.zero;
|
||||
currentlyTravelingToDroneBay = false;
|
||||
await Task.Delay(RechargeTime);
|
||||
CurrentBatteryCapacity = MaxBatteryCapacity;
|
||||
currentlyRecharging = false;
|
||||
canFire = true;
|
||||
}
|
||||
|
||||
// General Utility
|
||||
bool DetermineLineOfSight(GameObject object1, GameObject object2)
|
||||
{
|
||||
Vector3 RaycastStart = object1.transform.position;
|
||||
Vector3 RaycastDirection = (object2.transform.position - object1.transform.position).normalized;
|
||||
float RaycastDistance = Vector3.Distance(object2.transform.position, object1.transform.position);
|
||||
|
||||
if (Physics2D.Raycast(RaycastStart, RaycastDirection, RaycastDistance, LayerMask.GetMask("SolidGround")) == false)
|
||||
{
|
||||
Debug.DrawRay(RaycastStart, RaycastDirection * RaycastDistance);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Part of the pursue cycle
|
||||
void ComputeClosestPositionToPlayer()
|
||||
{
|
||||
// find a target in the air a set radius away from the player
|
||||
if (DetermineLineOfSight(player, gameObject))
|
||||
{
|
||||
canMove = true;
|
||||
target.transform.position = new Vector2((player.transform.position.x + UnityEngine.Random.Range(-MinumumDistance, MinumumDistance)), (player.transform.position.y + MinumumDistance + UnityEngine.Random.Range(-5, 0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a21aeddab50704439aa410e19bcee64
|
||||
@@ -0,0 +1 @@
|
||||
uid://btkxwdxwwqhh3
|
||||
@@ -0,0 +1,468 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Pathfinding;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
//Designed by Jacob Weedman
|
||||
//DO NOT USE ON ANY ENEMIES
|
||||
|
||||
public class LightDroneAI : MonoBehaviour
|
||||
{
|
||||
//MISC
|
||||
GameObject target;
|
||||
float nextWaypointDistance = 5;
|
||||
Path path;
|
||||
int currentWaypoint = 0;
|
||||
bool reachedEndOfPath = false;
|
||||
GameObject projectile;
|
||||
GameObject player;
|
||||
GameObject barrel;
|
||||
public List<GameObject> AllPositions;
|
||||
float DistanceFromPlayer;
|
||||
Vector2 StartPosition;
|
||||
GameObject LastKnownPlayerLocation;
|
||||
Transform DroneBayLocation;
|
||||
|
||||
//CONDITIONS/GENERAL INFORMATION
|
||||
bool canMove = false;
|
||||
bool canPursue = false;
|
||||
bool canFire = true;
|
||||
bool currentlyReloading = false;
|
||||
bool currentlyInDroneBay = true;
|
||||
float DesiredDistance;
|
||||
float MinumumDistance = 10f;
|
||||
bool targetingPlayer = false;
|
||||
bool inFiringCycle = false;
|
||||
int WeaponCurrentMagazineAmmount;
|
||||
int CurrentBatteryCapacity;
|
||||
bool currentlyTravelingToDroneBay = false;
|
||||
bool currentlyRecharging = false;
|
||||
float angle;
|
||||
|
||||
//ENEMY STATS (Changeable)
|
||||
public float Speed = 0.7f; // In relation to player's walking speed
|
||||
public float Health = 100;
|
||||
public int PlayerDetectionRange = 25;
|
||||
public int MaxBatteryCapacity = 30; // Depletes one every second it is out of the bay
|
||||
public int RechargeTime = 6000; //ms
|
||||
|
||||
//WEAPON STATS (CHANGEABLE)
|
||||
public int WeaponDamage = 5; // Damage per hit
|
||||
public int WeaponFireRate = 100; // Delay in time between attacks both melee and ranged
|
||||
public float WeaponRandomSpread = 5f; // Random direction of lanched projectiles (DOES NOT APPLY TO MELEE ATTACKS)
|
||||
public int WeaponRange = 30; // Maximum range of the projectile before it drops off (DOES NOT APPLY TO MELEE ATTACKS)
|
||||
public float WeaponProjectileSpeed = 30f; // Speed of launched projectiles (DOES NOT APPLY TO MELEE WEAPONS)
|
||||
public int WeaponMagazineSize = 50; // Number of shots the enemy will take before having to reload
|
||||
public int WeaponReloadTime = 5000; // Time it takes to reload the magazine
|
||||
|
||||
|
||||
Seeker seeker;
|
||||
Rigidbody2D rb;
|
||||
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
void Start()
|
||||
{
|
||||
seeker = GetComponent<Seeker>();
|
||||
rb = GetComponent<Rigidbody2D>();
|
||||
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
transform.Find("BatteryIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
|
||||
StartPosition = transform.position;
|
||||
AllPositions = GameObject.FindGameObjectsWithTag("PossiblePositions").ToList();
|
||||
|
||||
target = Instantiate(GameObject.Find("FlyingTarget"), transform.position, Quaternion.identity);
|
||||
|
||||
projectile = GameObject.Find("EnemyProjectile");
|
||||
player = GameObject.FindGameObjectWithTag("Player");
|
||||
barrel = transform.Find("Barrel").gameObject;
|
||||
LastKnownPlayerLocation = null;
|
||||
DroneBayLocation = transform.parent.transform;
|
||||
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
DesiredDistance = WeaponRange - 5;
|
||||
CurrentBatteryCapacity = MaxBatteryCapacity;
|
||||
|
||||
InvokeRepeating("UpdatePath", 0f, 0.1f);
|
||||
InvokeRepeating("PathfindingTimeout", 0f, 30);
|
||||
InvokeRepeating("BatteryDrain", 0f, 1);
|
||||
|
||||
Recharge();
|
||||
}
|
||||
|
||||
// When enemy has reached the next node
|
||||
void OnPathComplete(Path p)
|
||||
{
|
||||
if (!p.error)
|
||||
{
|
||||
path = p;
|
||||
currentWaypoint = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Select next node
|
||||
void UpdatePath()
|
||||
{
|
||||
if (seeker.IsDone())
|
||||
{
|
||||
seeker.StartPath(rb.position, target.transform.position, OnPathComplete);
|
||||
}
|
||||
}
|
||||
|
||||
// Pathfiniding Timeout
|
||||
void PathfindingTimeout()
|
||||
{
|
||||
if (Vector2.Distance(transform.position, target.transform.position) > 0.5)
|
||||
{
|
||||
//target = gameObject;
|
||||
targetingPlayer = false;
|
||||
LastKnownPlayerLocation = null;
|
||||
//ReturnToDroneBay();
|
||||
}
|
||||
}
|
||||
|
||||
// Battery Drain
|
||||
void BatteryDrain()
|
||||
{
|
||||
if (currentlyInDroneBay == false)
|
||||
{
|
||||
CurrentBatteryCapacity -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// MAIN LOGIC
|
||||
void FixedUpdate()
|
||||
{
|
||||
//DEATH
|
||||
if (CurrentBatteryCapacity == 0)
|
||||
{
|
||||
Health = 0;
|
||||
}
|
||||
|
||||
if (Health <= 0)
|
||||
{
|
||||
GameObject DeadBody;
|
||||
DeadBody = Instantiate(gameObject, transform.position, Quaternion.identity);
|
||||
DeadBody.GetComponent<Rigidbody2D>().gravityScale = 3;
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<LightDroneAI>());
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<Seeker>());
|
||||
|
||||
foreach (Transform child in DeadBody.transform)
|
||||
{
|
||||
GameObject.Destroy(child.gameObject);
|
||||
}
|
||||
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
//Battery
|
||||
// Low battery return
|
||||
if (CurrentBatteryCapacity <= 10)
|
||||
{
|
||||
ReturnToDroneBay();
|
||||
}
|
||||
|
||||
// Recharge battery & bay detection
|
||||
if (Vector2.Distance(transform.position, DroneBayLocation.position) <= 0.5)
|
||||
{
|
||||
currentlyInDroneBay = true;
|
||||
currentlyTravelingToDroneBay = false;
|
||||
if (currentlyRecharging == false && CurrentBatteryCapacity < 10)
|
||||
{
|
||||
Recharge();
|
||||
}
|
||||
else
|
||||
{
|
||||
canMove = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//canMove = true;
|
||||
currentlyInDroneBay = false;
|
||||
}
|
||||
|
||||
//CHANGE ICONS
|
||||
if (targetingPlayer)
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
if (currentlyReloading)
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
if (CurrentBatteryCapacity <= MaxBatteryCapacity / 10)
|
||||
{
|
||||
transform.Find("BatteryIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("BatteryIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
if (currentlyInDroneBay == false)
|
||||
{
|
||||
seeker.enabled = true;
|
||||
}
|
||||
|
||||
//MISC PATHFINDING
|
||||
if (path == null)
|
||||
return;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
if (currentWaypoint >= path.vectorPath.Count)
|
||||
{
|
||||
reachedEndOfPath = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
reachedEndOfPath = false;
|
||||
}
|
||||
|
||||
//RANGED ATTACK
|
||||
// Check if enemy has line of sight on the player & if they are in the acceptable range
|
||||
|
||||
if (WeaponCurrentMagazineAmmount > 0 && canFire == true && currentlyReloading == false && currentlyInDroneBay == false)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == true && inFiringCycle == false && Vector2.Distance(transform.position, player.transform.position) <= WeaponRange && Vector2.Distance(transform.position, player.transform.position) <= DesiredDistance)
|
||||
{
|
||||
UseWeapon();
|
||||
}
|
||||
}
|
||||
else if (WeaponCurrentMagazineAmmount == 0 && currentlyReloading == false)
|
||||
{
|
||||
ReloadWeapon();
|
||||
}
|
||||
|
||||
//MOVEMENT
|
||||
if (canMove == true)
|
||||
{
|
||||
Vector2 direction = ((Vector2)path.vectorPath[currentWaypoint] - rb.position).normalized;
|
||||
Vector2 force = direction * Speed * 20;
|
||||
|
||||
rb.AddForce(force);
|
||||
|
||||
// A* logic
|
||||
float distance = Vector2.Distance(rb.position, path.vectorPath[currentWaypoint]);
|
||||
|
||||
if (distance < nextWaypointDistance)
|
||||
{
|
||||
currentWaypoint++;
|
||||
}
|
||||
}
|
||||
|
||||
//DETECTION
|
||||
// Enemy detects player
|
||||
if (DetermineLineOfSight(transform.parent.gameObject, player) && Vector2.Distance(DroneBayLocation.position, player.transform.position) <= PlayerDetectionRange && currentlyRecharging == false)
|
||||
{
|
||||
if (currentlyInDroneBay == true)
|
||||
{
|
||||
rb.linearVelocity = transform.parent.transform.up * 10;
|
||||
}
|
||||
canMove = true;
|
||||
targetingPlayer = true;
|
||||
canPursue = true;
|
||||
|
||||
// Angle barrel towards player
|
||||
angle = Mathf.Atan2(player.transform.position.y - barrel.transform.position.y, player.transform.position.x - barrel.transform.position.x) * Mathf.Rad2Deg;
|
||||
|
||||
}
|
||||
else if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= PlayerDetectionRange)
|
||||
{
|
||||
targetingPlayer = true;
|
||||
canPursue = true;
|
||||
// Get the last know player location by finding which of the positions is closest to the player
|
||||
if (LastKnownPlayerLocation == null)
|
||||
{
|
||||
LastKnownPlayerLocation = gameObject;
|
||||
}
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
if (Vector2.Distance(player.transform.position, pos.transform.position) < Vector2.Distance(player.transform.position, LastKnownPlayerLocation.transform.position))
|
||||
{
|
||||
LastKnownPlayerLocation = pos;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset barrel rotation
|
||||
angle = -90f;
|
||||
}
|
||||
// Player has broken line of sight and the enemy will attempt to move to the last known location
|
||||
else if (LastKnownPlayerLocation != null)
|
||||
{
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) > 0.5)
|
||||
{
|
||||
canPursue = false;
|
||||
target.transform.position = LastKnownPlayerLocation.transform.position;
|
||||
}
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) < 0.5 && DetermineLineOfSight(player, gameObject) == false)
|
||||
{
|
||||
targetingPlayer = false;
|
||||
LastKnownPlayerLocation = null;
|
||||
}
|
||||
|
||||
// Reset barrel rotation
|
||||
angle = -90f;
|
||||
}
|
||||
// Go back to drone bay
|
||||
else
|
||||
{
|
||||
LastKnownPlayerLocation = null;
|
||||
ReturnToDroneBay();
|
||||
targetingPlayer = false;
|
||||
|
||||
// Reset barrel rotation
|
||||
angle = -90f;
|
||||
}
|
||||
|
||||
// Rotate barrel towards player
|
||||
Quaternion targetRotation = Quaternion.Euler(new Vector3(0, 0, angle));
|
||||
barrel.transform.rotation = Quaternion.RotateTowards(barrel.transform.rotation, targetRotation, 100 * Time.deltaTime);
|
||||
|
||||
|
||||
if (currentlyTravelingToDroneBay == false)
|
||||
{
|
||||
//TARGET DETERMINATION
|
||||
|
||||
// If enemy is at the target position
|
||||
if (Vector2.Distance(target.transform.position, transform.position) < 1.5)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the player moves away (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) > DesiredDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
//If the player is too close to the target (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) < MinumumDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the player is not within line of sight of the desired position (CHANGE TARGET)
|
||||
if (DetermineLineOfSight(target, player) == false)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the enemy reaches the target
|
||||
if (Vector2.Distance(target.transform.position, transform.position) <= 1)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == false) // If the enemy has LOS on the player
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MISC METHODS
|
||||
// Use Ranged Weapon
|
||||
async Task UseWeapon() // Weapon for projectile based enemy
|
||||
{
|
||||
inFiringCycle = true;
|
||||
|
||||
// Create Projectile
|
||||
GameObject BulletInstance;
|
||||
BulletInstance = Instantiate(projectile, transform.position, Quaternion.LookRotation(transform.position - GameObject.FindGameObjectWithTag("Player").transform.position + new Vector3(UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), 0)));
|
||||
BulletInstance.transform.parent = transform;
|
||||
|
||||
// Send it on it's way
|
||||
BulletInstance.GetComponent<Rigidbody2D>().linearVelocity = BulletInstance.transform.forward * -1 * WeaponProjectileSpeed;
|
||||
BulletInstance.transform.rotation = Quaternion.Euler(new Vector3(0, 0, Vector2.SignedAngle(Vector2.right, BulletInstance.transform.forward) - 90));
|
||||
WeaponCurrentMagazineAmmount--;
|
||||
|
||||
await Task.Delay(WeaponFireRate);
|
||||
|
||||
inFiringCycle = false;
|
||||
}
|
||||
|
||||
// Reload Weapon
|
||||
async Task ReloadWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
|
||||
//play reload animation
|
||||
currentlyReloading = true;
|
||||
await Task.Delay(WeaponReloadTime);
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
currentlyReloading = false;
|
||||
|
||||
canFire = true;
|
||||
}
|
||||
|
||||
// Return To Drone Bay
|
||||
async Task ReturnToDroneBay()
|
||||
{
|
||||
if (currentlyTravelingToDroneBay == false && currentlyInDroneBay == false)
|
||||
{
|
||||
currentlyTravelingToDroneBay = true;
|
||||
target.transform.position = DroneBayLocation.position;
|
||||
}
|
||||
}
|
||||
|
||||
// Recharge
|
||||
async Task Recharge()
|
||||
{
|
||||
currentlyRecharging = true;
|
||||
canMove = false;
|
||||
canFire = false;
|
||||
seeker.enabled = false;
|
||||
transform.position = DroneBayLocation.position;
|
||||
rb.linearVelocity = Vector3.zero;
|
||||
currentlyTravelingToDroneBay = false;
|
||||
await Task.Delay(RechargeTime);
|
||||
CurrentBatteryCapacity = MaxBatteryCapacity;
|
||||
currentlyRecharging = false;
|
||||
canFire = true;
|
||||
}
|
||||
|
||||
// General Utility
|
||||
bool DetermineLineOfSight(GameObject object1, GameObject object2)
|
||||
{
|
||||
Vector3 RaycastStart = object1.transform.position;
|
||||
Vector3 RaycastDirection = (object2.transform.position - object1.transform.position).normalized;
|
||||
float RaycastDistance = Vector3.Distance(object2.transform.position, object1.transform.position);
|
||||
|
||||
if (Physics2D.Raycast(RaycastStart, RaycastDirection, RaycastDistance, LayerMask.GetMask("SolidGround")) == false)
|
||||
{
|
||||
Debug.DrawRay(RaycastStart, RaycastDirection * RaycastDistance);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Part of the pursue cycle
|
||||
void ComputeClosestPositionToPlayer()
|
||||
{
|
||||
// find a target in the air a set radius away from the player
|
||||
if (DetermineLineOfSight(player, gameObject))
|
||||
{
|
||||
canMove = true;
|
||||
target.transform.position = new Vector2((player.transform.position.x + UnityEngine.Random.Range(-MinumumDistance, MinumumDistance)), (player.transform.position.y + MinumumDistance + UnityEngine.Random.Range(-5, 0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 908158ab2214cc04ea3af93728a2c94d
|
||||
@@ -0,0 +1 @@
|
||||
uid://dbhj5fkwajasw
|
||||
@@ -0,0 +1,186 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Pathfinding;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
//Designed by Jacob Weedman
|
||||
//Use on machine gun turrets
|
||||
|
||||
public class MachineGunTurretAI : MonoBehaviour
|
||||
{
|
||||
|
||||
//MISC
|
||||
GameObject projectile;
|
||||
GameObject player;
|
||||
GameObject barrel;
|
||||
float DistanceFromPlayer;
|
||||
Vector2 StartPosition;
|
||||
|
||||
//CONDITIONS/GENERAL INFORMATION
|
||||
bool canFire = true;
|
||||
bool currentlyReloading = false;
|
||||
bool targetingPlayer = false;
|
||||
bool inFiringCycle = false;
|
||||
int WeaponCurrentMagazineAmmount;
|
||||
float angle;
|
||||
|
||||
//ENEMY STATS (Changeable)
|
||||
public float Health = 100;
|
||||
public int PlayerDetectionRange = 25;
|
||||
|
||||
//WEAPON STATS (CHANGEABLE)
|
||||
public int WeaponDamage = 15; // Damage per hit
|
||||
public int WeaponFireRate = 150; // Delay in time between attacks both melee and ranged
|
||||
public float WeaponRandomSpread = 6.5f; // Random direction of lanched projectiles
|
||||
public int WeaponRange = 20; // Maximum range of the projectile before it drops off
|
||||
public float WeaponProjectileSpeed = 40f; // Speed of launched projectiles
|
||||
public int WeaponMagazineSize = 50; // Number of shots the enemy will take before having to reload
|
||||
public int WeaponReloadTime = 10000; // Time it takes to reload the magazine
|
||||
|
||||
//ONCE THE GAME STARTS
|
||||
void Start()
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
|
||||
projectile = GameObject.Find("EnemyProjectile");
|
||||
player = GameObject.FindGameObjectWithTag("Player");
|
||||
barrel = transform.Find("Barrel").gameObject;
|
||||
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
|
||||
canFire = true;
|
||||
}
|
||||
|
||||
//MAIN LOGIC
|
||||
void FixedUpdate()
|
||||
{
|
||||
DistanceFromPlayer = Vector2.Distance(transform.position, player.transform.position);
|
||||
|
||||
// Death
|
||||
if (Health <= 0)
|
||||
{
|
||||
GameObject DeadBody;
|
||||
DeadBody = Instantiate(gameObject, transform.position, Quaternion.identity);
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<TestEnemyAIGroundRanged>());
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<Seeker>());
|
||||
|
||||
foreach (Transform child in DeadBody.transform) {
|
||||
GameObject.Destroy(child.gameObject);
|
||||
}
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
|
||||
//ICONS
|
||||
if (targetingPlayer)
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
if (currentlyReloading)
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
//RANGED ATTACK
|
||||
// Check if enemy has line of sight on the player & if they are in the acceptable range
|
||||
if (WeaponCurrentMagazineAmmount > 0 && canFire == true && currentlyReloading == false)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= WeaponRange)
|
||||
{
|
||||
UseWeapon();
|
||||
}
|
||||
}
|
||||
else if (WeaponCurrentMagazineAmmount == 0 && currentlyReloading == false)
|
||||
{
|
||||
ReloadWeapon();
|
||||
}
|
||||
|
||||
//PLAYER TARGETING
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= WeaponRange + 10)
|
||||
{
|
||||
targetingPlayer = true;
|
||||
|
||||
// Set angle to player
|
||||
angle = Mathf.Atan2(player.transform.position.y - barrel.transform.position.y, player.transform.position.x - barrel.transform.position.x) * Mathf.Rad2Deg;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reset barrel rotation
|
||||
angle = 90f;
|
||||
}
|
||||
|
||||
// Rotate barrel towards player
|
||||
Quaternion targetRotation = Quaternion.Euler(new Vector3(0, 0, angle));
|
||||
barrel.transform.rotation = Quaternion.RotateTowards(barrel.transform.rotation, targetRotation, 100 * Time.deltaTime);
|
||||
|
||||
}
|
||||
|
||||
//MISC METHODS
|
||||
// Use Ranged Weapon
|
||||
async Task UseWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
inFiringCycle = true;
|
||||
|
||||
// Create Projectile
|
||||
GameObject BulletInstance;
|
||||
BulletInstance = Instantiate(projectile, transform.position, Quaternion.LookRotation(transform.position - GameObject.FindGameObjectWithTag("Player").transform.position + new Vector3(UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), 0)));
|
||||
BulletInstance.transform.parent = transform;
|
||||
|
||||
// Send it on it's way
|
||||
BulletInstance.GetComponent<Rigidbody2D>().linearVelocity = BulletInstance.transform.forward * -1 * WeaponProjectileSpeed;
|
||||
BulletInstance.transform.rotation = Quaternion.Euler(new Vector3(0, 0, Vector2.SignedAngle(Vector2.right, BulletInstance.transform.forward) - 90));
|
||||
WeaponCurrentMagazineAmmount--;
|
||||
|
||||
await Task.Delay(WeaponFireRate);
|
||||
|
||||
canFire = true;
|
||||
inFiringCycle = false;
|
||||
}
|
||||
|
||||
// Reload Weapon
|
||||
async Task ReloadWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
//play reload animation
|
||||
currentlyReloading = true;
|
||||
await Task.Delay(WeaponReloadTime);
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
currentlyReloading = false;
|
||||
|
||||
canFire = true;
|
||||
}
|
||||
|
||||
// General Utility
|
||||
bool DetermineLineOfSight(GameObject object1, GameObject object2)
|
||||
{
|
||||
Vector3 RaycastStart = object1.transform.position;
|
||||
Vector3 RaycastDirection = (object2.transform.position - object1.transform.position).normalized;
|
||||
float RaycastDistance = Vector3.Distance(object2.transform.position, object1.transform.position);
|
||||
|
||||
if (Physics2D.Raycast(RaycastStart, RaycastDirection, RaycastDistance, LayerMask.GetMask("SolidGround")) == false)
|
||||
{
|
||||
Debug.DrawRay(RaycastStart, RaycastDirection * RaycastDistance);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6eaccebea63257f4685a23d638a4ca16
|
||||
@@ -0,0 +1 @@
|
||||
uid://djux2mx2lqlfc
|
||||
@@ -0,0 +1,385 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Pathfinding;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
//Designed by Jacob Weedman
|
||||
//Use on regular flying ranged enemies
|
||||
|
||||
public class RegularFlyingAI : MonoBehaviour
|
||||
{
|
||||
//MISC
|
||||
GameObject target;
|
||||
float nextWaypointDistance = 5;
|
||||
Path path;
|
||||
int currentWaypoint = 0;
|
||||
bool reachedEndOfPath = false;
|
||||
GameObject projectile;
|
||||
GameObject player;
|
||||
public List<GameObject> AllPositions;
|
||||
float DistanceFromPlayer;
|
||||
Vector2 StartPosition;
|
||||
public List<GameObject> PatrolPositions;
|
||||
GameObject LastKnownPlayerLocation;
|
||||
|
||||
//CONDITIONS/GENERAL INFORMATION
|
||||
bool canMove = true;
|
||||
bool canPursue = false;
|
||||
bool canFire = true;
|
||||
bool currentlyReloading = false;
|
||||
bool currentlyPatrolling;
|
||||
bool currentlyMovingToNextPatrolTarget = false;
|
||||
float DesiredDistance;
|
||||
float MinumumDistance = 10f;
|
||||
bool targetingPlayer = false;
|
||||
bool inFiringCycle = false;
|
||||
int WeaponCurrentMagazineAmmount;
|
||||
|
||||
//ENEMY STATS (Changeable)
|
||||
public float Speed = 0.7f; // In relation to player's walking speed
|
||||
public float Health = 100;
|
||||
public float PatrolDistance = 10;
|
||||
public int PatrolStallTime = 1500; //ms
|
||||
public int PlayerDetectionRange = 25;
|
||||
|
||||
//WEAPON STATS (CHANGEABLE)
|
||||
public int WeaponDamage = 5; // Damage per hit
|
||||
public int WeaponFireRate = 100; // Delay in time between attacks both melee and ranged
|
||||
public float WeaponRandomSpread = 5f; // Random direction of lanched projectiles (DOES NOT APPLY TO MELEE ATTACKS)
|
||||
public int WeaponRange = 30; // Maximum range of the projectile before it drops off (DOES NOT APPLY TO MELEE ATTACKS)
|
||||
public float WeaponProjectileSpeed = 30f; // Speed of launched projectiles (DOES NOT APPLY TO MELEE WEAPONS)
|
||||
public int WeaponMagazineSize = 50; // Number of shots the enemy will take before having to reload
|
||||
public int WeaponReloadTime = 5000; // Time it takes to reload the magazine
|
||||
|
||||
|
||||
Seeker seeker;
|
||||
Rigidbody2D rb;
|
||||
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
void Start()
|
||||
{
|
||||
seeker = GetComponent<Seeker>();
|
||||
rb = GetComponent<Rigidbody2D>();
|
||||
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
|
||||
StartPosition = transform.position;
|
||||
AllPositions = GameObject.FindGameObjectsWithTag("PossiblePositions").ToList();
|
||||
|
||||
target = Instantiate(GameObject.Find("FlyingTarget"), transform.position, Quaternion.identity);
|
||||
|
||||
projectile = GameObject.Find("Projectile");
|
||||
player = GameObject.FindGameObjectWithTag("Player");
|
||||
LastKnownPlayerLocation = null;
|
||||
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
DesiredDistance = WeaponRange - 5;
|
||||
|
||||
InvokeRepeating("UpdatePath", 0f, 0.1f);
|
||||
InvokeRepeating("PathfindingTimeout", 0f, 30);
|
||||
|
||||
}
|
||||
|
||||
// When enemy has reached the next node
|
||||
void OnPathComplete(Path p)
|
||||
{
|
||||
if (!p.error)
|
||||
{
|
||||
path = p;
|
||||
currentWaypoint = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Select next node
|
||||
void UpdatePath()
|
||||
{
|
||||
if (seeker.IsDone())
|
||||
{
|
||||
seeker.StartPath(rb.position, target.transform.position, OnPathComplete);
|
||||
}
|
||||
}
|
||||
|
||||
// Pathfiniding Timeout
|
||||
void PathfindingTimeout()
|
||||
{
|
||||
if (Vector2.Distance(transform.position, target.transform.position) > 0.5)
|
||||
{
|
||||
//target = gameObject;
|
||||
targetingPlayer = false;
|
||||
LastKnownPlayerLocation = null;
|
||||
MoveNextPatrol();
|
||||
}
|
||||
}
|
||||
|
||||
//Main logic
|
||||
void FixedUpdate()
|
||||
{
|
||||
// Death
|
||||
if (Health <= 0)
|
||||
{
|
||||
GameObject DeadBody;
|
||||
DeadBody = Instantiate(gameObject, transform.position, Quaternion.identity);
|
||||
DeadBody.GetComponent<Rigidbody2D>().gravityScale = 3;
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<TestEnemyAIAirRanged>());
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<Seeker>());
|
||||
|
||||
foreach (Transform child in DeadBody.transform)
|
||||
{
|
||||
GameObject.Destroy(child.gameObject);
|
||||
}
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
// See where the enemy wants to go (DEBUGGING ONLY)
|
||||
/*
|
||||
if (target != null)
|
||||
{
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
pos.GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
target.GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
*/
|
||||
|
||||
// Change Icons
|
||||
if (targetingPlayer)
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
if (currentlyReloading)
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
//MISC PATHFINDING
|
||||
if (path == null)
|
||||
return;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
if (currentWaypoint >= path.vectorPath.Count)
|
||||
{
|
||||
reachedEndOfPath = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
reachedEndOfPath = false;
|
||||
}
|
||||
|
||||
//RANGED ATTACK
|
||||
// Check if enemy has line of sight on the player & if they are in the acceptable range
|
||||
|
||||
if (WeaponCurrentMagazineAmmount > 0 && canFire == true && currentlyReloading == false && currentlyPatrolling == false)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == true && inFiringCycle == false && Vector2.Distance(transform.position, player.transform.position) <= WeaponRange && Vector2.Distance(transform.position, player.transform.position) <= DesiredDistance)
|
||||
{
|
||||
UseWeapon();
|
||||
}
|
||||
}
|
||||
else if (WeaponCurrentMagazineAmmount == 0 && currentlyReloading == false)
|
||||
{
|
||||
ReloadWeapon();
|
||||
}
|
||||
|
||||
//MOVEMENT
|
||||
if (canMove == true)
|
||||
{
|
||||
Vector2 direction = ((Vector2)path.vectorPath[currentWaypoint] - rb.position).normalized;
|
||||
Vector2 force = direction * Speed * 20;
|
||||
|
||||
rb.AddForce(force);
|
||||
|
||||
// A* logic
|
||||
float distance = Vector2.Distance(rb.position, path.vectorPath[currentWaypoint]);
|
||||
|
||||
if (distance < nextWaypointDistance)
|
||||
{
|
||||
currentWaypoint++;
|
||||
}
|
||||
}
|
||||
|
||||
//PATROL
|
||||
// Enemy detects player
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= PlayerDetectionRange)
|
||||
{
|
||||
//target = null;
|
||||
currentlyPatrolling = false;
|
||||
targetingPlayer = true;
|
||||
canPursue = true;
|
||||
// Get the last know player location by finding which of the positions is closest to the player
|
||||
if (LastKnownPlayerLocation == null)
|
||||
{
|
||||
LastKnownPlayerLocation = gameObject;
|
||||
}
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
if (Vector2.Distance(player.transform.position, pos.transform.position) < Vector2.Distance(player.transform.position, LastKnownPlayerLocation.transform.position))
|
||||
{
|
||||
LastKnownPlayerLocation = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Player has broken line of sight and the enemy will attempt to move to the last known location
|
||||
else if (LastKnownPlayerLocation != null)
|
||||
{
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) > 0.5)
|
||||
{
|
||||
canPursue = false;
|
||||
target.transform.position = LastKnownPlayerLocation.transform.position;
|
||||
}
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) < 0.5 && DetermineLineOfSight(player, gameObject) == false)
|
||||
{
|
||||
targetingPlayer = false;
|
||||
LastKnownPlayerLocation = null;
|
||||
}
|
||||
}
|
||||
// Go back to patrol move
|
||||
else
|
||||
{
|
||||
//target = PatrolPositions[UnityEngine.Random.Range(0, PatrolPositions.Count)];
|
||||
LastKnownPlayerLocation = gameObject;
|
||||
currentlyPatrolling = true;
|
||||
targetingPlayer = false;
|
||||
}
|
||||
|
||||
// Call patrol method
|
||||
if (currentlyPatrolling)
|
||||
{
|
||||
if (canMove == true && currentlyMovingToNextPatrolTarget == false)
|
||||
{
|
||||
MoveNextPatrol();
|
||||
}
|
||||
}
|
||||
|
||||
if (canPursue == true)
|
||||
{
|
||||
//TARGET DETERMINATION
|
||||
|
||||
// If the player moves away (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) > DesiredDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
canMove = true;
|
||||
}
|
||||
|
||||
//If the player is too close to the target (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) < MinumumDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
canMove = true;
|
||||
}
|
||||
|
||||
// If the player is not within line of sight of the desired position (CHANGE TARGET)
|
||||
if (DetermineLineOfSight(target, player) == false)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
canMove = true;
|
||||
}
|
||||
|
||||
// If the enemy reaches the target
|
||||
if (Vector2.Distance(target.transform.position, transform.position) <= 1)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == false) // If the enemy has LOS on the player
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
canMove = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use Ranged Weapon
|
||||
async Task UseWeapon() // Weapon for projectile based enemy
|
||||
{
|
||||
inFiringCycle = true;
|
||||
|
||||
// Create Projectile
|
||||
GameObject BulletInstance;
|
||||
BulletInstance = Instantiate(projectile, transform.position, Quaternion.LookRotation(transform.position - GameObject.FindGameObjectWithTag("Player").transform.position + new Vector3(UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), 0)));
|
||||
BulletInstance.transform.parent = transform;
|
||||
|
||||
// Send it on it's way
|
||||
BulletInstance.GetComponent<Rigidbody2D>().linearVelocity = BulletInstance.transform.forward * -1 * WeaponProjectileSpeed;
|
||||
BulletInstance.transform.rotation = Quaternion.Euler(new Vector3(0, 0, Vector2.SignedAngle(Vector2.right, BulletInstance.transform.forward) - 90));
|
||||
WeaponCurrentMagazineAmmount--;
|
||||
|
||||
await Task.Delay(WeaponFireRate);
|
||||
|
||||
inFiringCycle = false;
|
||||
}
|
||||
|
||||
// Reload Weapon
|
||||
async Task ReloadWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
|
||||
//play reload animation
|
||||
currentlyReloading = true;
|
||||
await Task.Delay(WeaponReloadTime);
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
currentlyReloading = false;
|
||||
|
||||
canFire = true;
|
||||
}
|
||||
|
||||
// Part of the patrol cycle
|
||||
async Task MoveNextPatrol()
|
||||
{
|
||||
LastKnownPlayerLocation = gameObject;
|
||||
target.transform.position = transform.position;
|
||||
|
||||
canPursue = false;
|
||||
currentlyMovingToNextPatrolTarget = true;
|
||||
|
||||
//Find Random Position nearby
|
||||
target.transform.position = new Vector2((StartPosition.x + UnityEngine.Random.Range(-1 * PatrolDistance, PatrolDistance)), (StartPosition.y + UnityEngine.Random.Range(-1 * PatrolDistance, PatrolDistance)));
|
||||
await Task.Delay(PatrolStallTime + UnityEngine.Random.Range((PatrolStallTime * -1), PatrolStallTime));
|
||||
|
||||
currentlyMovingToNextPatrolTarget = false;
|
||||
|
||||
}
|
||||
|
||||
// General Utility
|
||||
bool DetermineLineOfSight(GameObject object1, GameObject object2)
|
||||
{
|
||||
Vector3 RaycastStart = object1.transform.position;
|
||||
Vector3 RaycastDirection = (object2.transform.position - object1.transform.position).normalized;
|
||||
float RaycastDistance = Vector3.Distance(object2.transform.position, object1.transform.position);
|
||||
|
||||
if (Physics2D.Raycast(RaycastStart, RaycastDirection, RaycastDistance, LayerMask.GetMask("SolidGround")) == false)
|
||||
{
|
||||
Debug.DrawRay(RaycastStart, RaycastDirection * RaycastDistance);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Partof the pursue cycle
|
||||
void ComputeClosestPositionToPlayer()
|
||||
{
|
||||
// find a target in the air a set radius away from the player
|
||||
if (DetermineLineOfSight(player, gameObject))
|
||||
{
|
||||
target.transform.position = new Vector2((player.transform.position.x + UnityEngine.Random.Range(-MinumumDistance, MinumumDistance)), (player.transform.position.y + MinumumDistance + UnityEngine.Random.Range(-5, 0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bfee0f4605660014d909f908d7917b8e
|
||||
@@ -0,0 +1 @@
|
||||
uid://bdce6nq0qj46b
|
||||
@@ -0,0 +1,154 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Pathfinding;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
//Designed by Jacob Weedman
|
||||
//Use on rocket turrets
|
||||
|
||||
public class RocketTurretAI : MonoBehaviour
|
||||
{
|
||||
|
||||
//MISC
|
||||
GameObject projectile;
|
||||
GameObject player;
|
||||
GameObject barrel;
|
||||
float DistanceFromPlayer;
|
||||
Vector2 StartPosition;
|
||||
|
||||
//CONDITIONS/GENERAL INFORMATION
|
||||
bool canFire = true;
|
||||
bool targetingPlayer = false;
|
||||
bool inFiringCycle = false;
|
||||
float angle;
|
||||
|
||||
//ENEMY STATS (Changeable)
|
||||
public float Health = 100;
|
||||
public int PlayerDetectionRange = 50;
|
||||
|
||||
//WEAPON STATS (CHANGEABLE)
|
||||
public int WeaponDamage = 30; // Damage per hit
|
||||
public int WeaponFireRate = 2500; // Delay in time between attacks both melee and ranged
|
||||
public float WeaponRandomSpread = 0.5f; // Random direction of lanched projectiles
|
||||
public int WeaponRange = 40; // Maximum range of the projectile before it drops off
|
||||
public float WeaponProjectileSpeed = 50f; // Speed of launched projectiles
|
||||
|
||||
//ONCE THE GAME STARTS
|
||||
void Start()
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
|
||||
projectile = GameObject.Find("EnemyRocketWeapon");
|
||||
player = GameObject.FindGameObjectWithTag("Player");
|
||||
barrel = transform.Find("Barrel").gameObject;
|
||||
|
||||
canFire = true;
|
||||
}
|
||||
|
||||
//MAIN LOGIC
|
||||
void FixedUpdate()
|
||||
{
|
||||
DistanceFromPlayer = Vector2.Distance(transform.position, player.transform.position);
|
||||
|
||||
// Death
|
||||
if (Health <= 0)
|
||||
{
|
||||
GameObject DeadBody;
|
||||
DeadBody = Instantiate(gameObject, transform.position, Quaternion.identity);
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<TestEnemyAIGroundRanged>());
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<Seeker>());
|
||||
|
||||
foreach (Transform child in DeadBody.transform) {
|
||||
GameObject.Destroy(child.gameObject);
|
||||
}
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
|
||||
//ICONS
|
||||
if (targetingPlayer)
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
//RANGED ATTACK
|
||||
// Check if enemy has line of sight on the player & if they are in the acceptable range
|
||||
if (canFire == true)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= WeaponRange)
|
||||
{
|
||||
UseWeapon();
|
||||
}
|
||||
}
|
||||
|
||||
//PLAYER TARGETING
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= WeaponRange + 10)
|
||||
{
|
||||
targetingPlayer = true;
|
||||
|
||||
// Set angle to player
|
||||
angle = Mathf.Atan2(player.transform.position.y - barrel.transform.position.y, player.transform.position.x - barrel.transform.position.x) * Mathf.Rad2Deg;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reset barrel rotation
|
||||
angle = 90f;
|
||||
}
|
||||
|
||||
// Rotate barrel towards player
|
||||
Quaternion targetRotation = Quaternion.Euler(new Vector3(0, 0, angle));
|
||||
barrel.transform.rotation = Quaternion.RotateTowards(barrel.transform.rotation, targetRotation, 100 * Time.deltaTime);
|
||||
|
||||
}
|
||||
|
||||
//MISC METHODS
|
||||
// Use Ranged Weapon
|
||||
async Task UseWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
inFiringCycle = true;
|
||||
|
||||
GameObject Rocket;
|
||||
Rocket = Instantiate(GameObject.Find("EnemyRocket"), new Vector3(transform.position.x + UnityEngine.Random.Range(-0.5f, 0.5f), transform.position.y + UnityEngine.Random.Range(-0.5f, 0.5f), GameObject.Find("EvilAura").transform.position.z), Quaternion.identity);
|
||||
Rocket.transform.rotation = Quaternion.Euler(Vector3.forward * UnityEngine.Random.Range(-90, 90));
|
||||
|
||||
// Send it on its way
|
||||
Rocket.GetComponent<Rigidbody2D>().linearVelocity = new Vector2(transform.position.x - player.transform.position.x + UnityEngine.Random.Range(-WeaponRandomSpread, WeaponRandomSpread), transform.position.y - player.transform.position.y + UnityEngine.Random.Range(-WeaponRandomSpread, WeaponRandomSpread)).normalized * WeaponProjectileSpeed * -1;
|
||||
Rocket.transform.rotation = Quaternion.Euler(new Vector3(0, 0, Vector2.SignedAngle(Vector2.right, Rocket.transform.forward) - 90));
|
||||
|
||||
// Variables
|
||||
Rocket.GetComponent<EnemyRocket>().WeaponDamage = WeaponDamage;
|
||||
|
||||
await Task.Delay(WeaponFireRate);
|
||||
|
||||
canFire = true;
|
||||
inFiringCycle = false;
|
||||
}
|
||||
|
||||
// General Utility
|
||||
bool DetermineLineOfSight(GameObject object1, GameObject object2)
|
||||
{
|
||||
Vector3 RaycastStart = object1.transform.position;
|
||||
Vector3 RaycastDirection = (object2.transform.position - object1.transform.position).normalized;
|
||||
float RaycastDistance = Vector3.Distance(object2.transform.position, object1.transform.position);
|
||||
|
||||
if (Physics2D.Raycast(RaycastStart, RaycastDirection, RaycastDistance, LayerMask.GetMask("SolidGround")) == false)
|
||||
{
|
||||
Debug.DrawRay(RaycastStart, RaycastDirection * RaycastDistance);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fff6f5b1fd22d4a41afda949998e4dd0
|
||||
@@ -0,0 +1 @@
|
||||
uid://b5dao1ktmi05l
|
||||
@@ -0,0 +1,469 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Pathfinding;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
//Designed by Jacob Weedman
|
||||
//Use on Rocketman type enemies
|
||||
|
||||
public class RocketmanAI : MonoBehaviour
|
||||
{
|
||||
|
||||
//MISC
|
||||
GameObject target;
|
||||
float nextWaypointDistance = 5;
|
||||
Path path;
|
||||
int currentWaypoint = 0;
|
||||
bool reachedEndOfPath = false;
|
||||
GameObject projectile;
|
||||
GameObject player;
|
||||
GameObject barrel;
|
||||
public List<GameObject> SuitablePositions;
|
||||
public List<GameObject> AllPositions;
|
||||
float DistanceFromPlayer;
|
||||
Vector2 StartPosition;
|
||||
public List<GameObject> PatrolPositions;
|
||||
public GameObject LastKnownPlayerLocation;
|
||||
|
||||
//CONDITIONS/GENERAL INFORMATION
|
||||
bool canJump = true;
|
||||
bool canMove = true; // Prevent all movement
|
||||
bool canPursue = false; // Follow player
|
||||
bool canFire = false;
|
||||
bool currentlyReloading = false;
|
||||
bool currentlyPatrolling;
|
||||
bool currentlyMovingToNextPatrolTarget = false;
|
||||
float DesiredDistance;
|
||||
float MinumumDistance = 5f;
|
||||
bool targetingPlayer = false;
|
||||
bool inFiringCycle = false;
|
||||
int WeaponCurrentMagazineAmmount;
|
||||
float angle;
|
||||
|
||||
//ENEMY STATS (Changeable)
|
||||
public float Speed = 0.8f; // In relation to player's walking speed
|
||||
public float JumpHeight = 0.8f; // In relation to player's regular jump height
|
||||
public float Health = 30;
|
||||
public float PatrolDistance = 15;
|
||||
public int PatrolStallTime = 2000; //ms
|
||||
public int PlayerDetectionRange = 40;
|
||||
|
||||
//WEAPON STATS (CHANGEABLE)
|
||||
public int WeaponDamage = 30; // Damage per hit
|
||||
public int WeaponFireRate = 2500; // Delay in time between attacks both melee and ranged
|
||||
public float WeaponRandomSpread = 1.5f; // Random direction of lanched projectiles
|
||||
public int WeaponRange = 30; // Maximum range of the projectile before it drops off
|
||||
public float WeaponProjectileSpeed = 40f; // Speed of launched projectiles
|
||||
public int WeaponMagazineSize = 1; // Number of shots the enemy will take before having to reload
|
||||
public int WeaponReloadTime = 5000; // Time it takes to reload the magazine
|
||||
|
||||
//REFERENCES
|
||||
Seeker seeker;
|
||||
Rigidbody2D rb;
|
||||
|
||||
//ONCE THE GAME STARTS
|
||||
void Start()
|
||||
{
|
||||
seeker = GetComponent<Seeker>();
|
||||
rb = GetComponent<Rigidbody2D>();
|
||||
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
|
||||
StartPosition = transform.position;
|
||||
AllPositions = GameObject.FindGameObjectsWithTag("PossiblePositions").ToList();
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
if (Vector2.Distance(pos.transform.position, StartPosition) <= PatrolDistance)
|
||||
{
|
||||
PatrolPositions.Add(pos);
|
||||
}
|
||||
}
|
||||
target = PatrolPositions[UnityEngine.Random.Range(0, PatrolPositions.Count)];
|
||||
|
||||
projectile = GameObject.Find("EnemyProjectile");
|
||||
player = GameObject.FindGameObjectWithTag("Player");
|
||||
barrel = transform.Find("Barrel").gameObject;
|
||||
LastKnownPlayerLocation = null;
|
||||
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
DesiredDistance = WeaponRange - 5;
|
||||
|
||||
InvokeRepeating("UpdatePath", 0f, 0.1f);
|
||||
InvokeRepeating("PathfindingTimeout", 0f, 10);
|
||||
|
||||
}
|
||||
|
||||
// When enemy has reached the next node
|
||||
void OnPathComplete(Path p)
|
||||
{
|
||||
if (!p.error)
|
||||
{
|
||||
path = p;
|
||||
currentWaypoint = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Select next node
|
||||
void UpdatePath()
|
||||
{
|
||||
if (seeker.IsDone() && target != null)
|
||||
{
|
||||
seeker.StartPath(rb.position, target.transform.position, OnPathComplete);
|
||||
}
|
||||
}
|
||||
|
||||
// Pathfiniding Timeout
|
||||
void PathfindingTimeout()
|
||||
{
|
||||
if (Vector2.Distance(transform.position, target.transform.position) > 0.5)
|
||||
{
|
||||
target = gameObject;
|
||||
MoveNextPatrol();
|
||||
}
|
||||
}
|
||||
|
||||
//MAIN LOGIC
|
||||
void FixedUpdate()
|
||||
{
|
||||
//DEATH
|
||||
if (Health <= 0)
|
||||
{
|
||||
GameObject DeadBody;
|
||||
DeadBody = Instantiate(gameObject, transform.position, Quaternion.identity);
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<TestEnemyAIGroundRanged>());
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<Seeker>());
|
||||
|
||||
foreach (Transform child in DeadBody.transform) {
|
||||
GameObject.Destroy(child.gameObject);
|
||||
}
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
//ICONS
|
||||
if (targetingPlayer)
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
if (currentlyReloading)
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
//if (90 <= angle || angle <= 270)
|
||||
//{
|
||||
// barrel.transform.localScale = new Vector2(-barrel.transform.localScale.x, barrel.transform.localScale.y);
|
||||
//}
|
||||
|
||||
//MISC PATHFINDING
|
||||
if (path == null)
|
||||
return;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
if (currentWaypoint >= path.vectorPath.Count)
|
||||
{
|
||||
reachedEndOfPath = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
reachedEndOfPath = false;
|
||||
}
|
||||
|
||||
//RANGED ATTACK
|
||||
// Check if enemy has line of sight on the player & if they are in the acceptable range
|
||||
if (WeaponCurrentMagazineAmmount > 0 && canFire == true && currentlyReloading == false && currentlyPatrolling == false)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= WeaponRange && Vector2.Distance(transform.position, player.transform.position) <= DesiredDistance)
|
||||
{
|
||||
UseWeapon();
|
||||
}
|
||||
}
|
||||
else if (WeaponCurrentMagazineAmmount == 0 && currentlyReloading == false)
|
||||
{
|
||||
ReloadWeapon();
|
||||
}
|
||||
|
||||
//MOVEMENT
|
||||
if (canMove == true)
|
||||
{
|
||||
canFire = false;
|
||||
|
||||
Vector2 direction = (Vector2)path.vectorPath[currentWaypoint] - rb.position;
|
||||
|
||||
if (direction.x > 0) // Move right
|
||||
{
|
||||
rb.AddForce(new Vector2(Speed * 20, rb.linearVelocity.y));
|
||||
}
|
||||
|
||||
if (direction.x < 0) // Move left
|
||||
{
|
||||
rb.AddForce(new Vector2(-1 * Speed * 20, rb.linearVelocity.y));
|
||||
}
|
||||
|
||||
if (direction.y > 1f) // Wants to jump
|
||||
{
|
||||
JumpMethod();
|
||||
}
|
||||
|
||||
// A* logic
|
||||
float distance = Vector2.Distance(rb.position, path.vectorPath[currentWaypoint]);
|
||||
|
||||
if (distance < nextWaypointDistance)
|
||||
{
|
||||
currentWaypoint++;
|
||||
}
|
||||
}
|
||||
|
||||
//GROUND DETECTION
|
||||
//Detecting if the enemy has reached the ground
|
||||
if (GetComponentInChildren<GroundCheck>().isGrounded == true && rb.linearVelocity.y == 0)
|
||||
{
|
||||
canJump = true;
|
||||
if (inFiringCycle == false)
|
||||
{
|
||||
canFire = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
canJump = false;
|
||||
canFire = false;
|
||||
}
|
||||
|
||||
//PATROL & DETECTION
|
||||
// Enemy detects player
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= PlayerDetectionRange)
|
||||
{
|
||||
currentlyPatrolling = false;
|
||||
targetingPlayer = true;
|
||||
canPursue = true;
|
||||
// Get the last know player location by finding which of the positions is closest to the player
|
||||
if (LastKnownPlayerLocation == null)
|
||||
{
|
||||
LastKnownPlayerLocation = gameObject;
|
||||
}
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
if (Vector2.Distance(player.transform.position, pos.transform.position) < Vector2.Distance(player.transform.position, LastKnownPlayerLocation.transform.position))
|
||||
{
|
||||
LastKnownPlayerLocation = pos;
|
||||
}
|
||||
}
|
||||
|
||||
// Angle barrel towards player
|
||||
angle = Mathf.Atan2(player.transform.position.y - barrel.transform.position.y, player.transform.position.x - barrel.transform.position.x) * Mathf.Rad2Deg;
|
||||
|
||||
}
|
||||
// Player has broken line of sight and the enemy will attempt to move to the last known location
|
||||
else if (LastKnownPlayerLocation != null)
|
||||
{
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) > 0.5)
|
||||
{
|
||||
canPursue = false;
|
||||
target = LastKnownPlayerLocation;
|
||||
}
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) < 0.5 && DetermineLineOfSight(player, gameObject) == false)
|
||||
{
|
||||
targetingPlayer = false;
|
||||
LastKnownPlayerLocation = null;
|
||||
}
|
||||
|
||||
// Reset barrel rotation
|
||||
angle = 0f;
|
||||
|
||||
}
|
||||
// Go back to patrol move
|
||||
else
|
||||
{
|
||||
currentlyPatrolling = true;
|
||||
targetingPlayer = false;
|
||||
if (canMove == true && currentlyMovingToNextPatrolTarget == false)
|
||||
{
|
||||
MoveNextPatrol();
|
||||
}
|
||||
|
||||
// Reset barrel rotation
|
||||
angle = 0f;
|
||||
}
|
||||
|
||||
// Rotate barrel towards player
|
||||
Quaternion targetRotation = Quaternion.Euler(new Vector3(0, 0, angle));
|
||||
barrel.transform.rotation = Quaternion.RotateTowards(barrel.transform.rotation, targetRotation, 200 * Time.deltaTime);
|
||||
|
||||
//TARGET DETERMINATION
|
||||
if (canPursue == true)
|
||||
{
|
||||
// If the player moves away (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) > DesiredDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the player is too close to the target (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) < MinumumDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the target is on the other side of the player (CHANGE TARGET)
|
||||
if (Vector2.Distance(transform.position, player.transform.position) < Vector2.Distance(target.transform.position, player.transform.position))
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the player is not within line of sight of the desired position (CHANGE TARGET)
|
||||
if (DetermineLineOfSight(target, player) == false)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the enemy reaches the target
|
||||
if (Vector2.Distance(target.transform.position, transform.position) <= 1 && DetermineLineOfSight(gameObject, player))
|
||||
{
|
||||
if (canFire == false && inFiringCycle == false)
|
||||
{
|
||||
canFire = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MISC METHODS
|
||||
// Perform jump
|
||||
async Task JumpMethod()
|
||||
{
|
||||
if (canJump == true)
|
||||
{
|
||||
rb.linearVelocity = new Vector2(rb.linearVelocity.x, JumpHeight * 12);
|
||||
canJump = false;
|
||||
await Task.Delay(500);
|
||||
}
|
||||
}
|
||||
// Use Ranged Weapon
|
||||
async Task UseWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
canMove = false;
|
||||
inFiringCycle = true;
|
||||
|
||||
WeaponCurrentMagazineAmmount--;
|
||||
|
||||
GameObject Rocket;
|
||||
Rocket = Instantiate(GameObject.Find("EnemyRocket"), new Vector3(transform.position.x + UnityEngine.Random.Range(-0.5f, 0.5f), transform.position.y + UnityEngine.Random.Range(-0.5f, 0.5f), GameObject.Find("EnemyRocket").transform.position.z), Quaternion.identity);
|
||||
Rocket.transform.rotation = Quaternion.Euler(Vector3.forward * UnityEngine.Random.Range(-90, 90));
|
||||
|
||||
// Send it on its way
|
||||
Rocket.GetComponent<Rigidbody2D>().linearVelocity = new Vector2(transform.position.x - player.transform.position.x + UnityEngine.Random.Range(-WeaponRandomSpread, WeaponRandomSpread), transform.position.y - player.transform.position.y + UnityEngine.Random.Range(-WeaponRandomSpread, WeaponRandomSpread)).normalized * WeaponProjectileSpeed * -1;
|
||||
Rocket.transform.rotation = Quaternion.Euler(new Vector3(0, 0, Vector2.SignedAngle(Vector2.right, Rocket.transform.forward) - 90));
|
||||
|
||||
// Variables
|
||||
Rocket.GetComponent<EnemyRocket>().WeaponDamage = WeaponDamage;
|
||||
|
||||
await Task.Delay(WeaponFireRate);
|
||||
|
||||
canFire = true;
|
||||
canMove = true;
|
||||
inFiringCycle = false;
|
||||
}
|
||||
|
||||
// Reload Weapon
|
||||
async Task ReloadWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
canMove = false;
|
||||
//play reload animation
|
||||
currentlyReloading = true;
|
||||
await Task.Delay(WeaponReloadTime);
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
currentlyReloading = false;
|
||||
|
||||
canFire = true;
|
||||
canMove = true;
|
||||
}
|
||||
|
||||
// Part of the patrol cycle
|
||||
async Task MoveNextPatrol()
|
||||
{
|
||||
LastKnownPlayerLocation = null;
|
||||
|
||||
canPursue = false;
|
||||
currentlyMovingToNextPatrolTarget = true;
|
||||
|
||||
//Find Random Position nearby
|
||||
if (Vector2.Distance(transform.position, target.transform.position) <= 0.5 || PatrolPositions.Contains(target) == false)
|
||||
{
|
||||
target = PatrolPositions[UnityEngine.Random.Range(0, PatrolPositions.Count)];
|
||||
await Task.Delay(PatrolStallTime + UnityEngine.Random.Range((PatrolStallTime * -1), PatrolStallTime));
|
||||
}
|
||||
|
||||
currentlyMovingToNextPatrolTarget = false;
|
||||
|
||||
}
|
||||
|
||||
// General Utility
|
||||
bool DetermineLineOfSight(GameObject object1, GameObject object2)
|
||||
{
|
||||
Vector3 RaycastStart = object1.transform.position;
|
||||
Vector3 RaycastDirection = (object2.transform.position - object1.transform.position).normalized;
|
||||
float RaycastDistance = Vector3.Distance(object2.transform.position, object1.transform.position);
|
||||
|
||||
if (Physics2D.Raycast(RaycastStart, RaycastDirection, RaycastDistance, LayerMask.GetMask("SolidGround")) == false)
|
||||
{
|
||||
Debug.DrawRay(RaycastStart, RaycastDirection * RaycastDistance);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Part of the pursue cycle
|
||||
void ComputeClosestPositionToPlayer()
|
||||
{
|
||||
canFire = false;
|
||||
canMove = true;
|
||||
|
||||
SuitablePositions.Clear();
|
||||
foreach (GameObject query in AllPositions)
|
||||
{
|
||||
// Check the distance of the position
|
||||
if (Vector2.Distance(query.transform.position, player.transform.position) < DesiredDistance && Vector2.Distance(query.transform.position, player.transform.position) >= MinumumDistance)
|
||||
{
|
||||
// Check line of sight of the position
|
||||
if (DetermineLineOfSight(query, player))
|
||||
{
|
||||
SuitablePositions.Add(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (SuitablePositions.Count > 0)
|
||||
{
|
||||
target = SuitablePositions[UnityEngine.Random.Range(0, SuitablePositions.Count)];
|
||||
foreach (GameObject pos in SuitablePositions)
|
||||
{
|
||||
//Find the point that is closest to the enemy
|
||||
if (Vector2.Distance(transform.position, pos.transform.position) < Vector2.Distance(transform.position, target.transform.position))
|
||||
{
|
||||
target = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cade0ad7c945f4047a65286465645db6
|
||||
@@ -0,0 +1 @@
|
||||
uid://chiq1pru4dj02
|
||||
@@ -0,0 +1,468 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Pathfinding;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
//Designed by Jacob Weedman
|
||||
//Use on Sniper type enemies
|
||||
|
||||
public class SniperAI : MonoBehaviour
|
||||
{
|
||||
|
||||
//MISC
|
||||
GameObject target;
|
||||
float nextWaypointDistance = 5;
|
||||
Path path;
|
||||
int currentWaypoint = 0;
|
||||
bool reachedEndOfPath = false;
|
||||
GameObject projectile;
|
||||
GameObject player;
|
||||
GameObject barrel;
|
||||
public List<GameObject> SuitablePositions;
|
||||
public List<GameObject> AllPositions;
|
||||
float DistanceFromPlayer;
|
||||
Vector2 StartPosition;
|
||||
public List<GameObject> PatrolPositions;
|
||||
public GameObject LastKnownPlayerLocation;
|
||||
|
||||
//CONDITIONS/GENERAL INFORMATION
|
||||
bool canJump = true;
|
||||
bool canMove = true; // Prevent all movement
|
||||
bool canPursue = false; // Follow player
|
||||
bool canFire = false;
|
||||
bool currentlyReloading = false;
|
||||
bool currentlyPatrolling;
|
||||
bool currentlyMovingToNextPatrolTarget = false;
|
||||
float DesiredDistance;
|
||||
float MinumumDistance = 10f;
|
||||
bool targetingPlayer = false;
|
||||
bool inFiringCycle = false;
|
||||
int WeaponCurrentMagazineAmmount;
|
||||
float angle;
|
||||
|
||||
//ENEMY STATS (Changeable)
|
||||
public float Speed = 0.4f; // In relation to player's walking speed
|
||||
public float JumpHeight = 0.9f; // In relation to player's regular jump height
|
||||
public float Health = 50;
|
||||
public float PatrolDistance = 3;
|
||||
public int PatrolStallTime = 2000; //ms
|
||||
public int PlayerDetectionRange = 50;
|
||||
|
||||
//WEAPON STATS (CHANGEABLE)
|
||||
public int WeaponDamage = 25; // Damage per hit
|
||||
public int WeaponFireRate = 1000; // Delay in time between attacks both melee and ranged
|
||||
public float WeaponRandomSpread = 0.5f; // Random direction of lanched projectiles
|
||||
public int WeaponRange = 45; // Maximum range of the projectile before it drops off
|
||||
public float WeaponProjectileSpeed = 100f; // Speed of launched projectiles
|
||||
public int WeaponMagazineSize = 5; // Number of shots the enemy will take before having to reload
|
||||
public int WeaponReloadTime = 6000; // Time it takes to reload the magazine
|
||||
|
||||
//REFERENCES
|
||||
Seeker seeker;
|
||||
Rigidbody2D rb;
|
||||
|
||||
//ONCE THE GAME STARTS
|
||||
void Start()
|
||||
{
|
||||
seeker = GetComponent<Seeker>();
|
||||
rb = GetComponent<Rigidbody2D>();
|
||||
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
|
||||
StartPosition = transform.position;
|
||||
AllPositions = GameObject.FindGameObjectsWithTag("PossiblePositions").ToList();
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
if (Vector2.Distance(pos.transform.position, StartPosition) <= PatrolDistance)
|
||||
{
|
||||
PatrolPositions.Add(pos);
|
||||
}
|
||||
}
|
||||
target = PatrolPositions[UnityEngine.Random.Range(0, PatrolPositions.Count)];
|
||||
|
||||
projectile = GameObject.Find("EnemyProjectile");
|
||||
player = GameObject.FindGameObjectWithTag("Player");
|
||||
barrel = transform.Find("Barrel").gameObject;
|
||||
LastKnownPlayerLocation = null;
|
||||
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
DesiredDistance = WeaponRange - 5;
|
||||
|
||||
InvokeRepeating("UpdatePath", 0f, 0.1f);
|
||||
InvokeRepeating("PathfindingTimeout", 0f, 10);
|
||||
|
||||
}
|
||||
|
||||
// When enemy has reached the next node
|
||||
void OnPathComplete(Path p)
|
||||
{
|
||||
if (!p.error)
|
||||
{
|
||||
path = p;
|
||||
currentWaypoint = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Select next node
|
||||
void UpdatePath()
|
||||
{
|
||||
if (seeker.IsDone() && target != null)
|
||||
{
|
||||
seeker.StartPath(rb.position, target.transform.position, OnPathComplete);
|
||||
}
|
||||
}
|
||||
|
||||
// Pathfiniding Timeout
|
||||
void PathfindingTimeout()
|
||||
{
|
||||
if (Vector2.Distance(transform.position, target.transform.position) > 0.5)
|
||||
{
|
||||
target = gameObject;
|
||||
MoveNextPatrol();
|
||||
}
|
||||
}
|
||||
|
||||
//MAIN LOGIC
|
||||
void FixedUpdate()
|
||||
{
|
||||
//DEATH
|
||||
if (Health <= 0)
|
||||
{
|
||||
GameObject DeadBody;
|
||||
DeadBody = Instantiate(gameObject, transform.position, Quaternion.identity);
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<TestEnemyAIGroundRanged>());
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<Seeker>());
|
||||
|
||||
foreach (Transform child in DeadBody.transform) {
|
||||
GameObject.Destroy(child.gameObject);
|
||||
}
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
//ICONS
|
||||
if (targetingPlayer)
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
if (currentlyReloading)
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
//if (90 <= angle || angle <= 270)
|
||||
//{
|
||||
// barrel.transform.localScale = new Vector2(-barrel.transform.localScale.x, barrel.transform.localScale.y);
|
||||
//}
|
||||
|
||||
//MISC PATHFINDING
|
||||
if (path == null)
|
||||
return;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
if (currentWaypoint >= path.vectorPath.Count)
|
||||
{
|
||||
reachedEndOfPath = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
reachedEndOfPath = false;
|
||||
}
|
||||
|
||||
//RANGED ATTACK
|
||||
// Check if enemy has line of sight on the player & if they are in the acceptable range
|
||||
if (WeaponCurrentMagazineAmmount > 0 && canFire == true && currentlyReloading == false && currentlyPatrolling == false)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= WeaponRange && Vector2.Distance(transform.position, player.transform.position) <= DesiredDistance)
|
||||
{
|
||||
UseWeapon();
|
||||
}
|
||||
}
|
||||
else if (WeaponCurrentMagazineAmmount == 0 && currentlyReloading == false)
|
||||
{
|
||||
ReloadWeapon();
|
||||
}
|
||||
|
||||
//MOVEMENT
|
||||
if (canMove == true)
|
||||
{
|
||||
canFire = false;
|
||||
|
||||
Vector2 direction = (Vector2)path.vectorPath[currentWaypoint] - rb.position;
|
||||
//float xDirection = target.transform.position.x - transform.position.x;
|
||||
|
||||
if (direction.x > 0) // Move right
|
||||
{
|
||||
rb.AddForce(new Vector2(Speed * 20, rb.linearVelocity.y));
|
||||
}
|
||||
|
||||
if (direction.x < 0) // Move left
|
||||
{
|
||||
rb.AddForce(new Vector2(-1 * Speed * 20, rb.linearVelocity.y));
|
||||
}
|
||||
|
||||
if (direction.y > 1f) // Wants to jump
|
||||
{
|
||||
JumpMethod();
|
||||
}
|
||||
|
||||
// A* logic
|
||||
float distance = Vector2.Distance(rb.position, path.vectorPath[currentWaypoint]);
|
||||
|
||||
if (distance < nextWaypointDistance)
|
||||
{
|
||||
currentWaypoint++;
|
||||
}
|
||||
}
|
||||
|
||||
//GROUND DETECTION
|
||||
//Detecting if the enemy has reached the ground
|
||||
if (GetComponentInChildren<GroundCheck>().isGrounded == true && rb.linearVelocity.y == 0)
|
||||
{
|
||||
canJump = true;
|
||||
if (inFiringCycle == false)
|
||||
{
|
||||
canFire = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
canJump = false;
|
||||
canFire = false;
|
||||
}
|
||||
|
||||
//PATROL & DETECTION
|
||||
// Enemy detects player
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= PlayerDetectionRange)
|
||||
{
|
||||
currentlyPatrolling = false;
|
||||
targetingPlayer = true;
|
||||
canPursue = true;
|
||||
// Get the last know player location by finding which of the positions is closest to the player
|
||||
if (LastKnownPlayerLocation == null)
|
||||
{
|
||||
LastKnownPlayerLocation = gameObject;
|
||||
}
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
if (Vector2.Distance(player.transform.position, pos.transform.position) < Vector2.Distance(player.transform.position, LastKnownPlayerLocation.transform.position))
|
||||
{
|
||||
LastKnownPlayerLocation = pos;
|
||||
}
|
||||
}
|
||||
|
||||
// Angle barrel towards player
|
||||
angle = Mathf.Atan2(player.transform.position.y - barrel.transform.position.y, player.transform.position.x - barrel.transform.position.x) * Mathf.Rad2Deg;
|
||||
|
||||
}
|
||||
// Player has broken line of sight and the enemy will attempt to move to the last known location
|
||||
else if (LastKnownPlayerLocation != null)
|
||||
{
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) > 0.5)
|
||||
{
|
||||
canPursue = false;
|
||||
target = LastKnownPlayerLocation;
|
||||
}
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) < 0.5 && DetermineLineOfSight(player, gameObject) == false)
|
||||
{
|
||||
targetingPlayer = false;
|
||||
LastKnownPlayerLocation = null;
|
||||
}
|
||||
|
||||
// Reset barrel rotation
|
||||
angle = 0f;
|
||||
|
||||
}
|
||||
// Go back to patrol move
|
||||
else
|
||||
{
|
||||
currentlyPatrolling = true;
|
||||
targetingPlayer = false;
|
||||
if (canMove == true && currentlyMovingToNextPatrolTarget == false)
|
||||
{
|
||||
MoveNextPatrol();
|
||||
}
|
||||
|
||||
// Reset barrel rotation
|
||||
angle = 0f;
|
||||
}
|
||||
|
||||
// Rotate barrel towards player
|
||||
Quaternion targetRotation = Quaternion.Euler(new Vector3(0, 0, angle));
|
||||
barrel.transform.rotation = Quaternion.RotateTowards(barrel.transform.rotation, targetRotation, 100 * Time.deltaTime);
|
||||
|
||||
//TARGET DETERMINATION
|
||||
if (canPursue == true)
|
||||
{
|
||||
|
||||
// If the player moves away (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) > DesiredDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the player is too close to the target (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) < MinumumDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the target is on the other side of the player (CHANGE TARGET)
|
||||
if (Vector2.Distance(transform.position, player.transform.position) < Vector2.Distance(target.transform.position, player.transform.position))
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the player is not within line of sight of the desired position (CHANGE TARGET)
|
||||
if (DetermineLineOfSight(target, player) == false)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the enemy reaches the target
|
||||
if (Vector2.Distance(target.transform.position, transform.position) <= 1 && DetermineLineOfSight(gameObject, player))
|
||||
{
|
||||
if (canFire == false && inFiringCycle == false)
|
||||
{
|
||||
canFire = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MISC METHODS
|
||||
// Perform jump
|
||||
async Task JumpMethod()
|
||||
{
|
||||
if (canJump == true)
|
||||
{
|
||||
rb.linearVelocity = new Vector2(rb.linearVelocity.x, JumpHeight * 12);
|
||||
canJump = false;
|
||||
await Task.Delay(500);
|
||||
}
|
||||
}
|
||||
// Use Ranged Weapon
|
||||
async Task UseWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
canMove = false;
|
||||
inFiringCycle = true;
|
||||
|
||||
// Create Projectile
|
||||
GameObject BulletInstance;
|
||||
BulletInstance = Instantiate(projectile, transform.position, Quaternion.LookRotation(transform.position - GameObject.FindGameObjectWithTag("Player").transform.position + new Vector3(UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), 0)));
|
||||
BulletInstance.transform.parent = transform;
|
||||
|
||||
// Send it on it's way
|
||||
BulletInstance.GetComponent<Rigidbody2D>().linearVelocity = BulletInstance.transform.forward * -1 * WeaponProjectileSpeed;
|
||||
BulletInstance.transform.rotation = Quaternion.Euler(new Vector3(0, 0, Vector2.SignedAngle(Vector2.right, BulletInstance.transform.forward) - 90));
|
||||
WeaponCurrentMagazineAmmount--;
|
||||
|
||||
await Task.Delay(WeaponFireRate);
|
||||
|
||||
canFire = true;
|
||||
canMove = true;
|
||||
inFiringCycle = false;
|
||||
}
|
||||
|
||||
// Reload Weapon
|
||||
async Task ReloadWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
canMove = false;
|
||||
//play reload animation
|
||||
currentlyReloading = true;
|
||||
await Task.Delay(WeaponReloadTime);
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
currentlyReloading = false;
|
||||
|
||||
canFire = true;
|
||||
canMove = true;
|
||||
}
|
||||
|
||||
// Part of the patrol cycle
|
||||
async Task MoveNextPatrol()
|
||||
{
|
||||
LastKnownPlayerLocation = null;
|
||||
|
||||
canPursue = false;
|
||||
currentlyMovingToNextPatrolTarget = true;
|
||||
|
||||
//Find Random Position nearby
|
||||
if (Vector2.Distance(transform.position, target.transform.position) <= 0.5 || PatrolPositions.Contains(target) == false)
|
||||
{
|
||||
target = PatrolPositions[UnityEngine.Random.Range(0, PatrolPositions.Count)];
|
||||
await Task.Delay(PatrolStallTime + UnityEngine.Random.Range((PatrolStallTime * -1), PatrolStallTime));
|
||||
}
|
||||
|
||||
currentlyMovingToNextPatrolTarget = false;
|
||||
|
||||
}
|
||||
|
||||
// General Utility
|
||||
bool DetermineLineOfSight(GameObject object1, GameObject object2)
|
||||
{
|
||||
Vector3 RaycastStart = object1.transform.position;
|
||||
Vector3 RaycastDirection = (object2.transform.position - object1.transform.position).normalized;
|
||||
float RaycastDistance = Vector3.Distance(object2.transform.position, object1.transform.position);
|
||||
|
||||
if (Physics2D.Raycast(RaycastStart, RaycastDirection, RaycastDistance, LayerMask.GetMask("SolidGround")) == false)
|
||||
{
|
||||
Debug.DrawRay(RaycastStart, RaycastDirection * RaycastDistance);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Part of the pursue cycle
|
||||
void ComputeClosestPositionToPlayer()
|
||||
{
|
||||
canFire = false;
|
||||
canMove = true;
|
||||
|
||||
SuitablePositions.Clear();
|
||||
foreach (GameObject query in AllPositions)
|
||||
{
|
||||
// Check the distance of the position
|
||||
if (Vector2.Distance(query.transform.position, player.transform.position) < DesiredDistance && Vector2.Distance(query.transform.position, player.transform.position) >= MinumumDistance)
|
||||
{
|
||||
// Check line of sight of the position
|
||||
if (DetermineLineOfSight(query, player))
|
||||
{
|
||||
SuitablePositions.Add(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (SuitablePositions.Count > 0)
|
||||
{
|
||||
target = SuitablePositions[UnityEngine.Random.Range(0, SuitablePositions.Count)];
|
||||
foreach (GameObject pos in SuitablePositions)
|
||||
{
|
||||
//Find the point that is closest to the enemy
|
||||
if (Vector2.Distance(transform.position, pos.transform.position) < Vector2.Distance(transform.position, target.transform.position))
|
||||
{
|
||||
target = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 198b98dba749aa845975d64154c7837a
|
||||
@@ -0,0 +1 @@
|
||||
uid://wbr8l875pltc
|
||||
@@ -0,0 +1,186 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Pathfinding;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
//Designed by Jacob Weedman
|
||||
//Use on sniper turrets
|
||||
|
||||
public class SniperTurretAI : MonoBehaviour
|
||||
{
|
||||
|
||||
//MISC
|
||||
GameObject projectile;
|
||||
GameObject player;
|
||||
GameObject barrel;
|
||||
float DistanceFromPlayer;
|
||||
Vector2 StartPosition;
|
||||
|
||||
//CONDITIONS/GENERAL INFORMATION
|
||||
bool canFire = true;
|
||||
bool currentlyReloading = false;
|
||||
bool targetingPlayer = false;
|
||||
bool inFiringCycle = false;
|
||||
int WeaponCurrentMagazineAmmount;
|
||||
float angle;
|
||||
|
||||
//ENEMY STATS (Changeable)
|
||||
public float Health = 100;
|
||||
public int PlayerDetectionRange = 60;
|
||||
|
||||
//WEAPON STATS (CHANGEABLE)
|
||||
public int WeaponDamage = 30; // Damage per hit
|
||||
public int WeaponFireRate = 2500; // Delay in time between attacks both melee and ranged
|
||||
public float WeaponRandomSpread = 1.5f; // Random direction of lanched projectiles
|
||||
public int WeaponRange = 50; // Maximum range of the projectile before it drops off
|
||||
public float WeaponProjectileSpeed = 80f; // Speed of launched projectiles
|
||||
public int WeaponMagazineSize = 20; // Number of shots the enemy will take before having to reload
|
||||
public int WeaponReloadTime = 10000; // Time it takes to reload the magazine
|
||||
|
||||
//ONCE THE GAME STARTS
|
||||
void Start()
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
|
||||
projectile = GameObject.Find("EnemyProjectile");
|
||||
player = GameObject.FindGameObjectWithTag("Player");
|
||||
barrel = transform.Find("Barrel").gameObject;
|
||||
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
|
||||
canFire = true;
|
||||
}
|
||||
|
||||
//MAIN LOGIC
|
||||
void FixedUpdate()
|
||||
{
|
||||
DistanceFromPlayer = Vector2.Distance(transform.position, player.transform.position);
|
||||
|
||||
// Death
|
||||
if (Health <= 0)
|
||||
{
|
||||
GameObject DeadBody;
|
||||
DeadBody = Instantiate(gameObject, transform.position, Quaternion.identity);
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<TestEnemyAIGroundRanged>());
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<Seeker>());
|
||||
|
||||
foreach (Transform child in DeadBody.transform) {
|
||||
GameObject.Destroy(child.gameObject);
|
||||
}
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
|
||||
//ICONS
|
||||
if (targetingPlayer)
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
if (currentlyReloading)
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
//RANGED ATTACK
|
||||
// Check if enemy has line of sight on the player & if they are in the acceptable range
|
||||
if (WeaponCurrentMagazineAmmount > 0 && canFire == true && currentlyReloading == false)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= WeaponRange)
|
||||
{
|
||||
UseWeapon();
|
||||
}
|
||||
}
|
||||
else if (WeaponCurrentMagazineAmmount == 0 && currentlyReloading == false)
|
||||
{
|
||||
ReloadWeapon();
|
||||
}
|
||||
|
||||
//PLAYER TARGETING
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= WeaponRange + 10)
|
||||
{
|
||||
targetingPlayer = true;
|
||||
|
||||
// Set angle to player
|
||||
angle = Mathf.Atan2(player.transform.position.y - barrel.transform.position.y, player.transform.position.x - barrel.transform.position.x) * Mathf.Rad2Deg;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reset barrel rotation
|
||||
angle = 90f;
|
||||
}
|
||||
|
||||
// Rotate barrel towards player
|
||||
Quaternion targetRotation = Quaternion.Euler(new Vector3(0, 0, angle));
|
||||
barrel.transform.rotation = Quaternion.RotateTowards(barrel.transform.rotation, targetRotation, 100 * Time.deltaTime);
|
||||
|
||||
}
|
||||
|
||||
//MISC METHODS
|
||||
// Use Ranged Weapon
|
||||
async Task UseWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
inFiringCycle = true;
|
||||
|
||||
// Create Projectile
|
||||
GameObject BulletInstance;
|
||||
BulletInstance = Instantiate(projectile, transform.position, Quaternion.LookRotation(transform.position - GameObject.FindGameObjectWithTag("Player").transform.position + new Vector3(UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), 0)));
|
||||
BulletInstance.transform.parent = transform;
|
||||
|
||||
// Send it on it's way
|
||||
BulletInstance.GetComponent<Rigidbody2D>().linearVelocity = BulletInstance.transform.forward * -1 * WeaponProjectileSpeed;
|
||||
BulletInstance.transform.rotation = Quaternion.Euler(new Vector3(0, 0, Vector2.SignedAngle(Vector2.right, BulletInstance.transform.forward) - 90));
|
||||
WeaponCurrentMagazineAmmount--;
|
||||
|
||||
await Task.Delay(WeaponFireRate);
|
||||
|
||||
canFire = true;
|
||||
inFiringCycle = false;
|
||||
}
|
||||
|
||||
// Reload Weapon
|
||||
async Task ReloadWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
//play reload animation
|
||||
currentlyReloading = true;
|
||||
await Task.Delay(WeaponReloadTime);
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
currentlyReloading = false;
|
||||
|
||||
canFire = true;
|
||||
}
|
||||
|
||||
// General Utility
|
||||
bool DetermineLineOfSight(GameObject object1, GameObject object2)
|
||||
{
|
||||
Vector3 RaycastStart = object1.transform.position;
|
||||
Vector3 RaycastDirection = (object2.transform.position - object1.transform.position).normalized;
|
||||
float RaycastDistance = Vector3.Distance(object2.transform.position, object1.transform.position);
|
||||
|
||||
if (Physics2D.Raycast(RaycastStart, RaycastDirection, RaycastDistance, LayerMask.GetMask("SolidGround")) == false)
|
||||
{
|
||||
Debug.DrawRay(RaycastStart, RaycastDirection * RaycastDistance);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4029e0cfa8f769c4ca76d5b3da3ea1c9
|
||||
@@ -0,0 +1 @@
|
||||
uid://ememlrxy8md8
|
||||
@@ -0,0 +1,367 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Pathfinding;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
//Designed by Jacob Weedman
|
||||
//Use on switchblade drone enemies
|
||||
|
||||
public class SwitchbladeAI : MonoBehaviour
|
||||
{
|
||||
//MISC
|
||||
public GameObject target;
|
||||
public float nextWaypointDistance = 5;
|
||||
Path path;
|
||||
int currentWaypoint = 0;
|
||||
bool reachedEndOfPath = false;
|
||||
GameObject projectile;
|
||||
GameObject player;
|
||||
GameObject barrel;
|
||||
public List<GameObject> AllPositions;
|
||||
public float DistanceFromPlayer;
|
||||
Vector2 StartPosition;
|
||||
public List<GameObject> PatrolPositions;
|
||||
public GameObject LastKnownPlayerLocation;
|
||||
|
||||
//CONDITIONS/GENERAL INFORMATION
|
||||
public bool canMove = true;
|
||||
public bool canPursue = false;
|
||||
public bool canFire = true;
|
||||
public bool currentlyReloading = false;
|
||||
public bool currentlyPatrolling;
|
||||
public bool currentlyMovingToNextPatrolTarget = false;
|
||||
public float DesiredDistance = 15;
|
||||
int MinumumDistance = 10;
|
||||
public bool targetingPlayer = false;
|
||||
public bool inFiringCycle = false;
|
||||
public bool primed = false;
|
||||
|
||||
//ENEMY STATS (Changeable)
|
||||
float Speed = 0.7f; // In relation to player's walking speed
|
||||
float Health = 5;
|
||||
float PatrolDistance = 10;
|
||||
int PatrolStallTime = 1500; //ms
|
||||
int PlayerDetectionRange = 25;
|
||||
int WeaponFireRate = 1000;
|
||||
int WeaponRange = 30;
|
||||
int WeaponDamage = 20;
|
||||
float dashSpeed = 40f;
|
||||
|
||||
Seeker seeker;
|
||||
Rigidbody2D rb;
|
||||
|
||||
//START OF THE GAME
|
||||
void Start()
|
||||
{
|
||||
seeker = GetComponent<Seeker>();
|
||||
rb = GetComponent<Rigidbody2D>();
|
||||
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
|
||||
StartPosition = transform.position;
|
||||
AllPositions = GameObject.FindGameObjectsWithTag("PossiblePositions").ToList();
|
||||
|
||||
target = Instantiate(GameObject.Find("FlyingTarget"), transform.position, Quaternion.identity);
|
||||
|
||||
player = GameObject.FindGameObjectWithTag("Player");
|
||||
LastKnownPlayerLocation = null;
|
||||
|
||||
InvokeRepeating("UpdatePath", 0f, 0.1f);
|
||||
InvokeRepeating("PathfindingTimeout", 0f, 30);
|
||||
}
|
||||
|
||||
// When enemy has reached the next node
|
||||
void OnPathComplete(Path p)
|
||||
{
|
||||
if (!p.error)
|
||||
{
|
||||
path = p;
|
||||
currentWaypoint = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Select next node
|
||||
void UpdatePath()
|
||||
{
|
||||
if (seeker.IsDone())
|
||||
{
|
||||
seeker.StartPath(rb.position, target.transform.position, OnPathComplete);
|
||||
}
|
||||
}
|
||||
|
||||
// Pathfiniding Timeout
|
||||
void PathfindingTimeout()
|
||||
{
|
||||
if (Vector2.Distance(transform.position, target.transform.position) > 0.5)
|
||||
{
|
||||
//target = gameObject;
|
||||
targetingPlayer = false;
|
||||
LastKnownPlayerLocation = null;
|
||||
MoveNextPatrol();
|
||||
}
|
||||
}
|
||||
|
||||
//MAIN LOGIC
|
||||
void FixedUpdate()
|
||||
{
|
||||
// Death
|
||||
if (Health <= 0)
|
||||
{
|
||||
Explode();
|
||||
}
|
||||
|
||||
// Explode when close to the player
|
||||
if (primed && Vector2.Distance(player.transform.position, transform.position) <= 2)
|
||||
{
|
||||
Explode();
|
||||
}
|
||||
|
||||
// See where the enemy wants to go (DEBUGGING ONLY)
|
||||
if (target != null)
|
||||
{
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
pos.GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
target.GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
|
||||
//CHANGE ICONS
|
||||
if (targetingPlayer)
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
//MISC PATHFINDING
|
||||
if (path == null)
|
||||
return;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
if (currentWaypoint >= path.vectorPath.Count)
|
||||
{
|
||||
reachedEndOfPath = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
reachedEndOfPath = false;
|
||||
}
|
||||
|
||||
//DIVE BOMB
|
||||
// Check if enemy has line of sight on the player & if they are in the acceptable range
|
||||
if (canFire == true && currentlyReloading == false && currentlyPatrolling == false)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == true && inFiringCycle == false && Vector2.Distance(transform.position, player.transform.position) <= WeaponRange)
|
||||
{
|
||||
UseWeapon();
|
||||
}
|
||||
}
|
||||
|
||||
//MOVEMENT
|
||||
if (canMove == true)
|
||||
{
|
||||
Vector2 direction = ((Vector2)path.vectorPath[currentWaypoint] - rb.position).normalized;
|
||||
Vector2 force = direction * Speed * 20;
|
||||
|
||||
rb.AddForce(force);
|
||||
|
||||
// A* logic
|
||||
float distance = Vector2.Distance(rb.position, path.vectorPath[currentWaypoint]);
|
||||
|
||||
if (distance < nextWaypointDistance)
|
||||
{
|
||||
currentWaypoint++;
|
||||
}
|
||||
}
|
||||
|
||||
//PATROL
|
||||
// Enemy detects player
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= PlayerDetectionRange)
|
||||
{
|
||||
currentlyPatrolling = false;
|
||||
targetingPlayer = true;
|
||||
canPursue = true;
|
||||
primed = true;
|
||||
|
||||
// Get the last know player location by finding which of the positions is closest to the player
|
||||
if (LastKnownPlayerLocation == null)
|
||||
{
|
||||
LastKnownPlayerLocation = gameObject;
|
||||
}
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
if (Vector2.Distance(player.transform.position, pos.transform.position) < Vector2.Distance(player.transform.position, LastKnownPlayerLocation.transform.position))
|
||||
{
|
||||
LastKnownPlayerLocation = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Player has broken line of sight and the enemy will attempt to move to the last known location
|
||||
else if (LastKnownPlayerLocation != null)
|
||||
{
|
||||
primed = false;
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) > 0.5)
|
||||
{
|
||||
canPursue = false;
|
||||
target.transform.position = LastKnownPlayerLocation.transform.position;
|
||||
}
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) < 0.5 && DetermineLineOfSight(player, gameObject) == false)
|
||||
{
|
||||
targetingPlayer = false;
|
||||
LastKnownPlayerLocation = null;
|
||||
}
|
||||
}
|
||||
// Go back to patrol move
|
||||
else
|
||||
{
|
||||
LastKnownPlayerLocation = gameObject;
|
||||
currentlyPatrolling = true;
|
||||
targetingPlayer = false;
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Call patrol method
|
||||
if (currentlyPatrolling)
|
||||
{
|
||||
if (canMove == true && currentlyMovingToNextPatrolTarget == false)
|
||||
{
|
||||
MoveNextPatrol();
|
||||
}
|
||||
}
|
||||
|
||||
//TARGET DETERMINATION
|
||||
if (canPursue == true)
|
||||
{
|
||||
/*
|
||||
// If the player moves away (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) > DesiredDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
*/
|
||||
|
||||
//If the player is too close to the target (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) < MinumumDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
|
||||
// If the player is not within line of sight of the desired position (CHANGE TARGET)
|
||||
if (DetermineLineOfSight(target, player) == false)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the enemy reaches the target
|
||||
if (Vector2.Distance(target.transform.position, transform.position) <= 1)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == false) // If the enemy has LOS on the player
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MISC METHODS
|
||||
// Attempt to explode on the player
|
||||
async Task UseWeapon()
|
||||
{
|
||||
inFiringCycle = true;
|
||||
|
||||
await Task.Delay(3000); // Dash delay ms
|
||||
|
||||
//Dash into player
|
||||
rb.linearVelocity = new Vector2(transform.position.x - player.transform.position.x, transform.position.y - player.transform.position.y).normalized * dashSpeed * -2f;
|
||||
|
||||
inFiringCycle = false;
|
||||
}
|
||||
|
||||
// Part of the patrol cycle
|
||||
async Task MoveNextPatrol()
|
||||
{
|
||||
LastKnownPlayerLocation = gameObject;
|
||||
target.transform.position = transform.position;
|
||||
|
||||
canPursue = false;
|
||||
currentlyMovingToNextPatrolTarget = true;
|
||||
|
||||
//Find Random Position nearby
|
||||
target.transform.position = new Vector2((StartPosition.x + UnityEngine.Random.Range(-1 * PatrolDistance, PatrolDistance)), (StartPosition.y + UnityEngine.Random.Range(-1 * PatrolDistance, PatrolDistance)));
|
||||
await Task.Delay(PatrolStallTime + UnityEngine.Random.Range((PatrolStallTime * -1), PatrolStallTime));
|
||||
|
||||
currentlyMovingToNextPatrolTarget = false;
|
||||
}
|
||||
|
||||
// General Utility
|
||||
bool DetermineLineOfSight(GameObject object1, GameObject object2)
|
||||
{
|
||||
Vector3 RaycastStart = object1.transform.position;
|
||||
Vector3 RaycastDirection = (object2.transform.position - object1.transform.position).normalized;
|
||||
float RaycastDistance = Vector3.Distance(object2.transform.position, object1.transform.position);
|
||||
|
||||
if (Physics2D.Raycast(RaycastStart, RaycastDirection, RaycastDistance, LayerMask.GetMask("SolidGround")) == false)
|
||||
{
|
||||
Debug.DrawRay(RaycastStart, RaycastDirection * RaycastDistance);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Part of the pursue cycle
|
||||
void ComputeClosestPositionToPlayer()
|
||||
{
|
||||
canMove = true;
|
||||
// find a target in the air a set radius away from the player
|
||||
if (DetermineLineOfSight(player, gameObject))
|
||||
{
|
||||
target.transform.position = new Vector2(player.transform.position.x + UnityEngine.Random.Range(-MinumumDistance, MinumumDistance), player.transform.position.y + MinumumDistance);
|
||||
}
|
||||
}
|
||||
|
||||
// Explode
|
||||
void Explode()
|
||||
{
|
||||
//Create Explosion
|
||||
GameObject Explosion;
|
||||
Explosion = Instantiate(GameObject.Find("Explosion"), new Vector3(transform.position.x, transform.position.y, GameObject.Find("Explosion").transform.position.z), Quaternion.identity);
|
||||
Explosion.transform.rotation = Quaternion.Euler(Vector3.forward);
|
||||
|
||||
//Set Variables
|
||||
Explosion.GetComponent<EnemyParticleWeapon>().destroy = true;
|
||||
Explosion.GetComponent<EnemyParticleWeapon>().opacity = true;
|
||||
Explosion.GetComponent<EnemyParticleWeapon>().timer = 3;
|
||||
Explosion.GetComponent<EnemyParticleWeapon>().damageAmmount = WeaponDamage;
|
||||
|
||||
//Delete Enemy
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
void OnTriggerEnter2D(Collider2D collision)
|
||||
{
|
||||
if (primed)
|
||||
{
|
||||
if (collision.gameObject.tag == "Ground")
|
||||
{
|
||||
if (collision.gameObject.layer == 6)
|
||||
{
|
||||
Explode();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 80de3fa3d92ba7548b32e6bbd98c513a
|
||||
@@ -0,0 +1 @@
|
||||
uid://b0w350q6bo8kq
|
||||
@@ -0,0 +1,415 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Pathfinding;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
//Designed by Jacob Weedman
|
||||
//Use on tank type enemies
|
||||
|
||||
public class TankAI : MonoBehaviour
|
||||
{
|
||||
|
||||
//MISC
|
||||
GameObject target;
|
||||
float nextWaypointDistance = 5;
|
||||
Path path;
|
||||
int currentWaypoint = 0;
|
||||
bool reachedEndOfPath = false;
|
||||
GameObject projectile;
|
||||
GameObject player;
|
||||
GameObject barrel;
|
||||
public List<GameObject> SuitablePositions;
|
||||
public List<GameObject> AllPositions;
|
||||
float DistanceFromPlayer;
|
||||
Vector2 StartPosition;
|
||||
public List<GameObject> PatrolPositions;
|
||||
public GameObject LastKnownPlayerLocation;
|
||||
|
||||
//CONDITIONS/GENERAL INFORMATION
|
||||
bool canMove = true; // Prevent all movement
|
||||
bool canPursue = false; // Follow player
|
||||
bool canFire = true;
|
||||
bool currentlyReloading = false;
|
||||
bool currentlyPatrolling;
|
||||
bool currentlyMovingToNextPatrolTarget = false;
|
||||
float DesiredDistance;
|
||||
float MinumumDistance = 5f;
|
||||
bool targetingPlayer = false;
|
||||
bool inFiringCycle = false;
|
||||
int WeaponCurrentMagazineAmmount;
|
||||
float angle;
|
||||
|
||||
//ENEMY STATS (Changeable)
|
||||
public float Speed = 0.5f; // In relation to player's walking speed
|
||||
public float Health = 200;
|
||||
public float PatrolDistance = 10;
|
||||
public int PatrolStallTime = 200; //ms
|
||||
public int PlayerDetectionRange = 50;
|
||||
|
||||
//WEAPON STATS (CHANGEABLE)
|
||||
public int WeaponDamage = 15; // Damage per hit
|
||||
public int WeaponFireRate = 1000; // Delay in time between attacks both melee and ranged
|
||||
public float WeaponRandomSpread = 5f; // Random direction of lanched projectiles
|
||||
public int WeaponRange = 40; // Maximum range of the projectile before it drops off
|
||||
public float WeaponProjectileSpeed = 40f; // Speed of launched projectiles
|
||||
public int WeaponMagazineSize = 30; // Number of shots the enemy will take before having to reload
|
||||
public int WeaponReloadTime = 6000; // Time it takes to reload the magazine
|
||||
|
||||
//REFERENCES
|
||||
Seeker seeker;
|
||||
Rigidbody2D rb;
|
||||
|
||||
//ONCE THE GAME STARTS
|
||||
void Start()
|
||||
{
|
||||
seeker = GetComponent<Seeker>();
|
||||
rb = GetComponent<Rigidbody2D>();
|
||||
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
|
||||
StartPosition = transform.position;
|
||||
AllPositions = GameObject.FindGameObjectsWithTag("PossiblePositions").ToList();
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
if (Vector2.Distance(pos.transform.position, StartPosition) <= PatrolDistance && transform.position.y - pos.transform.position.y > 0)
|
||||
{
|
||||
PatrolPositions.Add(pos);
|
||||
}
|
||||
}
|
||||
target = PatrolPositions[UnityEngine.Random.Range(0, PatrolPositions.Count)];
|
||||
|
||||
projectile = GameObject.Find("EnemyProjectile");
|
||||
player = GameObject.FindGameObjectWithTag("Player");
|
||||
barrel = transform.Find("Barrel").gameObject;
|
||||
LastKnownPlayerLocation = null;
|
||||
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
DesiredDistance = WeaponRange - 15;
|
||||
|
||||
InvokeRepeating("UpdatePath", 0f, 0.1f);
|
||||
InvokeRepeating("PathfindingTimeout", 0f, 10);
|
||||
|
||||
}
|
||||
|
||||
// When enemy has reached the next node
|
||||
void OnPathComplete(Path p)
|
||||
{
|
||||
if (!p.error)
|
||||
{
|
||||
path = p;
|
||||
currentWaypoint = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Select next node
|
||||
void UpdatePath()
|
||||
{
|
||||
if (seeker.IsDone() && target != null)
|
||||
{
|
||||
seeker.StartPath(rb.position, target.transform.position, OnPathComplete);
|
||||
}
|
||||
}
|
||||
|
||||
// Pathfiniding Timeout
|
||||
void PathfindingTimeout()
|
||||
{
|
||||
if (Vector2.Distance(transform.position, target.transform.position) > 0.5)
|
||||
{
|
||||
target = gameObject;
|
||||
MoveNextPatrol();
|
||||
}
|
||||
}
|
||||
|
||||
//MAIN LOGIC
|
||||
void FixedUpdate()
|
||||
{
|
||||
//DEATH
|
||||
if (Health <= 0)
|
||||
{
|
||||
GameObject DeadBody;
|
||||
DeadBody = Instantiate(gameObject, transform.position, Quaternion.identity);
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<TestEnemyAIGroundRanged>());
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<Seeker>());
|
||||
|
||||
foreach (Transform child in DeadBody.transform)
|
||||
{
|
||||
GameObject.Destroy(child.gameObject);
|
||||
}
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
//ICONS
|
||||
if (targetingPlayer)
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
if (currentlyReloading)
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
//if (90 <= angle || angle <= 270)
|
||||
//{
|
||||
// barrel.transform.localScale = new Vector2(-barrel.transform.localScale.x, barrel.transform.localScale.y);
|
||||
//}
|
||||
|
||||
//MISC PATHFINDING
|
||||
if (path == null)
|
||||
return;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
if (currentWaypoint >= path.vectorPath.Count)
|
||||
{
|
||||
reachedEndOfPath = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
reachedEndOfPath = false;
|
||||
}
|
||||
|
||||
//RANGED ATTACK
|
||||
// Check if enemy has line of sight on the player & if they are in the acceptable range
|
||||
if (WeaponCurrentMagazineAmmount > 0 && canFire == true && currentlyReloading == false && currentlyPatrolling == false)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= WeaponRange && Vector2.Distance(transform.position, player.transform.position) <= DesiredDistance)
|
||||
{
|
||||
UseWeapon();
|
||||
}
|
||||
}
|
||||
else if (WeaponCurrentMagazineAmmount == 0 && currentlyReloading == false)
|
||||
{
|
||||
ReloadWeapon();
|
||||
}
|
||||
|
||||
//MOVEMENT
|
||||
if (canMove == true)
|
||||
{
|
||||
|
||||
Vector2 direction = (Vector2)path.vectorPath[currentWaypoint] - rb.position;
|
||||
//float xDirection = target.transform.position.x - transform.position.x;
|
||||
|
||||
if (direction.x > 0) // Move right
|
||||
{
|
||||
rb.AddForce(new Vector2(Speed * 20, rb.linearVelocity.y));
|
||||
}
|
||||
|
||||
if (direction.x < 0) // Move left
|
||||
{
|
||||
rb.AddForce(new Vector2(-1 * Speed * 20, rb.linearVelocity.y));
|
||||
}
|
||||
|
||||
// A* logic
|
||||
float distance = Vector2.Distance(rb.position, path.vectorPath[currentWaypoint]);
|
||||
|
||||
if (distance < nextWaypointDistance)
|
||||
{
|
||||
currentWaypoint++;
|
||||
}
|
||||
}
|
||||
|
||||
//PATROL & DETECTION
|
||||
// Enemy detects player
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= PlayerDetectionRange)
|
||||
{
|
||||
currentlyPatrolling = false;
|
||||
targetingPlayer = true;
|
||||
canPursue = true;
|
||||
// Get the last know player location by finding which of the positions is closest to the player
|
||||
if (LastKnownPlayerLocation == null)
|
||||
{
|
||||
LastKnownPlayerLocation = gameObject;
|
||||
}
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
if (Vector2.Distance(player.transform.position, pos.transform.position) < Vector2.Distance(player.transform.position, LastKnownPlayerLocation.transform.position))
|
||||
{
|
||||
LastKnownPlayerLocation = pos;
|
||||
}
|
||||
}
|
||||
|
||||
// Angle barrel towards player
|
||||
angle = Mathf.Atan2(player.transform.position.y - barrel.transform.position.y, player.transform.position.x - barrel.transform.position.x) * Mathf.Rad2Deg;
|
||||
|
||||
}
|
||||
// Player has broken line of sight and the enemy will attempt to move to the last known location
|
||||
else if (LastKnownPlayerLocation != null)
|
||||
{
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) > 0.5)
|
||||
{
|
||||
canPursue = false;
|
||||
target = LastKnownPlayerLocation;
|
||||
}
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) < 0.5 && DetermineLineOfSight(player, gameObject) == false)
|
||||
{
|
||||
targetingPlayer = false;
|
||||
LastKnownPlayerLocation = null;
|
||||
}
|
||||
|
||||
// Reset barrel rotation
|
||||
angle = 0f;
|
||||
|
||||
}
|
||||
// Go back to patrol move
|
||||
else
|
||||
{
|
||||
currentlyPatrolling = true;
|
||||
targetingPlayer = false;
|
||||
if (canMove == true && currentlyMovingToNextPatrolTarget == false)
|
||||
{
|
||||
MoveNextPatrol();
|
||||
}
|
||||
|
||||
// Reset barrel rotation
|
||||
angle = 0f;
|
||||
}
|
||||
|
||||
// Rotate barrel towards player
|
||||
Quaternion targetRotation = Quaternion.Euler(new Vector3(0, 0, angle));
|
||||
barrel.transform.rotation = Quaternion.RotateTowards(barrel.transform.rotation, targetRotation, 100 * Time.deltaTime);
|
||||
|
||||
//TARGET DETERMINATION
|
||||
if (canPursue == true)
|
||||
{
|
||||
// If enemy is at the target position (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, transform.position) < 1.5)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the player moves away (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) > DesiredDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the player is not within line of sight of the desired position (CHANGE TARGET)
|
||||
if (DetermineLineOfSight(target, player) == false)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the enemy reaches the target
|
||||
if (Vector2.Distance(target.transform.position, transform.position) <= 1 && DetermineLineOfSight(gameObject, player))
|
||||
{
|
||||
if (canFire == false && inFiringCycle == false)
|
||||
{
|
||||
canFire = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MISC METHODS
|
||||
// Use Ranged Weapon
|
||||
async Task UseWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
inFiringCycle = true;
|
||||
|
||||
// Create Projectile
|
||||
GameObject BulletInstance;
|
||||
BulletInstance = Instantiate(projectile, transform.position, Quaternion.LookRotation(transform.position - GameObject.FindGameObjectWithTag("Player").transform.position + new Vector3(UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), 0)));
|
||||
BulletInstance.transform.parent = transform;
|
||||
|
||||
// Send it on it's way
|
||||
BulletInstance.GetComponent<Rigidbody2D>().linearVelocity = BulletInstance.transform.forward * -1 * WeaponProjectileSpeed;
|
||||
BulletInstance.transform.rotation = Quaternion.Euler(new Vector3(0, 0, Vector2.SignedAngle(Vector2.right, BulletInstance.transform.forward) - 90));
|
||||
WeaponCurrentMagazineAmmount--;
|
||||
|
||||
await Task.Delay(WeaponFireRate);
|
||||
|
||||
canFire = true;
|
||||
inFiringCycle = false;
|
||||
}
|
||||
|
||||
// Reload Weapon
|
||||
async Task ReloadWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
//play reload animation
|
||||
currentlyReloading = true;
|
||||
await Task.Delay(WeaponReloadTime);
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
currentlyReloading = false;
|
||||
|
||||
canFire = true;
|
||||
}
|
||||
|
||||
// Part of the patrol cycle
|
||||
async Task MoveNextPatrol()
|
||||
{
|
||||
LastKnownPlayerLocation = null;
|
||||
|
||||
canPursue = false;
|
||||
currentlyMovingToNextPatrolTarget = true;
|
||||
|
||||
//Find Random Position nearby
|
||||
if (Vector2.Distance(transform.position, target.transform.position) <= 0.5 || PatrolPositions.Contains(target) == false)
|
||||
{
|
||||
target = PatrolPositions[UnityEngine.Random.Range(0, PatrolPositions.Count)];
|
||||
await Task.Delay(PatrolStallTime + UnityEngine.Random.Range((PatrolStallTime * -1), PatrolStallTime));
|
||||
}
|
||||
|
||||
currentlyMovingToNextPatrolTarget = false;
|
||||
|
||||
}
|
||||
|
||||
// General Utility
|
||||
bool DetermineLineOfSight(GameObject object1, GameObject object2)
|
||||
{
|
||||
Vector3 RaycastStart = object1.transform.position;
|
||||
Vector3 RaycastDirection = (object2.transform.position - object1.transform.position).normalized;
|
||||
float RaycastDistance = Vector3.Distance(object2.transform.position, object1.transform.position);
|
||||
|
||||
if (Physics2D.Raycast(RaycastStart, RaycastDirection, RaycastDistance, LayerMask.GetMask("SolidGround")) == false)
|
||||
{
|
||||
Debug.DrawRay(RaycastStart, RaycastDirection * RaycastDistance);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Part of the pursue cycle
|
||||
void ComputeClosestPositionToPlayer()
|
||||
{
|
||||
canMove = true;
|
||||
|
||||
SuitablePositions.Clear();
|
||||
foreach (GameObject query in AllPositions)
|
||||
{
|
||||
// Check the distance of the position
|
||||
if (Vector2.Distance(query.transform.position, player.transform.position) < DesiredDistance && Vector2.Distance(query.transform.position, player.transform.position) >= MinumumDistance && transform.position.y - query.transform.position.y > 0 && Vector2.Distance(query.transform.position, transform.position) < 10)
|
||||
{
|
||||
// Check line of sight of the position
|
||||
if (DetermineLineOfSight(query, player))
|
||||
{
|
||||
SuitablePositions.Add(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (SuitablePositions.Count > 0)
|
||||
{
|
||||
target = SuitablePositions[UnityEngine.Random.Range(0, SuitablePositions.Count)];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b581fa8060dd2534c961df6092303e29
|
||||
@@ -0,0 +1 @@
|
||||
uid://c10q5uurc7lot
|
||||
@@ -0,0 +1,414 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Pathfinding;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
//Designed by Jacob Weedman
|
||||
//V 0.0.2
|
||||
//DO NOT USE THIS ON ANY ENEMIES
|
||||
|
||||
public class TestEnemyAIAirRanged : MonoBehaviour
|
||||
{
|
||||
//MISC
|
||||
public GameObject target;
|
||||
public float nextWaypointDistance = 5;
|
||||
Path path;
|
||||
int currentWaypoint = 0;
|
||||
bool reachedEndOfPath = false;
|
||||
GameObject projectile;
|
||||
GameObject player;
|
||||
GameObject barrel;
|
||||
public List<GameObject> AllPositions;
|
||||
public float DistanceFromPlayer;
|
||||
Vector2 StartPosition;
|
||||
public List<GameObject> PatrolPositions;
|
||||
public GameObject LastKnownPlayerLocation;
|
||||
|
||||
//CONDITIONS/GENERAL INFORMATION
|
||||
public bool canMove = true;
|
||||
public bool canPursue = false;
|
||||
public bool canFire = true;
|
||||
public bool currentlyReloading = false;
|
||||
public bool currentlyPatrolling;
|
||||
public bool currentlyMovingToNextPatrolTarget = false;
|
||||
public float DesiredDistance;
|
||||
public float MinumumDistance = 20f;
|
||||
public bool targetingPlayer = false;
|
||||
public bool inFiringCycle = false;
|
||||
public int WeaponCurrentMagazineAmmount;
|
||||
public int NumberOfHitsPerMag = 0;
|
||||
public float angle;
|
||||
|
||||
//ENEMY STATS (Changeable)
|
||||
public float Speed = 0.7f; // In relation to player's walking speed
|
||||
public float Health = 100;
|
||||
public float PatrolDistance = 10;
|
||||
public int PatrolStallTime = 1500; //ms
|
||||
public int PlayerDetectionRange = 25;
|
||||
|
||||
//WEAPON STATS (CHANGEABLE)
|
||||
public int WeaponDamage = 5; // Damage per hit
|
||||
public int WeaponFireRate = 100; // Delay in time between attacks both melee and ranged
|
||||
public float WeaponRandomSpread = 5f; // Random direction of lanched projectiles
|
||||
public int WeaponRange = 30; // Maximum range of the projectile before it drops off
|
||||
public float WeaponProjectileSpeed = 30f; // Speed of launched projectiles
|
||||
public int WeaponMagazineSize = 50; // Number of shots the enemy will take before having to reload
|
||||
public int WeaponReloadTime = 5000; // Time it takes to reload the magazine
|
||||
|
||||
|
||||
Seeker seeker;
|
||||
Rigidbody2D rb;
|
||||
|
||||
//START OF THE GAME
|
||||
void Start()
|
||||
{
|
||||
seeker = GetComponent<Seeker>();
|
||||
rb = GetComponent<Rigidbody2D>();
|
||||
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
|
||||
StartPosition = transform.position;
|
||||
AllPositions = GameObject.FindGameObjectsWithTag("PossiblePositions").ToList();
|
||||
|
||||
target = Instantiate(GameObject.Find("FlyingTarget"), transform.position,Quaternion.identity);
|
||||
|
||||
projectile = GameObject.Find("EnemyProjectile");
|
||||
player = GameObject.FindGameObjectWithTag("Player");
|
||||
barrel = transform.Find("Barrel").gameObject;
|
||||
LastKnownPlayerLocation = null;
|
||||
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
DesiredDistance = WeaponRange - 5;
|
||||
|
||||
InvokeRepeating("UpdatePath", 0f, 0.1f);
|
||||
InvokeRepeating("PathfindingTimeout", 0f, 30);
|
||||
}
|
||||
|
||||
// When enemy has reached the next node
|
||||
void OnPathComplete(Path p)
|
||||
{
|
||||
if (!p.error)
|
||||
{
|
||||
path = p;
|
||||
currentWaypoint = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Select next node
|
||||
void UpdatePath()
|
||||
{
|
||||
if (seeker.IsDone())
|
||||
{
|
||||
seeker.StartPath(rb.position, target.transform.position, OnPathComplete);
|
||||
}
|
||||
}
|
||||
|
||||
// Pathfiniding Timeout
|
||||
void PathfindingTimeout()
|
||||
{
|
||||
if (Vector2.Distance(transform.position, target.transform.position) > 0.5)
|
||||
{
|
||||
//target = gameObject;
|
||||
targetingPlayer = false;
|
||||
LastKnownPlayerLocation = null;
|
||||
MoveNextPatrol();
|
||||
}
|
||||
}
|
||||
|
||||
//MAIN LOGIC
|
||||
void FixedUpdate()
|
||||
{
|
||||
// Death
|
||||
if (Health <= 0)
|
||||
{
|
||||
GameObject DeadBody;
|
||||
DeadBody = Instantiate(gameObject, transform.position, Quaternion.identity);
|
||||
DeadBody.GetComponent<Rigidbody2D>().gravityScale = 3;
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<TestEnemyAIAirRanged>());
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<Seeker>());
|
||||
|
||||
foreach (Transform child in DeadBody.transform)
|
||||
{
|
||||
GameObject.Destroy(child.gameObject);
|
||||
}
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
// See where the enemy wants to go (DEBUGGING ONLY)
|
||||
/*
|
||||
if (target != null)
|
||||
{
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
pos.GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
target.GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
*/
|
||||
|
||||
//CHANGE ICONS
|
||||
if (targetingPlayer)
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
if (currentlyReloading)
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
//MISC PATHFINDING
|
||||
if (path == null)
|
||||
return;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
if (currentWaypoint >= path.vectorPath.Count)
|
||||
{
|
||||
reachedEndOfPath = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
reachedEndOfPath = false;
|
||||
}
|
||||
|
||||
//RANGED ATTACK
|
||||
// Check if enemy has line of sight on the player & if they are in the acceptable range
|
||||
if (WeaponCurrentMagazineAmmount > 0 && canFire == true && currentlyReloading == false && currentlyPatrolling == false)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == true && inFiringCycle == false && Vector2.Distance(transform.position, player.transform.position) <= WeaponRange && Vector2.Distance(transform.position, player.transform.position) <= DesiredDistance)
|
||||
{
|
||||
UseWeapon();
|
||||
}
|
||||
}
|
||||
else if (WeaponCurrentMagazineAmmount == 0 && currentlyReloading == false)
|
||||
{
|
||||
ReloadWeapon();
|
||||
}
|
||||
|
||||
//MOVEMENT
|
||||
if (canMove == true)
|
||||
{
|
||||
Vector2 direction = ((Vector2)path.vectorPath[currentWaypoint] - rb.position).normalized;
|
||||
Vector2 force = direction * Speed * 20;
|
||||
|
||||
rb.AddForce(force);
|
||||
|
||||
// A* logic
|
||||
float distance = Vector2.Distance(rb.position, path.vectorPath[currentWaypoint]);
|
||||
|
||||
if (distance < nextWaypointDistance)
|
||||
{
|
||||
currentWaypoint++;
|
||||
}
|
||||
}
|
||||
|
||||
//PATROL
|
||||
// Enemy detects player
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= PlayerDetectionRange)
|
||||
{
|
||||
currentlyPatrolling = false;
|
||||
targetingPlayer = true;
|
||||
canPursue = true;
|
||||
|
||||
// Angle barrel towards player
|
||||
angle = Mathf.Atan2(player.transform.position.y - barrel.transform.position.y, player.transform.position.x - barrel.transform.position.x) * Mathf.Rad2Deg;
|
||||
|
||||
// Get the last know player location by finding which of the positions is closest to the player
|
||||
if (LastKnownPlayerLocation == null)
|
||||
{
|
||||
LastKnownPlayerLocation = gameObject;
|
||||
}
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
if (Vector2.Distance(player.transform.position, pos.transform.position) < Vector2.Distance(player.transform.position, LastKnownPlayerLocation.transform.position))
|
||||
{
|
||||
LastKnownPlayerLocation = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Player has broken line of sight and the enemy will attempt to move to the last known location
|
||||
else if (LastKnownPlayerLocation != null)
|
||||
{
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) > 0.5)
|
||||
{
|
||||
canPursue = false;
|
||||
target.transform.position = LastKnownPlayerLocation.transform.position;
|
||||
}
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) < 0.5 && DetermineLineOfSight(player, gameObject) == false)
|
||||
{
|
||||
targetingPlayer = false;
|
||||
LastKnownPlayerLocation = null;
|
||||
}
|
||||
|
||||
// Reset barrel rotation
|
||||
angle = -90f;
|
||||
}
|
||||
// Go back to patrol move
|
||||
else
|
||||
{
|
||||
LastKnownPlayerLocation = gameObject;
|
||||
currentlyPatrolling = true;
|
||||
targetingPlayer = false;
|
||||
|
||||
// Reset barrel rotation
|
||||
angle = -90f;
|
||||
}
|
||||
|
||||
// Rotate barrel towards player
|
||||
Quaternion targetRotation = Quaternion.Euler(new Vector3(0, 0, angle));
|
||||
barrel.transform.rotation = Quaternion.RotateTowards(barrel.transform.rotation, targetRotation, 100 * Time.deltaTime);
|
||||
|
||||
// Call patrol method
|
||||
if (currentlyPatrolling)
|
||||
{
|
||||
if (canMove == true && currentlyMovingToNextPatrolTarget == false)
|
||||
{
|
||||
MoveNextPatrol();
|
||||
}
|
||||
}
|
||||
|
||||
//TARGET DETERMINATION
|
||||
if (canPursue == true)
|
||||
{
|
||||
// Catch desired distance error
|
||||
if (DesiredDistance <= MinumumDistance)
|
||||
{
|
||||
DesiredDistance = MinumumDistance + 1;
|
||||
}
|
||||
|
||||
// If enemy is at the target position
|
||||
if (Vector2.Distance(target.transform.position, transform.position) < 1.5)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the player moves away (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) > DesiredDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
//If the player is too close to the target (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) < MinumumDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the player is not within line of sight of the desired position (CHANGE TARGET)
|
||||
if (DetermineLineOfSight(target, player) == false)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the enemy reaches the target
|
||||
if (Vector2.Distance(target.transform.position, transform.position) <= 1)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == false) // If the enemy has LOS on the player
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MISC METHODS
|
||||
// Use Ranged Weapon
|
||||
async Task UseWeapon() // Weapon for projectile based enemy
|
||||
{
|
||||
inFiringCycle = true;
|
||||
|
||||
// Create Projectile
|
||||
GameObject BulletInstance;
|
||||
BulletInstance = Instantiate(projectile, transform.position, Quaternion.LookRotation(transform.position - GameObject.FindGameObjectWithTag("Player").transform.position + new Vector3(UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), 0)));
|
||||
BulletInstance.transform.parent = transform;
|
||||
|
||||
// Send it on it's way
|
||||
BulletInstance.GetComponent<Rigidbody2D>().linearVelocity = BulletInstance.transform.forward * -1 * WeaponProjectileSpeed;
|
||||
BulletInstance.transform.rotation = Quaternion.Euler(new Vector3(0, 0, Vector2.SignedAngle(Vector2.right, BulletInstance.transform.forward) - 90));
|
||||
WeaponCurrentMagazineAmmount--;
|
||||
|
||||
await Task.Delay(WeaponFireRate);
|
||||
|
||||
inFiringCycle = false;
|
||||
}
|
||||
|
||||
// Reload Weapon
|
||||
async Task ReloadWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
|
||||
//play reload animation
|
||||
currentlyReloading = true;
|
||||
await Task.Delay(WeaponReloadTime);
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
currentlyReloading = false;
|
||||
|
||||
if (NumberOfHitsPerMag / WeaponMagazineSize < 0.5)
|
||||
{
|
||||
DesiredDistance -= 5;
|
||||
}
|
||||
NumberOfHitsPerMag = 0;
|
||||
|
||||
canFire = true;
|
||||
}
|
||||
|
||||
// Part of the patrol cycle
|
||||
async Task MoveNextPatrol()
|
||||
{
|
||||
LastKnownPlayerLocation = gameObject;
|
||||
target.transform.position = transform.position;
|
||||
|
||||
canPursue = false;
|
||||
currentlyMovingToNextPatrolTarget = true;
|
||||
|
||||
//Find Random Position nearby
|
||||
target.transform.position = new Vector2((StartPosition.x + UnityEngine.Random.Range(-1 * PatrolDistance, PatrolDistance)), (StartPosition.y + UnityEngine.Random.Range(-1 * PatrolDistance, PatrolDistance)));
|
||||
await Task.Delay(PatrolStallTime + UnityEngine.Random.Range((PatrolStallTime * -1), PatrolStallTime));
|
||||
|
||||
currentlyMovingToNextPatrolTarget = false;
|
||||
}
|
||||
|
||||
// General Utility
|
||||
bool DetermineLineOfSight(GameObject object1, GameObject object2)
|
||||
{
|
||||
Vector3 RaycastStart = object1.transform.position;
|
||||
Vector3 RaycastDirection = (object2.transform.position - object1.transform.position).normalized;
|
||||
float RaycastDistance = Vector3.Distance(object2.transform.position, object1.transform.position);
|
||||
|
||||
if (Physics2D.Raycast(RaycastStart, RaycastDirection, RaycastDistance, LayerMask.GetMask("SolidGround")) == false)
|
||||
{
|
||||
Debug.DrawRay(RaycastStart, RaycastDirection * RaycastDistance);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Partof the pursue cycle
|
||||
void ComputeClosestPositionToPlayer()
|
||||
{
|
||||
canMove = true;
|
||||
// find a target in the air a set radius away from the player
|
||||
if (DetermineLineOfSight(player, gameObject))
|
||||
{
|
||||
target.transform.position = new Vector2((player.transform.position.x + UnityEngine.Random.Range(-MinumumDistance, MinumumDistance)), (player.transform.position.y + MinumumDistance + UnityEngine.Random.Range(-5, 0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ba7d100626939a34a940695ac9a80a8c
|
||||
@@ -0,0 +1 @@
|
||||
uid://oxmo5iseeica
|
||||
@@ -0,0 +1,484 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Pathfinding;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
//Designed by Jacob Weedman
|
||||
//V 0.0.1
|
||||
//DO NOT USE ON ANY ENEMIES
|
||||
|
||||
public class TestEnemyAIDroneRanged : MonoBehaviour
|
||||
{
|
||||
//MISC
|
||||
GameObject target;
|
||||
float nextWaypointDistance = 5;
|
||||
Path path;
|
||||
int currentWaypoint = 0;
|
||||
bool reachedEndOfPath = false;
|
||||
GameObject projectile;
|
||||
GameObject player;
|
||||
GameObject barrel;
|
||||
public List<GameObject> AllPositions;
|
||||
float DistanceFromPlayer;
|
||||
Vector2 StartPosition;
|
||||
GameObject LastKnownPlayerLocation;
|
||||
Transform DroneBayLocation;
|
||||
|
||||
//CONDITIONS/GENERAL INFORMATION
|
||||
public bool canMove = false;
|
||||
public bool canPursue = false;
|
||||
public bool canFire = true;
|
||||
public bool currentlyReloading = false;
|
||||
public bool currentlyInDroneBay = true;
|
||||
public float DesiredDistance;
|
||||
public float MinumumDistance = 10f;
|
||||
public bool targetingPlayer = false;
|
||||
public bool inFiringCycle = false;
|
||||
public int WeaponCurrentMagazineAmmount;
|
||||
public int NumberOfHitsPerMag = 0;
|
||||
public int CurrentBatteryCapacity;
|
||||
public bool currentlyTravelingToDroneBay = false;
|
||||
public bool currentlyRecharging = false;
|
||||
public float angle;
|
||||
|
||||
//ENEMY STATS (Changeable)
|
||||
public float Speed = 0.7f; // In relation to player's walking speed
|
||||
public float Health = 100;
|
||||
public int PlayerDetectionRange = 25;
|
||||
public int MaxBatteryCapacity = 30; // Depletes one every second it is out of the bay
|
||||
public int RechargeTime = 6000; //ms
|
||||
|
||||
//WEAPON STATS (CHANGEABLE)
|
||||
public int WeaponDamage = 5; // Damage per hit
|
||||
public int WeaponFireRate = 100; // Delay in time between attacks both melee and ranged
|
||||
public float WeaponRandomSpread = 5f; // Random direction of lanched projectiles (DOES NOT APPLY TO MELEE ATTACKS)
|
||||
public int WeaponRange = 30; // Maximum range of the projectile before it drops off (DOES NOT APPLY TO MELEE ATTACKS)
|
||||
public float WeaponProjectileSpeed = 30f; // Speed of launched projectiles (DOES NOT APPLY TO MELEE WEAPONS)
|
||||
public int WeaponMagazineSize = 50; // Number of shots the enemy will take before having to reload
|
||||
public int WeaponReloadTime = 5000; // Time it takes to reload the magazine
|
||||
|
||||
|
||||
Seeker seeker;
|
||||
Rigidbody2D rb;
|
||||
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
void Start()
|
||||
{
|
||||
seeker = GetComponent<Seeker>();
|
||||
rb = GetComponent<Rigidbody2D>();
|
||||
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
transform.Find("BatteryIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
|
||||
StartPosition = transform.position;
|
||||
AllPositions = GameObject.FindGameObjectsWithTag("PossiblePositions").ToList();
|
||||
|
||||
target = Instantiate(GameObject.Find("FlyingTarget"), transform.position, Quaternion.identity);
|
||||
|
||||
projectile = GameObject.Find("EnemyProjectile");
|
||||
player = GameObject.FindGameObjectWithTag("Player");
|
||||
barrel = transform.Find("Barrel").gameObject;
|
||||
LastKnownPlayerLocation = null;
|
||||
DroneBayLocation = transform.parent.transform;
|
||||
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
DesiredDistance = WeaponRange - 5;
|
||||
CurrentBatteryCapacity = MaxBatteryCapacity;
|
||||
|
||||
InvokeRepeating("UpdatePath", 0f, 0.1f);
|
||||
InvokeRepeating("PathfindingTimeout", 0f, 30);
|
||||
InvokeRepeating("BatteryDrain", 0f, 1);
|
||||
|
||||
Recharge();
|
||||
}
|
||||
|
||||
// When enemy has reached the next node
|
||||
void OnPathComplete(Path p)
|
||||
{
|
||||
if (!p.error)
|
||||
{
|
||||
path = p;
|
||||
currentWaypoint = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Select next node
|
||||
void UpdatePath()
|
||||
{
|
||||
if (seeker.IsDone())
|
||||
{
|
||||
seeker.StartPath(rb.position, target.transform.position, OnPathComplete);
|
||||
}
|
||||
}
|
||||
|
||||
// Pathfiniding Timeout
|
||||
void PathfindingTimeout()
|
||||
{
|
||||
if (Vector2.Distance(transform.position, target.transform.position) > 0.5)
|
||||
{
|
||||
//target = gameObject;
|
||||
targetingPlayer = false;
|
||||
LastKnownPlayerLocation = null;
|
||||
//ReturnToDroneBay();
|
||||
}
|
||||
}
|
||||
|
||||
// Battery Drain
|
||||
void BatteryDrain()
|
||||
{
|
||||
if (currentlyInDroneBay == false)
|
||||
{
|
||||
CurrentBatteryCapacity -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// MAIN LOGIC
|
||||
void FixedUpdate()
|
||||
{
|
||||
//DEATH
|
||||
if (CurrentBatteryCapacity == 0)
|
||||
{
|
||||
Health = 0;
|
||||
}
|
||||
|
||||
if (Health <= 0)
|
||||
{
|
||||
GameObject DeadBody;
|
||||
DeadBody = Instantiate(gameObject, transform.position, Quaternion.identity);
|
||||
DeadBody.GetComponent<Rigidbody2D>().gravityScale = 3;
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<TestEnemyAIDroneRanged>());
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<Seeker>());
|
||||
|
||||
foreach (Transform child in DeadBody.transform)
|
||||
{
|
||||
GameObject.Destroy(child.gameObject);
|
||||
}
|
||||
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
//Battery
|
||||
// Low battery return
|
||||
if (CurrentBatteryCapacity <= MaxBatteryCapacity / 10)
|
||||
{
|
||||
ReturnToDroneBay();
|
||||
}
|
||||
|
||||
// Recharge battery & bay detection
|
||||
if (Vector2.Distance(transform.position, DroneBayLocation.position) <= 0.5)
|
||||
{
|
||||
currentlyInDroneBay = true;
|
||||
currentlyTravelingToDroneBay = false;
|
||||
if (currentlyRecharging == false && CurrentBatteryCapacity < MaxBatteryCapacity / 10)
|
||||
{
|
||||
Recharge();
|
||||
}
|
||||
else
|
||||
{
|
||||
canMove = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//canMove = true;
|
||||
currentlyInDroneBay = false;
|
||||
}
|
||||
|
||||
// See where the enemy wants to go (DEBUGGING ONLY)
|
||||
/*
|
||||
if (target != null)
|
||||
{
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
pos.GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
target.GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
*/
|
||||
|
||||
//CHANGE ICONS
|
||||
if (targetingPlayer)
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
if (currentlyReloading)
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
if (CurrentBatteryCapacity <= MaxBatteryCapacity / 10)
|
||||
{
|
||||
transform.Find("BatteryIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("BatteryIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
if (currentlyInDroneBay == false)
|
||||
{
|
||||
seeker.enabled = true;
|
||||
}
|
||||
|
||||
//MISC PATHFINDING
|
||||
if (path == null)
|
||||
return;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
if (currentWaypoint >= path.vectorPath.Count)
|
||||
{
|
||||
reachedEndOfPath = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
reachedEndOfPath = false;
|
||||
}
|
||||
|
||||
//RANGED ATTACK
|
||||
// Check if enemy has line of sight on the player & if they are in the acceptable range
|
||||
|
||||
if (WeaponCurrentMagazineAmmount > 0 && canFire == true && currentlyReloading == false && currentlyInDroneBay == false)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == true && inFiringCycle == false && Vector2.Distance(transform.position, player.transform.position) <= WeaponRange && Vector2.Distance(transform.position, player.transform.position) <= DesiredDistance)
|
||||
{
|
||||
UseWeapon();
|
||||
}
|
||||
}
|
||||
else if (WeaponCurrentMagazineAmmount == 0 && currentlyReloading == false)
|
||||
{
|
||||
ReloadWeapon();
|
||||
}
|
||||
|
||||
//MOVEMENT
|
||||
if (canMove == true)
|
||||
{
|
||||
Vector2 direction = ((Vector2)path.vectorPath[currentWaypoint] - rb.position).normalized;
|
||||
Vector2 force = direction * Speed * 20;
|
||||
|
||||
rb.AddForce(force);
|
||||
|
||||
// A* logic
|
||||
float distance = Vector2.Distance(rb.position, path.vectorPath[currentWaypoint]);
|
||||
|
||||
if (distance < nextWaypointDistance)
|
||||
{
|
||||
currentWaypoint++;
|
||||
}
|
||||
}
|
||||
|
||||
//DETECTION
|
||||
// Enemy detects player
|
||||
if (DetermineLineOfSight(transform.parent.gameObject, player) && Vector2.Distance(DroneBayLocation.position, player.transform.position) <= PlayerDetectionRange && currentlyRecharging == false)
|
||||
{
|
||||
if (currentlyInDroneBay == true)
|
||||
{
|
||||
rb.linearVelocity = transform.parent.transform.up * 10;
|
||||
}
|
||||
canMove = true;
|
||||
targetingPlayer = true;
|
||||
canPursue = true;
|
||||
|
||||
// Angle barrel towards player
|
||||
angle = Mathf.Atan2(player.transform.position.y - barrel.transform.position.y, player.transform.position.x - barrel.transform.position.x) * Mathf.Rad2Deg;
|
||||
|
||||
}
|
||||
else if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= PlayerDetectionRange)
|
||||
{
|
||||
targetingPlayer = true;
|
||||
canPursue = true;
|
||||
// Get the last know player location by finding which of the positions is closest to the player
|
||||
if (LastKnownPlayerLocation == null)
|
||||
{
|
||||
LastKnownPlayerLocation = gameObject;
|
||||
}
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
if (Vector2.Distance(player.transform.position, pos.transform.position) < Vector2.Distance(player.transform.position, LastKnownPlayerLocation.transform.position))
|
||||
{
|
||||
LastKnownPlayerLocation = pos;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset barrel rotation
|
||||
angle = -90f;
|
||||
}
|
||||
// Player has broken line of sight and the enemy will attempt to move to the last known location
|
||||
else if (LastKnownPlayerLocation != null)
|
||||
{
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) > 0.5)
|
||||
{
|
||||
canPursue = false;
|
||||
target.transform.position = LastKnownPlayerLocation.transform.position;
|
||||
}
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) < 0.5 && DetermineLineOfSight(player, gameObject) == false)
|
||||
{
|
||||
targetingPlayer = false;
|
||||
LastKnownPlayerLocation = null;
|
||||
}
|
||||
|
||||
// Reset barrel rotation
|
||||
angle = -90f;
|
||||
}
|
||||
// Go back to drone bay
|
||||
else
|
||||
{
|
||||
LastKnownPlayerLocation = null;
|
||||
ReturnToDroneBay();
|
||||
targetingPlayer = false;
|
||||
|
||||
// Reset barrel rotation
|
||||
angle = -90f;
|
||||
}
|
||||
|
||||
// Rotate barrel towards player
|
||||
Quaternion targetRotation = Quaternion.Euler(new Vector3(0, 0, angle));
|
||||
barrel.transform.rotation = Quaternion.RotateTowards(barrel.transform.rotation, targetRotation, 100 * Time.deltaTime);
|
||||
|
||||
|
||||
if (currentlyTravelingToDroneBay == false)
|
||||
{
|
||||
//TARGET DETERMINATION
|
||||
|
||||
// If enemy is at the target position
|
||||
if (Vector2.Distance(target.transform.position, transform.position) < 1.5)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the player moves away (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) > DesiredDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
//If the player is too close to the target (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) < MinumumDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the player is not within line of sight of the desired position (CHANGE TARGET)
|
||||
if (DetermineLineOfSight(target, player) == false)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the enemy reaches the target
|
||||
if (Vector2.Distance(target.transform.position, transform.position) <= 1)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == false) // If the enemy has LOS on the player
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MISC METHODS
|
||||
// Use Ranged Weapon
|
||||
async Task UseWeapon() // Weapon for projectile based enemy
|
||||
{
|
||||
inFiringCycle = true;
|
||||
|
||||
// Create Projectile
|
||||
GameObject BulletInstance;
|
||||
BulletInstance = Instantiate(projectile, transform.position, Quaternion.LookRotation(transform.position - GameObject.FindGameObjectWithTag("Player").transform.position + new Vector3(UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), 0)));
|
||||
BulletInstance.transform.parent = transform;
|
||||
|
||||
// Send it on it's way
|
||||
BulletInstance.GetComponent<Rigidbody2D>().linearVelocity = BulletInstance.transform.forward * -1 * WeaponProjectileSpeed;
|
||||
BulletInstance.transform.rotation = Quaternion.Euler(new Vector3(0, 0, Vector2.SignedAngle(Vector2.right, BulletInstance.transform.forward) - 90));
|
||||
WeaponCurrentMagazineAmmount--;
|
||||
|
||||
await Task.Delay(WeaponFireRate);
|
||||
|
||||
inFiringCycle = false;
|
||||
}
|
||||
|
||||
// Reload Weapon
|
||||
async Task ReloadWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
|
||||
//play reload animation
|
||||
currentlyReloading = true;
|
||||
await Task.Delay(WeaponReloadTime);
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
currentlyReloading = false;
|
||||
|
||||
NumberOfHitsPerMag = 0;
|
||||
|
||||
canFire = true;
|
||||
}
|
||||
|
||||
// Return To Drone Bay
|
||||
async Task ReturnToDroneBay()
|
||||
{
|
||||
if (currentlyTravelingToDroneBay == false && currentlyInDroneBay == false)
|
||||
{
|
||||
currentlyTravelingToDroneBay = true;
|
||||
target.transform.position = DroneBayLocation.position;
|
||||
}
|
||||
}
|
||||
|
||||
// Recharge
|
||||
async Task Recharge()
|
||||
{
|
||||
currentlyRecharging = true;
|
||||
canMove = false;
|
||||
canFire = false;
|
||||
seeker.enabled = false;
|
||||
transform.position = DroneBayLocation.position;
|
||||
rb.linearVelocity = Vector3.zero;
|
||||
currentlyTravelingToDroneBay = false;
|
||||
await Task.Delay(RechargeTime);
|
||||
CurrentBatteryCapacity = MaxBatteryCapacity;
|
||||
currentlyRecharging = false;
|
||||
canFire = true;
|
||||
}
|
||||
|
||||
// General Utility
|
||||
bool DetermineLineOfSight(GameObject object1, GameObject object2)
|
||||
{
|
||||
Vector3 RaycastStart = object1.transform.position;
|
||||
Vector3 RaycastDirection = (object2.transform.position - object1.transform.position).normalized;
|
||||
float RaycastDistance = Vector3.Distance(object2.transform.position, object1.transform.position);
|
||||
|
||||
if (Physics2D.Raycast(RaycastStart, RaycastDirection, RaycastDistance, LayerMask.GetMask("SolidGround")) == false)
|
||||
{
|
||||
Debug.DrawRay(RaycastStart, RaycastDirection * RaycastDistance);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Part of the pursue cycle
|
||||
void ComputeClosestPositionToPlayer()
|
||||
{
|
||||
// find a target in the air a set radius away from the player
|
||||
if (DetermineLineOfSight(player, gameObject))
|
||||
{
|
||||
canMove = true;
|
||||
target.transform.position = new Vector2((player.transform.position.x + UnityEngine.Random.Range(-MinumumDistance, MinumumDistance)), (player.transform.position.y + MinumumDistance + UnityEngine.Random.Range(-5, 0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c73bec38592c574d813f6e649bfab1c
|
||||
@@ -0,0 +1 @@
|
||||
uid://dguws18kyc1tb
|
||||
@@ -0,0 +1,492 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Pathfinding;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
//Designed by Jacob Weedman
|
||||
//V 0.0.6
|
||||
//DO NOT USE THIS ON ANY ENEMIES
|
||||
|
||||
public class TestEnemyAIGroundRanged : MonoBehaviour
|
||||
{
|
||||
|
||||
//MISC
|
||||
public GameObject target;
|
||||
public float nextWaypointDistance = 5;
|
||||
Path path;
|
||||
int currentWaypoint = 0;
|
||||
bool reachedEndOfPath = false;
|
||||
GameObject projectile;
|
||||
GameObject player;
|
||||
GameObject barrel;
|
||||
public List<GameObject> SuitablePositions;
|
||||
public List<GameObject> AllPositions;
|
||||
public float DistanceFromPlayer;
|
||||
Vector2 StartPosition;
|
||||
public List<GameObject> PatrolPositions;
|
||||
public GameObject LastKnownPlayerLocation;
|
||||
|
||||
//CONDITIONS/GENERAL INFORMATION
|
||||
public bool canJump = true; //
|
||||
public bool canMove = true; // Prevent all movement
|
||||
public bool canPursue = false; // Follow player
|
||||
public bool canFire = false;
|
||||
public bool currentlyReloading = false;
|
||||
public bool currentlyPatrolling;
|
||||
public bool currentlyMovingToNextPatrolTarget = false;
|
||||
public float DesiredDistance;
|
||||
public float MinumumDistance = 5f;
|
||||
public bool targetingPlayer = false;
|
||||
public bool inFiringCycle = false;
|
||||
public int WeaponCurrentMagazineAmmount;
|
||||
public int NumberOfHitsPerMag = 0;
|
||||
public float angle;
|
||||
|
||||
//ENEMY STATS (Changeable)
|
||||
public float Speed = 0.7f; // In relation to player's walking speed
|
||||
public float JumpHeight = 1; // In relation to player's regular jump height
|
||||
public float Health = 100;
|
||||
public float PatrolDistance = 10;
|
||||
public int PatrolStallTime = 2000; //ms
|
||||
public int PlayerDetectionRange = 25;
|
||||
|
||||
//WEAPON STATS (CHANGEABLE)
|
||||
public int WeaponDamage = 15; // Damage per hit
|
||||
public int WeaponFireRate = 250; // Delay in time between attacks both melee and ranged
|
||||
public float WeaponRandomSpread = 5f; // Random direction of lanched projectiles
|
||||
public int WeaponRange = 20; // Maximum range of the projectile before it drops off
|
||||
public float WeaponProjectileSpeed = 30f; // Speed of launched projectiles
|
||||
public int WeaponMagazineSize = 20; // Number of shots the enemy will take before having to reload
|
||||
public int WeaponReloadTime = 5000; // Time it takes to reload the magazine
|
||||
|
||||
//REFERENCES
|
||||
Seeker seeker;
|
||||
Rigidbody2D rb;
|
||||
|
||||
//ONCE THE GAME STARTS
|
||||
void Start()
|
||||
{
|
||||
seeker = GetComponent<Seeker>();
|
||||
rb = GetComponent<Rigidbody2D>();
|
||||
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
|
||||
StartPosition = transform.position;
|
||||
AllPositions = GameObject.FindGameObjectsWithTag("PossiblePositions").ToList();
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
if (Vector2.Distance(pos.transform.position, StartPosition) <= PatrolDistance)
|
||||
{
|
||||
PatrolPositions.Add(pos);
|
||||
}
|
||||
}
|
||||
target = PatrolPositions[UnityEngine.Random.Range(0, PatrolPositions.Count)];
|
||||
|
||||
projectile = GameObject.Find("EnemyProjectile");
|
||||
player = GameObject.FindGameObjectWithTag("Player");
|
||||
barrel = transform.Find("Barrel").gameObject;
|
||||
LastKnownPlayerLocation = null;
|
||||
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
DesiredDistance = WeaponRange - 5;
|
||||
|
||||
InvokeRepeating("UpdatePath", 0f, 0.1f);
|
||||
InvokeRepeating("PathfindingTimeout", 0f, 10);
|
||||
|
||||
}
|
||||
|
||||
// When enemy has reached the next node
|
||||
void OnPathComplete(Path p)
|
||||
{
|
||||
if (!p.error)
|
||||
{
|
||||
path = p;
|
||||
currentWaypoint = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Select next node
|
||||
void UpdatePath()
|
||||
{
|
||||
if (seeker.IsDone() && target != null)
|
||||
{
|
||||
seeker.StartPath(rb.position, target.transform.position, OnPathComplete);
|
||||
}
|
||||
}
|
||||
|
||||
// Pathfiniding Timeout
|
||||
void PathfindingTimeout()
|
||||
{
|
||||
if (Vector2.Distance(transform.position, target.transform.position) > 0.5)
|
||||
{
|
||||
target = gameObject;
|
||||
MoveNextPatrol();
|
||||
}
|
||||
}
|
||||
|
||||
//MAIN LOGIC
|
||||
void FixedUpdate()
|
||||
{
|
||||
//DEATH
|
||||
if (Health <= 0)
|
||||
{
|
||||
GameObject DeadBody;
|
||||
DeadBody = Instantiate(gameObject, transform.position, Quaternion.identity);
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<TestEnemyAIGroundRanged>());
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<Seeker>());
|
||||
|
||||
foreach (Transform child in DeadBody.transform) {
|
||||
GameObject.Destroy(child.gameObject);
|
||||
}
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
// See where the enemy wants to go (DEBUGGING ONLY)
|
||||
if (target != null)
|
||||
{
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
pos.GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
target.GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
|
||||
//ICONS
|
||||
if (targetingPlayer)
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
if (currentlyReloading)
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
//if (90 <= angle || angle <= 270)
|
||||
//{
|
||||
// barrel.transform.localScale = new Vector2(-barrel.transform.localScale.x, barrel.transform.localScale.y);
|
||||
//}
|
||||
|
||||
//MISC PATHFINDING
|
||||
if (path == null)
|
||||
return;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
if (currentWaypoint >= path.vectorPath.Count)
|
||||
{
|
||||
reachedEndOfPath = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
reachedEndOfPath = false;
|
||||
}
|
||||
|
||||
//RANGED ATTACK
|
||||
// Check if enemy has line of sight on the player & if they are in the acceptable range
|
||||
if (WeaponCurrentMagazineAmmount > 0 && canFire == true && currentlyReloading == false && currentlyPatrolling == false)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= WeaponRange && Vector2.Distance(transform.position, player.transform.position) <= DesiredDistance)
|
||||
{
|
||||
UseWeapon();
|
||||
}
|
||||
}
|
||||
else if (WeaponCurrentMagazineAmmount == 0 && currentlyReloading == false)
|
||||
{
|
||||
ReloadWeapon();
|
||||
}
|
||||
|
||||
//MOVEMENT
|
||||
if (canMove == true)
|
||||
{
|
||||
canFire = false;
|
||||
|
||||
Vector2 direction = (Vector2)path.vectorPath[currentWaypoint] - rb.position;
|
||||
//float xDirection = target.transform.position.x - transform.position.x;
|
||||
|
||||
if (direction.x > 0) // Move right
|
||||
{
|
||||
rb.AddForce(new Vector2(Speed * 20, rb.linearVelocity.y));
|
||||
}
|
||||
|
||||
if (direction.x < 0) // Move left
|
||||
{
|
||||
rb.AddForce(new Vector2(-1 * Speed * 20, rb.linearVelocity.y));
|
||||
}
|
||||
|
||||
if (direction.y > 1f) // Wants to jump
|
||||
{
|
||||
JumpMethod();
|
||||
}
|
||||
|
||||
// A* logic
|
||||
float distance = Vector2.Distance(rb.position, path.vectorPath[currentWaypoint]);
|
||||
|
||||
if (distance < nextWaypointDistance)
|
||||
{
|
||||
currentWaypoint++;
|
||||
}
|
||||
}
|
||||
|
||||
//GROUND DETECTION
|
||||
//Detecting if the enemy has reached the ground
|
||||
if (GetComponentInChildren<GroundCheck>().isGrounded == true && rb.linearVelocity.y == 0)
|
||||
{
|
||||
canJump = true;
|
||||
if (inFiringCycle == false)
|
||||
{
|
||||
canFire = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
canJump = false;
|
||||
canFire = false;
|
||||
}
|
||||
|
||||
//PATROL & DETECTION
|
||||
// Enemy detects player
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= PlayerDetectionRange)
|
||||
{
|
||||
currentlyPatrolling = false;
|
||||
targetingPlayer = true;
|
||||
canPursue = true;
|
||||
// Get the last know player location by finding which of the positions is closest to the player
|
||||
if (LastKnownPlayerLocation == null)
|
||||
{
|
||||
LastKnownPlayerLocation = gameObject;
|
||||
}
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
if (Vector2.Distance(player.transform.position, pos.transform.position) < Vector2.Distance(player.transform.position, LastKnownPlayerLocation.transform.position))
|
||||
{
|
||||
LastKnownPlayerLocation = pos;
|
||||
}
|
||||
}
|
||||
|
||||
// Angle barrel towards player
|
||||
angle = Mathf.Atan2(player.transform.position.y - barrel.transform.position.y, player.transform.position.x - barrel.transform.position.x) * Mathf.Rad2Deg;
|
||||
|
||||
}
|
||||
// Player has broken line of sight and the enemy will attempt to move to the last known location
|
||||
else if (LastKnownPlayerLocation != null)
|
||||
{
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) > 0.5)
|
||||
{
|
||||
canPursue = false;
|
||||
target = LastKnownPlayerLocation;
|
||||
}
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) < 0.5 && DetermineLineOfSight(player, gameObject) == false)
|
||||
{
|
||||
targetingPlayer = false;
|
||||
LastKnownPlayerLocation = null;
|
||||
}
|
||||
|
||||
// Reset barrel rotation
|
||||
angle = 0f;
|
||||
|
||||
}
|
||||
// Go back to patrol move
|
||||
else
|
||||
{
|
||||
currentlyPatrolling = true;
|
||||
targetingPlayer = false;
|
||||
if (canMove == true && currentlyMovingToNextPatrolTarget == false)
|
||||
{
|
||||
MoveNextPatrol();
|
||||
}
|
||||
|
||||
// Reset barrel rotation
|
||||
angle = 0f;
|
||||
}
|
||||
|
||||
// Rotate barrel towards player
|
||||
Quaternion targetRotation = Quaternion.Euler(new Vector3(0, 0, angle));
|
||||
barrel.transform.rotation = Quaternion.RotateTowards(barrel.transform.rotation, targetRotation, 100 * Time.deltaTime);
|
||||
|
||||
//TARGET DETERMINATION
|
||||
if (canPursue == true)
|
||||
{
|
||||
// Catch desired distance error
|
||||
if (DesiredDistance <= MinumumDistance)
|
||||
{
|
||||
DesiredDistance = MinumumDistance + 1;
|
||||
}
|
||||
|
||||
// If the player moves away (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) > DesiredDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the player is too close to the target (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) < MinumumDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the target is on the other side of the player (CHANGE TARGET)
|
||||
if (Vector2.Distance(transform.position, player.transform.position) < Vector2.Distance(target.transform.position, player.transform.position))
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the player is not within line of sight of the desired position (CHANGE TARGET)
|
||||
if (DetermineLineOfSight(target, player) == false)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the enemy reaches the target
|
||||
if (Vector2.Distance(target.transform.position, transform.position) <= 1 && DetermineLineOfSight(gameObject, player))
|
||||
{
|
||||
if (canFire == false && inFiringCycle == false)
|
||||
{
|
||||
canFire = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MISC METHODS
|
||||
// Perform jump
|
||||
async Task JumpMethod()
|
||||
{
|
||||
if (canJump == true)
|
||||
{
|
||||
rb.linearVelocity = new Vector2(rb.linearVelocity.x, JumpHeight * 12);
|
||||
canJump = false;
|
||||
await Task.Delay(500);
|
||||
}
|
||||
}
|
||||
// Use Ranged Weapon
|
||||
async Task UseWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
canMove = false;
|
||||
inFiringCycle = true;
|
||||
|
||||
// Create Projectile
|
||||
GameObject BulletInstance;
|
||||
BulletInstance = Instantiate(projectile, transform.position, Quaternion.LookRotation(transform.position - GameObject.FindGameObjectWithTag("Player").transform.position + new Vector3(UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), 0)));
|
||||
BulletInstance.transform.parent = transform;
|
||||
|
||||
// Send it on it's way
|
||||
BulletInstance.GetComponent<Rigidbody2D>().linearVelocity = BulletInstance.transform.forward * -1 * WeaponProjectileSpeed;
|
||||
BulletInstance.transform.rotation = Quaternion.Euler(new Vector3(0, 0, Vector2.SignedAngle(Vector2.right, BulletInstance.transform.forward) - 90));
|
||||
WeaponCurrentMagazineAmmount--;
|
||||
|
||||
await Task.Delay(WeaponFireRate);
|
||||
|
||||
canFire = true;
|
||||
canMove = true;
|
||||
inFiringCycle = false;
|
||||
}
|
||||
|
||||
// Reload Weapon
|
||||
async Task ReloadWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
canMove = false;
|
||||
//play reload animation
|
||||
currentlyReloading = true;
|
||||
await Task.Delay(WeaponReloadTime);
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
currentlyReloading = false;
|
||||
|
||||
if (NumberOfHitsPerMag / WeaponMagazineSize < 0.5)
|
||||
{
|
||||
DesiredDistance -= 5;
|
||||
}
|
||||
NumberOfHitsPerMag = 0;
|
||||
|
||||
canFire = true;
|
||||
canMove = true;
|
||||
}
|
||||
|
||||
// Part of the patrol cycle
|
||||
async Task MoveNextPatrol()
|
||||
{
|
||||
LastKnownPlayerLocation = null;
|
||||
DesiredDistance = WeaponRange - 5;
|
||||
|
||||
canPursue = false;
|
||||
currentlyMovingToNextPatrolTarget = true;
|
||||
|
||||
//Find Random Position nearby
|
||||
if (Vector2.Distance(transform.position, target.transform.position) <= 0.5 || PatrolPositions.Contains(target) == false)
|
||||
{
|
||||
target = PatrolPositions[UnityEngine.Random.Range(0, PatrolPositions.Count)];
|
||||
await Task.Delay(PatrolStallTime + UnityEngine.Random.Range((PatrolStallTime * -1), PatrolStallTime));
|
||||
}
|
||||
|
||||
currentlyMovingToNextPatrolTarget = false;
|
||||
|
||||
}
|
||||
|
||||
// General Utility
|
||||
bool DetermineLineOfSight(GameObject object1, GameObject object2)
|
||||
{
|
||||
Vector3 RaycastStart = object1.transform.position;
|
||||
Vector3 RaycastDirection = (object2.transform.position - object1.transform.position).normalized;
|
||||
float RaycastDistance = Vector3.Distance(object2.transform.position, object1.transform.position);
|
||||
|
||||
if (Physics2D.Raycast(RaycastStart, RaycastDirection, RaycastDistance, LayerMask.GetMask("SolidGround")) == false)
|
||||
{
|
||||
Debug.DrawRay(RaycastStart, RaycastDirection * RaycastDistance);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Part of the pursue cycle
|
||||
void ComputeClosestPositionToPlayer()
|
||||
{
|
||||
canFire = false;
|
||||
canMove = true;
|
||||
|
||||
SuitablePositions.Clear();
|
||||
foreach (GameObject query in AllPositions)
|
||||
{
|
||||
// Check the distance of the position
|
||||
if (Vector2.Distance(query.transform.position, player.transform.position) < DesiredDistance && Vector2.Distance(query.transform.position, player.transform.position) >= MinumumDistance)
|
||||
{
|
||||
// Check line of sight of the position
|
||||
if (DetermineLineOfSight(query, player))
|
||||
{
|
||||
SuitablePositions.Add(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (SuitablePositions.Count > 0)
|
||||
{
|
||||
target = SuitablePositions[UnityEngine.Random.Range(0, SuitablePositions.Count)];
|
||||
foreach (GameObject pos in SuitablePositions)
|
||||
{
|
||||
//Find the point that is closest to the enemy
|
||||
if (Vector2.Distance(transform.position, pos.transform.position) < Vector2.Distance(transform.position, target.transform.position))
|
||||
{
|
||||
target = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 80fa5955b49f1ca43afa1e8e6ac2eb39
|
||||
@@ -0,0 +1 @@
|
||||
uid://bps4tp2ybpdcy
|
||||
@@ -0,0 +1,189 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Pathfinding;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
//Designed by Jacob Weedman
|
||||
//V 0.0.1
|
||||
//DO NOT USE THIS ON ANY ENEMIES
|
||||
|
||||
public class TestEnemyAITurret : MonoBehaviour
|
||||
{
|
||||
|
||||
//MISC
|
||||
GameObject projectile;
|
||||
GameObject player;
|
||||
GameObject barrel;
|
||||
public float DistanceFromPlayer;
|
||||
Vector2 StartPosition;
|
||||
|
||||
//CONDITIONS/GENERAL INFORMATION
|
||||
public bool canFire = true;
|
||||
public bool currentlyReloading = false;
|
||||
public bool targetingPlayer = false;
|
||||
public bool inFiringCycle = false;
|
||||
public int WeaponCurrentMagazineAmmount;
|
||||
public float angle;
|
||||
|
||||
//ENEMY STATS (Changeable)
|
||||
public float Health = 100;
|
||||
public int PlayerDetectionRange = 25;
|
||||
|
||||
//WEAPON STATS (CHANGEABLE)
|
||||
public int WeaponDamage = 15; // Damage per hit
|
||||
public int WeaponFireRate = 250; // Delay in time between attacks both melee and ranged
|
||||
public float WeaponRandomSpread = 5f; // Random direction of lanched projectiles
|
||||
public int WeaponRange = 20; // Maximum range of the projectile before it drops off
|
||||
public float WeaponProjectileSpeed = 30f; // Speed of launched projectiles
|
||||
public int WeaponMagazineSize = 20; // Number of shots the enemy will take before having to reload
|
||||
public int WeaponReloadTime = 5000; // Time it takes to reload the magazine
|
||||
|
||||
//ONCE THE GAME STARTS
|
||||
void Start()
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
|
||||
projectile = GameObject.Find("EnemyProjectile");
|
||||
player = GameObject.FindGameObjectWithTag("Player");
|
||||
barrel = transform.Find("Barrel").gameObject;
|
||||
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
|
||||
canFire = true;
|
||||
}
|
||||
|
||||
//MAIN LOGIC
|
||||
void FixedUpdate()
|
||||
{
|
||||
DistanceFromPlayer = Vector2.Distance(transform.position, player.transform.position);
|
||||
|
||||
// Death
|
||||
if (Health <= 0)
|
||||
{
|
||||
GameObject DeadBody;
|
||||
DeadBody = Instantiate(gameObject, transform.position, Quaternion.identity);
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<TestEnemyAIGroundRanged>());
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<Seeker>());
|
||||
|
||||
foreach (Transform child in DeadBody.transform) {
|
||||
GameObject.Destroy(child.gameObject);
|
||||
}
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
|
||||
//ICONS
|
||||
if (targetingPlayer)
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
if (currentlyReloading)
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
//RANGED ATTACK
|
||||
// Check if enemy has line of sight on the player & if they are in the acceptable range
|
||||
if (WeaponCurrentMagazineAmmount > 0 && canFire == true && currentlyReloading == false)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= WeaponRange)
|
||||
{
|
||||
UseWeapon();
|
||||
}
|
||||
}
|
||||
else if (WeaponCurrentMagazineAmmount == 0 && currentlyReloading == false)
|
||||
{
|
||||
ReloadWeapon();
|
||||
}
|
||||
|
||||
//PLAYER TARGETING
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= WeaponRange + 10)
|
||||
{
|
||||
targetingPlayer = true;
|
||||
|
||||
// Set angle to player
|
||||
angle = Mathf.Atan2(player.transform.position.y - barrel.transform.position.y, player.transform.position.x - barrel.transform.position.x) * Mathf.Rad2Deg;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reset barrel rotation
|
||||
angle = 90f;
|
||||
}
|
||||
|
||||
// Rotate barrel towards player
|
||||
Quaternion targetRotation = Quaternion.Euler(new Vector3(0, 0, angle));
|
||||
barrel.transform.rotation = Quaternion.RotateTowards(barrel.transform.rotation, targetRotation, 100 * Time.deltaTime);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
//MISC METHODS
|
||||
// Use Ranged Weapon
|
||||
async Task UseWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
inFiringCycle = true;
|
||||
|
||||
// Create Projectile
|
||||
GameObject BulletInstance;
|
||||
BulletInstance = Instantiate(projectile, transform.position, Quaternion.LookRotation(transform.position - GameObject.FindGameObjectWithTag("Player").transform.position + new Vector3(UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), 0)));
|
||||
BulletInstance.transform.parent = transform;
|
||||
|
||||
// Send it on it's way
|
||||
BulletInstance.GetComponent<Rigidbody2D>().linearVelocity = BulletInstance.transform.forward * -1 * WeaponProjectileSpeed;
|
||||
BulletInstance.transform.rotation = Quaternion.Euler(new Vector3(0, 0, Vector2.SignedAngle(Vector2.right, BulletInstance.transform.forward) - 90));
|
||||
WeaponCurrentMagazineAmmount--;
|
||||
|
||||
await Task.Delay(WeaponFireRate);
|
||||
|
||||
canFire = true;
|
||||
inFiringCycle = false;
|
||||
}
|
||||
|
||||
// Reload Weapon
|
||||
async Task ReloadWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
//play reload animation
|
||||
currentlyReloading = true;
|
||||
await Task.Delay(WeaponReloadTime);
|
||||
WeaponCurrentMagazineAmmount = WeaponMagazineSize;
|
||||
currentlyReloading = false;
|
||||
|
||||
canFire = true;
|
||||
}
|
||||
|
||||
// General Utility
|
||||
bool DetermineLineOfSight(GameObject object1, GameObject object2)
|
||||
{
|
||||
Vector3 RaycastStart = object1.transform.position;
|
||||
Vector3 RaycastDirection = (object2.transform.position - object1.transform.position).normalized;
|
||||
float RaycastDistance = Vector3.Distance(object2.transform.position, object1.transform.position);
|
||||
|
||||
if (Physics2D.Raycast(RaycastStart, RaycastDirection, RaycastDistance, LayerMask.GetMask("SolidGround")) == false)
|
||||
{
|
||||
Debug.DrawRay(RaycastStart, RaycastDirection * RaycastDistance);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94c55f3189ba7ee4399dc3c6f10550ce
|
||||
@@ -0,0 +1 @@
|
||||
uid://b6jwhvkhdkpdo
|
||||
@@ -0,0 +1,440 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Pathfinding;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
//Designed by Jacob Weedman
|
||||
//Use on warper type enemies
|
||||
|
||||
public class WarperAI : MonoBehaviour
|
||||
{
|
||||
|
||||
//MISC
|
||||
GameObject target;
|
||||
float nextWaypointDistance = 5;
|
||||
Path path;
|
||||
int currentWaypoint = 0;
|
||||
bool reachedEndOfPath = false;
|
||||
GameObject projectile;
|
||||
GameObject player;
|
||||
public List<GameObject> SuitablePositions;
|
||||
public List<GameObject> AllPositions;
|
||||
float DistanceFromPlayer;
|
||||
Vector2 StartPosition;
|
||||
public List<GameObject> PatrolPositions;
|
||||
GameObject LastKnownPlayerLocation;
|
||||
|
||||
//CONDITIONS/GENERAL INFORMATION
|
||||
bool canJump = true;
|
||||
bool canMove = true; // Prevent all movement
|
||||
bool canPursue = false; // Follow player
|
||||
bool canFire = false;
|
||||
bool currentlyPatrolling;
|
||||
bool currentlyMovingToNextPatrolTarget = false;
|
||||
float DesiredDistance;
|
||||
float MinumumDistance = 15f;
|
||||
bool targetingPlayer = false;
|
||||
bool inFiringCycle = false;
|
||||
int NumberOfHitsPerMag = 0;
|
||||
bool canTeleport = true;
|
||||
|
||||
//ENEMY STATS (Changeable)
|
||||
public float Speed = 0.7f; // In relation to player's walking speed
|
||||
public float JumpHeight = 0.7f; // In relation to player's regular jump height
|
||||
public float Health = 100;
|
||||
public float PatrolDistance = 10;
|
||||
public int PatrolStallTime = 2000; //ms
|
||||
public int PlayerDetectionRange = 25;
|
||||
public int TeleportCooldown = 500; //ms
|
||||
|
||||
//WEAPON STATS (CHANGEABLE)
|
||||
public int WeaponRange = 30; // Maximum range of the projectile before it drops off
|
||||
public int WeaponFireRate = 5000; // ms
|
||||
public int WeaponDamage = 1;
|
||||
|
||||
//REFERENCES
|
||||
Seeker seeker;
|
||||
Rigidbody2D rb;
|
||||
|
||||
//ONCE THE GAME STARTS
|
||||
void Start()
|
||||
{
|
||||
seeker = GetComponent<Seeker>();
|
||||
rb = GetComponent<Rigidbody2D>();
|
||||
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
|
||||
StartPosition = transform.position;
|
||||
AllPositions = GameObject.FindGameObjectsWithTag("PossiblePositions").ToList();
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
if (Vector2.Distance(pos.transform.position, StartPosition) <= PatrolDistance)
|
||||
{
|
||||
PatrolPositions.Add(pos);
|
||||
}
|
||||
}
|
||||
target = PatrolPositions[UnityEngine.Random.Range(0, PatrolPositions.Count)];
|
||||
|
||||
projectile = GameObject.Find("EnemyProjectile");
|
||||
player = GameObject.FindGameObjectWithTag("Player");
|
||||
LastKnownPlayerLocation = null;
|
||||
|
||||
DesiredDistance = WeaponRange - 5;
|
||||
|
||||
InvokeRepeating("UpdatePath", 0f, 0.1f);
|
||||
InvokeRepeating("PathfindingTimeout", 0f, 30);
|
||||
|
||||
}
|
||||
|
||||
// When enemy has reached the next node
|
||||
void OnPathComplete(Path p)
|
||||
{
|
||||
if (!p.error)
|
||||
{
|
||||
path = p;
|
||||
currentWaypoint = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Select next node
|
||||
void UpdatePath()
|
||||
{
|
||||
if (seeker.IsDone() && target != null)
|
||||
{
|
||||
seeker.StartPath(rb.position, target.transform.position, OnPathComplete);
|
||||
}
|
||||
}
|
||||
|
||||
// Pathfiniding Timeout
|
||||
void PathfindingTimeout()
|
||||
{
|
||||
if (Vector2.Distance(transform.position, target.transform.position) > 0.5)
|
||||
{
|
||||
if (targetingPlayer == false)
|
||||
{
|
||||
target = gameObject;
|
||||
MoveNextPatrol();
|
||||
Teleport();
|
||||
}
|
||||
else
|
||||
{
|
||||
Teleport();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MAIN LOGIC
|
||||
void FixedUpdate()
|
||||
{
|
||||
//DEATH
|
||||
if (Health <= 0)
|
||||
{
|
||||
GameObject DeadBody;
|
||||
DeadBody = Instantiate(gameObject, transform.position, Quaternion.identity);
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<TestEnemyAIGroundRanged>());
|
||||
Destroy(GameObject.Find(DeadBody.name).GetComponent<Seeker>());
|
||||
|
||||
foreach (Transform child in DeadBody.transform) {
|
||||
GameObject.Destroy(child.gameObject);
|
||||
}
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
//ICONS
|
||||
if (targetingPlayer)
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
//MISC PATHFINDING
|
||||
if (path == null)
|
||||
return;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
if (currentWaypoint >= path.vectorPath.Count)
|
||||
{
|
||||
reachedEndOfPath = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
reachedEndOfPath = false;
|
||||
}
|
||||
|
||||
//RANGED ATTACK
|
||||
// Check if enemy has line of sight on the player & if they are in the acceptable range
|
||||
if ( canFire == true && currentlyPatrolling == false)
|
||||
{
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= WeaponRange && Vector2.Distance(transform.position, player.transform.position) <= DesiredDistance)
|
||||
{
|
||||
UseWeapon();
|
||||
}
|
||||
}
|
||||
|
||||
//MOVEMENT
|
||||
if (canMove == true)
|
||||
{
|
||||
canFire = false;
|
||||
|
||||
Vector2 direction = (Vector2)path.vectorPath[currentWaypoint] - rb.position;
|
||||
//float xDirection = target.transform.position.x - transform.position.x;
|
||||
|
||||
if (direction.x > 0) // Move right
|
||||
{
|
||||
rb.AddForce(new Vector2(Speed * 20, rb.linearVelocity.y));
|
||||
}
|
||||
|
||||
if (direction.x < 0) // Move left
|
||||
{
|
||||
rb.AddForce(new Vector2(-1 * Speed * 20, rb.linearVelocity.y));
|
||||
}
|
||||
|
||||
if (direction.y > 1f && targetingPlayer == false) // Wants to jump
|
||||
{
|
||||
JumpMethod();
|
||||
}
|
||||
|
||||
// A* logic
|
||||
float distance = Vector2.Distance(rb.position, path.vectorPath[currentWaypoint]);
|
||||
|
||||
if (distance < nextWaypointDistance)
|
||||
{
|
||||
currentWaypoint++;
|
||||
}
|
||||
}
|
||||
|
||||
//GROUND DETECTION
|
||||
//Detecting if the enemy has reached the ground
|
||||
if (GetComponentInChildren<GroundCheck>().isGrounded == true && rb.linearVelocity.y == 0)
|
||||
{
|
||||
canJump = true;
|
||||
if (inFiringCycle == false)
|
||||
{
|
||||
canFire = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
canJump = false;
|
||||
canFire = false;
|
||||
}
|
||||
|
||||
//PATROL & DETECTION
|
||||
// Enemy detects player
|
||||
if (DetermineLineOfSight(gameObject, player) == true && Vector2.Distance(transform.position, player.transform.position) <= PlayerDetectionRange)
|
||||
{
|
||||
currentlyPatrolling = false;
|
||||
targetingPlayer = true;
|
||||
canPursue = true;
|
||||
// Get the last know player location by finding which of the positions is closest to the player
|
||||
if (LastKnownPlayerLocation == null)
|
||||
{
|
||||
LastKnownPlayerLocation = gameObject;
|
||||
}
|
||||
foreach (GameObject pos in AllPositions)
|
||||
{
|
||||
if (Vector2.Distance(player.transform.position, pos.transform.position) < Vector2.Distance(player.transform.position, LastKnownPlayerLocation.transform.position))
|
||||
{
|
||||
LastKnownPlayerLocation = pos;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// Player has broken line of sight and the enemy will attempt to move to the last known location
|
||||
else if (LastKnownPlayerLocation != null)
|
||||
{
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) > 0.5)
|
||||
{
|
||||
canPursue = false;
|
||||
target = LastKnownPlayerLocation;
|
||||
Teleport();
|
||||
}
|
||||
if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) < 0.5 && DetermineLineOfSight(player, gameObject) == false)
|
||||
{
|
||||
targetingPlayer = false;
|
||||
LastKnownPlayerLocation = null;
|
||||
}
|
||||
|
||||
}
|
||||
// Go back to patrol move
|
||||
else
|
||||
{
|
||||
currentlyPatrolling = true;
|
||||
targetingPlayer = false;
|
||||
if (canMove == true && currentlyMovingToNextPatrolTarget == false)
|
||||
{
|
||||
MoveNextPatrol();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//TARGET DETERMINATION
|
||||
if (canPursue == true)
|
||||
{
|
||||
// If enemy is at the target position (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, transform.position) < 1.5 && canTeleport)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the player moves away (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) > DesiredDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the player is too close to the target (CHANGE TARGET)
|
||||
if (Vector2.Distance(target.transform.position, player.transform.position) < MinumumDistance)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the target is on the other side of the player (CHANGE TARGET)
|
||||
if (Vector2.Distance(transform.position, player.transform.position) < Vector2.Distance(target.transform.position, player.transform.position))
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// If the player is not within line of sight of the desired position (CHANGE TARGET)
|
||||
if (DetermineLineOfSight(target, player) == false)
|
||||
{
|
||||
ComputeClosestPositionToPlayer();
|
||||
}
|
||||
|
||||
// Teleport enemy
|
||||
if (canTeleport)
|
||||
{
|
||||
if (Vector2.Distance(transform.position, target.transform.position) > 0.5)
|
||||
{
|
||||
Teleport();
|
||||
}
|
||||
}
|
||||
|
||||
// If the enemy reaches the target
|
||||
if (Vector2.Distance(target.transform.position, transform.position) <= 1 && DetermineLineOfSight(gameObject, player))
|
||||
{
|
||||
if (canFire == false && inFiringCycle == false)
|
||||
{
|
||||
canFire = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MISC METHODS
|
||||
// Perform jump
|
||||
async Task JumpMethod()
|
||||
{
|
||||
rb.linearVelocity = new Vector2(rb.linearVelocity.x, JumpHeight * 12);
|
||||
canJump = false;
|
||||
await Task.Delay(500);
|
||||
}
|
||||
// Use Weapon
|
||||
async Task UseWeapon()
|
||||
{
|
||||
canFire = false;
|
||||
canMove = false;
|
||||
inFiringCycle = true;
|
||||
|
||||
GameObject EvilAura;
|
||||
EvilAura = Instantiate(GameObject.Find("EvilAura"), new Vector3(transform.position.x + UnityEngine.Random.Range(-0.5f, 0.5f), transform.position.y + UnityEngine.Random.Range(-0.5f, 0.5f), GameObject.Find("EvilAura").transform.position.z), Quaternion.identity);
|
||||
EvilAura.transform.rotation = Quaternion.Euler(Vector3.forward * UnityEngine.Random.Range(-90, 90));
|
||||
|
||||
// Send it on its way
|
||||
EvilAura.GetComponent<Rigidbody2D>().linearVelocity = new Vector2(transform.position.x - player.transform.position.x, transform.position.y - player.transform.position.y).normalized * WeaponRange * -1;
|
||||
|
||||
//Set Variables
|
||||
EvilAura.GetComponent<EnemyParticleWeapon>().destroy = true;
|
||||
EvilAura.GetComponent<EnemyParticleWeapon>().opacity = true;
|
||||
EvilAura.GetComponent<EnemyParticleWeapon>().rotate = true;
|
||||
EvilAura.GetComponent<EnemyParticleWeapon>().damageAmmount = WeaponDamage;
|
||||
|
||||
await Task.Delay(WeaponFireRate);
|
||||
|
||||
canFire = true;
|
||||
canMove = true;
|
||||
inFiringCycle = false;
|
||||
}
|
||||
|
||||
// Part of the patrol cycle
|
||||
async Task MoveNextPatrol()
|
||||
{
|
||||
LastKnownPlayerLocation = null;
|
||||
DesiredDistance = WeaponRange - 5;
|
||||
|
||||
canPursue = false;
|
||||
currentlyMovingToNextPatrolTarget = true;
|
||||
|
||||
//Find Random Position nearby
|
||||
if (Vector2.Distance(transform.position, target.transform.position) <= 0.5 || PatrolPositions.Contains(target) == false)
|
||||
{
|
||||
target = PatrolPositions[UnityEngine.Random.Range(0, PatrolPositions.Count)];
|
||||
await Task.Delay(PatrolStallTime + UnityEngine.Random.Range((PatrolStallTime * -1), PatrolStallTime));
|
||||
}
|
||||
|
||||
currentlyMovingToNextPatrolTarget = false;
|
||||
|
||||
}
|
||||
|
||||
// General Utility
|
||||
bool DetermineLineOfSight(GameObject object1, GameObject object2)
|
||||
{
|
||||
Vector3 RaycastStart = object1.transform.position;
|
||||
Vector3 RaycastDirection = (object2.transform.position - object1.transform.position).normalized;
|
||||
float RaycastDistance = Vector3.Distance(object2.transform.position, object1.transform.position);
|
||||
|
||||
if (Physics2D.Raycast(RaycastStart, RaycastDirection, RaycastDistance, LayerMask.GetMask("SolidGround")) == false)
|
||||
{
|
||||
Debug.DrawRay(RaycastStart, RaycastDirection * RaycastDistance);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Teleport
|
||||
async Task Teleport()
|
||||
{
|
||||
canTeleport = false;
|
||||
transform.position = target.transform.position;
|
||||
await Task.Delay(TeleportCooldown + UnityEngine.Random.Range(0, TeleportCooldown / 2));
|
||||
canTeleport = true;
|
||||
}
|
||||
|
||||
// Part of the pursue cycle
|
||||
void ComputeClosestPositionToPlayer()
|
||||
{
|
||||
canFire = false;
|
||||
canMove = true;
|
||||
|
||||
SuitablePositions.Clear();
|
||||
foreach (GameObject query in AllPositions)
|
||||
{
|
||||
// Check the distance of the position
|
||||
if (Vector2.Distance(query.transform.position, player.transform.position) < 30 && Vector2.Distance(query.transform.position, player.transform.position) >= 10)
|
||||
{
|
||||
// Check line of sight of the position
|
||||
if (DetermineLineOfSight(query, player))
|
||||
{
|
||||
SuitablePositions.Add(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (SuitablePositions.Count > 0)
|
||||
{
|
||||
target = SuitablePositions[UnityEngine.Random.Range(0, SuitablePositions.Count)];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8326ce0bf6190774b8982a52045b38e2
|
||||
@@ -0,0 +1 @@
|
||||
uid://c0dqxf7d6xd7d
|
||||
@@ -0,0 +1,124 @@
|
||||
using JetBrains.Annotations;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using static UnityEngine.Rendering.DebugUI;
|
||||
|
||||
public class playerMovement : MonoBehaviour
|
||||
{
|
||||
|
||||
private GroundCheck groundCheck; //Allows Use Of GroundCheck Script Variables
|
||||
|
||||
private Rigidbody2D body;
|
||||
private BoxCollider2D hBox;
|
||||
|
||||
private bool allowedToMove = true, allowedToJump = true, doubleJumpUnlocked = true; //Unlocking & Locking Abilities
|
||||
private bool isGrounded, canJumpAgain; //Physics Checks
|
||||
private float CoyoteTimer, BufferTimer, airjumpTurnTimer, turnaround; //Timers & Calculation Variables
|
||||
private float moveSpeed = 10f, groundJumpSpeed = 18f, airJumpSpeed = 14f, turnResponsiveness = 4f; //Physics Values
|
||||
private float coyoteTimeAmount = .1f, airjumpTurnaroundTimeAmount = 1.5f, jumpBufferAmount = .2f; //Leniency Velues
|
||||
|
||||
private KeyCode hop = KeyCode.W, crouch = KeyCode.S, left = KeyCode.A, right = KeyCode.D; //Controls
|
||||
|
||||
|
||||
void Start()
|
||||
{
|
||||
body = GetComponent<Rigidbody2D>();
|
||||
hBox = transform.GetComponent<BoxCollider2D>();
|
||||
body.gravityScale = 4;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
|
||||
|
||||
TimerHandler();
|
||||
|
||||
GroundCheck();
|
||||
|
||||
Movement();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void GroundCheck() {
|
||||
|
||||
groundCheck = GetComponentInChildren<GroundCheck>(); // Ground Check
|
||||
isGrounded = groundCheck.isGrounded;
|
||||
|
||||
if (doubleJumpUnlocked && isGrounded) canJumpAgain = true; //Realoading Double Jump
|
||||
|
||||
} //Ground & Double Jump Checks
|
||||
|
||||
void TimerHandler()
|
||||
{
|
||||
if (isGrounded && !(body.linearVelocity.y > 0)) CoyoteTimer = coyoteTimeAmount; //Coyote Time
|
||||
else CoyoteTimer -= Time.deltaTime;
|
||||
|
||||
if (Input.GetKeyDown(hop)) BufferTimer = jumpBufferAmount; //Jump Buffering
|
||||
if (BufferTimer > 0) BufferTimer -= Time.deltaTime;
|
||||
|
||||
if (airjumpTurnTimer > 0) airjumpTurnTimer -= Time.deltaTime; //Double Jump Physics
|
||||
} //Input Leniency & Special Input Time Windows
|
||||
|
||||
void Movement() {
|
||||
|
||||
if (allowedToMove)
|
||||
{
|
||||
if (!(Input.GetKey(left) && Input.GetKey(right)))
|
||||
{
|
||||
|
||||
if (!isGrounded) turnaround = turnResponsiveness * .75f;
|
||||
else turnaround = turnResponsiveness;
|
||||
|
||||
if (Input.GetKey(left))
|
||||
{
|
||||
body.linearVelocity += new Vector2(-moveSpeed * turnaround * Time.deltaTime, 0);
|
||||
body.linearVelocity = new Vector2(Mathf.Clamp(body.linearVelocity.x, -moveSpeed, moveSpeed), body.linearVelocity.y);
|
||||
}
|
||||
|
||||
if (Input.GetKey(right))
|
||||
{
|
||||
body.linearVelocity += new Vector2(moveSpeed * turnaround * Time.deltaTime, 0);
|
||||
body.linearVelocity = new Vector2(Mathf.Clamp(body.linearVelocity.x, -moveSpeed, moveSpeed), body.linearVelocity.y);
|
||||
}
|
||||
} //Walking
|
||||
|
||||
if ( !(Input.GetKey(left) || Input.GetKey(right)) || (Input.GetKey(left) && Input.GetKey(right)))
|
||||
{
|
||||
body.linearVelocity = new Vector2(body.linearVelocity.x * .985f, body.linearVelocity.y);
|
||||
} //Custom friction
|
||||
} //Horizontal Movement & Physics
|
||||
|
||||
|
||||
if (allowedToJump)
|
||||
{
|
||||
if ((CoyoteTimer > 0 && BufferTimer > 0))
|
||||
{
|
||||
body.linearVelocity = new Vector2(body.linearVelocity.x, groundJumpSpeed);
|
||||
BufferTimer = 0;
|
||||
CoyoteTimer = 0;
|
||||
} //Normal jump is performed
|
||||
|
||||
|
||||
else if (canJumpAgain && Input.GetKeyDown(hop))
|
||||
{
|
||||
body.linearVelocity = new Vector2(body.linearVelocity.x, airJumpSpeed);
|
||||
canJumpAgain = false;
|
||||
|
||||
airjumpTurnTimer = airjumpTurnaroundTimeAmount;
|
||||
} //Air jump is performed
|
||||
|
||||
|
||||
if (!Input.GetKey(hop) && body.linearVelocity.y > 0)
|
||||
{
|
||||
body.linearVelocity = new Vector2(body.linearVelocity.x, body.linearVelocity.y * .92f);
|
||||
} //Jump Released
|
||||
|
||||
} //Vertical Movement & Physics
|
||||
|
||||
} //Check Player Input
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2627a91a1f5308c41a35eeeea160fda1
|
||||
@@ -0,0 +1 @@
|
||||
uid://df2ji7oywfcfu
|
||||
Reference in New Issue
Block a user