Ответы пользователя по тегу C#
  • Как загрузить картинку через sprite?

    BasmanovDaniil
    @BasmanovDaniil
    Геймдизайнер-телепат
    Для начала вытащите замену спрайта из апдейта, суньте в Awake или Start. Потом дебаггером проверьте что у вас действительно что-то грузится из ресурсов. В остальном всё должно работать.
    Ответ написан
  • Как вернуть прежнее состояние объекта после использования Physics2D.IgnoreCollision?

    BasmanovDaniil
    @BasmanovDaniil
    Геймдизайнер-телепат
    IgnoreCollision можно сбросить выключением коллайдера, как и написано в документации:
    When deactivating the collider the IgnoreCollision state will be lost and you have to call Physics2D.IgnoreCollision again.

    IgnoreLayerCollision, по всей видимости, меняет матрицу взаимодействия слоёв, и отменить его так просто нельзя. Либо запоминайте то, что вы игнорировали, либо сохраняйте матрицу целиком с помощью GetIgnoreLayerCollision и GetLayerCollisionMask, а когда понадобится, так же целиком перезаписывайте текущую матрицу.

    Ещё можно разрулить всё слоями и переносить либо движущиеся объекты между слоями, либо препятствия. IgnoreCollision это скорее костыль для особых ситуаций, повсеместно использовать его не стоит.
    Ответ написан
    Комментировать
  • Есть ли в юнити глобальные переменные?

    BasmanovDaniil
    @BasmanovDaniil
    Геймдизайнер-телепат
    В юнити, а точнее в c# нет глобальных переменных. Существуют статические переменные и константы, которыми можно пользоваться для хранения глобальной информации, но весьма сомнительно, что вам нужна действительно глобальная доступность. Скорее всего, у вас есть несколько классов, которые из-за неправильного разделения обязанностей вынуждены залезать друг в друга для синхронизации состояния. Это решается только переорганизацией кода, глобальные переменные и синглтоны дело только усугубят.
    В контексте юнити для разрешения зависимостей хорошо работает техника dependency injection, которая реализована во многих фреймворках, например в Zenject. Помимо этого, в юнити есть ScriptableObject, который очень удобен для хранения конфигов, позволяет легко передавать их между классами и редактировать в рантайме. Думаю, этих двух вещей должно быть достаточно для начала, остальное придёт с практикой.
    Ответ написан
    Комментировать
  • Как реализованы порталы в игре Portal?

    BasmanovDaniil
    @BasmanovDaniil
    Геймдизайнер-телепат
    Честные порталы делать очень сложно, нужно хорошо понимать физику и весь пайплайн рендера. Есть очень много граничных случаев, каждый из которых нужно отдельно поддерживать: портал внутри портала, пересечение границ внутреннего и внешнего портала, несколько внутренних порталов, повороты плоскости портала, перемещение предметов, тени и освещение, прозрачные материалы и т. п. Есть две основные техники реализации отрисовки порталов: через рендер текстуры и стенсильный буфер.

    Порталы на рендер текстурах делаются проще, но они дороже по производительности, даже на простой геометрии несколько порталов могут оказаться непосильной задачей для компьютера. Основной принцип следующий: когда вы смотрите на портал, на другой стороне портала камера рисует картинку в текстуру, эта текстура натягивается на плоскую геометрию, и со стороны кажется, будто бы через дырку видно другую часть сцены. Примитивную реализацию такой схемы можно собрать руками в редакторе, но дальше начинаются сложности. Если у конечного портала "за спиной" будет геометрия, она попадёт во вьюпорт камеры и будет странно смотреться. Чтобы это предотвратить нужно ближнюю плоскость отсечения у камеры сдвигать в плоскость портала с помощью CalculateObliqueMatrix. Для вычисления положения камеры и пересчёта матрицы есть удобный колбэк OnRenderObject. После корректировки матрицы порталы будут смотреться более-менее корректно, но будут всплывать всевозможные косяки при перемещении через плоскость портала из-за буфера глубины и nearClipPlane основной камеры.

    Стенсильные порталы будут работать сильно быстрее, но для их реализации нужно хорошо шарить в шейдерах. Расписывать их работу подробно не буду, в целом они делаются точно так же, только трюки с рендер текстурами заменяются на трюки с буферами, предлагаю лучше почитать пару полезных статей на тему: раз и два. В контексте юнити всё немного сложнее, чем на чистом OpenGL, потому что нет прямого доступа ко всем буферам и для получения похожего результата придётся немного поизвращаться. Потенциально облегчить задачу может низкоуровневое API, но у меня не получилось добиться хороших результатов без потери качества картинки. Я возлагаю большие надежды на ScriptableRenderLoop, если судить по документации, это как раз то, что нужно.

    Прошу заметить, что всё вышесказанное касается чисто графической части, для простейшего случая, когда мы имеем FPS камеру без анимаций, без дополнительных механик, которая просто ездит по уровню и может заглядывать в порталы. Что уж говорить про миллион потенциальных багов, если вы захотите включить рядом с порталом систему частиц или взять в руки кубик. Посему советую либо купить плагин и довольствоваться тем, что он умеет, либо приготовиться потратить несколько месяцев на разработку своей портальной системы. И в том и другом случае ограничения движка не позволят вам в разумные сроки получить Portal, в лучшем случае это будет Narbacular Drop.
    Ответ написан
    Комментировать
  • Есть ли игры в открытом мире с круглой землей (картой)?

    BasmanovDaniil
    @BasmanovDaniil
    Геймдизайнер-телепат
    Много игр с такими мирами: No Man's Sky, Super Mario Galaxy, Planetary Annihilation. Разрабы No Man's Sky недавно проводили презентацию про свои технологии генерации. Про Super Mario Galaxy есть небольшой видеоролик на YouTube. У разработчиков Planetary Annihilation даже есть свой видеоблог, там обсуждалось много технических моментов.
    Ответ написан
    Комментировать
  • Как бросить луч в Unity?

    BasmanovDaniil
    @BasmanovDaniil
    Геймдизайнер-телепат
    SphereIndicator это корутина, а корутины запускаются с помощью StartCoroutine:
    void Update ()
    {
        if (Input.GetMouseButton(0))
        {
            var centerPoint = new Vector3(_camera.pixelWidth / 2, _camera.pixelHeight /2, 0);
            Ray ray = _camera.ScreenPointToRay(centerPoint);
            RaycastHit hit;
            bool e = Physics.Raycast(ray, out hit);
            if (e)
            {
                StartCoroutine(SphereIndicator(hit.point));
            }
        }
    }
    Ответ написан
    3 комментария
  • Почему не работает движение по сценарию?

    BasmanovDaniil
    @BasmanovDaniil
    Геймдизайнер-телепат
    А зачем вы свои точно такие же оси создавали, если в юнити из коробки уже есть Horizontal и Vertical? И кстати да, у вас в названии опечатка, правильно будет через букву z. В консоли ошибки есть? На пустой сцене с кубиком пробовали запускать? Скрипт в инспекторе включён? GameObject не помечен как статичный? На худой конец можете попробовать использовать кнопки напрямую:
    using UnityEngine;
    
    public class Moving : MonoBehaviour
    {
        public float Speed = 0.5f;
    
        private void Update()
        {
            float horizontal = Input.GetKey(KeyCode.A) ? -Speed : Input.GetKey(KeyCode.D) ? Speed : 0;
            float vertical = Input.GetKey(KeyCode.S) ? -Speed : Input.GetKey(KeyCode.W) ? Speed : 0;
            transform.Translate(horizontal, 0, vertical);
        }
    }
    Ответ написан
    Комментировать
  • Как поменять направление пули?

    BasmanovDaniil
    @BasmanovDaniil
    Геймдизайнер-телепат
    Пуля летит вправо, потому что внутри ShootScript ей задаётся такое направление:
    MoveScript move = shootTransform.gameObject.GetComponent<MoveScript>();
    if (move != null)
    {
        move.direction = this.transform.right;
    }

    Зададите другое - полетит в другую сторону. Нужный вектор можно найти с помощью Input.mousePosition и Camera.ScreenToWorldPoint.
    А вообще говоря, специально для таких случаев юнити создаёт проекты-туториалы, где есть готовые скрипты, которые можно изучать и делать своё, либо использовать как есть.
    Ответ написан
    1 комментарий
  • Контроллер на C#, Unity3d. Что я делаю не правильно?

    BasmanovDaniil
    @BasmanovDaniil
    Геймдизайнер-телепат
    TheRocket.transform.position это мировые координаты ракеты. Input.mousePosition это двадэшные координаты в плоскости экрана. Используя Camera.main.ScreenToWorldPoint вы проецируете двадэшные координаты на плоскость перед камерой, расстояние до которой определяется координатой z в передаваемом параметре.
    Таким образом, если вы не хотите телепортировать ракету в место нажатия, то высчитывать вам нужно дельту перемещения курсора по сравнению с предыдущим кадром в мировых координатах. Сначала получаете расстояние плоскости ракеты до камеры с помощью Camera.WorldToScreenPoint, потом в каждом кадре переводите курсор в мировые координаты через ScreenToWorldPoint и считаете вектор от предыдущего положения в текущее. Полученный вектор просто плюсуете к положению ракеты.
    Ответ написан
    3 комментария
  • Почему предмет появляется только на коллайдерах?

    BasmanovDaniil
    @BasmanovDaniil
    Геймдизайнер-телепат
    Если я правильно понял, по нажатию кнопки в инвентаре вы хотите что-то спавнить в одной конкретной точке? Внутри метода Reset у вас вызывается Instantiate с параметром curPos, просто поменяйте curPos на что-то другое и всё.
    P. S. Называть метод Reset - плохая идея, это зарезервированное ключевое слово юнити, наряду с Update и Awake.
    Ответ написан
  • Как решить эту ошибку?

    BasmanovDaniil
    @BasmanovDaniil
    Геймдизайнер-телепат
    По всей видимости, Camera.main у вас null, это можно проверить дебаггером. Если она null, значит у вашей камеры не стоит тег MainCamera, поставьте, и должно заработать. Либо дотаскивайте камеру до скрипта через инспектор или код.
    Ответ написан
    1 комментарий
  • Как к объекту окно прикрепить аудиолисенер и аудиосурс?

    BasmanovDaniil
    @BasmanovDaniil
    Геймдизайнер-телепат
    Никак, EditorWindow это ScriptableObject, а компоненты можно цеплять только на GameObject. Судя по коду UnityEditor.SceneView, в редакторе они используются, но с конкретной логикой их создания нужно разбираться. Могу предположить, что на активной сцене создаются невидимые источники звука с помощью HideFlags, но не факт. Кроме того, есть класс UnityEditor.AudioUtil с методами PlayClip, StopClip и подобными, вероятно, можно его как-то задействовать для воспроизведения звуков.
    Ответ написан
  • Как ускорить реакцию на наведение на кнопку курсора в GUILayout?

    BasmanovDaniil
    @BasmanovDaniil
    Геймдизайнер-телепат
    Скорее всего это связано с тем, что отрисовка GUI зажирает много ресурсов, а посему имеет смысл начать с профайлера, там можно посмотреть что конкретно тормозит.
    Ответ написан
  • Как отобразить проводник, для указывания директории?

    BasmanovDaniil
    @BasmanovDaniil
    Геймдизайнер-телепат
    Для показа диалогов в редакторе существует специальный класс EditorUtility, в котором есть OpenFilePanel, OpenFolderPanel и подобные.
    Ответ написан
  • Как при word wrap узнать количество строк на которое разбит текст?

    BasmanovDaniil
    @BasmanovDaniil
    Геймдизайнер-телепат
    Для рендеринга исходная строка не меняется. У текста есть cachedTextGenerator, из которого можно достать lineCount.

    UPD: Для GUI.Label количество строк можно вычислисть с помощью GUIStyle.CalcHeight и GUIStyle.lineHeight.
    Ответ написан
  • Где можно посмотреть правильный код(C#) Unity3d от опытных разработчиков?

    BasmanovDaniil
    @BasmanovDaniil
    Геймдизайнер-телепат
    В новых официальных туториалах код хороший. На Catlike Coding код тоже неплохой, многие полезные практики обстоятельно расписаны. Кроме туториалов можете поковырять большие популярные плагины, например UniRx, в том же Asset Store полно бесплатных плагинов от маститых разработчиков, качайте да читайте.
    Ответ написан
    Комментировать
  • Как реализовать в инспекторе подобный регулятор?

    BasmanovDaniil
    @BasmanovDaniil
    Геймдизайнер-телепат
    В API такого слайдера нету, но до него можно доковыряться декомпилятором:
    UnityEditor.ShadowCascadeSplitGUI
    namespace UnityEditor
    {
        using System;
        using System.Linq;
        using UnityEngine;
    
        internal static class ShadowCascadeSplitGUI
        {
            private static readonly Color[] kCascadeColors = new Color[] { new Color(0.5f, 0.5f, 0.6f, 1f), new Color(0.5f, 0.6f, 0.5f, 1f), new Color(0.6f, 0.6f, 0.5f, 1f), new Color(0.6f, 0.5f, 0.5f, 1f) };
            private const int kPartitionHandleExtraHitAreaWidth = 2;
            private const int kPartitionHandleWidth = 2;
            private const int kSliderbarBottomMargin = 2;
            private const int kSliderbarHeight = 0x18;
            private const int kSliderbarTopMargin = 2;
            private static readonly GUIStyle s_CascadeSliderBG = "LODSliderRange";
            private static readonly int s_CascadeSliderId;
            private static DragCache s_DragCache;
            private static DrawCameraMode s_OldSceneDrawMode;
            private static bool s_OldSceneLightingMode;
            private static SceneView s_RestoreSceneView;
            private static readonly GUIStyle s_TextCenteredStyle;
    
            static ShadowCascadeSplitGUI()
            {
                GUIStyle style = new GUIStyle(EditorStyles.whiteMiniLabel) {
                    alignment = TextAnchor.MiddleCenter
                };
                s_TextCenteredStyle = style;
                s_CascadeSliderId = "s_CascadeSliderId".GetHashCode();
                s_OldSceneDrawMode = DrawCameraMode.Textured;
            }
    
            public static void HandleCascadeSliderGUI(ref float[] normalizedCascadePartitions)
            {
                GUILayout.Label("Cascade splits", new GUILayoutOption[0]);
                GUILayoutOption[] options = new GUILayoutOption[] { GUILayout.Height(28f), GUILayout.ExpandWidth(true) };
                Rect position = GUILayoutUtility.GetRect(GUIContent.none, s_CascadeSliderBG, options);
                GUI.Box(position, GUIContent.none);
                float x = position.x;
                float y = position.y + 2f;
                float num3 = position.width - (normalizedCascadePartitions.Length * 2);
                Color color = GUI.color;
                Color backgroundColor = GUI.backgroundColor;
                int index = -1;
                float[] destinationArray = new float[normalizedCascadePartitions.Length + 1];
                Array.Copy(normalizedCascadePartitions, destinationArray, normalizedCascadePartitions.Length);
                destinationArray[destinationArray.Length - 1] = 1f - normalizedCascadePartitions.Sum();
                int controlID = GUIUtility.GetControlID(s_CascadeSliderId, FocusType.Passive);
                Event current = Event.current;
                int activePartition = -1;
                for (int i = 0; i < destinationArray.Length; i++)
                {
                    float num8 = destinationArray[i];
                    index = (index + 1) % kCascadeColors.Length;
                    GUI.backgroundColor = kCascadeColors[index];
                    float width = num3 * num8;
                    Rect rect2 = new Rect(x, y, width, 24f);
                    GUI.Box(rect2, GUIContent.none, s_CascadeSliderBG);
                    x += width;
                    GUI.color = Color.white;
                    Rect rect3 = rect2;
                    string t = string.Format("{0}\n{1:F1}%", i, num8 * 100f);
                    GUI.Label(rect3, GUIContent.Temp(t, t), s_TextCenteredStyle);
                    if (i == (destinationArray.Length - 1))
                    {
                        break;
                    }
                    GUI.backgroundColor = Color.black;
                    Rect rect4 = rect2;
                    rect4.x = x;
                    rect4.width = 2f;
                    GUI.Box(rect4, GUIContent.none, s_CascadeSliderBG);
                    Rect rect5 = rect4;
                    rect5.xMin -= 2f;
                    rect5.xMax += 2f;
                    if (rect5.Contains(current.mousePosition))
                    {
                        activePartition = i;
                    }
                    if (s_DragCache == null)
                    {
                        EditorGUIUtility.AddCursorRect(rect5, MouseCursor.ResizeHorizontal, controlID);
                    }
                    x += 2f;
                }
                GUI.color = color;
                GUI.backgroundColor = backgroundColor;
                EventType typeForControl = current.GetTypeForControl(controlID);
                if (typeForControl == EventType.MouseDown)
                {
                    if (activePartition >= 0)
                    {
                        s_DragCache = new DragCache(activePartition, normalizedCascadePartitions[activePartition], current.mousePosition);
                        if (GUIUtility.hotControl == 0)
                        {
                            GUIUtility.hotControl = controlID;
                        }
                        current.Use();
                        if (s_RestoreSceneView == null)
                        {
                            s_RestoreSceneView = SceneView.lastActiveSceneView;
                            if (s_RestoreSceneView != null)
                            {
                                s_OldSceneDrawMode = s_RestoreSceneView.renderMode;
                                s_OldSceneLightingMode = s_RestoreSceneView.m_SceneLighting;
                                s_RestoreSceneView.renderMode = DrawCameraMode.ShadowCascades;
                            }
                        }
                    }
                }
                else if (typeForControl == EventType.MouseUp)
                {
                    if (GUIUtility.hotControl == controlID)
                    {
                        GUIUtility.hotControl = 0;
                        current.Use();
                    }
                    s_DragCache = null;
                    if (s_RestoreSceneView != null)
                    {
                        s_RestoreSceneView.renderMode = s_OldSceneDrawMode;
                        s_RestoreSceneView.m_SceneLighting = s_OldSceneLightingMode;
                        s_RestoreSceneView = null;
                    }
                }
                else if ((typeForControl == EventType.MouseDrag) && (GUIUtility.hotControl == controlID))
                {
                    Vector2 vector = current.mousePosition - s_DragCache.m_LastCachedMousePosition;
                    float num10 = vector.x / num3;
                    bool flag = (destinationArray[s_DragCache.m_ActivePartition] + num10) > 0f;
                    bool flag2 = (destinationArray[s_DragCache.m_ActivePartition + 1] - num10) > 0f;
                    if (flag && flag2)
                    {
                        s_DragCache.m_NormalizedPartitionSize += num10;
                        normalizedCascadePartitions[s_DragCache.m_ActivePartition] = s_DragCache.m_NormalizedPartitionSize;
                        if (s_DragCache.m_ActivePartition < (normalizedCascadePartitions.Length - 1))
                        {
                            normalizedCascadePartitions[s_DragCache.m_ActivePartition + 1] -= num10;
                        }
                        GUI.changed = true;
                    }
                    s_DragCache.m_LastCachedMousePosition = current.mousePosition;
                    current.Use();
                }
            }
    
            private class DragCache
            {
                public int m_ActivePartition;
                public Vector2 m_LastCachedMousePosition;
                public float m_NormalizedPartitionSize;
    
                public DragCache(int activePartition, float normalizedPartitionSize, Vector2 currentMousePos)
                {
                    this.m_ActivePartition = activePartition;
                    this.m_NormalizedPartitionSize = normalizedPartitionSize;
                    this.m_LastCachedMousePosition = currentMousePos;
                }
            }
        }
    }


    Как можно видеть из кода выше, используется обычный GUI.Box в комбинации с Event. Чтобы получить нечто похожее, придётся проделать много ручной работы, я бы на вашем месте не стал возиться, пока совсем не припрёт. Можно ещё попробовать через рефлексию попользовать ShadowCascadeSplitGUI, но не факт, что заведётся.

    Вызывается оно из QualitySettingsEditor следующим образом:
    private void DrawCascadeSplitGUI<T>(ref SerializedProperty shadowCascadeSplit)
    {
        float[] normalizedCascadePartitions = null;
        Type type = typeof(T);
        if (type == typeof(float))
        {
            normalizedCascadePartitions = new float[] { shadowCascadeSplit.floatValue };
        }
        else if (type == typeof(Vector3))
        {
            Vector3 vector = shadowCascadeSplit.vector3Value;
            normalizedCascadePartitions = new float[] { Mathf.Clamp(vector[0], 0f, 1f), Mathf.Clamp((float)(vector[1] - vector[0]), (float)0f, (float)1f), Mathf.Clamp((float)(vector[2] - vector[1]), (float)0f, (float)1f) };
        }
        if (normalizedCascadePartitions != null)
        {
            EditorGUI.BeginChangeCheck();
            // ↓↓↓↓
            ShadowCascadeSplitGUI.HandleCascadeSliderGUI(ref normalizedCascadePartitions);
            // ↑↑↑↑
            if (EditorGUI.EndChangeCheck())
            {
                if (type == typeof(float))
                {
                    shadowCascadeSplit.floatValue = normalizedCascadePartitions[0];
                }
                else
                {
                    Vector3 vector2 = new Vector3();
                    vector2[0] = normalizedCascadePartitions[0];
                    vector2[1] = vector2[0] + normalizedCascadePartitions[1];
                    vector2[2] = vector2[1] + normalizedCascadePartitions[2];
                    shadowCascadeSplit.vector3Value = vector2;
                }
            }
        }
    }

    Заодно декомпильнул редактор компонента LODGroup, где есть похожий слайдер, но там код гораздо запутаннее.
    UnityEditor.LODGroupEditor
    UnityEditor.LODGroupGUI
    Ответ написан
  • Как сделать реакцию на событие в консоле Unity 3D C#?

    BasmanovDaniil
    @BasmanovDaniil
    Геймдизайнер-телепат
    А причём тут консоль? Вам надо не на текст в консоли подписываться, а напрямую в место вызова события. Как конкретно это сделать зависит от используемого фреймворка. Начать можете с чтения MSDN и официального туториала.
    Ответ написан
    Комментировать
  • Как создать 3D Эффекты атак и прочего Unity?

    BasmanovDaniil
    @BasmanovDaniil
    Геймдизайнер-телепат
    Все эффекты делают в юнити, в особо продвинутых случаях могут использовать специализированный софт, но это редкость. Туториалов по Particle System полно, начать можете с официального, посвещённого чисто эффектам, либо можете взять любой общий туториал по созданию игры, там тоже есть эффекты. Кроме того, можете скачать Standard Assets и посмотреть как там всё сделано. Что же касается того, как сделать эффекты красивыми, для этого вам надо художественное образование, готовых рецептов нету.
    Ответ написан
    Комментировать