← Назад к вопросам

Реализовать мини-карту для игры

2.2 Middle🔥 161 комментариев
#C# и ООП#UI#Unity Core#Рендеринг и графика

Условие

Реализуйте мини-карту в углу экрана.

Требования

  1. Отображение области вокруг игрока
  2. Иконки для игрока, врагов, целей
  3. Вращение карты вместе с игроком или статичная ориентация
  4. Масштабирование карты
  5. Fog of war (опционально)

Реализация

  • Отдельная камера сверху
  • Render Texture
  • UI Image для отображения

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение: Система мини-карты для игры

Эта задача требует создания интерактивной мини-карты с отслеживанием игрока, врагов и целей, с поддержкой различных режимов ориентации. Расскажу о полной архитектуре.

Архитектура системы

Основные компоненты:

  1. MinimapCamera — отдельная камера для мини-карты
  2. MinimapRenderer — управление RenderTexture
  3. MinimapMarker — маркеры на мини-карте
  4. MinimapController — основной контроллер
  5. FogOfWar — система скрытия области (опционально)

MinimapCamera - Камера мини-карты

public class MinimapCamera : MonoBehaviour
{
    [SerializeField] private Camera minimapCamera;
    [SerializeField] private Transform playerTransform;
    [SerializeField] private float cameraHeight = 50f;
    [SerializeField] private float minimapSize = 100f;
    [SerializeField] private MinimapOrientation orientation = MinimapOrientation.Rotated;
    [SerializeField] private Color backgroundColor = Color.black;
    
    public enum MinimapOrientation
    {
        Rotated,        // Карта вращается с игроком
        Static          // Карта ориентирована фиксированно
    }
    
    private void Start()
    {
        if (minimapCamera == null)
            minimapCamera = GetComponent<Camera>();
        
        minimapCamera.orthographic = true;
        minimapCamera.orthographicSize = minimapSize;
        minimapCamera.backgroundColor = backgroundColor;
    }
    
    private void LateUpdate()
    {
        if (playerTransform == null)
            return;
        
        // Позиционируем камеру над игроком
        Vector3 cameraPos = playerTransform.position + Vector3.up * cameraHeight;
        minimapCamera.transform.position = cameraPos;
        
        // Поворот карты в зависимости от режима
        if (orientation == MinimapOrientation.Rotated)
        {
            // Карта вращается следуя направлению игрока
            Quaternion targetRotation = Quaternion.Euler(90, playerTransform.eulerAngles.y, 0);
            minimapCamera.transform.rotation = targetRotation;
        }
        else
        {
            // Карта всегда ориентирована одинаково
            minimapCamera.transform.rotation = Quaternion.Euler(90, 0, 0);
        }
    }
    
    public void SetCameraSize(float size)
    {
        minimapCamera.orthographicSize = size;
        minimapSize = size;
    }
    
    public void SetOrientation(MinimapOrientation newOrientation)
    {
        orientation = newOrientation;
    }
}

MinimapMarker - Маркеры

public enum MarkerType
{
    Player,
    Enemy,
    Ally,
    Objective,
    Item,
    Waypoint
}

public class MinimapMarker : MonoBehaviour
{
    [SerializeField] private MarkerType markerType = MarkerType.Enemy;
    [SerializeField] private Color markerColor = Color.red;
    [SerializeField] private float markerSize = 0.5f;
    [SerializeField] private bool rotateWithPlayer = false;
    [SerializeField] private GameObject markerVisuals;
    
    private Transform targetTransform;
    private Image markerImage;
    private RectTransform markerRect;
    private MinimapController minimapController;
    private Vector3 localOffset = Vector3.zero;
    
    public MarkerType Type => markerType;
    public Transform TargetTransform => targetTransform;
    
    private void Start()
    {
        minimapController = FindObjectOfType<MinimapController>();
        
        if (markerVisuals != null)
        {
            markerImage = markerVisuals.GetComponent<Image>();
            markerRect = markerVisuals.GetComponent<RectTransform>();
        }
        
        if (markerImage != null)
            markerImage.color = markerColor;
    }
    
    public void Initialize(Transform target, Color color, MarkerType type, Vector3 offset = default)
    {
        targetTransform = target;
        markerType = type;
        markerColor = color;
        localOffset = offset;
        
        if (markerImage != null)
            markerImage.color = color;
    }
    
    public void UpdatePosition(Vector3 playerPosition, Quaternion playerRotation, float scale)
    {
        if (targetTransform == null || minimapController == null)
            return;
        
        // Вычисляем позицию относительно игрока
        Vector3 relativePos = targetTransform.position - playerPosition + localOffset;
        
        // Масштабируем позицию
        relativePos *= scale;
        
        // Если карта вращается - применяем поворот
        if (minimapController.IsMapRotated())
        {
            relativePos = Quaternion.Inverse(playerRotation) * relativePos;
        }
        
        if (markerRect != null)
        {
            markerRect.anchoredPosition = new Vector2(relativePos.x, relativePos.z);
            
            // Применяем размер маркера
            markerRect.sizeDelta = Vector2.one * markerSize * 10f;
        }
        
        // Опционально вращаем маркер с игроком
        if (rotateWithPlayer && markerVisuals != null)
        {
            markerVisuals.transform.eulerAngles = new Vector3(0, 0, -playerRotation.eulerAngles.y);
        }
    }
    
    public void SetVisibility(bool visible)
    {
        if (markerVisuals != null)
            markerVisuals.SetActive(visible);
    }
}

MinimapController - Основной контроллер

public class MinimapController : MonoBehaviour
{
    [SerializeField] private RawImage minimapDisplay;
    [SerializeField] private RenderTexture minimapTexture;
    [SerializeField] private MinimapCamera minimapCameraComponent;
    [SerializeField] private Transform playerTransform;
    [SerializeField] private Canvas minimapCanvas;
    [SerializeField] private float minimapScale = 1f;
    [SerializeField] private float updateRate = 0.016f; // ~60 FPS
    [SerializeField] private bool enableFogOfWar = false;
    [SerializeField] private MinimapCamera.MinimapOrientation orientation = MinimapCamera.MinimapOrientation.Rotated;
    
    private Dictionary<Transform, MinimapMarker> markerMap = new Dictionary<Transform, MinimapMarker>();
    private List<MinimapMarker> allMarkers = new List<MinimapMarker>();
    private float updateTimer = 0f;
    private FogOfWarSystem fogOfWar;
    
    public event System.Action<MinimapMarker> OnMarkerAdded;
    public event System.Action<MinimapMarker> OnMarkerRemoved;
    
    private void Start()
    {
        if (minimapTexture == null)
        {
            minimapTexture = new RenderTexture(512, 512, 16);
            minimapTexture.name = "MinimapTexture";
        }
        
        if (minimapDisplay != null)
            minimapDisplay.texture = minimapTexture;
        
        if (minimapCameraComponent != null)
            minimapCameraComponent.minimapCamera.targetTexture = minimapTexture;
        
        if (enableFogOfWar)
        {
            fogOfWar = gameObject.AddComponent<FogOfWarSystem>();
        }
        
        CollectExistingMarkers();
    }
    
    private void LateUpdate()
    {
        updateTimer += Time.deltaTime;
        
        if (updateTimer >= updateRate)
        {
            UpdateMinimapMarkers();
            updateTimer = 0f;
        }
    }
    
    private void CollectExistingMarkers()
    {
        MinimapMarker[] markers = FindObjectsOfType<MinimapMarker>();
        foreach (var marker in markers)
        {
            RegisterMarker(marker);
        }
    }
    
    private void UpdateMinimapMarkers()
    {
        if (playerTransform == null)
            return;
        
        foreach (var marker in allMarkers)
        {
            if (marker == null || marker.TargetTransform == null)
                continue;
            
            marker.UpdatePosition(
                playerTransform.position,
                playerTransform.rotation,
                minimapScale
            );
            
            // Обновляем Fog of War
            if (fogOfWar != null)
            {
                bool isVisible = fogOfWar.IsPositionRevealed(marker.TargetTransform.position);
                marker.SetVisibility(isVisible);
            }
        }
    }
    
    public void RegisterMarker(MinimapMarker marker)
    {
        if (marker.TargetTransform != null && !markerMap.ContainsKey(marker.TargetTransform))
        {
            allMarkers.Add(marker);
            markerMap[marker.TargetTransform] = marker;
            OnMarkerAdded?.Invoke(marker);
        }
    }
    
    public void UnregisterMarker(MinimapMarker marker)
    {
        if (marker.TargetTransform != null && markerMap.ContainsKey(marker.TargetTransform))
        {
            allMarkers.Remove(marker);
            markerMap.Remove(marker.TargetTransform);
            OnMarkerRemoved?.Invoke(marker);
        }
    }
    
    public void SetMinimapScale(float scale)
    {
        minimapScale = Mathf.Clamp(scale, 0.1f, 5f);
    }
    
    public void SetOrientation(MinimapCamera.MinimapOrientation newOrientation)
    {
        orientation = newOrientation;
        if (minimapCameraComponent != null)
            minimapCameraComponent.SetOrientation(newOrientation);
    }
    
    public bool IsMapRotated() => orientation == MinimapCamera.MinimapOrientation.Rotated;
    
    public void ToggleFogOfWar(bool enabled)
    {
        enableFogOfWar = enabled;
    }
    
    private void OnDestroy()
    {
        if (minimapTexture != null)
            Destroy(minimapTexture);
    }
}

FogOfWar - Система скрытия области

public class FogOfWarSystem : MonoBehaviour
{
    [SerializeField] private float revealRadius = 20f;
    [SerializeField] private float fogDecay = 5f; // Как быстро исчезает видимость
    [SerializeField] private int textureResolution = 256;
    
    private Texture2D fogTexture;
    private float[] fogData;
    private List<Transform> revealingSources = new List<Transform>();
    
    private void Start()
    {
        // Инициализируем текстуру тумана
        fogTexture = new Texture2D(textureResolution, textureResolution, TextureFormat.R8, false);
        fogData = new float[textureResolution * textureResolution];
        
        // Заполняем данные тумана
        for (int i = 0; i < fogData.Length; i++)
        {
            fogData[i] = 0f; // 0 = видно, 1 = скрыто
        }
        
        // Находим источники света видимости
        revealingSources.AddRange(FindObjectsOfType<Transform>());
    }
    
    private void Update()
    {
        UpdateFogOfWar();
    }
    
    private void UpdateFogOfWar()
    {
        // Обновляем данные тумана
        for (int i = 0; i < fogData.Length; i++)
        {
            fogData[i] = Mathf.Min(fogData[i] + fogDecay * Time.deltaTime, 1f);
        }
        
        // Открываем области для источников видимости
        foreach (var source in revealingSources)
        {
            if (source == null) continue;
            
            RevealArea(source.position, revealRadius);
        }
        
        // Обновляем текстуру
        UpdateFogTexture();
    }
    
    private void RevealArea(Vector3 position, float radius)
    {
        int centerX = Mathf.RoundToInt((position.x / 100f + 0.5f) * textureResolution);
        int centerZ = Mathf.RoundToInt((position.z / 100f + 0.5f) * textureResolution);
        int radiusInTexels = Mathf.RoundToInt(radius / 100f * textureResolution);
        
        for (int x = centerX - radiusInTexels; x <= centerX + radiusInTexels; x++)
        {
            for (int z = centerZ - radiusInTexels; z <= centerZ + radiusInTexels; z++)
            {
                if (x >= 0 && x < textureResolution && z >= 0 && z < textureResolution)
                {
                    float distance = Vector2.Distance(new Vector2(x, z), new Vector2(centerX, centerZ));
                    if (distance <= radiusInTexels)
                    {
                        int index = z * textureResolution + x;
                        fogData[index] = 0f; // Открываем
                    }
                }
            }
        }
    }
    
    private void UpdateFogTexture()
    {
        Color[] colors = new Color[fogData.Length];
        for (int i = 0; i < fogData.Length; i++)
        {
            colors[i] = new Color(fogData[i], fogData[i], fogData[i], 1f);
        }
        
        fogTexture.SetPixels(colors);
        fogTexture.Apply();
    }
    
    public bool IsPositionRevealed(Vector3 position)
    {
        int x = Mathf.RoundToInt((position.x / 100f + 0.5f) * textureResolution);
        int z = Mathf.RoundToInt((position.z / 100f + 0.5f) * textureResolution);
        
        if (x >= 0 && x < textureResolution && z >= 0 && z < textureResolution)
        {
            int index = z * textureResolution + x;
            return fogData[index] < 0.5f;
        }
        
        return false;
    }
}

UI Setup - Настройка интерфейса

public class MinimapUI : MonoBehaviour
{
    [SerializeField] private MinimapController minimapController;
    [SerializeField] private Button zoomInButton;
    [SerializeField] private Button zoomOutButton;
    [SerializeField] private Button orientationToggleButton;
    [SerializeField] private Slider zoomSlider;
    
    private float currentZoom = 1f;
    
    private void Start()
    {
        zoomInButton?.onClick.AddListener(() => AdjustZoom(0.2f));
        zoomOutButton?.onClick.AddListener(() => AdjustZoom(-0.2f));
        orientationToggleButton?.onClick.AddListener(() => ToggleOrientation());
        zoomSlider?.onValueChanged.AddListener((value) => SetZoom(value));
    }
    
    private void AdjustZoom(float delta)
    {
        currentZoom = Mathf.Clamp(currentZoom + delta, 0.5f, 3f);
        minimapController.SetMinimapScale(currentZoom);
        zoomSlider.value = currentZoom;
    }
    
    private void SetZoom(float value)
    {
        currentZoom = value;
        minimapController.SetMinimapScale(currentZoom);
    }
    
    private void ToggleOrientation()
    {
        MinimapCamera.MinimapOrientation newOrientation = 
            minimapController.IsMapRotated() 
                ? MinimapCamera.MinimapOrientation.Static 
                : MinimapCamera.MinimapOrientation.Rotated;
        
        minimapController.SetOrientation(newOrientation);
    }
}

Пример использования

public class EnemyMarkerSpawner : MonoBehaviour
{
    [SerializeField] private GameObject enemyMarkerPrefab;
    [SerializeField] private Color enemyMarkerColor = Color.red;
    [SerializeField] private MinimapController minimapController;
    
    private void Start()
    {
        Enemy[] enemies = FindObjectsOfType<Enemy>();
        foreach (var enemy in enemies)
        {
            CreateMarkerForEnemy(enemy);
        }
    }
    
    private void CreateMarkerForEnemy(Enemy enemy)
    {
        GameObject markerObj = Instantiate(enemyMarkerPrefab);
        MinimapMarker marker = markerObj.GetComponent<MinimapMarker>();
        
        marker.Initialize(enemy.transform, enemyMarkerColor, MarkerType.Enemy);
        minimapController.RegisterMarker(marker);
    }
}

Ключевые особенности

1. RenderTexture — динамическое рендеринг в отдельную текстуру

2. Отдельная камера — orthographic камера смотрящая сверху

3. Маркеры — система маркеров для разных типов объектов

4. Два режима ориентации — вращающаяся и статичная карта

5. Масштабирование — управление зумом карты через слайдер

6. Fog of War — опциональное скрытие неоткрытых областей

7. Производительность — обновление только необходимых компонентов

Расширение возможностей

  • Клик на карте — переход к точке
  • Иконки для POI — достопримечательности на карте
  • Растеризация — рендеринг окружения в карту
  • Радиус видимости — отображение зоны видимости игрока
  • Маркеры миссии — красные маркеры целей
  • Слои карты — переключение между уровнями

Эта система обеспечивает гибкую и производительную мини-карту для любого типа игры.