using UnityEngine; using UnityEditor; using UnityEditor.Animations; using UnityEngine.UI; using UnityEngine.EventSystems; /// /// Инструмент создания полного игрока с опциональным инвентарём. /// Tools > Player Builder /// public class PlayerBuilderTool : EditorWindow { // ── Модель ──────────────────────────────────────────────────────────────── private GameObject characterModel; private Transform headBone; private Transform handBone; // ── Параметры движения ──────────────────────────────────────────────────── 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 height = 1.8f; private float radius = 0.5f; private float centerY = 0.9f; // ── Камера ──────────────────────────────────────────────────────────────── 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; private AnimationClip jumpClip; private AnimationClip crouchIdleClip; private AnimationClip crouchWalkClip; // ── Scroll ──────────────────────────────────────────────────────────────── private Vector2 _scroll; [MenuItem("Tools/Player Builder")] public static void ShowWindow() { GetWindow("Конструктор Персонажа"); } void OnGUI() { _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("Кость головы (Head)", headBone, typeof(Transform), true); handBone = (Transform)EditorGUILayout.ObjectField("Кость руки (RightHand)", handBone, typeof(Transform), true); 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); // ── Коллайдер ───────────────────────────────────────────────────────── GUILayout.Space(6); GUILayout.Label("Коллайдер CharacterController", EditorStyles.miniBoldLabel); height = EditorGUILayout.FloatField("Высота", height); radius = EditorGUILayout.FloatField("Радиус", radius); centerY = EditorGUILayout.FloatField("Центр Y", centerY); // ── Камера ──────────────────────────────────────────────────────────── GUILayout.Space(6); GUILayout.Label("Камера", EditorStyles.miniBoldLabel); 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(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); GUILayout.Space(20); // ── Кнопки ──────────────────────────────────────────────────────────── GUI.color = Color.green; 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.white; GUILayout.Space(10); EditorGUILayout.HelpBox( "1. Назначьте модель персонажа (опционально)\n" + "2. Укажите кость головы и кость руки\n" + "3. Назначьте PlayerControls.inputactions\n" + "4. Нажмите 'Создать игрока'", MessageType.Info); EditorGUILayout.EndScrollView(); } // ── Автопоиск костей ───────────────────────────────────────────────────── void AutoFindBones() { 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(); cc.height = height; cc.radius = radius; cc.center = new Vector3(0, centerY, 0); // Скрипт персонажа var character = player.AddComponent(); character.walkSpeed = walkSpeed; character.runSpeed = runSpeed; character.crouchSpeed = crouchSpeed; character.jumpHeight = jumpHeight; character.gravity = gravity; // Модель Transform resolvedHead = headBone; Transform resolvedHand = handBone; if (characterModel != null) { var model = (GameObject)PrefabUtility.InstantiatePrefab(characterModel, player.transform); model.transform.localPosition = Vector3.zero; model.transform.localRotation = Quaternion.identity; 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"); var modelAnimator = model.GetComponent(); if (modelAnimator != null && modelAnimator.avatar != null) { var animator = player.AddComponent(); animator.avatar = modelAnimator.avatar; } } // Кость головы (или создаём точку) if (resolvedHead == null) { var headObj = new GameObject("HeadPoint"); headObj.transform.SetParent(player.transform); headObj.transform.localPosition = new Vector3(0, height - 0.2f, 0); resolvedHead = headObj.transform; } character.head = resolvedHead; // Камера if (addCamera) { var camObj = new GameObject("PlayerCamera"); camObj.transform.SetParent(resolvedHead); camObj.transform.localPosition = new Vector3(0, 0.1f, 0); var cam = camObj.AddComponent(); cam.tag = "MainCamera"; cam.nearClipPlane = 0.05f; var oldCam = GameObject.FindWithTag("MainCamera"); if (oldCam != null && oldCam != camObj) DestroyImmediate(oldCam); } // PlayerInput var piGO = new GameObject("PlayerInput"); var pi = piGO.AddComponent(); if (inputActions != null) pi.actions = inputActions; // HandEquipSystem if (resolvedHand != null) { var handEquip = player.AddComponent(); handEquip.handBone = resolvedHand; } else { Debug.LogWarning("[PlayerBuilder] Кость руки не найдена — HandEquipSystem не добавлен. Назначьте вручную."); } player.tag = "Player"; 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; Debug.Log($"[PlayerBuilder] Создан: {name}"); } void BuildTestPlayer() { var savedModel = characterModel; characterModel = null; var player = BuildPlayerGO("TestPlayer"); characterModel = savedModel; // Визуальная капсула var body = GameObject.CreatePrimitive(PrimitiveType.Capsule); body.name = "Body"; 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()); 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() == null) { var esGO = new GameObject("EventSystem"); esGO.AddComponent(); esGO.AddComponent(); } // InventoryEvents new GameObject("InventoryEvents").AddComponent(); // InventoryManager var imGO = new GameObject("InventoryManager"); var im = imGO.AddComponent(); im.inventorySize = inventorySlots; // InventoryCanvas — дочерний объект Player var canvasGO = new GameObject("InventoryCanvas"); canvasGO.transform.SetParent(player.transform, false); var canvas = canvasGO.AddComponent(); canvas.renderMode = RenderMode.ScreenSpaceOverlay; canvas.sortingOrder = 100; canvasGO.AddComponent(); var uiBuilder = canvasGO.AddComponent(); 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; foreach (Transform child in parent) { var result = FindChildBone(child, boneName); if (result != null) return result; } return null; } }