В этой статье опишу несколько полезных возможностей и реализаций, которые мне помогли в разработке. Unity уже имеет множество методов чтобы не только «дебажить логами», но и расширять, дорисовывать необходимые данные в окне редактора. Если вы новичок в Unity или вам захотелось освежить знания – «Нужно брать!».
Дебажим логами.
Класс Debug позволяет выводить текст, ошибки, рисовать линии, ставить редактор на паузу и тд.
Рассмотрим пример.
Метод будет вызван когда противник будет в зоне видимости орудия. В нем, меш оружия повернется в его сторону, нарисует линию к нему. Как только это произойдет — редактор станет в паузу.
Показать фрагмент кода
//Навести оружие на врага
void LookOnEnemy()
{
meshPoint.LookAt(_currentEnemy.myTransform.position); // Повернуть на врага
Debug.Log("Look on" + _currentEnemy.name, gameObject); //Написать в консоли на кого мы смотрим
Debug.DrawLine(myTransform.position, _currentEnemy.myTransform.position); //Нарисовать линию к текущей цели
Debug.Break(); //Поставить редактор в режим паузы
}
Gizmos
Gizmos используются для визуального дебага.
Рассмотрим пример.
Блоки, которые проходимы для врага, окрашиваются синими кубами, непроходимы – красными (заняты преградой) или серым (нет пути), финиш – голубой цвет. Если выделен элемент земли (мышкой в редакторе) – то зеленым подсвечиваются его соседи (в данном случае диагональные клетки соседями не считаются, система соседей используется для поиска пути).
Показать фрагмент кода
void OnDrawGizmos()
{
Gizmos.color = (walkable)? Color.blue: Color.red;
if (rcost == 0) // Финиш
Gizmos.color = Color.cyan;
if (walkable && rcost < 0) //Не участвуют в расчете, нет пути к ним
Gizmos.color = Color.gray;
for (int i = 0; i < Selection.gameObjects.Length; i++)
{
if (Selection.gameObjects[i] == gameObject) //Выбрана текущая земля
{
Gizmos.color = Color.green;
drawLinks = true;
}
else
drawLinks = false;
}
Gizmos.DrawCube(transform.position, Vector3.one * 0.5f); //Рисуем куб
if (drawLinks && Neighbors != null && Neighbors.Count > 0) //Показываем соседей
for (int i = 0; i < Neighbors.Count; ++i)
if (Neighbors[i] != null)
{
Gizmos.DrawLine(transform.position, Neighbors[i].transform.position);
Gizmos.DrawCube(Neighbors[i].transform.position, Vector3.one * 0.6f);
}
}
Обратите внимание что Gizmos можно включать и отключать раздельно для каждого типа компонентов в окне Scene (вверху справа) — показано развернутым на первой картинке.
Handles.
Handles используют для отображения и управления объектами в окне сцены.
Вот пошаговый пример небольшого редактора для построения кривых: http://catlikecoding.com/unity/tutorials/curves-and-splines/
Если у вас не заработает OnSceneGUI в классе BezierSplineInspector то используйте реализацию через SceneView.onSceneGUIDelegate.
Показать фрагмент кода
void OnEnable()
{
SceneView.onSceneGUIDelegate += SceneUpdate;
}
void OnDisable()
{
SceneView.onSceneGUIDelegate -= SceneUpdate;
}
void SceneUpdate(SceneView sceneView)
{
...
Handles.color = Color.white;
Handles.DrawBezier(p0, p1, c0, c1, Color.white, null, 2f);
...
}
Благодаря этому расширению кролики летают по цикличным кривым. И все это красиво настраивается в редакторе.
Attributes.
Атрибуты помогут вам в совершенно разных ситуациях: украшать инспектор, организовывать меню, работать в runtime, обрабатывать компоненты. Их становится все больше, с каждой новой версией Unity. Атрибуты можно писать самостоятельно, вот несколько интересных примеров: https://github.com/Thundernerd
Приведу пример скрипта, который двигает вертексы меша, создавая простейшую имитацию волн. Тут представлены визуальные помощники, которые ограничивают значения некоторых переменных (Range), и показывают всплывающие подсказки (Tooltip). Так же можно настроить контекстное меню для свойства (ContextMenuItem).
Показать фрагмент кода
using UnityEngine;
using System.Collections;
public class SinWater : MonoBehaviour
{
[Header("Иминация водной поверхности")]
public Vector2 range = new Vector2(0.1f, 1);
[Tooltip("Скорость волн")]
[ContextMenuItem("Reset", "resetTheValue")]
public float speed = 1f;
[Tooltip("Высота волн")]
public float maxY = 0.5f;
[Range(0.1f, 1f)]
public float offXMax = 0.1f;
[Range(0.1f, 1f)]
public float offYMax = 0.1f;
[Range(0.1f, 1f)]
public float offXSpeed = 0.1f;
[Range(0.1f, 1f)]
public float offYSpeed = 0.1f;
private Mesh mesh;
private Material mat;
void Start()
{
mesh = GetComponent<MeshFilter>().mesh;
mat = GetComponent<Renderer>().material;
}
void Update()
{
Vector3[] vertices = mesh.vertices;
int i = 0;
while (i < vertices.Length)
{
vertices[i].y = Mathf.Sin(Time.time * speed) * maxY;
if (i % 2 == 0)
vertices[i].y *= -1;
i++;
}
mesh.vertices = vertices;
Vector2 off = mat.GetTextureOffset("_MainTex");
off.x = Mathf.Sin(Time.time * offXSpeed)* offXMax;
off.y = Mathf.Sin(Time.time * offYSpeed)* offYMax;
mat.SetTextureOffset("_MainTex", off);
}
void resetTheValue()
{
speed = 1f;
}
}
Еще один часто применяемый мной прием связан с ContextMenu и ленью. Допустим, вы построили UI с 30 достижениями, они отображаются и скролятся как вы задумали. Вам необходимо заполнить List-ы в классе ссылками на них. Нужно 30 раз перетянуть названия, 30 раз описания и тд. Можно этот процесс автоматизировать.
Показать фрагмент кода
[ContextMenu("AutoFill")]
public void Fiil()
{
for (int i = 0; i < 30; ++i)
{
GameObject go = GameObject.Find("a" + i.ToString("#00"));
achivs[i].image = go.transform.FindChild("aImage").GetComponent<Image>();
achivs[i].name = go.transform.FindChild("aName").GetComponent<Text>();
achivs[i].desription = go.transform.FindChild("aDescription").GetComponent<Text>();
}
}
Свой редактор уровней
Если ваша игра подразумевает структурированные, однотипные уровни в большом количестве (в моем примере это Tower Defence). То огромную кучу времени вам может сэкономить собственный редактор уровней.
Основной его задачей, считаю, показывать только необходимую информацию, множественные действия по нажатию на одну кнопку в инспекторе, подсказки, чтение / загрузку в дополнительные файлы.
Я не буду приводить файл редактора полностью, а приведу лишь общую структуру для понимания. Суть простая – рисуем в инспекторе, ползунки да кнопочки, если необходимо отлавливать нажатия клавиш пользуемся SceneView.onSceneGUIDelegate. А видео покажет полный функционал, конкретно для моего случая.
Показать фрагмент кода
[CustomEditor(typeof(LevelEditor))]
public class LevelEditorEditor : Editor
{
LevelEditor my;
void OnEnable()
{
SceneView.onSceneGUIDelegate = WhenUpdate;
}
void WhenUpdate(SceneView sceneview)
{
Event e = Event.current;
if (e.isKey && e.character == 'a') //По нажатию клавиши а – меняем тип земли на выбранный
ChangeLand(e.mousePosition);
…
}
public override void OnInspectorGUI()
{
DrawDefaultInspector();
my = (LevelEditor)target;
if (my == null)
return;
…
DrawMoney();
…
}
void DrawMoney()
{
GUILayout.Space(10);
GUILayout.BeginVertical("box");
my.btMoney = EditorGUILayout.Foldout(my.btMoney, "Money");
if (my.btMoney)
{
my.level.gold = EditorGUILayout.IntSlider("Start Money Bank:", my.level.gold, 0, 1000);
my.level.goldStart = EditorGUILayout.IntSlider("Coins Start:", my.level.goldStart, 0, 1000);
my.level.goldMax = EditorGUILayout.IntSlider("Coins Max:", my.level.goldMax, 0, 40);
my.level.goldMinTime = EditorGUILayout.Slider("Coins Min Time:", my.level.goldMinTime, 0, 100f);
my.level.goldMaxTime = EditorGUILayout.Slider("Coins Max Time:", my.level.goldMaxTime, 0, 100f);
}
GUILayout.EndVertical();
}
}