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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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