Files
ScienceLab.TimeAndSpace/Assets/Scripts/Countries/PlaneController.cs
2026-03-17 18:10:00 +02:00

192 lines
6.8 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlaneController : MonoBehaviour
{
public GameObject planePrefab;
public float flightDuration = 5f;
public float arcHeight = 0.3f;
public float hoverHeight = 0.2f;
public float globeRadius = 5f;
public float rotateToDuration = 1.5f;
[Header("Camera Follow")]
public float cameraDistance = 3f;
public float cameraHeight = 0.5f;
public float cameraSmoothing = 3f;
private bool _flying = false;
private GameObject _plane;
private LineRenderer _trail;
private Transform _globe;
private Camera _mainCamera;
private Vector3 _cameraStartPos;
private Quaternion _cameraStartRot;
void Start()
{
_globe = GameObject.Find("Globe")?.transform;
_mainCamera = Camera.main;
SpawnPlane();
PlaceOverCountry("Ukraine");
}
void SpawnPlane()
{
if (planePrefab == null) return;
_plane = Instantiate(planePrefab);
if (_globe != null) _plane.transform.SetParent(_globe);
_trail = _plane.AddComponent<LineRenderer>();
_trail.positionCount = 0;
_trail.startWidth = 0.03f;
_trail.endWidth = 0.03f;
_trail.useWorldSpace = true;
_trail.material = new Material(Shader.Find("Sprites/Default"));
_trail.material.color = new Color(1f, 1f, 1f, 0.8f);
}
Vector3 GetLocalDirection(CountryData c)
{
Renderer rend = c.GetComponentInChildren<Renderer>();
Vector3 worldCenter = rend != null ? rend.bounds.center : c.transform.position;
if (_globe == null) return worldCenter.normalized;
return _globe.InverseTransformPoint(worldCenter).normalized;
}
public void PlaceOverCountry(string countryName)
{
if (_plane == null) return;
CountryData[] all = FindObjectsOfType<CountryData>();
foreach (CountryData c in all)
{
if (c.countryName == countryName)
{
SnapToSurface(c);
return;
}
}
}
void SnapToSurface(CountryData country)
{
if (_plane == null || _globe == null) return;
Vector3 dir = GetLocalDirection(country);
_plane.transform.localPosition = dir * (globeRadius + hoverHeight);
_plane.transform.up = (_plane.transform.position - _globe.position).normalized;
}
public void Fly(CountryData fromCountry, CountryData toCountry, System.Action onArrived)
{
if (_flying || _plane == null) return;
StartCoroutine(FlyCoroutine(fromCountry, toCountry, onArrived));
}
IEnumerator FlyCoroutine(CountryData fromCountry, CountryData toCountry, System.Action onArrived)
{
_flying = true;
_trail.positionCount = 0;
_cameraStartPos = _mainCamera.transform.position;
_cameraStartRot = _mainCamera.transform.rotation;
Vector3 fromDir = GetLocalDirection(fromCountry);
float elapsed = 0f;
while (elapsed < flightDuration)
{
elapsed += Time.deltaTime;
float t = Mathf.Clamp01(elapsed / flightDuration);
float smoothT = Mathf.SmoothStep(0f, 1f, t);
Vector3 toDir = GetLocalDirection(toCountry);
Vector3 onSphere = Vector3.Slerp(fromDir, toDir, smoothT);
float arc = Mathf.Sin(smoothT * Mathf.PI) * arcHeight;
_plane.transform.localPosition = onSphere * (globeRadius + hoverHeight + arc);
float tNext = Mathf.Min(smoothT + 0.02f, 1f);
Vector3 nextOnSphere = Vector3.Slerp(fromDir, toDir, tNext);
float nextArc = Mathf.Sin(tNext * Mathf.PI) * arcHeight;
Vector3 nextWorld = _globe.TransformPoint(nextOnSphere * (globeRadius + hoverHeight + nextArc));
Vector3 worldForward = (nextWorld - _plane.transform.position).normalized;
Vector3 worldUp = (_plane.transform.position - _globe.position).normalized;
if (worldForward.magnitude > 0.001f)
_plane.transform.rotation = Quaternion.LookRotation(worldForward, worldUp);
Vector3 sideDir = Vector3.Cross(worldForward, worldUp).normalized;
Vector3 desiredPos = _plane.transform.position
+ sideDir * cameraDistance
+ worldUp * cameraHeight;
Quaternion desiredRot = Quaternion.LookRotation(
_plane.transform.position - desiredPos, worldUp);
_mainCamera.transform.position = Vector3.Lerp(
_mainCamera.transform.position, desiredPos,
Time.deltaTime * cameraSmoothing);
_mainCamera.transform.rotation = Quaternion.Slerp(
_mainCamera.transform.rotation, desiredRot,
Time.deltaTime * cameraSmoothing);
_trail.positionCount++;
_trail.SetPosition(_trail.positionCount - 1, _plane.transform.position);
yield return null;
}
SnapToSurface(toCountry);
_trail.positionCount = 0;
yield return StartCoroutine(RestoreCamera());
yield return StartCoroutine(RotateGlobeToCountry(toCountry));
_flying = false;
onArrived?.Invoke();
}
IEnumerator RestoreCamera()
{
float elapsed = 0f;
float duration = 0.8f;
Vector3 fromPos = _mainCamera.transform.position;
Quaternion fromRot = _mainCamera.transform.rotation;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = Mathf.SmoothStep(0f, 1f, elapsed / duration);
_mainCamera.transform.position = Vector3.Lerp(fromPos, _cameraStartPos, t);
_mainCamera.transform.rotation = Quaternion.Slerp(fromRot, _cameraStartRot, t);
yield return null;
}
_mainCamera.transform.position = _cameraStartPos;
_mainCamera.transform.rotation = _cameraStartRot;
}
IEnumerator RotateGlobeToCountry(CountryData country)
{
if (_globe == null || _mainCamera == null) yield break;
Vector3 countryWorldPos = _globe.TransformPoint(GetLocalDirection(country) * globeRadius);
Vector3 globeCenter = _globe.position;
Vector3 camDir = (_mainCamera.transform.position - globeCenter).normalized;
Quaternion startRot = _globe.rotation;
Quaternion targetRot = Quaternion.FromToRotation(
(countryWorldPos - globeCenter).normalized, camDir) * _globe.rotation;
float elapsed = 0f;
while (elapsed < rotateToDuration)
{
elapsed += Time.deltaTime;
float t = Mathf.SmoothStep(0f, 1f, elapsed / rotateToDuration);
_globe.rotation = Quaternion.Slerp(startRot, targetRot, t);
yield return null;
}
_globe.rotation = targetRot;
}
public bool IsFlying() => _flying;
}