using System.Collections.Generic; using UnityEngine; // Sci-Fi Ship Controller. Copyright (c) 2018-2023 SCSM Pty Ltd. All rights reserved. namespace SciFiShipController { /// /// Demo to spawn an AI Ship and head towards the first target location /// using a custom AIState and custom behaviour. /// Ship prefabs should NOT contain a PlayerInputModule. /// Optionally use a Ship that is already in the scene rather than spawning /// a new ship from a prefab. /// This is only sample to demonstrate how API calls could be used in /// your own code. /// public class DemoFlyToLocation : MonoBehaviour { #region Public variables /// /// The prefab from the project of the ship that will be instantiated /// for squadron members. See also ShipSpawner.cs /// public GameObject shipPrefab; // This enables an existing Ship GameObject in the scene // to be used instead of the prefab. public bool isShipAlreadyInScene = false; public DemoLocation flyToLocation; public AIBehaviourInput.AIBehaviourType primaryBehaviourType = AIBehaviourInput.AIBehaviourType.CustomSeekArrival; // This may be set for each location or globally like in this demo script. It could also be hard coded // into the CustomArrival method. We are added it here for testing. public float minArrivalSpeed = 30f; public int squadronId = -1; public int factionId = -1; public bool isHealthDisplayed = false; #endregion #region Private variables private Canvas canvas; private UnityEngine.UI.Text uiTextHealthLabel; private UnityEngine.UI.Text uiTextHealthValue; private ShipControlModule shipControlModule; private ShipAIInputModule shipAIInputModule; private Vector3 firstLocationPosition; //private AIBehaviourInput.AIBehaviourType currentBehaviourType = AIBehaviourInput.AIBehaviourType.CustomSeekArrival; #endregion #region Initialisation Methods void Awake() { // We could just instantiate the prefab but ShipSpawner can // help out with a few other things too. ShipSpawner shipSpawner = new ShipSpawner(); if (shipSpawner != null) { if (isShipAlreadyInScene) { if (shipPrefab == null) { Debug.LogWarning("DemoFlyToLocation - no gameobject was supplied"); } else { shipControlModule = shipPrefab.GetComponent(); } } else { shipControlModule = shipSpawner.CreateShip(shipPrefab, transform.position, transform.rotation); } if (shipControlModule != null) { // Update squadron-related fields in the ship if (shipControlModule.shipInstance != null) { shipControlModule.shipInstance.factionId = factionId; shipControlModule.shipInstance.squadronId = squadronId; // Tell the ship to call our custom method immediately before the ship is destroyed shipControlModule.callbackOnDestroy = ShipDestroyed; // We assume the ShipAIInputModule was attached to the prefab and AwakeOnInitialise was disabled. shipAIInputModule = shipControlModule.GetComponent(); if (shipAIInputModule != null) { if (!shipAIInputModule.IsInitialised) { shipAIInputModule.Initialise(); } if (shipAIInputModule.IsInitialised) { DemoFlyToLocationShipData shipData = shipAIInputModule.GetComponent(); if (shipData == null) { shipData = shipAIInputModule.gameObject.AddComponent(); } // Add a custom state and set it as the current state shipAIInputModule.SetState(AIState.AddState("Demo Custom State", DemoFlyToLocationState, AIState.BehaviourCombiner.PriorityOnly)); firstLocationPosition = flyToLocation.transform.position; shipAIInputModule.AssignTargetPosition(firstLocationPosition); if (primaryBehaviourType == AIBehaviourInput.AIBehaviourType.CustomSeekArrival) { // Whenever the CustomArrival behaviour is used call our DemoArrivalBehaviour(...) method // rather than the standard ShipAIInputModule Arrival behaviour. shipAIInputModule.callbackCustomSeekArrivalBehaviour = DemoArrivalBehaviour; } else { shipAIInputModule.callbackCustomSeekArrivalBehaviour = null; } // Set the current AI behaviour type shipData.currentBehaviourType = primaryBehaviourType; } } #if UNITY_EDITOR else { throw new MissingComponentException(shipPrefab.name + " prefab is missing the ShipAIInputModule"); } #endif if (isHealthDisplayed) { SampleShowShipHealth shipHealth = GetComponent(); if (shipHealth == null) { shipHealth = gameObject.AddComponent(); } if (shipHealth != null) { shipHealth.shipControlModule = shipControlModule; shipHealth.isHealthDisplayed = true; shipHealth.Initialise(); } } } } } } #endregion #region Public Member Methods /// /// Method that gets called from ShipControlModule immediately before the AI Ship is destroyed. /// This is configured in the Awake method above /// /// public void ShipDestroyed(Ship ship) { // Go back to the start and fly to the first location if (ship.respawningMode == Ship.RespawningMode.RespawnAtOriginalPosition) { ship.ResetHealth(); if (shipAIInputModule != null && shipAIInputModule.IsInitialised) { shipAIInputModule.AssignTargetPosition(firstLocationPosition); } } } /// /// Demo state method. /// /// public void DemoFlyToLocationState (AIStateMethodParameters stateMethodParameters) { // Check AI Ship Data to find what behaviour type should be used DemoFlyToLocationShipData shipData = shipAIInputModule.GetComponent(); if (shipData != null) { // Configure a single behaviour with that behaviour type stateMethodParameters.aiBehaviourInputsList[0].behaviourType = shipData.currentBehaviourType; stateMethodParameters.aiBehaviourInputsList[0].targetPosition = stateMethodParameters.targetPosition; stateMethodParameters.aiBehaviourInputsList[0].weighting = 1f; } #if UNITY_EDITOR else { throw new MissingComponentException(shipAIInputModule.name + " prefab is missing the DemoFlyToLocationShipData component"); } #endif } /// /// Method that gets called from ShipAIModule when one of the AIBehaviours is CustomArrival. /// This is configured in the Awake method above /// /// /// public void DemoArrivalBehaviour(AIBehaviourInput behaviourInput, AIBehaviourOutput behaviourOutput) { // We should already have a reference to the NPC ship that was spawned in Awake(). if (shipControlModule != null && shipAIInputModule != null) { // Desired heading is towards the target position Vector3 shipPosition = shipControlModule.shipInstance.TransformPosition; Vector3 headingVector = behaviourInput.targetPosition - shipPosition; // Distance between 2 positions is SQRT( (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y) + (a.z - b.z) * (a.z - b.z) ) float headingVectorMagnitude = (float)System.Math.Sqrt(headingVector.x * headingVector.x + headingVector.y * headingVector.y + headingVector.z * headingVector.z); Vector3 headingVectorNormalised = headingVector / headingVectorMagnitude; behaviourOutput.heading = headingVectorNormalised; // If deceleration rate is not greater than zero, set to some arbitary rate //float decelerationRate = shipAIInputModule.decelerationRate > 0f ? shipAIInputModule.decelerationRate : 10f; //behaviourOutput.velocityOutput = headingVectorNormalised * (float)System.Math.Sqrt((minArrivalSpeed * minArrivalSpeed) + (2f * decelerationRate * headingVectorMagnitude)); // Calculate max speed we can do now while still being able to slow down to the correct speed upon arrival behaviourOutput.velocity = headingVectorNormalised * shipAIInputModule.MaxSpeedFromBrakingDistance(minArrivalSpeed, headingVectorMagnitude, behaviourInput.shipInstance.LocalVelocity.normalized); // Target output is the target ship's position behaviourOutput.target = shipPosition; behaviourOutput.setTarget = true; // No desired up direction behaviourOutput.up = Vector3.zero; } } #endregion } }