using System; using System.Collections; using System.Collections.Generic; using System.Data.Common; using System.Linq; using System.Reflection; using log4net; using PrimeTween; using Unity.Mathematics; using Unity.VisualScripting; using UnityEditor.Rendering; using UnityEngine; using UnityEngine.Rendering.Universal; using static UnityEngine.Mathf; public class CameraOperator : MonoBehaviour { private static ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private Dictionary players = new Dictionary(); [SerializeField] private int MaxAdditionalDistance = 30; [SerializeField] private float FarOutBias = 0.3f; [SerializeField] private float MaxFollowDistance = 100f; [SerializeField] [Tooltip("Target Offset/ (Framerate * (1/FollowSpeed))")] private float FollowSpeed = 2f; [SerializeField] [Tooltip("Lower values make the camera tilt harder to keep the mid point centered")] private float TiltFactor = 1.5f; [SerializeField] private float ZoomOutMargin = Screen.height / 4; [SerializeField] private float ZoomInMargin = Screen.height / 3; [SerializeField] private float MinZoomSpeed = 0.4f; [SerializeField] private float MaxZoomSpeed = 0.5f; private Vector3 _currentZoomSpeed = Vector3.zero; private float InitialDistance = 0; private float MaxDistance; private float ZoomTriggerXDistance = 0; private float ZoomTriggerYDistance = 0; private Camera cam; private void Awake() { cam = gameObject.GetComponent(); // Distance here is in the negative direction on the z axis InitialDistance = transform.localPosition.z; MaxDistance = InitialDistance - MaxAdditionalDistance; } public void AddCharacter(GameObject ship) { players[ship.GetInstanceID()] = ship; } public void RemoveCharacter(GameObject ship) { players.Remove(ship.GetInstanceID()); ZoomTriggerXDistance = 0; ZoomTriggerYDistance = 0; } /// /// Tilts the camera to point at the center between the players. /// Zooms to keep players in frame. /// Follows the players mid-point. /// private void FixedUpdate() { Vector3 center = CalculatePlayersCenter(); if (players.Count == 1) { FollowPosition(players.First().Value.transform.localPosition); FacePlayersCenter(center - transform.localPosition); } else if (players.Count > 0) { FollowPosition(center); FacePlayersCenter(center); AdjustZoom(); } else { return; } } private Vector3 CalculatePlayersCenter() { Vector3 center = new Vector3(); if (players.Count < 1) { return transform.localPosition; } Vector3 furthestPoint = new Vector3(); foreach (GameObject p in players.Values) { if (p.IsDestroyed()) continue; Vector3 position; position = p.transform.localPosition; center += position; if ((position - center).magnitude > furthestPoint.magnitude) { furthestPoint = position - center; } } center /= players.Count(); // TODO: Competing furthest points cause jitter if (FarOutBias > 0 && players.Count > 2) { center += (center - furthestPoint) * FarOutBias; } return center; } private void FacePlayersCenter(Vector3 center) { var x = center.x; var y = center.y; var z = center.z; x /= players.Count(); y /= players.Count(); float a = z - transform.localPosition.z * TiltFactor; float cXAxis = (float)Sqrt(Pow(a, 2) + Pow(x, 2)); float cYAxis = (float)Sqrt(Pow(a, 2) + Pow(y, 2)); Vector3 xyRotation = new Vector3(Rad2Deg * Acos(a / cYAxis) * -Math.Sign(y), Rad2Deg * Acos(a / cXAxis) * Math.Sign(x), 0); if (transform.localEulerAngles != xyRotation / 2) { transform.localEulerAngles = xyRotation / 2; } } private void CalculateMaxInterPlayerDistance() { if (players.Count < 2) { return; } float Margin = Screen.height / 2; float maxXDistance = 0; float maxYDistance = 0; foreach (GameObject player in players.Values) { var screenPos1 = cam.WorldToScreenPoint(player.transform.position); foreach (GameObject p in players.Values) { if (p == player) continue; var screenPos2 = cam.WorldToScreenPoint(p.transform.position); var distance = screenPos2 - screenPos1; float xDistance = Abs(distance.x); if (maxXDistance < xDistance) maxXDistance = xDistance; float yDistance = Abs(distance.y); if (maxYDistance < yDistance) maxYDistance = Abs(distance.y); } } } private void AdjustZoom() { if (players.Count < 2) { return; } float maxXPos = 0; float maxYPos = 0; float minXPos = -1; float minYPos = -1; foreach (GameObject player in players.Values) { var screenPos = cam.WorldToScreenPoint(player.transform.position); if (minXPos == -1) minXPos = screenPos.x; if (minYPos == -1) minYPos = screenPos.y; if (screenPos.x < minXPos) minXPos = screenPos.x; if (screenPos.y < minYPos) minYPos = screenPos.y; if (screenPos.x > maxXPos) maxXPos = screenPos.x; if (screenPos.y > maxYPos) maxYPos = screenPos.y; } if (maxXPos < Screen.width - ZoomInMargin && maxYPos < Screen.height - ZoomInMargin && minXPos > ZoomInMargin && minYPos > ZoomInMargin) // Anti-Shake // && (ZoomTriggerXDistance < maxXDistance - 3f // || ZoomTriggerYDistance < maxYDistance - 3f)) { if (transform.localPosition.z < InitialDistance) { Vector3 target = transform.localPosition + new Vector3(0, 0, MinZoomSpeed); transform.localPosition = Vector3.SmoothDamp(transform.localPosition, target, ref _currentZoomSpeed, 0.0003f); } return; } if (maxXPos > Screen.width - ZoomOutMargin || maxYPos > Screen.height - ZoomOutMargin || minXPos < ZoomOutMargin || minYPos < ZoomOutMargin) { if (transform.localPosition.z > MaxDistance) { Vector3 target = transform.localPosition - new Vector3(0, 0, MaxZoomSpeed); transform.localPosition = Vector3.SmoothDamp(transform.localPosition, target, ref _currentZoomSpeed, 0.0007f); } } } private void FollowPosition(Vector3 position) { Vector3 offset = position - transform.localPosition; offset.z = 0; Vector3 ignoreZ = new Vector3(transform.localPosition.x, transform.localPosition.y, 0); if (offset.magnitude < 1) { return; } if (MaxFollowDistance != -1 && ignoreZ.magnitude > MaxFollowDistance && (ignoreZ + offset).magnitude > ignoreZ.magnitude) { return; } transform.localPosition += offset / ((1 / Time.deltaTime) * (1 / FollowSpeed)); } }