danilaxxl danilaxxl

CollectableItemData.cs

[CreateMenuItem(fileName = "newItem", menuName = "Data/Items/Collectable", order = 51]

GoloGames GoloGames

vadya_ivan, рад, что вам игра показалась интересной : )

P.S. Кстати уже доступна бесплатная демо-версия в Steam

vadya_ivan vadya_ivan

Визуал, задумка, музыка , механики, все в цель

GoloGames GoloGames

Ato_Ome, спасибо за позитивные эмоции, будем стараться : )

Ato_Ome Ato_Ome

Потрясающий результат, все так четенько, плавненько)
То ли саунд, то ли плавность напомнили мне игрушку World of Goo, удачи вам в разработке и сил побольше дойти до релиза!)

Cute Fox Cute Fox

Graphics are a little cool, good HD content. But this game doesn't cause nary interest me.
However the game is well done.

GMSD3D GMSD3D

Почему действие после всех условий выполняется?
[step another object]

Zemlaynin Zemlaynin

Jusper, Везде, но наугад строить смысла нет. Нужно разведать сперва территорию на наличие ресурсов.

Jusper Jusper

Zemlaynin, а карьеры можно будет везде запихать?
Или под них "особые" зоны будут?

Zemlaynin Zemlaynin

Это так скажем тестовое строительство, а так да у города будет зона влияния которую нужно будет расширять.

Jusper Jusper

А ссылка есть?

Jusper Jusper

Я не оч понял из скриншота, как вообще работает стройка. У игрока будет как бы поле строительства?

split97 split97

в игру нужно добавить время песочные часы в инвентаре, пока бегаешь наберается усталость и ты очень тормозной мобильный враг просто убевает

split97 split97

в игру нужно добавить время песочные часы в инвентаре, пока бегаешь наберается усталость и ты очень тормозной мобильный враг просто убевает

ViktorJaguar ViktorJaguar

Почему я нигде не могу найти нормальный туториал, где покажут как экипировать предмет (например, меч) в определенную (выделенную под оружие) ячейку???

Логотип проекта Unity

Как мы шейдер писали

Вместо предисловия

Мы с LLlypuK'ом в очередной раз затеяли захват мира долгостроящийся проект, но на этот раз мы не гарантируем его выполнение... и это скорее даже не сама цель... Целью можно назвать изучение такой среды как Unity3d, а профит в получении некоторых навыков. Если получится из этого еще и игру сделать, то мы не расстроимся, а даже наоборот. Если кому интересно, проект имеет рабочее название Voluntarium, но это все, что мы пока о нем можем сказать.
О чем же я буду говорить? О том, какие интересные (сугубо на мой личный взгляд) решения мы нашли, чем можем поделиться и так далее. Ну что-ж.... первый блин -поехали.

Медленно к сути

Задался я целью соорудить на базе стандартного редактора ландшафта некий шейдер, который изменил бы внешний вид игры в лучшую сторону, так как стандартный весьма уныл... все чего мы добились я показывать не стану, но об одном аспекте рассказать стоит. Те кто пользовался стандартным ландшафтом заметили, что при рисовании невозможно изменить размер рисуемых текстур... с этого и начнем.

Нам понадобятся

http://xgm.guru/files/192/123998/Ashen_Grass_small.tga
http://xgm.guru/files/192/123998/Ashen_Vines_small.tga
http://xgm.guru/files/192/123998/Ashen_Rock_small.tga

я вежливо позаимствовал их у старичка WC3... и слегка подправил

Можно скачать отсюда
Или же взять здесь... нам понадобятся эти шейдеры:

Shader "Nature/Terrain/Diffuse" {
Properties {
	[HideInInspector] _Control ("Control (RGBA)", 2D) = "red" {}
	[HideInInspector] _Splat3 ("Layer 3 (A)", 2D) = "white" {}
	[HideInInspector] _Splat2 ("Layer 2 (B)", 2D) = "white" {}
	[HideInInspector] _Splat1 ("Layer 1 (G)", 2D) = "white" {}
	[HideInInspector] _Splat0 ("Layer 0 (R)", 2D) = "white" {}
	// used in fallback on old cards & base map
	[HideInInspector] _MainTex ("BaseMap (RGB)", 2D) = "white" {}
	[HideInInspector] _Color ("Main Color", Color) = (1,1,1,1)
}
	
SubShader {
	Tags {
		"SplatCount" = "4"
		"Queue" = "Geometry-100"
		"RenderType" = "Opaque"
	}
CGPROGRAM
#pragma surface surf Lambert
struct Input {
	float2 uv_Control : TEXCOORD0;
	float2 uv_Splat0 : TEXCOORD1;
	float2 uv_Splat1 : TEXCOORD2;
	float2 uv_Splat2 : TEXCOORD3;
	float2 uv_Splat3 : TEXCOORD4;
};

sampler2D _Control;
sampler2D _Splat0,_Splat1,_Splat2,_Splat3;

void surf (Input IN, inout SurfaceOutput o) {
	fixed4 splat_control = tex2D (_Control, IN.uv_Control);
	fixed3 col;
	col  = splat_control.r * tex2D (_Splat0, IN.uv_Splat0).rgb;
	col += splat_control.g * tex2D (_Splat1, IN.uv_Splat1).rgb;
	col += splat_control.b * tex2D (_Splat2, IN.uv_Splat2).rgb;
	col += splat_control.a * tex2D (_Splat3, IN.uv_Splat3).rgb;
	o.Albedo = col;
	o.Alpha = 0.0;
}
ENDCG  
}

Dependency "AddPassShader" = "Hidden/TerrainEngine/Splatmap/Lightmap-AddPass"
Dependency "BaseMapShader" = "Diffuse"
Dependency "Details0"      = "Hidden/TerrainEngine/Details/Vertexlit"
Dependency "Details1"      = "Hidden/TerrainEngine/Details/WavingDoublePass"
Dependency "Details2"      = "Hidden/TerrainEngine/Details/BillboardWavingDoublePass"
Dependency "Tree0"         = "Hidden/TerrainEngine/BillboardTree"

// Fallback to Diffuse
Fallback "Diffuse"
}
Shader "Hidden/TerrainEngine/Splatmap/Lightmap-AddPass" {
Properties {
	_Control ("Control (RGBA)", 2D) = "black" {}
	_Splat3 ("Layer 3 (A)", 2D) = "white" {}
	_Splat2 ("Layer 2 (B)", 2D) = "white" {}
	_Splat1 ("Layer 1 (G)", 2D) = "white" {}
	_Splat0 ("Layer 0 (R)", 2D) = "white" {}
}
	
SubShader {
	Tags {
		"SplatCount" = "4"
		"Queue" = "Geometry-99"
		"IgnoreProjector"="True"
		"RenderType" = "Opaque"
	}
	
CGPROGRAM
#pragma surface surf Lambert decal:add
struct Input {
	float2 uv_Control : TEXCOORD0;
	float2 uv_Splat0 : TEXCOORD1;
	float2 uv_Splat1 : TEXCOORD2;
	float2 uv_Splat2 : TEXCOORD3;
	float2 uv_Splat3 : TEXCOORD4;
};

sampler2D _Control;
sampler2D _Splat0,_Splat1,_Splat2,_Splat3;

void surf (Input IN, inout SurfaceOutput o) {
	fixed4 splat_control = tex2D (_Control, IN.uv_Control);
	fixed3 col;
	col  = splat_control.r * tex2D (_Splat0, IN.uv_Splat0).rgb;
	col += splat_control.g * tex2D (_Splat1, IN.uv_Splat1).rgb;
	col += splat_control.b * tex2D (_Splat2, IN.uv_Splat2).rgb;
	col += splat_control.a * tex2D (_Splat3, IN.uv_Splat3).rgb;
	o.Albedo = col;
	o.Alpha = 0.0;
}
ENDCG  
}

Fallback off
}
Ну и базовое понимание, что такое шейдеры, UV координаты, и как это все работает в unity3d.

К делу

  1. Для начала создадим Terrain в Unity3d. Добавим в него три наши текстуры... и что нибудь ими нарисуем. У меня лично получилось что-то подобное:
Как мы шейдер писали — Unity — DevTribe: инди-игры, разработка, сообщество

Да... не очень красиво, но нам лишь для понимания сути.
Начнем с создания своего материала и двух шейдеров с тем кодом, который приведен выше.
Внесем в первый шейдер правки:

//заменим строчку
Shader "Nature/Terrain/Diffuse"
//на эту
Shader "MyShader/Terrain"
//а эту
Dependency "AddPassShader" = "Hidden/TerrainEngine/Splatmap/Lightmap-AddPass"
//на эту
Dependency "AddPassShader" = "Hidden/Terrain/AddPass"

а во втором шейдере правки будут такими

//заменим строчку
Shader "Hidden/TerrainEngine/Splatmap/Lightmap-AddPass"
//на эту
Shader "Hidden/Terrain/AddPass"

Этим самым мы сменим путь, по которому их будет искать Unity3d в своей библиотеки шейдеров в рамках нашего проекта.
Стоит немного рассказать о роли данных шейдеров. Первый - это основной шейдер, который занимается смешиванием текстур на основе управляющей текстуры, а точнее он занимается смешиванием первых 4х текстур, для всех последующих четверок вызывается второй шейдер. Для этого в первом и создается зависимость:

Dependency "AddPassShader" = "Hidden/Terrain/AddPass"

Как работает сам шейдер? Очень просто. Когда вы водите кистью по ландшафту, внутренний скрипт рисует в одном из каналов так называемой управляющей текстуры (в шейдере это текстура _Control). То есть красный канал - степень влияния первой текстуры, зеленый - второй, синий - третьей и альфа канал - степень влияния на итог текстуру четвертой текстуры. Для последующих текстур, создаются дополнительный управляющие текстуры (по одной на каждую дополнительную четверку). В самом шейдере просто перемножаются текстуры с их степенью влияния, складываются и выводится результат. Так как скрипт отслеживает редактирования всех контрольных текстур таким образом, чтоб сумма всех влияний в каждой точке не превышала 1, то у нас на экране не мешанина из пикселей, а вполне вменяемая картинка. Каждый проход шейдера накладывается на предыдущие аддитивным блендингом. За что отвечает вот эта строчка во втором шейдере:

#pragma surface surf Lambert decal:add //а точнее даже вот эта ее часть: decal:add

Назначьте вашему материалу шейдер MyShader -> Terrain, затем поменяйте у ландшафта стандартный материал, на ваш:

Как мы шейдер писали — Unity — DevTribe: инди-игры, разработка, сообщество

Начнем небольшие правки

Все правки вноситься будут в оба файла в аналогичные разделы, потому я не буду лишний раз писать, что изменения надо дублировать. Для начала развяжем себе руки и впишем после

#pragma surface surf Lambert

вот это

#pragma target 3.0

Этим самым мы указали, что наши шейдеры будут использовать третью модель шейдеров. Тем самым отсекли очень старые видеокарточки, но увеличили количество доступных в шейдере вычислений... так или иначе в конце нам это понадобится.
Затем в раздел Properties добавим следующее:

_Scale ("Texture Scale", Float) = 1

а после

sampler2D _Splat0,_Splat1,_Splat2,_Splat3;

добавим

float _Scale;

это будет наш масштаб. Его мы можем увидеть в свойствах нашего материала:

Как мы шейдер писали — Unity — DevTribe: инди-игры, разработка, сообщество

но его изменения нам пока ничего не дадут. Для того, чтоб он заработал, давайте изменим UV координаты наших текстур. Для этого заменим функцию

void surf (Input IN, inout SurfaceOutput o) {
	fixed4 splat_control = tex2D (_Control, IN.uv_Control);
	fixed3 col;
	col  = splat_control.r * tex2D (_Splat0, IN.uv_Splat0).rgb;
	col += splat_control.g * tex2D (_Splat1, IN.uv_Splat1).rgb;
	col += splat_control.b * tex2D (_Splat2, IN.uv_Splat2).rgb;
	col += splat_control.a * tex2D (_Splat3, IN.uv_Splat3).rgb;
	o.Albedo = col;
	o.Alpha = 0.0;
}

на

void surf (Input IN, inout SurfaceOutput o) {
	float2 realUV0, realUV1, realUV2, realUV3;
	
	realUV0 = IN.uv_Splat0 * _Scale;
	realUV1 = IN.uv_Splat1 * _Scale;
	realUV2 = IN.uv_Splat2 * _Scale;
	realUV3 = IN.uv_Splat3 * _Scale;
	
	fixed4 splat_control = tex2D (_Control, IN.uv_Control);
	fixed3 col;
	
	col  = splat_control.r * tex2D (_Splat0, realUV0).rgb;
	col += splat_control.g * tex2D (_Splat1, realUV1).rgb;
	col += splat_control.b * tex2D (_Splat2, realUV2).rgb;
	col += splat_control.a * tex2D (_Splat3, realUV3).rgb;
	o.Albedo = col;

	o.Alpha = 0.0;
}

Сохраним и вернемся в редактор. Если вы все сделали правильно, то при изменении добавленного нами параметра в материале, должны меняться и размеры текстур. Например вот так у меня теперь выглядит ландшафт для параметров 2 и 0.2 соответственно без изменений на самой сцене и неизменном положении камеры.

Как мы шейдер писали — Unity — DevTribe: инди-игры, разработка, сообщество
Как мы шейдер писали — Unity — DevTribe: инди-игры, разработка, сообщество

При большом удалении заметна "регулярность" текстур... что не так приятно... Избавимся от этого!

Следующий шаг

Я на самом деле изначально схитрил, объединив 4 тайла в одну текстуру... теперь моя идея заключается в следующем: порезать каждую текстуру на 4 куска, и вместо самой текстуры выводить рандомно взятый кусок.
Чтоб этого добиться введем параметр

_TileCount ("Texture grid size", Float) = 2.0

и переменную

float _TileCount;

так же как мы это делали со _Scale. Плюс... мы напишем еще две функции:

float2 rand2(float2 n)
{
  float2 result;
  result.x = frac(sin(fmod(dot(n.xy, float2(12.9898, 78.233)),3.14)) * 43758.5453);
  result.y = frac(sin(fmod(dot(n.yx, float2(12.9898, 78.233)),3.14)) * 43758.5453);   
  
  return result;
}

float2 TextOffset(float2 n)
{
	float2 result;
	result = rand2(floor(n));
	result = floor(result * _TileCount)/_TileCount;
	return result;
}
//вставить их надо перед "void surf (Input IN, inout SurfaceOutput o) {"

Введенный нами параметр - это количество тайлов в текстуре, по одной стороне. То есть в нашем случае это 2. Текстура 2 на 2 тайла.
Первая - функция это псевдорандом для шейдеров. Взят из интернета, для наших целей он вполне сгодится. Его минус в том, что при передаче в него одинаковых векторов, мы получаем одинаковые значения... но так как у нас для одних и тех же ячеек в течении всей игры тайл должен быть одним и тем же... нас это вполне устроит. Возвращает эта функция вектор со случайными координатами.
Вторая функция интереснее. В нее мы будем передавать UV координаты самой текстуры. Сам ландшафт отдает их не от 0 до 1, а от 0 до n*1, где n - это во сколько раз сам ландшафт больше нашей текстуры... ну или сколько раз она в него войдет. Переданные UV мы обрезаем до целой части и передаем рандому. Обрезаем мы его для того, чтоб для всего квадрата выдавалось одно и то же значение (например для 2.4 и 2.8 это всегда будет 2). Рандом возвращает значения от 0 до 1. Мы умножаем полученное на количество тайлов по одной стороне текстуры. Тем самым расширяя диапозон до "от 0 до _TileCount". Полученное число мы обрезаем до целой части, получая "случайно" выбранный номер тайла... а затем снова делим его на _TileCount, получая смещение UV координат тайла в текстуре.
Далее нам следует изменить функцию surf, и добавить после

        realUV0 = IN.uv_Splat0 * _Scale;
	realUV1 = IN.uv_Splat1 * _Scale;
	realUV2 = IN.uv_Splat2 * _Scale;
	realUV3 = IN.uv_Splat3 * _Scale;

эти строчки

	realUV0 = (frac(realUV0)/_TileCount + TextOffset(realUV0));
	realUV1 = (frac(realUV1)/_TileCount + TextOffset(realUV1));
	realUV2 = (frac(realUV2)/_TileCount + TextOffset(realUV2));
	realUV3 = (frac(realUV3)/_TileCount + TextOffset(realUV3));

Что мы делаем этим? Во первых мы переходим к честным UV координатам от 0 до 1, вычленяя дробную часть из них функцией frac. Затем мы делим полученное на _TileCount, чтоб получить координаты не от 0 до 1 (то есть всей текстуры), а только одного тайла. Затем делаем поправку на смещение... и получаем нужные нам координаты. Сохраняем шейдеры, идем в редактор и ставим значение нового параметра в 2.

Как мы шейдер писали — Unity — DevTribe: инди-игры, разработка, сообщество

Если все сделали правильно, то должно получится что-то вроде:

Как мы шейдер писали — Unity — DevTribe: инди-игры, разработка, сообщество

Далее можно сделать еще одно. Заменить текстуры на следующие (в них не 4 тайла, а 16):
http://xgm.guru/files/192/123998/Ashen_Grass.tga
http://xgm.guru/files/192/123998/Ashen_Rock.tga
http://xgm.guru/files/192/123998/Ashen_Vines.tga
И изменить параметр отвечающий за количество текстур с 2, до 4... у меня получилось что-то такое:

Как мы шейдер писали — Unity — DevTribe: инди-игры, разработка, сообщество

Вместо послесловия

Собственно жду отзывов, как и от тех, кто крут в этом (а ведь я только учусь, не претендую на лавры), так и тех, кто только хочет заняться изучением unity3d и шейдеров в том числе. Будет интересны ваши отзывы, предложения и вопросы.

Смотрите также:


Комментарии



триггеры варкрафтовские на юнити создают, терраин тоже, пора уже делать модели и графику ) и будет Вар 4 )

Padalekki:

триггеры варкрафтовские на юнити создают, терраин тоже, пора уже делать модели и графику ) и будет Вар 4 )

Есть такое выражение: можно вывезти бабу из деревни, но деревню из бабы вывезти нельзя. Вот по аналогии и конечно же в хорошем смысле)

Padalekki, ну текстурки я взял стеба ради, не более =)

Давненько тебя здесь не было видно, Андреич..)

У этого подхода есть крохотный недостаток - нет возможности принудительно сменить вариацию тайла в том или ином фрагменте карты, если случайная генерация выдала не очень приятный результат, плюс я не уверен что генератор случайных чисел выдаст одинаковые значения между запусками.

Как по мне, то наложение процедурных искажений на текстуру более перспективно в плане украшательства картинки, чем возня с вариациями тайлов.

prog, генератор выдаст одно и то же. Посмотри внимательно на функцию. Увидишь там хоть что-то вариативное от запуска к запуску... ну будешь большим молодцом. Что касается возможности принудительно сменить вариацию тайла... ну да, есть такое. Но как бы цель была сделать стандартный террайн чуть более красивым... если речь идет о такого рода модификациях, то скорее всего придется писать свой террайн энжин. Тут же есть ряд ограничений, хотя бы связанных с тем, что для каждого прохода шейдера все текстуры устанавливаются программно + шейдер как таковой не умеет сохранять данные на диск и брать их оттуда =)

MF, да, не очень внимательно посмотрел - перепутал с другой реализацией.

Не, ну просто не вежливо такие hi-res картинки вылаживать. Точнее претензия не к разрешению, но к размеру. Сделайте в jpeg, это ведь не сложно. //Или ужать, или под кат(я хз, подгружает ли он сейчас аяксом своё содержимое, но если да, то use it).
Я конечно понимаю, что тут все миллионеры и живут в столицах, где дешевый быстрый безлимитный интернет, но...

Mihahail, я извиняюсь за свою привычку работать с пнг файлами. В интернет что-то не выкладывал уже давно, а для моих нужд формат более чем удобный, вот и привык =). Теперь там jpg, но под каты прятать не стал.

Перенес статью в Юнити.

Справка