SteamDB

» » Урок 2. Инструменты

Урок 2. Инструменты

Введение

Ты ж программист!!

Или кто?!

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

В этой статье я тебе покажу пару своих инструментов. Но если тебе в процессе создания мода понадобится специфический инструмент, то не стесняйся и делай сам.

ОглавлениеУрок 1. Скприты Lua для TTS
Урок 2. Инструменты

iHelper - палим координаты и свойства

Это маленький кубик для измерения точных координат. Создай обычный куб. Уменьши его до минимума - так измерения будут точнее. Теперь добавь к нему такой скрипт:
local my_counter = 100 --Счётчик фреймов. local obj --Текущий объект, которого мы коснулись. local ON_TABLE_Y -- "Уровень" поверхности стола. --Удобная функция для округления. local function r(x) --round return math.floor(x * 10000 + 0.5)/10000 end local TABLES_TYPES = { --Все виды столов в игре Table_Custom_Square=1, Table_Custom=1, Table_Circular=1, Table_Square=1, Table_Octagon=1, Table_Poker=1, Table_RPG=1, Table_Glass=1, Table_Hexagon=1 } --Показывает интересную информацию о координатах. local function ShowCoords() local self_coords = self:getPosition() local level = self_coords.y - 0.125 --Нижняя грань кубика (в теории). print("self ",r(self_coords.x),', ',r(self_coords.y),', ',r(self_coords.z)) print("LEVEL = ",level) if TABLES_TYPES[obj.name] then --Касание стола. ON_TABLE_Y = level --Запоминаем уровень (высоту) стола. return --Про стол нам не интересно знать, выходим. end if ON_TABLE_Y then --Если знаем уровень стола, то вычисляем высоту над столом. print("over table = ",level - ON_TABLE_Y) end local obj_coords = obj:getPosition() --Координаты объекта. print("obj = ",r(obj_coords.x),', ',r(obj_coords.y),', ',r(obj_coords.z)) --Высота = (расстояние от нижней грани кубика до середы объекта) х2. print("height = ", (level - obj_coords.y) * 2 ) local rot = obj:getRotation() print("rot = ",r(rot.x), ', ', r(rot.y), ', ', r(rot.z)) local scale = obj:getScale() print("scale = ",r(scale.x), ', ', r(scale.y), ', ', r(scale.z)) end --Событие при соприкосновении с объектом. function onCollisionEnter(info) --info - это структура с информацией (см. доки). obj = info.collision_object --Запоминаем объект для функции ShowCoords(). print(obj.name) --Выводим имя (тип) объекта в момент столкновения. my_counter = 0 --Навчинаем отсчёт времени. end function update() my_counter = my_counter + 1 if my_counter == 50 then --Ждём, когда всё утрясётся. ShowCoords() end end Затем назови его "iHelper" и добавь в свою библиотеку объектов. Всё!

Что он может? Сам посмотри. Он выводит свои координаты, это самое банальное. А также он выводит координаты объекта под собой. В скрипт можно добавить прочие свойства по необходимости.

Поиграй с кубиком. Попробуй понять, в какую сторону направлены ось X, ось Y и ось Z. Если лень, то сразу напишу ответ. Оси X и Z лежат в горизонтальной плоскости, а ось Y (высота) направлена вверх. То есть чем больше Y, тем центр предмета выше.

ИнфаОпытным путём выясняется, что размеры куба изначально 1х1х1, а максимальная степень уменьшения - в 4 раза. Таким образом, длина ребра получившегося iHelper равна 0.25, а это в свою очередь значит, что расстояние от центра до любой грани равно 0.125.

Мы уже знаем высоту нижней грани! Она выводится в строке "LEVEL =", то есть это как бы уровень того, на чем лежит кубик.

Не факт, что между предметами нет "воздушного" зазора, но ты можешь это легко проверить. Узнай высоту другого кубика. Если это 1, то зазора нет.

Далее, это позволяет нам вычислить координату поверхности стола, когда кубик лежит на столе. Надо ли напоминать, насколько это полезно?! Например, для каждого предмета теперь можно понять, лежит он или висит.

Y поверхности стола = 0.96

Если положить iHelper на другой предмет, то у нас будет доступ к его координатам. Так что мы можем вычислить его предполагаемую высоту. Именно это куб и делает. Хотя ты можешь "считерить" и коснуться боком, и получится ложная высота, но это твои проблемы - инструмент-то твой.

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

При падении на стол или другой предмет, кубик может слегка подпрыгнуть. Таким образом, столкновений может быть несколько за короткое время. Плюс, координаты в момент касания могут быть (ошибочно) получены, когда кубик ещё в полёте (т.е. вот-вот коснётся объекта). Решение простое - надо подождать немного, пока кубик не уляжется. Каждое касание с другим объектом сбрасывает таймер отсчёта, так что несколько коротких столкновений не приведут к выводу кучи информации.

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

ТочностьОна оставляет желать лучшего. Как видишь, координата может превратиться в 1.0000000789 или в 0.9999999789. Поэтому точное сравнение координат оператором "==" - плохая идея. То же касается остальных данных - поворот, ускорение, угловое ускорение. Приходится использовать операторы "больше" и "меньше" (">", "<", ">=", "<=").

Другой вариант - округление чисел. Практика показывает, что странности начинаются на шестом знаке после запятой. Для надёжности лучше рассчитывать лишь на 4 знака после запятой (в моих экспериментах были случаи, когда на 5-м знаке тоже творились чудеса).

Функции округления типа math.round в Луа - НЕТ! Увы и ах. Но есть функция отбрасывания дробной части - math.floor(). А округление легко делается на её основе:
local function round(x) return math.floor(x + 0.5) end Вот так просто. Так что даже 0.99999789 превратится в 1.49999789, а после отбрасывания дробной части станет 1.

ОптимизацияТакже на заметку. Деление существенно дороже умножения. Так что вместо x / 10000 лучше записать x * 0.0001. По сути разницы никакой, зато быстрее. Для нашего iHelper это не критично, но в высоко нагруженном скрипте лучше обратить на это внимание.

iRect - измеряем площадь

В интересных случаях тебе понадобится измерять зоны. В самом простом виде зона - это прямоугольник, параллельный осям. Он характеризуется четырьмя координатами (или двумя точками).

Конечно, можно использовать для этого iHelper - и переписывать координаты каждой точки вручную из лога. А что если зон много? Замучаешься же! Так сделаем же для этой специфической цели специфический инструмент и назовём его "iRect"!

Создай куб, покрась его в синий (чтобы отличать от iHelper) и сделай максимально маленьким. Затем добавь к нему скрипт:
--Инструмент для измерения прямоугольников. --Первый раз запоминает вершину, второй раз запоминает вторую вершину. local params = { --Для взаимодействия с тетрадью. index = 0, title = "iRect", body = "", } local point --Это сохранённая первая точка (или nil, если первой ещё нет). --Новое касание. Первое или второе - определяется чередованием. local function UpdateCollision() local coords = self:getPosition() for j=1,3 do --Округляем координаты сразу. coords[j] = math.floor(coords[j] * 1000000 + 0.5)/1000000 end --Получаем в переменную txt текущее содержимое тетради. local p = getNotebookTabs(); local txt = p[1].body; if point then --Если первая точка уже есть, то эта вторая. print("SECOND point") --Упорядочиваем координаты. Должно быть: x1 < x2 и y1 < y2. if point[1] > coords[1] then local tmp = point[1] point[1] = coords[1] coords[1] = tmp end if point[3] > coords[3] then local tmp = point[3] point[3] = coords[3] coords[3] = tmp end --Дописываем луа-структуру в тетрадь. txt = txt .. " { " .. point[1] .. ", " ..point[3] .. ", " .. coords[1] .. ", " ..coords[3] .. "},n" --Вычисляем координаты центра. --local cx = (point[1] + coords[1]) / 2 --local cz = (point[2] + coords[2]) / 2 --txt = txt .. " { " --??? Типа заготовка. point = nil --Забываем всё. else --Если первой точки нет, то эта будет первой. print("FIRST POINT") point = coords --Просто запоминаем координаты. Все расчёты на второй точке. end --Изменяем тетрадь. params.body = txt editNotebookTab(params) end local my_counter = 51 function onCollisionEnter(info) if my_counter < 200 then --Если прошло слишком мало времени, то игнорим. return --В частности, при загрузке скрипта игнорим первое касание. end --local obj = info.collision_object --Объект нам не интересен. Достаточно self --print(obj.name) my_counter = 0 end function update() my_counter = my_counter + 1 if my_counter == 50 then UpdateCollision() end end Удобство в том, что ты можешь измерить все зоны, а потом просто скопировать координаты из тетради (Notebook). Таким образом, получается не только быстрее, но и точнее, т.к. ты застрахован от ошибок и опечаток.

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

Короче, в большинстве случаев этого достаточно. Но если нет, то всё в твоих руках! Есть проблема - решай. Чего-то не хватает - сделай. Не удобно - добавь удобств. И так далее.

Ты ж программист, в конце концов! скачать dle 10.6фильмы бесплатно