1617 lines
69 KiB
C#
1617 lines
69 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// A centralised futuristic radar system based on Automatic Dependent Surveillance - Broadcast (ADS-B).
|
|
/// Instead of the radar system doing a sweep of the environment, craft which need situational
|
|
/// awareness broadcast (send) data to the central radar system.
|
|
/// This system will be the Primary Radar System (PRS).
|
|
/// This implementation requires all craft to query the radar system (incoming broadcasts are private
|
|
/// and don't get sent to every craft - the radar system receives private msgs from ships).
|
|
/// </summary>
|
|
// [AddComponentMenu("Sci-Fi Ship Controller/Managers/Radar")]
|
|
[HelpURL("http://scsmmedia.com/ssc-documentation")]
|
|
public class SSCRadar : MonoBehaviour
|
|
{
|
|
#region Static Read-only valiables
|
|
public static readonly int NEUTRAL_FACTION = 0;
|
|
#endregion
|
|
|
|
#region Enumerations
|
|
|
|
public enum RadarScreenLocale : int
|
|
{
|
|
TopLeft = 0,
|
|
TopCenter = 1,
|
|
TopRight = 2,
|
|
MiddleLeft = 3,
|
|
MiddleCenter = 4,
|
|
MiddleRight = 5,
|
|
BottomLeft = 6,
|
|
BottomCenter = 7,
|
|
BottomRight = 8,
|
|
Custom = 99
|
|
};
|
|
|
|
#endregion
|
|
|
|
#region Public variables and properties
|
|
|
|
/// <summary>
|
|
/// If enabled, the GetOrCreateRadar() will be called as soon as Start() runs. If there is a UI (mini-map) configured,
|
|
/// it will automatically be made visible. This should be disabled if you are instantiating the SSCRadar through code
|
|
/// and using the SSCRadar API methods.
|
|
/// </summary>
|
|
public bool initialiseOnStart = false;
|
|
|
|
/// <summary>
|
|
/// [READONLY] Has the radar been initialised?
|
|
/// </summary>
|
|
public bool IsInitialised { get { return isInitialised; } }
|
|
|
|
public int poolInitialSize = 100;
|
|
|
|
/// <summary>
|
|
/// [INTERNAL USE ONLY]
|
|
/// </summary>
|
|
public int poolIncrementSize = 10;
|
|
|
|
/// <summary>
|
|
/// [INTERNAL USE ONLY]
|
|
/// </summary>
|
|
public bool allowRepaint = false;
|
|
public bool generalShowInEditor = false;
|
|
public bool visualsShowInEditor = false;
|
|
public bool movementShowInEditor = false;
|
|
public RadarScreenLocale screenLocale = RadarScreenLocale.TopLeft;
|
|
public Vector2 screenLocaleCustomXY = Vector2.zero;
|
|
// Normalised width as 0.0-1.0 as a proportion of the screen space.
|
|
// e.g. 0.2 would be 20% of the screen width.
|
|
[Range(0.1f, 1f)] public float radarDisplayWidthN = 0.2f;
|
|
|
|
/// <summary>
|
|
/// The sort order of the canvas in the scene. Higher numbers are on top.
|
|
/// At runtime call SetCanvasSortOrder(..)
|
|
/// </summary>
|
|
public int canvasSortOrder = 1;
|
|
|
|
/// <summary>
|
|
/// When the built-in UI is used, this is the colour of the outer rim
|
|
/// of the mini-map display along with any decals.
|
|
/// </summary>
|
|
public Color32 overlayColour = Color.white;
|
|
|
|
/// <summary>
|
|
/// When the built-in UI is used, this is the background colour of the
|
|
/// mini-map.
|
|
/// </summary>
|
|
public Color32 backgroundColour = Color.clear;
|
|
|
|
/// <summary>
|
|
/// When the built-in UI is used, this is the colour of any blip that are considered
|
|
/// as friendly. Determined by the factionId when available
|
|
/// </summary>
|
|
public Color32 blipFriendColour = Color.green;
|
|
|
|
/// <summary>
|
|
/// When the built-in UI is used, this is the colour of any blip that are considered
|
|
/// as hostile. Determined by the factionId when available
|
|
/// </summary>
|
|
public Color32 blipFoeColour = Color.red;
|
|
|
|
/// <summary>
|
|
/// When the built-in UI is used, this is the colour of any blip that are considered
|
|
/// as neutral. Determined by the factionId when available.
|
|
/// </summary>
|
|
public Color32 blipNeutralColour = Color.white;
|
|
|
|
/// <summary>
|
|
/// If changing this at runtime, call RefreshRadarImageStatus().
|
|
/// </summary>
|
|
public UnityEngine.UI.RawImage radarImage = null;
|
|
|
|
/// <summary>
|
|
/// The number of results returned in the last query.
|
|
/// </summary>
|
|
public int ResultCount { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Uses 3D distances to determine range when querying the radar data.
|
|
/// </summary>
|
|
public bool is3DQueryEnabled = true;
|
|
|
|
/// <summary>
|
|
/// The sort order of the results. None is the fastest option and has
|
|
/// the lowest performance impact.
|
|
/// </summary>
|
|
public SSCRadarQuery.QuerySortOrder querySortOrder;
|
|
|
|
/// <summary>
|
|
/// [READONLY] The direction the on-screen UI display is facing
|
|
/// </summary>
|
|
public Quaternion DisplayRotation { get { return displayUIRotation; } }
|
|
|
|
/// <summary>
|
|
/// [INTERNAL ONLY] Instead call SetDisplay(..) or GetRadarResults(..)
|
|
/// Minimum range is 10 metres
|
|
/// </summary>
|
|
public float displayRange = 100f;
|
|
|
|
/// <summary>
|
|
/// [INTERNAL ONLY] Use FollowShip(..) instead.
|
|
/// The centre of the radar will move around with this ship.
|
|
/// </summary>
|
|
public ShipControlModule shipToFollow = null;
|
|
|
|
/// <summary>
|
|
/// [INTERNAL ONLY] Use FollowGameObject(..) instead.
|
|
/// The centre of the radar will move around with this gameobject
|
|
/// </summary>
|
|
public GameObject gameobjectToFollow = null;
|
|
|
|
/// <summary>
|
|
/// [INTERNAL ONLY]
|
|
/// The centre of the radar
|
|
/// </summary>
|
|
public Vector3 centrePosition = Vector3.zero;
|
|
|
|
#endregion
|
|
|
|
#region Public Delegates
|
|
|
|
public delegate void CallbackOnDrawBlip(Texture2D tex, Quaternion displayRotation, int factionId, SSCRadarBlip sscRadarBlip);
|
|
|
|
/// <summary>
|
|
/// The name of the custom method that is called when a blip is to be
|
|
/// draw on the radar display. Your method must take 4 parameters -
|
|
/// Texture2D, Quaternion, Int, and SSCRadarBlip. Your custom method should
|
|
/// "paint" the blip onto the texture by modifying the pixels.
|
|
/// </summary>
|
|
public CallbackOnDrawBlip callbackOnDrawBlip = null;
|
|
|
|
#endregion
|
|
|
|
#region Private variables
|
|
private static SSCRadar currentRadar = null;
|
|
private bool isInitialised = false;
|
|
private List<SSCRadarItem> sscRadarItemList = null;
|
|
private int numRadarItems = 0;
|
|
private int currentPoolSize = 0;
|
|
private BitArray radarItemBitArray = null;
|
|
private bool isRadarImageAvailable = false;
|
|
private bool isShowRadarImage = false;
|
|
// The width of the RawImage texture if in use
|
|
private int displayUITexWidth = 10;
|
|
private int displayUITexHeight = 10;
|
|
private int displayUITexCentreX = 5;
|
|
private int displayUITexCentreY = 5;
|
|
private Quaternion displayUIRotation = Quaternion.identity;
|
|
private Vector3 defaultDisplayFwdDirection = Vector3.forward;
|
|
|
|
// Used when our UI is enabled
|
|
private bool isFollowShip = false;
|
|
private bool isFollowGameObject = false;
|
|
private const int displayRimWidth = 4;
|
|
private Color32[] uiRimPixels;
|
|
private Color32[] uiInnerPixels;
|
|
|
|
private SSCRadarQuery sscRadarQuery;
|
|
private List<SSCRadarBlip> sscRadarResultsList;
|
|
|
|
#endregion
|
|
|
|
#region Initialisation Methods
|
|
|
|
/// <summary>
|
|
/// Called after Awake() just before the scene is rendered
|
|
/// </summary>
|
|
private void Start()
|
|
{
|
|
if (initialiseOnStart)
|
|
{
|
|
GetOrCreateRadar();
|
|
|
|
// If the UI is available, display it
|
|
if (isInitialised && isRadarImageAvailable)
|
|
{
|
|
ShowUI();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// [INTERNAL ONLY] Instead use SSCRadar.GetOrCreateRadar()
|
|
/// </summary>
|
|
private void Initialise()
|
|
{
|
|
if (sscRadarItemList == null)
|
|
{
|
|
if (poolInitialSize < 1) { sscRadarItemList = new List<SSCRadarItem>(10); }
|
|
else { sscRadarItemList = new List<SSCRadarItem>(poolInitialSize); }
|
|
}
|
|
else { sscRadarItemList.Clear(); }
|
|
|
|
// Reset the number items in the last query
|
|
ResultCount = 0;
|
|
|
|
FillPool();
|
|
|
|
RefreshRadarImageStatus();
|
|
|
|
isFollowShip = shipToFollow != null;
|
|
if (isFollowShip)
|
|
{
|
|
isFollowGameObject = false;
|
|
}
|
|
else
|
|
{
|
|
isFollowGameObject = gameobjectToFollow != null;
|
|
}
|
|
|
|
// By default Radar UI is not visible in the scene
|
|
if (isRadarImageAvailable)
|
|
{
|
|
ScreenResized();
|
|
|
|
HideUI();
|
|
}
|
|
|
|
isInitialised = true;
|
|
|
|
SetCanvasSortOrder(canvasSortOrder);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Update Methods
|
|
|
|
// Update is called once per frame
|
|
void Update()
|
|
{
|
|
if (isShowRadarImage && isInitialised)
|
|
{
|
|
if (isFollowShip)
|
|
{
|
|
if (shipToFollow != null && shipToFollow.IsInitialised)
|
|
{
|
|
centrePosition = shipToFollow.shipInstance.TransformPosition;
|
|
}
|
|
}
|
|
else if (isFollowGameObject)
|
|
{
|
|
if (gameobjectToFollow != null) { centrePosition = gameobjectToFollow.transform.position; }
|
|
}
|
|
|
|
// Run the query
|
|
sscRadarQuery.centrePosition = centrePosition;
|
|
sscRadarQuery.range = displayRange;
|
|
sscRadarQuery.is3DQueryEnabled = is3DQueryEnabled;
|
|
sscRadarQuery.querySortOrder = querySortOrder;
|
|
sscRadarQuery.factionId = SSCRadarQuery.IGNOREFACTION;
|
|
GetRadarResults(sscRadarQuery, sscRadarResultsList);
|
|
|
|
DisplayResults(false);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Member Methods
|
|
|
|
/// <summary>
|
|
/// Fill the pool with empty radar items. These are added to the end
|
|
/// of the existing pool up to the current capacity.
|
|
/// </summary>
|
|
private void FillPool()
|
|
{
|
|
int capacity = sscRadarItemList == null ? 0 : sscRadarItemList.Capacity;
|
|
numRadarItems = sscRadarItemList == null ? 0 : sscRadarItemList.Count;
|
|
|
|
int numNewItems = capacity - numRadarItems;
|
|
if (radarItemBitArray == null) { radarItemBitArray = new BitArray(capacity, true); }
|
|
|
|
// Make sure the bit array can hold enough data
|
|
if (radarItemBitArray != null && radarItemBitArray.Length < capacity) { radarItemBitArray.Length = capacity; }
|
|
|
|
for (int itemIdx = capacity - numNewItems; itemIdx < capacity; itemIdx++)
|
|
{
|
|
// Create a new (empty) slot
|
|
sscRadarItemList.Add(new SSCRadarItem());
|
|
// Mark this slot as empty
|
|
radarItemBitArray[itemIdx] = true;
|
|
}
|
|
|
|
// Cache number of items
|
|
numRadarItems = sscRadarItemList == null ? 0 : sscRadarItemList.Count;
|
|
|
|
currentPoolSize = numRadarItems;
|
|
}
|
|
|
|
/// <summary>
|
|
/// If required, increase the size of the pool
|
|
/// </summary>
|
|
private void ExpandPool()
|
|
{
|
|
if (isInitialised)
|
|
{
|
|
currentPoolSize = sscRadarItemList == null ? 0 : sscRadarItemList.Count;
|
|
|
|
int capacity = sscRadarItemList == null ? 0 : sscRadarItemList.Capacity;
|
|
|
|
// If there is less than poolIncrementSize left at the end of the pool for expansion,
|
|
// add some more capacity
|
|
if (capacity - currentPoolSize < poolIncrementSize)
|
|
{
|
|
sscRadarItemList.Capacity += poolIncrementSize;
|
|
}
|
|
|
|
FillPool();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set a slot in the pool of radarItems as being empty or not.
|
|
/// </summary>
|
|
/// <param name="itemIndex"></param>
|
|
/// <param name="isEmpty"></param>
|
|
private void SetBitmap(int itemIndex, bool isEmpty)
|
|
{
|
|
if (isInitialised && itemIndex >= 0 && itemIndex < currentPoolSize)
|
|
{
|
|
radarItemBitArray[itemIndex] = isEmpty;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draw the background Radar UI, then add the resultant blips
|
|
/// </summary>
|
|
/// <param name="redraw">Force overlay inner and outer redraw</param>
|
|
private void DisplayResults(bool redraw = false)
|
|
{
|
|
int numResults = ResultCount;
|
|
int outerRadius = (int)(displayUITexWidth / 2f);
|
|
|
|
Texture2D radarTex = radarImage.texture as Texture2D;
|
|
|
|
// If the canvas image has been resized but doesn't match
|
|
// the width of the texture, refresh it.
|
|
if (displayUITexWidth > radarTex.width)
|
|
{
|
|
RefreshRadarImageStatus();
|
|
return;
|
|
}
|
|
|
|
// Cache the texture pixels to improve performance and prevent GC in each frame
|
|
DrawCircle(radarTex, ref uiRimPixels, displayUITexCentreX, displayUITexCentreY, outerRadius, overlayColour, true, redraw);
|
|
DrawCircle(radarTex, ref uiInnerPixels, displayUITexCentreX, displayUITexCentreY, outerRadius - displayRimWidth, backgroundColour, true, redraw);
|
|
|
|
bool isCallbackEnabled = callbackOnDrawBlip != null;
|
|
|
|
int factionId = 0; // neutral
|
|
|
|
// Get the direction the central ship or gameobject is facing
|
|
if (isFollowShip && shipToFollow != null)
|
|
{
|
|
if (shipToFollow.shipInstance != null)
|
|
{
|
|
displayUIRotation = Quaternion.Euler(0f, shipToFollow.shipInstance.TransformInverseRotation.eulerAngles.y, 0f);
|
|
factionId = shipToFollow.shipInstance.factionId;
|
|
}
|
|
else
|
|
{
|
|
// Ship may be in the process of being destroyed
|
|
FollowShip(null);
|
|
displayUIRotation = Quaternion.LookRotation(defaultDisplayFwdDirection);
|
|
}
|
|
}
|
|
else if (isFollowGameObject && gameobjectToFollow != null)
|
|
{
|
|
displayUIRotation = Quaternion.Euler(0f, Quaternion.Inverse(gameobjectToFollow.transform.rotation).eulerAngles.y, 0f);
|
|
}
|
|
else { displayUIRotation = Quaternion.LookRotation(defaultDisplayFwdDirection); }
|
|
|
|
// Populate the UI display with blips
|
|
for (int blipIdx = 0; blipIdx < numResults; blipIdx++)
|
|
{
|
|
if (isCallbackEnabled) { callbackOnDrawBlip(radarTex, displayUIRotation, factionId, sscRadarResultsList[blipIdx]); }
|
|
else { DrawBlip(radarTex, displayUIRotation, factionId, sscRadarResultsList[blipIdx]); }
|
|
}
|
|
|
|
// Only apply once all operations have finished
|
|
radarTex.Apply();
|
|
radarImage.texture = radarTex;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draw blips onto the radar UI display texture.
|
|
/// The factionId is the factionId of the item or place running the query. Items with the same factionId
|
|
/// will be displayed in the friend colour, items with factionId = 0 will be neutral, while all other items
|
|
/// will be considered hostile (foe blip colour).
|
|
/// </summary>
|
|
/// <param name="tex"></param>
|
|
/// <param name="displayRotation"></param>
|
|
/// <param name="factionId"></param>
|
|
/// <param name="sscRadarBlip"></param>
|
|
private void DrawBlip(Texture2D tex, Quaternion displayRotation, int factionId, SSCRadarBlip sscRadarBlip)
|
|
{
|
|
Vector3 rotatedPosition = displayRotation * (sscRadarBlip.wsPosition - sscRadarQuery.centrePosition);
|
|
|
|
int blipOffsetX = (int)((rotatedPosition.x / sscRadarQuery.range) * displayUITexWidth / 2f);
|
|
int blipOffsetZ = (int)((rotatedPosition.z / sscRadarQuery.range) * displayUITexHeight / 2f);
|
|
|
|
Color blipColour = blipNeutralColour;
|
|
|
|
//int itemTypeInt = (int)sscRadarBlip.radarItemType;
|
|
|
|
// If the item running the query is neutral, all others are assumed friendly...
|
|
// If the blip is not neutral, must be friend or foe
|
|
if (factionId != 0 && sscRadarBlip.factionId != 0)
|
|
{
|
|
// Is this radar item in the same faction or alliance as the radar system
|
|
if (sscRadarBlip.factionId == factionId) { blipColour = blipFriendColour; }
|
|
else { blipColour = blipFoeColour; }
|
|
}
|
|
|
|
int blipSize = (int)sscRadarBlip.blipSize;
|
|
|
|
// Draw a 3x3, 4x4, 5x5 blip etc
|
|
for (int x = blipOffsetX + displayUITexCentreX - blipSize; x < blipOffsetX + displayUITexCentreX + blipSize; x++)
|
|
{
|
|
for (int y = blipOffsetZ + displayUITexCentreY - blipSize; y < blipOffsetZ + displayUITexCentreY + blipSize; y++)
|
|
{
|
|
// Clip with display rim width indent
|
|
if (x > displayRimWidth + 1 && x < displayUITexWidth - displayRimWidth - 2 && y > displayRimWidth + 1 && y < displayUITexHeight - displayRimWidth - 2)
|
|
{
|
|
tex.SetPixel(x, y, blipColour);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Internal Methods
|
|
|
|
/// <summary>
|
|
/// This method uses SetPixels which may be slower on some devices. It has very minimal GC.
|
|
/// If you pass in a Color32 as a parameter, it will be converted to a Color struct.
|
|
/// </summary>
|
|
/// <param name="tex"></param>
|
|
/// <param name="centreX"></param>
|
|
/// <param name="centreY"></param>
|
|
/// <param name="circleRadius"></param>
|
|
/// <param name="pixelColour"></param>
|
|
/// <param name="isFilled"></param>
|
|
/// <param name="apply"></param>
|
|
public void DrawCircle(Texture2D tex, int centreX, int centreY, int circleRadius, Color pixelColour, bool isFilled, bool apply)
|
|
{
|
|
for (int x = -circleRadius; x < circleRadius; x++)
|
|
{
|
|
int dist = (int)Mathf.Ceil(Mathf.Sqrt(circleRadius * circleRadius - x * x));
|
|
|
|
if (isFilled)
|
|
{
|
|
for (int y = -dist; y < dist; y++)
|
|
{
|
|
tex.SetPixel(x + centreX, y + centreY, pixelColour);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tex.SetPixel(x + centreX, -dist + centreY, pixelColour);
|
|
tex.SetPixel(x + centreX, dist + centreY, pixelColour);
|
|
|
|
if (x < -circleRadius + 4 || x > circleRadius - 6)
|
|
{
|
|
for (int i = 0; i < 16 && i < circleRadius / 6f; i++)
|
|
{
|
|
tex.SetPixel(x + centreX, -dist + i + centreY, pixelColour);
|
|
tex.SetPixel(x + centreX, dist - i + centreY, pixelColour);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (apply) { tex.Apply(); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Uses a cached copy of the pixels for each circle that needs to be regularly drawn. This is useful when wanting
|
|
/// to update a Texture2D in the UI very regularly (like each frame). The first time it runs for a particular circle
|
|
/// it will create GC but after that there is no GC unless redraw = true.
|
|
/// IMPORTANT: After calling DrawCircle(), call tex.Apply() after you have finished drawing to the texture.
|
|
/// </summary>
|
|
/// <param name="tex"></param>
|
|
/// <param name="pixels"></param>
|
|
/// <param name="centreX"></param>
|
|
/// <param name="centreY"></param>
|
|
/// <param name="circleRadius"></param>
|
|
/// <param name="pixelColour"></param>
|
|
/// <param name="isFilled"></param>
|
|
/// <param name="redraw"></param>
|
|
public void DrawCircle(Texture2D tex, ref Color32[] pixels, int centreX, int centreY, int circleRadius, Color32 pixelColour, bool isFilled, bool redraw)
|
|
{
|
|
if (redraw || pixels == null)
|
|
{
|
|
pixels = tex.GetPixels32();
|
|
|
|
// Draw the circle
|
|
for (int x = -circleRadius; x < circleRadius; x++)
|
|
{
|
|
// NOTE: Mathf.Ceil is almost twice as slow as Mathf.Sqrt
|
|
int dist = (int)Mathf.Ceil(Mathf.Sqrt(circleRadius * circleRadius - x * x));
|
|
|
|
if (isFilled)
|
|
{
|
|
for (int y = -dist; y < dist; y++)
|
|
{
|
|
pixels[(y + centreY) * tex.width + x + centreX] = pixelColour;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pixels[(-dist + centreY) * tex.width + x + centreX] = pixelColour;
|
|
pixels[(dist + centreY) * tex.width + x + centreX] = pixelColour;
|
|
|
|
if (x < -circleRadius + 4 || x > circleRadius - 6)
|
|
{
|
|
for (int i = 0; i < 16 && i < circleRadius / 6f; i++)
|
|
{
|
|
pixels[(-dist + i + centreY) * tex.width + x + centreX] = pixelColour;
|
|
pixels[(dist - i + centreY) * tex.width + x + centreX] = pixelColour;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
tex.SetPixels32(pixels);
|
|
}
|
|
|
|
/// <summary>
|
|
/// [INTERNAL ONLY]
|
|
/// Get the anchor points and correct rect transform offset for the radar display screen locale. e.g. BottomLeft, TopRight etc.
|
|
/// </summary>
|
|
/// <param name="screenLocaleInt"></param>
|
|
/// <param name="canvasSize"></param>
|
|
/// <param name="panelSize"></param>
|
|
/// <param name="anchorMin"></param>
|
|
/// <param name="anchorMax"></param>
|
|
/// <param name="panelOffset"></param>
|
|
public void GetMinimapScreenLocation(int screenLocaleInt, Vector2 canvasSize, Vector2 panelSize, ref Vector2 anchorMin, ref Vector2 anchorMax, ref Vector2 panelOffset)
|
|
{
|
|
float indentX = canvasSize.x * 0.01f;
|
|
float indentY = canvasSize.y * 0.01f;
|
|
|
|
switch (screenLocaleInt)
|
|
{
|
|
case (int)RadarScreenLocale.BottomLeft:
|
|
anchorMin.x = 0f; anchorMin.y = 0f; anchorMax.x = 0f; anchorMax.y = 0f;
|
|
panelOffset.x += indentX;
|
|
panelOffset.y += indentY;
|
|
break;
|
|
case (int)RadarScreenLocale.BottomCenter:
|
|
anchorMin.x = 0.5f; anchorMin.y = 0f; anchorMax.x = 0.5f; anchorMax.y = 0f;
|
|
panelOffset.x += (canvasSize.x * 0.5f) - (panelSize.x * 0.5f);
|
|
panelOffset.y += indentY;
|
|
break;
|
|
case (int)RadarScreenLocale.BottomRight:
|
|
anchorMin.x = 1f; anchorMin.y = 0f; anchorMax.x = 1f; anchorMax.y = 0f;
|
|
panelOffset.x += canvasSize.x - panelSize.x - indentX;
|
|
panelOffset.y += indentY;
|
|
break;
|
|
case (int)RadarScreenLocale.TopRight:
|
|
anchorMin.x = 0f; anchorMin.y = 0f; anchorMax.x = 1f; anchorMax.y = 1f;
|
|
panelOffset.x += canvasSize.x - panelSize.x - indentX;
|
|
panelOffset.y += canvasSize.y - panelSize.y - indentY;
|
|
break;
|
|
case (int)RadarScreenLocale.TopCenter:
|
|
anchorMin.x = 0.5f; anchorMin.y = 0f; anchorMax.x = 0.5f; anchorMax.y = 0f;
|
|
panelOffset.x += (canvasSize.x * 0.5f) - (panelSize.x * 0.5f);
|
|
panelOffset.y += canvasSize.y - panelSize.y - indentY;
|
|
break;
|
|
case (int)RadarScreenLocale.TopLeft:
|
|
anchorMin.x = 0f; anchorMin.y = 1f; anchorMax.x = 0f; anchorMax.y = 1f;
|
|
panelOffset.x += indentX;
|
|
panelOffset.y += canvasSize.y - panelSize.y - indentY;
|
|
break;
|
|
case (int)RadarScreenLocale.MiddleLeft:
|
|
anchorMin.x = 0f; anchorMin.y = 0.5f; anchorMax.x = 0; anchorMax.y = 0.5f;
|
|
panelOffset.x += indentX;
|
|
panelOffset.y += (canvasSize.y * 0.5f) - (panelSize.y * 0.5f);
|
|
break;
|
|
case (int)RadarScreenLocale.MiddleCenter:
|
|
anchorMin.x = 0f; anchorMin.y = 0.5f; anchorMax.x = 0f; anchorMax.y = 0.5f;
|
|
panelOffset.x += (canvasSize.x * 0.5f) - (panelSize.x * 0.5f);
|
|
panelOffset.y += (canvasSize.y * 0.5f) - (panelSize.y * 0.5f);
|
|
break;
|
|
case (int)RadarScreenLocale.MiddleRight:
|
|
anchorMin.x = 1f; anchorMin.y = 0.5f; anchorMax.x = 1f; anchorMax.y = 0.5f;
|
|
panelOffset.x += canvasSize.x - panelSize.x - indentX;
|
|
panelOffset.y += (canvasSize.y * 0.5f) - (panelSize.y * 0.5f);
|
|
break;
|
|
default:
|
|
// custom
|
|
anchorMin.x = 0f; anchorMin.y = 0f; anchorMax.x = 0f; anchorMax.y = 0f;
|
|
panelOffset.x = screenLocaleCustomXY.x;
|
|
panelOffset.y = screenLocaleCustomXY.y;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// [INTERNAL ONLY]
|
|
/// Get the Radar canvas if it exists, or create a new one.
|
|
/// NOTE: When creating a new canvas before Unity 2019.3, the sizeDelta returns
|
|
/// the incorrect canvas size. The correct value isn't returned until the next frame.
|
|
/// At runtime does NOT create an instance of the EventSystem - you would need to
|
|
/// do this some other way...
|
|
/// </summary>
|
|
/// <param name="radarCanvasGO"></param>
|
|
/// <param name="radarCanvas"></param>
|
|
/// <param name="canvasSize"></param>
|
|
/// <param name="canvasScale"></param>
|
|
public void GetorCreateRadarCanvas(out GameObject radarCanvasGO, out Canvas radarCanvas, out Vector2 canvasSize, out Vector3 canvasScale)
|
|
{
|
|
radarCanvasGO = null;
|
|
canvasSize = Vector2.zero;
|
|
canvasScale = Vector3.one;
|
|
|
|
radarCanvas = SSCUtils.FindCanvas("SSCRadarCanvas");
|
|
|
|
// If SSCRadarCanvas doesn't exist, create it
|
|
if (radarCanvas == null)
|
|
{
|
|
radarCanvasGO = new GameObject("SSCRadarCanvas");
|
|
radarCanvasGO.layer = 5;
|
|
radarCanvasGO.AddComponent<Canvas>();
|
|
|
|
radarCanvas = radarCanvasGO.GetComponent<Canvas>();
|
|
radarCanvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
|
UnityEngine.UI.CanvasScaler canvasScaler = radarCanvasGO.AddComponent<UnityEngine.UI.CanvasScaler>();
|
|
if (canvasScaler != null)
|
|
{
|
|
canvasScaler.uiScaleMode = UnityEngine.UI.CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
|
canvasScaler.screenMatchMode = UnityEngine.UI.CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
|
|
canvasScaler.referenceResolution = new Vector2(1920f, 1080f);
|
|
canvasScaler.matchWidthOrHeight = 0.5f;
|
|
}
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
// Add an Event System if it doesn't already exist.
|
|
// NOTE: If it is disabled in scene, a new one isn't added.
|
|
UnityEditor.EditorApplication.ExecuteMenuItem("GameObject/UI/Event System");
|
|
#endif
|
|
|
|
if (radarCanvas != null)
|
|
{
|
|
radarCanvasGO = radarCanvas.gameObject;
|
|
canvasSize = radarCanvasGO.GetComponent<RectTransform>().sizeDelta;
|
|
canvasScale = radarCanvasGO.GetComponent<RectTransform>().localScale;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public API Static Methods
|
|
|
|
/// <summary>
|
|
/// Returns the current SSCRadar instance for this scene. If one does not already exist, a new one is created.
|
|
/// USAGE: SSCRadar sscRadar = SSCRadar.GetOrCreateRadar();
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public static SSCRadar GetOrCreateRadar()
|
|
{
|
|
// Check whether we have already found a radar instance for this scene
|
|
if (currentRadar == null)
|
|
{
|
|
// Otherwise, check whether this scene already has a radar system
|
|
currentRadar = GameObject.FindObjectOfType<SSCRadar>();
|
|
|
|
if (currentRadar == null)
|
|
{
|
|
// If this scene does not already have a manager, create one
|
|
GameObject newRadarGameObject = new GameObject("SSC Radar");
|
|
newRadarGameObject.transform.position = Vector3.zero;
|
|
newRadarGameObject.transform.parent = null;
|
|
currentRadar = newRadarGameObject.AddComponent<SSCRadar>();
|
|
}
|
|
}
|
|
|
|
if (currentRadar != null)
|
|
{
|
|
// Initialise the radar if it hasn't already been initialised
|
|
if (!currentRadar.isInitialised) { currentRadar.Initialise(); }
|
|
}
|
|
#if UNITY_EDITOR
|
|
// If currentRadar is still null, log a warning to the console
|
|
else
|
|
{
|
|
Debug.LogWarning("SSCRadar GetOrCreateRadar() Warning: Could not find or create radar, so returned null.");
|
|
}
|
|
#endif
|
|
|
|
return currentRadar;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public API Member Methods - CORE
|
|
|
|
/// <summary>
|
|
/// Attempt to add a gameobject to the radar system. If added, it will be immediately visible
|
|
/// to radar and the RadarId (itemIndex) will be return. A value of -1 indicates it wasn't added.
|
|
/// NOTE: To add a ship to radar, use shipControlModule.EnableRadar(). For Locations use
|
|
/// sscManager.EnableRadar(location).
|
|
/// </summary>
|
|
/// <param name="gameObjectToAdd"></param>
|
|
/// <param name="position">typically the position of the gameobject</param>
|
|
/// <param name="factionId"></param>
|
|
/// <param name="squadronId"></param>
|
|
/// <param name="guidHash">The unique hash to identify this gameObject. If it is 0 we will set it to gameObjectToAdd.GetHashCode()</param>
|
|
/// <param name="blipSize">The relative size it appears to be on the radar mini-map. Acceptable values 1 to 5</param>
|
|
/// <returns></returns>
|
|
public int EnableRadar(GameObject gameObjectToAdd, Vector3 position, int factionId, int squadronId, int guidHash, int blipSize)
|
|
{
|
|
// Not assigned in the radar system
|
|
int radarItemIndex = -1;
|
|
|
|
if (gameObjectToAdd != null && isInitialised)
|
|
{
|
|
SSCRadarItem sscRadarItem = new SSCRadarItem();
|
|
sscRadarItem.radarItemType = SSCRadarItem.RadarItemType.GameObject;
|
|
sscRadarItem.itemGameObject = gameObjectToAdd;
|
|
sscRadarItem.isVisibleToRadar = true;
|
|
sscRadarItem.guidHash = guidHash == 0 ? gameObjectToAdd.GetHashCode() : guidHash;
|
|
sscRadarItem.position = position;
|
|
sscRadarItem.factionId = factionId;
|
|
sscRadarItem.squadronId = squadronId;
|
|
sscRadarItem.blipSize = blipSize > 0 && blipSize < 6 ? (byte)blipSize : (byte)1;
|
|
|
|
radarItemIndex = AddItem(sscRadarItem);
|
|
}
|
|
return radarItemIndex;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The item will no longer be visible in the radar system.
|
|
/// If you want to change the visibility to other radar
|
|
/// consumers, consider changing the radar item data rather
|
|
/// than disabling the radar and (later) calling EnableRadar again.
|
|
/// </summary>
|
|
/// <param name="itemIndex">This is the value returned from sscRadar.EnableRadar(gameObject...)</param>
|
|
public void DisableRadar(int itemIndex)
|
|
{
|
|
if (isInitialised)
|
|
{
|
|
RemoveItem(itemIndex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add an item to be tracked by radar. Items are not visible to radar initially, instead
|
|
/// they are only made visible (if set to be so) for ships in ShipControlModule.Update(),
|
|
/// or sscManager.EnableRadar(..) for Locations or sscRadar.EnableRadar(..) for gameobjects.
|
|
/// </summary>
|
|
/// <param name="sscRadarItemInput"></param>
|
|
/// <returns></returns>
|
|
public int AddItem(SSCRadarItem sscRadarItemInput)
|
|
{
|
|
int itemIndex = -1;
|
|
|
|
// Find first available empty slots in sscRadarItemList using the bitmap
|
|
for (int i = 0; i < currentPoolSize; i++)
|
|
{
|
|
// Is this an empty slot?
|
|
if (radarItemBitArray[i]) { itemIndex = i; break; }
|
|
}
|
|
|
|
// If the current pool has been exhausted, add some more capacity
|
|
if (itemIndex < 0)
|
|
{
|
|
int originalPoolSize = currentPoolSize;
|
|
ExpandPool();
|
|
// If the pool has been expanded, allocate the next (empty) slot that was added
|
|
if (currentPoolSize > originalPoolSize) { itemIndex = originalPoolSize; }
|
|
}
|
|
|
|
if (itemIndex >= 0)
|
|
{
|
|
// Remove this from the available empty slots
|
|
SetBitmap(itemIndex, false);
|
|
|
|
if (sscRadarItemInput == null)
|
|
{
|
|
// This should never be null.
|
|
SSCRadarItem sscRadarItem = sscRadarItemList[itemIndex];
|
|
|
|
sscRadarItem.SetClassDefaults();
|
|
sscRadarItem.isVisibleToRadar = false;
|
|
}
|
|
else
|
|
{
|
|
SSCRadarItem sscRadarItem = sscRadarItemList[itemIndex];
|
|
|
|
// Set rarely changing fields
|
|
sscRadarItem.radarItemType = sscRadarItemInput.radarItemType;
|
|
sscRadarItem.itemGameObject = sscRadarItemInput.itemGameObject;
|
|
sscRadarItem.shipControlModule = sscRadarItemInput.shipControlModule;
|
|
sscRadarItem.guidHash = sscRadarItemInput.guidHash;
|
|
sscRadarItem.blipSize = sscRadarItemInput.blipSize;
|
|
sscRadarItem.IncrementSequenceNumber();
|
|
|
|
// Set frequently changing fields
|
|
UpdateItem(itemIndex, new SSCRadarPacket(sscRadarItemInput));
|
|
}
|
|
}
|
|
|
|
return itemIndex;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove a single item from the Radar
|
|
/// </summary>
|
|
/// <param name="itemIndex"></param>
|
|
public void RemoveItem(int itemIndex)
|
|
{
|
|
// Return item to available pool by setting isEmpty to true
|
|
SetBitmap(itemIndex, true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove all items from the Radar
|
|
/// </summary>
|
|
public void RemoveItemsAll()
|
|
{
|
|
if (isInitialised)
|
|
{
|
|
// All slots should be empty (true)
|
|
radarItemBitArray.SetAll(true);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get an active (assigned) radar item.
|
|
/// For ships and locations, the itemIndex is stored as RadarId. e.g. shipControlModule.shipInstance.RadarId
|
|
/// </summary>
|
|
/// <param name="itemIndex"></param>
|
|
/// <returns></returns>
|
|
public SSCRadarItem GetRadarItem(int itemIndex)
|
|
{
|
|
// Is the itemIndex within the valid range?
|
|
if (isInitialised && itemIndex >= 0 && itemIndex < currentPoolSize)
|
|
{
|
|
// Check to see if the slot is assigned
|
|
if (!radarItemBitArray[itemIndex]) { return sscRadarItemList[itemIndex]; }
|
|
else { return null; }
|
|
}
|
|
else { return null; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get an active (assigned) radar item given the itemIndex AND its sequenceNumber.
|
|
/// The sequence number is used to validate that this is the correct radarItem.
|
|
/// For ships and locations, the itemIndex is stored as RadarId. e.g. shipControlModule.shipInstance.RadarId
|
|
/// </summary>
|
|
/// <param name="itemIndex"></param>
|
|
/// <param name="sequenceNumber"></param>
|
|
/// <returns></returns>
|
|
public SSCRadarItem GetRadarItem(int itemIndex, uint sequenceNumber)
|
|
{
|
|
// Is the itemIndex within the valid range?
|
|
if (isInitialised && itemIndex >= 0 && itemIndex < currentPoolSize)
|
|
{
|
|
// Check to see if the slot is assigned and it matches the sequence number
|
|
if (!radarItemBitArray[itemIndex] && sscRadarItemList[itemIndex].itemSequenceNumber == sequenceNumber) { return sscRadarItemList[itemIndex]; }
|
|
else { return null; }
|
|
}
|
|
else { return null; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get an active (assigned) radar item given the SSCRadarItemKey.
|
|
/// For ships and locations, the sscRadarItemKey.radarItemIndex is stored as RadarId. e.g. shipControlModule.shipInstance.RadarId
|
|
/// </summary>
|
|
/// <param name="sscRadarItemKey"></param>
|
|
/// <returns></returns>
|
|
public SSCRadarItem GetRadarItem(SSCRadarItemKey sscRadarItemKey)
|
|
{
|
|
// Is the itemIndex within the valid range?
|
|
if (isInitialised && sscRadarItemKey.radarItemIndex >= 0 && sscRadarItemKey.radarItemIndex < currentPoolSize)
|
|
{
|
|
// Check to see if the slot is assigned and it matches the sequence number
|
|
if (!radarItemBitArray[sscRadarItemKey.radarItemIndex] && sscRadarItemList[sscRadarItemKey.radarItemIndex].itemSequenceNumber == sscRadarItemKey.radarItemSequenceNumber)
|
|
{
|
|
return sscRadarItemList[sscRadarItemKey.radarItemIndex];
|
|
}
|
|
else { return null; }
|
|
}
|
|
else { return null; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Location radarItemTypes store a unique guidHash.
|
|
/// GameObject radarItemTypes by default store a Unity hash code.
|
|
/// Ship Damage Regions radarItemTypes store the damage region unique guidHash.
|
|
/// For these three types, when the itemIndex (or RadarId) is unknown, this method
|
|
/// can be used to retrieve it.
|
|
/// If there are no matches, -1 is returned.
|
|
/// </summary>
|
|
/// <param name="guidHash"></param>
|
|
/// <returns></returns>
|
|
public int GetRadarItemIndexByHash(int guidHash)
|
|
{
|
|
int itemIndex = -1;
|
|
|
|
// Unset guidHash is 0, so ignore this.
|
|
if (guidHash != 0)
|
|
{
|
|
for (int iIdx = 0; iIdx < currentPoolSize; iIdx++)
|
|
{
|
|
// Check to see if the slot is assigned AND the hashes match
|
|
if (!radarItemBitArray[iIdx] && sscRadarItemList[iIdx].guidHash == guidHash)
|
|
{
|
|
itemIndex = iIdx;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return itemIndex;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Does this item appear in Radar queries? If the itemIndex is invalid, this method
|
|
/// will always return false.
|
|
/// NOTE: For performance reasons, it doesn't check if this is non-empty radar item.
|
|
/// For ships and locations, the itemIndex is stored as RadarId. e.g. shipControlModule.shipInstance.RadarId
|
|
/// </summary>
|
|
/// <param name="itemIndex"></param>
|
|
public bool GetVisibility (int itemIndex)
|
|
{
|
|
// Is the itemIndex within the valid range?
|
|
if (isInitialised && itemIndex >= 0 && itemIndex < currentPoolSize)
|
|
{
|
|
return sscRadarItemList[itemIndex].isVisibleToRadar;
|
|
}
|
|
else { return false; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set if this item should appear in Radar queries?
|
|
/// NOTE: For performance reasons, it doesn't check if this is non-empty radar item.
|
|
/// For ships and locations, the itemIndex is stored as RadarId. e.g. shipControlModule.shipInstance.RadarId
|
|
/// </summary>
|
|
/// <param name="itemIndex"></param>
|
|
/// <param name="isVisibleToRadar"></param>
|
|
public void SetVisibility(int itemIndex, bool isVisibleToRadar)
|
|
{
|
|
// Is the itemIndex within the valid range?
|
|
if (isInitialised && itemIndex >= 0 && itemIndex < currentPoolSize)
|
|
{
|
|
sscRadarItemList[itemIndex].isVisibleToRadar = isVisibleToRadar;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set a new world space position for a radar item. See also UpdateItem(..).
|
|
/// NOTE: For performance reasons, it doesn't check if this is non-empty radar item.
|
|
/// For ships and locations, the itemIndex is stored as RadarId. e.g. locationData.RadarId
|
|
/// </summary>
|
|
/// <param name="itemIndex"></param>
|
|
/// <param name="wsPosition"></param>
|
|
public void SetItemPosition(int itemIndex, Vector3 wsPosition)
|
|
{
|
|
// Is the itemIndex within the valid range?
|
|
if (isInitialised && itemIndex >= 0 && itemIndex < currentPoolSize)
|
|
{
|
|
sscRadarItemList[itemIndex].position = wsPosition;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the type of the radarItem. Typically, this will only be performed once for
|
|
/// each object (e.g. a ship or ground installation)
|
|
/// NOTE: For performance reasons, it doesn't check if this is non-empty radar item.
|
|
/// For ships, the itemIndex is stored as RadarId. e.g. shipControlModule.shipInstance.RadarId
|
|
/// For surfaceTurretModule, the itemIndex is stored as RadarId
|
|
/// </summary>
|
|
/// <param name="itemIndex"></param>
|
|
/// <param name="radarItemType"></param>
|
|
public void SetItemType(int itemIndex, SSCRadarItem.RadarItemType radarItemType)
|
|
{
|
|
if (isInitialised && itemIndex >= 0 && itemIndex < currentPoolSize)
|
|
{
|
|
sscRadarItemList[itemIndex].radarItemType = radarItemType;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the blip size of the radarItem. Typically this is set automatically when a ship or
|
|
/// Location is enabled for radar. However, this lets the blip size be overridden at runtime.
|
|
/// NOTE: For performance reasons, it doesn't check if this is non-empty radar item.
|
|
/// For ships, the itemIndex is stored as RadarId. e.g. shipControlModule.shipInstance.RadarId
|
|
/// For surfaceTurretModule, the itemIndex is stored as RadarId
|
|
/// </summary>
|
|
/// <param name="itemIndex"></param>
|
|
/// <param name="blipSize"></param>
|
|
public void SetBlipSize (int itemIndex, byte blipSize)
|
|
{
|
|
if (isInitialised && itemIndex >= 0 && itemIndex < currentPoolSize)
|
|
{
|
|
if (blipSize < 1) { sscRadarItemList[itemIndex].blipSize = 1; }
|
|
if (blipSize > 5) { sscRadarItemList[itemIndex].blipSize = 5; }
|
|
else { sscRadarItemList[itemIndex].blipSize = blipSize; }
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the factionId of the radarItem.
|
|
/// NOTE: For performance reasons, it doesn't check if this is non-empty radar item.
|
|
/// For ships, the itemIndex is stored as RadarId. e.g. shipControlModule.shipInstance.RadarId
|
|
/// For surfaceTurretModule, the itemIndex is stored as RadarId
|
|
/// </summary>
|
|
/// <param name="itemIndex"></param>
|
|
/// <param name="factionId"></param>
|
|
public void SetFactionId (int itemIndex, int factionId)
|
|
{
|
|
if (isInitialised && itemIndex >= 0 && itemIndex < currentPoolSize)
|
|
{
|
|
sscRadarItemList[itemIndex].factionId = factionId;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the squadronId of the radarItem.
|
|
/// NOTE: For performance reasons, it doesn't check if this is non-empty radar item.
|
|
/// For ships, the itemIndex is stored as RadarId. e.g. shipControlModule.shipInstance.RadarId
|
|
/// For surfaceTurretModule, the itemIndex is stored as RadarId
|
|
/// </summary>
|
|
/// <param name="itemIndex"></param>
|
|
/// <param name="squadronId"></param>
|
|
public void SetSquardronId (int itemIndex, int squadronId)
|
|
{
|
|
if (isInitialised && itemIndex >= 0 && itemIndex < currentPoolSize)
|
|
{
|
|
sscRadarItemList[itemIndex].squadronId = squadronId;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send information about a ship or object to the radar system.
|
|
/// NOTE: For performance reasons, it doesn't check if this is non-empty radar item.
|
|
/// </summary>
|
|
/// <param name="itemIndex"></param>
|
|
/// <param name="sscRadarPacket"></param>
|
|
public void UpdateItem(int itemIndex, SSCRadarPacket sscRadarPacket)
|
|
{
|
|
// Is the itemIndex within the valid range?
|
|
if (isInitialised && itemIndex >= 0 && itemIndex < currentPoolSize && sscRadarPacket != null)
|
|
{
|
|
// This should never be null.
|
|
SSCRadarItem sscRadarItem = sscRadarItemList[itemIndex];
|
|
|
|
// Update the radar system with the latest data from the source
|
|
sscRadarItem.position = sscRadarPacket.position;
|
|
sscRadarItem.velocity = sscRadarPacket.velocity;
|
|
sscRadarItem.isVisibleToRadar = sscRadarPacket.isVisibleToRadar;
|
|
sscRadarItem.factionId = sscRadarPacket.factionId;
|
|
sscRadarItem.squadronId = sscRadarPacket.squadronId;
|
|
}
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
#region Public API Member Methods - UI
|
|
|
|
/// <summary>
|
|
/// Redraw the results
|
|
/// </summary>
|
|
public void RefreshResults()
|
|
{
|
|
if (isShowRadarImage && isInitialised)
|
|
{
|
|
DisplayResults(true);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the world space position of the radar system. Use when
|
|
/// you want the radar system to "move" with gameobject.
|
|
/// </summary>
|
|
/// <param name="centre"></param>
|
|
/// <param name="range"></param>
|
|
public void SetDisplay(GameObject centre, float range)
|
|
{
|
|
gameobjectToFollow = centre;
|
|
displayRange = range;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the world space position of the radar system. Use when
|
|
/// you want the radar system to be fixed to a given location or
|
|
/// will move infrequently.
|
|
/// </summary>
|
|
/// <param name="centre"></param>
|
|
/// <param name="range"></param>
|
|
public void SetDisplay(Vector3 centre, float range)
|
|
{
|
|
gameobjectToFollow = null;
|
|
centrePosition = centre;
|
|
displayRange = range;
|
|
}
|
|
|
|
/// <summary>
|
|
/// If the UI has been configured in the Editor under Visuals,
|
|
/// show the radar display on the screen.
|
|
/// </summary>
|
|
public void ShowUI()
|
|
{
|
|
// Only show the UI if it is available
|
|
isShowRadarImage = isRadarImageAvailable;
|
|
|
|
if (isShowRadarImage) { radarImage.gameObject.SetActive(true); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// If the UI has been configured in the Editor under Visuals,
|
|
/// hide the radar display.
|
|
/// </summary>
|
|
public void HideUI()
|
|
{
|
|
isShowRadarImage = false;
|
|
|
|
if (isRadarImageAvailable)
|
|
{
|
|
radarImage.gameObject.SetActive(false);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The centre of the radar will follow the ship.
|
|
/// Currently this only works when the built-in UI is configured in the
|
|
/// editor under Visuals. The same can be achieve via the API
|
|
/// when calling GetRadarResults(..) and passing in the ship
|
|
/// TransformPosition.
|
|
/// </summary>
|
|
/// <param name="shipControlModule"></param>
|
|
public void FollowShip (ShipControlModule shipControlModule)
|
|
{
|
|
shipToFollow = shipControlModule;
|
|
isFollowShip = shipToFollow != null;
|
|
if (isFollowShip)
|
|
{
|
|
gameobjectToFollow = null;
|
|
isFollowGameObject = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The centre of the radar will follow the gameobject.
|
|
/// Currently this only works when the built-in UI is configured in the
|
|
/// editor under Visuals. The same can be achieve via the API
|
|
/// when calling GetRadarResults(..) and passing in the tranform
|
|
/// position.
|
|
/// </summary>
|
|
/// <param name="gameobject"></param>
|
|
public void FollowGameObject(GameObject gameobject)
|
|
{
|
|
gameobjectToFollow = gameobject;
|
|
isFollowGameObject = gameobjectToFollow != null;
|
|
if (isFollowGameObject)
|
|
{
|
|
shipToFollow = null;
|
|
isFollowShip = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the sort order in the scene of the radar mini-map. Higher values appear on top.
|
|
/// </summary>
|
|
/// <param name="newSortOrder"></param>
|
|
public void SetCanvasSortOrder(int newSortOrder)
|
|
{
|
|
canvasSortOrder = newSortOrder;
|
|
|
|
if (IsInitialised)
|
|
{
|
|
Canvas _canvas = radarImage == null ? null : radarImage.transform.parent.GetComponent<Canvas>();
|
|
|
|
if (_canvas != null) { _canvas.sortingOrder = newSortOrder; }
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the radar mini-map to use a particular display. Displays or monitors are numbered from 1 to 8.
|
|
/// </summary>
|
|
/// <param name="displayNumber">1 to 8</param>
|
|
public void SetCanvasTargetDisplay (int displayNumber)
|
|
{
|
|
if (IsInitialised && SSCUtils.VerifyTargetDisplay(displayNumber, true))
|
|
{
|
|
Canvas _canvas = radarImage == null ? null : radarImage.transform.parent.GetComponent<Canvas>();
|
|
|
|
if (_canvas != null) { _canvas.targetDisplay = displayNumber - 1; }
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Call at runtime if radarImage is swapped or made null
|
|
/// </summary>
|
|
public void RefreshRadarImageStatus()
|
|
{
|
|
isRadarImageAvailable = radarImage != null && radarImage.texture != null;
|
|
|
|
if (isRadarImageAvailable)
|
|
{
|
|
sscRadarQuery = new SSCRadarQuery();
|
|
sscRadarResultsList = new List<SSCRadarBlip>(20);
|
|
// There are currently no results.
|
|
ResultCount = 0;
|
|
displayUITexWidth = radarImage.texture.width;
|
|
displayUITexHeight = radarImage.texture.height;
|
|
displayUITexCentreX = Mathf.CeilToInt(displayUITexWidth * 0.5f);
|
|
displayUITexCentreY = Mathf.CeilToInt(displayUITexHeight * 0.5f);
|
|
}
|
|
else
|
|
{
|
|
isShowRadarImage = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// When the screen is resize, we may need to refresh things.
|
|
/// NOTE: Panel offset x/y seem to be incorrect...
|
|
/// </summary>
|
|
public void ScreenResized()
|
|
{
|
|
if (isRadarImageAvailable)
|
|
{
|
|
Canvas radarCanvas = SSCUtils.FindCanvas("SSCRadarCanvas");
|
|
if (radarCanvas != null)
|
|
{
|
|
GameObject radarCanvasGO = radarCanvas.gameObject;
|
|
Vector2 canvasSize = radarCanvasGO.GetComponent<RectTransform>().sizeDelta;
|
|
Vector3 canvasScale = radarCanvasGO.GetComponent<RectTransform>().localScale;
|
|
|
|
Vector2 anchorMin = Vector2.zero;
|
|
Vector2 anchorMax = Vector2.zero;
|
|
Vector2 panelOffset = Vector2.zero;
|
|
|
|
float panelWidth = Mathf.Ceil(radarDisplayWidthN * canvasSize.x), panelHeight = panelWidth;
|
|
|
|
GetMinimapScreenLocation((int)screenLocale, canvasSize, new Vector2(panelWidth, panelHeight), ref anchorMin, ref anchorMax, ref panelOffset);
|
|
|
|
int texWidth = Mathf.CeilToInt(panelWidth);
|
|
int texHeight = Mathf.CeilToInt(panelHeight);
|
|
|
|
Texture2D radarTex = (Texture2D)radarImage.texture;
|
|
|
|
if (radarTex.width != texWidth || radarTex.height != texHeight)
|
|
{
|
|
#if !UNITY_2021_2_OR_NEWER
|
|
radarTex.Resize(texWidth, texHeight);
|
|
#else
|
|
radarTex.Reinitialize(texWidth, texHeight);
|
|
#endif
|
|
}
|
|
|
|
SSCUtils.UpdateCanvasPanel(radarImage.rectTransform, panelOffset.x, panelOffset.y, panelWidth, panelHeight,
|
|
anchorMin.x, anchorMin.y, anchorMax.x, anchorMax.y, canvasScale);
|
|
SSCUtils.FillTexture(radarTex, Color.clear, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public API Member Methods - Query
|
|
|
|
/// <summary>
|
|
/// Send a query to the radar system and populate a list of matching items.
|
|
/// The queryResultsList must be a non-null, empty list.
|
|
/// A query will never return items with isVisibleToRadar = false.
|
|
/// TODO - consider items that are close in 2D but far in 3D space
|
|
/// FILTERING ORDER:
|
|
/// - Range
|
|
/// - Factions to Exclude
|
|
/// - FactionId
|
|
/// - Squadrons to Exclude
|
|
/// - Factions to Include
|
|
/// - Squadrons to Include
|
|
/// </summary>
|
|
/// <param name="sscRadarQuery"></param>
|
|
/// <param name="queryResultsList"></param>
|
|
/// <returns></returns>
|
|
public bool GetRadarResults (SSCRadarQuery sscRadarQuery, List<SSCRadarBlip> queryResultsList)
|
|
{
|
|
if (sscRadarQuery != null && queryResultsList != null && sscRadarQuery.range > 0f)
|
|
{
|
|
// Loop through the list of items and see which ones match the query
|
|
queryResultsList.Clear();
|
|
|
|
float sqrRange = sscRadarQuery.range * sscRadarQuery.range;
|
|
float sqrDistance2D = 0f, sqrDistance3D = 0f;
|
|
float deltaX, deltaY, deltaZ;
|
|
int factionId, squadronId;
|
|
|
|
// factionsToInclude is only considered if sscRadarQuery.factionId is not set.
|
|
int numFactionsToInclude = sscRadarQuery.factionId != SSCRadarQuery.IGNOREFACTION || sscRadarQuery.factionsToInclude == null ? 0 : sscRadarQuery.factionsToInclude.Length;
|
|
|
|
int numFactionsToExclude = sscRadarQuery.factionsToExclude == null ? 0 : sscRadarQuery.factionsToExclude.Length;
|
|
|
|
int numSquadronsToInclude = sscRadarQuery.squadronsToInclude == null ? 0 : sscRadarQuery.squadronsToInclude.Length;
|
|
int numSquadronsToExclude = sscRadarQuery.squadronsToExclude == null ? 0 : sscRadarQuery.squadronsToExclude.Length;
|
|
|
|
for (int itemIndex = 0; itemIndex < currentPoolSize; itemIndex++)
|
|
{
|
|
// Make sure slot isn't empty
|
|
if (!radarItemBitArray[itemIndex])
|
|
{
|
|
SSCRadarItem sscRadarItem = sscRadarItemList[itemIndex];
|
|
|
|
if (sscRadarItem.isVisibleToRadar)
|
|
{
|
|
deltaX = sscRadarItem.position.x - sscRadarQuery.centrePosition.x;
|
|
deltaY = sscRadarItem.position.y - sscRadarQuery.centrePosition.y;
|
|
deltaZ = sscRadarItem.position.z - sscRadarQuery.centrePosition.z;
|
|
|
|
sqrDistance2D = (deltaX * deltaX) + (deltaZ * deltaZ);
|
|
sqrDistance3D = sqrDistance2D + (deltaY * deltaY);
|
|
|
|
// Is this item within the range of the radar?
|
|
if ((sscRadarQuery.is3DQueryEnabled && sqrDistance3D <= sqrRange) || (!sscRadarQuery.is3DQueryEnabled && sqrDistance2D <= sqrRange))
|
|
{
|
|
// Moving the sscRadarItem outside the Array.IndexOf/Exists() reduces GC
|
|
factionId = sscRadarItem.factionId;
|
|
squadronId = sscRadarItem.squadronId;
|
|
|
|
// Avoid GC by using Array.IndexOf( ) > -1 rather than Array.Exists(..)
|
|
// Skip anything in the array of factions to exclude
|
|
if (numFactionsToExclude > 0 && System.Array.IndexOf(sscRadarQuery.factionsToExclude, factionId) > -1) { continue; }
|
|
|
|
// Does the faction match the query?
|
|
if (sscRadarQuery.factionId != SSCRadarQuery.IGNOREFACTION && sscRadarQuery.factionId != factionId) { continue; }
|
|
|
|
// Skip anything in the array of squadrons to exclude
|
|
if (numSquadronsToExclude > 0 && System.Array.IndexOf(sscRadarQuery.squadronsToExclude, squadronId) > -1) { continue; }
|
|
|
|
// Skip any factions not in the array of factions to include
|
|
if (numFactionsToInclude > 0 && System.Array.IndexOf(sscRadarQuery.factionsToInclude, factionId) < 0) { continue; }
|
|
|
|
// Skip any squadron not in the array of squadrons to include
|
|
if (numSquadronsToInclude > 0 && System.Array.IndexOf(sscRadarQuery.squadronsToInclude, squadronId) < 0) { continue; }
|
|
|
|
// Blips are structs rather than classes. These are created on the stack rather than in heap memory.
|
|
// Although, these are in a List... but generate no garbage and don't impact GC.
|
|
SSCRadarBlip blip = new SSCRadarBlip();
|
|
blip.wsPosition = sscRadarItem.position;
|
|
blip.radarItemType = sscRadarItem.radarItemType;
|
|
|
|
blip.distanceSqr2D = sqrDistance2D;
|
|
blip.distanceSqr3D = sqrDistance3D;
|
|
|
|
blip.itemGameObject = sscRadarItem.itemGameObject;
|
|
blip.shipControlModule = sscRadarItem.shipControlModule;
|
|
|
|
blip.factionId = sscRadarItem.factionId;
|
|
blip.squadronId = sscRadarItem.squadronId;
|
|
blip.blipSize = sscRadarItem.blipSize;
|
|
blip.radarItemIndex = itemIndex;
|
|
blip.radarItemSequenceNumber = sscRadarItem.itemSequenceNumber;
|
|
|
|
// Locations, GameObjects and Ship DamageRegions store a unique identifier to assist with lookups.
|
|
// For other types, the guidHash is 0.
|
|
blip.guidHash = sscRadarItem.guidHash;
|
|
|
|
//int radarItemTypeInt = (int)sscRadarItem.radarItemType;
|
|
// If this is a Location, set the guidHash so that Locations can be looked up using sscManager.GetLocation(blip.guidHash)
|
|
// GameObjects can store gameObject.GetHashCode() in this field.
|
|
//if (radarItemTypeInt == SSCRadarItem.RadarItemTypeLocationInt || radarItemTypeInt == SSCRadarItem.RadarItemTypeGameObjectInt)
|
|
//{
|
|
// blip.guidHash = sscRadarItem.guidHash;
|
|
//}
|
|
|
|
queryResultsList.Add(blip);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else { return false; }
|
|
|
|
int querySortOrderInt = (int)sscRadarQuery.querySortOrder;
|
|
|
|
// Quickly bypass if there is no sort order set.
|
|
if (querySortOrderInt > 0)
|
|
{
|
|
if (querySortOrderInt == SSCRadarQuery.querySortOrderDistanceAsc2DInt)
|
|
{
|
|
queryResultsList.Sort(delegate (SSCRadarBlip blip1, SSCRadarBlip blip2)
|
|
{
|
|
// If x > y return 1, if x < y return -1, if x == y return 0 (this is equivalent to x.CompareTo(y))
|
|
|
|
// NOTE: CompareTo is slightly faster than if statements to return 1, -1 or 0 - but can impact GC
|
|
// Ascending order
|
|
if (blip1.distanceSqr2D > blip2.distanceSqr2D) { return 1; }
|
|
else if (blip1.distanceSqr2D < blip2.distanceSqr2D) { return -1; }
|
|
// With floats, x == y is less likely, so do last.
|
|
else { return 0; }
|
|
}
|
|
);
|
|
}
|
|
else if (querySortOrderInt == SSCRadarQuery.querySortOrderDistanceAsc3DInt)
|
|
{
|
|
queryResultsList.Sort(delegate (SSCRadarBlip blip1, SSCRadarBlip blip2)
|
|
{
|
|
// Ascending order
|
|
if (blip1.distanceSqr3D > blip2.distanceSqr3D) { return 1; }
|
|
else if (blip1.distanceSqr3D < blip2.distanceSqr3D) { return -1; }
|
|
else { return 0; }
|
|
}
|
|
);
|
|
}
|
|
else if (querySortOrderInt == SSCRadarQuery.querySortOrderDistanceDesc2DInt)
|
|
{
|
|
queryResultsList.Sort(delegate (SSCRadarBlip blip1, SSCRadarBlip blip2)
|
|
{
|
|
// Descending order
|
|
if (blip1.distanceSqr2D < blip2.distanceSqr2D) { return 1; }
|
|
else if (blip1.distanceSqr2D > blip2.distanceSqr2D) { return -1; }
|
|
else { return 0; }
|
|
}
|
|
);
|
|
}
|
|
else if (querySortOrderInt == SSCRadarQuery.querySortOrderDistanceDesc3DInt)
|
|
{
|
|
queryResultsList.Sort(delegate (SSCRadarBlip blip1, SSCRadarBlip blip2)
|
|
{
|
|
// Descending order
|
|
if (blip1.distanceSqr3D < blip2.distanceSqr3D) { return 1; }
|
|
else if (blip1.distanceSqr3D > blip2.distanceSqr3D) { return -1; }
|
|
else { return 0; }
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
ResultCount = queryResultsList == null ? 0 : queryResultsList.Count;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the next blip in the supplied list, that is within the camera's viewing area. The startIndex
|
|
/// is the zero-based index to begin searching the supplied list.
|
|
/// If a match is found, the zero-based index of that blip will be returned, else -1 will be returned.
|
|
/// NOTE: Currently assumes the camera is full screen.
|
|
/// </summary>
|
|
/// <param name="blipList"></param>
|
|
/// <param name="startIndex"></param>
|
|
/// <param name="camera"></param>
|
|
/// <param name="viewSize">This is usually the screen resolution</param>
|
|
/// <returns></returns>
|
|
public int GetNextBlipInView(List<SSCRadarBlip> blipList, int startIndex, Camera camera, Vector2 viewSize)
|
|
{
|
|
int blipIndex = -1;
|
|
|
|
int numBlips = blipList == null ? 0 : blipList.Count;
|
|
|
|
for (int blpIdx = startIndex; blpIdx < numBlips; blpIdx++)
|
|
{
|
|
if (SSCUtils.PointViewDirection(camera, blipList[blpIdx].wsPosition, viewSize) == SSCUtils.ViewDirection.InFront)
|
|
{
|
|
blipIndex = blpIdx;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return blipIndex;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the next blip in the supplied list, that is in front of the camera within a custom 2D viewport.
|
|
/// The startIndex is the zero-based index to begin searching the supplied list.
|
|
/// If a match is found, the zero-based index of that blip will be returned, else -1 will be returned.
|
|
/// </summary>
|
|
/// <param name="blipList"></param>
|
|
/// <param name="startIndex"></param>
|
|
/// <param name="camera"></param>
|
|
/// <param name="viewSize">This is usually the screen resolution</param>
|
|
/// <param name="viewportSize">The width and height as 0.0-1.0 values of the full viewSize</param>
|
|
/// <param name="viewportOffset">-1.0 to 1.0 with 0,0 as the centre of the screen</param>
|
|
/// <returns></returns>
|
|
public int GetNextBlipinScreenViewPort(List<SSCRadarBlip> blipList, int startIndex, Camera camera, Vector2 viewSize, Vector2 viewportSize, Vector2 viewportOffset)
|
|
{
|
|
int blipIndex = -1;
|
|
|
|
int numBlips = blipList == null ? 0 : blipList.Count;
|
|
|
|
for (int blpIdx = startIndex; blpIdx < numBlips; blpIdx++)
|
|
{
|
|
if (SSCUtils.IsPointInScreenViewport(camera, blipList[blpIdx].wsPosition, viewSize, viewportSize, viewportOffset))
|
|
{
|
|
blipIndex = blpIdx;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return blipIndex;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is the blip within the camera's viewing area?
|
|
/// NOTE: Currently assumes the camera is full screen.
|
|
/// </summary>
|
|
/// <param name="radarBlip"></param>
|
|
/// <param name="camera"></param>
|
|
/// <param name="viewSize">This is usually the screen resolution</param>
|
|
/// <returns></returns>
|
|
public bool IsBlipInView(SSCRadarBlip radarBlip, Camera camera, Vector2 viewSize)
|
|
{
|
|
return SSCUtils.PointViewDirection(camera, radarBlip.wsPosition, viewSize) == SSCUtils.ViewDirection.InFront;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is the blip in front of the camera within a custom 2D viewport.
|
|
/// Offset is -1.0 to 1.0 with 0,0 as the centre of the screen.
|
|
/// </summary>
|
|
/// <param name="radarBlip"></param>
|
|
/// <param name="camera"></param>
|
|
/// <param name="viewSize">This is usually the screen resolution</param>
|
|
/// <param name="viewportSize">The width and height as 0.0-1.0 values of the full viewSize</param>
|
|
/// <param name="viewportOffset">-1.0 to 1.0 with 0,0 as the centre of the screen</param>
|
|
/// <returns></returns>
|
|
public bool IsBlipInScreenViewPort(SSCRadarBlip radarBlip, Camera camera, Vector2 viewSize, Vector2 viewportSize, Vector2 viewportOffset)
|
|
{
|
|
return SSCUtils.IsPointInScreenViewport(camera, radarBlip.wsPosition, viewSize, viewportSize, viewportOffset);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Does the radar blip contain information about a GameObject?
|
|
/// </summary>
|
|
/// <param name="radarBlip"></param>
|
|
/// <returns></returns>
|
|
public bool IsGameObjectBlip(SSCRadarBlip radarBlip)
|
|
{
|
|
return radarBlip.itemGameObject != null && radarBlip.radarItemType == SSCRadarItem.RadarItemType.GameObject;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Does the radar blip contain information about a Location?
|
|
/// </summary>
|
|
/// <param name="radarBlip"></param>
|
|
/// <returns></returns>
|
|
public bool IsLocationBlip(SSCRadarBlip radarBlip)
|
|
{
|
|
return radarBlip.guidHash != 0 && radarBlip.radarItemType == SSCRadarItem.RadarItemType.Location;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Does the radar blip contain information about a ship?
|
|
/// </summary>
|
|
/// <param name="radarBlip"></param>
|
|
/// <returns></returns>
|
|
public bool IsShipBlip(SSCRadarBlip radarBlip)
|
|
{
|
|
return radarBlip.shipControlModule != null && (radarBlip.radarItemType == SSCRadarItem.RadarItemType.AIShip || radarBlip.radarItemType == SSCRadarItem.RadarItemType.PlayerShip);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Does the radar blip contain information about a ship damage region?
|
|
/// </summary>
|
|
/// <param name="radarBlip"></param>
|
|
/// <returns></returns>
|
|
public bool IsShipDamageRegionBlip(SSCRadarBlip radarBlip)
|
|
{
|
|
return radarBlip.guidHash != 0 && radarBlip.shipControlModule != null && radarBlip.radarItemType == SSCRadarItem.RadarItemType.ShipDamageRegion;
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
} |