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(); _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(); 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(); 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; }