Clamp's Physics Engine

» опубликован
Небольшая наработка, реализующая достаточно высокоэффективную одновременную обработку множества снарядов, движущихся с соблюдением основных законов кинематики. В карте присутствует сама система и техдемо к ней, а также краткое описание. Для работы требуются установленные парсеры vJass и cJass.
Особенности:
  • Одновременная обработка до 400 активных снарядов без падения производительности
  • Возможность "на ходу" изменять частоту обработки снарядов и основные константы
  • Максимальная отвязанность системы от внутриигровых контроллеров движения
  • Хорошо написанный самодокументирующийся код с прозрачной архитектурой
Кроме того, в карте можно найти полную эмулированую библиотеку "math.h" из набора стандартных библиотек Си (в системе практически не используется, делал когда-то ради собственного удовольствия), полновесный набор функций для работы с векторами и классную модельку дамми-юнита со множеством анимаций разных углов наклона (к сожалению, автор модели мне неизвестен).
С радостью приму любой фидбек относительно системы, а также предложения по улучшению реализованного в ней функционала.
P.S.: версия системы указана как "0.9", однако при отсутствии каких-либо существенных замечаний по поводу её реализации она же будет являться финальной ("1.0").
P.P.S.: возможно, на днях запилю пару небольших статеек с более-менее подробным разбором системы и/или векторного подхода к движению (старая, кмк, не слишком информативна), о потребности пишите в комменты.
P.P.P.S.: если что, я не программист и никогда себя им не считал / не называл, так что в случае нахождения каких-нибудь заметных косяков прошу не пылать экспрессией на весь сайт. // Toadcop, привет маме! Мне понятны твои мотивы, но фильтры на базарах таки иногда надо включать.


Просмотров: 1 346

» Лучшие комментарии


Clamp #1 - 1 год назад 0
Если кому-нибудь зачем-нибудь понадобится, то по отдельной просьбе закину код системы в сам ресурс.
Кет #2 - 1 год назад 0
«0x41726176» — чо это за абила?
Doc #4 - 1 год назад (отредактировано ) 4
Сори, но это пиздец говно. Я заревьювил все твои 500-600 строк кода и единственное, что в нем хорошего это стиль, код реально чистый.
Начнем по порядку.
    private void StackPop(int id) {
        stackCounter--;
        stack[id] = stack[stackCounter];
    }
Здесь у меня просто случилась аневризма. Стэк так не работает. При чем тут стек и зачем эта переменная названа стек - непонятно. Это random access list. В стеке нет доступа ни к чему, кроме хвоста.
        private PhysObj physic;
        private VisObj visual;
        private DmgObj damage;
Честно просто смешная попытка замутить нечто похожее на entity-component-system, ни о какой модульности речи тут не идет, с тем же успехом можно было слить все эти чайлд структуры в основную и ничего бы не изменилось. Оверинжиниринг без цели и бенефитсов.
Снаряды тупо всегда уничтожают себя при колижне с юнитами. Это та самая хваленая "матмодель"? Я не знаю смеяться или плакать. Это преимущество над движком тсх? Ты понимаешь вообще что там было основано на входе в ректангл только чтобы максимально перенести отсеивание колижнов в нативный код? Что наверняка в движке реализовано какое-нибудь quadtree которое нормально работает с большим количеством юнитов? Что есть у тебя? GroupEnumUnits? Я уже не говорю о том что поделие совершенно нерасширяемое, ты просто сам попробуй не трогая основной код реализовать пару спеллов. Тут даже такие банальности как то, что гравитация это скаляр, а не вектор. Зато зачем-то есть настраиваемый тикрейт и сила трения. Сила трения правда одна на всю карту, удобно.
Мега физика в "движке" это семплинг нормали граунда через GetTerrainZ? А, нет, вот, нашел:
this.impulse.z -= this.mass*gravity;
вот с этого просто в АХАХАХА, т.е. объект который весит меньше будет медленнее падать?
Какбы в итоге зашибись, учиться - учись. Но понты и умничанье в оффтопке лучше попридержать до момента пока не научишься. Тонна таких и намного лучше систем валяется на барахолке уже с десяток лет, без шуток. Код с семплингом нормали я видел в каждой из них, да и сам писал такой. Подобная система есть, например, у ханабиши.
girvel #5 - 1 год назад 0
Doc, справедливости ради, объект, который весит больше, действительно в реальности быстрее падает из-за сопротивления воздуха. Да, переменную лучше назвать не mass, а как-нибудь наподобие resistance_factor, но фича с практической точки зрения полезная.
Doc #6 - 1 год назад (отредактировано ) 0
То есть в этом случае чем больше сопротивление тем быстрее падает объект?)
Я бы ничего это не писал если бы не заявления в духе
[20:06:50] Clamp * Потому что всё от и до построено на матмодели, а не через костыли типа "юнит вошёл в область"
[20:06:16] Clamp * Доделал по инерции, смею утверждать, что у меня физика производительнее, чем в TcX
И прочий ненужный пафос.
Clamp #7 - 1 год назад (отредактировано ) 0
ты просто сам попробуй не трогая основной код реализовать пару спеллов
Реализовал, потом мб залью мапу (если допилю).
Тут даже такие банальности как то, что гравитация это скаляр, а не вектор.
Исходно делал как раз через вектор, но не смог найти векторному выражению никаких применений, и перевёл в скаляр, хз.
Ты понимаешь вообще что там было основано на входе в ректангл только чтобы максимально перенести отсеивание колижнов в нативный код?
Тащемта, да, и это вроде где-то обсуждалось даже активно
зачем-то есть настраиваемый тикрейт
Глубоко в концепции это задумывалось как метод снизить нагрузку через снижение тикрейта при повышении количества объектов, но позже эмперически выяснил, что сам факт наличия такого числа юнитов оказывает на нагрузку несравнимо большее влияние, за сим оставил.

На самом деле, если бы не "ненужный пафос", то хрена-с-два я бы дождался здесь хоть какого-нибудь code review от практикующего специалиста, так что хз даже про его "нужность" или "ненужность". Хотя метод, конечно, не самый красивый, тут каюсь и приношу извинения.

При чем тут стек
Копипаста со статьи Скорпа, я знаю, что такое стак, но не увидел никакой проблемы в именовании, простите, если задел в нежных чувствах =)
с тем же успехом можно было слить все эти чайлд структуры в основную и ничего бы не изменилось.
стало бы нечитаемо, лол
На деле, это использую примерно так: есть набор пресозданых "визобжей" и дамагов, сам "физобж" варится по требованиям и на него вешаются копии нужных мне кусков конфигурации. Не помню, как это "правильно" называется, допустим, "factory" (надеюсь, не задел в нежных чувствах, если ошибся ;D)
Doc:
вот с этого просто в АХАХАХА
Ничего не могу сказать, действительно обосрался в полный рост \о/
Как-то получилось, что в игре выглядит нормально, "да и хер с ним, не буду трогать"
объект который весит меньше будет медленнее падать?
Там же проекция на ось z, то есть технически это ускорение падения, а не наоборот.
Снаряды тупо всегда уничтожают себя при колижне с юнитами.
А что им ещё делать, лол?

Если в сухом остатке, то действительно спасибо за то, что понатыкал в несколько куч, учту. Хотел бы попросить менее эмоционально и более предметно потыкать, по возможности, но само собой настаивать не могу.
Doc #8 - 1 год назад 0
Фактически проблемы нет в наименовании никакой, работает - ок, но по факту если претендуешь на понятный код то естественно называть нужно своими именами.
Уничтожаться ниче офк не должно, какого фига физический объект должен за это отвечать? Но мое изначальное замечание было в сторону настраиваемости "движка".
Clamp #9 - 1 год назад 0
если претендуешь на понятный код то естественно называть нужно своими именами.
Справедливо.
Уничтожаться ниче офк не должно, какого фига физический объект должен за это отвечать?
написал было возражение, но потом призадумался и передумал...


ЕМНИП, операции с базовыми типами (int, float, ...) напрямую обрабатываются, без дополнительных обёрток. Идея как раз заключалась в том, что всё, кроме визуала (юнит + эффект) хранится и обрабатывается в виде конструкций из этих базовых типов, даже запилил квадтри (только понятия не имел, что оно так называется) для упихивания "своих" декораций. Но, увы, там нашёлся баг, локализацию которого я счёл неоправданно трудоёмкой после ряда попыток и вовсе отказался от идеи юзать дудады.

единственное, что в нем хорошего это стиль
Вот это, должен признаться, нормально так огрело. Действительно, ничего больше?
Doc #10 - 1 год назад 2
В коде нет совершенно ничего нового да и старое сделано на данный момент не везде лучшим образом, в этом и суть.
xgm.guru/forum/showthread.php?t=25570 пжлст, опана, гляди-ка
void HitGround(Body a) {
     real z0 = GetZ(a.x,a.y)
     real z2 = GetZ(a.x+CollPlosk,a.y)
     real z1 = GetZ(a.x,a.y+CollPlosk)
     real nx = (z0-z2)/SquareRoot((z0-z2)*(z0-z2) + (z0-z1)*(z0-z1) + CollPlosk*CollPlosk)
     real ny = (z0-z1)/SquareRoot((z0-z2)*(z0-z2) + (z0-z1)*(z0-z1) + CollPlosk*CollPlosk)
     real nz = CollPlosk/SquareRoot((z0-z2)*(z0-z2) + (z0-z1)*(z0-z1) + CollPlosk*CollPlosk)
Абсолютно то же самое, например. Да, код выглядит как нечитабельное говно. Да, тут тоже массивы называются стеком. Но тут и фичи и конфигурация.
Sergarr #11 - 1 год назад 0
gravity = 9.8/tickrate;
Я конечно понимаю, что "матмодели" это круто, и вообще код выглядит немного за пределами моего разумения как недопрограммиста, но постоянная гравитации ну никак не может зависеть от внутренней частоты работы физ. системы.
И еще, то, что в системе называется "impulse", это на самом деле должно называться "velocity", потому что импульс это масса*скорость, отсюда кстати и та вышеуказанная ошибка с умножением на массу.
Clamp #12 - 1 год назад (отредактировано ) 0
Sergarr:
gravity = 9.8/tickrate;
постоянная гравитации ну никак не может зависеть от внутренней частоты работы физ. системы
Каждые 1/tickrate секунды запускается обработка списка снарядов. Ускорение свободного падение (как и любое) измеряется в "метров в секунду за секунду", то есть в "изменении скорости за секунду". А за 1/tickrate секунды скорость изменится на 1/tickrate от полного значения.
И еще, то, что в системе называется "impulse", это на самом деле должно называться "velocity", потому что импульс это масса*скорость, отсюда кстати и та вышеуказанная ошибка с умножением на массу.
Вполне вероятно, что так и есть, исходную версию начинал писать через импульсы.
Doc #13 - 1 год назад 0
И еще, то, что в системе называется "impulse", это на самом деле должно называться "velocity", потому что импульс это масса*скорость, отсюда кстати и та вышеуказанная ошибка с умножением на массу.
Я вот с этим кстати мега согласен и на самом деле просто забыл написать.
Sergarr #14 - 1 год назад (отредактировано ) 0
Clamp:
Sergarr:
gravity = 9.8/tickrate;
постоянная гравитации ну никак не может зависеть от внутренней частоты работы физ. системы
Каждые 1/tickrate секунды запускается обработка списка снарядов. Ускорение свободного падение (как и любое) измеряется в "метров в секунду за секунду", то есть в "изменении скорости за секунду". А за 1/tickrate секунды скорость изменится на 1/tickrate от полного значения.
А почему нельзя вот это деление на tickrate сделать непосредственно в физическом коде? Вот здесь, например:
this.impulse.z -= this.mass*gravity;
Вместо того, чтобы умножать на массу, делить на тикрейт. В бонусе - тебе не придется перерасчитывать величину gravity каждый раз, когда ты меняешь tickrate.
И еще:
                float nextX = this.location.x + this.impulse.x;
                float nextY = this.location.y + this.impulse.y;
Вообще, у тебя этот "импульс" (который на самом деле скорость), для того чтобы все было именно что по "матмодели", обязательно должен везде делится на tickrate - иначе получается, что ты к величине размерности расстояния добавляешь величину размерности скорости, что не есть хорошо.
Да, и еще: 9,8 - это для метров. В варике 1 единица расстояния это вовсе не метр, а гораздо меньшая величина. В варике 1 метр это где-то 100 единиц расстояния. Так что константа gravity у тебя должно быть не =9.8, а =9.8/100. И без всякого tickrate-а, разумеется.
Clamp #15 - 1 год назад (отредактировано ) 0
Да, и еще: 9,8 - это для метров. В варике 1 единица расстояния это вовсе не метр, а гораздо меньшая величина.
Это вполне очевидно. Выставил 9.8, визуально результат меня устроил, с чего бы менять?

В бонусе - тебе не придется перерасчитывать величину gravity каждый раз, когда ты меняешь tickrate.
Такой себе по ценности, одно дополнительное деление и одно дополнительное умножение раз в практически никогда процессор сможет выдержать.
обязательно должен везде делится на tickrate
Точно! Каждое деление вектора на число - это три операции деления, каждый объект описывается двумя векторами, то есть всего шесть операций деления. Достаточно согласиться с твоим предложением, и их количество вырастает до девяти, что не то, чтобы критично, но в 1.5 раза тяжелее без каких-либо профитов вообще.
Кроме того, в самом коде физического компонента объекта логика описывается пусть с уже обмусоленной ошибкой, но всё-таки универсально: при обновлении объекта он смещается в направлении суммирующей на её величину, а скейлинг этой величины под тикрейт - работа контроллера, который обрабатывает этот объект и устанавливает сам тикрейт.
Sergarr #16 - 1 год назад 0
Clamp:
без каких-либо профитов вообще.
Смысловое понимание кода увеличивается в разы. Что напрямую сказывается на времени его написания и отладки, из-за отсутствия/наличия дурацких ошибок, как с этим impulse и умножением на массу. Оптимизация это вообще дело последнее, которое выполняется уже после реализации всего, что хотели реализовать, поскольку добавлять что-нибудь еще к оптимизированному коду - это чистое мучение.
Clamp:
Кроме того, в самом коде физического компонента объекта логика описывается пусть с уже обмусоленной ошибкой, но всё-таки универсально: при обновлении объекта он смещается в направлении суммирующей на её величину, а скейлинг этой величины под тикрейт - работа контроллера, который обрабатывает этот объект и устанавливает сам тикрейт.
Вот эту часть текста я не очень понял. Tickrate устанавливается в GameField, а обработка объектов идет в PhysObj. Чья же тогда это работа?
Clamp #17 - 1 год назад (отредактировано ) 0
обработка объектов идет в PhysObj
В нём писывается логика движения, однако свой .update() он самостоятельно никаким образом не вызывает.

Кстати, технически вектор "impulse" выражает m*v, поскольку в карте-примере из поста масса фактически не имеет значения (видимо, просто забыл удалить её из кода) и её хранение отдельным используемым скаляром приведёт только к увеличению числа рассчётов; при увеличении функционала, например, при добавлении взаимных столкновений, массу нужно будет вынести из вектора в отдельный скаляр, а вектор назвать "velocity". Показанная карта-пример, конечно, не лучшая из возможных, но, на мой взгляд, вполне адекватна (если оставить за скобками утверждение о полноценности модели в ней).

Попробую популярно пояснить лежащий в основе архитектуры подход.
Физический объект является описанием поведения игрового объекта (behavior; VisObj и DmgObj - тоже behavior по сути), он содержит в себе только те данные, которые касаются непосредственно связанного с физикой поведения игрового объекта (определение и движение объекта в пространстве), и никаких более. Этими данными являются: радиус-вектор, вектор импульса, радиус столкновений и логика движения. Последнюю в теории можно вынести из объекта, но тогда система несколько потеряет в модульности. Кроме того, хранить логику движения внутри метода полезно тем, что в случае её отсутствия/отключения у какого-либо объекта код движения даже читаться не будет.
Как абсолютно верно заметил Doc, результаты движения должны обрабатываться не в движимом теле или его компоненте, а в движущей системе, так как в результате перемещения игрового объекта он может вступить в те или иные взаимодействия с другими сущностями, обладающими физическим компонентом. Их сам по себе объект обработать не способен и не должен быть способен, он не "божественный".
Сам игровой объект - оболочка, через которую связаны все его компоненты. Система обработки ("стак" в GameField) обращается к нему с командой на апдейт, все взаимодействия между компонентами производятся в нём и нигде более.

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

Оптимизация это вообще дело последнее, которое выполняется уже после реализации всего, что хотели реализовать, поскольку добавлять что-нибудь еще к оптимизированному коду - это чистое мучение.
В карте-примере отсутствует преждевременная оптимизация. Ощущение её наличия могло возникнуть из-за того, что данные, которые никак не задействованы в логике примера, были удалены из неё целиком. Массу вот только пропустил.
Это сообщение удалено