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