using System.Collections.Generic; using RR.Game2D.Core.Managers; using UnityEngine; using UnityEngine.U2D.Animation; namespace RR.Game2D.Behaviour { /// /// Заменяет спрайты созданного игрока спрайтами из совместимого prefab-скина. /// Компонент действует только в собственной сцене и не изменяет prefab игрока. /// [DisallowMultipleComponent] public sealed class PlayerSkinOverride : MonoBehaviour { [Header("Скин игрока")] [Tooltip("Prefab с той же иерархией SpriteRenderer + SpriteSkin, что и у игрока.")] [SerializeField] private GameObject skinPrefab; private GameManager _subscribedManager; private GameObject _lastAppliedPlayer; private string _lastReportedProblem; private void OnEnable() { SubscribeToPlayerSpawn(); TryApplyToCurrentPlayer(); } private void Start() { // Повторная попытка нужна, если компонент включился раньше GameManager. SubscribeToPlayerSpawn(); TryApplyToCurrentPlayer(); } private void OnDisable() { UnsubscribeFromPlayerSpawn(); _lastAppliedPlayer = null; } private void SubscribeToPlayerSpawn() { GameManager manager = GameManager.Instance; if (_subscribedManager == manager) return; UnsubscribeFromPlayerSpawn(); if (manager == null) return; _subscribedManager = manager; _subscribedManager.PlayerSpawned += OnPlayerSpawned; } private void UnsubscribeFromPlayerSpawn() { if (_subscribedManager != null) _subscribedManager.PlayerSpawned -= OnPlayerSpawned; _subscribedManager = null; } private void TryApplyToCurrentPlayer() { GameObject player = _subscribedManager != null ? _subscribedManager.PlayerObj : null; if (player == null || player.scene != gameObject.scene) return; ApplySkin(player); } private void OnPlayerSpawned(GameObject player) { if (player == null || player.scene != gameObject.scene) return; ApplySkin(player); } private void ApplySkin(GameObject player) { if (_lastAppliedPlayer == player) return; if (HasAnotherOverrideInScene()) { ReportProblem("В сцене найдено несколько активных PlayerSkinOverride. Скин не применён.", true); return; } if (skinPrefab == null) { ReportProblem("Prefab скина не назначен. Используется стандартный скин игрока.", false); return; } if (skinPrefab.scene.IsValid()) { ReportProblem("В поле Skin Prefab должен быть назначен prefab-ассет, а не объект сцены.", true); return; } if (!TryCollectParts(skinPrefab, "скина", true, out Dictionary skinParts)) return; if (!TryCollectParts(player, "игрока", false, out Dictionary playerParts)) return; if (skinParts.Count != playerParts.Count) { ReportProblem( $"Структура скина несовместима с игроком: частей в скине — {skinParts.Count}, " + $"частей у игрока — {playerParts.Count}. Скин не применён.", true); return; } foreach (KeyValuePair skinPart in skinParts) { if (!playerParts.ContainsKey(skinPart.Key)) { ReportProblem( $"У игрока отсутствует визуальная часть \"{skinPart.Key}\". Скин не применён.", true); return; } } // Замена выполняется только после полной проверки, чтобы не получить частично применённый скин. foreach (KeyValuePair skinPart in skinParts) playerParts[skinPart.Key].sprite = skinPart.Value.sprite; _lastAppliedPlayer = player; _lastReportedProblem = null; } private bool TryCollectParts( GameObject root, string rootDescription, bool requireSprite, out Dictionary parts) { parts = new Dictionary(); SpriteSkin[] spriteSkins = root.GetComponentsInChildren(true); if (spriteSkins.Length == 0) { ReportProblem( $"В иерархии {rootDescription} \"{root.name}\" не найдено ни одного SpriteSkin.", true); return false; } foreach (SpriteSkin spriteSkin in spriteSkins) { SpriteRenderer spriteRenderer = spriteSkin.GetComponent(); string relativePath = GetRelativePath(root.transform, spriteSkin.transform); if (spriteRenderer == null) { ReportProblem( $"На части {rootDescription} \"{relativePath}\" отсутствует SpriteRenderer.", true); return false; } if (requireSprite && spriteRenderer.sprite == null) { ReportProblem( $"На части {rootDescription} \"{relativePath}\" не назначен Sprite.", true); return false; } if (!parts.TryAdd(relativePath, spriteRenderer)) { ReportProblem( $"В иерархии {rootDescription} повторяется путь \"{relativePath}\".", true); return false; } } return true; } private bool HasAnotherOverrideInScene() { PlayerSkinOverride[] overrides = FindObjectsByType( FindObjectsInactive.Exclude, FindObjectsSortMode.None); foreach (PlayerSkinOverride candidate in overrides) { if (candidate != this && candidate.gameObject.scene == gameObject.scene) return true; } return false; } private static string GetRelativePath(Transform root, Transform child) { if (root == child) return string.Empty; Stack names = new(); Transform current = child; while (current != null && current != root) { names.Push(current.name); current = current.parent; } return string.Join("/", names); } private void ReportProblem(string message, bool isError) { if (_lastReportedProblem == message) return; _lastReportedProblem = message; string formattedMessage = $"[{nameof(PlayerSkinOverride)}] {message}"; if (isError) Debug.LogError(formattedMessage, this); else Debug.LogWarning(formattedMessage, this); } } }