Space-Smash-Out/Assets/Scripts/CameraOperator.cs
Jakob Feldmann 94863a7eb5 feat: free fly arena, 4-player support, 4-player arena, test-scene option etc.
There is also overhauled camera zoom behavior, an update to the match logic,
tweaks to the main menu scene.
2024-05-05 17:53:56 +02:00

263 lines
6.4 KiB
C#

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<int, GameObject> players = new Dictionary<int, GameObject>();
[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<Camera>();
// 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;
}
/// <summary>
/// Tilts the camera to point at the center between the players.
/// Zooms to keep players in frame.
/// Follows the players mid-point.
/// </summary>
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));
}
}