При разработке 2D-игр на Unity часто возникает необходимость делать множество элементов различного размера из одного и того же материала. Самый простой пример – тайлы земли, травы, каменей и прочие элементы во всякого рода платформерах. Как правило, одинаковые тайлы по умолчанию используют один и тот же материал (в противном случае кол-во заранее созаднных материалов было бы чересчур большим). Часто делать каркас уровня из элементарных тайлов может быть неудобно из-за слишком большого кол-ва объектов на уровне, поэтому вместо элементарных тайлов используются большие тайлы – те же тайлы, только увеличенного масштаба. Допустим, нам надо поверх земли «посадить» два участка травы:

На этом рисунке поверх земли находятся 9 отдельных тайлов травы, размером 1х1. При помощи Ctrl+C, Ctrl+V восемь тайлов справа были успешно расставлены, но если на уровне должно быть порядка пары сотен тайлов травы, а самих уровней – несколько десятков, то расстановка одиночных тайлов будет отнимать слишком много времени. Оптимальным решением для такого рода задач является масштабирование одиночного тайла. Т.е. в данном случае нужно будет скопировать кусок травы и растянуть полученный объект по оси Х в восемь раз (например, в инспекторе редактора), получится такая штука:

Едва ли полученный результат можно назвать приемлемым. Для нормального масштабирования, помимо самого объекта, нужно изменить масштаб материала данного объекта (опять же в редакторе, во вкладке Inspector), а именно – параметр Tiling:

Получим следующий результат:

Как видите, блок из восьми тайлов выглядит как и задумывалось, а вот блок из одного тайла слева – сжался в восемь раз. Всё дело в том, что эти два объекта используют один и тот же материал, поэтому изменения материала одного объекта влекут за собой изменения материала другого объекта.
Данную проблему можно решить следующим путем: у каждого объекта есть компонента transform.sharedMaterial – это общий материал, а так же компонента transfrom.material – при изменении данной компоненты, создается копия используемого объектом материала, которую данный объект и будет в дальнейшем использовать, и которую можно изменять, при этом не затронув другие объекты. Создадим простенький скрипт:
TexTilingScript.js
Данный скрипт создает и масштабирует новый материал для объекта во время инициализации объекта. Добавим данный скрипт обоим объектам. Если нажать Play, то получим следующий результат:

Казалось бы, жизнь прекрасна, но есть одно «но»: такой результат получается только во время «игры», во время редактирования по-прежнему всё печально:

Вполне ожидаемый результат, т.к. скрипт выполняется только во время запуска приложения. Для того, что бы материал правильно масштабировался и во время редактирования, надо добавить обработчик данного скрипта в редактор. Для этого создадим еще один скрипт в папке Assets/Editor:
TexTilingEditorScript.js
Данный скрипт будет вызывать функцию scaleMaterial для объекта, который имеет компоненту TexTilingScript, каждый раз, когда такой объект выделяется на экране (например, выбирается мышью), и сохраняет полученные изменения:

Всё хорошо, кроме одного – в Log-е появляется ошибка:
Instantiating material due to calling renderer.material during edit mode. This will leak materials into the scene. You most likely want to use renderer.sharedMaterial instead.
Дело в том, что изменяя материал объекта во время редактирования – мы создаем новый временный материал, который после запуска приложения будет удалён. Я искренне не понимаю, почему разработчики Unity оформили данный case в виде ошибки, а не в виде предупреждения, т.к. такой расклад вполне приемлем и не приводит к каким-то конфликтам. Но если кого напрягает красный цвет – можно последовать их же совету – для масштабирования можно использовать не renderer.material, а renderer.sharedMaterial – в таком случае во время редактирования выделенного объекта его пропорции будут правильными, но другие объекты с тем же материалом будут искажены, тем не менее такой вариант вполне можно считать примелимым и удобным. Добавим новую функцию scaleSharedMaterial() в TexTilingScript:
TexTilingScript.js
Теперь в скрипте TexTilingEditorScript изменим вызываемую ф-ю:
TexTilingEditorScript.js
Теперь «ошибка» не появляется, но для того, что бы во время редактирования материал объекта масштабировался верно – надо его сначала выделить. Тоже вполне приемлимый вариант.
Ну и еще один вариант решения данной проблемы – заранее создавать для каждого объекта свой материал. Но не всем понравится иметь в исходниках проекта сотню заранее созданных материалов, когда можно обойтись всего одним, поэтому использование скриптов – одно из самых лучших решений.
Все скрипты написаны на JavaScript. Unity 3.3, Pro.

На этом рисунке поверх земли находятся 9 отдельных тайлов травы, размером 1х1. При помощи Ctrl+C, Ctrl+V восемь тайлов справа были успешно расставлены, но если на уровне должно быть порядка пары сотен тайлов травы, а самих уровней – несколько десятков, то расстановка одиночных тайлов будет отнимать слишком много времени. Оптимальным решением для такого рода задач является масштабирование одиночного тайла. Т.е. в данном случае нужно будет скопировать кусок травы и растянуть полученный объект по оси Х в восемь раз (например, в инспекторе редактора), получится такая штука:

Едва ли полученный результат можно назвать приемлемым. Для нормального масштабирования, помимо самого объекта, нужно изменить масштаб материала данного объекта (опять же в редакторе, во вкладке Inspector), а именно – параметр Tiling:

Получим следующий результат:

Как видите, блок из восьми тайлов выглядит как и задумывалось, а вот блок из одного тайла слева – сжался в восемь раз. Всё дело в том, что эти два объекта используют один и тот же материал, поэтому изменения материала одного объекта влекут за собой изменения материала другого объекта.
Данную проблему можно решить следующим путем: у каждого объекта есть компонента transform.sharedMaterial – это общий материал, а так же компонента transfrom.material – при изменении данной компоненты, создается копия используемого объектом материала, которую данный объект и будет в дальнейшем использовать, и которую можно изменять, при этом не затронув другие объекты. Создадим простенький скрипт:
TexTilingScript.js
scaleMaterial();
function scaleMaterial() {
//создадим новый материал и изменим его масштаб
renderer.material.mainTextureScale.x = transform.localScale.x;
renderer.material.mainTextureScale.y = transform.localScale.y;
}
Данный скрипт создает и масштабирует новый материал для объекта во время инициализации объекта. Добавим данный скрипт обоим объектам. Если нажать Play, то получим следующий результат:

Казалось бы, жизнь прекрасна, но есть одно «но»: такой результат получается только во время «игры», во время редактирования по-прежнему всё печально:

Вполне ожидаемый результат, т.к. скрипт выполняется только во время запуска приложения. Для того, что бы материал правильно масштабировался и во время редактирования, надо добавить обработчик данного скрипта в редактор. Для этого создадим еще один скрипт в папке Assets/Editor:
TexTilingEditorScript.js
@CustomEditor(TexTilingScript)
class TexTilingEditorScript extends Editor {
function OnSceneGUI () {
//получим ссылку на компоненту TexTilingScript для каждого объекта на сцене, который данную компоненту имеет
var script = target as TexTilingScript;
//создаём временный материал и изменяем масштаб материала объекта
script.scaleMaterial();
}
}
Данный скрипт будет вызывать функцию scaleMaterial для объекта, который имеет компоненту TexTilingScript, каждый раз, когда такой объект выделяется на экране (например, выбирается мышью), и сохраняет полученные изменения:

Всё хорошо, кроме одного – в Log-е появляется ошибка:
Instantiating material due to calling renderer.material during edit mode. This will leak materials into the scene. You most likely want to use renderer.sharedMaterial instead.
Дело в том, что изменяя материал объекта во время редактирования – мы создаем новый временный материал, который после запуска приложения будет удалён. Я искренне не понимаю, почему разработчики Unity оформили данный case в виде ошибки, а не в виде предупреждения, т.к. такой расклад вполне приемлем и не приводит к каким-то конфликтам. Но если кого напрягает красный цвет – можно последовать их же совету – для масштабирования можно использовать не renderer.material, а renderer.sharedMaterial – в таком случае во время редактирования выделенного объекта его пропорции будут правильными, но другие объекты с тем же материалом будут искажены, тем не менее такой вариант вполне можно считать примелимым и удобным. Добавим новую функцию scaleSharedMaterial() в TexTilingScript:
TexTilingScript.js
scaleMaterial();
function scaleMaterial() {
//создадим новый материал и изменим его масштаб
renderer.material.mainTextureScale.x = transform.localScale.x;
renderer.material.mainTextureScale.y = transform.localScale.y;
}
function scaleSharedMaterial() {
//редактируем только общий материал, не создавая при этом нового
renderer.sharedMaterial.mainTextureScale.x = transform.localScale.x;
renderer.sharedMaterial.mainTextureScale.y = transform.localScale.y;
}
Теперь в скрипте TexTilingEditorScript изменим вызываемую ф-ю:
TexTilingEditorScript.js
@CustomEditor(TexTilingScript)
class TexTilingEditorScript extends Editor {
function OnSceneGUI () {
//получим ссылку на компоненту TexTilingScript для каждого объекта на сцене, который данную компоненту имеет
var script = target as TexTilingScript;
//изменяем масштаб общего материала объекта
script.scaleSharedMaterial();
}
}
Теперь «ошибка» не появляется, но для того, что бы во время редактирования материал объекта масштабировался верно – надо его сначала выделить. Тоже вполне приемлимый вариант.
Ну и еще один вариант решения данной проблемы – заранее создавать для каждого объекта свой материал. Но не всем понравится иметь в исходниках проекта сотню заранее созданных материалов, когда можно обойтись всего одним, поэтому использование скриптов – одно из самых лучших решений.
Все скрипты написаны на JavaScript. Unity 3.3, Pro.