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,52 +78,34 @@ 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");
eventsGO.AddComponent<InventoryEvents>();
}
// Попробуем загрузить сохранённый инвентарь
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.OnToggleInventory += ToggleInventory;
PlayerInput.Instance.OnDropPressed += DropSelectedItem; PlayerInput.Instance.OnDropPressed += DropSelectedItem;
PlayerInput.Instance.OnHotbarSelect += SelectHotbarSlot; PlayerInput.Instance.OnHotbarSelect += SelectHotbarSlot;
} }
else
{
Debug.LogWarning("[InventoryManager] PlayerInput not found. Run Tools > Setup Complete Scene first.");
}
// Загрузить сохранённый инвентарь (слоты уже существуют)
LoadFromFile();
}
public void ToggleInventory() public void ToggleInventory()
@@ -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,12 +1,92 @@
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");
@@ -15,288 +95,39 @@ public class InventorySceneSetup
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,30 +24,24 @@ 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)
{ {
@@ -56,18 +52,12 @@ public class InventoryUIBuilder : MonoBehaviour
} }
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);
@@ -75,23 +65,17 @@ public class InventoryUIBuilder : MonoBehaviour
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>();
@@ -104,37 +88,30 @@ public class InventoryUIBuilder : MonoBehaviour
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>();
@@ -153,7 +130,7 @@ public class InventoryUIBuilder : MonoBehaviour
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>();
@@ -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,8 +182,8 @@ 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;
@@ -217,10 +193,9 @@ public class InventoryUIBuilder : MonoBehaviour
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;
@@ -231,16 +206,13 @@ public class InventoryUIBuilder : MonoBehaviour
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,8 +1,11 @@
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
{ {
// Основные данные // Основные данные
@@ -23,8 +26,8 @@ public class ItemBuilderTool : EditorWindow
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()
@@ -39,7 +42,7 @@ 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);
@@ -47,13 +50,11 @@ public class ItemBuilderTool : EditorWindow
// Визуал // Визуал
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);
@@ -63,42 +64,36 @@ public class ItemBuilderTool : EditorWindow
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,7 +107,7 @@ 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;
@@ -124,93 +119,89 @@ public class ItemBuilderTool : EditorWindow
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); var itemGO = new GameObject(itemName);
var visual = GameObject.CreatePrimitive(PrimitiveType.Cube);
visual.name = "Visual"; // Визуал
visual.transform.SetParent(itemGO.transform); GameObject visual;
visual.transform.localPosition = Vector3.zero; if (modelPrefab != null)
{
visual = (GameObject)PrefabUtility.InstantiatePrefab(modelPrefab, itemGO.transform);
}
else
{
visual = GameObject.CreatePrimitive(PrimitiveType.Cube);
visual.transform.SetParent(itemGO.transform, false);
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;
// WorldItem компонент
var worldItem = itemGO.AddComponent<WorldItem>(); var worldItem = itemGO.AddComponent<WorldItem>();
worldItem.interactRange = interactRange;
// Trigger-коллайдер для OverlapSphere (не для авто-pickup!)
var col = itemGO.AddComponent<SphereCollider>(); var col = itemGO.AddComponent<SphereCollider>();
col.radius = 0.5f; col.radius = 0.5f;
col.isTrigger = true; col.isTrigger = true;
itemGO.transform.position = Vector3.zero; itemGO.transform.position = Vector3.zero;
Selection.activeGameObject = itemGO; Selection.activeGameObject = itemGO;
}
else
{
var itemGO = Instantiate(modelPrefab);
itemGO.name = itemName;
itemGO.AddComponent<WorldItem>();
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}");
@@ -221,9 +212,7 @@ public class ItemBuilderTool : EditorWindow
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,9 +30,7 @@ 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;
@@ -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,46 +58,67 @@ 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);
createAnimatorController = EditorGUILayout.Toggle("Создать контроллер", createAnimatorController);
idleClip = (AnimationClip)EditorGUILayout.ObjectField("Idle", idleClip, typeof(AnimationClip), false); idleClip = (AnimationClip)EditorGUILayout.ObjectField("Idle", idleClip, typeof(AnimationClip), false);
walkClip = (AnimationClip)EditorGUILayout.ObjectField("Walk", walkClip, typeof(AnimationClip), false); walkClip = (AnimationClip)EditorGUILayout.ObjectField("Walk", walkClip, typeof(AnimationClip), false);
runClip = (AnimationClip)EditorGUILayout.ObjectField("Run", runClip, typeof(AnimationClip), false); runClip = (AnimationClip)EditorGUILayout.ObjectField("Run", runClip, typeof(AnimationClip), false);
@@ -92,50 +126,53 @@ public class PlayerBuilderTool : EditorWindow
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>();
@@ -152,24 +189,20 @@ public class PlayerBuilderTool : EditorWindow
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>();
var cam = cameraObj.AddComponent<Camera>();
cam.tag = "MainCamera"; cam.tag = "MainCamera";
cam.nearClipPlane = 0.1f; cam.nearClipPlane = 0.05f;
// Удалить старую
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
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.tag = "Player";
// Позиция
player.transform.position = new Vector3(0, 2, -5); player.transform.position = new Vector3(0, 2, -5);
// Инвентарь
if (includeInventory)
SetupInventoryForPlayer(player);
return player;
}
void BuildPlayer(string name)
{
var player = BuildPlayerGO(name);
Selection.activeGameObject = player; Selection.activeGameObject = player;
Debug.Log($"[PlayerBuilder] Создан: {playerName}"); Debug.Log($"[PlayerBuilder] Создан: {name}");
}
void SaveAsPrefab()
{
string path = EditorUtility.SaveFilePanelInProject(
"Сохранить префаб",
"Player",
"prefab",
"Выберите место");
if (string.IsNullOrEmpty(path)) return;
BuildPlayer("PlayerTemp");
GameObject player = GameObject.Find("PlayerTemp");
if (player == null)
{
Debug.LogError("Не удалось создать игрока");
return;
}
GameObject prefab = PrefabUtility.SaveAsPrefabAsset(player, path);
DestroyImmediate(player);
Selection.activeObject = prefab;
Debug.Log($"[PlayerBuilder] Сохранено: {path}");
} }
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>(); void SaveAsPrefab()
if (character != null)
{ {
character.head = head.transform; 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}");
} }
if (addCamera) // ── Создание инвентаря ────────────────────────────────────────────────────
void SetupInventoryForPlayer(GameObject player)
{ {
GameObject camObj = new GameObject("PlayerCamera"); // EventSystem
camObj.transform.SetParent(head.transform); if (FindObjectOfType<EventSystem>() == null)
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); var esGO = new GameObject("EventSystem");
} esGO.AddComponent<EventSystem>();
esGO.AddComponent<StandaloneInputModule>();
} }
Debug.Log("[PlayerBuilder] Создан тестовый игрок"); // 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 Vector2 MoveInput { get; private set; }
public Vector2 LookInput { get; private set; }
public bool Crouch { get; private set; } public bool Crouch { get; private set; }
public bool Run { 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>();
Crouch = _gameplayMap["Crouch"].IsPressed();
Run = _gameplayMap["Run"].IsPressed();
}
// Axes // ── Сохранение/загрузка биндингов ────────────────────────────────────────
MouseX = Input.GetAxis("Mouse X");
MouseY = Input.GetAxis("Mouse Y");
Horizontal = Input.GetAxisRaw("Horizontal");
Vertical = Input.GetAxisRaw("Vertical");
// Continuous keys [ContextMenu("Save Bindings")]
Crouch = Input.GetKey(KeyCode.C); public void SaveBindings()
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)) if (actions == null) return;
PlayerPrefs.SetString(BINDINGS_PREF_KEY, actions.SaveBindingOverridesAsJson());
PlayerPrefs.Save();
Debug.Log("[PlayerInput] Биндинги сохранены");
}
[ContextMenu("Reset Bindings to Defaults")]
public void ResetBindings()
{ {
OnHotbarSelect?.Invoke(i); 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

@@ -46,19 +46,20 @@ 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; };
} }
@@ -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); float dist = Vector3.Distance(transform.position, worldItem.transform.position);
Debug.Log($"[Pickup] Found WorldItem: {worldItem.itemData?.itemName ?? "null"} at dist {dist}"); if (dist < closestDist && dist <= worldItem.interactRange)
if (dist < closestDist)
{ {
closestDist = dist; closestDist = dist;
closestItem = worldItem; 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,8 +106,8 @@ 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);
} }
@@ -135,11 +116,11 @@ public class charecter : MonoBehaviour
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,10 +157,9 @@ 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);
@@ -193,10 +173,11 @@ public class charecter : MonoBehaviour
{ {
transform.rotation = Quaternion.Euler(0f, _bodyYaw, 0f); transform.rotation = Quaternion.Euler(0f, _bodyYaw, 0f);
if (head == null) return;
float headOffset = Mathf.DeltaAngle(_bodyYaw, _headYaw); float headOffset = Mathf.DeltaAngle(_bodyYaw, _headYaw);
Quaternion yawRot = Quaternion.Euler(0f, headOffset, 0f); Quaternion yawRot = Quaternion.Euler(0f, headOffset, 0f);
Quaternion pitchRot = Quaternion.Euler(_pitch, 0f, 0f); Quaternion pitchRot = Quaternion.Euler(_pitch, 0f, 0f);
head.localRotation = _headBindPose * yawRot * pitchRot; 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: []