using System.Collections; using System.Collections.Generic; using UnityEngine; // Sci-Fi Ship Controller. Copyright (c) 2018-2023 SCSM Pty Ltd. All rights reserved. namespace SciFiShipController { /// /// Executes a simple state machine for determining state information for a "race" AI. /// This is a SAMPLE ONLY and may get modified in future releases. If you wish to use something /// similar in your own game create a new script in your own namespace to avoid it getting /// overwritten by Sci-Fi Ship Controller updates. /// If instantiated / added at runtime, also call Initialise(). /// Paths are assigned per ship (as this script is attached to each AI Ship). However, /// they could be managed centrally and set via code. /// [AddComponentMenu("Sci-Fi Ship Controller/Samples/Race AI")] [HelpURL("http://scsmmedia.com/ssc-documentation")] [RequireComponent(typeof(ShipAIInputModule))] public class SampleRace : MonoBehaviour { #region Public Variables /// /// If enabled, the Initialise() will be called as soon as Awake() runs. This should be disabled if you are /// instantiating the SampleRace through code. /// public bool initialiseOnAwake = false; /// /// Array of track path names. /// public string[] trackPathNames = { "Path 1 Name Here", "Path 2 Name Here" }; /// /// Has the SampleRace been initialised? /// public bool IsInitialised { get { return isInitialised; } } /// /// Get the index of the Path in the /// //public int CurrentRacePathIndex { get { return currentRacePathIndex; } } #endregion #region Private Variables private ShipAIInputModule shipAIInputModule; private SSCManager sscManager; private List racePathsList; private int numPaths = 0; private int currentRacePathIndex = 0; private bool isInitialised = false; #endregion #region Initialise Methods private void Awake() { if (initialiseOnAwake) { Initialise(); } } /// /// If shipAIInputModule is not initialised, SampleRace will initialise it. /// public void Initialise() { // Don't attempt to re-initialise multiple times. if (isInitialised) { return; } // Get a reference to the ship AI input module attached to this gameobject shipAIInputModule = GetComponent(); // Initialise the ship AI (if it hasn't been initialised already) shipAIInputModule.Initialise(); // Get a reference to the Ship Controller Manager instance sscManager = SSCManager.GetOrCreateManager(); if (sscManager != null) { // Initialise the list of race paths racePathsList = new List(); // Find the paths and add them to the list PathData racePath; for (int i = 0; i < trackPathNames.Length; i++) { racePath = sscManager.GetPath(trackPathNames[i]); if (racePath != null) { racePathsList.Add(racePath); } #if UNITY_EDITOR else { Debug.Log("Path not found: " + trackPathNames[i]); } #endif } // cache the number of paths so we don't need to keep counting the list numPaths = racePathsList == null ? 0 : racePathsList.Count; // If it exists, assign the ship to follow the first Path AssignPath(0); isInitialised = true; } } #endregion #region Public Member Methods /// /// Set the ship to follow the 0-based Path included in trackPathNames. /// If no Path are found or the index is out of range, the Ship will be /// placed in the idle state. /// /// public void AssignPath(int pathIndex) { // Assign the first race path if (pathIndex >= 0 && pathIndex < racePathsList.Count) { // Initialise the AI in the "move to" state shipAIInputModule.SetState(AIState.moveToStateID); currentRacePathIndex = pathIndex; shipAIInputModule.AssignTargetPath(racePathsList[currentRacePathIndex]); // Assign the has completed state action callback (this will then be called every time the // ship gets to the end of a path) shipAIInputModule.callbackCompletedStateAction = FinishedPathCallback; } else { shipAIInputModule.SetState(AIState.idleStateID); shipAIInputModule.callbackCompletedStateAction = null; } } /// /// Gets the distance from the start of the list of Paths. /// /// public float GetDistanceFromStart() { float distanceFromStart = 0f; if (isInitialised && numPaths > 0) { // Add the paths already completed. // start at the second path (if there is one) for (int pIdx = 1; pIdx < numPaths; pIdx++) { // Add the distance for the previous path distanceFromStart += racePathsList[pIdx-1].splineTotalDistance; // Exit the loop once we're up to the current path if (currentRacePathIndex >= pIdx) { break; } } // Get the distance from the start of the current path PathData pathData = racePathsList[currentRacePathIndex]; if (pathData != null) { // Add the distance to the previous location on the path int prevLocationIdx = shipAIInputModule.GetPreviousTargetPathLocationIndex(); if (prevLocationIdx >= 0) { PathLocationData prevPathLocationData = pathData.pathLocationDataList[prevLocationIdx]; if (prevPathLocationData != null) { // In a closed circuit the first Location contains the total distance for the Path. if (prevLocationIdx > 0) { distanceFromStart += prevPathLocationData.distanceCumulative; } // This is the location on the path the ship is heading towards int nextLocationIdx = shipAIInputModule.GetCurrentTargetPathLocationIndex(); // Calc distance from the previous location float tValue = shipAIInputModule.GetCurrentTargetPathTValue(); float deltaDistance = SSCManager.GetPathDistance(pathData, prevLocationIdx, nextLocationIdx) * tValue; distanceFromStart += deltaDistance; } } } } return distanceFromStart; } #endregion #region Callback Methods /// /// Function to be called when the ship has completed the current path. /// public void FinishedPathCallback (ShipAIInputModule shipAIInputModule) { // Increment the race path index currentRacePathIndex++; // If we have finished all of the paths loop back to the first one if (currentRacePathIndex >= racePathsList.Count) { currentRacePathIndex = 0; } // Get the next path and assign it to the ship AI shipAIInputModule.AssignTargetPath(racePathsList[currentRacePathIndex]); } #endregion } }