fix чать инвенторя

This commit is contained in:
jze9
2026-04-16 01:51:46 +05:00
parent 057f5d3d1e
commit 6c3f72385e
35 changed files with 18160 additions and 14313 deletions

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 91470f1965cf83a4a8c49831ec2f7691
NativeFormatImporter:
guid: 037877a8b07e2d84e8a888f939cfbab6
folderAsset: yes
DefaultImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,428 @@
{
"name": "PlayerControls",
"maps": [
{
"name": "Gameplay",
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"actions": [
{
"name": "Move",
"type": "Value",
"id": "10000000-0000-0000-0000-000000000001",
"expectedControlType": "Vector2",
"processors": "",
"interactions": "",
"initialStateCheck": true
},
{
"name": "Look",
"type": "Value",
"id": "10000000-0000-0000-0000-000000000002",
"expectedControlType": "Vector2",
"processors": "",
"interactions": "",
"initialStateCheck": true
},
{
"name": "Jump",
"type": "Button",
"id": "10000000-0000-0000-0000-000000000003",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "Run",
"type": "Button",
"id": "10000000-0000-0000-0000-000000000004",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "Crouch",
"type": "Button",
"id": "10000000-0000-0000-0000-000000000005",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "Pickup",
"type": "Button",
"id": "10000000-0000-0000-0000-000000000006",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "Drop",
"type": "Button",
"id": "10000000-0000-0000-0000-000000000007",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "Hotbar0",
"type": "Button",
"id": "20000000-0000-0000-0000-000000000001",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "Hotbar1",
"type": "Button",
"id": "20000000-0000-0000-0000-000000000002",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "Hotbar2",
"type": "Button",
"id": "20000000-0000-0000-0000-000000000003",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "Hotbar3",
"type": "Button",
"id": "20000000-0000-0000-0000-000000000004",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "Hotbar4",
"type": "Button",
"id": "20000000-0000-0000-0000-000000000005",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "Hotbar5",
"type": "Button",
"id": "20000000-0000-0000-0000-000000000006",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "Hotbar6",
"type": "Button",
"id": "20000000-0000-0000-0000-000000000007",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "Hotbar7",
"type": "Button",
"id": "20000000-0000-0000-0000-000000000008",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "Hotbar8",
"type": "Button",
"id": "20000000-0000-0000-0000-000000000009",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
}
],
"bindings": [
{
"name": "WASD",
"id": "30000000-0000-0000-0000-000000000001",
"path": "2DVector",
"interactions": "",
"processors": "",
"groups": "",
"action": "Move",
"isComposite": true,
"isPartOfComposite": false
},
{
"name": "up",
"id": "30000000-0000-0000-0000-000000000002",
"path": "<Keyboard>/w",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "Move",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "down",
"id": "30000000-0000-0000-0000-000000000003",
"path": "<Keyboard>/s",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "Move",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "left",
"id": "30000000-0000-0000-0000-000000000004",
"path": "<Keyboard>/a",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "Move",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "right",
"id": "30000000-0000-0000-0000-000000000005",
"path": "<Keyboard>/d",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "Move",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "",
"id": "30000000-0000-0000-0000-000000000010",
"path": "<Mouse>/delta",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "Look",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "30000000-0000-0000-0000-000000000020",
"path": "<Keyboard>/space",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "Jump",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "30000000-0000-0000-0000-000000000021",
"path": "<Keyboard>/leftShift",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "Run",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "30000000-0000-0000-0000-000000000022",
"path": "<Keyboard>/c",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "Crouch",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "30000000-0000-0000-0000-000000000023",
"path": "<Keyboard>/e",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "Pickup",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "30000000-0000-0000-0000-000000000024",
"path": "<Keyboard>/q",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "Drop",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "40000000-0000-0000-0000-000000000001",
"path": "<Keyboard>/1",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "Hotbar0",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "40000000-0000-0000-0000-000000000002",
"path": "<Keyboard>/2",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "Hotbar1",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "40000000-0000-0000-0000-000000000003",
"path": "<Keyboard>/3",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "Hotbar2",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "40000000-0000-0000-0000-000000000004",
"path": "<Keyboard>/4",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "Hotbar3",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "40000000-0000-0000-0000-000000000005",
"path": "<Keyboard>/5",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "Hotbar4",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "40000000-0000-0000-0000-000000000006",
"path": "<Keyboard>/6",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "Hotbar5",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "40000000-0000-0000-0000-000000000007",
"path": "<Keyboard>/7",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "Hotbar6",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "40000000-0000-0000-0000-000000000008",
"path": "<Keyboard>/8",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "Hotbar7",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "40000000-0000-0000-0000-000000000009",
"path": "<Keyboard>/9",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "Hotbar8",
"isComposite": false,
"isPartOfComposite": false
}
]
},
{
"name": "UI",
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"actions": [
{
"name": "ToggleInventory",
"type": "Button",
"id": "50000000-0000-0000-0000-000000000001",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "CloseInventory",
"type": "Button",
"id": "50000000-0000-0000-0000-000000000002",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
}
],
"bindings": [
{
"name": "",
"id": "60000000-0000-0000-0000-000000000001",
"path": "<Keyboard>/tab",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "ToggleInventory",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "60000000-0000-0000-0000-000000000002",
"path": "<Keyboard>/escape",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "CloseInventory",
"isComposite": false,
"isPartOfComposite": false
}
]
}
],
"controlSchemes": [
{
"name": "KeyboardMouse",
"restrictToSuppliedDevices": false,
"bindingGroup": "KeyboardMouse",
"devices": [
{
"devicePath": "<Keyboard>",
"isOptional": false,
"isOR": false
},
{
"devicePath": "<Mouse>",
"isOptional": false,
"isOR": false
}
]
}
]
}

View File

@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 7d782555f7e02f84ca2502d52361f19a
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 8404be70184654265930450def6a9037, type: 3}
generateWrapperCode: 0
wrapperCodePath:
wrapperClassName:
wrapperCodeNamespace:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ebd3754781a0d3e48a34311a699ad38b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,135 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &4546282257700047937
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1948853633481286021}
- component: {fileID: 6658874805278007762}
- component: {fileID: 3056228376157587138}
m_Layer: 0
m_Name: NewItem
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1948853633481286021
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4546282257700047937}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 7434756968248499828}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &6658874805278007762
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4546282257700047937}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: b8ac54fbf9ed99e45a83319443952a91, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::WorldItem
itemData: {fileID: 0}
amount: 1
pickupDelay: 0
interactRange: 2.5
--- !u!135 &3056228376157587138
SphereCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4546282257700047937}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 1
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
m_Radius: 0.5
m_Center: {x: 0, y: 0, z: 0}
--- !u!1001 &1495056769490629797
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 1948853633481286021}
m_Modifications:
- target: {fileID: 282714382156279063, guid: 4fcbba5b8725e254f98c052c4db89e02, type: 3}
propertyPath: m_Name
value: Visual
objectReference: {fileID: 0}
- target: {fileID: 8327738622413892305, guid: 4fcbba5b8725e254f98c052c4db89e02, type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8327738622413892305, guid: 4fcbba5b8725e254f98c052c4db89e02, type: 3}
propertyPath: m_LocalPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8327738622413892305, guid: 4fcbba5b8725e254f98c052c4db89e02, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8327738622413892305, guid: 4fcbba5b8725e254f98c052c4db89e02, type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 8327738622413892305, guid: 4fcbba5b8725e254f98c052c4db89e02, type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8327738622413892305, guid: 4fcbba5b8725e254f98c052c4db89e02, type: 3}
propertyPath: m_LocalRotation.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8327738622413892305, guid: 4fcbba5b8725e254f98c052c4db89e02, type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8327738622413892305, guid: 4fcbba5b8725e254f98c052c4db89e02, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8327738622413892305, guid: 4fcbba5b8725e254f98c052c4db89e02, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8327738622413892305, guid: 4fcbba5b8725e254f98c052c4db89e02, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 4fcbba5b8725e254f98c052c4db89e02, type: 3}
--- !u!4 &7434756968248499828 stripped
Transform:
m_CorrespondingSourceObject: {fileID: 8327738622413892305, guid: 4fcbba5b8725e254f98c052c4db89e02, type: 3}
m_PrefabInstance: {fileID: 1495056769490629797}
m_PrefabAsset: {fileID: 0}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 223ec2dc359ac0845957ab0b29284414
guid: 9f444e9048146434087c3db3550617f9
PrefabImporter:
externalObjects: {}
userData:

View File

@@ -12,9 +12,9 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 5741b63764436354ebad192c9b71f06c, type: 3}
m_Name: NewItem
m_EditorClassIdentifier: Assembly-CSharp::ItemData
id: 2
id: d28a3db9-17bd-489d-9caf-739005a7c780
itemName: NewItem
description: 23
description: 123
type: 5
modelPrefab: {fileID: 282714382156279063, guid: 4fcbba5b8725e254f98c052c4db89e02, type: 3}
icon: {fileID: 21300000, guid: 0115b2d990086ae4cbc31baf7c64e680, type: 3}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: e9765c2fbc4980049940f96e9b0f70dc
guid: 20cff4d8f616b1140b2264c6f049ba8a
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000

View File

@@ -1,31 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5741b63764436354ebad192c9b71f06c, type: 3}
m_Name: test
m_EditorClassIdentifier: Assembly-CSharp::ItemData
id: 1
itemName: test
description: 123
type: 0
modelPrefab: {fileID: 282714382156279063, guid: 4fcbba5b8725e254f98c052c4db89e02, type: 3}
icon: {fileID: 21300000, guid: 0115b2d990086ae4cbc31baf7c64e680, type: 3}
maxStack: 64
isCraftable: 0
craftingRecipe:
ingredients: []
amounts:
resultId:
resultAmount: 1
weight: 1
value: 1
isDroppable: 1
isStackable: 1

View File

@@ -78,51 +78,33 @@ public class InventoryManager : MonoBehaviour
void Start()
{
// Инициализация слотов
// Слоты созданы editor-инструментом и назначены в Inspector
if (slots == null || slots.Length == 0)
{
Debug.LogWarning("Slots not assigned! Assign them in Inspector or use InventoryUIBuilder.");
}
Debug.LogWarning("[InventoryManager] Slots not assigned! Run Tools > Setup Complete Scene first.");
else
{
foreach (var slot in slots)
{
slot?.UpdateSlotUI();
}
}
foreach (var slot in slots) slot?.UpdateSlotUI();
if (hotbarSlots != null)
{
foreach (var slot in hotbarSlots)
{
slot?.UpdateSlotUI();
}
}
foreach (var slot in hotbarSlots) slot?.UpdateSlotUI();
// Скрыть инвентарь по умолчанию
if (inventoryPanel != null)
inventoryPanel.SetActive(false);
// Убедимся что события существуют
if (InventoryEvents.Instance == null)
// Подписка на ввод
if (PlayerInput.Instance != null)
{
var eventsGO = new GameObject("InventoryEvents");
eventsGO.AddComponent<InventoryEvents>();
PlayerInput.Instance.OnToggleInventory += ToggleInventory;
PlayerInput.Instance.OnDropPressed += DropSelectedItem;
PlayerInput.Instance.OnHotbarSelect += SelectHotbarSlot;
}
else
{
Debug.LogWarning("[InventoryManager] PlayerInput not found. Run Tools > Setup Complete Scene first.");
}
// Попробуем загрузить сохранённый инвентарь
// Загрузить сохранённый инвентарь (слоты уже существуют)
LoadFromFile();
// Ensure PlayerInput exists and subscribe to input events
if (PlayerInput.Instance == null)
{
var go = new GameObject("PlayerInput");
go.AddComponent<PlayerInput>();
}
PlayerInput.Instance.OnToggleInventory += ToggleInventory;
PlayerInput.Instance.OnDropPressed += DropSelectedItem;
PlayerInput.Instance.OnHotbarSelect += SelectHotbarSlot;
}
@@ -135,14 +117,18 @@ public class InventoryManager : MonoBehaviour
if (!wasActive)
{
// Открытие инвентаря
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
InventoryEvents.Instance?.OnInventoryOpened?.Invoke();
if (PlayerInput.Instance != null)
PlayerInput.Instance.Enabled = false;
}
else
{
// Закрытие инвентаря
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
InventoryEvents.Instance?.OnInventoryClosed?.Invoke();
if (PlayerInput.Instance != null)
PlayerInput.Instance.Enabled = true;
@@ -559,8 +545,10 @@ public class InventoryManager : MonoBehaviour
}
/// <summary>
/// Сохранить инвентарь в файл (Application.persistentDataPath)
/// Сохранить инвентарь в файл (Application.persistentDataPath).
/// Доступно через правый клик на компоненте в Inspector во время Play Mode.
/// </summary>
[ContextMenu("Save Inventory")]
public void SaveToFile()
{
try
@@ -577,8 +565,11 @@ public class InventoryManager : MonoBehaviour
}
/// <summary>
/// Загрузить инвентарь из файла (если есть)
/// Загрузить инвентарь из файла (если есть).
/// Вызывается из InventoryUIBuilder после создания слотов.
/// Доступно через правый клик на компоненте в Inspector во время Play Mode.
/// </summary>
[ContextMenu("Load Inventory")]
public void LoadFromFile()
{
try

View File

@@ -1,302 +1,133 @@
using UnityEngine;
using UnityEditor;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using TMPro;
public class InventorySceneSetup
/// <summary>
/// Tools > Setup Complete Scene — создаёт всё до запуска игры (editor-time).
/// Canvas инвентаря помещается внутрь Player.
/// InventoryManager получает все ссылки сразу.
/// </summary>
public static class InventorySceneSetup
{
[MenuItem("Tools/Create Player")]
public static void CreatePlayer()
[MenuItem("Tools/Setup Complete Scene")]
public static void SetupCompleteScene()
{
if (Object.FindObjectOfType<InventoryManager>() != null)
{
EditorUtility.DisplayDialog(
"Сцена уже настроена",
"InventoryManager уже существует. Удалите существующие объекты сцены перед повторной настройкой.",
"OK");
return;
}
// ── EventSystem ───────────────────────────────────────────────────────
if (Object.FindObjectOfType<EventSystem>() == null)
{
var esGO = new GameObject("EventSystem");
esGO.AddComponent<EventSystem>();
esGO.AddComponent<StandaloneInputModule>();
}
// ── InventoryEvents ───────────────────────────────────────────────────
new GameObject("InventoryEvents").AddComponent<InventoryEvents>();
// ── InventoryManager ──────────────────────────────────────────────────
var imGO = new GameObject("InventoryManager");
var im = imGO.AddComponent<InventoryManager>();
imGO.AddComponent<InventoryExampleItems>();
// ── Player ────────────────────────────────────────────────────────────
var player = CreatePlayerGO();
// ── InventoryCanvas (дочерний объект Player) ──────────────────────────
var canvasGO = new GameObject("InventoryCanvas");
canvasGO.transform.SetParent(player.transform, false);
var canvas = canvasGO.AddComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvas.sortingOrder = 100;
canvasGO.AddComponent<GraphicRaycaster>();
var uiBuilder = canvasGO.AddComponent<InventoryUIBuilder>();
// Строим весь UI прямо сейчас, до запуска игры
uiBuilder.BuildInventoryUI();
// Назначаем ссылки в InventoryManager
im.slots = uiBuilder.GetInventorySlots();
im.hotbarSlots = uiBuilder.GetHotbarSlots();
im.inventoryPanel = uiBuilder.inventoryPanelRect != null ? uiBuilder.inventoryPanelRect.gameObject : null;
im.inventoryCanvas = canvas;
// Скрыть панель инвентаря по умолчанию
if (im.inventoryPanel != null)
im.inventoryPanel.SetActive(false);
// ── PlayerInput (дочерний объект Player) ──────────────────────────────
var piGO = new GameObject("PlayerInput");
piGO.transform.SetParent(player.transform, false);
var pi = piGO.AddComponent<PlayerInput>();
var inputActions = AssetDatabase.LoadAssetAtPath<UnityEngine.InputSystem.InputActionAsset>(
"Assets/Input/PlayerControls.inputactions");
if (inputActions != null)
pi.actions = inputActions;
else
Debug.LogWarning("[Setup] Назначьте Assets/Input/PlayerControls.inputactions на компонент PlayerInput вручную.");
// ── HandEquipSystem ───────────────────────────────────────────────────
player.AddComponent<HandEquipSystem>();
Selection.activeGameObject = player;
Debug.Log("[Setup] Готово! Назначьте кость руки (handBone) в HandEquipSystem на Player.");
}
// ── Вспомогательные методы ────────────────────────────────────────────────
static GameObject CreatePlayerGO()
{
var go = new GameObject("Player");
var cc = go.AddComponent<CharacterController>();
var cc = go.AddComponent<CharacterController>();
cc.height = 1.8f;
cc.radius = 0.5f;
cc.center = new Vector3(0, 0.9f, 0);
var character = go.AddComponent<charecter>();
character.walkSpeed = 5f;
character.runSpeed = 9f;
character.crouchSpeed = 2.5f;
character.jumpHeight = 1.5f;
character.gravity = -9.81f;
var ch = go.AddComponent<charecter>();
ch.walkSpeed = 5f;
ch.runSpeed = 9f;
ch.crouchSpeed = 2.5f;
ch.jumpHeight = 1.5f;
ch.gravity = -9.81f;
GameObject body = GameObject.CreatePrimitive(PrimitiveType.Capsule);
// Визуальная капсула
var body = GameObject.CreatePrimitive(PrimitiveType.Capsule);
body.name = "Body";
body.transform.SetParent(go.transform);
body.transform.localPosition = Vector3.zero;
GameObject.Destroy(body.GetComponent<Collider>());
body.transform.SetParent(go.transform, false);
Object.DestroyImmediate(body.GetComponent<Collider>());
GameObject head = new GameObject("Head");
head.transform.SetParent(go.transform);
// Голова
var head = new GameObject("Head");
head.transform.SetParent(go.transform, false);
head.transform.localPosition = new Vector3(0, 0.8f, 0);
character.head = head.transform;
ch.head = head.transform;
GameObject camObj = new GameObject("PlayerCamera");
camObj.transform.SetParent(head.transform);
// Камера
var camObj = new GameObject("PlayerCamera");
camObj.transform.SetParent(head.transform, false);
camObj.transform.localPosition = new Vector3(0, 0.1f, 0);
var cam = camObj.AddComponent<Camera>();
cam.tag = "MainCamera";
var cam = camObj.AddComponent<Camera>();
cam.tag = "MainCamera";
cam.nearClipPlane = 0.05f;
var existingCam = GameObject.FindGameObjectWithTag("MainCamera");
if (existingCam != null && existingCam != camObj)
{
GameObject.Destroy(existingCam);
}
var oldCam = GameObject.FindWithTag("MainCamera");
if (oldCam != null && oldCam != camObj)
Object.DestroyImmediate(oldCam);
go.tag = "Player";
go.transform.position = new Vector3(0, 2, -5);
Selection.activeGameObject = go;
Debug.Log("[Tools] Создан игрок!");
}
[MenuItem("Tools/Create Item Prefab")]
public static void CreateItemPrefab()
{
var itemGO = new GameObject("NewItem");
itemGO.AddComponent<WorldItem>();
var sphere = itemGO.AddComponent<SphereCollider>();
sphere.radius = 0.5f;
sphere.isTrigger = true;
var visual = GameObject.CreatePrimitive(PrimitiveType.Cube);
visual.name = "Visual";
visual.transform.SetParent(itemGO.transform);
visual.transform.localPosition = Vector3.zero;
visual.transform.localScale = new Vector3(0.3f, 0.3f, 0.3f);
GameObject.Destroy(visual.GetComponent<Collider>());
Selection.activeGameObject = itemGO;
Debug.Log("[Tools] Создан префаб предмета!");
}
[MenuItem("Tools/Create Test Item in Scene")]
public static void CreateTestItem()
{
// Создаём тестовый предмет в сцене
var itemGO = new GameObject("TestItem");
itemGO.transform.position = new Vector3(0, 0.5f, 3);
// Визуал
var visual = GameObject.CreatePrimitive(PrimitiveType.Cube);
visual.name = "Visual";
visual.transform.SetParent(itemGO.transform);
visual.transform.localPosition = Vector3.zero;
visual.transform.localScale = Vector3.one * 0.3f;
GameObject.Destroy(visual.GetComponent<Collider>());
// Компонент предмета
var worldItem = itemGO.AddComponent<WorldItem>();
worldItem.useFactory = false;
// Тестовые данные
var testItem = ScriptableObject.CreateInstance<ItemData>();
testItem.id = "test_item";
testItem.itemName = "Тестовый предмет";
testItem.description = "Просто тестовый предмет";
testItem.type = ItemType.Material;
testItem.maxStack = 64;
// Используем Setup чтобы инициализировать визуал и включить мгновенный подбор
worldItem.Setup(testItem, 5);
// Коллайдер для подбора (trigger для обычных коллайдеров)
var box = itemGO.AddComponent<BoxCollider>();
box.isTrigger = true;
box.size = Vector3.one;
Selection.activeGameObject = itemGO;
Debug.Log("[Tools] Создан тестовый предмет! Подойдите к нему игроком.");
}
[MenuItem("Tools/Setup Inventory Scene")]
public static void SetupScene()
{
var scene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
var rootObjects = scene.GetRootGameObjects();
foreach (var root in rootObjects)
{
if (root.GetComponent<InventoryManager>() != null)
{
Debug.LogWarning("InventoryManager уже существует!");
return;
}
}
// Создаём GameManager
var gameManager = new GameObject("GameManager");
var im = gameManager.AddComponent<InventoryManager>();
gameManager.AddComponent<InventoryExampleItems>();
gameManager.AddComponent<InventoryTester>();
// Создаём Canvas
var canvasGO = new GameObject("InventoryCanvas");
var canvas = canvasGO.AddComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvas.sortingOrder = 100;
canvasGO.AddComponent<GraphicRaycaster>();
// Ensure EventSystem exists for UI interaction
var existingES = GameObject.FindObjectOfType<UnityEngine.EventSystems.EventSystem>();
if (existingES == null)
{
var es = new GameObject("EventSystem");
es.AddComponent<UnityEngine.EventSystems.EventSystem>();
es.AddComponent<UnityEngine.EventSystems.StandaloneInputModule>();
}
// Создаём панель инвентаря
var inventoryPanel = CreatePanel(canvasGO.transform, "InventoryPanel", new Color(0.15f, 0.15f, 0.15f, 0.95f));
var inventoryRect = inventoryPanel.GetComponent<RectTransform>();
SetRect(inventoryRect, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, new Vector2(500, 300));
// Заголовок
CreateTitle(inventoryPanel.transform, "Инвентарь", new Vector2(0, 130));
// Сетка инвентаря (4x9 = 36 слотов)
var inventoryGrid = CreateGrid(inventoryPanel.transform, "InventoryGrid", 9, 4, 50, 4, new Vector2(10, 40));
im.slots = CreateSlots(inventoryGrid, "InventorySlot_", 0, 36);
// Создаём хотбар
var hotbarPanel = CreatePanel(canvasGO.transform, "HotbarPanel", new Color(0.1f, 0.1f, 0.1f, 0.9f));
var hotbarRect = hotbarPanel.GetComponent<RectTransform>();
SetRect(hotbarRect, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0, 15), new Vector2(460, 60));
var hotbarGrid = CreateGrid(hotbarPanel.transform, "HotbarGrid", 9, 1, 50, 4, new Vector2(10, 10));
im.hotbarSlots = CreateSlots(hotbarGrid, "HotbarSlot_", 0, 9);
// Подключаем ссылку на панель
im.inventoryPanel = inventoryPanel;
im.inventoryCanvas = canvas;
// Скрываем панель
inventoryPanel.SetActive(false);
Debug.Log("[Tools] Инвентарь настроен! (36 слотов + 9 хотбар)");
}
static GameObject CreatePanel(Transform parent, string name, Color color)
{
var panel = new GameObject(name);
panel.transform.SetParent(parent, false);
panel.AddComponent<RectTransform>();
panel.AddComponent<Image>().color = color;
return panel;
}
static void SetRect(RectTransform rect, Vector2 anchorMin, Vector2 anchorMax, Vector2 anchoredPosition, Vector2 sizeDelta)
{
rect.anchorMin = anchorMin;
rect.anchorMax = anchorMax;
rect.pivot = new Vector2(0.5f, 0.5f);
rect.anchoredPosition = anchoredPosition;
rect.sizeDelta = sizeDelta;
}
static void CreateTitle(Transform parent, string text, Vector2 position)
{
var titleObj = new GameObject("Title");
titleObj.transform.SetParent(parent, false);
var rect = titleObj.AddComponent<RectTransform>();
rect.anchorMin = new Vector2(0, 1);
rect.anchorMax = new Vector2(1, 1);
rect.pivot = new Vector2(0.5f, 1);
rect.anchoredPosition = position;
rect.sizeDelta = new Vector2(-20, 30);
var tmp = titleObj.AddComponent<TextMeshProUGUI>();
tmp.text = text;
tmp.fontSize = 18;
tmp.alignment = TextAlignmentOptions.Center;
tmp.color = Color.white;
}
static Transform CreateGrid(Transform parent, string name, int cols, int rows, float cellSize, float spacing, Vector2 offset)
{
var gridObj = new GameObject(name);
gridObj.transform.SetParent(parent, false);
var rect = gridObj.AddComponent<RectTransform>();
rect.anchorMin = Vector2.zero;
rect.anchorMax = Vector2.one;
rect.offsetMin = offset;
rect.offsetMax = -offset;
var grid = gridObj.AddComponent<GridLayoutGroup>();
grid.cellSize = new Vector2(cellSize, cellSize);
grid.spacing = new Vector2(spacing, spacing);
grid.startCorner = GridLayoutGroup.Corner.UpperLeft;
grid.startAxis = GridLayoutGroup.Axis.Horizontal;
grid.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
grid.constraintCount = cols;
return gridObj.transform;
}
static Slot[] CreateSlots(Transform parent, string prefix, int startIndex, int count)
{
var slots = new Slot[count];
var slotSize = 50f;
var emptyColor = new Color(0.25f, 0.25f, 0.25f, 0.8f);
for (int i = 0; i < count; i++)
{
var slotObj = new GameObject(prefix + (startIndex + i));
slotObj.transform.SetParent(parent, false);
slotObj.AddComponent<RectTransform>().sizeDelta = new Vector2(slotSize, slotSize);
var bg = slotObj.AddComponent<Image>();
bg.color = emptyColor;
var slot = slotObj.AddComponent<Slot>();
slot.slotIndex = startIndex + i;
// Icon
var iconObj = new GameObject("Icon");
iconObj.transform.SetParent(slotObj.transform, false);
var iconRect = iconObj.AddComponent<RectTransform>();
iconRect.anchorMin = Vector2.zero;
iconRect.anchorMax = Vector2.one;
iconRect.offsetMin = new Vector2(4, 4);
iconRect.offsetMax = new Vector2(-4, -4);
slot.iconImage = iconObj.AddComponent<Image>();
slot.iconImage.preserveAspect = true;
slot.iconImage.enabled = false;
// Amount panel
var amountObj = new GameObject("AmountPanel");
amountObj.transform.SetParent(slotObj.transform, false);
var amountRect = amountObj.AddComponent<RectTransform>();
amountRect.anchorMin = Vector2.one;
amountRect.anchorMax = Vector2.one;
amountRect.pivot = new Vector2(1, 0);
amountRect.anchoredPosition = new Vector2(-2, 2);
amountRect.sizeDelta = new Vector2(22, 22);
slot.amountPanel = amountObj;
slot.amountPanel.SetActive(false);
amountObj.AddComponent<Image>().color = new Color(0, 0, 0, 0.7f);
var amountTextObj = new GameObject("Text");
amountTextObj.transform.SetParent(amountObj.transform, false);
amountTextObj.AddComponent<RectTransform>().sizeDelta = new Vector2(20, 20);
slot.amountText = amountTextObj.AddComponent<TextMeshProUGUI>();
slot.amountText.fontSize = 14;
slot.amountText.alignment = TextAlignmentOptions.Right;
slot.amountText.color = Color.white;
slot.amountText.fontStyle = FontStyles.Bold;
// Drag & Drop
var dragDrop = slotObj.AddComponent<InventoryDragDrop>();
dragDrop.slot = slot;
dragDrop.slotIndex = slot.slotIndex;
slots[i] = slot;
}
return slots;
return go;
}
}

View File

@@ -1,101 +0,0 @@
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// Автоматически настраивает всю систему инвентаря на сцене
/// Добавить на пустой GameObject "GameManager" в сцене
/// </summary>
public class InventorySetup : MonoBehaviour
{
[Header("Настройки")]
public bool createUI = true;
public bool createManagers = true;
void Awake()
{
// Не создаём если уже есть на сцене (после ручной настройки)
if (FindObjectOfType<InventoryManager>() != null)
{
Debug.Log("[InventorySetup] Inventory already exists, skipping creation");
return;
}
if (createManagers)
SetupManagers();
if (createUI)
SetupUI();
}
void SetupManagers()
{
var imGO = new GameObject("InventoryManager");
imGO.transform.SetParent(transform);
imGO.AddComponent<InventoryManager>();
var itemsGO = new GameObject("InventoryItems");
itemsGO.transform.SetParent(transform);
itemsGO.AddComponent<InventoryExampleItems>();
var testerGO = new GameObject("InventoryTester");
testerGO.transform.SetParent(transform);
testerGO.AddComponent<InventoryTester>();
Debug.Log("[InventorySetup] Managers created");
}
void SetupUI()
{
var canvasGO = new GameObject("InventoryCanvas");
canvasGO.transform.SetParent(transform);
var canvas = canvasGO.AddComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvas.sortingOrder = 100;
canvasGO.AddComponent<GraphicRaycaster>();
canvasGO.AddComponent<InventoryUIBuilder>();
Debug.Log("[InventorySetup] UI created");
}
}
#if UNITY_EDITOR
/// <summary>
/// Кнопка в инспекторе для быстрой настройки сцены
/// </summary>
[CustomEditor(typeof(InventorySetup))]
public class InventorySetupEditor : Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
if (GUILayout.Button("Настроить сцену для инвентаря"))
{
var target = (InventorySetup)this.target;
if (FindObjectOfType<InventoryManager>() == null)
{
target.createManagers = true;
target.createUI = true;
Debug.Log("Инвентарь будет настроен при запуске");
}
else
{
Debug.LogWarning("InventoryManager уже существует на сцене!");
}
}
GUILayout.Space(10);
if (GUILayout.Button("Открыть сцену в Unity"))
{
EditorApplication.ExecuteMenuItem("Window/General/Scene");
}
}
}
#endif

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 9a832ca7ed419e6479e1cc308a222f33

View File

@@ -3,7 +3,9 @@ using UnityEngine.UI;
using TMPro;
/// <summary>
/// Создаёт UI инвентаря программно с использованием TextMeshPro
/// Создаёт UI инвентаря программно.
/// Вызывается ТОЛЬКО из редакторных инструментов (InventorySceneSetup, PlayerBuilderTool).
/// Никакого runtime-создания: Start() отсутствует.
/// </summary>
public class InventoryUIBuilder : MonoBehaviour
{
@@ -22,138 +24,113 @@ public class InventoryUIBuilder : MonoBehaviour
[SerializeField] private Color inventoryPanelColor = new Color(0.15f, 0.15f, 0.15f, 0.95f);
[SerializeField] private Color hotbarPanelColor = new Color(0.1f, 0.1f, 0.1f, 0.9f);
[SerializeField] private Color slotEmptyColor = new Color(0.25f, 0.25f, 0.25f, 0.8f);
[SerializeField] private Color slotFilledColor = new Color(0.35f, 0.35f, 0.35f, 0.95f);
[Header("Container References")]
public RectTransform inventoryPanelRect;
public RectTransform hotbarPanelRect;
public Canvas inventoryCanvas;
void Start()
{
// Проверяем, не запущен ли уже BuildInventoryUI
if (InventoryManager.Instance != null &&
InventoryManager.Instance.slots != null &&
InventoryManager.Instance.slots.Length > 0)
{
Debug.Log("[InventoryUIBuilder] UI already built, skipping");
return;
}
// Созданные слоты — читаются редакторным инструментом после BuildInventoryUI()
[SerializeField] private Slot[] _inventorySlots;
[SerializeField] private Slot[] _hotbarSlots;
BuildInventoryUI();
}
public Slot[] GetInventorySlots() => _inventorySlots;
public Slot[] GetHotbarSlots() => _hotbarSlots;
// ── Построение UI ────────────────────────────────────────────────────────
public void BuildInventoryUI()
{
// Создаём или получаем Canvas
// Получаем или добавляем Canvas
var canvas = GetComponent<Canvas>();
if (canvas == null)
{
canvas = gameObject.AddComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvas.sortingOrder = 100;
gameObject.AddComponent<GraphicRaycaster>();
}
inventoryCanvas = canvas;
// Настраиваем EventSystem если нет
if (FindObjectOfType<TMPro.TMP_InputField>() != null || FindObjectsOfType<TMPro.TextMeshProUGUI>().Length > 0)
{
// EventSystem уже есть
}
// Создаём панель инвентаря
// Панель инвентаря
GameObject inventoryPanel = CreatePanel("InventoryPanel", transform, inventoryPanelColor);
inventoryPanelRect = inventoryPanel.GetComponent<RectTransform>();
float inventoryWidth = inventoryCols * (slotSize + slotSpacing) + inventoryPadding * 2;
float inventoryHeight = inventoryRows * (slotSize + slotSpacing) + inventoryPadding * 2 + 40; // +40 для заголовка
float inventoryWidth = inventoryCols * (slotSize + slotSpacing) + inventoryPadding * 2;
float inventoryHeight = inventoryRows * (slotSize + slotSpacing) + inventoryPadding * 2 + 40;
inventoryPanelRect.anchorMin = new Vector2(0.5f, 0.5f);
inventoryPanelRect.anchorMax = new Vector2(0.5f, 0.5f);
inventoryPanelRect.pivot = new Vector2(0.5f, 0.5f);
inventoryPanelRect.anchorMin = new Vector2(0.5f, 0.5f);
inventoryPanelRect.anchorMax = new Vector2(0.5f, 0.5f);
inventoryPanelRect.pivot = new Vector2(0.5f, 0.5f);
inventoryPanelRect.anchoredPosition = Vector2.zero;
inventoryPanelRect.sizeDelta = new Vector2(inventoryWidth, inventoryHeight);
inventoryPanelRect.sizeDelta = new Vector2(inventoryWidth, inventoryHeight);
// Заголовок
CreateTitle(inventoryPanel.transform, "Инвентарь", new Vector2(0, inventoryHeight / 2 - 20));
// Сетка инвентаря (4x9 = 36 слотов)
float gridHeight = inventoryRows * (slotSize + slotSpacing);
var inventoryGrid = CreateGrid(
inventoryPanel.transform,
"InventoryGrid",
inventoryCols,
inventoryRows,
slotSize,
slotSpacing,
new Vector2(inventoryPadding, inventoryPadding + 30)
);
InventoryManager.Instance.slots = CreateSlots(inventoryGrid, "InventorySlot_", 0, inventoryCols * inventoryRows);
inventoryPanel.transform, "InventoryGrid",
inventoryCols, inventoryRows,
slotSize, slotSpacing,
new Vector2(inventoryPadding, inventoryPadding + 30));
// Создаём хотбар
_inventorySlots = CreateSlots(inventoryGrid, "InventorySlot_", 0, inventoryCols * inventoryRows);
// Хотбар
GameObject hotbarPanel = CreatePanel("HotbarPanel", transform, hotbarPanelColor);
hotbarPanelRect = hotbarPanel.GetComponent<RectTransform>();
float hotbarWidth = hotbarSlots * (slotSize + slotSpacing) + hotbarPadding * 2;
hotbarPanelRect.anchorMin = new Vector2(0.5f, 0f);
hotbarPanelRect.anchorMax = new Vector2(0.5f, 0f);
hotbarPanelRect.pivot = new Vector2(0.5f, 0f);
hotbarPanelRect.anchorMin = new Vector2(0.5f, 0f);
hotbarPanelRect.anchorMax = new Vector2(0.5f, 0f);
hotbarPanelRect.pivot = new Vector2(0.5f, 0f);
hotbarPanelRect.anchoredPosition = new Vector2(0, 15);
hotbarPanelRect.sizeDelta = new Vector2(hotbarWidth, slotSize + hotbarPadding * 2);
hotbarPanelRect.sizeDelta = new Vector2(hotbarWidth, slotSize + hotbarPadding * 2);
var hotbarGrid = CreateGrid(
hotbarPanel.transform,
"HotbarGrid",
hotbarSlots,
1,
slotSize,
slotSpacing,
new Vector2(hotbarPadding, hotbarPadding)
);
InventoryManager.Instance.hotbarSlots = CreateSlots(hotbarGrid, "HotbarSlot_", 0, hotbarSlots);
hotbarPanel.transform, "HotbarGrid",
hotbarSlots, 1,
slotSize, slotSpacing,
new Vector2(hotbarPadding, hotbarPadding));
// Настраиваем InventoryManager
InventoryManager.Instance.inventoryPanel = inventoryPanel;
InventoryManager.Instance.inventoryCanvas = inventoryCanvas;
_hotbarSlots = CreateSlots(hotbarGrid, "HotbarSlot_", 0, hotbarSlots);
Debug.Log($"[InventoryUIBuilder] Created {inventoryCols * inventoryRows} inventory slots + {hotbarSlots} hotbar slots");
Debug.Log($"[InventoryUIBuilder] Built {inventoryCols * inventoryRows} inventory slots + {hotbarSlots} hotbar slots");
}
// ── Вспомогательные методы ────────────────────────────────────────────────
GameObject CreatePanel(string name, Transform parent, Color color)
{
GameObject panel = new GameObject(name);
var panel = new GameObject(name);
panel.transform.SetParent(parent, false);
var rect = panel.AddComponent<RectTransform>();
panel.AddComponent<RectTransform>();
panel.AddComponent<Image>().color = color;
return panel;
}
void CreateTitle(Transform parent, string text, Vector2 position)
{
GameObject titleObj = new GameObject("Title");
var titleObj = new GameObject("Title");
titleObj.transform.SetParent(parent, false);
var rect = titleObj.AddComponent<RectTransform>();
rect.anchorMin = new Vector2(0, 1);
rect.anchorMax = new Vector2(1, 1);
rect.pivot = new Vector2(0.5f, 1);
rect.anchorMin = new Vector2(0, 1);
rect.anchorMax = new Vector2(1, 1);
rect.pivot = new Vector2(0.5f, 1);
rect.anchoredPosition = position;
rect.sizeDelta = new Vector2(-20, 30);
rect.sizeDelta = new Vector2(-20, 30);
var tmp = titleObj.AddComponent<TextMeshProUGUI>();
tmp.text = text;
tmp.fontSize = 18;
tmp.text = text;
tmp.fontSize = 18;
tmp.alignment = TextAlignmentOptions.Center;
tmp.color = Color.white;
tmp.color = Color.white;
}
Transform CreateGrid(Transform parent, string name, int cols, int rows, float cellSize, float spacing, Vector2 offset)
{
GameObject gridObj = new GameObject(name);
var gridObj = new GameObject(name);
gridObj.transform.SetParent(parent, false);
var rect = gridObj.AddComponent<RectTransform>();
@@ -163,13 +140,13 @@ public class InventoryUIBuilder : MonoBehaviour
rect.offsetMax = -offset;
var grid = gridObj.AddComponent<GridLayoutGroup>();
grid.cellSize = new Vector2(cellSize, cellSize);
grid.spacing = new Vector2(spacing, spacing);
grid.startCorner = GridLayoutGroup.Corner.UpperLeft;
grid.startAxis = GridLayoutGroup.Axis.Horizontal;
grid.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
grid.cellSize = new Vector2(cellSize, cellSize);
grid.spacing = new Vector2(spacing, spacing);
grid.startCorner = GridLayoutGroup.Corner.UpperLeft;
grid.startAxis = GridLayoutGroup.Axis.Horizontal;
grid.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
grid.constraintCount = cols;
grid.padding = new RectOffset(0, 0, 0, 0);
grid.padding = new RectOffset(0, 0, 0, 0);
return gridObj.transform;
}
@@ -180,22 +157,21 @@ public class InventoryUIBuilder : MonoBehaviour
for (int i = 0; i < count; i++)
{
GameObject slotObj = new GameObject(prefix + (startIndex + i));
var slotObj = new GameObject(prefix + (startIndex + i));
slotObj.transform.SetParent(parent, false);
var rect = slotObj.AddComponent<RectTransform>();
rect.sizeDelta = new Vector2(slotSize, slotSize);
// Background
var bg = slotObj.AddComponent<Image>();
bg.color = slotEmptyColor;
// Slot компонент
var slot = slotObj.AddComponent<Slot>();
slot.slotIndex = startIndex + i;
slot.slotIndex = startIndex + i;
slot.slotBackground = bg;
// Icon
GameObject iconObj = new GameObject("Icon");
// Иконка
var iconObj = new GameObject("Icon");
iconObj.transform.SetParent(slotObj.transform, false);
var iconRect = iconObj.AddComponent<RectTransform>();
iconRect.anchorMin = Vector2.zero;
@@ -206,41 +182,37 @@ public class InventoryUIBuilder : MonoBehaviour
slot.iconImage.preserveAspect = true;
slot.iconImage.enabled = false;
// Amount panel
GameObject amountObj = new GameObject("AmountPanel");
// Количество
var amountObj = new GameObject("AmountPanel");
amountObj.transform.SetParent(slotObj.transform, false);
var amountRect = amountObj.AddComponent<RectTransform>();
amountRect.anchorMin = Vector2.one;
amountRect.anchorMax = Vector2.one;
amountRect.pivot = new Vector2(1, 0);
amountRect.anchorMin = Vector2.one;
amountRect.anchorMax = Vector2.one;
amountRect.pivot = new Vector2(1, 0);
amountRect.anchoredPosition = new Vector2(-2, 2);
amountRect.sizeDelta = new Vector2(22, 22);
amountRect.sizeDelta = new Vector2(22, 22);
slot.amountPanel = amountObj;
var amountBg = amountObj.AddComponent<Image>();
amountBg.color = new Color(0, 0, 0, 0.7f);
amountObj.AddComponent<Image>().color = new Color(0, 0, 0, 0.7f);
GameObject amountTextObj = new GameObject("Text");
var amountTextObj = new GameObject("Text");
amountTextObj.transform.SetParent(amountObj.transform, false);
var amountTextRect = amountTextObj.AddComponent<RectTransform>();
amountTextRect.anchorMin = Vector2.zero;
amountTextRect.anchorMax = Vector2.one;
slot.amountText = amountTextObj.AddComponent<TextMeshProUGUI>();
slot.amountText.fontSize = 14;
slot.amountText.alignment = TextAlignmentOptions.Right;
slot.amountText.color = Color.white;
slot.amountText.fontStyle = FontStyles.Bold;
slot.amountText = amountTextObj.AddComponent<TextMeshProUGUI>();
slot.amountText.fontSize = 14;
slot.amountText.alignment = TextAlignmentOptions.Right;
slot.amountText.color = Color.white;
slot.amountText.fontStyle = FontStyles.Bold;
// Применяем настройки через сеттер
slot.amountPanel.SetActive(false);
slot.slotBackground = bg;
// Drag & Drop
var dragDrop = slotObj.AddComponent<InventoryDragDrop>();
dragDrop.slot = slot;
dragDrop.slot = slot;
dragDrop.slotIndex = slot.slotIndex;
// Подписка на события слота
slot.OnSlotClicked += OnSlotClicked;
slots[i] = slot;
@@ -249,40 +221,35 @@ public class InventoryUIBuilder : MonoBehaviour
return slots;
}
// ── Выделение слота (runtime) ─────────────────────────────────────────────
private int _selectedSlot = -1;
private void OnSlotClicked(Slot slot)
{
// Снимаем выделение с предыдущего
if (_selectedSlot >= 0 && _selectedSlot < InventoryManager.Instance.slots.Length)
{
InventoryManager.Instance.slots[_selectedSlot].SetSelected(false);
}
if (_selectedSlot >= 0 && _inventorySlots != null && _selectedSlot < _inventorySlots.Length)
_inventorySlots[_selectedSlot].SetSelected(false);
// Выделяем новый
slot.SetSelected(true);
_selectedSlot = slot.slotIndex;
Debug.Log($"[UI] Selected slot: {_selectedSlot} - {slot.GetItem()?.itemName ?? "Empty"}");
}
/// <summary>
/// Пересоздать UI (например, при изменении настроек)
/// </summary>
// ── Утилиты ───────────────────────────────────────────────────────────────
public void Rebuild()
{
// Удаляем старые панели
if (inventoryPanelRect != null)
Destroy(inventoryPanelRect.gameObject);
if (hotbarPanelRect != null)
Destroy(hotbarPanelRect.gameObject);
_inventorySlots = null;
_hotbarSlots = null;
BuildInventoryUI();
}
/// <summary>
/// Показать/скрыть инвентарь
/// </summary>
public void ToggleInventory(bool show)
{
if (inventoryPanelRect != null)

View File

@@ -1,30 +1,33 @@
using UnityEngine;
using UnityEngine.UI;
using UnityEditor;
using TMPro;
/// <summary>
/// Инструмент создания предметов.
/// Tools > Item Builder
/// </summary>
public class ItemBuilderTool : EditorWindow
{
// Основные данные
private string itemName = "NewItem";
private string itemId = "";
private string description = "";
private ItemType itemType = ItemType.Material;
private string itemName = "NewItem";
private string itemId = "";
private string description = "";
private ItemType itemType = ItemType.Material;
// Визуал
private GameObject modelPrefab;
private Sprite icon;
private Color previewColor = Color.gray;
private Sprite icon;
private Color previewColor = Color.gray;
// Свойства
private int maxStack = 64;
private int value = 1;
private float weight = 1f;
private bool isDroppable = true;
private bool isStackable = true;
private int maxStack = 64;
private int value = 1;
private float weight = 1f;
private bool isDroppable = true;
private bool isStackable = true;
// Префаб
private GameObject worldItemPrefab;
// Pickup
private float interactRange = 2.5f;
[MenuItem("Tools/Item Builder")]
public static void ShowWindow()
@@ -38,67 +41,59 @@ public class ItemBuilderTool : EditorWindow
// Основные данные
GUILayout.Label("Основное", EditorStyles.miniBoldLabel);
itemName = EditorGUILayout.TextField("Название", itemName);
itemId = EditorGUILayout.TextField("ID", itemId);
itemName = EditorGUILayout.TextField("Название", itemName);
itemId = EditorGUILayout.TextField("ID (оставь пустым для GUID)", itemId);
description = EditorGUILayout.TextField("Описание", description);
itemType = (ItemType)EditorGUILayout.EnumPopup("Тип предмета", itemType);
itemType = (ItemType)EditorGUILayout.EnumPopup("Тип предмета", itemType);
GUILayout.Space(10);
// Визуал
GUILayout.Label("Визуал", EditorStyles.miniBoldLabel);
modelPrefab = (GameObject)EditorGUILayout.ObjectField("3D модель", modelPrefab, typeof(GameObject), false);
icon = (Sprite)EditorGUILayout.ObjectField("Иконка", icon, typeof(Sprite), false);
modelPrefab = (GameObject)EditorGUILayout.ObjectField("3D модель (префаб)", modelPrefab, typeof(GameObject), false);
icon = (Sprite)EditorGUILayout.ObjectField("Иконка", icon, typeof(Sprite), false);
if (icon == null)
{
previewColor = EditorGUILayout.ColorField("Цвет иконки", previewColor);
}
previewColor = EditorGUILayout.ColorField("Цвет иконки (заглушка)", previewColor);
GUILayout.Space(10);
// Свойства
GUILayout.Label("Свойства", EditorStyles.miniBoldLabel);
maxStack = EditorGUILayout.IntField("Макс. стек", maxStack);
value = EditorGUILayout.IntField("Цена", value);
weight = EditorGUILayout.FloatField("Вес", weight);
maxStack = EditorGUILayout.IntField("Макс. стек", maxStack);
value = EditorGUILayout.IntField("Цена", value);
weight = EditorGUILayout.FloatField("Вес", weight);
isDroppable = EditorGUILayout.Toggle("Можно выбросить", isDroppable);
isStackable = EditorGUILayout.Toggle("Стек", isStackable);
isStackable = EditorGUILayout.Toggle("Стекируемый", isStackable);
GUILayout.Space(10);
// Префаб
GUILayout.Label("Префаб предмета в мире", EditorStyles.miniBoldLabel);
worldItemPrefab = (GameObject)EditorGUILayout.ObjectField("WorldItem Prefab", worldItemPrefab, typeof(GameObject), false);
GUILayout.Label("Подбор", EditorStyles.miniBoldLabel);
interactRange = EditorGUILayout.FloatField("Дальность подбора (E)", interactRange);
GUILayout.Space(20);
// Кнопки
GUI.color = Color.green;
if (GUILayout.Button("Создать ItemData"))
{
if (GUILayout.Button("Создать ItemData (ScriptableObject)"))
CreateItemData();
}
GUI.color = Color.cyan;
if (GUILayout.Button("Создать WorldItem в сцене"))
{
CreateWorldItem();
}
GUI.color = Color.yellow;
if (GUILayout.Button("Создать префаб"))
{
if (GUILayout.Button("Создать WorldItem как префаб"))
CreateItemPrefab();
}
GUI.color = Color.white;
GUILayout.Space(10);
EditorGUILayout.HelpBox(
"1. Заполните данные предмета\n" +
"2. Назначьте модель и иконку\n" +
"3. Создайте ItemData или префаб",
"Подбор предметов — только по кнопке E (авто-pickup при касании убран намеренно).\n" +
"1. Заполните данные → Создайте ItemData\n" +
"2. Назначьте ItemData в InventoryManager.itemDatabase\n" +
"3. Создайте WorldItem для размещения предметов на сцене",
MessageType.Info);
}
@@ -112,105 +107,101 @@ public class ItemBuilderTool : EditorWindow
var item = ScriptableObject.CreateInstance<ItemData>();
item.id = string.IsNullOrEmpty(itemId) ? GUID.Generate().ToString() : itemId;
item.itemName = itemName;
item.id = string.IsNullOrEmpty(itemId) ? System.Guid.NewGuid().ToString() : itemId;
item.itemName = itemName;
item.description = description;
item.type = itemType;
item.type = itemType;
item.modelPrefab = modelPrefab;
item.icon = icon != null ? icon : CreatePlaceholderIcon();
item.maxStack = maxStack;
item.value = value;
item.weight = weight;
item.icon = icon != null ? icon : CreatePlaceholderIcon();
item.maxStack = maxStack;
item.value = value;
item.weight = weight;
item.isDroppable = isDroppable;
item.isStackable = isStackable;
// Сохраняем
string path = $"Assets/Inventory/Items/{itemName}.asset";
string folder = System.IO.Path.GetDirectoryName(path);
string folder = "Assets/Inventory/Items";
if (!System.IO.Directory.Exists(folder))
{
System.IO.Directory.CreateDirectory(folder);
}
string path = $"{folder}/{itemName}.asset";
AssetDatabase.CreateAsset(item, path);
AssetDatabase.SaveAssets();
Selection.activeObject = item;
Debug.Log($"[ItemBuilder] Создан: {itemName}");
Debug.Log($"[ItemBuilder] Создан: {itemName} → {path}");
}
void CreateWorldItem()
{
if (modelPrefab == null)
var itemGO = new GameObject(itemName);
// Визуал
GameObject visual;
if (modelPrefab != null)
{
// Создаём простой визуал
var itemGO = new GameObject(itemName);
var visual = GameObject.CreatePrimitive(PrimitiveType.Cube);
visual.name = "Visual";
visual.transform.SetParent(itemGO.transform);
visual.transform.localPosition = Vector3.zero;
visual.transform.localScale = Vector3.one * 0.3f;
DestroyImmediate(visual.GetComponent<Collider>());
var worldItem = itemGO.AddComponent<WorldItem>();
var col = itemGO.AddComponent<SphereCollider>();
col.radius = 0.5f;
col.isTrigger = true;
itemGO.transform.position = Vector3.zero;
Selection.activeGameObject = itemGO;
visual = (GameObject)PrefabUtility.InstantiatePrefab(modelPrefab, itemGO.transform);
}
else
{
var itemGO = Instantiate(modelPrefab);
itemGO.name = itemName;
itemGO.AddComponent<WorldItem>();
itemGO.transform.position = Vector3.zero;
Selection.activeGameObject = itemGO;
visual = GameObject.CreatePrimitive(PrimitiveType.Cube);
visual.transform.SetParent(itemGO.transform, false);
visual.transform.localScale = Vector3.one * 0.3f;
DestroyImmediate(visual.GetComponent<Collider>());
}
visual.name = "Visual";
visual.transform.localPosition = Vector3.zero;
// WorldItem компонент
var worldItem = itemGO.AddComponent<WorldItem>();
worldItem.interactRange = interactRange;
// Trigger-коллайдер для OverlapSphere (не для авто-pickup!)
var col = itemGO.AddComponent<SphereCollider>();
col.radius = 0.5f;
col.isTrigger = true;
itemGO.transform.position = Vector3.zero;
Selection.activeGameObject = itemGO;
Debug.Log($"[ItemBuilder] Создан WorldItem: {itemName}");
}
void CreateItemPrefab()
{
string path = EditorUtility.SaveFilePanelInProject(
"Сохранить префаб",
"Сохранить WorldItem префаб",
itemName,
"prefab",
"Выберите место");
"Выберите место сохранения");
if (string.IsNullOrEmpty(path)) return;
GameObject prefab;
var prefabGO = new GameObject(itemName);
// Визуал
GameObject visual;
if (modelPrefab != null)
{
prefab = Instantiate(modelPrefab);
prefab.name = itemName;
visual = (GameObject)PrefabUtility.InstantiatePrefab(modelPrefab, prefabGO.transform);
}
else
{
prefab = new GameObject(itemName);
var visual = GameObject.CreatePrimitive(PrimitiveType.Cube);
visual.name = "Visual";
visual.transform.SetParent(prefab.transform);
visual.transform.localPosition = Vector3.zero;
visual = GameObject.CreatePrimitive(PrimitiveType.Cube);
visual.transform.SetParent(prefabGO.transform, false);
visual.transform.localScale = Vector3.one * 0.3f;
DestroyImmediate(visual.GetComponent<Collider>());
}
visual.name = "Visual";
visual.transform.localPosition = Vector3.zero;
prefab.AddComponent<WorldItem>();
var worldItem = prefabGO.AddComponent<WorldItem>();
worldItem.interactRange = interactRange;
var col = prefab.GetComponent<Collider>();
if (col == null)
{
var sphere = prefab.AddComponent<SphereCollider>();
sphere.radius = 0.5f;
sphere.isTrigger = true;
}
var col = prefabGO.AddComponent<SphereCollider>();
col.radius = 0.5f;
col.isTrigger = true;
GameObject savedPrefab = PrefabUtility.SaveAsPrefabAsset(prefab, path);
DestroyImmediate(prefab);
GameObject savedPrefab = PrefabUtility.SaveAsPrefabAsset(prefabGO, path);
DestroyImmediate(prefabGO);
Selection.activeObject = savedPrefab;
Debug.Log($"[ItemBuilder] Префаб сохранён: {path}");
@@ -218,12 +209,10 @@ public class ItemBuilderTool : EditorWindow
Sprite CreatePlaceholderIcon()
{
var tex = new Texture2D(64, 64);
var tex = new Texture2D(64, 64);
var colors = new Color[64 * 64];
for (int i = 0; i < colors.Length; i++)
{
colors[i] = previewColor;
}
tex.SetPixels(colors);
tex.Apply();
return Sprite.Create(tex, new Rect(0, 0, 64, 64), new Vector2(0.5f, 0.5f));

View File

@@ -5,6 +5,10 @@ using System.Collections.Generic;
using UnityEditor;
#endif
/// <summary>
/// Компонент для генерации ItemData ScriptableObject из настроек в Inspector.
/// Добавьте на GameObject, заполните поля и нажмите "Generate ItemData" в Inspector.
/// </summary>
public class ItemGenerator : MonoBehaviour
{
[Header("Item Template")]
@@ -26,27 +30,25 @@ public class ItemGenerator : MonoBehaviour
public ItemData GenerateItemData()
{
if (string.IsNullOrEmpty(itemId))
{
itemId = System.Guid.NewGuid().ToString();
}
var itemData = ScriptableObject.CreateInstance<ItemData>();
itemData.id = itemId;
itemData.itemName = itemName;
itemData.id = itemId;
itemData.itemName = itemName;
itemData.description = description;
itemData.type = itemType;
itemData.type = itemType;
itemData.modelPrefab = modelPrefab;
itemData.icon = icon;
itemData.maxStack = maxStack;
itemData.icon = icon;
itemData.maxStack = maxStack;
itemData.isCraftable = isCraftable;
if (isCraftable && craftIngredients != null && craftIngredients.Length > 0)
{
itemData.craftingRecipe = new ItemData.CraftingRecipe
{
ingredients = craftIngredients,
amounts = craftAmounts,
resultId = craftResultId,
ingredients = craftIngredients,
amounts = craftAmounts,
resultId = craftResultId,
resultAmount = craftResultAmount
};
}
@@ -57,187 +59,15 @@ public class ItemGenerator : MonoBehaviour
public static List<ItemData> GenerateAll(List<ItemGenerator> generators)
{
var items = new List<ItemData>();
foreach (var generator in generators)
foreach (var gen in generators)
{
if (generator != null)
{
items.Add(generator.GenerateItemData());
}
if (gen != null)
items.Add(gen.GenerateItemData());
}
return items;
}
}
/// <summary>
/// Предмет в мире - подбирается игроком
/// </summary>
public class WorldItem : MonoBehaviour
{
public ItemData itemData;
public int amount = 1;
[Header("Settings")]
public float pickupDelay = 0f;
public bool useFactory = true;
private bool _canPickup;
public void Setup(ItemData data, int count)
{
itemData = data;
amount = count;
if (data.modelPrefab != null)
{
var model = Instantiate(data.modelPrefab, transform);
model.transform.localPosition = Vector3.zero;
}
// Задержка перед подбором
if (pickupDelay > 0)
Invoke(nameof(EnablePickup), pickupDelay);
else
_canPickup = true;
}
private void EnablePickup() => _canPickup = true;
void OnTriggerEnter(Collider other)
{
TryPickup(other);
}
void OnControllerColliderHit(ControllerColliderHit hit)
{
TryPickup(hit.controller);
}
/// <summary>
/// Подобрать предмет вручную (по кнопке)
/// </summary>
public bool TryPickupByButton()
{
Debug.Log($"[TryPickup] _canPickup={_canPickup}, itemData={itemData?.itemName}, amount={amount}, Instance={InventoryManager.Instance != null}");
if (!_canPickup)
{
Debug.LogWarning("[TryPickup] Cannot pickup - not ready yet");
return false;
}
if (itemData == null)
{
Debug.LogWarning("[TryPickup] itemData is null!");
return false;
}
if (InventoryManager.Instance == null)
{
Debug.LogWarning("[TryPickup] InventoryManager.Instance is null!");
return false;
}
int remaining = InventoryManager.Instance.AddItem(itemData, amount);
Debug.Log($"[TryPickup] AddItem returned remaining={remaining}");
if (remaining <= 0)
{
Destroy(gameObject);
return true;
}
else
{
amount = remaining;
return false;
}
}
void TryPickup(Collider other)
{
if (!_canPickup) return;
if (other == null) return;
if (!other.CompareTag("Player")) return;
if (useFactory && InventoryItemFactory.Instance != null)
{
InventoryItemFactory.Instance.PickupItem(this, this);
}
else
{
// Fallback - старый способ
int remaining = InventoryManager.Instance.AddItem(itemData, amount);
if (remaining <= 0)
{
Destroy(gameObject);
}
else
{
amount = remaining;
}
}
}
/// <summary>
/// Выбросить предмет с силой
/// </summary>
public void Throw(Vector3 velocity)
{
var rb = GetComponent<Rigidbody>();
if (rb == null) rb = gameObject.AddComponent<Rigidbody>();
rb.linearVelocity = velocity;
}
}
/// <summary>
/// Точка спавна предмета
/// </summary>
public class ItemSpawner : MonoBehaviour
{
public ItemData item;
public int amount = 1;
public float despawnTime = 300f;
public bool spawnOnStart = false;
public bool useFactory = true;
public void Setup(ItemData itemData, int itemAmount, float despawn)
{
item = itemData;
amount = itemAmount;
despawnTime = despawn;
}
void Start()
{
if (spawnOnStart) Spawn();
}
public void Spawn()
{
if (useFactory && InventoryItemFactory.Instance != null)
{
var worldItem = InventoryItemFactory.Instance.CreateWorldItem(item, amount, transform.position);
if (worldItem != null && despawnTime > 0)
{
Destroy(worldItem.gameObject, despawnTime);
}
}
else
{
// Fallback - старый способ
var prefab = Resources.Load<GameObject>("Prefabs/WorldItem");
if (prefab != null)
{
var worldItem = Instantiate(prefab, transform.position, Quaternion.identity).GetComponent<WorldItem>();
worldItem.Setup(item, amount);
Destroy(worldItem.gameObject, despawnTime);
}
}
}
public void Despawn()
{
Destroy(gameObject);
}
}
#if UNITY_EDITOR
[CustomEditor(typeof(ItemGenerator))]
public class ItemGeneratorEditor : Editor
@@ -246,16 +76,22 @@ public class ItemGeneratorEditor : Editor
{
base.OnInspectorGUI();
GUILayout.Space(8);
if (GUILayout.Button("Generate ItemData"))
{
var generator = (ItemGenerator)target;
var itemData = generator.GenerateItemData();
var itemData = generator.GenerateItemData();
string path = $"Assets/Inventory/Items/{itemData.itemName}.asset";
string folder = "Assets/Inventory/Items";
if (!System.IO.Directory.Exists(folder))
System.IO.Directory.CreateDirectory(folder);
string path = $"{folder}/{itemData.itemName}.asset";
AssetDatabase.CreateAsset(itemData, path);
AssetDatabase.SaveAssets();
Debug.Log($"Created item: {itemData.itemName} at {path}");
Debug.Log($"[ItemGenerator] Created item: {itemData.itemName} at {path}");
}
}
}

View File

@@ -0,0 +1,39 @@
using UnityEngine;
/// <summary>
/// Точка спавна предмета в мире. Создаёт WorldItem при старте или по вызову Spawn().
/// </summary>
public class ItemSpawner : MonoBehaviour
{
public ItemData item;
public int amount = 1;
public float despawnTime = 300f;
public bool spawnOnStart = false;
public void Setup(ItemData itemData, int itemAmount, float despawn)
{
item = itemData;
amount = itemAmount;
despawnTime = despawn;
}
void Start()
{
if (spawnOnStart) Spawn();
}
public void Spawn()
{
if (InventoryItemFactory.Instance != null)
{
var worldItem = InventoryItemFactory.Instance.CreateWorldItem(item, amount, transform.position);
if (worldItem != null && despawnTime > 0)
Destroy(worldItem.gameObject, despawnTime);
}
}
public void Despawn()
{
Destroy(gameObject);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 42e70e59bb536584aa49153a0f13c202

View File

@@ -0,0 +1,78 @@
using UnityEngine;
/// <summary>
/// Предмет в мире — подбирается только по нажатию кнопки E (через charecter.cs).
/// Авто-подбор при касании намеренно убран.
/// </summary>
public class WorldItem : MonoBehaviour
{
public ItemData itemData;
public int amount = 1;
[Header("Pickup")]
public float pickupDelay = 0f;
public float interactRange = 2.5f;
private bool _canPickup;
public void Setup(ItemData data, int count)
{
itemData = data;
amount = count;
if (data.modelPrefab != null)
{
var model = Instantiate(data.modelPrefab, transform);
model.transform.localPosition = Vector3.zero;
}
if (pickupDelay > 0)
Invoke(nameof(EnablePickup), pickupDelay);
else
_canPickup = true;
}
private void EnablePickup() => _canPickup = true;
/// <summary>
/// Вызывается из charecter.cs при нажатии E рядом с предметом.
/// </summary>
public bool TryPickupByButton()
{
if (!_canPickup)
{
Debug.LogWarning("[WorldItem] Cannot pickup — delay not elapsed yet");
return false;
}
if (itemData == null)
{
Debug.LogWarning("[WorldItem] itemData is null!");
return false;
}
if (InventoryManager.Instance == null)
{
Debug.LogWarning("[WorldItem] InventoryManager.Instance is null!");
return false;
}
int remaining = InventoryManager.Instance.AddItem(itemData, amount);
if (remaining <= 0)
{
Destroy(gameObject);
return true;
}
amount = remaining;
return false;
}
/// <summary>
/// Выбросить предмет с физической силой.
/// </summary>
public void Throw(Vector3 velocity)
{
var rb = GetComponent<Rigidbody>();
if (rb == null) rb = gameObject.AddComponent<Rigidbody>();
rb.linearVelocity = velocity;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b8ac54fbf9ed99e45a83319443952a91

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,106 @@
using UnityEngine;
/// <summary>
/// Показывает модель активного предмета хотбара в руке персонажа.
/// Принцип — как в Minecraft: выбранный слот хотбара = предмет в правой руке.
/// Добавьте на Player, назначьте handBone в Inspector.
/// </summary>
public class HandEquipSystem : MonoBehaviour
{
[Header("Hand Setup")]
[Tooltip("Кость правой руки из скелета персонажа")]
public Transform handBone;
[Tooltip("Смещение предмета относительно кости руки")]
public Vector3 itemOffset = Vector3.zero;
[Tooltip("Поворот предмета в руке (euler)")]
public Vector3 itemRotation = Vector3.zero;
[Tooltip("Масштаб предмета в руке")]
public Vector3 itemScale = Vector3.one;
// ── Приватные поля ────────────────────────────────────────────────────────
private GameObject _currentHandItem;
// ── Lifecycle ─────────────────────────────────────────────────────────────
void Start()
{
// Подписываемся на смену слота хотбара
if (PlayerInput.Instance != null)
PlayerInput.Instance.OnHotbarSelect += OnHotbarSlotSelected;
// Подписываемся на изменение слотов (drag-drop, добавление/удаление)
if (InventoryEvents.Instance != null)
InventoryEvents.Instance.OnSlotChanged.AddListener(OnSlotChanged);
UpdateHandItem();
}
void OnDestroy()
{
if (PlayerInput.Instance != null)
PlayerInput.Instance.OnHotbarSelect -= OnHotbarSlotSelected;
if (InventoryEvents.Instance != null)
InventoryEvents.Instance.OnSlotChanged.RemoveListener(OnSlotChanged);
}
// ── Обработчики событий ───────────────────────────────────────────────────
private void OnHotbarSlotSelected(int index) => UpdateHandItem();
private void OnSlotChanged(int slotIndex, string itemId) => UpdateHandItem();
// ── Логика обновления предмета в руке ────────────────────────────────────
/// <summary>
/// Обновить объект в руке под текущий выбранный слот хотбара.
/// Можно вызвать вручную если нужно принудительное обновление.
/// </summary>
public void UpdateHandItem()
{
// Уничтожить старый объект в руке
if (_currentHandItem != null)
{
Destroy(_currentHandItem);
_currentHandItem = null;
}
if (handBone == null)
{
Debug.LogWarning("[HandEquipSystem] handBone не назначен!");
return;
}
if (InventoryManager.Instance == null) return;
var item = InventoryManager.Instance.GetSelectedHotbarItem();
if (item == null || item.modelPrefab == null) return;
// Создать модель как дочерний объект кости руки
_currentHandItem = Instantiate(item.modelPrefab, handBone);
_currentHandItem.name = $"HandItem_{item.itemName}";
_currentHandItem.transform.localPosition = itemOffset;
_currentHandItem.transform.localEulerAngles = itemRotation;
_currentHandItem.transform.localScale = itemScale;
// Отключить физику и коллайдеры — предмет в руке не должен взаимодействовать с миром
foreach (var col in _currentHandItem.GetComponentsInChildren<Collider>())
col.enabled = false;
foreach (var rb in _currentHandItem.GetComponentsInChildren<Rigidbody>())
rb.isKinematic = true;
}
/// <summary>
/// Убрать предмет из руки (например когда открыт инвентарь).
/// </summary>
public void ClearHandItem()
{
if (_currentHandItem != null)
{
Destroy(_currentHandItem);
_currentHandItem = null;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a73f597bcce79104fb38225b98ff9348

View File

@@ -1,185 +0,0 @@
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class PlayerBuilder : MonoBehaviour
{
[Header("Character Model")]
public GameObject characterModel;
public Transform headBone;
[Header("Movement Settings")]
public float walkSpeed = 5f;
public float runSpeed = 9f;
public float crouchSpeed = 2.5f;
public float jumpHeight = 1.5f;
public float gravity = -9.81f;
[Header("Camera")]
public bool addCamera = true;
[Header("Collider")]
public float height = 1.8f;
public float radius = 0.5f;
public Vector3 center = new Vector3(0, 0.9f, 0);
public GameObject BuildPlayer(string playerName = "Player")
{
GameObject player = new GameObject(playerName);
var cc = player.AddComponent<CharacterController>();
cc.height = height;
cc.radius = radius;
cc.center = center;
var character = player.AddComponent<charecter>();
character.walkSpeed = walkSpeed;
character.runSpeed = runSpeed;
character.crouchSpeed = crouchSpeed;
character.jumpHeight = jumpHeight;
character.gravity = gravity;
GameObject model = null;
if (characterModel != null)
{
model = Instantiate(characterModel, player.transform);
model.transform.localPosition = Vector3.zero;
model.transform.localRotation = Quaternion.identity;
if (headBone != null)
{
character.head = headBone;
}
else
{
character.head = FindChildBone(model.transform, "Head");
}
}
if (character.head == null)
{
GameObject headObj = new GameObject("HeadPoint");
headObj.transform.SetParent(model != null ? model.transform : player.transform);
headObj.transform.localPosition = new Vector3(0, 1.6f, 0);
character.head = headObj.transform;
}
if (addCamera && character.head != null)
{
GameObject cameraObj = new GameObject("PlayerCamera");
cameraObj.transform.SetParent(character.head);
cameraObj.transform.localPosition = new Vector3(0, 0.1f, 0);
var camera = cameraObj.AddComponent<Camera>();
camera.tag = "MainCamera";
var oldCamera = GameObject.FindWithTag("MainCamera");
if (oldCamera != null && oldCamera != cameraObj)
{
oldCamera.tag = "Untagged";
}
}
player.tag = "Player";
var rb = player.AddComponent<Rigidbody>();
rb.useGravity = false;
rb.isKinematic = true;
rb.constraints = RigidbodyConstraints.FreezeRotation;
return player;
}
public Transform FindChildBone(Transform parent, string boneName)
{
if (parent == null) return null;
if (parent.name.ToLower().Contains(boneName.ToLower()))
return parent;
foreach (Transform child in parent)
{
var result = FindChildBone(child, boneName);
if (result != null) return result;
}
return null;
}
}
#if UNITY_EDITOR
[CustomEditor(typeof(PlayerBuilder))]
public class PlayerBuilderEditor : Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
var builder = (PlayerBuilder)target;
GUILayout.Space(15);
if (GUILayout.Button("Создать игрока в сцене"))
{
builder.BuildPlayer();
}
GUILayout.Space(10);
EditorGUILayout.HelpBox(
"1. Назначьте модель персонажа\n" +
"2. Настройте параметры движения\n" +
"3. Нажмите 'Создать игрока'",
MessageType.Info);
}
}
public static class PlayerBuilderMenu
{
[MenuItem("GameObject/Create Player/Quick Test Player", false, 0)]
public static void CreateQuickTestPlayer()
{
var go = new GameObject("TestPlayer");
var cc = go.AddComponent<CharacterController>();
cc.height = 1.8f;
cc.radius = 0.5f;
cc.center = new Vector3(0, 0.9f, 0);
var character = go.AddComponent<charecter>();
character.walkSpeed = 5f;
character.runSpeed = 9f;
character.crouchSpeed = 2.5f;
character.jumpHeight = 1.5f;
character.gravity = -9.81f;
GameObject body = GameObject.CreatePrimitive(PrimitiveType.Capsule);
body.name = "Body";
body.transform.SetParent(go.transform);
body.transform.localPosition = Vector3.zero;
GameObject.Destroy(body.GetComponent<Collider>());
GameObject head = new GameObject("Head");
head.transform.SetParent(go.transform);
head.transform.localPosition = new Vector3(0, 0.8f, 0);
character.head = head.transform;
GameObject camObj = new GameObject("PlayerCamera");
camObj.transform.SetParent(head.transform);
camObj.transform.localPosition = new Vector3(0, 0.1f, 0);
var cam = camObj.AddComponent<Camera>();
cam.tag = "MainCamera";
var existingCam = GameObject.FindGameObjectWithTag("MainCamera");
if (existingCam != null && existingCam != camObj)
{
GameObject.Destroy(existingCam);
}
go.tag = "Player";
go.transform.position = new Vector3(0, 2, -5);
Selection.activeGameObject = go;
}
}
#endif

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 47acf8134d655aa4da32eb965ee9590e

View File

@@ -1,31 +1,45 @@
using UnityEngine;
using UnityEngine.UI;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine.UI;
using UnityEngine.EventSystems;
/// <summary>
/// Инструмент создания полного игрока с опциональным инвентарём.
/// Tools > Player Builder
/// </summary>
public class PlayerBuilderTool : EditorWindow
{
// Модель
// ── Модель ────────────────────────────────────────────────────────────────
private GameObject characterModel;
private Transform headBone;
private Transform headBone;
private Transform handBone;
// Параметры движения
private float walkSpeed = 5f;
private float runSpeed = 9f;
// ── Параметры движения ────────────────────────────────────────────────────
private float walkSpeed = 5f;
private float runSpeed = 9f;
private float crouchSpeed = 2.5f;
private float jumpHeight = 1.5f;
private float gravity = -9.81f;
private float jumpHeight = 1.5f;
private float gravity = -9.81f;
// Коллайдер
private float height = 1.8f;
private float radius = 0.5f;
// ── Коллайдер ─────────────────────────────────────────────────────────────
private float height = 1.8f;
private float radius = 0.5f;
private float centerY = 0.9f;
// Камера
private bool addCamera = true;
// ── Камера ────────────────────────────────────────────────────────────────
private bool addCamera = true;
private float cameraHeight = 1.7f;
// Анимации
// ── Инвентарь ─────────────────────────────────────────────────────────────
private bool includeInventory = true;
private int inventorySlots = 36;
private int hotbarSlots = 9;
// ── Input Actions ─────────────────────────────────────────────────────────
private UnityEngine.InputSystem.InputActionAsset inputActions;
// ── Анимации ──────────────────────────────────────────────────────────────
private AnimationClip idleClip;
private AnimationClip walkClip;
private AnimationClip runClip;
@@ -33,9 +47,8 @@ public class PlayerBuilderTool : EditorWindow
private AnimationClip crouchIdleClip;
private AnimationClip crouchWalkClip;
// Аниматор контроллер
private AnimatorController animatorController;
private bool createAnimatorController = true;
// ── Scroll ────────────────────────────────────────────────────────────────
private Vector2 _scroll;
[MenuItem("Tools/Player Builder")]
public static void ShowWindow()
@@ -45,97 +58,121 @@ public class PlayerBuilderTool : EditorWindow
void OnGUI()
{
GUILayout.Label("Настройки персонажа", EditorStyles.boldLabel);
_scroll = EditorGUILayout.BeginScrollView(_scroll);
// Секция модели
GUILayout.Label("Конструктор Персонажа", EditorStyles.boldLabel);
// ── Секция модели ─────────────────────────────────────────────────────
GUILayout.Space(6);
GUILayout.Label("Модель", EditorStyles.miniBoldLabel);
characterModel = (GameObject)EditorGUILayout.ObjectField("Модель персонажа", characterModel, typeof(GameObject), false);
headBone = (Transform)EditorGUILayout.ObjectField("Кость головы", headBone, typeof(Transform), true);
headBone = (Transform)EditorGUILayout.ObjectField("Кость головы (Head)", headBone, typeof(Transform), true);
handBone = (Transform)EditorGUILayout.ObjectField("Кость руки (RightHand)", handBone, typeof(Transform), true);
GUILayout.Space(10);
if (characterModel != null && GUILayout.Button("Автопоиск костей Head/Hand"))
AutoFindBones();
// Секция движения
// ── Движение ──────────────────────────────────────────────────────────
GUILayout.Space(6);
GUILayout.Label("Движение", EditorStyles.miniBoldLabel);
walkSpeed = EditorGUILayout.FloatField("Скорость ходьбы", walkSpeed);
runSpeed = EditorGUILayout.FloatField("Скорость бега", runSpeed);
crouchSpeed = EditorGUILayout.FloatField("Скорость краулинга", crouchSpeed);
jumpHeight = EditorGUILayout.FloatField("Высота прыжка", jumpHeight);
gravity = EditorGUILayout.FloatField("Гравитация", gravity);
walkSpeed = EditorGUILayout.FloatField("Скорость ходьбы", walkSpeed);
runSpeed = EditorGUILayout.FloatField("Скорость бега", runSpeed);
crouchSpeed = EditorGUILayout.FloatField("Скорость присядки", crouchSpeed);
jumpHeight = EditorGUILayout.FloatField("Высота прыжка", jumpHeight);
gravity = EditorGUILayout.FloatField("Гравитация", gravity);
GUILayout.Space(10);
// Секция коллайдера
GUILayout.Label("Коллайдер", EditorStyles.miniBoldLabel);
height = EditorGUILayout.FloatField("Высота", height);
radius = EditorGUILayout.FloatField("Радиус", radius);
// ── Коллайдер ─────────────────────────────────────────────────────────
GUILayout.Space(6);
GUILayout.Label("Коллайдер CharacterController", EditorStyles.miniBoldLabel);
height = EditorGUILayout.FloatField("Высота", height);
radius = EditorGUILayout.FloatField("Радиус", radius);
centerY = EditorGUILayout.FloatField("Центр Y", centerY);
GUILayout.Space(10);
// Секция камеры
// ── Камера ────────────────────────────────────────────────────────────
GUILayout.Space(6);
GUILayout.Label("Камера", EditorStyles.miniBoldLabel);
addCamera = EditorGUILayout.Toggle("Добавить камеру", addCamera);
addCamera = EditorGUILayout.Toggle("Добавить камеру", addCamera);
if (addCamera)
{
cameraHeight = EditorGUILayout.Slider("Высота камеры", cameraHeight, 1f, 2f);
// ── Input Actions ─────────────────────────────────────────────────────
GUILayout.Space(6);
GUILayout.Label("Input System", EditorStyles.miniBoldLabel);
inputActions = (UnityEngine.InputSystem.InputActionAsset)EditorGUILayout.ObjectField(
"PlayerControls.inputactions",
inputActions,
typeof(UnityEngine.InputSystem.InputActionAsset),
false);
if (inputActions == null)
EditorGUILayout.HelpBox("Назначьте Assets/Input/PlayerControls.inputactions", MessageType.Warning);
// ── Инвентарь ─────────────────────────────────────────────────────────
GUILayout.Space(6);
GUILayout.Label("Инвентарь", EditorStyles.miniBoldLabel);
includeInventory = EditorGUILayout.Toggle("Включить инвентарь", includeInventory);
if (includeInventory)
{
inventorySlots = EditorGUILayout.IntField("Слотов инвентаря", inventorySlots);
hotbarSlots = EditorGUILayout.IntField("Слотов хотбара", hotbarSlots);
}
GUILayout.Space(10);
// Секция анимаций
GUILayout.Label("Анимации", EditorStyles.miniBoldLabel);
createAnimatorController = EditorGUILayout.Toggle("Создать контроллер", createAnimatorController);
idleClip = (AnimationClip)EditorGUILayout.ObjectField("Idle", idleClip, typeof(AnimationClip), false);
walkClip = (AnimationClip)EditorGUILayout.ObjectField("Walk", walkClip, typeof(AnimationClip), false);
runClip = (AnimationClip)EditorGUILayout.ObjectField("Run", runClip, typeof(AnimationClip), false);
jumpClip = (AnimationClip)EditorGUILayout.ObjectField("Jump", jumpClip, typeof(AnimationClip), false);
// ── Анимации ──────────────────────────────────────────────────────────
GUILayout.Space(6);
GUILayout.Label("Анимации (опционально)", EditorStyles.miniBoldLabel);
idleClip = (AnimationClip)EditorGUILayout.ObjectField("Idle", idleClip, typeof(AnimationClip), false);
walkClip = (AnimationClip)EditorGUILayout.ObjectField("Walk", walkClip, typeof(AnimationClip), false);
runClip = (AnimationClip)EditorGUILayout.ObjectField("Run", runClip, typeof(AnimationClip), false);
jumpClip = (AnimationClip)EditorGUILayout.ObjectField("Jump", jumpClip, typeof(AnimationClip), false);
crouchIdleClip = (AnimationClip)EditorGUILayout.ObjectField("Crouch Idle", crouchIdleClip, typeof(AnimationClip), false);
crouchWalkClip = (AnimationClip)EditorGUILayout.ObjectField("Crouch Walk", crouchWalkClip, typeof(AnimationClip), false);
if (characterModel != null)
{
var animator = characterModel.GetComponent<Animator>();
if (animator != null && animator.avatar != null)
{
EditorGUILayout.HelpBox("Модель содержит Animator - аватар будет скопирован", MessageType.Info);
}
}
GUILayout.Space(20);
// Кнопки действий
// ── Кнопки ────────────────────────────────────────────────────────────
GUI.color = Color.green;
if (GUILayout.Button("Создать в сцене"))
{
if (GUILayout.Button("Создать игрока" + (includeInventory ? " + Инвентарь" : "")))
BuildPlayer("Player");
}
GUI.color = Color.yellow;
if (GUILayout.Button("Создать тестового игрока (капсула)"))
BuildTestPlayer();
GUI.color = Color.cyan;
if (GUILayout.Button("Сохранить как префаб"))
{
SaveAsPrefab();
}
GUI.color = Color.yellow;
if (GUILayout.Button("Создать с тестовой моделью (капсула)"))
{
BuildTestPlayer();
}
GUI.color = Color.white;
GUILayout.Space(10);
EditorGUILayout.HelpBox(
"1. Назначьте модель персонажа\n" +
"2. Назначьте анимации (опционально)\n" +
"3. Настройте параметры\n" +
"4. Нажмите 'Создать в сцене'",
"1. Назначьте модель персонажа (опционально)\n" +
"2. Укажите кость головы и кость руки\n" +
"3. Назначьте PlayerControls.inputactions\n" +
"4. Нажмите 'Создать игрока'",
MessageType.Info);
EditorGUILayout.EndScrollView();
}
void BuildPlayer(string playerName)
// ── Автопоиск костей ─────────────────────────────────────────────────────
void AutoFindBones()
{
GameObject player = new GameObject(playerName);
if (characterModel == null) return;
headBone = FindChildBone(characterModel.transform, "head") ??
FindChildBone(characterModel.transform, "Head");
handBone = FindChildBone(characterModel.transform, "hand_r") ??
FindChildBone(characterModel.transform, "RightHand") ??
FindChildBone(characterModel.transform, "Hand_R") ??
FindChildBone(characterModel.transform, "hand");
Debug.Log($"[PlayerBuilder] Head: {headBone?.name ?? "не найдена"}, Hand: {handBone?.name ?? "не найдена"}");
}
// ── Создание игрока ───────────────────────────────────────────────────────
GameObject BuildPlayerGO(string playerName)
{
var player = new GameObject(playerName);
// CharacterController
var cc = player.AddComponent<CharacterController>();
@@ -145,31 +182,27 @@ public class PlayerBuilderTool : EditorWindow
// Скрипт персонажа
var character = player.AddComponent<charecter>();
character.walkSpeed = walkSpeed;
character.runSpeed = runSpeed;
character.walkSpeed = walkSpeed;
character.runSpeed = runSpeed;
character.crouchSpeed = crouchSpeed;
character.jumpHeight = jumpHeight;
character.gravity = gravity;
character.jumpHeight = jumpHeight;
character.gravity = gravity;
// Модель
GameObject model = null;
Transform resolvedHead = headBone;
Transform resolvedHand = handBone;
if (characterModel != null)
{
model = Instantiate(characterModel, player.transform);
var model = (GameObject)PrefabUtility.InstantiatePrefab(characterModel, player.transform);
model.transform.localPosition = Vector3.zero;
model.transform.localRotation = Quaternion.identity;
// Голова
if (headBone != null)
{
character.head = headBone;
}
else
{
character.head = FindChildBone(model.transform, "Head");
}
if (resolvedHead == null)
resolvedHead = FindChildBone(model.transform, "head") ?? FindChildBone(model.transform, "Head");
if (resolvedHand == null)
resolvedHand = FindChildBone(model.transform, "hand_r") ?? FindChildBone(model.transform, "RightHand");
//Animator
var modelAnimator = model.GetComponent<Animator>();
if (modelAnimator != null && modelAnimator.avatar != null)
{
@@ -178,121 +211,149 @@ public class PlayerBuilderTool : EditorWindow
}
}
// Голова если не найдена
if (character.head == null)
// Кость головы (или создаём точку)
if (resolvedHead == null)
{
GameObject headObj = new GameObject("HeadPoint");
headObj.transform.SetParent(model != null ? model.transform : player.transform);
var headObj = new GameObject("HeadPoint");
headObj.transform.SetParent(player.transform);
headObj.transform.localPosition = new Vector3(0, height - 0.2f, 0);
character.head = headObj.transform;
resolvedHead = headObj.transform;
}
character.head = resolvedHead;
// Камера
if (addCamera && character.head != null)
if (addCamera)
{
GameObject cameraObj = new GameObject("PlayerCamera");
cameraObj.transform.SetParent(character.head);
cameraObj.transform.localPosition = new Vector3(0, 0.1f, 0);
var camObj = new GameObject("PlayerCamera");
camObj.transform.SetParent(resolvedHead);
camObj.transform.localPosition = new Vector3(0, 0.1f, 0);
var cam = camObj.AddComponent<Camera>();
cam.tag = "MainCamera";
cam.nearClipPlane = 0.05f;
var cam = cameraObj.AddComponent<Camera>();
cam.tag = "MainCamera";
cam.nearClipPlane = 0.1f;
// Удалить старую
var oldCam = GameObject.FindWithTag("MainCamera");
if (oldCam != null && oldCam != cameraObj)
{
if (oldCam != null && oldCam != camObj)
DestroyImmediate(oldCam);
}
}
// Тег
player.tag = "Player";
// PlayerInput
var piGO = new GameObject("PlayerInput");
var pi = piGO.AddComponent<PlayerInput>();
if (inputActions != null) pi.actions = inputActions;
// Позиция
// HandEquipSystem
if (resolvedHand != null)
{
var handEquip = player.AddComponent<HandEquipSystem>();
handEquip.handBone = resolvedHand;
}
else
{
Debug.LogWarning("[PlayerBuilder] Кость руки не найдена — HandEquipSystem не добавлен. Назначьте вручную.");
}
player.tag = "Player";
player.transform.position = new Vector3(0, 2, -5);
Selection.activeGameObject = player;
Debug.Log($"[PlayerBuilder] Создан: {playerName}");
// Инвентарь
if (includeInventory)
SetupInventoryForPlayer(player);
return player;
}
void SaveAsPrefab()
void BuildPlayer(string name)
{
string path = EditorUtility.SaveFilePanelInProject(
"Сохранить префаб",
"Player",
"prefab",
"Выберите место");
if (string.IsNullOrEmpty(path)) return;
BuildPlayer("PlayerTemp");
GameObject player = GameObject.Find("PlayerTemp");
if (player == null)
{
Debug.LogError("Не удалось создать игрока");
return;
}
GameObject prefab = PrefabUtility.SaveAsPrefabAsset(player, path);
DestroyImmediate(player);
Selection.activeObject = prefab;
Debug.Log($"[PlayerBuilder] Сохранено: {path}");
var player = BuildPlayerGO(name);
Selection.activeGameObject = player;
Debug.Log($"[PlayerBuilder] Создан: {name}");
}
void BuildTestPlayer()
{
var savedModel = characterModel;
characterModel = null;
headBone = null;
BuildPlayer("TestPlayer");
var player = BuildPlayerGO("TestPlayer");
characterModel = savedModel;
GameObject player = Selection.activeGameObject;
if (player == null) return;
// Создаём визуальную часть
GameObject body = GameObject.CreatePrimitive(PrimitiveType.Capsule);
// Визуальная капсула
var body = GameObject.CreatePrimitive(PrimitiveType.Capsule);
body.name = "Body";
body.transform.SetParent(player.transform);
body.transform.localPosition = Vector3.zero;
body.transform.SetParent(player.transform, false);
body.transform.localPosition = new Vector3(0, centerY, 0);
body.transform.localScale = new Vector3(radius * 2, height / 2f, radius * 2);
DestroyImmediate(body.GetComponent<Collider>());
GameObject head = new GameObject("Head");
head.transform.SetParent(player.transform);
head.transform.localPosition = new Vector3(0, centerY + 0.2f, 0);
var character = player.GetComponent<charecter>();
if (character != null)
{
character.head = head.transform;
}
if (addCamera)
{
GameObject camObj = new GameObject("PlayerCamera");
camObj.transform.SetParent(head.transform);
camObj.transform.localPosition = new Vector3(0, 0.1f, 0);
var cam = camObj.AddComponent<Camera>();
cam.tag = "MainCamera";
var oldCam = GameObject.FindWithTag("MainCamera");
if (oldCam != null && oldCam != camObj)
{
DestroyImmediate(oldCam);
}
}
Debug.Log("[PlayerBuilder] Создан тестовый игрок");
Selection.activeGameObject = player;
Debug.Log("[PlayerBuilder] Создан тестовый игрок (капсула)");
}
void SaveAsPrefab()
{
string path = EditorUtility.SaveFilePanelInProject("Сохранить префаб", "Player", "prefab", "Выберите место");
if (string.IsNullOrEmpty(path)) return;
var temp = BuildPlayerGO("PlayerTemp");
var prefab = PrefabUtility.SaveAsPrefabAsset(temp, path);
DestroyImmediate(temp);
Selection.activeObject = prefab;
Debug.Log($"[PlayerBuilder] Сохранено: {path}");
}
// ── Создание инвентаря ────────────────────────────────────────────────────
void SetupInventoryForPlayer(GameObject player)
{
// EventSystem
if (FindObjectOfType<EventSystem>() == null)
{
var esGO = new GameObject("EventSystem");
esGO.AddComponent<EventSystem>();
esGO.AddComponent<StandaloneInputModule>();
}
// InventoryEvents
new GameObject("InventoryEvents").AddComponent<InventoryEvents>();
// InventoryManager
var imGO = new GameObject("InventoryManager");
var im = imGO.AddComponent<InventoryManager>();
im.inventorySize = inventorySlots;
// InventoryCanvas — дочерний объект Player
var canvasGO = new GameObject("InventoryCanvas");
canvasGO.transform.SetParent(player.transform, false);
var canvas = canvasGO.AddComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvas.sortingOrder = 100;
canvasGO.AddComponent<UnityEngine.UI.GraphicRaycaster>();
var uiBuilder = canvasGO.AddComponent<InventoryUIBuilder>();
uiBuilder.inventoryRows = inventorySlots / 9;
uiBuilder.hotbarSlots = hotbarSlots;
// Строим UI сразу до запуска игры
uiBuilder.BuildInventoryUI();
// Назначаем ссылки в InventoryManager
im.slots = uiBuilder.GetInventorySlots();
im.hotbarSlots = uiBuilder.GetHotbarSlots();
im.inventoryPanel = uiBuilder.inventoryPanelRect != null ? uiBuilder.inventoryPanelRect.gameObject : null;
im.inventoryCanvas = canvas;
if (im.inventoryPanel != null)
im.inventoryPanel.SetActive(false);
Debug.Log("[PlayerBuilder] Инвентарь создан и назначен.");
}
// ── Утилиты ───────────────────────────────────────────────────────────────
Transform FindChildBone(Transform parent, string boneName)
{
if (parent == null) return null;
if (parent.name.ToLower().Contains(boneName.ToLower()))
return parent;
if (parent.name.ToLower().Contains(boneName.ToLower())) return parent;
foreach (Transform child in parent)
{
var result = FindChildBone(child, boneName);

View File

@@ -1,29 +1,51 @@
using UnityEngine;
using System;
using UnityEngine.InputSystem;
/// <summary>
/// Централизованный ввод на основе New Input System.
/// Singleton, DontDestroyOnLoad.
/// Биндинги сохраняются в PlayerPrefs автоматически при изменении.
/// </summary>
public class PlayerInput : MonoBehaviour
{
public static PlayerInput Instance { get; private set; }
// Continuous axes
public float MouseX { get; private set; }
public float MouseY { get; private set; }
public float Horizontal { get; private set; }
public float Vertical { get; private set; }
// ── Ссылка на InputActionAsset ────────────────────────────────────────────
[Tooltip("Назначьте Assets/Input/PlayerControls.inputactions")]
public InputActionAsset actions;
// Continuous states
public bool Crouch { get; private set; }
public bool Run { get; private set; }
// ── Непрерывные значения ──────────────────────────────────────────────────
public Vector2 MoveInput { get; private set; }
public Vector2 LookInput { get; private set; }
public bool Crouch { get; private set; }
public bool Run { get; private set; }
// Events for discrete actions
public event Action OnToggleInventory;
public event Action OnDropPressed;
public event Action OnPickupPressed;
public event Action OnJumpPressed;
public event Action<int> OnHotbarSelect;
// ── Дискретные события ────────────────────────────────────────────────────
public event Action OnToggleInventory;
public event Action OnDropPressed;
public event Action OnPickupPressed;
public event Action OnJumpPressed;
public event Action<int> OnHotbarSelect;
public bool Enabled { get; set; } = true;
// ── Включение/выключение gameplay-ввода (инвентарь открыт) ───────────────
public bool Enabled
{
get => _gameplayMap != null && _gameplayMap.enabled;
set
{
if (_gameplayMap == null) return;
if (value) _gameplayMap.Enable();
else _gameplayMap.Disable();
}
}
// ── Приватные поля ────────────────────────────────────────────────────────
private InputActionMap _gameplayMap;
private InputActionMap _uiMap;
private const string BINDINGS_PREF_KEY = "PlayerBindings_v1";
// ── Lifecycle ─────────────────────────────────────────────────────────────
void Awake()
{
if (Instance == null)
@@ -34,46 +56,116 @@ public class PlayerInput : MonoBehaviour
else
{
Destroy(gameObject);
return;
}
if (actions == null)
{
actions = Resources.Load<InputActionAsset>("PlayerControls");
if (actions == null)
{
Debug.LogError("[PlayerInput] PlayerControls.inputactions не назначен и не найден в Resources!");
return;
}
}
// Создаём рабочую копию чтобы не менять оригинальный asset
actions = Instantiate(actions);
// Загрузить сохранённые биндинги
var saved = PlayerPrefs.GetString(BINDINGS_PREF_KEY, "");
if (!string.IsNullOrEmpty(saved))
actions.LoadBindingOverridesFromJson(saved);
_gameplayMap = actions.FindActionMap("Gameplay", throwIfNotFound: true);
_uiMap = actions.FindActionMap("UI", throwIfNotFound: true);
// Подписка: дискретные кнопки
_gameplayMap["Jump"].performed += _ => OnJumpPressed?.Invoke();
_gameplayMap["Pickup"].performed += _ => OnPickupPressed?.Invoke();
_gameplayMap["Drop"].performed += _ => OnDropPressed?.Invoke();
// UI map всегда активна — нет double-fire бага
_uiMap["ToggleInventory"].performed += _ => OnToggleInventory?.Invoke();
_uiMap["CloseInventory"].performed += _ => OnToggleInventory?.Invoke();
for (int i = 0; i < 9; i++)
{
int idx = i;
_gameplayMap[$"Hotbar{idx}"].performed += _ => OnHotbarSelect?.Invoke(idx);
}
_gameplayMap.Enable();
_uiMap.Enable();
}
void OnDestroy()
{
_gameplayMap?.Disable();
_uiMap?.Disable();
}
void Update()
{
// Always allow toggle inventory (Tab/Escape) even when input is disabled
if (Input.GetKeyDown(KeyCode.Tab) || Input.GetKeyDown(KeyCode.Escape))
OnToggleInventory?.Invoke();
if (_gameplayMap == null || !_gameplayMap.enabled) return;
if (!Enabled) return;
// Axes
MouseX = Input.GetAxis("Mouse X");
MouseY = Input.GetAxis("Mouse Y");
Horizontal = Input.GetAxisRaw("Horizontal");
Vertical = Input.GetAxisRaw("Vertical");
// Continuous keys
Crouch = Input.GetKey(KeyCode.C);
Run = Input.GetKey(KeyCode.LeftShift);
// Discrete actions
if (Input.GetButtonDown("Jump"))
OnJumpPressed?.Invoke();
if (Input.GetKeyDown(KeyCode.Tab))
OnToggleInventory?.Invoke();
if (Input.GetKeyDown(KeyCode.Q))
OnDropPressed?.Invoke();
if (Input.GetKeyDown(KeyCode.E))
OnPickupPressed?.Invoke();
for (int i = 0; i < 9; i++)
{
if (Input.GetKeyDown(KeyCode.Alpha1 + i))
{
OnHotbarSelect?.Invoke(i);
}
}
MoveInput = _gameplayMap["Move"].ReadValue<Vector2>();
LookInput = _gameplayMap["Look"].ReadValue<Vector2>();
Crouch = _gameplayMap["Crouch"].IsPressed();
Run = _gameplayMap["Run"].IsPressed();
}
// ── Сохранение/загрузка биндингов ────────────────────────────────────────
[ContextMenu("Save Bindings")]
public void SaveBindings()
{
if (actions == null) return;
PlayerPrefs.SetString(BINDINGS_PREF_KEY, actions.SaveBindingOverridesAsJson());
PlayerPrefs.Save();
Debug.Log("[PlayerInput] Биндинги сохранены");
}
[ContextMenu("Reset Bindings to Defaults")]
public void ResetBindings()
{
if (actions == null) return;
foreach (var action in actions)
action.RemoveAllBindingOverrides();
PlayerPrefs.DeleteKey(BINDINGS_PREF_KEY);
Debug.Log("[PlayerInput] Биндинги сброшены к умолчаниям");
}
/// <summary>
/// Интерактивный ремаппинг. Передайте имя action, имя map и callback для UI.
/// Пример: StartRebind("Jump", "Gameplay", () => UpdateUI());
/// </summary>
public void StartRebind(string actionName, string mapName, Action onComplete)
{
var map = actions?.FindActionMap(mapName);
var action = map?.FindAction(actionName);
if (action == null)
{
Debug.LogWarning($"[PlayerInput] Действие '{actionName}' в map '{mapName}' не найдено");
return;
}
action.Disable();
action.PerformInteractiveRebinding()
.WithControlsExcluding("<Mouse>/position")
.WithControlsExcluding("<Mouse>/delta")
.OnComplete(op =>
{
op.Dispose();
action.Enable();
SaveBindings();
onComplete?.Invoke();
})
.Start();
}
/// <summary>
/// Доступ к InputActionAsset для UI-ремаппинга и отображения текущих биндингов.
/// </summary>
public InputActionAsset GetActions() => actions;
}

View File

@@ -4,11 +4,11 @@ using UnityEngine;
public class charecter : MonoBehaviour
{
[Header("Movement")]
public float walkSpeed = 5f;
public float runSpeed = 9f;
public float walkSpeed = 5f;
public float runSpeed = 9f;
public float crouchSpeed = 2.5f;
public float jumpHeight = 1.5f;
public float gravity = -9.81f;
public float jumpHeight = 1.5f;
public float gravity = -9.81f;
[Header("References")]
[Tooltip("Head BONE of the character.")]
@@ -46,20 +46,21 @@ public class charecter : MonoBehaviour
_cc = GetComponent<CharacterController>();
_anim = GetComponentInChildren<Animator>();
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
_bodyYaw = transform.eulerAngles.y;
_headYaw = _bodyYaw;
_headBindPose = head.localRotation;
_headBindPose = head != null ? head.localRotation : Quaternion.identity;
// Ensure PlayerInput exists and subscribe to events
// Убедиться что PlayerInput существует
if (PlayerInput.Instance == null)
{
var go = new GameObject("PlayerInput");
go.AddComponent<PlayerInput>();
}
PlayerInput.Instance.OnPickupPressed += () => TryPickupNearbyItem();
PlayerInput.Instance.OnJumpPressed += () => { _jumpRequested = true; };
PlayerInput.Instance.OnPickupPressed += TryPickupNearbyItem;
PlayerInput.Instance.OnJumpPressed += () => { _jumpRequested = true; };
}
void Update()
@@ -68,55 +69,35 @@ public class charecter : MonoBehaviour
HandleMovement();
}
// ── Item Pickup ───────────────────────────────────────────────────────────
void HandlePickup()
void LateUpdate()
{
// Input moved to PlayerInput events
ApplyRotations();
}
// ── Item Pickup ───────────────────────────────────────────────────────────
void TryPickupNearbyItem()
{
float pickupRadius = 2.5f;
Collider[] colliders = Physics.OverlapSphere(transform.position, pickupRadius);
Debug.Log($"[Pickup] Checking {colliders.Length} colliders in radius {pickupRadius}");
WorldItem closestItem = null;
float closestDist = float.MaxValue;
foreach (var col in colliders)
{
var worldItem = col.GetComponent<WorldItem>();
if (worldItem != null)
if (worldItem == null) continue;
float dist = Vector3.Distance(transform.position, worldItem.transform.position);
if (dist < closestDist && dist <= worldItem.interactRange)
{
float dist = Vector3.Distance(transform.position, worldItem.transform.position);
Debug.Log($"[Pickup] Found WorldItem: {worldItem.itemData?.itemName ?? "null"} at dist {dist}");
if (dist < closestDist)
{
closestDist = dist;
closestItem = worldItem;
}
closestDist = dist;
closestItem = worldItem;
}
}
if (closestItem != null)
{
Debug.Log($"[Pickup] Trying to pick up: {closestItem.itemData?.itemName}");
bool pickedUp = closestItem.TryPickupByButton();
if (pickedUp)
{
Debug.Log($"Picked up: {closestItem.itemData.itemName}");
}
}
else
{
Debug.Log("[Pickup] No WorldItem found nearby");
}
}
void LateUpdate()
{
ApplyRotations();
closestItem.TryPickupByButton();
}
// ── Mouse look ────────────────────────────────────────────────────────────
@@ -125,21 +106,21 @@ public class charecter : MonoBehaviour
var input = PlayerInput.Instance;
if (input != null)
{
_headYaw += input.MouseX * mouseSensitivity;
_pitch -= input.MouseY * mouseSensitivity;
_headYaw += input.LookInput.x * mouseSensitivity;
_pitch -= input.LookInput.y * mouseSensitivity;
}
_pitch = Mathf.Clamp(_pitch, -maxPitch, maxPitch);
_pitch = Mathf.Clamp(_pitch, -maxPitch, maxPitch);
}
// ── Movement ──────────────────────────────────────────────────────────────
void HandleMovement()
{
var input = PlayerInput.Instance;
float h = input != null ? input.Horizontal : Input.GetAxisRaw("Horizontal");
float v = input != null ? input.Vertical : Input.GetAxisRaw("Vertical");
float h = input != null ? input.MoveInput.x : 0f;
float v = input != null ? input.MoveInput.y : 0f;
bool isCrouching = input != null ? input.Crouch : Input.GetKey(KeyCode.C);
bool isRunning = input != null ? input.Run && !isCrouching : Input.GetKey(KeyCode.LeftShift) && !isCrouching;
bool isCrouching = input != null && input.Crouch;
bool isRunning = input != null && input.Run && !isCrouching;
bool isMoving = (h * h + v * v) > 0.01f;
float speed = isCrouching ? crouchSpeed : isRunning ? runSpeed : walkSpeed;
@@ -148,7 +129,7 @@ public class charecter : MonoBehaviour
if (isMoving)
_bodyYaw = Mathf.MoveTowardsAngle(_bodyYaw, _headYaw, bodyAlignSpeed * Time.deltaTime);
// Head-drag
// Head-drag clamp
float offset = Mathf.DeltaAngle(_bodyYaw, _headYaw);
float clampedOffset = Mathf.Clamp(offset, -maxHeadYaw, maxHeadYaw);
_bodyYaw += offset - clampedOffset;
@@ -176,12 +157,11 @@ public class charecter : MonoBehaviour
moveDir.y = _verticalVelocity;
_cc.Move(moveDir * speed * Time.deltaTime);
// Animator params
// Animator
if (_anim != null)
{
// Speed: 0 = idle, 0.5 = walk, 1 = run
float animSpeed = !isMoving ? 0f : isRunning ? 1f : 0.5f;
_anim.SetFloat(HashSpeed, animSpeed, 0.1f, Time.deltaTime);
_anim.SetFloat(HashSpeed, animSpeed, 0.1f, Time.deltaTime);
_anim.SetBool (HashIsMoving, isMoving);
_anim.SetBool (HashIsCrouching, isCrouching);
_anim.SetBool (HashIsGrounded, _cc.isGrounded);
@@ -193,10 +173,11 @@ public class charecter : MonoBehaviour
{
transform.rotation = Quaternion.Euler(0f, _bodyYaw, 0f);
float headOffset = Mathf.DeltaAngle(_bodyYaw, _headYaw);
Quaternion yawRot = Quaternion.Euler(0f, headOffset, 0f);
Quaternion pitchRot = Quaternion.Euler(_pitch, 0f, 0f);
head.localRotation = _headBindPose * yawRot * pitchRot;
if (head == null) return;
float headOffset = Mathf.DeltaAngle(_bodyYaw, _headYaw);
Quaternion yawRot = Quaternion.Euler(0f, headOffset, 0f);
Quaternion pitchRot = Quaternion.Euler(_pitch, 0f, 0f);
head.localRotation = _headBindPose * yawRot * pitchRot;
}
}

107
CLAUDE.md
View File

@@ -4,64 +4,69 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview
This is a Unity game project with two major systems:
1. **Character Controller** - First/third-person movement with animations
2. **Inventory System** - Full inventory with hotbar, drag-drop, crafting, and item generation
Unity game project (no CLI build commands — use the Unity Editor). Two major systems:
1. **Character Controller** — first/third-person movement with animations
2. **Inventory System** hotbar, drag-drop, crafting, world items, save/load
## Controls
- **Movement**: WASD
- **Look**: Mouse
- **Run**: Left Shift
- **Crouch**: C
- **Jump**: Space
- **Toggle Inventory**: Tab
- **Close Inventory**: Escape
- **Hotbar Selection**: 1-9 keys
- **Drop Item**: Q
## Key Files and Structure
### Character Controller (`Assets/charecter-controller/scripts/`)
- `charecter.cs` - Main character controller with movement, jumping, crouching, mouse look
- `PlayerBuilder.cs` - Player setup utility
- `PlayerBuilderTool.cs` - Editor tool for building player objects
### Inventory System (`Assets/Inventory/Scripts/`)
- `InventoryManager.cs` - Core inventory logic (singleton, item operations, save/load)
- `Slot.cs` - Individual inventory slot with stacking logic
- `ItemData.cs` - Item definition (id, name, icon, maxStack, etc.)
- `InventoryEvents.cs` - Centralized event system for inventory changes
- `InventoryItemFactory.cs` - Creates dropped items in the world
- `InventoryDragDrop.cs` - Drag and drop UI handling
- `CraftingManager.cs` - Recipe-based crafting system
- `InventoryUIBuilder.cs` - Runtime UI generation
### Scene
- `Assets/Scenes/SampleScene.unity` - Main test scene
| Action | Key |
|---|---|
| Move | WASD |
| Look | Mouse |
| Run | Left Shift |
| Crouch | C |
| Jump | Space |
| Pickup | E |
| Toggle Inventory | Tab |
| Close Inventory | Escape |
| Hotbar Select | 19 |
| Drop Item | Q |
## Architecture
### Character Controller Design
- Uses Unity's CharacterController component for collision
- Head-body separation: body rotates toward look direction, head can offset within limits
- Animator parameters use `Animator.StringToHash` for performance (Speed, IsMoving, IsCrouching, IsGrounded, Jump)
- Physics-based jumping: `v = sqrt(jumpHeight * -2 * gravity)`
- All movement is framerate-independent with `Time.deltaTime`
### Input Layer — `PlayerInput` (singleton, DontDestroyOnLoad)
All input is centralised in `Assets/charecter-controller/scripts/PlayerInput.cs`. It exposes:
- Continuous properties: `MouseX`, `MouseY`, `Horizontal`, `Vertical`, `Crouch`, `Run`
- C# events: `OnJumpPressed`, `OnPickupPressed`, `OnToggleInventory`, `OnDropPressed`, `OnHotbarSelect`
- `Enabled` flag — set to `false` when the inventory is open to block gameplay input
### Inventory Architecture
- **Singleton pattern**: `InventoryManager.Instance` persists across scenes (DontDestroyOnLoad)
- **Dual storage**: Main inventory (36 slots) + Hotbar (9 slots, indices 0-8)
- **Item lookup**: `_itemDict` dictionary maps item IDs to ItemData
- **Stacking**: Items stack up to `maxStack` defined in ItemData
- **Events**: InventoryEvents provides OnItemAdded, OnItemRemoved, OnInventoryOpened/Closed
Both `charecter.cs` and `InventoryManager.cs` subscribe to `PlayerInput.Instance` events in `Start()`. Never read `Input.*` directly in new code — go through `PlayerInput`.
### Inventory Flow
1. Items added via `AddItem(itemId, amount)` - tries existing stacks first, then empty slots
2. Removed via `RemoveItem(itemId, amount)` - returns true if successful
3. Hotbar selection via `SelectHotbarSlot(index)` - updates visual selection
4. Item dropped via `DropSelectedItem()` - spawns world object via InventoryItemFactory
5. JSON serialization via `ExportToJson()` / `ImportFromJson()`
### Character Controller — `charecter.cs`
- Requires `CharacterController` component
- Head-bone / body separation: body yaw tracks camera yaw when moving; head offset is clamped to `±maxHeadYaw`
- Bone rotations applied in `LateUpdate` via `ApplyRotations()`
- Animator hashes cached as `static readonly int` fields; animator Speed param: 0 = idle, 0.5 = walk, 1 = run
- Item pickup radius-cast in `TryPickupNearbyItem()` (2.5 m sphere, closest `WorldItem` wins)
### Inventory System
**Singleton chain** (all DontDestroyOnLoad, all auto-created if missing):
```
PlayerInput → InventoryManager → InventoryItemFactory
→ InventoryEvents
```
**InventoryManager** (`Assets/Inventory/Scripts/InventoryManager.cs`)
- `slots[]` (36) = main inventory; `hotbarSlots[]` (9, indices 0-8)
- `_itemDict` maps `ItemData.id → ItemData`; rebuilt via `RebuildItemDictionary()` — call this after runtime DB changes
- `AddItem()` fill order: existing main stacks → empty main slots → existing hotbar stacks → empty hotbar slots
- Save/load: JSON to `Application.persistentDataPath/inventory_save.json`; auto-save on `OnApplicationQuit`
**ItemData** — ScriptableObject (`Assets > Create > Inventory > Item`). `id` is auto-assigned as GUID on first `OnEnable`. ItemType enum: `Block, Weapon, Armor, Consumable, Tool, Material`.
**InventoryItemFactory** — creates/drops `WorldItem` GameObjects. Needs an `ItemFactoryConfig` ScriptableObject (`Assets > Create > Inventory > Item Factory Config`) assigned in the Inspector for prefab-based spawning; falls back to a primitive cube if no prefab is configured.
**CraftingManager** — recipe key is sorted comma-joined ingredient IDs (`"id1,id2,id3"`). Recipes can be added at runtime via `AddRecipe()`.
**InventoryUIBuilder** — generates the entire inventory UI programmatically at runtime (Canvas, GridLayoutGroup, Slot components, drag-drop handlers). Assigns `InventoryManager.Instance.slots` and `.hotbarSlots` directly. Do not assign these arrays in the Inspector when using this builder.
**InventorySetup** — convenience MonoBehaviour; add to a "GameManager" object to auto-create all managers and UI on scene start. Has an Editor button in the Inspector. Skips creation if `InventoryManager` already exists.
### Scene Setup
`Assets/Scenes/SampleScene.unity` is the only scene. Typical hierarchy:
- `GameManager` with `InventorySetup` (or manually placed `InventoryManager` + `InventoryUIBuilder`)
- Player with `charecter.cs` + `CharacterController` + Animator + head bone reference
### Meta Files
Unity requires .meta files for all assets - always commit them alongside their assets.
Unity requires `.meta` files for all assets always commit them alongside their assets.

View File

@@ -8,6 +8,7 @@
"com.unity.collab-proxy": "2.12.4",
"com.unity.ide.rider": "3.0.39",
"com.unity.ide.visualstudio": "2.0.27",
"com.unity.inputsystem": "1.19.0",
"com.unity.multiplayer.center": "1.0.1",
"com.unity.purchasing": "4.15.0",
"com.unity.test-framework": "1.6.0",

View File

@@ -75,6 +75,15 @@
},
"url": "https://packages.unity.com"
},
"com.unity.inputsystem": {
"version": "1.19.0",
"depth": 0,
"source": "registry",
"dependencies": {
"com.unity.modules.uielements": "1.0.0"
},
"url": "https://packages.unity.com"
},
"com.unity.multiplayer.center": {
"version": "1.0.1",
"depth": 0,

View File

@@ -5,5 +5,5 @@ EditorBuildSettings:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Scenes: []
m_configObjects: {}
m_UseUCBPForAssetBundles: 0
m_configObjects:
com.unity.input.settings.actions: {fileID: -944628639613478452, guid: 7d782555f7e02f84ca2502d52361f19a, type: 3}

View File

@@ -41,7 +41,6 @@ PlayerSettings:
height: 1
m_SplashScreenLogos: []
m_VirtualRealitySplashScreen: {fileID: 0}
m_HolographicTrackingLossScreen: {fileID: 0}
defaultScreenWidth: 1920
defaultScreenHeight: 1080
defaultScreenWidthWeb: 960
@@ -148,12 +147,10 @@ PlayerSettings:
preloadedAssets: []
metroInputSource: 0
wsaTransparentSwapchain: 0
m_HolographicPauseOnTrackingLoss: 1
xboxOneDisableKinectGpuReservation: 1
xboxOneEnable7thCore: 1
vrSettings:
enable360StereoCapture: 0
isWsaHolographicRemotingEnabled: 0
enableFrameTimingStats: 0
enableOpenGLProfilerGPURecorders: 1
allowHDRDisplaySupport: 0
@@ -762,8 +759,9 @@ PlayerSettings:
qnxGraphicConfPath:
apiCompatibilityLevel: 6
captureStartupLogs: {}
activeInputHandler: 0
activeInputHandler: 2
windowsGamepadBackendHint: 0
enableDirectStorage: 0
cloudProjectId:
framebufferDepthMemorylessMode: 0
qualitySettingsNames: []