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 fileFormatVersion: 2
guid: 91470f1965cf83a4a8c49831ec2f7691 guid: 037877a8b07e2d84e8a888f939cfbab6
NativeFormatImporter: folderAsset: yes
DefaultImporter:
externalObjects: {} externalObjects: {}
mainObjectFileID: 11400000
userData: userData:
assetBundleName: assetBundleName:
assetBundleVariant: 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 fileFormatVersion: 2
guid: 223ec2dc359ac0845957ab0b29284414 guid: 9f444e9048146434087c3db3550617f9
PrefabImporter: PrefabImporter:
externalObjects: {} externalObjects: {}
userData: userData:

View File

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

View File

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

View File

@@ -1,302 +1,133 @@
using UnityEngine; using UnityEngine;
using UnityEditor; using UnityEditor;
using UnityEngine.UI; using UnityEngine.UI;
using UnityEngine.EventSystems;
using TMPro; using TMPro;
public class InventorySceneSetup /// <summary>
/// Tools > Setup Complete Scene — создаёт всё до запуска игры (editor-time).
/// Canvas инвентаря помещается внутрь Player.
/// InventoryManager получает все ссылки сразу.
/// </summary>
public static class InventorySceneSetup
{ {
[MenuItem("Tools/Create Player")] [MenuItem("Tools/Setup Complete Scene")]
public static void CreatePlayer() 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 go = new GameObject("Player");
var cc = go.AddComponent<CharacterController>(); var cc = go.AddComponent<CharacterController>();
cc.height = 1.8f; cc.height = 1.8f;
cc.radius = 0.5f; cc.radius = 0.5f;
cc.center = new Vector3(0, 0.9f, 0); cc.center = new Vector3(0, 0.9f, 0);
var character = go.AddComponent<charecter>(); var ch = go.AddComponent<charecter>();
character.walkSpeed = 5f; ch.walkSpeed = 5f;
character.runSpeed = 9f; ch.runSpeed = 9f;
character.crouchSpeed = 2.5f; ch.crouchSpeed = 2.5f;
character.jumpHeight = 1.5f; ch.jumpHeight = 1.5f;
character.gravity = -9.81f; ch.gravity = -9.81f;
GameObject body = GameObject.CreatePrimitive(PrimitiveType.Capsule); // Визуальная капсула
var body = GameObject.CreatePrimitive(PrimitiveType.Capsule);
body.name = "Body"; body.name = "Body";
body.transform.SetParent(go.transform); body.transform.SetParent(go.transform, false);
body.transform.localPosition = Vector3.zero; Object.DestroyImmediate(body.GetComponent<Collider>());
GameObject.Destroy(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); 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); camObj.transform.localPosition = new Vector3(0, 0.1f, 0);
var cam = camObj.AddComponent<Camera>(); var cam = camObj.AddComponent<Camera>();
cam.tag = "MainCamera"; cam.tag = "MainCamera";
cam.nearClipPlane = 0.05f;
var existingCam = GameObject.FindGameObjectWithTag("MainCamera"); var oldCam = GameObject.FindWithTag("MainCamera");
if (existingCam != null && existingCam != camObj) if (oldCam != null && oldCam != camObj)
{ Object.DestroyImmediate(oldCam);
GameObject.Destroy(existingCam);
}
go.tag = "Player"; go.tag = "Player";
go.transform.position = new Vector3(0, 2, -5); go.transform.position = new Vector3(0, 2, -5);
return go;
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;
} }
} }

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

View File

@@ -1,30 +1,33 @@
using UnityEngine; using UnityEngine;
using UnityEngine.UI;
using UnityEditor; using UnityEditor;
using TMPro; using TMPro;
/// <summary>
/// Инструмент создания предметов.
/// Tools > Item Builder
/// </summary>
public class ItemBuilderTool : EditorWindow public class ItemBuilderTool : EditorWindow
{ {
// Основные данные // Основные данные
private string itemName = "NewItem"; private string itemName = "NewItem";
private string itemId = ""; private string itemId = "";
private string description = ""; private string description = "";
private ItemType itemType = ItemType.Material; private ItemType itemType = ItemType.Material;
// Визуал // Визуал
private GameObject modelPrefab; private GameObject modelPrefab;
private Sprite icon; private Sprite icon;
private Color previewColor = Color.gray; private Color previewColor = Color.gray;
// Свойства // Свойства
private int maxStack = 64; private int maxStack = 64;
private int value = 1; private int value = 1;
private float weight = 1f; private float weight = 1f;
private bool isDroppable = true; private bool isDroppable = true;
private bool isStackable = true; private bool isStackable = true;
// Префаб // Pickup
private GameObject worldItemPrefab; private float interactRange = 2.5f;
[MenuItem("Tools/Item Builder")] [MenuItem("Tools/Item Builder")]
public static void ShowWindow() public static void ShowWindow()
@@ -38,67 +41,59 @@ public class ItemBuilderTool : EditorWindow
// Основные данные // Основные данные
GUILayout.Label("Основное", EditorStyles.miniBoldLabel); GUILayout.Label("Основное", EditorStyles.miniBoldLabel);
itemName = EditorGUILayout.TextField("Название", itemName); itemName = EditorGUILayout.TextField("Название", itemName);
itemId = EditorGUILayout.TextField("ID", itemId); itemId = EditorGUILayout.TextField("ID (оставь пустым для GUID)", itemId);
description = EditorGUILayout.TextField("Описание", description); description = EditorGUILayout.TextField("Описание", description);
itemType = (ItemType)EditorGUILayout.EnumPopup("Тип предмета", itemType); itemType = (ItemType)EditorGUILayout.EnumPopup("Тип предмета", itemType);
GUILayout.Space(10); GUILayout.Space(10);
// Визуал // Визуал
GUILayout.Label("Визуал", EditorStyles.miniBoldLabel); GUILayout.Label("Визуал", EditorStyles.miniBoldLabel);
modelPrefab = (GameObject)EditorGUILayout.ObjectField("3D модель", modelPrefab, typeof(GameObject), false); modelPrefab = (GameObject)EditorGUILayout.ObjectField("3D модель (префаб)", modelPrefab, typeof(GameObject), false);
icon = (Sprite)EditorGUILayout.ObjectField("Иконка", icon, typeof(Sprite), false); icon = (Sprite)EditorGUILayout.ObjectField("Иконка", icon, typeof(Sprite), false);
if (icon == null) if (icon == null)
{ previewColor = EditorGUILayout.ColorField("Цвет иконки (заглушка)", previewColor);
previewColor = EditorGUILayout.ColorField("Цвет иконки", previewColor);
}
GUILayout.Space(10); GUILayout.Space(10);
// Свойства // Свойства
GUILayout.Label("Свойства", EditorStyles.miniBoldLabel); GUILayout.Label("Свойства", EditorStyles.miniBoldLabel);
maxStack = EditorGUILayout.IntField("Макс. стек", maxStack); maxStack = EditorGUILayout.IntField("Макс. стек", maxStack);
value = EditorGUILayout.IntField("Цена", value); value = EditorGUILayout.IntField("Цена", value);
weight = EditorGUILayout.FloatField("Вес", weight); weight = EditorGUILayout.FloatField("Вес", weight);
isDroppable = EditorGUILayout.Toggle("Можно выбросить", isDroppable); isDroppable = EditorGUILayout.Toggle("Можно выбросить", isDroppable);
isStackable = EditorGUILayout.Toggle("Стек", isStackable); isStackable = EditorGUILayout.Toggle("Стекируемый", isStackable);
GUILayout.Space(10); GUILayout.Space(10);
// Префаб GUILayout.Label("Подбор", EditorStyles.miniBoldLabel);
GUILayout.Label("Префаб предмета в мире", EditorStyles.miniBoldLabel); interactRange = EditorGUILayout.FloatField("Дальность подбора (E)", interactRange);
worldItemPrefab = (GameObject)EditorGUILayout.ObjectField("WorldItem Prefab", worldItemPrefab, typeof(GameObject), false);
GUILayout.Space(20); GUILayout.Space(20);
// Кнопки // Кнопки
GUI.color = Color.green; GUI.color = Color.green;
if (GUILayout.Button("Создать ItemData")) if (GUILayout.Button("Создать ItemData (ScriptableObject)"))
{
CreateItemData(); CreateItemData();
}
GUI.color = Color.cyan; GUI.color = Color.cyan;
if (GUILayout.Button("Создать WorldItem в сцене")) if (GUILayout.Button("Создать WorldItem в сцене"))
{
CreateWorldItem(); CreateWorldItem();
}
GUI.color = Color.yellow; GUI.color = Color.yellow;
if (GUILayout.Button("Создать префаб")) if (GUILayout.Button("Создать WorldItem как префаб"))
{
CreateItemPrefab(); CreateItemPrefab();
}
GUI.color = Color.white; GUI.color = Color.white;
GUILayout.Space(10); GUILayout.Space(10);
EditorGUILayout.HelpBox( EditorGUILayout.HelpBox(
"1. Заполните данные предмета\n" + "Подбор предметов — только по кнопке E (авто-pickup при касании убран намеренно).\n" +
"2. Назначьте модель и иконку\n" + "1. Заполните данные → Создайте ItemData\n" +
"3. Создайте ItemData или префаб", "2. Назначьте ItemData в InventoryManager.itemDatabase\n" +
"3. Создайте WorldItem для размещения предметов на сцене",
MessageType.Info); MessageType.Info);
} }
@@ -112,105 +107,101 @@ public class ItemBuilderTool : EditorWindow
var item = ScriptableObject.CreateInstance<ItemData>(); var item = ScriptableObject.CreateInstance<ItemData>();
item.id = string.IsNullOrEmpty(itemId) ? GUID.Generate().ToString() : itemId; item.id = string.IsNullOrEmpty(itemId) ? System.Guid.NewGuid().ToString() : itemId;
item.itemName = itemName; item.itemName = itemName;
item.description = description; item.description = description;
item.type = itemType; item.type = itemType;
item.modelPrefab = modelPrefab; item.modelPrefab = modelPrefab;
item.icon = icon != null ? icon : CreatePlaceholderIcon(); item.icon = icon != null ? icon : CreatePlaceholderIcon();
item.maxStack = maxStack; item.maxStack = maxStack;
item.value = value; item.value = value;
item.weight = weight; item.weight = weight;
item.isDroppable = isDroppable; item.isDroppable = isDroppable;
item.isStackable = isStackable; item.isStackable = isStackable;
// Сохраняем string folder = "Assets/Inventory/Items";
string path = $"Assets/Inventory/Items/{itemName}.asset";
string folder = System.IO.Path.GetDirectoryName(path);
if (!System.IO.Directory.Exists(folder)) if (!System.IO.Directory.Exists(folder))
{
System.IO.Directory.CreateDirectory(folder); System.IO.Directory.CreateDirectory(folder);
}
string path = $"{folder}/{itemName}.asset";
AssetDatabase.CreateAsset(item, path); AssetDatabase.CreateAsset(item, path);
AssetDatabase.SaveAssets(); AssetDatabase.SaveAssets();
Selection.activeObject = item; Selection.activeObject = item;
Debug.Log($"[ItemBuilder] Создан: {itemName}"); Debug.Log($"[ItemBuilder] Создан: {itemName} → {path}");
} }
void CreateWorldItem() void CreateWorldItem()
{ {
if (modelPrefab == null) var itemGO = new GameObject(itemName);
// Визуал
GameObject visual;
if (modelPrefab != null)
{ {
// Создаём простой визуал visual = (GameObject)PrefabUtility.InstantiatePrefab(modelPrefab, itemGO.transform);
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;
} }
else else
{ {
var itemGO = Instantiate(modelPrefab); visual = GameObject.CreatePrimitive(PrimitiveType.Cube);
itemGO.name = itemName; visual.transform.SetParent(itemGO.transform, false);
itemGO.AddComponent<WorldItem>(); visual.transform.localScale = Vector3.one * 0.3f;
itemGO.transform.position = Vector3.zero; DestroyImmediate(visual.GetComponent<Collider>());
Selection.activeGameObject = itemGO;
} }
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}"); Debug.Log($"[ItemBuilder] Создан WorldItem: {itemName}");
} }
void CreateItemPrefab() void CreateItemPrefab()
{ {
string path = EditorUtility.SaveFilePanelInProject( string path = EditorUtility.SaveFilePanelInProject(
"Сохранить префаб", "Сохранить WorldItem префаб",
itemName, itemName,
"prefab", "prefab",
"Выберите место"); "Выберите место сохранения");
if (string.IsNullOrEmpty(path)) return; if (string.IsNullOrEmpty(path)) return;
GameObject prefab; var prefabGO = new GameObject(itemName);
// Визуал
GameObject visual;
if (modelPrefab != null) if (modelPrefab != null)
{ {
prefab = Instantiate(modelPrefab); visual = (GameObject)PrefabUtility.InstantiatePrefab(modelPrefab, prefabGO.transform);
prefab.name = itemName;
} }
else else
{ {
prefab = new GameObject(itemName); visual = GameObject.CreatePrimitive(PrimitiveType.Cube);
var visual = GameObject.CreatePrimitive(PrimitiveType.Cube); visual.transform.SetParent(prefabGO.transform, false);
visual.name = "Visual";
visual.transform.SetParent(prefab.transform);
visual.transform.localPosition = Vector3.zero;
visual.transform.localScale = Vector3.one * 0.3f; visual.transform.localScale = Vector3.one * 0.3f;
DestroyImmediate(visual.GetComponent<Collider>()); 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>(); var col = prefabGO.AddComponent<SphereCollider>();
if (col == null) col.radius = 0.5f;
{ col.isTrigger = true;
var sphere = prefab.AddComponent<SphereCollider>();
sphere.radius = 0.5f;
sphere.isTrigger = true;
}
GameObject savedPrefab = PrefabUtility.SaveAsPrefabAsset(prefab, path); GameObject savedPrefab = PrefabUtility.SaveAsPrefabAsset(prefabGO, path);
DestroyImmediate(prefab); DestroyImmediate(prefabGO);
Selection.activeObject = savedPrefab; Selection.activeObject = savedPrefab;
Debug.Log($"[ItemBuilder] Префаб сохранён: {path}"); Debug.Log($"[ItemBuilder] Префаб сохранён: {path}");
@@ -218,12 +209,10 @@ public class ItemBuilderTool : EditorWindow
Sprite CreatePlaceholderIcon() Sprite CreatePlaceholderIcon()
{ {
var tex = new Texture2D(64, 64); var tex = new Texture2D(64, 64);
var colors = new Color[64 * 64]; var colors = new Color[64 * 64];
for (int i = 0; i < colors.Length; i++) for (int i = 0; i < colors.Length; i++)
{
colors[i] = previewColor; colors[i] = previewColor;
}
tex.SetPixels(colors); tex.SetPixels(colors);
tex.Apply(); tex.Apply();
return Sprite.Create(tex, new Rect(0, 0, 64, 64), new Vector2(0.5f, 0.5f)); 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; using UnityEditor;
#endif #endif
/// <summary>
/// Компонент для генерации ItemData ScriptableObject из настроек в Inspector.
/// Добавьте на GameObject, заполните поля и нажмите "Generate ItemData" в Inspector.
/// </summary>
public class ItemGenerator : MonoBehaviour public class ItemGenerator : MonoBehaviour
{ {
[Header("Item Template")] [Header("Item Template")]
@@ -26,27 +30,25 @@ public class ItemGenerator : MonoBehaviour
public ItemData GenerateItemData() public ItemData GenerateItemData()
{ {
if (string.IsNullOrEmpty(itemId)) if (string.IsNullOrEmpty(itemId))
{
itemId = System.Guid.NewGuid().ToString(); itemId = System.Guid.NewGuid().ToString();
}
var itemData = ScriptableObject.CreateInstance<ItemData>(); var itemData = ScriptableObject.CreateInstance<ItemData>();
itemData.id = itemId; itemData.id = itemId;
itemData.itemName = itemName; itemData.itemName = itemName;
itemData.description = description; itemData.description = description;
itemData.type = itemType; itemData.type = itemType;
itemData.modelPrefab = modelPrefab; itemData.modelPrefab = modelPrefab;
itemData.icon = icon; itemData.icon = icon;
itemData.maxStack = maxStack; itemData.maxStack = maxStack;
itemData.isCraftable = isCraftable; itemData.isCraftable = isCraftable;
if (isCraftable && craftIngredients != null && craftIngredients.Length > 0) if (isCraftable && craftIngredients != null && craftIngredients.Length > 0)
{ {
itemData.craftingRecipe = new ItemData.CraftingRecipe itemData.craftingRecipe = new ItemData.CraftingRecipe
{ {
ingredients = craftIngredients, ingredients = craftIngredients,
amounts = craftAmounts, amounts = craftAmounts,
resultId = craftResultId, resultId = craftResultId,
resultAmount = craftResultAmount resultAmount = craftResultAmount
}; };
} }
@@ -57,187 +59,15 @@ public class ItemGenerator : MonoBehaviour
public static List<ItemData> GenerateAll(List<ItemGenerator> generators) public static List<ItemData> GenerateAll(List<ItemGenerator> generators)
{ {
var items = new List<ItemData>(); var items = new List<ItemData>();
foreach (var generator in generators) foreach (var gen in generators)
{ {
if (generator != null) if (gen != null)
{ items.Add(gen.GenerateItemData());
items.Add(generator.GenerateItemData());
}
} }
return items; 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 #if UNITY_EDITOR
[CustomEditor(typeof(ItemGenerator))] [CustomEditor(typeof(ItemGenerator))]
public class ItemGeneratorEditor : Editor public class ItemGeneratorEditor : Editor
@@ -246,16 +76,22 @@ public class ItemGeneratorEditor : Editor
{ {
base.OnInspectorGUI(); base.OnInspectorGUI();
GUILayout.Space(8);
if (GUILayout.Button("Generate ItemData")) if (GUILayout.Button("Generate ItemData"))
{ {
var generator = (ItemGenerator)target; 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.CreateAsset(itemData, path);
AssetDatabase.SaveAssets(); 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;
using UnityEngine.UI;
using UnityEditor; using UnityEditor;
using UnityEditor.Animations; using UnityEditor.Animations;
using UnityEngine.UI;
using UnityEngine.EventSystems;
/// <summary>
/// Инструмент создания полного игрока с опциональным инвентарём.
/// Tools > Player Builder
/// </summary>
public class PlayerBuilderTool : EditorWindow public class PlayerBuilderTool : EditorWindow
{ {
// Модель // ── Модель ────────────────────────────────────────────────────────────────
private GameObject characterModel; private GameObject characterModel;
private Transform headBone; private Transform headBone;
private Transform handBone;
// Параметры движения // ── Параметры движения ────────────────────────────────────────────────────
private float walkSpeed = 5f; private float walkSpeed = 5f;
private float runSpeed = 9f; private float runSpeed = 9f;
private float crouchSpeed = 2.5f; private float crouchSpeed = 2.5f;
private float jumpHeight = 1.5f; private float jumpHeight = 1.5f;
private float gravity = -9.81f; private float gravity = -9.81f;
// Коллайдер // ── Коллайдер ─────────────────────────────────────────────────────────────
private float height = 1.8f; private float height = 1.8f;
private float radius = 0.5f; private float radius = 0.5f;
private float centerY = 0.9f; private float centerY = 0.9f;
// Камера // ── Камера ────────────────────────────────────────────────────────────────
private bool addCamera = true; private bool addCamera = true;
private float cameraHeight = 1.7f; 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 idleClip;
private AnimationClip walkClip; private AnimationClip walkClip;
private AnimationClip runClip; private AnimationClip runClip;
@@ -33,9 +47,8 @@ public class PlayerBuilderTool : EditorWindow
private AnimationClip crouchIdleClip; private AnimationClip crouchIdleClip;
private AnimationClip crouchWalkClip; private AnimationClip crouchWalkClip;
// Аниматор контроллер // ── Scroll ────────────────────────────────────────────────────────────────
private AnimatorController animatorController; private Vector2 _scroll;
private bool createAnimatorController = true;
[MenuItem("Tools/Player Builder")] [MenuItem("Tools/Player Builder")]
public static void ShowWindow() public static void ShowWindow()
@@ -45,97 +58,121 @@ public class PlayerBuilderTool : EditorWindow
void OnGUI() void OnGUI()
{ {
GUILayout.Label("Настройки персонажа", EditorStyles.boldLabel); _scroll = EditorGUILayout.BeginScrollView(_scroll);
// Секция модели GUILayout.Label("Конструктор Персонажа", EditorStyles.boldLabel);
// ── Секция модели ─────────────────────────────────────────────────────
GUILayout.Space(6);
GUILayout.Label("Модель", EditorStyles.miniBoldLabel); GUILayout.Label("Модель", EditorStyles.miniBoldLabel);
characterModel = (GameObject)EditorGUILayout.ObjectField("Модель персонажа", characterModel, typeof(GameObject), false); 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); GUILayout.Label("Движение", EditorStyles.miniBoldLabel);
walkSpeed = EditorGUILayout.FloatField("Скорость ходьбы", walkSpeed); walkSpeed = EditorGUILayout.FloatField("Скорость ходьбы", walkSpeed);
runSpeed = EditorGUILayout.FloatField("Скорость бега", runSpeed); runSpeed = EditorGUILayout.FloatField("Скорость бега", runSpeed);
crouchSpeed = EditorGUILayout.FloatField("Скорость краулинга", crouchSpeed); crouchSpeed = EditorGUILayout.FloatField("Скорость присядки", crouchSpeed);
jumpHeight = EditorGUILayout.FloatField("Высота прыжка", jumpHeight); jumpHeight = EditorGUILayout.FloatField("Высота прыжка", jumpHeight);
gravity = EditorGUILayout.FloatField("Гравитация", gravity); gravity = EditorGUILayout.FloatField("Гравитация", gravity);
GUILayout.Space(10); // ── Коллайдер ─────────────────────────────────────────────────────────
GUILayout.Space(6);
// Секция коллайдера GUILayout.Label("Коллайдер CharacterController", EditorStyles.miniBoldLabel);
GUILayout.Label("Коллайдер", EditorStyles.miniBoldLabel); height = EditorGUILayout.FloatField("Высота", height);
height = EditorGUILayout.FloatField("Высота", height); radius = EditorGUILayout.FloatField("Радиус", radius);
radius = EditorGUILayout.FloatField("Радиус", radius);
centerY = EditorGUILayout.FloatField("Центр Y", centerY); centerY = EditorGUILayout.FloatField("Центр Y", centerY);
GUILayout.Space(10); // ── Камера ────────────────────────────────────────────────────────────
GUILayout.Space(6);
// Секция камеры
GUILayout.Label("Камера", EditorStyles.miniBoldLabel); GUILayout.Label("Камера", EditorStyles.miniBoldLabel);
addCamera = EditorGUILayout.Toggle("Добавить камеру", addCamera); addCamera = EditorGUILayout.Toggle("Добавить камеру", addCamera);
if (addCamera) if (addCamera)
{
cameraHeight = EditorGUILayout.Slider("Высота камеры", cameraHeight, 1f, 2f); 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.Space(6);
// Секция анимаций GUILayout.Label("Анимации (опционально)", EditorStyles.miniBoldLabel);
GUILayout.Label("Анимации", EditorStyles.miniBoldLabel); idleClip = (AnimationClip)EditorGUILayout.ObjectField("Idle", idleClip, typeof(AnimationClip), false);
createAnimatorController = EditorGUILayout.Toggle("Создать контроллер", createAnimatorController); walkClip = (AnimationClip)EditorGUILayout.ObjectField("Walk", walkClip, typeof(AnimationClip), false);
idleClip = (AnimationClip)EditorGUILayout.ObjectField("Idle", idleClip, typeof(AnimationClip), false); runClip = (AnimationClip)EditorGUILayout.ObjectField("Run", runClip, typeof(AnimationClip), false);
walkClip = (AnimationClip)EditorGUILayout.ObjectField("Walk", walkClip, typeof(AnimationClip), false); jumpClip = (AnimationClip)EditorGUILayout.ObjectField("Jump", jumpClip, 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); crouchIdleClip = (AnimationClip)EditorGUILayout.ObjectField("Crouch Idle", crouchIdleClip, typeof(AnimationClip), false);
crouchWalkClip = (AnimationClip)EditorGUILayout.ObjectField("Crouch Walk", crouchWalkClip, 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); GUILayout.Space(20);
// Кнопки действий // ── Кнопки ────────────────────────────────────────────────────────────
GUI.color = Color.green; GUI.color = Color.green;
if (GUILayout.Button("Создать в сцене")) if (GUILayout.Button("Создать игрока" + (includeInventory ? " + Инвентарь" : "")))
{
BuildPlayer("Player"); BuildPlayer("Player");
}
GUI.color = Color.yellow;
if (GUILayout.Button("Создать тестового игрока (капсула)"))
BuildTestPlayer();
GUI.color = Color.cyan; GUI.color = Color.cyan;
if (GUILayout.Button("Сохранить как префаб")) if (GUILayout.Button("Сохранить как префаб"))
{
SaveAsPrefab(); SaveAsPrefab();
}
GUI.color = Color.yellow;
if (GUILayout.Button("Создать с тестовой моделью (капсула)"))
{
BuildTestPlayer();
}
GUI.color = Color.white; GUI.color = Color.white;
GUILayout.Space(10); GUILayout.Space(10);
EditorGUILayout.HelpBox( EditorGUILayout.HelpBox(
"1. Назначьте модель персонажа\n" + "1. Назначьте модель персонажа (опционально)\n" +
"2. Назначьте анимации (опционально)\n" + "2. Укажите кость головы и кость руки\n" +
"3. Настройте параметры\n" + "3. Назначьте PlayerControls.inputactions\n" +
"4. Нажмите 'Создать в сцене'", "4. Нажмите 'Создать игрока'",
MessageType.Info); 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 // CharacterController
var cc = player.AddComponent<CharacterController>(); var cc = player.AddComponent<CharacterController>();
@@ -145,31 +182,27 @@ public class PlayerBuilderTool : EditorWindow
// Скрипт персонажа // Скрипт персонажа
var character = player.AddComponent<charecter>(); var character = player.AddComponent<charecter>();
character.walkSpeed = walkSpeed; character.walkSpeed = walkSpeed;
character.runSpeed = runSpeed; character.runSpeed = runSpeed;
character.crouchSpeed = crouchSpeed; character.crouchSpeed = crouchSpeed;
character.jumpHeight = jumpHeight; character.jumpHeight = jumpHeight;
character.gravity = gravity; character.gravity = gravity;
// Модель // Модель
GameObject model = null; Transform resolvedHead = headBone;
Transform resolvedHand = handBone;
if (characterModel != null) if (characterModel != null)
{ {
model = Instantiate(characterModel, player.transform); var model = (GameObject)PrefabUtility.InstantiatePrefab(characterModel, player.transform);
model.transform.localPosition = Vector3.zero; model.transform.localPosition = Vector3.zero;
model.transform.localRotation = Quaternion.identity; model.transform.localRotation = Quaternion.identity;
// Голова if (resolvedHead == null)
if (headBone != null) resolvedHead = FindChildBone(model.transform, "head") ?? FindChildBone(model.transform, "Head");
{ if (resolvedHand == null)
character.head = headBone; resolvedHand = FindChildBone(model.transform, "hand_r") ?? FindChildBone(model.transform, "RightHand");
}
else
{
character.head = FindChildBone(model.transform, "Head");
}
//Animator
var modelAnimator = model.GetComponent<Animator>(); var modelAnimator = model.GetComponent<Animator>();
if (modelAnimator != null && modelAnimator.avatar != null) 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"); var headObj = new GameObject("HeadPoint");
headObj.transform.SetParent(model != null ? model.transform : player.transform); headObj.transform.SetParent(player.transform);
headObj.transform.localPosition = new Vector3(0, height - 0.2f, 0); 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"); var camObj = new GameObject("PlayerCamera");
cameraObj.transform.SetParent(character.head); camObj.transform.SetParent(resolvedHead);
cameraObj.transform.localPosition = new Vector3(0, 0.1f, 0); 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"); var oldCam = GameObject.FindWithTag("MainCamera");
if (oldCam != null && oldCam != cameraObj) if (oldCam != null && oldCam != camObj)
{
DestroyImmediate(oldCam); DestroyImmediate(oldCam);
}
} }
// Тег // PlayerInput
player.tag = "Player"; 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); 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( var player = BuildPlayerGO(name);
"Сохранить префаб", Selection.activeGameObject = player;
"Player", Debug.Log($"[PlayerBuilder] Создан: {name}");
"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}");
} }
void BuildTestPlayer() void BuildTestPlayer()
{ {
var savedModel = characterModel;
characterModel = null; characterModel = null;
headBone = null; var player = BuildPlayerGO("TestPlayer");
BuildPlayer("TestPlayer"); characterModel = savedModel;
GameObject player = Selection.activeGameObject; // Визуальная капсула
if (player == null) return; var body = GameObject.CreatePrimitive(PrimitiveType.Capsule);
// Создаём визуальную часть
GameObject body = GameObject.CreatePrimitive(PrimitiveType.Capsule);
body.name = "Body"; body.name = "Body";
body.transform.SetParent(player.transform); body.transform.SetParent(player.transform, false);
body.transform.localPosition = Vector3.zero; body.transform.localPosition = new Vector3(0, centerY, 0);
body.transform.localScale = new Vector3(radius * 2, height / 2f, radius * 2);
DestroyImmediate(body.GetComponent<Collider>()); DestroyImmediate(body.GetComponent<Collider>());
GameObject head = new GameObject("Head"); Selection.activeGameObject = player;
head.transform.SetParent(player.transform); Debug.Log("[PlayerBuilder] Создан тестовый игрок (капсула)");
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] Создан тестовый игрок");
} }
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) Transform FindChildBone(Transform parent, string boneName)
{ {
if (parent == null) return null; 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) foreach (Transform child in parent)
{ {
var result = FindChildBone(child, boneName); var result = FindChildBone(child, boneName);

View File

@@ -1,29 +1,51 @@
using UnityEngine; using UnityEngine;
using System; using System;
using UnityEngine.InputSystem;
/// <summary>
/// Централизованный ввод на основе New Input System.
/// Singleton, DontDestroyOnLoad.
/// Биндинги сохраняются в PlayerPrefs автоматически при изменении.
/// </summary>
public class PlayerInput : MonoBehaviour public class PlayerInput : MonoBehaviour
{ {
public static PlayerInput Instance { get; private set; } public static PlayerInput Instance { get; private set; }
// Continuous axes // ── Ссылка на InputActionAsset ────────────────────────────────────────────
public float MouseX { get; private set; } [Tooltip("Назначьте Assets/Input/PlayerControls.inputactions")]
public float MouseY { get; private set; } public InputActionAsset actions;
public float Horizontal { get; private set; }
public float Vertical { get; private set; }
// Continuous states // ── Непрерывные значения ──────────────────────────────────────────────────
public bool Crouch { get; private set; } public Vector2 MoveInput { get; private set; }
public bool Run { 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 OnToggleInventory;
public event Action OnDropPressed; public event Action OnDropPressed;
public event Action OnPickupPressed; public event Action OnPickupPressed;
public event Action OnJumpPressed; public event Action OnJumpPressed;
public event Action<int> OnHotbarSelect; 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() void Awake()
{ {
if (Instance == null) if (Instance == null)
@@ -34,46 +56,116 @@ public class PlayerInput : MonoBehaviour
else else
{ {
Destroy(gameObject); 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() void Update()
{ {
// Always allow toggle inventory (Tab/Escape) even when input is disabled if (_gameplayMap == null || !_gameplayMap.enabled) return;
if (Input.GetKeyDown(KeyCode.Tab) || Input.GetKeyDown(KeyCode.Escape))
OnToggleInventory?.Invoke();
if (!Enabled) return; MoveInput = _gameplayMap["Move"].ReadValue<Vector2>();
LookInput = _gameplayMap["Look"].ReadValue<Vector2>();
// Axes Crouch = _gameplayMap["Crouch"].IsPressed();
MouseX = Input.GetAxis("Mouse X"); Run = _gameplayMap["Run"].IsPressed();
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);
}
}
} }
// ── Сохранение/загрузка биндингов ────────────────────────────────────────
[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 public class charecter : MonoBehaviour
{ {
[Header("Movement")] [Header("Movement")]
public float walkSpeed = 5f; public float walkSpeed = 5f;
public float runSpeed = 9f; public float runSpeed = 9f;
public float crouchSpeed = 2.5f; public float crouchSpeed = 2.5f;
public float jumpHeight = 1.5f; public float jumpHeight = 1.5f;
public float gravity = -9.81f; public float gravity = -9.81f;
[Header("References")] [Header("References")]
[Tooltip("Head BONE of the character.")] [Tooltip("Head BONE of the character.")]
@@ -46,20 +46,21 @@ public class charecter : MonoBehaviour
_cc = GetComponent<CharacterController>(); _cc = GetComponent<CharacterController>();
_anim = GetComponentInChildren<Animator>(); _anim = GetComponentInChildren<Animator>();
Cursor.lockState = CursorLockMode.Locked; Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
_bodyYaw = transform.eulerAngles.y; _bodyYaw = transform.eulerAngles.y;
_headYaw = _bodyYaw; _headYaw = _bodyYaw;
_headBindPose = head.localRotation; _headBindPose = head != null ? head.localRotation : Quaternion.identity;
// Ensure PlayerInput exists and subscribe to events // Убедиться что PlayerInput существует
if (PlayerInput.Instance == null) if (PlayerInput.Instance == null)
{ {
var go = new GameObject("PlayerInput"); var go = new GameObject("PlayerInput");
go.AddComponent<PlayerInput>(); go.AddComponent<PlayerInput>();
} }
PlayerInput.Instance.OnPickupPressed += () => TryPickupNearbyItem(); PlayerInput.Instance.OnPickupPressed += TryPickupNearbyItem;
PlayerInput.Instance.OnJumpPressed += () => { _jumpRequested = true; }; PlayerInput.Instance.OnJumpPressed += () => { _jumpRequested = true; };
} }
void Update() void Update()
@@ -68,55 +69,35 @@ public class charecter : MonoBehaviour
HandleMovement(); HandleMovement();
} }
// ── Item Pickup ─────────────────────────────────────────────────────────── void LateUpdate()
void HandlePickup()
{ {
// Input moved to PlayerInput events ApplyRotations();
} }
// ── Item Pickup ───────────────────────────────────────────────────────────
void TryPickupNearbyItem() void TryPickupNearbyItem()
{ {
float pickupRadius = 2.5f; float pickupRadius = 2.5f;
Collider[] colliders = Physics.OverlapSphere(transform.position, pickupRadius); Collider[] colliders = Physics.OverlapSphere(transform.position, pickupRadius);
Debug.Log($"[Pickup] Checking {colliders.Length} colliders in radius {pickupRadius}");
WorldItem closestItem = null; WorldItem closestItem = null;
float closestDist = float.MaxValue; float closestDist = float.MaxValue;
foreach (var col in colliders) foreach (var col in colliders)
{ {
var worldItem = col.GetComponent<WorldItem>(); 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); closestDist = dist;
Debug.Log($"[Pickup] Found WorldItem: {worldItem.itemData?.itemName ?? "null"} at dist {dist}"); closestItem = worldItem;
if (dist < closestDist)
{
closestDist = dist;
closestItem = worldItem;
}
} }
} }
if (closestItem != null) if (closestItem != null)
{ closestItem.TryPickupByButton();
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();
} }
// ── Mouse look ──────────────────────────────────────────────────────────── // ── Mouse look ────────────────────────────────────────────────────────────
@@ -125,21 +106,21 @@ public class charecter : MonoBehaviour
var input = PlayerInput.Instance; var input = PlayerInput.Instance;
if (input != null) if (input != null)
{ {
_headYaw += input.MouseX * mouseSensitivity; _headYaw += input.LookInput.x * mouseSensitivity;
_pitch -= input.MouseY * mouseSensitivity; _pitch -= input.LookInput.y * mouseSensitivity;
} }
_pitch = Mathf.Clamp(_pitch, -maxPitch, maxPitch); _pitch = Mathf.Clamp(_pitch, -maxPitch, maxPitch);
} }
// ── Movement ────────────────────────────────────────────────────────────── // ── Movement ──────────────────────────────────────────────────────────────
void HandleMovement() void HandleMovement()
{ {
var input = PlayerInput.Instance; var input = PlayerInput.Instance;
float h = input != null ? input.Horizontal : Input.GetAxisRaw("Horizontal"); float h = input != null ? input.MoveInput.x : 0f;
float v = input != null ? input.Vertical : Input.GetAxisRaw("Vertical"); float v = input != null ? input.MoveInput.y : 0f;
bool isCrouching = input != null ? input.Crouch : Input.GetKey(KeyCode.C); bool isCrouching = input != null && input.Crouch;
bool isRunning = input != null ? input.Run && !isCrouching : Input.GetKey(KeyCode.LeftShift) && !isCrouching; bool isRunning = input != null && input.Run && !isCrouching;
bool isMoving = (h * h + v * v) > 0.01f; bool isMoving = (h * h + v * v) > 0.01f;
float speed = isCrouching ? crouchSpeed : isRunning ? runSpeed : walkSpeed; float speed = isCrouching ? crouchSpeed : isRunning ? runSpeed : walkSpeed;
@@ -148,7 +129,7 @@ public class charecter : MonoBehaviour
if (isMoving) if (isMoving)
_bodyYaw = Mathf.MoveTowardsAngle(_bodyYaw, _headYaw, bodyAlignSpeed * Time.deltaTime); _bodyYaw = Mathf.MoveTowardsAngle(_bodyYaw, _headYaw, bodyAlignSpeed * Time.deltaTime);
// Head-drag // Head-drag clamp
float offset = Mathf.DeltaAngle(_bodyYaw, _headYaw); float offset = Mathf.DeltaAngle(_bodyYaw, _headYaw);
float clampedOffset = Mathf.Clamp(offset, -maxHeadYaw, maxHeadYaw); float clampedOffset = Mathf.Clamp(offset, -maxHeadYaw, maxHeadYaw);
_bodyYaw += offset - clampedOffset; _bodyYaw += offset - clampedOffset;
@@ -176,12 +157,11 @@ public class charecter : MonoBehaviour
moveDir.y = _verticalVelocity; moveDir.y = _verticalVelocity;
_cc.Move(moveDir * speed * Time.deltaTime); _cc.Move(moveDir * speed * Time.deltaTime);
// Animator params // Animator
if (_anim != null) if (_anim != null)
{ {
// Speed: 0 = idle, 0.5 = walk, 1 = run
float animSpeed = !isMoving ? 0f : isRunning ? 1f : 0.5f; 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 (HashIsMoving, isMoving);
_anim.SetBool (HashIsCrouching, isCrouching); _anim.SetBool (HashIsCrouching, isCrouching);
_anim.SetBool (HashIsGrounded, _cc.isGrounded); _anim.SetBool (HashIsGrounded, _cc.isGrounded);
@@ -193,10 +173,11 @@ public class charecter : MonoBehaviour
{ {
transform.rotation = Quaternion.Euler(0f, _bodyYaw, 0f); transform.rotation = Quaternion.Euler(0f, _bodyYaw, 0f);
float headOffset = Mathf.DeltaAngle(_bodyYaw, _headYaw); if (head == null) return;
Quaternion yawRot = Quaternion.Euler(0f, headOffset, 0f);
Quaternion pitchRot = Quaternion.Euler(_pitch, 0f, 0f); float headOffset = Mathf.DeltaAngle(_bodyYaw, _headYaw);
head.localRotation = _headBindPose * yawRot * pitchRot; 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 ## Project Overview
This is a Unity game project with two major systems: Unity game project (no CLI build commands — use the Unity Editor). Two major systems:
1. **Character Controller** - First/third-person movement with animations 1. **Character Controller** — first/third-person movement with animations
2. **Inventory System** - Full inventory with hotbar, drag-drop, crafting, and item generation 2. **Inventory System** hotbar, drag-drop, crafting, world items, save/load
## Controls ## Controls
- **Movement**: WASD | Action | Key |
- **Look**: Mouse |---|---|
- **Run**: Left Shift | Move | WASD |
- **Crouch**: C | Look | Mouse |
- **Jump**: Space | Run | Left Shift |
- **Toggle Inventory**: Tab | Crouch | C |
- **Close Inventory**: Escape | Jump | Space |
- **Hotbar Selection**: 1-9 keys | Pickup | E |
- **Drop Item**: Q | Toggle Inventory | Tab |
| Close Inventory | Escape |
## Key Files and Structure | Hotbar Select | 19 |
| Drop Item | Q |
### 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
## Architecture ## Architecture
### Character Controller Design ### Input Layer — `PlayerInput` (singleton, DontDestroyOnLoad)
- Uses Unity's CharacterController component for collision All input is centralised in `Assets/charecter-controller/scripts/PlayerInput.cs`. It exposes:
- Head-body separation: body rotates toward look direction, head can offset within limits - Continuous properties: `MouseX`, `MouseY`, `Horizontal`, `Vertical`, `Crouch`, `Run`
- Animator parameters use `Animator.StringToHash` for performance (Speed, IsMoving, IsCrouching, IsGrounded, Jump) - C# events: `OnJumpPressed`, `OnPickupPressed`, `OnToggleInventory`, `OnDropPressed`, `OnHotbarSelect`
- Physics-based jumping: `v = sqrt(jumpHeight * -2 * gravity)` - `Enabled` flag — set to `false` when the inventory is open to block gameplay input
- All movement is framerate-independent with `Time.deltaTime`
### Inventory Architecture Both `charecter.cs` and `InventoryManager.cs` subscribe to `PlayerInput.Instance` events in `Start()`. Never read `Input.*` directly in new code — go through `PlayerInput`.
- **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
### Inventory Flow ### Character Controller — `charecter.cs`
1. Items added via `AddItem(itemId, amount)` - tries existing stacks first, then empty slots - Requires `CharacterController` component
2. Removed via `RemoveItem(itemId, amount)` - returns true if successful - Head-bone / body separation: body yaw tracks camera yaw when moving; head offset is clamped to `±maxHeadYaw`
3. Hotbar selection via `SelectHotbarSlot(index)` - updates visual selection - Bone rotations applied in `LateUpdate` via `ApplyRotations()`
4. Item dropped via `DropSelectedItem()` - spawns world object via InventoryItemFactory - Animator hashes cached as `static readonly int` fields; animator Speed param: 0 = idle, 0.5 = walk, 1 = run
5. JSON serialization via `ExportToJson()` / `ImportFromJson()` - 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 ### 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.collab-proxy": "2.12.4",
"com.unity.ide.rider": "3.0.39", "com.unity.ide.rider": "3.0.39",
"com.unity.ide.visualstudio": "2.0.27", "com.unity.ide.visualstudio": "2.0.27",
"com.unity.inputsystem": "1.19.0",
"com.unity.multiplayer.center": "1.0.1", "com.unity.multiplayer.center": "1.0.1",
"com.unity.purchasing": "4.15.0", "com.unity.purchasing": "4.15.0",
"com.unity.test-framework": "1.6.0", "com.unity.test-framework": "1.6.0",

View File

@@ -75,6 +75,15 @@
}, },
"url": "https://packages.unity.com" "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": { "com.unity.multiplayer.center": {
"version": "1.0.1", "version": "1.0.1",
"depth": 0, "depth": 0,

View File

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

View File

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