using System; using System.Collections; using System.Collections.Generic; using System.Data.Common; using System.Linq; using System.Reflection; using Edgegap.Editor; using GameKit.Dependencies.Utilities; 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] [Range(0.0f, 1f)] private float ZoomOutMargin = 0.5f; [SerializeField] [Range(0.0f, 1f)] private float ZoomInMargin = 0.3f; [SerializeField] private float ZoomInSpeed = 0.4f; [SerializeField] private float ZoomOutSpeed = 0.5f; [SerializeField] private float TimeUntilZoomIn = 0.3f; [SerializeField] private float TimeUntilZoomOut = 0.3f; private float _switchZoomInTime = 0f; private float _switchZoomOutTime = 0f; private Vector2 _zoomInPixels = new(); private Vector2 _zoomOutPixels = new(); 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; _zoomInPixels.x = Screen.width * 0.5f * ZoomInMargin; _zoomInPixels.y = Screen.height * 0.5f * ZoomInMargin; _zoomOutPixels.x = Screen.width * 0.5f * ZoomOutMargin; _zoomOutPixels.y = Screen.height * 0.5f * ZoomOutMargin; } public void ShakeCam(float strength = 0.1f, float duration = 0.05f, float frequency = 50f) { Tween.ShakeLocalPosition(cam.transform, new Vector2(strength, strength), duration, frequency); //Tween.ShakeLocalRotation(cam.transform, new Vector2(strength, strength), duration); } 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 LateUpdate() { Vector3 center = CalculatePlayersCenter(); if (center.IsNan()) return; 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(); 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 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; } Vector2 maxDistance = new Vector2(0, 0); Vector2 minDistance = new Vector2(-1, -1); foreach (GameObject player in players.Values) { var screenPos = cam.WorldToScreenPoint(player.transform.position); var screenMiddle = new Vector2(Screen.width / 2, Screen.height / 2); var distance = new Vector2 { x = Math.Abs(screenMiddle.x - screenPos.x), y = Math.Abs(screenMiddle.y - screenPos.y) }; if (minDistance.x == -1) minDistance.x = distance.x; if (minDistance.y == -1) minDistance.y = distance.y; if (distance.x < minDistance.x) minDistance.x = distance.x; if (distance.y < minDistance.y) minDistance.y = distance.y; if (distance.x > maxDistance.x) maxDistance.x = distance.x; if (distance.y > maxDistance.y) maxDistance.y = distance.y; } //Log.Debug(maxDistance); if (maxDistance.x < _zoomInPixels.x && maxDistance.y < _zoomInPixels.y) { _switchZoomOutTime = TimeUntilZoomOut; if (_switchZoomInTime > 0) { _switchZoomInTime -= Time.deltaTime; return; } if (transform.localPosition.z < InitialDistance) { transform.localPosition += new Vector3(0, 0, ZoomInSpeed); //Vector3 target = transform.localPosition + new Vector3(0, 0, MinZoomSpeed); // transform.localPosition = // Vector3.SmoothDamp(transform.localPosition, target, ref _currentZoomSpeed, 0.0008f); } return; } if (maxDistance.x > _zoomOutPixels.x || maxDistance.y > _zoomOutPixels.y) { _switchZoomInTime = TimeUntilZoomIn; // if (_switchZoomOutTime > 0) // { // _switchZoomOutTime -= Time.deltaTime; // return; // } if (transform.localPosition.z > MaxDistance) { transform.localPosition -= new Vector3(0, 0, ZoomOutSpeed); //Vector3.SmoothDamp(transform.localPosition, target, ref _currentZoomSpeed, 0.0003f); // transform.localPosition = // Vector3.SmoothDamp(transform.localPosition, target, ref _currentZoomSpeed, MaxZoomSpeed); } } } 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)); } }