Весьма распространенная практика.
Продуктовая компания+мобильный геймдев.
Зависит от жанра игры, если игра одиночная, то для нее постоянно надо добавлять контент, ибо пользователи его "выжигают" и покидают игру.
Это самый ад ибо тогда кранчи тянутся из спринта в спринт.
Спасибо большое за привлечение внимания к данной теме.
Выскажу свое мнение. В большинстве случаев главная причина и проблема овертаймов в том, что они не оплачиваются. Это влечет за собой то, что менеджмент не видит в этом никакой проблемы. Зачем решать проблему овертаймов, если компания от этого имеет прибыль, а менеджер имеет свои плюсики и баллы от начальства, тк его подопечные более результативны. Обратил внимание, как в статье, так и в жизни, в большинстве случаев кранчи вам будут оправдывать люди, не принимающие в них непосредственное участие. Да, те самые плохие менеджеры. Да и довольно сложно представить себе овертаймящего менеджера, что он/она будет делать? Митинги до глубокой ночи? Из последних сил заполнять капасити матрицы? Рассылать письма об успехах на передовых по субботам? Я так не думаю.
И, да, отдельное спасибо работникам, которые готовы овертаймить за спасибо, даже тем, кто находится на довольны высоких позициях. Какая бы там лапша на ушах у вас не висела: общее дело, командный дух, сегодня овертаймишь, а через неделю мы все будем отдыхать, в общем нужное подчеркнуть.
Увы, но пример вообще ни к месту. И таких мест по коду не "достаточно много", а одно - это функция IEnumerator Climbing(), где подряд делается три этапа перемещения, которые можно вынести в отдельный метод и это пойдёт только на пользу. Но меня смутило, что это происходит в рамка корутины и я не стал с этим разбираться. Я бы не стал резко реагировать, но ведь вы не могли оставить конкретно меня и мои навыки без своих оценочных суждения.
"Нормальные кодеры так не делают" - оскорбление чересчур очевидное.
Их 8.
Видимо мне надо было написать "я мог бы сделать это циклом, но мне не кажется такой код более читаемым, особенно в рамках статьи". Вы бы сами почитали и посчитали сначала. 20 строк тут не будет, потому что перебирается только 8 чисел. т.к. у параллелепипеда 8 углов. Если бы их было 20 то это было бы совсем другая ситуация. И да. Вот так код будет выглядеть, если его написать циклом:
for (int i = 0; i < 8; i++)
{
int coefX = (i & (1 << 0)) == 0 ? 1 : -1;
int coefY = (i & (1 << 1)) == 0 ? 1 : -1;
int coefZ = (i & (1 << 2)) == 0 ? 1 : -1;
points[0] = gameObject.transform.TransformPoint(new Vector3(center.x + xSize / 2 * coefX, center.y + ySize / 2 * coefY, center.z + zSize / 2 * coefZ));
}
Да выбор в пользу 8-ми строк был сделан осознанно.
Вы правы. Я не обратил на него внимания. Но думаю, что данный материал будет интересен не только в рамках разработки на Unity. Логика будет работать и для других движков. Unity идеально подходит для быстрого создания POC-а.
Сейчас попробую найти подходящий пример использования и добавлю.
Доброго времени суток! Нашёл баг в решении. Хочу поделится правильным решением. Ошибка в методе private void ShakeRotateCameraInternal(...).
Должен выглядеть следующим образом:
private void ShakeRotateCameraInternal(Vector2 direction, float angleDeg, float degVelocity)
{
_degVelocity = degVelocity;
direction = direction.normalized;
direction *= Mathf.Tan(angleDeg * Mathf.Deg2Rad);
Vector3 resDirection = ((Vector3)direction + Vector3.forward).normalized;
_targetRotation = Quaternion.FromToRotation(Vector3.forward, resDirection);
}
Прошу прощение за кривой формат кода.
По сути вместо transform.forward нужно использовать Vector3.forward, потому что именно таким будет направление взгляда локальное.
Для меня проблема в том, что Вы рубите с плеча. Что значит трясти ствол и не трясти камеру? Подскакивает прицел, который всегда по центру, значит подскакивает и сама камера, ведь так? Помимо этого, отдача, как и скорострельность, безумно важные понятия с точки зрения геймдизайна и баланса. Чем мощнее оружие, тем больше у него будет отдача и ударное вращение после выстрела. Урон от выстрела больше, значит и больше расплата за него.
По поводу "разработчиков". Ну вы так или иначе сказали это с издёвкой. Это как в статье на тему медицины написать "доктор". Подобное пренебрежение тут неуместно.
Чаще всего вот такие очень редкие случаи свидетельствуют о том, что баг репорт составлен не совсем корректно. То есть произошла какая-то ошибка, не связанная с данным тестовым сценарием. И данный сценарий он будет мешать разработчику найти реальную проблему, ибо над ним всё время будет висеть то, что надо подпрыгнуть 23 раза и тд. И это действительно большая проблема. И бывают такие ситуации, что что-то на этапе тетстирования не учтено Вы решаете, что это редко и чёрт с ним, отдаёте пользователям, а потом данный баг воспроизводится у каждого 3-го юзера.
Бывают редкие баги, которые возникают в очень редких случаях и если их исправлять то надо внести много изменений и скорее всего выгоднее такой баг не исправлять, а оставить всё как есть.
В общем, такие вот очень редкие и дико непонятные баги бывают, но чаще всего они даже и не связаны с предоставленным тестовым сценарием.
Ну и возникновение ошибки при частых повторениях чего-либо свидетельствует о том, что логика слабо заточена под частые вызовы, такая проблема была с моей предыдущей реализацией тряски камеры и бывает, что она у тестировщика воспроизвелась на 23-й раз, просто к 23-му разу он стал это делать гораздо чаще. Либо же ещё, если Вы выделяете какие-то ресурсы на каждый прыжок, но чистите их некорректно, у вас может произойти утечка памяти, и там будут уже обращения к запрещённым областям и тд.
Извиняюсь за сумбур, постарался покрыть как можно больше ситуаций.
Если честно, на втором видео складывается ощущение, что её вовсе нет(ну я только минут посмотрел).
Слева очень годно, авторы играют со смещением по позиции, что довольно опасно для 3D игр. Но в данной ситуации риск оправдан ибо выглядит отлично.
"Игра влетает потому что буфер обмена переполнен спрайтами взрыва" - это из Брандашмыга? Самое частое, что я вижу это access violation. Если по-простому, то попытка получить доступ к памяти, к которой доступа нет. Чаще всего возникает при работе с C или С++. Например у вас есть указатель на некую область памяти, а доступа к ней нет по разным причинам, забыли выделить память, забыли, что очистили и тд. Обратились в запрещённую область - приложение умерло. так же может да, не хватить памяти, программа попыталась выделить часть оперативной памяти, однако ресурсов ни в физической памяти ни в файле подкачки не хватает, бросается исключение мол не удалось, исключение не обрабатывается, это приводит к краху.
Ну и не обработанные исключения, этим болеют и более новые языки программирования, обратились к какой функции и передали ей неправильный параметр, она отреагировала на него исключением, которые вы должны обработать(перехватить) не перехватили или не обработали, получили упавшее приложение, то есть вылет на рабочий стол.
Ну не будут же разработчики добавлять загрузку, если вы предположим стоите на месте и повернулись влево на 10 градусов. То, что не попадает в кадр, то и не рисуется. Я видел странное решение в RE7, у меня тогда был довольно слабый ноутбук и игра пыталась подгружать кадры наперёд, когда я ворочал камеру. То есть вроде как и нет подгрузки текстур, но в то же время я мышку дёрнул -> пауза -> камера плавно доехала до нужной точки. Вроде как и фпс не падает и подгрузки нет, а играть всё равно невозможно.
Ох, ну тут может быть несколько причин. Одна лучше другой. Самое очевидное - плохая оптимизация. Не самая очевидная слабость ресурса Вашей машины и невозможность на ней заставить графику работать корректно. Так или иначе будут такие ситуации, когда игра не сможет забегать на любом железе. Но самое в этом плохое, что такие ситуации должны решаться системными требованиями. Например Вы смотрите, что игра слишком требовательная для вашего железа и её не покупаете. И Бизнес, отвечающий за продажи игры, намерено занижает минимальные системные требования игры, дабы игра продалась большему количеству людей.
Если вы это видете, это зло. Еще на заре зарождения 3D Кармак придумал "Двоичное разбиение пространства". Которое в итоге сводится к тому, что то, чего не видите, то и не рисуется. Ибо ресурсы видео памяти не безграничные.
Но, если вы видите подгрузку, значит что-то пошло не так.
Не думаю, что шутеры были бы лучше без отдачи оружия. Вы воспринимаете понятие тряска чересчур буквально. Я специально разделил на два понятия, вибрация и ударное вращение.
Мне кажется вы воспринимаете понятие тряска чересчур буквально. Для этого я выделил два понятия в статье. Тряска и ударное вращение. Хорошо тряска камеры не передаёт отдачу. Она её делает. как сделать подкидывание прицела после выстрела и последующее его возвращение на место без ударного вращения? И да, у меня "камера" в жизни трясётся. Если я к примеру выстрелю из огнестрельного оружия то, что я вижу тряхнётся, а ствол оружия подкинет вверх. Если я прыгну с большой высоты мою "камеру" тоже тряхнёт.
Я в этом не уверен. Но думаю в случае некоторых игр данный эффект призван скрыть недостатки движка, например видимая подгрузка текстур при резком повороте камеры. Таким очень страдал Rage и его id Tech 5.
Я сам небольшой фанат моушн блюра. Само по себе размытие(расфокус), если к месту, не такой уж и плохой эффект. Например в NFS Shift неплохо передавалось ощущение того, что водитель был оглушен после аварии. Неплохо работало в паре с тряской.
Не пойму, к чему тут эти кавычки. А сказали мне об этом тенденции развития индустрии. Любой инструмент, если его использовать чересчур активно, будет раздражать. Я же не говорю о вибрации постоянной, либо с большим разбросом углов, что сделает картинку дёрганной. Как вы будете передавать игроку отдачу оружия или ощущение падения? В видео я привожу в пример геймплей CS 1.6, где данные механизмы используются в меру и к месту. Вы считаете игра была бы лучше без них?
Дело в том, что она только в примере пустая. Просто из-за того, что надо как-то уместить материал в статью. Если речь идёт об играх, то это были какие-то объекты, которые бы имели возможность получать отклики например от движка.
Плюс добавляется возможность например поиска по иерархии, по полному имени например, чтобы не было "горизонтальных" связей. В общем тут уже в зависимости от того, что делаете)
А. решается задача объединения всех объектов в единую структуру от большего к меньшему. То есть что нужно написать, чтобы затем можно было добавлять некий объект как поле и он с небольшим оверхедом добавлялся в иерархию. Если исключить передачу имени переменной, то даже и макрос не понадобится. Тем не менее не всем понравилось.
Попробую всё разложить ещё раз. Частично скопировано с другого комментария.
ну смотрите, я там разбирал порядок вызовов конструкторов. Больше всего мне порядок вызова конструкторов базовых классов иерархии напоминает рекуррентный обход дерева. То есть мы от корня спускаемся к листьям, то бишь к конструкторам объектов-полей.
получается попав в конструктор мы помещаем себя на вершину стека, попав в другой конструктор(это будет уже объект-поле), обратившись к вершине стека мы получим указатель на нашего "родителя" в иерархии. Опять же разбирая порядок вызова, я показывал, что в очередном поле, попав непосредственно в конструктор данного поля(или же его класса) Мы должны снять себя с вершины стека, ведь конструкторы всех полей уже были вызваны, а значит "детей" больше не осталось. Как понять, что это уже конструктор непосредственно самого класса-наследника? Для этого мы и заводим временный объекта класса NodeName. Который живёт всю цепочку вызовов конструкторов. То есть мы пытаемся вызвать конструктор класса наследника, создаём временный объект, пока мы бегаем по базовым конструкторам и конструкторам полей, объект NodeName существует. А будет он разрушен по выходу из конструктора класса наследника, ну получается что мы снимаем себя не в момент попадания в конструктор, а на момент выхода из него(конструктора класса-наследника) и для этого во временном объекте в деструкторе NodeName мы снимаем указатель с вершины стека. И стек специально на пустоту не проверяется, чтобы приложение упало, если иерархия будет составлена неправильно.
"У вас есть общий стек объектов..." - да стоило добавить синхронизацию на стек.
"Я несколько раз перечитал вашу статью, и так и не понял цели всех этих манипуляций." - ну смотрите, я там разбирал порядок вызовов конструкторов, думаю тут Вам объяснять не надо. Больше всего мне порядок вызова конструкторов базовых классов иерархии напоминает рекуррентный обход дерева. То есть мы от корня спускаемся к листьям, то бишь к конструкторам объектов-полей.
получается попав в конструктор мы помещаем себя на вершину стека, попав в другой конструктор(это будет уже объект-поле), обратившись к вершине, стека мы получим указатель на нашего "родителя" в иерархии. Опять же разбирая порядок вызова, я показывал, что в очередном поле, попав непосредственно в конструктор данного поля(или же его класса) Мы должны снять себя с вершины стека, ведь конструкторы всех полей уже были вызваны, а значит "детей" больше не осталось. Как понять, что это уже конструктор непосредственно самого класс-наследника. Для этого мы и заводим временный объекта класса NodeName. Который живёт всю цепочку вызовов конструкторов. ТО есть мы пытаемся вызвать конструктор класса наследника, создаём временный объект, пока мы бегаем по базовым конструкторам и конструкторам полей, Объект NodeName существует. А будет он разрушен по выходу из конструктора класса наследника, ну получается что мы снимаем себя не в момент попадания в конструктор, а на момент выхода из него(конструктора класса-наследника) и для этого во временном объекте в деструкторе NodeName мы снимаем указатель с вершины стека. И стек специально на пусто не проверяется, чтобы приложение упало, если иерархия будет составлена неправильно.
Не могу не отреагировать на один момент, потому что тут именно Вы вгоняете людей в заблуждение. Проект в один поток = простой проект и ему не нужен C++. Очень странная попытка ударить по моей компетентности. Видимо Вы не знаете, что бывают ситуации когда проект должен быть однопоточным, если речь, например, идёт о симуляции. И длинных по времени тестах (длиной в несколько суток) и главное, что эти тесты должны быть полностью воспроизводимы каждый запуск. Чего очень сложно достичь в многопоточном приложении. И предваряя ваш вопрос о том, что это за однопоточная симуляция ведь железо работает параллельно и тд. Ну так многопоточность тоже симулировалась используя boost::context. Странно, что Вам это всё нужно объяснять, ведь вы ТАК давно в этой сфере.
Признаю два пробела своей статьи. Ошибкой было использовать std::string. Ну и можно было бы сделать работу со стеком потокобезопасной. Вообще подобная агрессивная полемика больше напоминает разговор за столом о важнейших темах типа политики и у меня нет желания принимать в подобном участие. Если Вы хотели что-то доказать мне, то пенять на мою профнепригодность плохая идея. С чего вообще Вы решили, что я перед Вами оправдываюсь, вы считаете что это плохое решение, однако я с этим не согласен, поскольку решение хорошее не потому, что оно просто работает, а потому что оно хорошо себя зарекомендовало на практике. Видимо, Вы не очень привыкли, когда Вам что-то спокойно пытаются объяснить, а не стрелять тезисами в духе: "Твой софт - говно". Не смотря на всё то, что Вы написали, я считаю, что продемонстрированный мной подход с алгоритмической точки зрения себя более чем оправдывает и подходит для описанных мной кейсов. Да, именно для тех кейсов, когда происходит создал-удалил последовательность, ведь между этими двумя действиями просто не может быть ничего чуть более комплексного.
Свои советы по поводу того, что писать, кому писать и для кого я настоятельно рекомендую Вам оставить при себе. Очень сложно из вашего потока желчи выловить что-то полезное.
На этом я желаю поставить точку в этом споре. Ибо мне не о чем больше с Вами разговаривать.
Если речь идёт о шаблоне, который будет полностью переносим. То обязательно. Я думаю отличным примером этого является STL. Есть один вариант, это объявить все типы, которые будут использовать данный шаблон в файле реализации, однако это будет работать только в рамках вашего проекта. Если речь идёт о создании шаблона "под ключ" - то, как бы это ни было печально, всё придётся писать в хидере.
Заморочки есть только с удалением временного объекта. Для того, чтобы определить нужный момент для снятия объекта иерархии с вершины стека. Ведь так можно определить момент вызова конструктора класса-наследника, не модифицируя его.
"Зачем вообще нужен общий стек объектов?" - в статье написано. На вершине стека лежит объект, в дети к которому надо добавить текущий объект.
"Потокобезопасность, как я понял, тут и не предполагается?" — в каком контексте тут необходима потокобезопасность? О каких разделяемых ресурсах идёт речь? В данной ситуации можно обернуть только инициализацию "главного" объекта. Однако в рамках статьи это было бы излишним.
Если вы хотите дать совет, не стоит начинать комментарий с фразы "эталон бесполезной статьи". но я Вам подыграю.
Эталон бесполезного комментария.
"Человек, знающий язык, поймёт, что ты написал лютый бред, да ещё и крайне плохо." - тоже весьма конструктивно. Данное решение успешно работает и работало на двух проектах, находящихся в продакшне, и оно себя отлично зарекомендовало. Более того, я заметил недостаток отсутствия этого подхода на проекте, работающем по такому же принципу, но без автоматического построения иерархии.
Макрос для объявления поля - не такая уж и большая плата, и иерархия защищена от неправильного построения.
"Я б прошел мимо ничего не сказав, но к сожалению есть риск, что кто-то посередине прочитает статью и подумает, что всё написанное в ней — хорошая идея." - в статье был описан определённый класс задач и архитектур, для которых это хорошая идея.
"так ещё и из каждого надо потом выбивать подобную ересь, которую он где-то в интернетах прочитал." - это конкретно ваша проблема, не стоит всем её вываливать.
std::forward нужен для передачи универсальных ссылок. std::string вполне комфортно копируется и передаются по константным ссылкам. Ибо существовал задолго до появления std::forward(C++11)
C макросом, который я использую для инициализации, только этот конструктор(const char* name) и будет вызываться. std::string оставлен для консистентности, ибо я храню с помощью std::string
Да, что-то я тут на эмоциях рубанул с плеча. Неправильно это. Думаю, все зависит от всего, от менеджмента, от разделения менеджеров и разработчиков(куа и тд.) от политики компании и прочее, от самих людей, естественно. Но, так или иначе, в большинстве случаев тот, кто говорит, что овертаймы неизбежны и тд. Тот сам не особо то и в курсе, что это такое и насколько это может быть трудно и вредно.