Раст
by TurboMeteor431285 lines45.7 KB
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<Chunk> _activeChunks = new List<Chunk>();
private Dictionary<Vector2Int, Chunk> _chunkMap = new Dictionary<Vector2Int, Chunk>();
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>();
_player.Initialize(inventoryUI, _guideSystem);
Camera.main.GetComponent<CameraFollow>().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<Chunk> toRemove = new List<Chunk>();
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<int, int, BlockType> 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<int, int, BlockType> 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<int, int, BlockType> 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<int, int, BlockType> 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<Rigidbody2D>();
_cam = Camera.main;
_inventory = new Inventory(hotbarSize + 41);
blockHighlight.SetActive(false);
_mobile = FindObjectOfType<MobileControls>();
}
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<Sprite>($"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<InventorySlot> _slots = new List<InventorySlot>();
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<InventorySlot>();
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<Recipe> _recipes = new List<Recipe>();
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, int> { { BlockType.Wood, 3 } }, BlockType.Planks, 1));
_recipes.Add(new Recipe("Каменная кирка", new Dictionary<BlockType, int> { { BlockType.Cobblestone, 3 } }, BlockType.Cobblestone, 1));
_recipes.Add(new Recipe("Деревянный меч", new Dictionary<BlockType, int> { { BlockType.Wood, 2 } }, BlockType.Planks, 1));
_recipes.Add(new Recipe("Сундук", new Dictionary<BlockType, int> { { BlockType.Wood, 8 } }, BlockType.Chest, 1));
_recipes.Add(new Recipe("Верстак", new Dictionary<BlockType, int> { { BlockType.Wood, 4 } }, BlockType.CraftingTable, 1));
_recipes.Add(new Recipe("Стекло", new Dictionary<BlockType, int> { { BlockType.Sand, 3 } }, BlockType.Glass, 3));
_recipes.Add(new Recipe("Кирпич", new Dictionary<BlockType, int> { { 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<RecipeUI>().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<Sprite>($"Items/{t}");
}
public class Recipe
{
public string Name;
public Dictionary<BlockType, int> Ingredients;
public BlockType Result;
public int ResultCount;
public Recipe(string n, Dictionary<BlockType, int> 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<Sprite>($"Items/{r.Result}");
GetComponent<Button>().onClick.AddListener(() => _craftingUI.SelectRecipe(_recipe));
}
}
// ============================================================
// 7. КВЕСТЫ
// ============================================================
public class QuestManager
{
private List<Quest> _activeQuests = new List<Quest>();
private List<Quest> _completedQuests = new List<Quest>();
private List<Quest> _availableQuests = new List<Quest>();
private int _questIndex;
public QuestManager()
{
_availableQuests.Add(new Quest("Сбор древесины", "Собери 15 дерева", QuestType.Gather, BlockType.Wood, 15, new Item(BlockType.Planks, 5), 10));
_availableQuests.Add(new Quest("Добыча камня", "Добудь 30 камня", QuestType.Gather, BlockType.Stone, 30, new Item(BlockType.Cobblestone, 10), 20));
_availableQuests.Add(new Quest("Исследование подземелий", "Найди 3 подземелья", QuestType.Explore, BlockType.Chest, 3, new Item(BlockType.GoldOre, 5), 50));
_availableQuests.Add(new Quest("Создание верстака", "Создай верстак", QuestType.Craft, BlockType.CraftingTable, 1, new Item(BlockType.Planks, 10), 15));
_activeQuests.Add(_availableQuests[0]); _questIndex = 1;
}
public void UpdateQuests(Inventory inv)
{
for (int i = _activeQuests.Count - 1; i >= 0; i--)
{
var q = _activeQuests[i];
if (!q.IsComplete)
{
q.CheckProgress(inv);
if (q.IsComplete)
{
inv?.AddItem(q.Reward);
_completedQuests.Add(q);
_activeQuests.RemoveAt(i);
GameController.Instance?.GetGuideSystem()?.ShowMessage($"Квест выполнен: {q.Name}!");
if (_questIndex < _availableQuests.Count)
{
_activeQuests.Add(_availableQuests[_questIndex]);
GameController.Instance?.GetGuideSystem()?.ShowMessage($"Новый квест: {_availableQuests[_questIndex].Name}");
_questIndex++;
}
}
}
}
if (_activeQuests.Count == 0 && _questIndex < _availableQuests.Count)
{
_activeQuests.Add(_availableQuests[_questIndex]);
GameController.Instance?.GetGuideSystem()?.ShowMessage($"Новый квест: {_availableQuests[_questIndex].Name}");
_questIndex++;
}
}
public List<Quest> GetActiveQuests() => _activeQuests;
public List<Quest> GetCompletedQuests() => _completedQuests;
public void Save(BinaryWriter w)
{
w.Write(_questIndex);
w.Write(_activeQuests.Count);
foreach (var q in _activeQuests) q.Save(w);
w.Write(_completedQuests.Count);
foreach (var q in _completedQuests) q.Save(w);
}
public void Load(BinaryReader r)
{
_questIndex = r.ReadInt32();
int ac = r.ReadInt32();
_activeQuests.Clear();
for (int i = 0; i < ac; i++) { var q = new Quest("", "", QuestType.Gather, BlockType.Air, 0, null, 0); q.Load(r); _activeQuests.Add(q); }
int cc = r.ReadInt32();
_completedQuests.Clear();
for (int i = 0; i < cc; i++) { var q = new Quest("", "", QuestType.Gather, BlockType.Air, 0, null, 0); q.Load(r); _completedQuests.Add(q); }
}
}
public class Quest
{
public string Name, Description;
public QuestType Type;
public BlockType Target;
public int Required, Progress, RewardXP;
public Item Reward;
public bool IsComplete;
public Quest(string n, string d, QuestType t, BlockType target, int req, Item reward, int xp)
{ Name = n; Description = d; Type = t; Target = target; Required = req; Reward = reward; RewardXP = xp; Progress = 0; IsComplete = false; }
public void CheckProgress(Inventory inv)
{
if (IsComplete) return;
switch (Type)
{
case QuestType.Gather: Progress = Mathf.Min(Progress + 1, Required); break;
case QuestType.Explore: break;
case QuestType.Craft: break;
}
if (Progress >= Required) IsComplete = true;
}
public void Save(BinaryWriter w)
{
w.Write(Name); w.Write(Description); w.Write((int)Type);
w.Write((int)Target); w.Write(Required); w.Write(Progress);
w.Write(IsComplete); w.Write(RewardXP);
w.Write(Reward != null);
if (Reward != null) { w.Write((int)Reward.Type); w.Write(Reward.Count); }
}
public void Load(BinaryReader r)
{
Name = r.ReadString(); Description = r.ReadString();
Type = (QuestType)r.ReadInt32(); Target = (BlockType)r.ReadInt32();
Required = r.ReadInt32(); Progress = r.ReadInt32();
IsComplete = r.ReadBoolean(); RewardXP = r.ReadInt32();
if (r.ReadBoolean()) Reward = new Item((BlockType)r.ReadInt32(), r.ReadInt32());
}
}
public enum QuestType { Gather, Kill, Explore, Craft }
public class QuestUI : MonoBehaviour
{
public GameObject panel;
public Transform questContainer;
public GameObject questPrefab;
public Text titleText;
private bool _isOpen;
public void Initialize() { panel.SetActive(false); }
public void Toggle()
{
_isOpen = !_isOpen;
panel.SetActive(_isOpen);
if (_isOpen) UpdateUI();
}
void UpdateUI()
{
foreach (Transform c in questContainer) Destroy(c.gameObject);
var qs = GameController.Instance?.GetQuestManager()?.GetActiveQuests();
if (qs == null) return;
foreach (var q in qs)
{
GameObject go = Instantiate(questPrefab, questContainer);
go.GetComponent<QuestEntry>().SetQuest(q);
}
}
}
public class QuestEntry : MonoBehaviour
{
public Text nameText;
public Text progressText;
public Text rewardText;
public void SetQuest(Quest q)
{
nameText.text = q.Name;
progressText.text = $"{q.Progress}/{q.Required}";
rewardText.text = $"Награда: {q.Reward?.Type} x{q.Reward?.Count ?? 0}";
}
}
// ============================================================
// 8. ПРОВОДНИК
// ============================================================
public class GuideSystem
{
private List<string> _messages = new List<string>();
private float _lastMessageTime;
private const float DELAY = 3f;
public GuideSystem()
{
_messages.Add("Добро пожаловать! Я буду помогать.");
_messages.Add("ЛКМ - добыча, ПКМ - установка.");
_messages.Add("E - инвентарь, C - крафт, Q - квесты.");
_messages.Add("Собирайте ресурсы и создавайте инструменты.");
_messages.Add("Исследуйте мир и находите подземелья!");
_messages.Add("R - сохранить, L - загрузить.");
}
public void ShowMessage(string msg)
{
if (Time.time - _lastMessageTime < DELAY) return;
_lastMessageTime = Time.time;
_messages.Insert(0, msg);
if (_messages.Count > 50) _messages.RemoveAt(_messages.Count - 1);
if (GuideUI.Instance != null) GuideUI.Instance.ShowMessage(msg);
}
public string GetRandomTip()
{
string[] tips = {
"Создайте деревянную кирку!", "Ищите руды глубоко под землёй.",
"Дерево - отличный ресурс.", "Стройте дом для защиты.",
"Крафтите улучшенные инструменты."
};
return tips[UnityEngine.Random.Range(0, tips.Length)];
}
public List<string> GetMessages() => _messages;
}
public class GuideUI : MonoBehaviour
{
public static GuideUI Instance;
public GameObject panel;
public Text messageText;
public Text tipText;
public Transform messageContainer;
public GameObject messagePrefab;
public Button tipButton;
public Button closeButton;
private GuideSystem _guide;
private bool _isOpen;
private float _msgTimer;
void Awake() { Instance = this; }
public void Initialize(GuideSystem g)
{
_guide = g;
panel.SetActive(false);
tipButton.onClick.AddListener(ShowTip);
closeButton.onClick.AddListener(() => Toggle());
}
public void Toggle()
{
_isOpen = !_isOpen;
panel.SetActive(_isOpen);
if (_isOpen) UpdateUI();
}
public void ShowMessage(string msg)
{
messageText.text = msg;
_msgTimer = 4f;
GameObject go = Instantiate(messagePrefab, messageContainer);
go.GetComponent<Text>().text = "▶ " + msg;
if (messageContainer.childCount > 20) Destroy(messageContainer.GetChild(0).gameObject);
}
void Update()
{
if (_msgTimer > 0) { _msgTimer -= Time.deltaTime; if (_msgTimer <= 0) messageText.text = "Готов..."; }
}
void ShowTip() { string t = _guide.GetRandomTip(); tipText.text = "💡 " + t; ShowMessage(t); }
void UpdateUI()
{
foreach (Transform c in messageContainer) Destroy(c.gameObject);
var msgs = _guide.GetMessages();
for (int i = msgs.Count - 1; i >= 0; i--)
{ GameObject go = Instantiate(messagePrefab, messageContainer); go.GetComponent<Text>().text = "▶ " + msgs[i]; }
}
}
// ============================================================
// 9. МОБИЛЬНОЕ УПРАВЛЕНИЕ
// ============================================================
public class MobileControls : MonoBehaviour
{
public Joystick movementJoystick;
public Button jumpButton, breakButton, placeButton, buildButton;
public Button inventoryButton, craftButton, questButton, guideButton;
private float _h;
private bool _jump, _break, _place, _build;
void Start()
{
jumpButton.onClick.AddListener(() => _jump = true);
breakButton.onClick.AddListener(() => _break = true);
placeButton.onClick.AddListener(() => _place = true);
buildButton.onClick.AddListener(() => _build = true);
inventoryButton.onClick.AddListener(() => GameController.Instance?.GetInventoryUI()?.Toggle());
craftButton.onClick.AddListener(() => GameController.Instance?.GetCraftingUI()?.Toggle());
questButton.onClick.AddListener(() => GameController.Instance?.GetQuestUI()?.Toggle());
guideButton.onClick.AddListener(() => GameController.Instance?.GetGuideUI()?.Toggle());
}
void Update()
{
_h = movementJoystick.Horizontal;
if (_jump) { _jump = false; }
if (_break) { _break = false; }
if (_place) { _place = false; }
if (_build) { _build = false; }
}
public float GetHorizontal() => _h;
public bool GetJump() => _jump;
public bool GetBreak() => _break;
public bool GetPlace() => _place;
public bool GetBuildToggle() => _build;
}
// ============================================================
// 10. ДЕНЬ/НОЧЬ
// ============================================================
public class DayNightCycle
{
private float _time;
private float _dayLength;
private Light2D _globalLight;
public DayNightCycle(float len)
{
_dayLength = len;
_time = 0;
_globalLight = GameObject.FindObjectOfType<Light2D>();
}
public void Tick()
{
_time += Time.deltaTime;
if (_time > _dayLength) _time = 0;
if (_globalLight != null)
{
float i = GetLightIntensity();
_globalLight.intensity = i * 0.8f + 0.2f;
_globalLight.color = i > 0.7f ? new Color(1, 0.95f, 0.8f) : (i > 0.4f ? new Color(0.8f, 0.7f, 0.9f) : new Color(0.2f, 0.1f, 0.3f));
}
}
public float GetLightIntensity()
{
float n = _time / _dayLength;
return Mathf.Clamp01(Mathf.Sin(n * 2 * Mathf.PI) * 0.6f + 0.4f);
}
public float GetTime() => _time;
public void SetTime(float t) => _time = t;
}
// ============================================================
// 11. ЧАНКИ
// ============================================================
public class Chunk
{
public Vector2Int Position;
private GameObject _go;
private Tilemap _tilemap;
public Chunk(Vector2Int pos) { Position = pos; }
public void Load(GameController ctrl)
{
_go = new GameObject($"Chunk_{Position.x}_{Position.y}");
_go.transform.position = new Vector3(Position.x * 16, Position.y * 16, 0);
_tilemap = _go.AddComponent<Tilemap>();
_go.AddComponent<TilemapRenderer>();
for (int x = 0; x < 16; x++)
for (int y = 0; y < 16; y++)
{
BlockType t = ctrl.GetBlock(Position.x * 16 + x, Position.y * 16 + y);
if (t != BlockType.Air) _tilemap.SetTile(new Vector3Int(x, y, 0), ctrl.GetTileBase(t));
}
}
public void Unload() { if (_go != null) GameObject.Destroy(_go); }
public void Tick() { }
}
// ============================================================
// 12. КАМЕРА
// ============================================================
public class CameraFollow : MonoBehaviour
{
private Transform _target;
public Vector3 offset = new Vector3(0, 0, -10);
public float smoothness = 0.125f;
public void SetTarget(Transform t) { _target = t; }
void LateUpdate()
{
if (_target == null) return;
transform.position = Vector3.Lerp(transform.position, _target.position + offset, smoothness);
}
}
// ============================================================
// 13. ТИПЫ БЛОКОВ
// ============================================================
public enum BlockType
{
Air = 0, Grass = 1, Dirt = 2, Stone = 3, Wood = 4, Leaf = 5,
IronOre = 6, GoldOre = 7, Chest = 8, CraftingTable = 9,
Furnace = 10, Torch = 11, Sand = 12, Water = 13, Flower = 14,
Planks = 15, Cobblestone = 16, Glass = 17, Brick = 18
}
// ============================================================
// 14. ДЖОЙСТИК (СТАНДАРТНЫЙ)
// ============================================================
public class Joystick : MonoBehaviour, IDragHandler, IPointerUpHandler, IPointerDownHandler
{
public Image background;
public Image handle;
public float handleRange = 1;
private Vector2 _input = Vector2.zero;
public float Horizontal => _input.x;
public float Vertical => _input.y;
public void OnPointerDown(PointerEventData e) { OnDrag(e); }
public void OnPointerUp(PointerEventData e) { _input = Vector2.zero; handle.rectTransform.anchoredPosition = Vector2.zero; }
public void OnDrag(PointerEventData e)
{
Vector2 pos = Vector2.zero;
RectTransformUtility.ScreenPointToLocalPointInRectangle(background.rectTransform, e.position, e.pressEventCamera, out pos);
pos.x /= background.rectTransform.sizeDelta.x;
pos.y /= background.rectTransform.sizeDelta.y;
_input = Vector2.ClampMagnitude(pos, 1);
handle.rectTransform.anchoredPosition = _input * handleRange * 50;
}
}Game Source: Раст
Creator: TurboMeteor43
Libraries: none
Complexity: complex (1285 lines, 45.7 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: game-turbometeor43" to link back to the original. Then publish at arcadelab.ai/publish.