using System; using System.Collections; using UnityEngine; public class Drone : MonoBehaviour, ILaserable { /// /// The Drone's Health Points. /// [Tooltip("The Drone's Health Points.")] public int Hitpoints = 100; /// /// How long between each laser shot. /// [Tooltip("How long between each laser shot.e")] public float SecondsBetweenLaserShots = 4f; /// /// Accuracy of each shot. The laser will aim at a random point in a sphere around the user. This value sets that sphere's radius. /// [Tooltip("Accuracy of each shot. The laser will aim at a random point in a sphere around the user. This value sets that sphere's radius.")] public float laserAccuracy = 0.5f; /// /// The object spawned when the drone shoots. /// [Tooltip("The object spawned when the drone shoots.")] public GameObject LaserPrefab; /// /// The object that gets spawned when the drone dies. Intended to be an explosion. /// [Tooltip("The object that gets spawned when the drone dies. Intended to be an explosion.")] public GameObject ExplosionPrefab; /// /// How long it takes the drone to move to a new position. /// [Tooltip("How long it takes the drone to move to a new position.")] public float smoothTime = 0.75f; /// /// How far a potential movement point must be from real geometry to be considered valid . /// Set to higher values if the drone is moving into walls or other objects. /// [Tooltip("How far a potential movement point must be from real geometry to be considered valid. " + "Set to higher values if the drone is moving alongside walls or other objects. ")] public float ClearRadius = 2f; /// /// How many times we check near a potential movement point to see if there are any obstacles around it. /// Higher values make it less likely a drone will move inside an object, but may cause noticeable stutter. /// [Tooltip("How many times we check near a potential movement point to see if there are any obstacles around it. " + "Higher values make it less likely a drone will move inside an object, but may cause noticeable stutter.")] public int radiusCheckRate = 100; /// /// The maximum amount of collisions detected near a movement point allowed. /// Higher values make it less likely for a drone to move inside an object, but too high and it may not move at all. /// [Tooltip("The maximum amount of collisions detected near a movement point allowed. " + "Higher values make it less likely for a drone to move inside an object, but too high and it may not move at all.")] public float percentageThreshold = 0.35f; /// /// Maximum angle between drone and its target to have before the drone will turn to face it. /// [Tooltip("Maximum angle between drone and its target to have before the drone will turn to face it.")] public float angleBeforeRotate = 20f; /// /// "AudioClips to play for the Drone. Element 0 is for its laser, 1 is for its destruction." /// [Tooltip("AudioClips to play for the Drone. Element 0 is for its laser, 1 is for its destruction.")] public AudioClip[] clips; /// /// Counts down the time between shots. /// private float lasershottimer; /// /// What the drone is looking/shooting at. /// private Transform target; /// /// The gun's target to rotate towards. /// private Vector3 guntarget; /// /// Reference to the audio source. /// private AudioSource audiosource; /// /// Main ZEDManager to use for determining where the drone spawns and what it attacks. /// If left empty, will choose the first available ZEDManager in the scene. /// [Tooltip("Main ZEDManager to use for determining where the drone spawns and what it attacks. " + "If left empty, will choose the first available ZEDManager in the scene. ")] public ZEDManager zedManager = null; /// /// The Mesh Renderer of the drone, so we can modify its material when it takes damage. /// private Renderer meshrenderer; /// /// FX for when we take damage. /// private Transform damagefx; /// /// Where we shoot the laser from. /// private Transform laseranchor; /// /// FX for when we shoot. /// private ParticleSystem muzzleflashfx; /// /// The bone that moves the drone's gun. /// private Transform dronearm; /// /// The next position that the drone needs to move to. Set only after a valid move location has been confirmed. /// private Vector3 nextposition; /// /// A reference for the SmoothDamp of the drone's movement. /// private Vector3 velocity = Vector3.zero; /// /// The light to be enabled briefly when the drone has fired. /// private Light gunlight; /// /// Whether or not we can rotate towards our target. /// private bool canrotate = false; /// /// Are we moving the drone or not. /// private bool canchangerotation = false; /// /// Link with the object that spawned this instance. Used to clear the spawner's reference in OnDestroy(). /// private DroneSpawner spawner; private float damageFlashAmount { #if !ZED_HDRP || !ZED_URP get { return meshrenderer.material.GetColor("_Color").a; } set { Color newcol = meshrenderer.material.GetColor("_Color"); newcol.a = value; meshrenderer.material.SetColor("_Color", newcol); } #elif ZED_HDRP get { return meshrenderer.material.GetColor("_BaseColor").a; } set { Color newcol = meshrenderer.material.GetColor("_BaseColor"); newcol.a = value; meshrenderer.material.SetColor("_BaseColor", newcol); } #elif ZED_URP get { return meshrenderer.material.GetColor("_BaseColor").a; } set { Color newcol = meshrenderer.material.GetColor("_BaseColor"); newcol.a = value; meshrenderer.material.SetColor("_BaseColor", newcol); } #endif } // Use this for initialization void Start () { //Cache the ZED's left camera for occlusion testing purposes if (zedManager==null) zedManager = FindObjectOfType(); // Set the default position of the Drone to the one he spawned at. nextposition = transform.position; //Set the countdown timer to fire a laser lasershottimer = SecondsBetweenLaserShots; // Set the audio source. audiosource = GetComponent(); if(audiosource != null && clips.Length > 2) { audiosource.clip = clips[2]; audiosource.volume = 1f; audiosource.Play(); } // If these variables aren't set, look for their objects by their default name. Transform[] children = transform.GetComponentsInChildren(); foreach (var child in children) { if (child.name == "Drone_Mesh") meshrenderer = child.GetComponent(); else if (child.name == "MuzzleFlash_FX") muzzleflashfx = child.GetComponent(); else if (child.name == "Damage_FX") damagefx = child; else if (child.name == "Laser_Anchor") laseranchor = child; else if (child.name == "Gun_Arm") dronearm = child; else if (child.name == "Point_Light") gunlight = child.GetComponent(); } //If the _target isn't set, set it to the PlayerDamageReceiver, assuming there is one in the scene. if(!target) { target = FindObjectOfType().transform; guntarget = target.position; } } void Update () { //If we've flashed the damage material, lower the blend amount. if (damageFlashAmount > 0) { float tmp = damageFlashAmount; tmp -= Time.deltaTime / 1.5f; if (tmp < 0) tmp = 0; damageFlashAmount = tmp; } //Enabling damage FX based on HitPoints left. switch (Hitpoints) { case 80: damagefx.GetChild (0).gameObject.SetActive (true); break; case 50: damagefx.GetChild (1).gameObject.SetActive (true); break; case 20: damagefx.GetChild (2).gameObject.SetActive (true); break; } //If its time to can change our position... if (canchangerotation) { //...then look for a new one until it's a valid location. if (FindNewMovePosition (out nextposition)) { canchangerotation = false; } } //Count down the laser shot timer. If zero, fire and reset it. lasershottimer -= Time.deltaTime; if (lasershottimer <= 0f && target != null) { //Apply a degree of accuracy based on the drone distance from the player //Take a random point on a radius around the Target's position. That radius becomes smaller as the target is closer to us. Vector3 randompoint = UnityEngine.Random.onUnitSphere * (laserAccuracy * (Vector3.Distance(target.position, transform.position) / (spawner.maxSpawnDistance / 2))) + target.position; //Check if the chosen point is closer to the edge of the camera. We dont want any projectile coming straight in the players eyes. if (randompoint.z >= target.position.z + 0.15f || randompoint.z <= target.position.z - 0.15f) { guntarget = randompoint; //Firing the laser FireLaser(randompoint); //Reseting the timer. lasershottimer = SecondsBetweenLaserShots; } } //Drone Movement & Rotation if (target != null) { //Get the direction to the target. Vector3 targetDir = target.position - transform.position; //Get the angle between the drone and the target. float angle = Vector3.Angle(targetDir, transform.forward); //Turn the drone to face the target if the angle between them if greater than... if (angle > angleBeforeRotate && canrotate == false) { canrotate = true; } if (canrotate == true) { var newRot = Quaternion.LookRotation (target.transform.position - transform.position); transform.rotation = Quaternion.Lerp (transform.rotation, newRot, Time.deltaTime * 2f); if (angle < 5 && canrotate == true) { canrotate = false; } } //Rotate the drone's gun to always face the target. dronearm.rotation = Quaternion.LookRotation (guntarget - dronearm.position); } //Simply moving nextposition to something other than transform.position will cause it to move. if (transform.position != nextposition) { transform.position = Vector3.SmoothDamp(transform.position, nextposition, ref velocity, smoothTime); } } /// /// Fires the laser. /// private void FireLaser(Vector3 randompoint) { if (audiosource.clip != clips[0]) { audiosource.clip = clips[0]; audiosource.volume = 0.2f; } //Creat a laser object GameObject laser = Instantiate(LaserPrefab); laser.transform.position = laseranchor.transform.position; laser.transform.rotation = Quaternion.LookRotation(randompoint - laseranchor.transform.position); //Play the Particle effect. muzzleflashfx.Play(); //Play a sound if (audiosource) { audiosource.Play(); } //MuzzleFlashLight StartCoroutine(FireLight()); //StartRelocatingDrone StartCoroutine(RelocationDelay()); } /// /// Forces a delay before moving, and then only allows rotation if the drone has reached its destination. /// /// IEnumerator RelocationDelay() { yield return new WaitForSeconds(1f); //Allow another relocation if we have already reached the current nextposition. if (Vector3.Distance(transform.position, nextposition) <= 0.1f) { canchangerotation = true; } } /// /// What happens when the drone gets damaged. In the ZED drone demo, Lasershot_Player calls this. /// /// Damage. void ILaserable.TakeDamage(int damage) { //Remove hitpoints as needed Hitpoints -= damage; //Blend the materials to make it take damage //meshrenderer.material.SetFloat("_Blend", 1); damageFlashAmount = 1f; //Destroy if it's health is below zero if (Hitpoints <= 0) { //Add time to prevent laser firing while we die. lasershottimer = 99f; if(spawner) spawner.ClearDrone(); if (ExplosionPrefab) { Instantiate(ExplosionPrefab, transform.position, Quaternion.identity); } if (audiosource != null && clips.Length > 1) { audiosource.Stop(); audiosource.clip = clips[1]; audiosource.volume = 1f; audiosource.Play(); } StartCoroutine(DestroyDrone()); return; } } /// /// Plays explosion FX, then destroys the drone. /// /// IEnumerator DestroyDrone() { transform.GetChild(0).gameObject.SetActive(false); transform.GetChild(1).gameObject.SetActive(false); yield return new WaitForSeconds(3f); Destroy(gameObject); } /// /// Checks nearby for valid places for the drone to move. /// Valid places must be in front of the player, and not intersect any objects within a reasonable tolerance. /// Use radiusCheckRate and percentageThreshold to tweak what counts as a valid location. /// /// /// private bool FindNewMovePosition(out Vector3 newpos) { //We can't move if the ZED isn't initialized. if (!zedManager.IsZEDReady) { newpos = transform.position; return false; } Vector3 randomPosition; // Look Around For a New Position //If the Drone is on the screen, search around a smaller radius. //Note that we only check the primary ZEDManager because we only want the drone to spawn in front of the player anyway. if (ZEDSupportFunctions.CheckScreenView(transform.position, zedManager.GetMainCamera())) randomPosition = UnityEngine.Random.onUnitSphere * UnityEngine.Random.Range(2f, 3f) + transform.position; else //if the drone is outside, look around a bigger radius to find a position which is inside the screen. randomPosition = UnityEngine.Random.onUnitSphere * UnityEngine.Random.Range(4f, 5f) + transform.position; // Look For Any Collisions Through The ZED bool hit = ZEDSupportFunctions.HitTestAtPoint(zedManager.zedCamera, zedManager.GetMainCamera(), randomPosition); if (!hit) { newpos = transform.position; return false; } //If we spawn the drone at that world point, it'll spawn inside a wall. Bring it closer by a distance of ClearRadius. Quaternion directiontoDrone = Quaternion.LookRotation(zedManager.GetMainCameraTransform().position - randomPosition, Vector3.up); Vector3 newPosition = randomPosition + directiontoDrone * Vector3.forward * ClearRadius; //Check the new position isn't too close from the camera. float dist = Vector3.Distance(zedManager.GetMainCamera().transform.position, randomPosition); if (dist < 1f) { newpos = transform.position; return false; } //Also check nearby points in a sphere of radius to make sure the whole drone has a clear space. if (ZEDSupportFunctions.HitTestOnSphere(zedManager.zedCamera, zedManager.GetMainCamera(), newPosition, 1f, radiusCheckRate, percentageThreshold)) { newpos = transform.position; return false; } //Return true if it's made it this far and out the location we chose. newpos = newPosition; return true; } /// /// Sets a reference to the Drone spawner governing its spawning. /// Used to notify the spawner when it's destroyed. /// /// Reference to the scene's DroneSpawner component. public void SetMySpawner(DroneSpawner spawner) { this.spawner = spawner; } /// /// Turns on the drone gun's light briefly to simulate a muzzle flash. Because lasers totally have muzzle flashes. /// /// IEnumerator FireLight() { gunlight.enabled = true; yield return new WaitForSeconds(0.15f); gunlight.enabled = false; } }