first commit

This commit is contained in:
2026-06-04 18:31:31 +03:00
commit deb988bddf
143 changed files with 13144 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
using UnityEngine;
public class CameraFollow : MonoBehaviour
{
public Transform target;
public Vector3 offset = new Vector3(0f, 8f, -7f);
public float followSpeed = 8f;
private void LateUpdate()
{
if (target == null)
{
return;
}
Vector3 desiredPosition = target.position + offset;
transform.position = Vector3.Lerp(transform.position, desiredPosition, followSpeed * Time.deltaTime);
transform.rotation = Quaternion.Euler(58f, 0f, 0f);
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6ce9cf7cdc7900e2183e5ea50b33da7e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+54
View File
@@ -0,0 +1,54 @@
using UnityEngine;
using UnityEngine.UI;
public class CraftingDemoController : MonoBehaviour
{
public Inventory inventory;
public CraftingSystem craftingSystem;
public GameObject craftingPanel;
public Text hintText;
public bool addStartingResources;
private void Start()
{
if (addStartingResources && inventory != null && inventory.slots.Count == 0)
{
inventory.AddTestResources();
}
if (craftingSystem != null)
{
craftingSystem.lastMessage = "Нажмите кнопку рецепта, чтобы создать предмет.";
craftingSystem.Refresh();
}
UpdateHint();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.R) && inventory != null)
{
inventory.AddTestResources();
if (craftingSystem != null)
{
craftingSystem.lastMessage = "Инвентарь сброшен к тестовому набору ресурсов.";
craftingSystem.Refresh();
}
}
if (Input.GetKeyDown(KeyCode.C) && craftingPanel != null)
{
craftingPanel.SetActive(!craftingPanel.activeSelf);
UpdateHint();
}
}
private void UpdateHint()
{
if (hintText != null)
{
hintText.text = "WASD - движение | E - подобрать | 1-9/0 - выбрать слот | F - использовать | R - тестовые ресурсы | C - UI";
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e84b636784b7d2fc1a62fc0f6defb8fe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+156
View File
@@ -0,0 +1,156 @@
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
public class CraftingSystem : MonoBehaviour
{
public Inventory playerInventory;
public List<RecipeData> availableRecipes = new List<RecipeData>();
[TextArea(1, 3)] public string lastMessage;
public event Action OnCraftingChanged;
public bool CanCraft(RecipeData recipe)
{
if (recipe == null || playerInventory == null || recipe.result == null)
{
return false;
}
if (!recipe.unlockedByDefault || recipe.ingredients == null)
{
return false;
}
for (int i = 0; i < recipe.ingredients.Length; i++)
{
Ingredient ingredient = recipe.ingredients[i];
if (ingredient == null || ingredient.item == null || ingredient.amount <= 0)
{
return false;
}
if (!playerInventory.HasItem(ingredient.item, ingredient.amount))
{
return false;
}
}
return playerInventory.CanAddItem(recipe.result, recipe.resultAmount);
}
public void Craft(RecipeData recipe)
{
if (recipe == null)
{
lastMessage = "Рецепт не выбран.";
Refresh();
return;
}
if (playerInventory == null)
{
lastMessage = "Инвентарь не подключен.";
Refresh();
return;
}
if (!HasIngredients(recipe))
{
lastMessage = "Не хватает ресурсов: " + GetMissingIngredientsText(recipe);
Refresh();
return;
}
if (recipe.result == null || !playerInventory.CanAddItem(recipe.result, recipe.resultAmount))
{
lastMessage = "Недостаточно места в инвентаре для результата.";
Refresh();
return;
}
// Ingredients are removed only after confirming that the result can be added.
for (int i = 0; i < recipe.ingredients.Length; i++)
{
Ingredient ingredient = recipe.ingredients[i];
playerInventory.RemoveItem(ingredient.item, ingredient.amount);
}
playerInventory.AddItem(recipe.result, recipe.resultAmount);
lastMessage = "Создан предмет: " + recipe.GetResultText();
Refresh();
}
public string GetMissingIngredientsText(RecipeData recipe)
{
if (recipe == null || recipe.ingredients == null || playerInventory == null)
{
return "нет данных";
}
StringBuilder builder = new StringBuilder();
for (int i = 0; i < recipe.ingredients.Length; i++)
{
Ingredient ingredient = recipe.ingredients[i];
if (ingredient == null || ingredient.item == null)
{
continue;
}
int current = playerInventory.GetItemAmount(ingredient.item);
if (current >= ingredient.amount)
{
continue;
}
if (builder.Length > 0)
{
builder.Append(", ");
}
builder.Append(ingredient.item.itemName);
builder.Append(" ");
builder.Append(current);
builder.Append("/");
builder.Append(ingredient.amount);
}
return builder.Length == 0 ? "место в инвентаре" : builder.ToString();
}
public void SetRecipes(List<RecipeData> recipes)
{
availableRecipes = recipes ?? new List<RecipeData>();
Refresh();
}
public void Refresh()
{
OnCraftingChanged?.Invoke();
}
private bool HasIngredients(RecipeData recipe)
{
if (recipe == null || recipe.ingredients == null || playerInventory == null)
{
return false;
}
for (int i = 0; i < recipe.ingredients.Length; i++)
{
Ingredient ingredient = recipe.ingredients[i];
if (ingredient == null || ingredient.item == null || ingredient.amount <= 0)
{
return false;
}
if (!playerInventory.HasItem(ingredient.item, ingredient.amount))
{
return false;
}
}
return true;
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d65badc258c79888b99baf10cba8731b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+381
View File
@@ -0,0 +1,381 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class CraftingUI : MonoBehaviour
{
public CraftingSystem craftingSystem;
public Inventory inventory;
public Transform recipeContainer;
public GameObject recipeButtonPrefab;
public Text messageText;
public Text selectedRecipeText;
public Font uiFont;
private readonly List<GameObject> recipeButtons = new List<GameObject>();
private RecipeData selectedRecipe;
private void OnEnable()
{
if (craftingSystem != null)
{
craftingSystem.OnCraftingChanged += RefreshRecipeButtons;
}
if (inventory != null)
{
inventory.OnInventoryChanged += RefreshRecipeButtons;
}
BuildRecipeList();
}
private void OnDisable()
{
if (craftingSystem != null)
{
craftingSystem.OnCraftingChanged -= RefreshRecipeButtons;
}
if (inventory != null)
{
inventory.OnInventoryChanged -= RefreshRecipeButtons;
}
}
public void BuildRecipeList()
{
ClearRecipeButtons();
if (craftingSystem == null || recipeContainer == null || recipeButtonPrefab == null)
{
return;
}
ConfigureRecipeContainer();
for (int i = 0; i < craftingSystem.availableRecipes.Count; i++)
{
RecipeData recipe = craftingSystem.availableRecipes[i];
if (recipe == null)
{
continue;
}
GameObject buttonObject = Instantiate(recipeButtonPrefab, recipeContainer);
buttonObject.SetActive(true);
ConfigureRecipeButtonFrame(buttonObject, i);
recipeButtons.Add(buttonObject);
Button button = buttonObject.GetComponent<Button>();
if (button != null)
{
RecipeData capturedRecipe = recipe;
button.onClick.AddListener(() => OnCraftButtonClicked(capturedRecipe));
}
}
RefreshRecipeButtons();
if (selectedRecipe == null && craftingSystem.availableRecipes.Count > 0)
{
SelectRecipe(craftingSystem.availableRecipes[0]);
}
}
public void RefreshRecipeButtons()
{
if (craftingSystem == null)
{
return;
}
for (int i = 0; i < recipeButtons.Count; i++)
{
GameObject buttonObject = recipeButtons[i];
RecipeData recipe = i < craftingSystem.availableRecipes.Count ? craftingSystem.availableRecipes[i] : null;
if (buttonObject == null || recipe == null)
{
continue;
}
bool canCraft = craftingSystem.CanCraft(recipe);
Button button = buttonObject.GetComponent<Button>();
if (button != null)
{
button.interactable = canCraft;
}
Image background = buttonObject.GetComponent<Image>();
if (background != null)
{
background.color = canCraft
? new Color(0.12f, 0.24f, 0.18f, 0.98f)
: new Color(0.13f, 0.16f, 0.18f, 0.98f);
}
Image stateStripe = buttonObject.transform.Find("StateStripe")?.GetComponent<Image>();
if (stateStripe != null)
{
stateStripe.color = canCraft
? new Color(0.25f, 0.75f, 0.38f, 1f)
: new Color(0.44f, 0.48f, 0.52f, 1f);
}
Text title = buttonObject.transform.Find("Title")?.GetComponent<Text>();
if (title != null)
{
title.text = recipe.recipeName;
}
Text result = buttonObject.transform.Find("Result")?.GetComponent<Text>();
if (result != null)
{
result.text = recipe.GetResultText();
}
Text ingredients = buttonObject.transform.Find("Ingredients")?.GetComponent<Text>();
if (ingredients != null)
{
ingredients.text = recipe.GetIngredientsText();
}
Text status = buttonObject.transform.Find("Status")?.GetComponent<Text>();
if (status != null)
{
status.text = canCraft ? "ГОТОВО" : "НЕТ РЕС.";
status.color = canCraft
? new Color(0.76f, 0.96f, 0.72f)
: new Color(0.94f, 0.72f, 0.62f);
}
Text label = buttonObject.transform.Find("Label")?.GetComponent<Text>();
if (label != null)
{
label.gameObject.SetActive(false);
}
}
if (messageText != null)
{
messageText.text = craftingSystem.lastMessage;
}
if (selectedRecipe != null)
{
SelectRecipe(selectedRecipe);
}
}
public void SelectRecipe(RecipeData recipe)
{
selectedRecipe = recipe;
if (selectedRecipeText == null)
{
return;
}
if (recipe == null)
{
selectedRecipeText.text = "Рецепт не выбран";
return;
}
string missing = craftingSystem != null && !craftingSystem.CanCraft(recipe)
? "\nНе хватает: " + craftingSystem.GetMissingIngredientsText(recipe)
: "\nМожно создать сейчас.";
selectedRecipeText.text =
recipe.recipeName + ": " + recipe.description + "\n" +
"Ингредиенты: " + recipe.GetIngredientsText() + "\n" +
"Результат: " + recipe.GetResultText() + missing;
}
public void OnCraftButtonClicked(RecipeData recipe)
{
SelectRecipe(recipe);
if (craftingSystem != null)
{
craftingSystem.Craft(recipe);
}
}
public void ClearRecipeButtons()
{
recipeButtons.Clear();
if (recipeContainer == null)
{
return;
}
for (int i = recipeContainer.childCount - 1; i >= 0; i--)
{
Destroy(recipeContainer.GetChild(i).gameObject);
}
}
private void ConfigureRecipeContainer()
{
RectTransform containerRect = recipeContainer as RectTransform;
if (containerRect == null)
{
return;
}
VerticalLayoutGroup layoutGroup = recipeContainer.GetComponent<VerticalLayoutGroup>();
if (layoutGroup != null)
{
layoutGroup.enabled = false;
}
containerRect.anchorMin = new Vector2(0.5f, 0.5f);
containerRect.anchorMax = new Vector2(0.5f, 0.5f);
containerRect.pivot = new Vector2(0.5f, 0.5f);
containerRect.anchoredPosition = new Vector2(0f, -8f);
containerRect.sizeDelta = new Vector2(336f, 286f);
Image panelImage = GetComponent<Image>();
if (panelImage != null)
{
panelImage.color = new Color(0.055f, 0.070f, 0.085f, 0.97f);
}
Text title = transform.Find("Recipe Title")?.GetComponent<Text>();
if (title != null)
{
SetRect(title.rectTransform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 160f), new Vector2(336f, 30f), new Vector2(0.5f, 0.5f));
}
Text debug = transform.Find("Recipe Debug")?.GetComponent<Text>();
if (debug != null)
{
SetRect(debug.rectTransform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, -162f), new Vector2(336f, 24f), new Vector2(0.5f, 0.5f));
}
}
private void ConfigureRecipeButtonFrame(GameObject buttonObject, int index)
{
RectTransform rect = buttonObject.GetComponent<RectTransform>();
if (rect != null)
{
rect.anchorMin = new Vector2(0f, 1f);
rect.anchorMax = new Vector2(0f, 1f);
rect.pivot = new Vector2(0f, 1f);
rect.anchoredPosition = new Vector2(0f, -index * 58f);
rect.sizeDelta = new Vector2(336f, 52f);
rect.localScale = Vector3.one;
}
Image background = buttonObject.GetComponent<Image>();
if (background != null)
{
background.color = new Color(0.13f, 0.16f, 0.18f, 0.98f);
}
Text oldLabel = buttonObject.transform.Find("Label")?.GetComponent<Text>();
Font font = ResolveUIFont(oldLabel);
if (oldLabel != null)
{
oldLabel.gameObject.SetActive(false);
}
Image stateStripe = EnsureImageChild(buttonObject.transform, "StateStripe", new Color(0.44f, 0.48f, 0.52f, 1f));
SetRect(stateStripe.rectTransform, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(3f, 0f), new Vector2(6f, 44f), new Vector2(0.5f, 0.5f));
Text title = EnsureTextChild(buttonObject.transform, "Title", font, 13, FontStyle.Bold, TextAnchor.UpperLeft, Color.white);
SetRect(title.rectTransform, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(110f, -10f), new Vector2(190f, 20f), new Vector2(0.5f, 0.5f));
Text result = EnsureTextChild(buttonObject.transform, "Result", font, 12, FontStyle.Bold, TextAnchor.UpperRight, new Color(0.78f, 0.92f, 1f));
SetRect(result.rectTransform, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-58f, -10f), new Vector2(100f, 20f), new Vector2(0.5f, 0.5f));
Text ingredients = EnsureTextChild(buttonObject.transform, "Ingredients", font, 9, FontStyle.Normal, TextAnchor.LowerLeft, new Color(0.82f, 0.86f, 0.88f));
ingredients.horizontalOverflow = HorizontalWrapMode.Wrap;
ingredients.verticalOverflow = VerticalWrapMode.Truncate;
SetRect(ingredients.rectTransform, new Vector2(0f, 0f), new Vector2(0f, 0f), new Vector2(130f, 12f), new Vector2(230f, 20f), new Vector2(0.5f, 0.5f));
Text status = EnsureTextChild(buttonObject.transform, "Status", font, 10, FontStyle.Bold, TextAnchor.LowerRight, new Color(0.94f, 0.72f, 0.62f));
SetRect(status.rectTransform, new Vector2(1f, 0f), new Vector2(1f, 0f), new Vector2(-46f, 12f), new Vector2(76f, 18f), new Vector2(0.5f, 0.5f));
}
private Text EnsureTextChild(Transform parent, string childName, Font font, int fontSize, FontStyle style, TextAnchor alignment, Color color)
{
Transform existing = parent.Find(childName);
GameObject child = existing != null ? existing.gameObject : new GameObject(childName, typeof(RectTransform), typeof(Text));
child.transform.SetParent(parent, false);
Text text = child.GetComponent<Text>();
text.font = font != null ? font : ResolveUIFont(null);
text.fontSize = fontSize;
text.fontStyle = style;
text.alignment = alignment;
text.color = color;
text.raycastTarget = false;
text.horizontalOverflow = HorizontalWrapMode.Wrap;
text.verticalOverflow = VerticalWrapMode.Truncate;
child.SetActive(true);
return text;
}
private Font ResolveUIFont(Text preferredText)
{
if (uiFont != null)
{
return uiFont;
}
if (preferredText != null && preferredText.font != null)
{
uiFont = preferredText.font;
return uiFont;
}
Canvas canvas = GetComponentInParent<Canvas>();
if (canvas != null)
{
Text[] texts = canvas.GetComponentsInChildren<Text>(true);
for (int i = 0; i < texts.Length; i++)
{
if (texts[i] != null && texts[i].font != null && texts[i].font.name.ToLowerInvariant().Contains("ubuntu"))
{
uiFont = texts[i].font;
return uiFont;
}
}
for (int i = 0; i < texts.Length; i++)
{
if (texts[i] != null && texts[i].font != null)
{
uiFont = texts[i].font;
return uiFont;
}
}
}
uiFont = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
return uiFont;
}
private Image EnsureImageChild(Transform parent, string childName, Color color)
{
Transform existing = parent.Find(childName);
GameObject child = existing != null ? existing.gameObject : new GameObject(childName, typeof(RectTransform), typeof(Image));
child.transform.SetParent(parent, false);
Image image = child.GetComponent<Image>();
image.color = color;
image.raycastTarget = false;
child.SetActive(true);
return image;
}
private void SetRect(RectTransform rect, Vector2 anchorMin, Vector2 anchorMax, Vector2 anchoredPosition, Vector2 sizeDelta, Vector2 pivot)
{
rect.anchorMin = anchorMin;
rect.anchorMax = anchorMax;
rect.pivot = pivot;
rect.anchoredPosition = anchoredPosition;
rect.sizeDelta = sizeDelta;
rect.localScale = Vector3.one;
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a203fb84ce0011b84b247ff5cd148925
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+117
View File
@@ -0,0 +1,117 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class HotbarUI : MonoBehaviour
{
public Inventory inventory;
public ItemUseSystem itemUseSystem;
public Transform slotContainer;
public GameObject slotPrefab;
public int slotCount = 10;
private readonly List<GameObject> views = new List<GameObject>();
private void OnEnable()
{
if (inventory != null)
{
inventory.OnInventoryChanged += RefreshUI;
}
if (itemUseSystem != null)
{
itemUseSystem.OnSelectionChanged += RefreshUI;
}
BuildSlots();
RefreshUI();
}
private void OnDisable()
{
if (inventory != null)
{
inventory.OnInventoryChanged -= RefreshUI;
}
if (itemUseSystem != null)
{
itemUseSystem.OnSelectionChanged -= RefreshUI;
}
}
public void BuildSlots()
{
ClearSlots();
if (slotContainer == null || slotPrefab == null)
{
return;
}
for (int i = 0; i < slotCount; i++)
{
GameObject view = Instantiate(slotPrefab, slotContainer);
view.SetActive(true);
int capturedIndex = i;
Button button = view.GetComponent<Button>();
if (button != null && itemUseSystem != null)
{
button.onClick.AddListener(() => itemUseSystem.SelectSlot(capturedIndex));
}
views.Add(view);
}
}
public void RefreshUI()
{
for (int i = 0; i < views.Count; i++)
{
GameObject view = views[i];
InventorySlot slot = inventory != null ? inventory.GetSlot(i) : null;
Text label = view.transform.Find("Label")?.GetComponent<Text>();
if (label != null)
{
label.text = slot != null ? slot.item.itemName + "\nx" + slot.amount : "Пусто";
}
Text number = view.transform.Find("Number")?.GetComponent<Text>();
if (number != null)
{
number.text = i == 9 ? "0" : (i + 1).ToString();
}
Image marker = view.transform.Find("ColorMarker")?.GetComponent<Image>();
if (marker != null)
{
marker.color = slot != null ? slot.item.itemColor : new Color(0.18f, 0.20f, 0.22f, 1f);
}
Image background = view.GetComponent<Image>();
if (background != null)
{
bool selected = itemUseSystem != null && itemUseSystem.selectedSlotIndex == i;
background.color = selected
? new Color(0.82f, 0.66f, 0.30f, 0.96f)
: new Color(0.10f, 0.12f, 0.15f, 0.92f);
}
}
}
public void ClearSlots()
{
views.Clear();
if (slotContainer == null)
{
return;
}
for (int i = slotContainer.childCount - 1; i >= 0; i--)
{
Destroy(slotContainer.GetChild(i).gameObject);
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d2b8484cf82d01ee7afde9b971f40251
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+217
View File
@@ -0,0 +1,217 @@
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
public class Inventory : MonoBehaviour
{
public List<InventorySlot> slots = new List<InventorySlot>();
public int maxSlots = 32;
public List<InventorySlot> testResources = new List<InventorySlot>();
public event Action OnInventoryChanged;
public InventorySlot GetSlot(int index)
{
if (index < 0 || index >= slots.Count)
{
return null;
}
InventorySlot slot = slots[index];
return slot == null || slot.IsEmpty() ? null : slot;
}
public bool HasItem(ItemData item, int amount)
{
if (item == null || amount <= 0)
{
return false;
}
return GetItemAmount(item) >= amount;
}
public int GetItemAmount(ItemData item)
{
if (item == null)
{
return 0;
}
int total = 0;
for (int i = 0; i < slots.Count; i++)
{
InventorySlot slot = slots[i];
if (slot != null && slot.item == item && slot.amount > 0)
{
total += slot.amount;
}
}
return total;
}
public bool AddItem(ItemData item, int amount)
{
if (!CanAddItem(item, amount))
{
return false;
}
AddItemInternal(item, amount);
RaiseChanged();
return true;
}
public bool RemoveItem(ItemData item, int amount)
{
if (item == null || amount <= 0 || !HasItem(item, amount))
{
return false;
}
int remaining = amount;
for (int i = slots.Count - 1; i >= 0 && remaining > 0; i--)
{
InventorySlot slot = slots[i];
if (slot == null || slot.item != item || slot.amount <= 0)
{
continue;
}
int removed = Mathf.Min(slot.amount, remaining);
slot.amount -= removed;
remaining -= removed;
if (slot.IsEmpty())
{
slots.RemoveAt(i);
}
}
RaiseChanged();
return true;
}
public bool RemoveFromSlot(int slotIndex, int amount)
{
InventorySlot slot = GetSlot(slotIndex);
if (slot == null || amount <= 0 || slot.amount < amount)
{
return false;
}
slot.amount -= amount;
if (slot.IsEmpty())
{
slots.RemoveAt(slotIndex);
}
RaiseChanged();
return true;
}
public void Clear()
{
slots.Clear();
RaiseChanged();
}
public void AddTestResources()
{
slots.Clear();
for (int i = 0; i < testResources.Count; i++)
{
InventorySlot resource = testResources[i];
if (resource != null && resource.item != null && resource.amount > 0)
{
AddItemInternal(resource.item, resource.amount);
}
}
RaiseChanged();
}
public string GetInventoryDebugText()
{
if (slots.Count == 0)
{
return "Инвентарь пуст";
}
StringBuilder builder = new StringBuilder();
for (int i = 0; i < slots.Count; i++)
{
InventorySlot slot = slots[i];
if (slot == null || slot.IsEmpty())
{
continue;
}
builder.AppendLine(slot.GetDisplayText());
}
return builder.ToString();
}
// Used by CraftingSystem to prevent losing ingredients when the result has no room.
public bool CanAddItem(ItemData item, int amount)
{
if (item == null || amount <= 0)
{
return false;
}
int remaining = amount;
int maxStack = Mathf.Max(1, item.maxStack);
for (int i = 0; i < slots.Count; i++)
{
InventorySlot slot = slots[i];
if (slot != null && slot.item == item && slot.amount > 0 && slot.amount < maxStack)
{
remaining -= maxStack - slot.amount;
if (remaining <= 0)
{
return true;
}
}
}
int freeSlots = Mathf.Max(0, maxSlots - slots.Count);
int requiredSlots = Mathf.CeilToInt(remaining / (float)maxStack);
return requiredSlots <= freeSlots;
}
private void AddItemInternal(ItemData item, int amount)
{
int remaining = amount;
int maxStack = Mathf.Max(1, item.maxStack);
for (int i = 0; i < slots.Count && remaining > 0; i++)
{
InventorySlot slot = slots[i];
if (slot == null || slot.item != item || slot.amount >= maxStack)
{
continue;
}
int added = Mathf.Min(maxStack - slot.amount, remaining);
slot.amount += added;
remaining -= added;
}
while (remaining > 0 && slots.Count < maxSlots)
{
int added = Mathf.Min(maxStack, remaining);
slots.Add(new InventorySlot(item, added));
remaining -= added;
}
}
private void RaiseChanged()
{
OnInventoryChanged?.Invoke();
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5c51713eb2f7200edb1670500e651059
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+29
View File
@@ -0,0 +1,29 @@
using UnityEngine;
[System.Serializable]
public class InventorySlot
{
public ItemData item;
[Min(0)] public int amount;
public InventorySlot(ItemData item, int amount)
{
this.item = item;
this.amount = Mathf.Max(0, amount);
}
public bool IsEmpty()
{
return item == null || amount <= 0;
}
public string GetDisplayText()
{
if (IsEmpty())
{
return "Пусто";
}
return item.itemName + " x" + amount;
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8b0c3eec8650bed31a58bd30ca562fc9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+79
View File
@@ -0,0 +1,79 @@
using UnityEngine;
using UnityEngine.UI;
public class InventoryUI : MonoBehaviour
{
public Inventory inventory;
public Transform slotContainer;
public GameObject slotPrefab;
private void OnEnable()
{
if (inventory != null)
{
inventory.OnInventoryChanged += RefreshUI;
}
RefreshUI();
}
private void OnDisable()
{
if (inventory != null)
{
inventory.OnInventoryChanged -= RefreshUI;
}
}
public void RefreshUI()
{
if (slotContainer == null || slotPrefab == null)
{
return;
}
ClearSlots();
if (inventory == null)
{
return;
}
for (int i = 0; i < inventory.slots.Count; i++)
{
InventorySlot slot = inventory.slots[i];
if (slot == null || slot.IsEmpty())
{
continue;
}
GameObject view = Instantiate(slotPrefab, slotContainer);
view.SetActive(true);
Text label = view.GetComponentInChildren<Text>(true);
if (label != null)
{
label.text = slot.GetDisplayText();
}
Image colorMarker = view.transform.Find("ColorMarker")?.GetComponent<Image>();
if (colorMarker != null && slot.item != null)
{
colorMarker.color = slot.item.itemColor;
}
}
}
public void ClearSlots()
{
if (slotContainer == null)
{
return;
}
for (int i = slotContainer.childCount - 1; i >= 0; i--)
{
Destroy(slotContainer.GetChild(i).gameObject);
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a8094cc4f3c11692fb46e46ff90ae99f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+18
View File
@@ -0,0 +1,18 @@
using UnityEngine;
[CreateAssetMenu(fileName = "NewItem", menuName = "Crafting/ItemData")]
public class ItemData : ScriptableObject
{
[Header("Identity")]
public string itemId;
public string itemName;
[TextArea(2, 4)] public string description;
[Header("Presentation")]
public Sprite icon;
public Color itemColor = Color.white;
[Header("Stacking")]
[Min(1)] public int maxStack = 64;
public bool isResource;
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6ef36b19b094710baaaabf5321e6d693
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+131
View File
@@ -0,0 +1,131 @@
using System;
using UnityEngine;
using UnityEngine.UI;
public class ItemUseSystem : MonoBehaviour
{
public Inventory inventory;
public CraftingSystem craftingSystem;
public PlayerMovement playerMovement;
public Transform playerTransform;
public Material torchLightMaterial;
public Text statusText;
public int selectedSlotIndex;
public int selectableSlotCount = 10;
public event Action OnSelectionChanged;
public void SelectSlot(int slotIndex)
{
selectedSlotIndex = Mathf.Clamp(slotIndex, 0, Mathf.Max(0, selectableSlotCount - 1));
OnSelectionChanged?.Invoke();
}
private void Update()
{
for (int i = 0; i < selectableSlotCount && i < 10; i++)
{
KeyCode keyCode = i == 9 ? KeyCode.Alpha0 : (KeyCode)((int)KeyCode.Alpha1 + i);
if (Input.GetKeyDown(keyCode))
{
SelectSlot(i);
}
}
if (Input.GetKeyDown(KeyCode.F))
{
UseSelectedItem();
}
}
public void UseSelectedItem()
{
if (inventory == null)
{
return;
}
InventorySlot slot = inventory.GetSlot(selectedSlotIndex);
if (slot == null || slot.item == null)
{
SetMessage("В выбранном слоте нет предмета.");
return;
}
string itemId = slot.item.itemId;
if (itemId == "bandage")
{
inventory.RemoveFromSlot(selectedSlotIndex, 1);
SetMessage("Использован бинт: здоровье восстановлено.");
}
else if (itemId == "torch")
{
inventory.RemoveFromSlot(selectedSlotIndex, 1);
PlaceTorch();
SetMessage("Факел установлен рядом с игроком.");
}
else if (itemId == "magic_amulet")
{
if (playerMovement != null)
{
playerMovement.ApplySpeedBuff(1.6f, 12f);
}
SetMessage("Амулет активирован: скорость увеличена на 12 секунд.");
}
else if (itemId == "axe")
{
SetMessage("Топор готов к использованию. Им можно рубить древесину в расширенной версии.");
}
else if (itemId == "pickaxe")
{
SetMessage("Кирка готова к использованию. Им можно добывать камень и руду в расширенной версии.");
}
else
{
SetMessage("Этот предмет нельзя использовать напрямую.");
}
OnSelectionChanged?.Invoke();
}
private void PlaceTorch()
{
if (playerTransform == null)
{
return;
}
GameObject torch = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
torch.name = "Placed Torch";
torch.transform.position = playerTransform.position + playerTransform.forward * 0.9f + Vector3.up * 0.45f;
torch.transform.localScale = new Vector3(0.12f, 0.45f, 0.12f);
if (torchLightMaterial != null)
{
torch.GetComponent<Renderer>().material = torchLightMaterial;
}
GameObject lightObject = new GameObject("Torch Light");
lightObject.transform.SetParent(torch.transform, false);
lightObject.transform.localPosition = new Vector3(0f, 0.75f, 0f);
Light light = lightObject.AddComponent<Light>();
light.type = LightType.Point;
light.range = 5f;
light.intensity = 2.2f;
light.color = new Color(1f, 0.65f, 0.28f);
}
private void SetMessage(string message)
{
if (craftingSystem != null)
{
craftingSystem.lastMessage = message;
craftingSystem.Refresh();
}
if (statusText != null)
{
statusText.text = message;
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d737d15fbf341fd9582d9f0fa3366b7e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+68
View File
@@ -0,0 +1,68 @@
using UnityEngine;
using UnityEngine.UI;
public class PlayerInteractor : MonoBehaviour
{
public Inventory inventory;
public CraftingSystem craftingSystem;
public float interactRadius = 1.8f;
public Text interactionText;
private ResourcePickup nearestPickup;
private void Update()
{
FindNearestPickup();
if (interactionText != null)
{
interactionText.text = nearestPickup != null
? "E - подобрать: " + nearestPickup.GetPickupText()
: "Подойдите к ресурсу и нажмите E";
}
if (Input.GetKeyDown(KeyCode.E) && nearestPickup != null)
{
if (nearestPickup.TryCollect(inventory))
{
SetMessage("Подобрано: " + nearestPickup.GetPickupText());
}
else
{
SetMessage("Инвентарь заполнен.");
}
}
}
private void FindNearestPickup()
{
nearestPickup = null;
Collider[] hits = Physics.OverlapSphere(transform.position, interactRadius);
float bestDistance = float.MaxValue;
for (int i = 0; i < hits.Length; i++)
{
ResourcePickup pickup = hits[i].GetComponentInParent<ResourcePickup>();
if (pickup == null)
{
continue;
}
float distance = Vector3.Distance(transform.position, pickup.transform.position);
if (distance < bestDistance)
{
bestDistance = distance;
nearestPickup = pickup;
}
}
}
private void SetMessage(string message)
{
if (craftingSystem != null)
{
craftingSystem.lastMessage = message;
craftingSystem.Refresh();
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a226e29ff6807deea808680d3168d748
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+58
View File
@@ -0,0 +1,58 @@
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
public float moveSpeed = 5f;
public float rotationSpeed = 12f;
private CharacterController characterController;
private float speedMultiplier = 1f;
private float buffTimer;
private void Awake()
{
characterController = GetComponent<CharacterController>();
}
private void Update()
{
Vector3 input = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical"));
if (input.sqrMagnitude > 1f)
{
input.Normalize();
}
Vector3 move = input * moveSpeed * speedMultiplier;
move.y = Physics.gravity.y * 0.2f;
if (characterController != null)
{
characterController.Move(move * Time.deltaTime);
}
else
{
transform.position += move * Time.deltaTime;
}
if (input.sqrMagnitude > 0.01f)
{
Quaternion targetRotation = Quaternion.LookRotation(input, Vector3.up);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
}
if (buffTimer > 0f)
{
buffTimer -= Time.deltaTime;
if (buffTimer <= 0f)
{
speedMultiplier = 1f;
}
}
}
public void ApplySpeedBuff(float multiplier, float duration)
{
speedMultiplier = Mathf.Max(1f, multiplier);
buffTimer = Mathf.Max(0f, duration);
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 48b98ce024e7a0859972c6cb9936944d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+60
View File
@@ -0,0 +1,60 @@
using System.Text;
using UnityEngine;
[System.Serializable]
public class Ingredient
{
public ItemData item;
[Min(1)] public int amount = 1;
}
[CreateAssetMenu(fileName = "NewRecipe", menuName = "Crafting/RecipeData")]
public class RecipeData : ScriptableObject
{
public string recipeId;
public string recipeName;
[TextArea(2, 4)] public string description;
public Ingredient[] ingredients;
public ItemData result;
[Min(1)] public int resultAmount = 1;
public bool unlockedByDefault = true;
public string GetIngredientsText()
{
if (ingredients == null || ingredients.Length == 0)
{
return "Без ингредиентов";
}
StringBuilder builder = new StringBuilder();
for (int i = 0; i < ingredients.Length; i++)
{
Ingredient ingredient = ingredients[i];
if (ingredient == null || ingredient.item == null)
{
continue;
}
if (builder.Length > 0)
{
builder.Append(", ");
}
builder.Append(ingredient.item.itemName);
builder.Append(" x");
builder.Append(ingredient.amount);
}
return builder.Length == 0 ? "Без ингредиентов" : builder.ToString();
}
public string GetResultText()
{
if (result == null)
{
return "Нет результата";
}
return result.itemName + " x" + resultAmount;
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6df24091ce261531692311bf89ece59a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+77
View File
@@ -0,0 +1,77 @@
using UnityEngine;
public class ResourcePickup : MonoBehaviour
{
public ItemData item;
public int amount = 1;
public float respawnSeconds = 8f;
private Renderer[] renderers;
private Collider[] colliders;
private bool available = true;
private float respawnTimer;
private void Awake()
{
renderers = GetComponentsInChildren<Renderer>();
colliders = GetComponentsInChildren<Collider>();
}
private void Update()
{
transform.Rotate(0f, 45f * Time.deltaTime, 0f, Space.World);
if (available)
{
return;
}
respawnTimer -= Time.deltaTime;
if (respawnTimer <= 0f)
{
SetAvailable(true);
}
}
public bool TryCollect(Inventory inventory)
{
if (!available || inventory == null || item == null || amount <= 0)
{
return false;
}
if (!inventory.AddItem(item, amount))
{
return false;
}
SetAvailable(false);
return true;
}
public string GetPickupText()
{
if (item == null)
{
return "Неизвестный ресурс";
}
return item.itemName + " x" + amount;
}
private void SetAvailable(bool value)
{
available = value;
respawnTimer = value ? 0f : respawnSeconds;
for (int i = 0; i < renderers.Length; i++)
{
renderers[i].enabled = value;
}
for (int i = 0; i < colliders.Length; i++)
{
colliders[i].enabled = value;
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f26d71e91c354e59b8afd6844bd07a7d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: