using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using UnityEngine; using UnityEngine.Tilemaps; using UnityEngine.UI; using UnityEngine.EventSystems; using UnityEngine.SceneManagement; using UnityEngine.Rendering.Universal; // ============================================================ // 1. ГЛАВНЫЙ КОНТРОЛЛЕР // ============================================================ public class GameController : MonoBehaviour { public static GameController Instance; [Header("Мир")] public int worldWidth = 120; public int worldHeight = 80; public int seed = 42; public Tilemap groundTilemap; public Tilemap wallTilemap; public Tilemap decorationTilemap; public Tilemap waterTilemap; public TileBase[] tiles; [Header("Игрок")] public GameObject playerPrefab; public Transform playerSpawn; [Header("UI")] public InventoryUI inventoryUI; public QuestUI questUI; public CraftingUI craftingUI; public GuideUI guideUI; public MobileControls mobileControls; public Text debugText; public GameObject pauseMenu; [Header("Настройки")] public int renderDistance = 15; public float dayLength = 600f; public int maxInventorySize = 50; public float autoSaveInterval = 60f; private Player _player; private WorldGenerator _worldGenerator; private QuestManager _questManager; private DayNightCycle _dayNightCycle; private GuideSystem _guideSystem; private List _activeChunks = new List(); private Dictionary _chunkMap = new Dictionary(); private Vector2Int _currentChunk; private float _tickTimer; private float _autoSaveTimer; private bool _isPaused; void Awake() { Instance = this; Application.targetFrameRate = 60; Screen.sleepTimeout = SleepTimeout.NeverSleep; } void Start() { InitializeSystems(); GenerateWorld(); SpawnPlayer(); StartCoroutine(UpdateChunks()); _guideSystem.ShowMessage("Добро пожаловать! Я проводник. Начни с добычи древесины."); } void Update() { if (_isPaused) return; _tickTimer += Time.deltaTime; if (_tickTimer >= 0.5f) { _tickTimer = 0; Tick(); } _autoSaveTimer += Time.deltaTime; if (_autoSaveTimer >= autoSaveInterval) { _autoSaveTimer = 0; SaveWorld(); } if (Input.GetKeyDown(KeyCode.E) || Input.GetKeyDown(KeyCode.Tab)) inventoryUI.Toggle(); if (Input.GetKeyDown(KeyCode.C)) craftingUI.Toggle(); if (Input.GetKeyDown(KeyCode.Q)) questUI.Toggle(); if (Input.GetKeyDown(KeyCode.G)) guideUI.Toggle(); if (Input.GetKeyDown(KeyCode.F3)) debugText.enabled = !debugText.enabled; if (Input.GetKeyDown(KeyCode.Escape)) TogglePause(); if (Input.GetKeyDown(KeyCode.R)) SaveWorld(); if (Input.GetKeyDown(KeyCode.L)) LoadWorld(); } void InitializeSystems() { _worldGenerator = new WorldGenerator(seed, worldWidth, worldHeight); _questManager = new QuestManager(); _dayNightCycle = new DayNightCycle(dayLength); _guideSystem = new GuideSystem(); inventoryUI.Initialize(maxInventorySize); craftingUI.Initialize(); questUI.Initialize(); guideUI.Initialize(_guideSystem); if (Application.isMobilePlatform && mobileControls != null) mobileControls.gameObject.SetActive(true); } void GenerateWorld() { for (int x = 0; x < worldWidth; x++) for (int y = 0; y < worldHeight; y++) SetBlock(x, y, _worldGenerator.GetBlockType(x, y)); _worldGenerator.GenerateTrees(SetBlock); _worldGenerator.GenerateOres(SetBlock); _worldGenerator.GenerateDungeons(SetBlock); _worldGenerator.GenerateWater(SetBlock); } void SpawnPlayer() { Vector3 spawnPos = playerSpawn.position; for (int y = 0; y < 20; y++) { Vector3 testPos = spawnPos + Vector3.up * y; if (!IsSolid(Mathf.RoundToInt(testPos.x), Mathf.RoundToInt(testPos.y))) { spawnPos = testPos; break; } } GameObject obj = Instantiate(playerPrefab, spawnPos, Quaternion.identity); _player = obj.GetComponent(); _player.Initialize(inventoryUI, _guideSystem); Camera.main.GetComponent().SetTarget(obj.transform); } void Tick() { _dayNightCycle.Tick(); _questManager.UpdateQuests(_player?.GetInventory()); foreach (var chunk in _activeChunks) chunk.Tick(); } void TogglePause() { _isPaused = !_isPaused; pauseMenu.SetActive(_isPaused); Time.timeScale = _isPaused ? 0 : 1; } IEnumerator UpdateChunks() { while (true) { if (!_isPaused && _player != null) { Vector2Int pc = GetChunkPosition(_player.transform.position); if (pc != _currentChunk) { _currentChunk = pc; UpdateActiveChunks(); } } yield return new WaitForSeconds(0.5f); } } void UpdateActiveChunks() { List toRemove = new List(); foreach (var kvp in _chunkMap) if (Vector2Int.Distance(kvp.Key, _currentChunk) > renderDistance) toRemove.Add(kvp.Value); foreach (var chunk in toRemove) { chunk.Unload(); _chunkMap.Remove(chunk.Position); _activeChunks.Remove(chunk); } for (int x = -renderDistance; x <= renderDistance; x++) for (int y = -renderDistance; y <= renderDistance; y++) { Vector2Int pos = _currentChunk + new Vector2Int(x, y); if (!_chunkMap.ContainsKey(pos)) { Chunk chunk = new Chunk(pos); chunk.Load(this); _chunkMap[pos] = chunk; _activeChunks.Add(chunk); } } } Vector2Int GetChunkPosition(Vector3 worldPos) => new Vector2Int(Mathf.FloorToInt(worldPos.x / 16), Mathf.FloorToInt(worldPos.y / 16)); public void SetBlock(int x, int y, BlockType type) { if (x < 0 || x >= worldWidth || y < 0 || y >= worldHeight) return; Vector3Int pos = new Vector3Int(x, y, 0); Tilemap target = GetTilemap(type); target.SetTile(pos, GetTileBase(type)); } Tilemap GetTilemap(BlockType type) { switch (type) { case BlockType.Grass: case BlockType.Dirt: case BlockType.Stone: case BlockType.Sand: return groundTilemap; case BlockType.Wood: case BlockType.Leaf: case BlockType.Flower: return decorationTilemap; case BlockType.Water: return waterTilemap; default: return wallTilemap; } } TileBase GetTileBase(BlockType type) { int idx = (int)type; return (idx >= 0 && idx < tiles.Length) ? tiles[idx] : null; } public BlockType GetBlock(int x, int y) { if (x < 0 || x >= worldWidth || y < 0 || y >= worldHeight) return BlockType.Air; Vector3Int pos = new Vector3Int(x, y, 0); TileBase t = groundTilemap.GetTile(pos) ?? decorationTilemap.GetTile(pos) ?? waterTilemap.GetTile(pos); if (t == null) return BlockType.Air; for (int i = 0; i < tiles.Length; i++) if (tiles[i] == t) return (BlockType)i; return BlockType.Air; } public bool IsSolid(int x, int y) { BlockType t = GetBlock(x, y); return t != BlockType.Air && t != BlockType.Water && t != BlockType.Flower; } public Player GetPlayer() => _player; public QuestManager GetQuestManager() => _questManager; public GuideSystem GetGuideSystem() => _guideSystem; public int GetWorldWidth() => worldWidth; public int GetWorldHeight() => worldHeight; public InventoryUI GetInventoryUI() => inventoryUI; public CraftingUI GetCraftingUI() => craftingUI; public QuestUI GetQuestUI() => questUI; public GuideUI GetGuideUI() => guideUI; public bool IsPaused() => _isPaused; public void SaveWorld() { try { string path = Application.persistentDataPath + "/world.save"; using BinaryWriter w = new BinaryWriter(File.Open(path, FileMode.Create)); w.Write(worldWidth); w.Write(worldHeight); w.Write(seed); w.Write(_dayNightCycle.GetTime()); for (int x = 0; x < worldWidth; x++) for (int y = 0; y < worldHeight; y++) w.Write((int)GetBlock(x, y)); _player?.Save(w); _questManager.Save(w); inventoryUI.Save(w); _guideSystem.ShowMessage("Мир сохранён!"); } catch (Exception e) { Debug.LogError("Ошибка сохранения: " + e.Message); } } public void LoadWorld() { try { string path = Application.persistentDataPath + "/world.save"; if (!File.Exists(path)) { _guideSystem.ShowMessage("Сохранение не найдено"); return; } using BinaryReader r = new BinaryReader(File.Open(path, FileMode.Open)); int w = r.ReadInt32(), h = r.ReadInt32(), s = r.ReadInt32(); float time = r.ReadSingle(); if (w != worldWidth || h != worldHeight) { _guideSystem.ShowMessage("Размеры не совпадают"); return; } for (int x = 0; x < worldWidth; x++) for (int y = 0; y < worldHeight; y++) SetBlock(x, y, (BlockType)r.ReadInt32()); _player?.Load(r); _questManager.Load(r); inventoryUI.Load(r); _dayNightCycle.SetTime(time); _guideSystem.ShowMessage("Мир загружен!"); } catch (Exception e) { Debug.LogError("Ошибка загрузки: " + e.Message); } } } // ============================================================ // 2. ГЕНЕРАТОР МИРА // ============================================================ public class WorldGenerator { private int _seed, _width, _height; private System.Random _random; private float[,] _heightMap, _caveMap, _moistureMap; public WorldGenerator(int seed, int width, int height) { _seed = seed; _width = width; _height = height; _random = new System.Random(seed); _heightMap = new float[width, height]; _caveMap = new float[width, height]; _moistureMap = new float[width, height]; for (int x = 0; x < width; x++) { float v = 0, s = 1, a = 1; for (int i = 0; i < 6; i++) { v += Mathf.PerlinNoise((x + seed) * s / 80f, seed * s / 80f) * a; s *= 2.2f; a *= 0.45f; } for (int y = 0; y < height; y++) _heightMap[x, y] = v * height / 3.5f + height / 4f; } for (int x = 0; x < width; x++) for (int y = 0; y < height; y++) { _caveMap[x, y] = Mathf.PerlinNoise((x + seed * 2) / 35f, (y + seed * 3) / 35f); _moistureMap[x, y] = Mathf.PerlinNoise((x + seed * 5) / 60f, (y + seed * 7) / 60f); } } public BlockType GetBlockType(int x, int y) { float h = _heightMap[x, y], c = _caveMap[x, y], m = _moistureMap[x, y]; if (y < _height / 4f && m > 0.6f) return BlockType.Water; if (y > h + 12) return BlockType.Air; if (y < h - 30) return c > 0.45f ? BlockType.Air : BlockType.Stone; if (y < h - 25) return BlockType.Stone; if (y < h) return BlockType.Dirt; if (Mathf.Abs(y - h) < 1f) return m > 0.5f ? BlockType.Grass : (m > 0.3f ? BlockType.Sand : BlockType.Dirt); if (Mathf.Abs(y - h) < 2f && m > 0.4f && _random.NextDouble() < 0.1f) return BlockType.Flower; return BlockType.Air; } public void GenerateTrees(Action set) { for (int x = 3; x < _width - 3; x++) for (int y = 3; y < _height - 3; y++) if (GetBlockType(x, y) == BlockType.Grass && _random.NextDouble() < 0.025f && _moistureMap[x, y] > 0.3f) { int height = _random.Next(5, 9); for (int i = 0; i < height; i++) if (y + i < _height) set(x, y + i, BlockType.Wood); for (int dx = -3; dx <= 3; dx++) for (int dy = -2; dy <= 2; dy++) if (Mathf.Abs(dx) + Mathf.Abs(dy) <= 4) { int lx = x + dx, ly = y + height - 3 + dy; if (lx >= 0 && lx < _width && ly >= 0 && ly < _height) set(lx, ly, BlockType.Leaf); } } } public void GenerateOres(Action set) { for (int i = 0; i < 80; i++) { int x = _random.Next(5, _width - 5), y = _random.Next(10, _height / 2); if (GetBlockType(x, y) == BlockType.Stone) { set(x, y, BlockType.IronOre); for (int j = 0; j < 3; j++) { int dx = _random.Next(-2, 3), dy = _random.Next(-2, 3); if (GetBlockType(x + dx, y + dy) == BlockType.Stone) set(x + dx, y + dy, BlockType.IronOre); } } } for (int i = 0; i < 20; i++) { int x = _random.Next(10, _width - 10), y = _random.Next(5, _height / 3); if (GetBlockType(x, y) == BlockType.Stone) set(x, y, BlockType.GoldOre); } } public void GenerateDungeons(Action set) { for (int i = 0; i < 5; i++) { int x = _random.Next(15, _width - 15), y = _random.Next(5, _height / 3); for (int dx = -3; dx <= 3; dx++) for (int dy = -3; dy <= 3; dy++) if (Mathf.Abs(dx) == 3 || Mathf.Abs(dy) == 3) set(x + dx, y + dy, BlockType.Stone); set(x, y, BlockType.Chest); if (_random.NextDouble() < 0.5f) set(x + 1, y, BlockType.Chest); } } public void GenerateWater(Action set) { for (int x = 0; x < _width; x++) for (int y = 0; y < _height / 4; y++) if (GetBlockType(x, y) == BlockType.Air && _moistureMap[x, y] > 0.7f) set(x, y, BlockType.Water); } } // ============================================================ // 3. ИГРОК // ============================================================ public class Player : MonoBehaviour { [Header("Движение")] public float speed = 4f; public float jumpForce = 7f; public float gravity = -25f; public int reachDistance = 5; public float fallResetY = -50f; [Header("Инвентарь")] public int hotbarSize = 9; public GameObject blockHighlight; public SpriteRenderer highlightRenderer; private Rigidbody2D _rb; private Vector2 _velocity; private bool _isGrounded; private Inventory _inventory; private InventoryUI _inventoryUI; private GuideSystem _guideSystem; private int _selectedSlot; private Vector3Int _targetBlock; private Camera _cam; private bool _isBuilding; private float _hInput; private bool _jumpInput, _breakInput, _placeInput; private MobileControls _mobile; void Start() { _rb = GetComponent(); _cam = Camera.main; _inventory = new Inventory(hotbarSize + 41); blockHighlight.SetActive(false); _mobile = FindObjectOfType(); } public void Initialize(InventoryUI ui, GuideSystem guide) { _inventoryUI = ui; _guideSystem = guide; _inventoryUI.SetInventory(_inventory); _inventoryUI.SetPlayer(this); _inventory.AddItem(new Item(BlockType.Wood, 10)); _inventory.AddItem(new Item(BlockType.Dirt, 20)); _inventory.AddItem(new Item(BlockType.Stone, 5)); _inventory.AddItem(new Item(BlockType.Grass, 3)); } void Update() { if (GameController.Instance == null || GameController.Instance.IsPaused()) return; if (transform.position.y < fallResetY) { transform.position = new Vector3(0, 20, 0); _guideSystem?.ShowMessage("Возврат из бездны!"); } HandleInput(); HandleMovement(); HandleInteraction(); HandleHotbar(); HandleBuildMode(); UpdateHighlight(); } void HandleInput() { if (_mobile != null && _mobile.gameObject.activeSelf) { _hInput = _mobile.GetHorizontal(); _jumpInput = _mobile.GetJump(); _breakInput = _mobile.GetBreak(); _placeInput = _mobile.GetPlace(); } else { _hInput = Input.GetAxis("Horizontal"); _jumpInput = Input.GetButtonDown("Jump"); _breakInput = Input.GetMouseButtonDown(0); _placeInput = Input.GetMouseButtonDown(1); } } void HandleMovement() { _velocity.x = _hInput * speed; if (_jumpInput && _isGrounded) _velocity.y = jumpForce; if (!_isGrounded) _velocity.y += gravity * Time.deltaTime; _rb.velocity = _velocity; RaycastHit2D hit = Physics2D.Raycast(transform.position, Vector2.down, 0.7f); _isGrounded = hit.collider != null && hit.collider.gameObject.layer == LayerMask.NameToLayer("Ground"); if (_hInput != 0) transform.localScale = new Vector3(Mathf.Sign(_hInput), 1, 1); } void HandleInteraction() { Vector2 mousePos = _cam.ScreenToWorldPoint(Input.mousePosition); Vector2 dir = (mousePos - (Vector2)transform.position).normalized; if (_isBuilding) { _targetBlock = GetTargetBlock(mousePos); if (_targetBlock != null) { blockHighlight.transform.position = _targetBlock + Vector3.one * 0.5f; blockHighlight.SetActive(true); if (_placeInput) PlaceBlock(); } return; } for (int i = 0; i < reachDistance; i++) { Vector2 pos = (Vector2)transform.position + dir * (i + 0.5f); Vector3Int gp = new Vector3Int(Mathf.RoundToInt(pos.x), Mathf.RoundToInt(pos.y), 0); if (GameController.Instance.IsSolid(gp.x, gp.y)) { _targetBlock = gp; blockHighlight.transform.position = gp + Vector3.one * 0.5f; blockHighlight.SetActive(true); if (_breakInput) BreakBlock(); else if (_placeInput) PlaceBlockAdjacent(); return; } } blockHighlight.SetActive(false); } Vector3Int GetTargetBlock(Vector2 mousePos) => Vector2.Distance(mousePos, transform.position) < reachDistance ? new Vector3Int(Mathf.RoundToInt(mousePos.x), Mathf.RoundToInt(mousePos.y), 0) : Vector3Int.zero; void BreakBlock() { if (_targetBlock == null) return; BlockType t = GameController.Instance.GetBlock(_targetBlock.x, _targetBlock.y); if (t != BlockType.Air && t != BlockType.Water) { GameController.Instance.SetBlock(_targetBlock.x, _targetBlock.y, BlockType.Air); _inventory.AddItem(new Item(t, 1)); foreach (var q in GameController.Instance.GetQuestManager().GetActiveQuests()) if (q.Type == QuestType.Gather && q.Target == t) q.Progress++; _guideSystem?.ShowMessage($"Добыто: {t}"); } } void PlaceBlockAdjacent() { if (_targetBlock == null) return; Vector3Int pos = _targetBlock; Vector2 dir = (Vector2)_targetBlock - (Vector2)transform.position; if (Mathf.Abs(dir.x) > Mathf.Abs(dir.y)) pos.x += dir.x > 0 ? -1 : 1; else pos.y += dir.y > 0 ? -1 : 1; PlaceBlockAt(pos); } void PlaceBlock() { if (_targetBlock != null) PlaceBlockAt(_targetBlock); } void PlaceBlockAt(Vector3Int pos) { if (pos.x < 0 || pos.x >= GameController.Instance.GetWorldWidth() || pos.y < 0 || pos.y >= GameController.Instance.GetWorldHeight()) return; if (!GameController.Instance.IsSolid(pos.x, pos.y)) { Item item = _inventory.GetItem(_selectedSlot); if (item != null && item.Count > 0 && item.Type != BlockType.Air && item.Type != BlockType.Water) { GameController.Instance.SetBlock(pos.x, pos.y, item.Type); item.Count--; if (item.Count <= 0) _inventory.SetItem(_selectedSlot, null); _inventoryUI.UpdateUI(); _guideSystem?.ShowMessage($"Установлен: {item.Type}"); } } } void UpdateHighlight() { if (!blockHighlight.activeSelf) return; highlightRenderer.color = _isBuilding ? new Color(0, 1, 0, 0.3f) : new Color(1, 1, 1, 0.2f); } void HandleHotbar() { for (int i = 0; i < hotbarSize; i++) if (Input.GetKeyDown(KeyCode.Alpha1 + i)) _selectedSlot = i; float scroll = Input.GetAxis("Mouse ScrollWheel"); if (scroll != 0) { _selectedSlot += (int)Mathf.Sign(scroll); _selectedSlot = (_selectedSlot + hotbarSize) % hotbarSize; } } void HandleBuildMode() { if (Input.GetKeyDown(KeyCode.B) || (_mobile != null && _mobile.GetBuildToggle())) _isBuilding = !_isBuilding; } public Inventory GetInventory() => _inventory; public void Save(BinaryWriter w) { w.Write(transform.position.x); w.Write(transform.position.y); w.Write(_selectedSlot); _inventory.Save(w); } public void Load(BinaryReader r) { transform.position = new Vector3(r.ReadSingle(), r.ReadSingle(), 0); _selectedSlot = r.ReadInt32(); _inventory.Load(r); _inventoryUI.UpdateUI(); } } // ============================================================ // 4. ИНВЕНТАРЬ // ============================================================ [System.Serializable] public class Item { public BlockType Type; public int Count; public Item(BlockType t, int c) { Type = t; Count = c; } public Sprite GetSprite() => Resources.Load($"Items/{Type}"); } public class Inventory { private Item[] _items; public int Length => _items.Length; public Inventory(int size) { _items = new Item[size]; } public Item GetItem(int idx) { return (idx >= 0 && idx < _items.Length) ? _items[idx] : null; } public void SetItem(int idx, Item item) { if (idx >= 0 && idx < _items.Length) _items[idx] = item; } public void AddItem(Item item) { for (int i = 0; i < _items.Length; i++) if (_items[i] != null && _items[i].Type == item.Type) { _items[i].Count += item.Count; return; } for (int i = 0; i < _items.Length; i++) if (_items[i] == null) { _items[i] = new Item(item.Type, item.Count); return; } } public int GetItemCount(BlockType type) { int c = 0; foreach (var item in _items) if (item != null && item.Type == type) c += item.Count; return c; } public void RemoveItem(BlockType type, int count) { for (int i = 0; i < _items.Length && count > 0; i++) { if (_items[i] != null && _items[i].Type == type) { int take = Mathf.Min(_items[i].Count, count); _items[i].Count -= take; count -= take; if (_items[i].Count <= 0) _items[i] = null; } } } public void Save(BinaryWriter w) { w.Write(_items.Length); foreach (var item in _items) { if (item == null) { w.Write(-1); continue; } w.Write((int)item.Type); w.Write(item.Count); } } public void Load(BinaryReader r) { int size = r.ReadInt32(); _items = new Item[size]; for (int i = 0; i < size; i++) { int type = r.ReadInt32(); if (type < 0) continue; _items[i] = new Item((BlockType)type, r.ReadInt32()); } } } // ============================================================ // 5. UI ИНВЕНТАРЯ // ============================================================ public class InventoryUI : MonoBehaviour { public GameObject slotPrefab; public Transform slotContainer; public Transform hotbarContainer; public Text titleText; public GameObject panel; private Inventory _inventory; private Player _player; private List _slots = new List(); private bool _isOpen; public void Initialize(int maxSize) { panel.SetActive(false); for (int i = 0; i < maxSize; i++) { Transform parent = i < 9 ? hotbarContainer : slotContainer; GameObject go = Instantiate(slotPrefab, parent); var slot = go.GetComponent(); slot.SetIndex(i); _slots.Add(slot); } } public void SetInventory(Inventory inv) { _inventory = inv; UpdateUI(); } public void SetPlayer(Player p) { _player = p; } public void UpdateUI() { for (int i = 0; i < _slots.Count; i++) _slots[i].SetItem(_inventory?.GetItem(i)); } public void Toggle() { _isOpen = !_isOpen; panel.SetActive(_isOpen); if (_isOpen) UpdateUI(); } public void Save(BinaryWriter w) { _inventory?.Save(w); } public void Load(BinaryReader r) { _inventory?.Load(r); UpdateUI(); } } public class InventorySlot : MonoBehaviour, IPointerClickHandler { public Image icon; public Text countText; public Image highlight; private int _index; private Item _item; public void SetIndex(int idx) { _index = idx; } public void SetItem(Item item) { _item = item; if (item == null) { icon.sprite = null; icon.color = Color.clear; countText.text = ""; return; } icon.sprite = item.GetSprite(); icon.color = Color.white; countText.text = item.Count > 1 ? item.Count.ToString() : ""; } public void OnPointerClick(PointerEventData e) { } } // ============================================================ // 6. КРАФТ // ============================================================ public class CraftingUI : MonoBehaviour { public GameObject panel; public Transform recipeContainer; public GameObject recipePrefab; public Button craftButton; public Image resultImage; public Text resultName; public Text resultCount; public Text ingredientsText; private Inventory _inventory; private List _recipes = new List(); private Recipe _selectedRecipe; private bool _isOpen; void Start() { panel.SetActive(false); LoadRecipes(); craftButton.onClick.AddListener(Craft); } void LoadRecipes() { _recipes.Add(new Recipe("Деревянная кирка", new Dictionary { { BlockType.Wood, 3 } }, BlockType.Planks, 1)); _recipes.Add(new Recipe("Каменная кирка", new Dictionary { { BlockType.Cobblestone, 3 } }, BlockType.Cobblestone, 1)); _recipes.Add(new Recipe("Деревянный меч", new Dictionary { { BlockType.Wood, 2 } }, BlockType.Planks, 1)); _recipes.Add(new Recipe("Сундук", new Dictionary { { BlockType.Wood, 8 } }, BlockType.Chest, 1)); _recipes.Add(new Recipe("Верстак", new Dictionary { { BlockType.Wood, 4 } }, BlockType.CraftingTable, 1)); _recipes.Add(new Recipe("Стекло", new Dictionary { { BlockType.Sand, 3 } }, BlockType.Glass, 3)); _recipes.Add(new Recipe("Кирпич", new Dictionary { { BlockType.Dirt, 2 } }, BlockType.Brick, 4)); } public void Initialize() { _inventory = GameController.Instance?.GetPlayer()?.GetInventory(); UpdateUI(); } public void Toggle() { _isOpen = !_isOpen; panel.SetActive(_isOpen); if (_isOpen) UpdateUI(); } void UpdateUI() { foreach (Transform c in recipeContainer) Destroy(c.gameObject); foreach (var r in _recipes) { GameObject go = Instantiate(recipePrefab, recipeContainer); go.GetComponent().SetRecipe(r, this); } UpdateCraftButton(); } public void SelectRecipe(Recipe r) { _selectedRecipe = r; resultImage.sprite = GetSprite(r.Result); resultName.text = r.Name; resultCount.text = $"x{r.ResultCount}"; ingredientsText.text = string.Join("\n", r.Ingredients.Select(kvp => $"{kvp.Key}: {kvp.Value}")); UpdateCraftButton(); } void UpdateCraftButton() { if (_selectedRecipe == null || _inventory == null) { craftButton.interactable = false; return; } bool can = true; foreach (var kvp in _selectedRecipe.Ingredients) if (_inventory.GetItemCount(kvp.Key) < kvp.Value) { can = false; break; } craftButton.interactable = can; } void Craft() { if (_selectedRecipe == null || _inventory == null) return; foreach (var kvp in _selectedRecipe.Ingredients) _inventory.RemoveItem(kvp.Key, kvp.Value); _inventory.AddItem(new Item(_selectedRecipe.Result, _selectedRecipe.ResultCount)); GameController.Instance?.GetGuideSystem()?.ShowMessage($"Создано: {_selectedRecipe.Name}"); UpdateUI(); GameController.Instance?.GetInventoryUI()?.UpdateUI(); } Sprite GetSprite(BlockType t) => Resources.Load($"Items/{t}"); } public class Recipe { public string Name; public Dictionary Ingredients; public BlockType Result; public int ResultCount; public Recipe(string n, Dictionary ing, BlockType r, int c) { Name = n; Ingredients = ing; Result = r; ResultCount = c; } } public class RecipeUI : MonoBehaviour { public Text nameText; public Image iconImage; private Recipe _recipe; private CraftingUI _craftingUI; public void SetRecipe(Recipe r, CraftingUI ui) { _recipe = r; _craftingUI = ui; nameText.text = r.Name; iconImage.sprite = Resources.Load($"Items/{r.Result}"); GetComponent